feat: improved front-end
This commit is contained in:
1 parent
de6480ba4e
commit
4473a6e3d0
2 files changed
+109
-59
No files matched your search
+101
-56
@@ -4,7 +4,51 @@ const API = "/api/v1/flashcards";
|
||||
|
||||
const Card = {
|
||||
view: function (v) {
|
||||
return m("li", v.attrs.card.id);
|
||||
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",
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,92 +60,93 @@ const List = {
|
||||
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("h1", "loading");
|
||||
return m(".cyber-center-xy", [
|
||||
m(".cyber-spinner.cyber-spinner--lg"),
|
||||
m("p.cyber-text-muted.cyber-mt-md", "loading…"),
|
||||
]);
|
||||
}
|
||||
const cards = m(
|
||||
".cards-list",
|
||||
v.state.cards.map((c) => m(Card, { card: c })),
|
||||
);
|
||||
return m("ol", cards);
|
||||
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.flashcard = null;
|
||||
v.state.entry = null;
|
||||
v.state.loading = true;
|
||||
v.state.flashcard = await findFlashCardByID(v.attrs.id);
|
||||
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(".loading", m("i.nes-icon.nes-logo.is-small"));
|
||||
if (v.state.loading) {
|
||||
return m(".cyber-center-xy", m(".cyber-spinner.cyber-spinner--lg"));
|
||||
}
|
||||
const e = v.state.entry;
|
||||
if (!e)
|
||||
return [
|
||||
m(
|
||||
".back",
|
||||
m(
|
||||
"button.nes-btn",
|
||||
{ onclick: () => m.route.set("/") },
|
||||
"\u2B05 Back",
|
||||
),
|
||||
),
|
||||
m("p", "Flashcard not found."),
|
||||
];
|
||||
return [
|
||||
m(
|
||||
".back",
|
||||
m(
|
||||
"button.nes-btn.is-warning",
|
||||
{ onclick: () => m.route.set("/") },
|
||||
"\u2B05 Back",
|
||||
),
|
||||
),
|
||||
m(".detail-card.nes-container.is-rounded.with-title", [
|
||||
m("p.title", "#" + e.public_id + " v" + e.version),
|
||||
m(".field", [m(".field-label", "Note"), m("p", e.note)]),
|
||||
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(".field", [m(".field-label", "Host"), m("p", e.host)])
|
||||
? m(".cyber-field.cyber-mt-sm", [m(".cyber-field__label", "Host"), m("p", e.host)])
|
||||
: null,
|
||||
e.user
|
||||
? m(".field", [m(".field-label", "User"), m("p", e.user)])
|
||||
? m(".cyber-field.cyber-mt-sm", [m(".cyber-field__label", "User"), m("p", e.user)])
|
||||
: null,
|
||||
e.context
|
||||
? m(".field", [m(".field-label", "Context"), m("p", e.context)])
|
||||
? m(".cyber-field.cyber-mt-sm", [m(".cyber-field__label", "Context"), m("p", e.context)])
|
||||
: null,
|
||||
e.repos?.length
|
||||
? m(".field", [
|
||||
m(".field-label", "Repos"),
|
||||
m(
|
||||
"div",
|
||||
e.repos.map((r) => m("span.nes-badge", m("span", r))),
|
||||
),
|
||||
? 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(".field", [
|
||||
m(".field-label", "Tags"),
|
||||
m(
|
||||
"div",
|
||||
e.tags.map((t) => m("span.nes-badge", m("span", t))),
|
||||
),
|
||||
? 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(".field", [m(".field-label", "Created"), m("p", e.created)]),
|
||||
m(".cyber-field.cyber-mt-sm", [m(".cyber-field__label", "Created"), m("p", e.created)]),
|
||||
e.delete_timestamp
|
||||
? m(".field", [
|
||||
m(".field-label", "Deleted"),
|
||||
m("p", e.delete_timestamp),
|
||||
])
|
||||
? m(".cyber-field.cyber-mt-sm", [m(".cyber-field__label", "Deleted"), m("p", e.delete_timestamp)])
|
||||
: null,
|
||||
]),
|
||||
];
|
||||
]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+8
-3
@@ -1,13 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="cyber-theme">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>St‑Pierre Flashcards</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/cybercore-css/dist/cybercore.min.css" />
|
||||
<style>
|
||||
html, body { height: 100%; }
|
||||
body { background: var(--cyber-void-950); }
|
||||
#app { height: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>coquelicot d'amour</h1>
|
||||
<div class="flashcard-body" id="app"></div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user