202 lines
5.6 KiB
JavaScript
202 lines
5.6 KiB
JavaScript
import m from "https://esm.sh/mithril@2.3.8";
|
|
|
|
const API = "/api/v1/flashcards";
|
|
|
|
const Card = {
|
|
view: function (v) {
|
|
const c = v.attrs.card;
|
|
const maxNote =
|
|
(c.note || "").length > 120 ? c.note.slice(0, 120) + "…" : c.note;
|
|
return m(
|
|
".cyber-card.cyber-card--interactive.cyber-neon-border.cyber-mb-md",
|
|
{
|
|
onclick: () => m.route.set("/flashcard/" + c.id),
|
|
style: "cursor:pointer",
|
|
},
|
|
[
|
|
c.tags?.length
|
|
? m(
|
|
".cyber-card__header",
|
|
c.tags.map((t) =>
|
|
m("span.cyber-badge.cyber-badge--pulse.cyber-mr-xs", t),
|
|
),
|
|
)
|
|
: null,
|
|
m(".cyber-card__body", [
|
|
m(".cyber-text-muted.cyber-font-mono", `#${c.id} v${c.version}`),
|
|
m(
|
|
"p.cyber-text-primary.cyber-mt-sm",
|
|
maxNote || m("em.cyber-text-muted", "no content"),
|
|
),
|
|
]),
|
|
],
|
|
);
|
|
},
|
|
};
|
|
|
|
const PAGE_SIZE = 20;
|
|
|
|
const Pager = {
|
|
view: function (v) {
|
|
const { page, total } = v.attrs;
|
|
if (total <= 1) return null;
|
|
return m(
|
|
".cyber-flex.cyber-justify-center.cyber-items-center.cyber-gap-sm.cyber-my-md",
|
|
[
|
|
m(
|
|
"button.cyber-btn.cyber-btn--sm" +
|
|
(page <= 1 ? ".cyber-btn--ghost" : ""),
|
|
{ disabled: page <= 1, onclick: () => v.attrs.onpage(page - 1) },
|
|
"\u2190",
|
|
),
|
|
m("span.cyber-font-mono.cyber-text-muted", `${page} / ${total}`),
|
|
m(
|
|
"button.cyber-btn.cyber-btn--sm" +
|
|
(page >= total ? ".cyber-btn--ghost" : ""),
|
|
{ disabled: page >= total, onclick: () => v.attrs.onpage(page + 1) },
|
|
"\u2192",
|
|
),
|
|
],
|
|
);
|
|
},
|
|
};
|
|
|
|
const List = {
|
|
oninit: async function (v) {
|
|
v.state.cards = [];
|
|
v.state.loading = true;
|
|
const res = await fetch(API);
|
|
const data = await res.json();
|
|
v.state.cards = data.flashcards || [];
|
|
v.state.loading = false;
|
|
v.state.page = 1;
|
|
m.redraw();
|
|
},
|
|
view: function (v) {
|
|
if (v.state.loading) {
|
|
return m(".cyber-center-xy", [
|
|
m(".cyber-spinner.cyber-spinner--lg"),
|
|
m("p.cyber-text-muted.cyber-mt-md", "loading…"),
|
|
]);
|
|
}
|
|
const totalPages = Math.max(1, Math.ceil(v.state.cards.length / PAGE_SIZE));
|
|
const page = Math.min(v.state.page, totalPages);
|
|
const slice = v.state.cards.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
|
|
return m(".cyber-p-lg", { style: "max-width:720px;margin:0 auto" }, [
|
|
m(Pager, {
|
|
page,
|
|
total: totalPages,
|
|
onpage: (n) => {
|
|
v.state.page = n;
|
|
},
|
|
}),
|
|
m(
|
|
".cyber-scanlines",
|
|
slice.map((c) => m(Card, { card: c })),
|
|
),
|
|
m(Pager, {
|
|
page,
|
|
total: totalPages,
|
|
onpage: (n) => {
|
|
v.state.page = n;
|
|
},
|
|
}),
|
|
]);
|
|
},
|
|
};
|
|
|
|
const Detail = {
|
|
oninit: async function (v) {
|
|
v.state.entry = null;
|
|
v.state.loading = true;
|
|
try {
|
|
const res = await fetch(`${API}/${v.attrs.id}`);
|
|
if (res.ok) v.state.entry = await res.json();
|
|
} catch {}
|
|
v.state.loading = false;
|
|
m.redraw();
|
|
},
|
|
view: function (v) {
|
|
if (v.state.loading) {
|
|
return m(".cyber-center-xy", m(".cyber-spinner.cyber-spinner--lg"));
|
|
}
|
|
const e = v.state.entry;
|
|
if (!e) {
|
|
return m(".cyber-p-lg", { style: "max-width:720px;margin:0 auto" }, [
|
|
m(
|
|
"button.cyber-btn.cyber-btn--ghost",
|
|
{ onclick: () => m.route.set("/") },
|
|
"\u2B05 Back",
|
|
),
|
|
m("p.cyber-text-magenta", "Flashcard not found."),
|
|
]);
|
|
}
|
|
return m(".cyber-p-lg", { style: "max-width:720px;margin:0 auto" }, [
|
|
m(
|
|
"button.cyber-btn.cyber-btn--ghost.cyber-mb-md",
|
|
{ onclick: () => m.route.set("/") },
|
|
"\u2B05 Back",
|
|
),
|
|
m(".cyber-card.cyber-card--holo", [
|
|
m(".cyber-card__header", [
|
|
m("span.cyber-font-mono.cyber-text-cyan", `#${e.id} v${e.version}`),
|
|
]),
|
|
m(".cyber-card__body", [
|
|
m(".cyber-field", [m(".cyber-field__label", "Note"), m("p", e.note)]),
|
|
e.host
|
|
? m(".cyber-field.cyber-mt-sm", [
|
|
m(".cyber-field__label", "Host"),
|
|
m("p", e.host),
|
|
])
|
|
: null,
|
|
e.user
|
|
? m(".cyber-field.cyber-mt-sm", [
|
|
m(".cyber-field__label", "User"),
|
|
m("p", e.user),
|
|
])
|
|
: null,
|
|
e.context
|
|
? m(".cyber-field.cyber-mt-sm", [
|
|
m(".cyber-field__label", "Context"),
|
|
m("p", e.context),
|
|
])
|
|
: null,
|
|
e.repos?.length
|
|
? m(".cyber-field.cyber-mt-sm", [
|
|
m(".cyber-field__label", "Repos"),
|
|
m(
|
|
"div",
|
|
e.repos.map((r) => m("span.cyber-badge.cyber-mr-xs", r)),
|
|
),
|
|
])
|
|
: null,
|
|
e.tags?.length
|
|
? m(".cyber-field.cyber-mt-sm", [
|
|
m(".cyber-field__label", "Tags"),
|
|
m(
|
|
"div",
|
|
e.tags.map((t) => m("span.cyber-badge.cyber-mr-xs", t)),
|
|
),
|
|
])
|
|
: null,
|
|
m(".cyber-field.cyber-mt-sm", [
|
|
m(".cyber-field__label", "Created"),
|
|
m("p", e.created),
|
|
]),
|
|
e.delete_timestamp
|
|
? m(".cyber-field.cyber-mt-sm", [
|
|
m(".cyber-field__label", "Deleted"),
|
|
m("p", e.delete_timestamp),
|
|
])
|
|
: null,
|
|
]),
|
|
]),
|
|
]);
|
|
},
|
|
};
|
|
|
|
m.route(document.getElementById("app"), "/", {
|
|
"/": List,
|
|
"/flashcard/:id": Detail,
|
|
});
|