diff --git a/custom_components/bahmcloud_store/panel/panel.js b/custom_components/bahmcloud_store/panel/panel.js index 0b4feb8..f8c1b91 100644 --- a/custom_components/bahmcloud_store/panel/panel.js +++ b/custom_components/bahmcloud_store/panel/panel.js @@ -21,7 +21,7 @@ class BahmcloudStorePanel extends HTMLElement { this._readmeLoading = false; this._readmeText = null; - this._readmeHtml = null; // backend may provide; we don't rely on it anymore + this._readmeHtml = null; // backend may provide; client renderer does not rely on it this._readmeError = null; } @@ -331,10 +331,17 @@ class BahmcloudStorePanel extends HTMLElement { details{ margin-top: 10px; } summary{ cursor:pointer; color: var(--bcs-accent); font-weight: 900; } - /* Pretty Markdown container spacing (ha-markdown renders inside) */ + /* Pretty Markdown container */ .md { line-height: 1.65; font-size: 14px; } .md :is(h1,h2,h3){ margin: 18px 0 10px; } .md :is(p,ul,ol,pre,blockquote,table){ margin: 10px 0; } + .md pre { overflow:auto; padding:12px; border-radius:14px; border:1px solid var(--divider-color); } + .md code { padding:2px 6px; border-radius:8px; border:1px solid var(--divider-color); } + .md blockquote { border-left:4px solid var(--bcs-accent); padding:8px 12px; border-radius:12px; + background: color-mix(in srgb, var(--bcs-accent) 8%, var(--card-background-color)); } + .md table{ width:100%; border-collapse: collapse; } + .md th,.md td{ border:1px solid var(--divider-color); padding:8px; text-align:left; } + .md img{ max-width:100%; height:auto; border-radius:12px; }
@@ -659,45 +666,79 @@ class BahmcloudStorePanel extends HTMLElement { `; } + _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") { + html = markedObj(md); + } else { + return ""; + } + } catch (_) { + return ""; + } + + try { + if (domPurify && typeof domPurify.sanitize === "function") { + html = domPurify.sanitize(html); + } + } catch (_) {} + + return html; + } + + _postprocessRenderedMarkdown(container) { + if (!container) return; + + // Make links open in new tab + try { + const links = container.querySelectorAll("a[href]"); + links.forEach((a) => { + a.setAttribute("target", "_blank"); + a.setAttribute("rel", "noreferrer noopener"); + }); + } catch (_) {} + } + _wireDetail() { const root = this.shadowRoot; const mount = root.getElementById("readmePretty"); if (!mount) return; - // Always prefer HA's frontend markdown renderer to get a professional look. - // This uses the same renderer concept as HA's Markdown card (Marked.js). :contentReference[oaicite:1]{index=1} if (this._readmeText) { - try { - mount.innerHTML = ""; + const html = this._mdToHtml(this._readmeText); - const el = document.createElement("ha-markdown"); - // Ensure it has access to HA context (themes, link handling, etc.) - el.hass = this._hass; - - // Different HA versions use either "content" or "markdown" internally; - // setting both is harmless and maximizes compatibility. - el.content = this._readmeText; - el.markdown = this._readmeText; - - // Some versions support these flags; safe to set even if ignored. - el.allowHtml = false; - el.breaks = true; - - mount.appendChild(el); + if (html) { + mount.innerHTML = html; + this._postprocessRenderedMarkdown(mount); return; - } catch (_) { - // fall through to html/raw fallback } - } - // Fallback: if backend provided sanitized HTML, use it - if (this._readmeHtml) { - mount.innerHTML = this._readmeHtml; + // 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; } - // Last resort: show nothing here (raw is still available via details) - mount.innerHTML = `
Unable to render Markdown on this client.
`; + // No readme text loaded + mount.innerHTML = ""; } _renderFabs() {