Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Extracts the palette + scale from `ShellDocs.Components/wwwroot/shelldocs-theme.
- **ShellUI integration path (NuGet install):** ShellUI's RCL detects `ShellDocs.Tokens` at runtime and skips emitting its own token file. Deferred to Phase 3 — needs a small opt-in flag on `AddShellUI()`.
- Unit tests: token file emits, dark-mode class toggling, no duplicate declarations across bundles

### `feat/codeblock-shiki`
### `feat/codeblock-shiki` — shipped
Ships to `ShellDocs.Components`.

- `CodeBlock` component — takes `Language`, `Code`, `Filename`, `HighlightLines`
Expand Down
20 changes: 11 additions & 9 deletions examples/ShellDocs.Preview/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
<link rel="stylesheet" href="_content/ShellDocs.Tokens/tokens.css" />
<link rel="stylesheet" href="_content/ShellDocs.Components/shelldocs-theme.css" />
<link rel="stylesheet" href="ShellDocs.Preview.styles.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" media="(prefers-color-scheme: dark)" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" media="(prefers-color-scheme: light)" />
<script>
(function () {
var q = new URLSearchParams(location.search).get('theme');
Expand All @@ -26,13 +24,17 @@
<body>
<Routes @rendermode="RenderMode.InteractiveServer" />
<script src="_content/ShellDocs.Components/shelldocs.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-csharp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markup-templating.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>
<script type="module">
/* Shiki singleton highlighter — VSCode-quality syntax colouring via
WASM. Loads the fixed subset of languages we support; dual-theme
(github-light + github-dark) resolves through CSS vars. */
import { createHighlighter } from 'https://esm.sh/shiki@1.24.0';
window.__shiki = await createHighlighter({
themes: ['github-light', 'github-dark'],
langs: ['razor', 'csharp', 'html', 'json', 'yaml', 'bash', 'typescript', 'javascript', 'markdown']
});
if (window.shelldocsHighlight) window.shelldocsHighlight();
</script>
<script src="_framework/blazor.web.js"></script>
</body>
</html>
4 changes: 2 additions & 2 deletions src/ShellDocs.Components/Content/PreviewFrame.razor
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
}
else
{
@* Prism ships no razor grammar; markup/html grammar handles the tag+attr+string structure just fine. *@
<pre class="preview-code language-markup" @ref="_codeEl"><code class="language-markup">@Preview!.Code</code></pre>
@* Shiki ships a razor grammar — full razor highlighting for previews. *@
<pre class="preview-code language-razor" @ref="_codeEl"><code class="language-razor">@Preview!.Code</code></pre>
}
</div>
</div>
Expand Down
25 changes: 16 additions & 9 deletions src/ShellDocs.Components/wwwroot/shelldocs-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -164,25 +164,32 @@ body { margin: 0; min-height: 100vh; }
font-feature-settings: 'ss01', 'cv02';
}

/* Prism.js overridelet Prism color tokens, keep our frame chrome */
.shelldocs-codeblock pre[class*="language-"],
.shelldocs-prose pre[class*="language-"] {
background: transparent !important;
/* Shiki outputnormalise its inline styles into our chrome. */
.shelldocs-codeblock pre.shiki,
.shelldocs-prose pre.shiki,
.preview-body pre.shiki {
margin: 0 !important;
padding: 1rem 1.125rem !important;
border: 0 !important;
border-radius: 0 !important;
font-family: var(--font-mono) !important;
font-size: 0.85rem !important;
line-height: 1.7 !important;
text-shadow: none !important;
overflow-x: auto;
}
.shelldocs-codeblock code[class*="language-"],
.shelldocs-prose code[class*="language-"] {
pre.shiki code {
background: transparent !important;
border: 0 !important;
padding: 0 !important;
border: 0 !important;
font-family: var(--font-mono) !important;
font-size: inherit !important;
text-shadow: none !important;
}

/* Dual-theme: Shiki emits --shiki-light + --shiki-dark on each token span
with defaultColor:false. Only set `color` — NEVER background on spans, or
every .line wrapper paints a bg strip across the block. */
pre.shiki { background-color: transparent !important; }
pre.shiki,
pre.shiki span { color: var(--shiki-light); }
:root.dark pre.shiki,
:root.dark pre.shiki span { color: var(--shiki-dark); }
50 changes: 40 additions & 10 deletions src/ShellDocs.Components/wwwroot/shelldocs.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,51 @@
window.ShellDocs = window.ShellDocs || {};

// Re-highlight all code blocks. Called from MarkdownContent after render.
window.shelldocsHighlight = function () {
if (window.Prism) {
try { window.Prism.highlightAll(); } catch (e) {}
/* Shiki-backed highlighters. Both functions replace <pre><code class="language-X">
with Shiki's rendered <pre> so we get VSCode-parity colouring. Idempotent —
a data-shiki flag prevents re-highlighting. */

function langOf(codeEl) {
var cls = (codeEl.className || '').split(/\s+/);
for (var i = 0; i < cls.length; i++) {
if (cls[i].indexOf('language-') === 0) return cls[i].substring(9);
}
};
return null;
}

// Highlight a specific <pre> element — used by PreviewFrame when the code tab
// mounts, so we don't re-scan the whole page on every tab flip.
window.shelldocsHighlightElement = function (preEl) {
if (!preEl || !window.Prism) return;
function highlightOne(preEl) {
if (!preEl || !window.__shiki) return;
if (preEl.dataset.shiki === 'done') return;
var code = preEl.querySelector('code');
if (!code) return;
try { window.Prism.highlightElement(code); } catch (e) {}
var lang = langOf(code);
if (!lang) return;
/* Shiki doesn't know every Prism alias — silently fall back. */
if (!window.__shiki.getLoadedLanguages().includes(lang)) return;

var source = code.textContent;
try {
var html = window.__shiki.codeToHtml(source, {
lang: lang,
themes: { light: 'github-light', dark: 'github-dark' },
defaultColor: false
});
var tpl = document.createElement('template');
tpl.innerHTML = html.trim();
var newPre = tpl.content.firstElementChild;
if (!newPre) return;
newPre.dataset.shiki = 'done';
preEl.parentNode.replaceChild(newPre, preEl);
} catch (e) { /* skip on grammar error */ }
}

window.shelldocsHighlight = function () {
if (!window.__shiki) return;
document.querySelectorAll('pre:not([data-shiki]) > code[class*="language-"]')
.forEach(function (code) { highlightOne(code.parentElement); });
};

window.shelldocsHighlightElement = function (preEl) { highlightOne(preEl); };

// Copy-to-clipboard for code blocks.
window.shelldocsCopyCode = function (button) {
var block = button.closest('.shelldocs-codeblock');
Expand Down
Loading