diff --git a/docs/index.html b/docs/index.html index ccad282..f4ca165 100644 --- a/docs/index.html +++ b/docs/index.html @@ -51,7 +51,7 @@

fledge-plugin-hub

Local web dashboard for browsing and managing fledge templates, plugins, lanes, and config

✓ official - v0.5.0 + v0.6.0 TypeScript
diff --git a/package.json b/package.json index 3a625bf..f5d0772 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fledge-plugin-hub", - "version": "0.5.0", + "version": "0.6.0", "description": "Local web dashboard for browsing and managing fledge templates, plugins, lanes, and config", "type": "module", "scripts": { diff --git a/plugin.toml b/plugin.toml index 21a1ed7..2c8f19d 100644 --- a/plugin.toml +++ b/plugin.toml @@ -1,6 +1,6 @@ [plugin] name = "fledge-plugin-hub" -version = "0.5.0" +version = "0.6.0" description = "Local web dashboard for browsing and managing fledge templates, plugins, lanes, and config" author = "CorvidLabs" diff --git a/public/app.js b/public/app.js index 0bf2bef..a1917d2 100644 --- a/public/app.js +++ b/public/app.js @@ -1,6 +1,8 @@ const $ = (sel) => document.querySelector(sel); const $$ = (sel) => document.querySelectorAll(sel); +// --- API --- + async function api(path, opts) { try { const res = await fetch(`/api${path}`, opts); @@ -18,24 +20,10 @@ async function postJSON(path, body) { }); } -function navigate(page) { - $$(".page").forEach((p) => p.classList.remove("active")); - $$(".nav-link").forEach((l) => l.classList.remove("active")); - $(`#page-${page}`)?.classList.add("active"); - $(`.nav-link[data-page="${page}"]`)?.classList.add("active"); - loaders[page]?.(); -} +// --- Helpers --- function html(el, content) { el.innerHTML = content; } -function renderError(msg) { - return `
${escape(msg)}
`; -} - -function renderEmpty(msg) { - return `
${msg}
`; -} - function escape(s) { if (s == null) return ""; const d = document.createElement("div"); @@ -43,24 +31,9 @@ function escape(s) { return d.innerHTML; } -function trustBadge(source, tier) { - const t = (tier || "").toLowerCase(); - if (t === "official") return 'official'; - if (t === "team") return 'team'; - if (t === "unverified") return 'community'; - if (!source) return ""; - return source.toLowerCase().startsWith("corvidlabs/") - ? 'official' - : 'community'; -} - -function typeBadge(topics) { - if (!topics) return ""; - if (topics.includes("fledge-plugin")) - return 'plugin'; - if (topics.includes("fledge-template")) - return 'template'; - return ""; +function truncate(s, n) { + s = String(s ?? ""); + return s.length > n ? s.slice(0, n) + "…" : s; } function timeAgo(dateStr) { @@ -68,22 +41,100 @@ function timeAgo(dateStr) { const diff = Date.now() - new Date(dateStr).getTime(); const days = Math.floor(diff / 86400000); if (days < 1) return "today"; - if (days === 1) return "1 day ago"; - if (days < 30) return `${days} days ago`; + if (days === 1) return "1d ago"; + if (days < 30) return `${days}d ago`; const months = Math.floor(days / 30); - if (months === 1) return "1 month ago"; - if (months < 12) return `${months} months ago`; + if (months < 12) return `${months}mo ago`; return `${Math.floor(months / 12)}y ago`; } +function renderError(msg) { + return `
${escape(msg)}
`; +} + +function renderEmpty(msg) { + return `
${msg}
`; +} + +// --- Markdown renderer --- + +function renderMarkdown(raw) { + if (!raw) return ""; + let text = raw; + + // Fenced code blocks + text = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => + `
${escape(code.trim())}
` + ); + + // Inline code (protect from further processing) + const codeMap = []; + text = text.replace(/`([^`]+)`/g, (_, code) => { + const id = `\x00CODE${codeMap.length}\x00`; + codeMap.push(`${escape(code)}`); + return id; + }); + + // Headings + text = text.replace(/^####\s+(.+)$/gm, "

$1

"); + text = text.replace(/^###\s+(.+)$/gm, "

$1

"); + text = text.replace(/^##\s+(.+)$/gm, "

$1

"); + text = text.replace(/^#\s+(.+)$/gm, "

$1

"); + + // Bold / italic + text = text.replace(/\*\*(.+?)\*\*/g, "$1"); + text = text.replace(/(?$1"); + + // Links + text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, + '$1' + ); + + // Images (render as links since we're in a modal) + text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, + '[$1]' + ); + + // Horizontal rules + text = text.replace(/^---+$/gm, "
"); + + // Blockquotes + text = text.replace(/^>\s*(.+)$/gm, "
$1
"); + + // List items + text = text.replace(/^[-*]\s+(.+)$/gm, "
  • $1
  • "); + + // Wrap consecutive
  • in