This commit is contained in:
2026-01-15 11:57:43 +00:00
parent f60b3a8730
commit d1a8526d2d

View File

@@ -15,6 +15,7 @@ class BahmcloudStorePanel extends HTMLElement {
this._search = ""; this._search = "";
this._category = "all"; this._category = "all";
this._provider = "all"; // NEW: provider filter (all|github|gitea|gitlab|other|custom)
this._detailRepoId = null; this._detailRepoId = null;
this._detailRepo = null; this._detailRepo = null;
@@ -268,12 +269,20 @@ class BahmcloudStorePanel extends HTMLElement {
.grid2{ display:grid; grid-template-columns: 1fr; gap: 12px; } .grid2{ display:grid; grid-template-columns: 1fr; gap: 12px; }
@media (min-width: 900px){ .grid2{ grid-template-columns: 1.2fr 0.8fr; } } @media (min-width: 900px){ .grid2{ grid-template-columns: 1.2fr 0.8fr; } }
.filters{ .filters{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-bottom:12px; }
display:flex; .chips{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:12px; }
gap: 10px;
flex-wrap: wrap; .chip{
align-items: center; display:inline-flex; align-items:center; gap:8px;
margin-bottom: 12px; 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{ input, select{
@@ -494,6 +503,9 @@ class BahmcloudStorePanel extends HTMLElement {
const repos = Array.isArray(this._data.repos) ? this._data.repos : []; const repos = Array.isArray(this._data.repos) ? this._data.repos : [];
const categories = this._computeCategories(repos); const categories = this._computeCategories(repos);
// Provider stats (for overview)
const stats = this._computeProviderStats(repos);
const filtered = repos const filtered = repos
.filter((r) => { .filter((r) => {
const q = (this._search || "").trim().toLowerCase(); const q = (this._search || "").trim().toLowerCase();
@@ -504,6 +516,12 @@ class BahmcloudStorePanel extends HTMLElement {
.filter((r) => { .filter((r) => {
if (this._category === "all") return true; if (this._category === "all") return true;
return (r.category || "").toLowerCase() === this._category; 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 = [ const options = [
@@ -516,6 +534,8 @@ class BahmcloudStorePanel extends HTMLElement {
), ),
].join(""); ].join("");
const providerChips = this._renderProviderChips(stats);
const rows = filtered const rows = filtered
.map((r) => { .map((r) => {
const badge = const badge =
@@ -547,19 +567,70 @@ class BahmcloudStorePanel extends HTMLElement {
.join(""); .join("");
return ` return `
<div class="card">
<div><strong>Providers</strong></div>
<div class="chips" id="providerChips">${providerChips}</div>
</div>
<div class="filters"> <div class="filters">
<input id="searchInput" placeholder="Search repositories…" value="${this._esc(this._search)}" /> <input id="searchInput" placeholder="Search repositories…" value="${this._esc(this._search)}" />
<select id="categorySelect">${options}</select> <select id="categorySelect">${options}</select>
</div> </div>
${rows || `<div class="card">No repositories configured.</div>`} ${rows || `<div class="card">No repositories match this filter.</div>`}
`; `;
} }
_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 `<div class="chip${active}" data-prov="${key}"><strong>${labels[key]}</strong> ${count}</div>`;
})
.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() { _wireStore() {
const root = this.shadowRoot; const root = this.shadowRoot;
const search = root.getElementById("searchInput"); const search = root.getElementById("searchInput");
const cat = root.getElementById("categorySelect"); const cat = root.getElementById("categorySelect");
const chips = root.getElementById("providerChips");
if (search) { if (search) {
search.addEventListener("input", (e) => { 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]")) { for (const card of root.querySelectorAll("[data-repo]")) {
card.addEventListener("click", () => { card.addEventListener("click", () => {
const id = card.getAttribute("data-repo"); 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 = `<div class="muted">Markdown renderer not available on this client. Use “Show raw Markdown”.</div>`;
return;
}
mount.innerHTML = "";
}
_mdToHtml(markdown) { _mdToHtml(markdown) {
const md = String(markdown || ""); const md = String(markdown || "");
if (!md.trim()) return ""; if (!md.trim()) return "";
// Prefer HA's frontend libs if present
const markedObj = window.marked; const markedObj = window.marked;
const domPurify = window.DOMPurify; const domPurify = window.DOMPurify;
let html = ""; let html = "";
try { try {
// marked can be a function or an object with parse()
if (markedObj && typeof markedObj.parse === "function") { if (markedObj && typeof markedObj.parse === "function") {
html = markedObj.parse(md, { breaks: true, gfm: true }); html = markedObj.parse(md, { breaks: true, gfm: true });
} else if (typeof markedObj === "function") { } else if (typeof markedObj === "function") {
@@ -700,8 +807,6 @@ class BahmcloudStorePanel extends HTMLElement {
_postprocessRenderedMarkdown(container) { _postprocessRenderedMarkdown(container) {
if (!container) return; if (!container) return;
// Make links open in new tab
try { try {
const links = container.querySelectorAll("a[href]"); const links = container.querySelectorAll("a[href]");
links.forEach((a) => { links.forEach((a) => {
@@ -711,36 +816,6 @@ class BahmcloudStorePanel extends HTMLElement {
} catch (_) {} } 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 = `<div class="muted">Markdown renderer not available on this client. Use “Show raw Markdown”.</div>`;
return;
}
// No readme text loaded
mount.innerHTML = "";
}
_renderFabs() { _renderFabs() {
const r = this._detailRepo; const r = this._detailRepo;
if (!r) return ""; if (!r) return "";
@@ -879,4 +954,4 @@ class BahmcloudStorePanel extends HTMLElement {
} }
} }
customElements.define("bahmcloud-store-panel", BahmcloudStorePanel); customElements.define("bahmcloud-store-panel", BahmcloudStorePanel);