@classic-homes/theme-docs 0.0.47 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,6 @@
5
5
  * For use outside of markdown content when you need
6
6
  * to display code with highlighting directly.
7
7
  */
8
- import { onMount } from 'svelte';
9
8
  import { cn } from '../utils.js';
10
9
  import { escapeHtml } from '../highlighter/index.js';
11
10
 
@@ -35,20 +34,36 @@
35
34
  let highlighted = $state('');
36
35
  let loading = $state(true);
37
36
 
38
- onMount(async () => {
39
- try {
40
- const { highlightCode } = await import('../highlighter/index.js');
41
- highlighted = await highlightCode(code, language, { theme });
42
- loading = false;
43
- } catch (err) {
44
- // Log warning for debugging - falling back to plain code
45
- console.warn(
46
- '[docs] Failed to highlight code block, showing plain code:',
47
- err instanceof Error ? err.message : err
48
- );
49
- highlighted = `<pre class="shiki bg-muted rounded-lg p-4 overflow-x-auto"><code>${escapeHtml(code)}</code></pre>`;
50
- loading = false;
51
- }
37
+ // Re-highlight whenever code/language/theme change (not just on mount)
38
+ $effect(() => {
39
+ const currentCode = code;
40
+ const currentLanguage = language;
41
+ const currentTheme = theme;
42
+ let cancelled = false;
43
+
44
+ loading = true;
45
+ (async () => {
46
+ try {
47
+ const { highlightCode } = await import('../highlighter/index.js');
48
+ const html = await highlightCode(currentCode, currentLanguage, { theme: currentTheme });
49
+ if (cancelled) return;
50
+ highlighted = html;
51
+ loading = false;
52
+ } catch (err) {
53
+ if (cancelled) return;
54
+ // Log warning for debugging - falling back to plain code
55
+ console.warn(
56
+ '[docs] Failed to highlight code block, showing plain code:',
57
+ err instanceof Error ? err.message : err
58
+ );
59
+ highlighted = `<pre class="shiki bg-muted rounded-lg p-4 overflow-x-auto"><code>${escapeHtml(currentCode)}</code></pre>`;
60
+ loading = false;
61
+ }
62
+ })();
63
+
64
+ return () => {
65
+ cancelled = true;
66
+ };
52
67
  });
53
68
  </script>
54
69
 
@@ -10,7 +10,7 @@
10
10
  import type { DocsItem } from '../types/docs.js';
11
11
 
12
12
  const docsCardVariants = tv({
13
- base: 'group relative flex flex-col rounded-lg border border-border bg-card p-6 transition-all hover:border-primary hover:shadow-md',
13
+ base: 'group relative flex flex-col rounded-lg border border-border bg-card p-6 hover:transition-shadow hover:border-primary hover:shadow-md',
14
14
  });
15
15
 
16
16
  interface Props {
@@ -57,9 +57,7 @@
57
57
  {/if}
58
58
 
59
59
  <div class="flex-1 min-w-0">
60
- <h3
61
- class="font-semibold text-foreground group-hover:text-primary transition-colors flex items-center gap-2"
62
- >
60
+ <h3 class="font-semibold text-foreground group-hover:text-primary flex items-center gap-2">
63
61
  {item.title}
64
62
  {#if item.external}
65
63
  <svg
@@ -8,7 +8,6 @@
8
8
  * Requires mermaid to be installed in the consuming application:
9
9
  * npm install mermaid
10
10
  */
11
- import { onMount } from 'svelte';
12
11
  import { cn } from '../utils.js';
13
12
 
14
13
  interface Props {
@@ -34,32 +33,48 @@
34
33
  let containerRef = $state<HTMLDivElement | null>(null);
35
34
  let rendered = $state(false);
36
35
  let error = $state<string | null>(null);
36
+ let renderCount = 0;
37
37
 
38
- onMount(async () => {
39
- if (!containerRef) return;
38
+ // Re-render whenever code/theme change (not just on mount)
39
+ $effect(() => {
40
+ const container = containerRef;
41
+ const currentCode = code;
42
+ const currentTheme = theme;
43
+ if (!container) return;
40
44
 
41
- try {
42
- // Dynamically import mermaid to avoid SSR issues
43
- const mermaid = await import('mermaid');
45
+ let cancelled = false;
44
46
 
45
- // Initialize mermaid with theme
46
- mermaid.default.initialize({
47
- startOnLoad: false,
48
- theme: theme,
49
- securityLevel: 'strict',
50
- });
47
+ (async () => {
48
+ try {
49
+ // Dynamically import mermaid to avoid SSR issues
50
+ const mermaid = await import('mermaid');
51
51
 
52
- // Render the diagram
53
- const { svg } = await mermaid.default.render(id, code);
54
- containerRef.innerHTML = svg;
55
- rendered = true;
56
- } catch (err) {
57
- console.warn(
58
- '[docs] Failed to render Mermaid diagram:',
59
- err instanceof Error ? err.message : err
60
- );
61
- error = err instanceof Error ? err.message : 'Failed to render diagram';
62
- }
52
+ // Initialize mermaid with theme
53
+ mermaid.default.initialize({
54
+ startOnLoad: false,
55
+ theme: currentTheme,
56
+ securityLevel: 'strict',
57
+ });
58
+
59
+ // Render the diagram; mermaid.render needs a fresh element id per run
60
+ const { svg } = await mermaid.default.render(`${id}-${++renderCount}`, currentCode);
61
+ if (cancelled) return;
62
+ container.innerHTML = svg;
63
+ rendered = true;
64
+ error = null;
65
+ } catch (err) {
66
+ if (cancelled) return;
67
+ console.warn(
68
+ '[docs] Failed to render Mermaid diagram:',
69
+ err instanceof Error ? err.message : err
70
+ );
71
+ error = err instanceof Error ? err.message : 'Failed to render diagram';
72
+ }
73
+ })();
74
+
75
+ return () => {
76
+ cancelled = true;
77
+ };
63
78
  });
64
79
  </script>
65
80
 
@@ -106,7 +106,7 @@
106
106
  }
107
107
 
108
108
  function getLevelStyles(level: number, isActive: boolean): string {
109
- const baseStyles = 'block py-1 rounded-sm transition-colors';
109
+ const baseStyles = 'block py-1 rounded-sm';
110
110
 
111
111
  if (isActive) {
112
112
  return cn(baseStyles, 'text-primary font-medium');
@@ -129,7 +129,6 @@
129
129
  'text-muted-foreground',
130
130
  'bg-transparent border-0 p-0 m-0',
131
131
  'cursor-pointer',
132
- 'transition-colors duration-150 ease-in-out',
133
132
  'hover:bg-muted hover:text-foreground',
134
133
  'focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2',
135
134
  ].join(' ');
@@ -208,10 +207,10 @@
208
207
 
209
208
  <style>
210
209
  /*
211
- * Non-button styles that don't conflict with Tailwind preflight.
212
- * Button styles are applied via Tailwind utility classes in the script
213
- * to ensure proper specificity with postcss-cascade-layers.
214
- */
210
+ * Non-button styles that don't conflict with Tailwind preflight.
211
+ * Button styles are applied via Tailwind utility classes in the script
212
+ * to ensure proper specificity with postcss-cascade-layers.
213
+ */
215
214
 
216
215
  /* Vertical text for folder tab */
217
216
  .toc-tab-text {
@@ -1,4 +1,4 @@
1
- import { marked, Renderer } from 'marked';
1
+ import { Marked, Renderer } from 'marked';
2
2
  import yaml from 'js-yaml';
3
3
  import { getHighlighter, escapeHtml } from '../highlighter/index.js';
4
4
  import { getAllExtensions, resetFootnoteStore, renderFootnotes } from './extensions.js';
@@ -89,7 +89,7 @@ export async function parseMarkdown(content, options = {}) {
89
89
  }
90
90
  };
91
91
  if (generateHeadingIds) {
92
- renderer.heading = ({ text, depth }) => {
92
+ renderer.heading = function ({ tokens, text, depth }) {
93
93
  const id = text
94
94
  .toLowerCase()
95
95
  .replace(/\./g, '-') // Convert periods to hyphens (preserves version numbers like v2.0 → v2-0)
@@ -97,13 +97,18 @@ export async function parseMarkdown(content, options = {}) {
97
97
  .replace(/\s+/g, '-') // Convert spaces to hyphens
98
98
  .replace(/-+/g, '-') // Collapse multiple hyphens
99
99
  .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
100
- return `<h${depth} id="${id}">${text}</h${depth}>`;
100
+ // Render inline tokens so markdown inside headings (bold, code, links) works
101
+ return `<h${depth} id="${id}">${this.parser.parseInline(tokens)}</h${depth}>`;
101
102
  };
102
103
  }
104
+ // Use a per-call Marked instance: the renderer captures per-call state
105
+ // (theme, highlighter), and `marked.use` on the shared global instance
106
+ // accumulates extensions across calls.
107
+ const md = new Marked();
103
108
  // Apply all markdown extensions (admonitions, footnotes, definition lists, mermaid)
104
- marked.use(...getAllExtensions());
105
- marked.use({ renderer, gfm: true });
106
- let html = await marked(markdownContent);
109
+ md.use(...getAllExtensions());
110
+ md.use({ renderer, gfm: true });
111
+ let html = await md.parse(markdownContent);
107
112
  // Append footnotes section if any were referenced
108
113
  const footnotesHtml = renderFootnotes();
109
114
  if (footnotesHtml) {
@@ -133,7 +138,9 @@ export async function parseMarkdown(content, options = {}) {
133
138
  * ```
134
139
  */
135
140
  export function extractToc(html, maxDepth = 3) {
136
- const headingRegex = /<h([1-6])[^>]*id="([^"]*)"[^>]*>([^<]*)<\/h[1-6]>/gi;
141
+ // Heading content may contain inline HTML (bold, code, links) — capture
142
+ // everything up to the matching close tag, then strip tags for the text.
143
+ const headingRegex = /<h([1-6])[^>]*id="([^"]*)"[^>]*>([\s\S]*?)<\/h\1>/gi;
137
144
  const toc = [];
138
145
  let match;
139
146
  while ((match = headingRegex.exec(html)) !== null) {
@@ -142,7 +149,7 @@ export function extractToc(html, maxDepth = 3) {
142
149
  toc.push({
143
150
  level,
144
151
  id: match[2],
145
- text: match[3],
152
+ text: match[3].replace(/<[^>]*>/g, ''),
146
153
  });
147
154
  }
148
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/theme-docs",
3
- "version": "0.0.47",
3
+ "version": "0.0.49",
4
4
  "description": "Markdown documentation components for the Classic theme system",
5
5
  "type": "module",
6
6
  "main": "./dist/lib/index.js",
@@ -25,7 +25,7 @@
25
25
  "scripts": {
26
26
  "build": "svelte-package -i src",
27
27
  "dev": "svelte-package -i src --watch",
28
- "clean": "rm -rf dist",
28
+ "clean": "node ../../scripts/rm.mjs dist",
29
29
  "typecheck": "svelte-check --tsconfig ./tsconfig.json"
30
30
  },
31
31
  "keywords": [