From d1a8526d2d0b37056a9e3a01022539837f4d61b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Bachmann?= Date: Thu, 15 Jan 2026 11:57:43 +0000 Subject: [PATCH] . --- .../bahmcloud_store/panel/panel.js | 159 +++++++++++++----- 1 file changed, 117 insertions(+), 42 deletions(-) diff --git a/custom_components/bahmcloud_store/panel/panel.js b/custom_components/bahmcloud_store/panel/panel.js index f8c1b91..b9e5e07 100644 --- a/custom_components/bahmcloud_store/panel/panel.js +++ b/custom_components/bahmcloud_store/panel/panel.js @@ -15,6 +15,7 @@ class BahmcloudStorePanel extends HTMLElement { this._search = ""; this._category = "all"; + this._provider = "all"; // NEW: provider filter (all|github|gitea|gitlab|other|custom) this._detailRepoId = null; this._detailRepo = null; @@ -268,12 +269,20 @@ class BahmcloudStorePanel extends HTMLElement { .grid2{ display:grid; grid-template-columns: 1fr; gap: 12px; } @media (min-width: 900px){ .grid2{ grid-template-columns: 1.2fr 0.8fr; } } - .filters{ - display:flex; - gap: 10px; - flex-wrap: wrap; - align-items: center; - margin-bottom: 12px; + .filters{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-bottom:12px; } + .chips{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:12px; } + + .chip{ + display:inline-flex; align-items:center; gap:8px; + padding:6px 10px; border-radius:999px; + border:1px solid var(--divider-color); + background:var(--card-background-color); + cursor:pointer; user-select:none; font-weight:800; font-size:12px; + } + .chip strong{ font-size:12px; } + .chip.active{ + border-color:var(--bcs-accent); + box-shadow:0 0 0 2px color-mix(in srgb, var(--bcs-accent) 18%, transparent); } input, select{ @@ -494,6 +503,9 @@ class BahmcloudStorePanel extends HTMLElement { const repos = Array.isArray(this._data.repos) ? this._data.repos : []; const categories = this._computeCategories(repos); + // Provider stats (for overview) + const stats = this._computeProviderStats(repos); + const filtered = repos .filter((r) => { const q = (this._search || "").trim().toLowerCase(); @@ -504,6 +516,12 @@ class BahmcloudStorePanel extends HTMLElement { .filter((r) => { if (this._category === "all") return true; return (r.category || "").toLowerCase() === this._category; + }) + .filter((r) => { + if (this._provider === "all") return true; + if (this._provider === "custom") return r.source === "custom"; + const pv = (r.provider || "other").toLowerCase(); + return pv === this._provider; }); const options = [ @@ -516,6 +534,8 @@ class BahmcloudStorePanel extends HTMLElement { ), ].join(""); + const providerChips = this._renderProviderChips(stats); + const rows = filtered .map((r) => { const badge = @@ -547,19 +567,70 @@ class BahmcloudStorePanel extends HTMLElement { .join(""); return ` +
+
Providers
+
${providerChips}
+
+
- ${rows || `
No repositories configured.
`} + ${rows || `
No repositories match this filter.
`} `; } + _renderProviderChips(stats) { + const order = ["all", "github", "gitea", "gitlab", "other", "custom"]; + const labels = { + all: "All", + github: "GitHub", + gitea: "Gitea", + gitlab: "GitLab", + other: "Other", + custom: "Custom", + }; + const values = { + all: stats.total, + github: stats.github, + gitea: stats.gitea, + gitlab: stats.gitlab, + other: stats.other, + custom: stats.custom, + }; + + return order + .map((key) => { + const count = values[key] ?? 0; + const active = this._provider === key ? " active" : ""; + return `
${labels[key]} ${count}
`; + }) + .join(""); + } + + _computeProviderStats(repos) { + const s = { total: 0, github: 0, gitea: 0, gitlab: 0, other: 0, custom: 0 }; + if (!Array.isArray(repos)) return s; + + for (const r of repos) { + s.total += 1; + if (r.source === "custom") s.custom += 1; + + const p = (r.provider || "other").toLowerCase(); + if (p === "github") s.github += 1; + else if (p === "gitea") s.gitea += 1; + else if (p === "gitlab") s.gitlab += 1; + else s.other += 1; + } + return s; + } + _wireStore() { const root = this.shadowRoot; const search = root.getElementById("searchInput"); const cat = root.getElementById("categorySelect"); + const chips = root.getElementById("providerChips"); if (search) { search.addEventListener("input", (e) => { @@ -575,6 +646,17 @@ class BahmcloudStorePanel extends HTMLElement { }); } + if (chips) { + for (const c of chips.querySelectorAll("[data-prov]")) { + c.addEventListener("click", () => { + const key = c.getAttribute("data-prov"); + if (!key) return; + this._provider = key; // set filter + this._update(); + }); + } + } + for (const card of root.querySelectorAll("[data-repo]")) { card.addEventListener("click", () => { const id = card.getAttribute("data-repo"); @@ -666,18 +748,43 @@ class BahmcloudStorePanel extends HTMLElement { `; } + _wireDetail() { + const root = this.shadowRoot; + const mount = root.getElementById("readmePretty"); + if (!mount) return; + + if (this._readmeText) { + const html = this._mdToHtml(this._readmeText); + + if (html) { + mount.innerHTML = html; + this._postprocessRenderedMarkdown(mount); + return; + } + + if (this._readmeHtml) { + mount.innerHTML = this._readmeHtml; + this._postprocessRenderedMarkdown(mount); + return; + } + + mount.innerHTML = `
Markdown renderer not available on this client. Use “Show raw Markdown”.
`; + return; + } + + mount.innerHTML = ""; + } + _mdToHtml(markdown) { const md = String(markdown || ""); if (!md.trim()) return ""; - // Prefer HA's frontend libs if present const markedObj = window.marked; const domPurify = window.DOMPurify; let html = ""; try { - // marked can be a function or an object with parse() if (markedObj && typeof markedObj.parse === "function") { html = markedObj.parse(md, { breaks: true, gfm: true }); } else if (typeof markedObj === "function") { @@ -700,8 +807,6 @@ class BahmcloudStorePanel extends HTMLElement { _postprocessRenderedMarkdown(container) { if (!container) return; - - // Make links open in new tab try { const links = container.querySelectorAll("a[href]"); links.forEach((a) => { @@ -711,36 +816,6 @@ class BahmcloudStorePanel extends HTMLElement { } catch (_) {} } - _wireDetail() { - const root = this.shadowRoot; - const mount = root.getElementById("readmePretty"); - if (!mount) return; - - if (this._readmeText) { - const html = this._mdToHtml(this._readmeText); - - if (html) { - mount.innerHTML = html; - this._postprocessRenderedMarkdown(mount); - return; - } - - // Fallback: if backend provided sanitized HTML, use it - if (this._readmeHtml) { - mount.innerHTML = this._readmeHtml; - this._postprocessRenderedMarkdown(mount); - return; - } - - // Last resort - mount.innerHTML = `
Markdown renderer not available on this client. Use “Show raw Markdown”.
`; - return; - } - - // No readme text loaded - mount.innerHTML = ""; - } - _renderFabs() { const r = this._detailRepo; if (!r) return ""; @@ -879,4 +954,4 @@ class BahmcloudStorePanel extends HTMLElement { } } -customElements.define("bahmcloud-store-panel", BahmcloudStorePanel); +customElements.define("bahmcloud-store-panel", BahmcloudStorePanel); \ No newline at end of file