@wireweave/markdown-plugin 1.1.0-beta.3 → 1.1.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wireweave/markdown-plugin",
3
- "version": "1.1.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "description": "Markdown plugins for wireweave",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "files": [
32
32
  "dist",
33
- "README.md"
33
+ "src"
34
34
  ],
35
35
  "scripts": {
36
36
  "build": "tsup",
package/src/index.ts ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @wireweave/markdown-plugin
3
+ *
4
+ * Markdown plugins for wireweave
5
+ */
6
+
7
+ import {
8
+ parse,
9
+ render,
10
+ renderToSvg,
11
+ resolveViewport,
12
+ wrapInPreviewContainer,
13
+ } from '@wireweave/core';
14
+
15
+ export interface WireframePluginOptions {
16
+ /**
17
+ * Output format
18
+ * - 'html': HTML/CSS rendering (inline styles)
19
+ * - 'html-preview': HTML with preview container (supports scaling)
20
+ * - 'svg': SVG image
21
+ * - 'svg-img': base64 encoded img tag
22
+ */
23
+ format?: 'html' | 'html-preview' | 'svg' | 'svg-img';
24
+
25
+ /**
26
+ * Theme for rendering
27
+ * - 'light': Light theme
28
+ * - 'dark': Dark theme
29
+ */
30
+ theme?: 'light' | 'dark';
31
+
32
+ /**
33
+ * Container class for wrapping SVG/HTML
34
+ */
35
+ containerClass?: string;
36
+
37
+ /**
38
+ * Error handling mode
39
+ * - 'code': Show original code
40
+ * - 'error': Show error message
41
+ * - 'both': Show both
42
+ */
43
+ errorHandling?: 'code' | 'error' | 'both';
44
+
45
+ /**
46
+ * Container width for preview scaling (in pixels)
47
+ * When set, the wireframe will be scaled to fit this width
48
+ */
49
+ containerWidth?: number;
50
+
51
+ /**
52
+ * Maximum scale factor (default: 1)
53
+ * Prevents the preview from being scaled up beyond this value
54
+ */
55
+ maxScale?: number;
56
+ }
57
+
58
+ const defaultOptions: Required<WireframePluginOptions> = {
59
+ format: 'svg-img',
60
+ theme: 'light',
61
+ containerClass: 'wireframe-container',
62
+ errorHandling: 'both',
63
+ containerWidth: 0, // 0 means no scaling
64
+ maxScale: 1,
65
+ };
66
+
67
+ /**
68
+ * Render wireframe code to output format
69
+ */
70
+ export function renderWireframe(
71
+ code: string,
72
+ options: WireframePluginOptions = {}
73
+ ): string {
74
+ const opts = { ...defaultOptions, ...options };
75
+
76
+ try {
77
+ const doc = parse(code);
78
+
79
+ switch (opts.format) {
80
+ case 'html': {
81
+ const { html, css } = render(doc, { theme: opts.theme });
82
+ return `
83
+ <div class="${opts.containerClass}">
84
+ <style>${css}</style>
85
+ ${html}
86
+ </div>
87
+ `.trim();
88
+ }
89
+
90
+ case 'html-preview': {
91
+ const { html, css } = render(doc, { theme: opts.theme });
92
+ const firstPage = doc.children[0];
93
+ const viewport = resolveViewport(firstPage?.viewport, firstPage?.device);
94
+
95
+ const previewHtml = wrapInPreviewContainer(html, viewport, {
96
+ darkMode: opts.theme === 'dark',
97
+ containerWidth: opts.containerWidth > 0 ? opts.containerWidth : undefined,
98
+ });
99
+
100
+ return `
101
+ <div class="${opts.containerClass}">
102
+ <style>${css}</style>
103
+ ${previewHtml}
104
+ </div>
105
+ `.trim();
106
+ }
107
+
108
+ case 'svg': {
109
+ const { svg } = renderToSvg(doc);
110
+ return `<div class="${opts.containerClass}">${svg}</div>`;
111
+ }
112
+
113
+ case 'svg-img':
114
+ default: {
115
+ const { svg } = renderToSvg(doc);
116
+ // Use btoa for browser compatibility, Buffer for Node.js
117
+ const base64 =
118
+ typeof Buffer !== 'undefined'
119
+ ? Buffer.from(svg).toString('base64')
120
+ : btoa(svg);
121
+ return `
122
+ <div class="${opts.containerClass}">
123
+ <img src="data:image/svg+xml;base64,${base64}" alt="Wireframe" />
124
+ </div>
125
+ `.trim();
126
+ }
127
+ }
128
+ } catch (error) {
129
+ return renderError(code, error as Error, opts);
130
+ }
131
+ }
132
+
133
+ function renderError(
134
+ code: string,
135
+ error: Error,
136
+ options: Required<WireframePluginOptions>
137
+ ): string {
138
+ const errorHtml = `<pre class="wireframe-error">${escapeHtml(error.message)}</pre>`;
139
+ const codeHtml = `<pre class="wireframe-source"><code>${escapeHtml(code)}</code></pre>`;
140
+
141
+ switch (options.errorHandling) {
142
+ case 'code':
143
+ return codeHtml;
144
+ case 'error':
145
+ return errorHtml;
146
+ case 'both':
147
+ default:
148
+ return `<div class="${options.containerClass} wireframe-error-container">${errorHtml}${codeHtml}</div>`;
149
+ }
150
+ }
151
+
152
+ function escapeHtml(text: string): string {
153
+ return text
154
+ .replace(/&/g, '&amp;')
155
+ .replace(/</g, '&lt;')
156
+ .replace(/>/g, '&gt;')
157
+ .replace(/"/g, '&quot;')
158
+ .replace(/'/g, '&#039;');
159
+ }
160
+
161
+ export { markdownItWireframe } from './markdown-it';
162
+ export { markedWireframe } from './marked';
163
+ export { remarkableWireframe } from './remarkable';
@@ -0,0 +1,43 @@
1
+ /**
2
+ * markdown-it plugin for wireweave
3
+ */
4
+
5
+ import type MarkdownIt from 'markdown-it';
6
+ import { renderWireframe, WireframePluginOptions } from './index';
7
+
8
+ /**
9
+ * markdown-it plugin
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import MarkdownIt from 'markdown-it';
14
+ * import { markdownItWireframe } from '@wireweave/markdown-plugin/markdown-it';
15
+ *
16
+ * const md = new MarkdownIt();
17
+ * md.use(markdownItWireframe, { format: 'svg' });
18
+ *
19
+ * const html = md.render('```wireframe\npage { text "Hello" }\n```');
20
+ * ```
21
+ */
22
+ export function markdownItWireframe(
23
+ md: MarkdownIt,
24
+ options: WireframePluginOptions = {}
25
+ ): void {
26
+ const defaultFence = md.renderer.rules.fence;
27
+
28
+ md.renderer.rules.fence = (tokens, idx, opts, env, self) => {
29
+ const token = tokens[idx];
30
+ const info = token.info.trim();
31
+
32
+ if (info === 'wireframe' || info === 'wf') {
33
+ return renderWireframe(token.content, options);
34
+ }
35
+
36
+ // Use default renderer for other code blocks
37
+ if (defaultFence) {
38
+ return defaultFence(tokens, idx, opts, env, self);
39
+ }
40
+
41
+ return `<pre><code class="language-${info}">${md.utils.escapeHtml(token.content)}</code></pre>`;
42
+ };
43
+ }
package/src/marked.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * marked extension for wireweave
3
+ */
4
+
5
+ import type { MarkedExtension, Tokens } from 'marked';
6
+ import { renderWireframe, WireframePluginOptions } from './index';
7
+
8
+ /**
9
+ * marked extension
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { marked } from 'marked';
14
+ * import { markedWireframe } from '@wireweave/markdown-plugin/marked';
15
+ *
16
+ * marked.use(markedWireframe({ format: 'svg' }));
17
+ *
18
+ * const html = marked.parse('```wireframe\npage { text "Hello" }\n```');
19
+ * ```
20
+ */
21
+ export function markedWireframe(
22
+ options: WireframePluginOptions = {}
23
+ ): MarkedExtension {
24
+ return {
25
+ renderer: {
26
+ code(token: Tokens.Code): string | false {
27
+ if (token.lang === 'wireframe' || token.lang === 'wf') {
28
+ return renderWireframe(token.text, options);
29
+ }
30
+ return false; // Use default renderer
31
+ },
32
+ },
33
+ };
34
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * remarkable plugin for wireweave
3
+ */
4
+
5
+ import { renderWireframe, WireframePluginOptions } from './index';
6
+
7
+ // Type declarations for remarkable (no @types/remarkable available)
8
+ interface RemarkableToken {
9
+ type: string;
10
+ params: string;
11
+ content: string;
12
+ }
13
+
14
+ interface RemarkableRenderer {
15
+ rules: {
16
+ fence: (
17
+ tokens: RemarkableToken[],
18
+ idx: number,
19
+ options: Record<string, unknown>,
20
+ env: Record<string, unknown>
21
+ ) => string;
22
+ };
23
+ }
24
+
25
+ interface RemarkableUtils {
26
+ escapeHtml: (text: string) => string;
27
+ }
28
+
29
+ interface Remarkable {
30
+ renderer: RemarkableRenderer;
31
+ utils: RemarkableUtils;
32
+ }
33
+
34
+ /**
35
+ * remarkable plugin
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { Remarkable } from 'remarkable';
40
+ * import { remarkableWireframe } from '@wireweave/markdown-plugin/remarkable';
41
+ *
42
+ * const md = new Remarkable();
43
+ * md.use(remarkableWireframe({ format: 'svg' }));
44
+ *
45
+ * const html = md.render('```wireframe\npage { text "Hello" }\n```');
46
+ * ```
47
+ */
48
+ /**
49
+ * Escape HTML entities
50
+ */
51
+ function escapeHtml(text: string): string {
52
+ return text
53
+ .replace(/&/g, '&amp;')
54
+ .replace(/</g, '&lt;')
55
+ .replace(/>/g, '&gt;')
56
+ .replace(/"/g, '&quot;');
57
+ }
58
+
59
+ export function remarkableWireframe(
60
+ options: WireframePluginOptions = {}
61
+ ): (md: Remarkable) => void {
62
+ return (md: Remarkable) => {
63
+ const rules = md.renderer.rules;
64
+
65
+ // Override fence rule completely
66
+ rules.fence = (
67
+ tokens: RemarkableToken[],
68
+ idx: number,
69
+ _opts: Record<string, unknown>,
70
+ _env: Record<string, unknown>
71
+ ): string => {
72
+ const token = tokens[idx];
73
+ const lang = token.params || '';
74
+
75
+ if (lang === 'wireframe' || lang === 'wf') {
76
+ return renderWireframe(token.content, options);
77
+ }
78
+
79
+ // Render other code blocks with syntax highlighting class
80
+ const langClass = lang ? ` class="language-${escapeHtml(lang)}"` : '';
81
+ return `<pre><code${langClass}>${escapeHtml(token.content)}</code></pre>\n`;
82
+ };
83
+ };
84
+ }