From 0682b24f27540e9ae943b5e8fefc605fdd44d1b5 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 16 Jun 2026 07:08:55 -0500 Subject: feat(theme-studio): realistic README preview for markdown-mode markdown-mode fell back to the generic preview (bare face names). I added renderMarkdownPreview, a realistic README that exercises the markdown faces in context: front matter, headers, bold/italic, inline and fenced code, links, lists and checkboxes, a blockquote with a footnote, a table, strikethrough, highlight, math, and inline HTML. A PREVIEW_KEYS map in app_inventory routes markdown-mode to the renderer, and the #mdtest gate checks every data-face it emits is a real markdown face. --- scripts/theme-studio/app.js | 41 ++++++++++++++++++++++- scripts/theme-studio/app_inventory.py | 9 ++++- scripts/theme-studio/browser-gates.js | 17 ++++++++++ scripts/theme-studio/theme-studio.html | 60 ++++++++++++++++++++++++++++++++-- 4 files changed, 123 insertions(+), 4 deletions(-) (limited to 'scripts/theme-studio') diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index f2c322aaf..e0344ea36 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -955,8 +955,47 @@ function renderAutodimPreview(){ +pane('auto-dim',dimBody,dimBg,false) +``; } +function renderMarkdownPreview(){const a='markdown-mode',L=[]; + const dl='markdown-header-delimiter-face',mk='markdown-markup-face'; + L.push(os(a,mk,'---')); + L.push(os(a,'markdown-metadata-key-face','title:')+' '+os(a,'markdown-metadata-value-face','Project Name')); + L.push(os(a,'markdown-metadata-key-face','version:')+' '+os(a,'markdown-metadata-value-face','1.2.0')); + L.push(os(a,mk,'---')); + L.push(''); + L.push(os(a,dl,'#')+' '+os(a,'markdown-header-face-1','Project Name')); + L.push(''); + L.push(os(a,'markdown-comment-face','<!-- a one-line tagline -->')); + L.push('A library for '+os(a,'markdown-bold-face','**doing things**')+' and '+os(a,'markdown-italic-face','*other things*')+'.'); + L.push(''); + L.push(os(a,dl,'##')+' '+os(a,'markdown-header-face-2','Installation')); + L.push(''); + L.push('Run '+os(a,'markdown-inline-code-face','`npm install project`')+' to get started.'); + L.push(''); + L.push(os(a,mk,'```')+os(a,'markdown-language-keyword-face','sh')); + L.push(os(a,'markdown-pre-face',' git clone https://example.com/project.git')); + L.push(os(a,'markdown-pre-face',' cd project; make')); + L.push(os(a,mk,'```')); + L.push(''); + L.push(os(a,dl,'###')+' '+os(a,'markdown-header-face-3','Usage')); + L.push(''); + L.push(os(a,'markdown-list-face','- ')+'See the '+os(a,'markdown-link-face','[docs]')+os(a,'markdown-url-face','(https://example.com/docs)')+' for details.'); + L.push(os(a,'markdown-list-face','- ')+'Or browse '+os(a,'markdown-plain-url-face','https://example.com')+' directly.'); + L.push(os(a,'markdown-gfm-checkbox-face','- [x]')+' shipped '+os(a,'markdown-gfm-checkbox-face','- [ ]')+' planned'); + L.push(''); + L.push(os(a,'markdown-blockquote-face','> A note worth quoting, with a footnote')+os(a,'markdown-footnote-marker-face','[^1]')+os(a,'markdown-blockquote-face','.')); + L.push(''); + L.push(os(a,'markdown-table-face','| Option | Default |')); + L.push(os(a,'markdown-table-face','|--------|---------|')); + L.push(os(a,'markdown-table-face','| debug | false |')); + L.push(''); + L.push(os(a,'markdown-hr-face','---')); + L.push(''); + L.push(os(a,'markdown-strike-through-face','~~deprecated~~')+' '+os(a,'markdown-highlight-face','==important==')+' '+os(a,'markdown-math-face','$E = mc^2$')); + L.push(os(a,'markdown-html-tag-delimiter-face','<')+os(a,'markdown-html-tag-name-face','kbd')+os(a,'markdown-html-tag-delimiter-face','>')+'Ctrl-C'+os(a,'markdown-html-tag-delimiter-face','</')+os(a,'markdown-html-tag-name-face','kbd')+os(a,'markdown-html-tag-delimiter-face','>')); + L.push(os(a,'markdown-footnote-marker-face','[^1]:')+' '+os(a,'markdown-footnote-text-face','the footnote text.')); + return previewLines(L);} const PACKAGE_PREVIEWS={ - autodim:renderAutodimPreview, + autodim:renderAutodimPreview,markdown:renderMarkdownPreview, org:renderOrgPreview,magit:renderMagitPreview,elfeed:renderElfeedPreview,ghostel:renderGhostelPreview, dashboard:renderDashboardPreview,mu4e:renderMu4ePreview,orgfaces:renderOrgFacesPreview,lsp:renderLspPreview,gitgutter:renderGitGutterPreview, flycheck:renderFlycheckPreview,dired:renderDiredPreview,dirvish:renderDirvishPreview,calibredb:renderCalibredbPreview, diff --git a/scripts/theme-studio/app_inventory.py b/scripts/theme-studio/app_inventory.py index 01dcb7db7..9add0f658 100644 --- a/scripts/theme-studio/app_inventory.py +++ b/scripts/theme-studio/app_inventory.py @@ -35,6 +35,13 @@ BESPOKE_APPS = { } +# Inventory apps (not in BESPOKE_APPS) default to the generic preview. A few have +# a dedicated PACKAGE_PREVIEWS renderer in app.js, keyed by name here. +PREVIEW_KEYS = { + "markdown-mode": "markdown", +} + + def face_label(face: str, prefix: str) -> str: label = face[len(prefix) :] if face.startswith(prefix) else face return label.replace("-face", "").replace("-", " ") @@ -55,7 +62,7 @@ def add_inventory_apps(apps: dict[str, Any], inventory_path: str) -> dict[str, A continue apps[pkg] = { "label": pkg, - "preview": "generic", + "preview": PREVIEW_KEYS.get(pkg, "generic"), "faces": [[face, face_label(face, pkg + "-"), {}] for face in inventory[pkg]], } return apps diff --git a/scripts/theme-studio/browser-gates.js b/scripts/theme-studio/browser-gates.js index b03ec6a47..87038552c 100644 --- a/scripts/theme-studio/browser-gates.js +++ b/scripts/theme-studio/browser-gates.js @@ -748,6 +748,23 @@ if(location.hash==='#navtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c) } document.title='NAVTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='navtest';d.textContent='NAVTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} +// Markdown-preview gate (open with #mdtest): markdown-mode has a dedicated README +// renderer, and every data-face it emits is a real markdown-mode face. +if(location.hash==='#mdtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + A(APPS['markdown-mode']&&APPS['markdown-mode'].preview==='markdown','markdown-mode wired to the markdown preview'); + A(!!PACKAGE_PREVIEWS['markdown'],'markdown renderer registered'); + if(PACKAGE_PREVIEWS['markdown']&&APPS['markdown-mode']){ + const box=document.createElement('div');box.innerHTML=PACKAGE_PREVIEWS['markdown'](); + const valid=new Set(APPS['markdown-mode'].faces.map(r=>r[0])); + const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); + A(used.length>=15,'preview exercises many faces ('+used.length+')'); + const bad=used.filter(f=>!valid.has(f)); + A(bad.length===0,'every data-face is a real markdown face; bad='+bad.join(',')); + for(const f of ['markdown-header-face-1','markdown-bold-face','markdown-inline-code-face','markdown-blockquote-face','markdown-gfm-checkbox-face','markdown-table-face']) + A(used.includes(f),'preview includes '+f); + } + document.title='MDTEST '+(ok?'PASS':'FAIL'); + const d=document.createElement('div');d.id='mdtest';d.textContent='MDTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} // Box-cluster gate (open with #boxtest): the box control is a 2x2 cluster of // four radio buttons (none / line / pressed / raised); the color swatch shows // only while a box style is active. diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index 34c3df70b..ad05c0465 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -270,7 +270,7 @@