From 4473a6e3d06884580140d6110eb2e2c6597c98eb Mon Sep 17 00:00:00 2001 From: didier amyot Date: Mon, 8 Jun 2026 08:54:01 -0400 Subject: [PATCH] feat: improved front-end --- public/app.js | 185 ++++++++++++++++++++++++++++------------------ public/index.html | 11 ++- 2 files changed, 123 insertions(+), 73 deletions(-) diff --git a/public/app.js b/public/app.js index a6e747d..df3e26b 100644 --- a/public/app.js +++ b/public/app.js @@ -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)]), - e.host - ? m(".field", [m(".field-label", "Host"), m("p", e.host)]) - : null, - e.user - ? m(".field", [m(".field-label", "User"), m("p", e.user)]) - : null, - e.context - ? m(".field", [m(".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))), - ), - ]) - : null, - e.tags?.length - ? m(".field", [ - m(".field-label", "Tags"), - m( - "div", - e.tags.map((t) => m("span.nes-badge", m("span", t))), - ), - ]) - : null, - m(".field", [m(".field-label", "Created"), m("p", e.created)]), - e.delete_timestamp - ? m(".field", [ - m(".field-label", "Deleted"), - m("p", e.delete_timestamp), - ]) - : null, + 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, + ]), ]), - ]; + ]); }, }; diff --git a/public/index.html b/public/index.html index 5e6ac2d..a3f135c 100644 --- a/public/index.html +++ b/public/index.html @@ -1,13 +1,18 @@ - + St‑Pierre Flashcards + + -

coquelicot d'amour

-
+