Fix skills detail markdown styling with preview-md wrapper
Skill detail and linked markdown files now use the same preview-md pipeline as Memory/Notes, with code highlighting and KaTeX enhancement.
This commit is contained in:
1 parent
e16f699313
commit
f24d633189
3 files changed
+75
-3
No files matched your search
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Skills detail view now renders `SKILL.md` markdown with the same `.preview-md` typography used by Memory and Notes, instead of unstyled `renderMd()` output. Linked markdown skill files use the same wrapper and post-render code/KaTeX enhancement.
|
||||||
|
|
||||||
## [v0.51.190] — 2026-05-31 — Release FJ (stage-batch2 — Windows upgrade state-stranding hotfix + gateway banner + quiet tool previews)
|
## [v0.51.190] — 2026-05-31 — Release FJ (stage-batch2 — Windows upgrade state-stranding hotfix + gateway banner + quiet tool previews)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+18
-3
@@ -3640,6 +3640,19 @@ function _stripYamlFrontmatter(content) {
|
|||||||
return { frontmatter: m[1], body: content.slice(m[0].length) };
|
return { frontmatter: m[1], body: content.slice(m[0].length) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _skillMarkdownHtml(markdown) {
|
||||||
|
return `<div class="preview-md">${renderMd(markdown || '')}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _enhanceSkillMarkdown(root) {
|
||||||
|
if (!root) return;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const mdRoot = root.querySelector('.preview-md') || root;
|
||||||
|
if (typeof highlightCode === 'function') highlightCode(mdRoot);
|
||||||
|
if (typeof renderKatexBlocks === 'function') renderKatexBlocks(mdRoot);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function _renderSkillDetail(name, content, linkedFiles) {
|
function _renderSkillDetail(name, content, linkedFiles) {
|
||||||
const title = $('skillDetailTitle');
|
const title = $('skillDetailTitle');
|
||||||
const body = $('skillDetailBody');
|
const body = $('skillDetailBody');
|
||||||
@@ -3652,7 +3665,7 @@ function _renderSkillDetail(name, content, linkedFiles) {
|
|||||||
if (frontmatter) {
|
if (frontmatter) {
|
||||||
html += `<details class="skill-frontmatter"><summary>${esc(t('skill_metadata'))}</summary><pre><code>${esc(frontmatter)}</code></pre></details>`;
|
html += `<details class="skill-frontmatter"><summary>${esc(t('skill_metadata'))}</summary><pre><code>${esc(frontmatter)}</code></pre></details>`;
|
||||||
}
|
}
|
||||||
html += renderMd(markdownBody || '(no content)');
|
html += _skillMarkdownHtml(markdownBody || '(no content)');
|
||||||
const lf = linkedFiles || {};
|
const lf = linkedFiles || {};
|
||||||
const categories = Object.entries(lf).filter(([,files]) => files && files.length > 0);
|
const categories = Object.entries(lf).filter(([,files]) => files && files.length > 0);
|
||||||
if (categories.length) {
|
if (categories.length) {
|
||||||
@@ -3667,6 +3680,7 @@ function _renderSkillDetail(name, content, linkedFiles) {
|
|||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
body.innerHTML = `<div class="main-view-content skill-detail-content">${html}</div>`;
|
body.innerHTML = `<div class="main-view-content skill-detail-content">${html}</div>`;
|
||||||
|
_enhanceSkillMarkdown(body);
|
||||||
body.querySelectorAll('.skill-linked-file').forEach(a => {
|
body.querySelectorAll('.skill-linked-file').forEach(a => {
|
||||||
a.addEventListener('click', e => { e.preventDefault(); openSkillFile(a.dataset.skillName, a.dataset.skillFile); });
|
a.addEventListener('click', e => { e.preventDefault(); openSkillFile(a.dataset.skillName, a.dataset.skillFile); });
|
||||||
});
|
});
|
||||||
@@ -3738,7 +3752,7 @@ async function openSkillFile(skillName, filePath) {
|
|||||||
const header = `<div class="skill-file-breadcrumb"><a href="#" class="skill-file-back" data-skill-name="${esc(skillName)}">← ${esc(backLabel)}</a><span class="skill-file-path">${esc(filePath)}</span></div>`;
|
const header = `<div class="skill-file-breadcrumb"><a href="#" class="skill-file-back" data-skill-name="${esc(skillName)}">← ${esc(backLabel)}</a><span class="skill-file-path">${esc(filePath)}</span></div>`;
|
||||||
let content;
|
let content;
|
||||||
if (isMd) {
|
if (isMd) {
|
||||||
content = `<div class="main-view-content">${renderMd(data.content || '')}</div>`;
|
content = `<div class="main-view-content">${_skillMarkdownHtml(data.content || '')}</div>`;
|
||||||
} else {
|
} else {
|
||||||
const escaped = esc(data.content || '');
|
const escaped = esc(data.content || '');
|
||||||
content = `<pre class="skill-file-code"><code>${escaped}</code></pre>`;
|
content = `<pre class="skill-file-code"><code>${escaped}</code></pre>`;
|
||||||
@@ -3757,7 +3771,8 @@ async function openSkillFile(skillName, filePath) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (!isMd) requestAnimationFrame(() => { if (typeof highlightCode === 'function') highlightCode(); });
|
if (isMd) _enhanceSkillMarkdown(body);
|
||||||
|
else requestAnimationFrame(() => { if (typeof highlightCode === 'function') highlightCode(); });
|
||||||
} catch(e) { setStatus(t('skill_file_load_failed') + e.message); }
|
} catch(e) { setStatus(t('skill_file_load_failed') + e.message); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""Skills detail markdown must use the shared preview-md styling pipeline."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PANELS_JS = Path("static/panels.js").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _function_block(name: str) -> str:
|
||||||
|
marker = f"function {name}("
|
||||||
|
start = PANELS_JS.find(marker)
|
||||||
|
assert start != -1, f"{name}() not found"
|
||||||
|
params_end = PANELS_JS.find("){", start)
|
||||||
|
assert params_end != -1, f"{name}() body not found"
|
||||||
|
brace = params_end + 1
|
||||||
|
depth = 0
|
||||||
|
for idx in range(brace, len(PANELS_JS)):
|
||||||
|
ch = PANELS_JS[idx]
|
||||||
|
if ch == "{":
|
||||||
|
depth += 1
|
||||||
|
elif ch == "}":
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0:
|
||||||
|
return PANELS_JS[start : idx + 1]
|
||||||
|
raise AssertionError(f"{name}() body did not close")
|
||||||
|
|
||||||
|
|
||||||
|
def test_skill_detail_wraps_markdown_in_preview_md():
|
||||||
|
block = _function_block("_renderSkillDetail")
|
||||||
|
assert "_skillMarkdownHtml(" in block, "Skill detail must render through the shared markdown wrapper"
|
||||||
|
assert "renderMd(markdownBody" not in block.replace(" ", ""), (
|
||||||
|
"Skill detail must not inject raw renderMd() output without preview-md styling"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_skill_markdown_helper_uses_preview_md():
|
||||||
|
block = _function_block("_skillMarkdownHtml")
|
||||||
|
assert 'class="preview-md"' in block.replace("'", '"')
|
||||||
|
assert "renderMd(" in block
|
||||||
|
|
||||||
|
|
||||||
|
def test_skill_detail_enhances_markdown_after_render():
|
||||||
|
detail_block = _function_block("_renderSkillDetail")
|
||||||
|
assert "_enhanceSkillMarkdown(body)" in detail_block
|
||||||
|
|
||||||
|
enhance_block = _function_block("_enhanceSkillMarkdown")
|
||||||
|
assert "highlightCode" in enhance_block
|
||||||
|
assert "renderKatexBlocks" in enhance_block
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_skill_file_markdown_uses_preview_md():
|
||||||
|
block = _function_block("openSkillFile")
|
||||||
|
assert "_skillMarkdownHtml(" in block
|
||||||
|
assert "if (isMd) _enhanceSkillMarkdown(body)" in block
|
||||||
Reference in new issue
Block a user