@rohal12/spindle 0.36.0 → 0.36.2
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/dist/pkg/format.js +1 -1
- package/package.json +1 -1
- package/src/markup/ast.ts +4 -1
- package/src/markup/render.tsx +15 -5
- package/src/markup/tokenizer.ts +9 -68
package/package.json
CHANGED
package/src/markup/ast.ts
CHANGED
|
@@ -152,7 +152,10 @@ export function buildAST(tokens: Token[]): ASTNode[] {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
const top = stack[stack.length - 1]!;
|
|
155
|
-
if (
|
|
155
|
+
if (
|
|
156
|
+
top.node.type !== 'html' ||
|
|
157
|
+
top.node.tag.toLowerCase() !== token.tag.toLowerCase()
|
|
158
|
+
) {
|
|
156
159
|
const expected =
|
|
157
160
|
top.node.type === 'html'
|
|
158
161
|
? `</${top.node.tag}>`
|
package/src/markup/render.tsx
CHANGED
|
@@ -23,6 +23,7 @@ const defaultUpdater: LocalsUpdater = {
|
|
|
23
23
|
export const LocalsValuesContext = createContext<Record<string, unknown>>({});
|
|
24
24
|
export const LocalsUpdateContext = createContext<LocalsUpdater>(defaultUpdater);
|
|
25
25
|
export const NobrContext = createContext(false);
|
|
26
|
+
export const SvgContext = createContext(false);
|
|
26
27
|
export const WidgetChildrenContext = createContext<ASTNode[] | null>(null);
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -85,16 +86,25 @@ function HtmlNodeRenderer({ node }: { node: HtmlNode }) {
|
|
|
85
86
|
const resolve = useInterpolate();
|
|
86
87
|
const nobr = useContext(NobrContext);
|
|
87
88
|
const locals = useContext(LocalsValuesContext);
|
|
89
|
+
const inSvg = useContext(SvgContext);
|
|
88
90
|
const attrs: Record<string, unknown> = {};
|
|
89
91
|
for (const [k, v] of Object.entries(node.attributes)) {
|
|
90
92
|
attrs[k] = resolve(v) ?? v;
|
|
91
93
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
const isSvgRoot = node.tag.toLowerCase() === 'svg';
|
|
95
|
+
// Inside SVG, skip markdown processing — markdown wraps content in <p> tags
|
|
96
|
+
// which break the SVG namespace and produce zero-dimension elements.
|
|
97
|
+
const children =
|
|
95
98
|
node.children.length > 0
|
|
96
|
-
?
|
|
97
|
-
|
|
99
|
+
? inSvg || isSvgRoot
|
|
100
|
+
? renderInlineNodes(node.children)
|
|
101
|
+
: renderNodes(node.children, { nobr, locals })
|
|
102
|
+
: undefined;
|
|
103
|
+
const element = h(node.tag, attrs, children);
|
|
104
|
+
return isSvgRoot ? (
|
|
105
|
+
<SvgContext.Provider value={true}>{element}</SvgContext.Provider>
|
|
106
|
+
) : (
|
|
107
|
+
element
|
|
98
108
|
);
|
|
99
109
|
}
|
|
100
110
|
|
package/src/markup/tokenizer.ts
CHANGED
|
@@ -53,68 +53,8 @@ export type Token =
|
|
|
53
53
|
| VariableToken
|
|
54
54
|
| HtmlToken;
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'article',
|
|
59
|
-
'aside',
|
|
60
|
-
'b',
|
|
61
|
-
'blockquote',
|
|
62
|
-
'br',
|
|
63
|
-
'caption',
|
|
64
|
-
'code',
|
|
65
|
-
'col',
|
|
66
|
-
'colgroup',
|
|
67
|
-
'dd',
|
|
68
|
-
'del',
|
|
69
|
-
'details',
|
|
70
|
-
'dfn',
|
|
71
|
-
'div',
|
|
72
|
-
'dl',
|
|
73
|
-
'dt',
|
|
74
|
-
'em',
|
|
75
|
-
'figcaption',
|
|
76
|
-
'figure',
|
|
77
|
-
'footer',
|
|
78
|
-
'h1',
|
|
79
|
-
'h2',
|
|
80
|
-
'h3',
|
|
81
|
-
'h4',
|
|
82
|
-
'h5',
|
|
83
|
-
'h6',
|
|
84
|
-
'header',
|
|
85
|
-
'hr',
|
|
86
|
-
'i',
|
|
87
|
-
'img',
|
|
88
|
-
'ins',
|
|
89
|
-
'kbd',
|
|
90
|
-
'li',
|
|
91
|
-
'main',
|
|
92
|
-
'mark',
|
|
93
|
-
'nav',
|
|
94
|
-
'ol',
|
|
95
|
-
'p',
|
|
96
|
-
'pre',
|
|
97
|
-
'q',
|
|
98
|
-
's',
|
|
99
|
-
'samp',
|
|
100
|
-
'section',
|
|
101
|
-
'small',
|
|
102
|
-
'span',
|
|
103
|
-
'strong',
|
|
104
|
-
'sub',
|
|
105
|
-
'summary',
|
|
106
|
-
'sup',
|
|
107
|
-
'table',
|
|
108
|
-
'tbody',
|
|
109
|
-
'td',
|
|
110
|
-
'tfoot',
|
|
111
|
-
'th',
|
|
112
|
-
'thead',
|
|
113
|
-
'tr',
|
|
114
|
-
'u',
|
|
115
|
-
'ul',
|
|
116
|
-
'wbr',
|
|
117
|
-
]);
|
|
56
|
+
/** Tag name must start with a letter (covers standard and custom elements). */
|
|
57
|
+
const VALID_TAG_START = /[a-zA-Z]/;
|
|
118
58
|
|
|
119
59
|
const HTML_VOID_TAGS = new Set(['br', 'col', 'hr', 'img', 'wbr']);
|
|
120
60
|
|
|
@@ -661,13 +601,14 @@ export function tokenize(input: string): Token[] {
|
|
|
661
601
|
const isClose = input[j] === '/';
|
|
662
602
|
if (isClose) j++;
|
|
663
603
|
|
|
664
|
-
// Read tag name
|
|
604
|
+
// Read tag name (letters, digits, hyphens for custom elements)
|
|
665
605
|
const tagStart = j;
|
|
666
|
-
while (j < input.length && /[a-zA-Z0-9]/.test(input[j]!)) j++;
|
|
667
|
-
const tag = input.slice(tagStart, j)
|
|
606
|
+
while (j < input.length && /[a-zA-Z0-9-]/.test(input[j]!)) j++;
|
|
607
|
+
const tag = input.slice(tagStart, j);
|
|
608
|
+
const tagLower = tag.toLowerCase();
|
|
668
609
|
|
|
669
|
-
//
|
|
670
|
-
if (tag &&
|
|
610
|
+
// Valid tag name must start with a letter
|
|
611
|
+
if (tag && VALID_TAG_START.test(tag[0]!)) {
|
|
671
612
|
if (isClose) {
|
|
672
613
|
// Closing tag: skip whitespace, expect >
|
|
673
614
|
while (j < input.length && /\s/.test(input[j]!)) j++;
|
|
@@ -692,7 +633,7 @@ export function tokenize(input: string): Token[] {
|
|
|
692
633
|
const parsed = parseHtmlAttributes(input, j);
|
|
693
634
|
j = parsed.endIdx;
|
|
694
635
|
|
|
695
|
-
let isSelfClose = HTML_VOID_TAGS.has(
|
|
636
|
+
let isSelfClose = HTML_VOID_TAGS.has(tagLower);
|
|
696
637
|
if (input[j] === '/') {
|
|
697
638
|
isSelfClose = true;
|
|
698
639
|
j++;
|