@humanspeak/svelte-markdown 0.6.0 → 0.6.8

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.
Files changed (113) hide show
  1. package/README.md +45 -38
  2. package/dist/Parser.svelte +12 -7
  3. package/dist/SvelteMarkdown.svelte +10 -8
  4. package/dist/SvelteMarkdown.svelte.d.ts +1 -1
  5. package/dist/renderers/Blockquote.svelte.d.ts +3 -2
  6. package/dist/renderers/Br.svelte.d.ts +3 -2
  7. package/dist/renderers/Code.svelte.d.ts +3 -2
  8. package/dist/renderers/Codespan.svelte.d.ts +3 -2
  9. package/dist/renderers/Del.svelte.d.ts +3 -2
  10. package/dist/renderers/Em.svelte.d.ts +3 -2
  11. package/dist/renderers/Heading.svelte.d.ts +3 -2
  12. package/dist/renderers/Image.svelte.d.ts +3 -2
  13. package/dist/renderers/Link.svelte.d.ts +3 -2
  14. package/dist/renderers/List.svelte.d.ts +3 -2
  15. package/dist/renderers/ListItem.svelte.d.ts +3 -2
  16. package/dist/renderers/Paragraph.svelte.d.ts +3 -2
  17. package/dist/renderers/Strong.svelte.d.ts +3 -2
  18. package/dist/renderers/Table.svelte.d.ts +3 -2
  19. package/dist/renderers/TableBody.svelte.d.ts +3 -2
  20. package/dist/renderers/TableCell.svelte.d.ts +4 -3
  21. package/dist/renderers/TableHead.svelte.d.ts +3 -2
  22. package/dist/renderers/TableRow.svelte.d.ts +3 -2
  23. package/dist/renderers/Text.svelte.d.ts +3 -2
  24. package/dist/renderers/html/A.svelte +1 -1
  25. package/dist/renderers/html/Abbr.svelte +1 -1
  26. package/dist/renderers/html/Address.svelte +1 -1
  27. package/dist/renderers/html/Article.svelte +1 -1
  28. package/dist/renderers/html/Aside.svelte +1 -1
  29. package/dist/renderers/html/Audio.svelte +1 -1
  30. package/dist/renderers/html/B.svelte +1 -1
  31. package/dist/renderers/html/Bdi.svelte +1 -1
  32. package/dist/renderers/html/Bdo.svelte +1 -1
  33. package/dist/renderers/html/Blockquote.svelte +1 -1
  34. package/dist/renderers/html/Button.svelte +1 -1
  35. package/dist/renderers/html/Canvas.svelte +1 -1
  36. package/dist/renderers/html/Cite.svelte +1 -1
  37. package/dist/renderers/html/Code.svelte +1 -1
  38. package/dist/renderers/html/Datalist.svelte +1 -1
  39. package/dist/renderers/html/Dd.svelte +1 -1
  40. package/dist/renderers/html/Del.svelte +1 -1
  41. package/dist/renderers/html/Details.svelte +1 -1
  42. package/dist/renderers/html/Dfn.svelte +1 -1
  43. package/dist/renderers/html/Dialog.svelte +1 -1
  44. package/dist/renderers/html/Div.svelte +1 -1
  45. package/dist/renderers/html/Dl.svelte +1 -1
  46. package/dist/renderers/html/Dt.svelte +1 -1
  47. package/dist/renderers/html/Em.svelte +1 -1
  48. package/dist/renderers/html/Embed.svelte +1 -1
  49. package/dist/renderers/html/Fieldset.svelte +1 -1
  50. package/dist/renderers/html/Footer.svelte +1 -1
  51. package/dist/renderers/html/Form.svelte +1 -1
  52. package/dist/renderers/html/H1.svelte +1 -1
  53. package/dist/renderers/html/H2.svelte +1 -1
  54. package/dist/renderers/html/H3.svelte +1 -1
  55. package/dist/renderers/html/H4.svelte +1 -1
  56. package/dist/renderers/html/H5.svelte +1 -1
  57. package/dist/renderers/html/H6.svelte +1 -1
  58. package/dist/renderers/html/Header.svelte +1 -1
  59. package/dist/renderers/html/Hgroup.svelte +1 -1
  60. package/dist/renderers/html/Hr.svelte +1 -3
  61. package/dist/renderers/html/I.svelte +1 -1
  62. package/dist/renderers/html/Iframe.svelte +1 -1
  63. package/dist/renderers/html/Img.svelte +1 -3
  64. package/dist/renderers/html/Input.svelte +1 -1
  65. package/dist/renderers/html/Kbd.svelte +1 -1
  66. package/dist/renderers/html/Label.svelte +1 -1
  67. package/dist/renderers/html/Legend.svelte +1 -1
  68. package/dist/renderers/html/Li.svelte +1 -1
  69. package/dist/renderers/html/Main.svelte +1 -1
  70. package/dist/renderers/html/Mark.svelte +1 -1
  71. package/dist/renderers/html/Menu.svelte +1 -1
  72. package/dist/renderers/html/Meter.svelte +1 -1
  73. package/dist/renderers/html/Nav.svelte +1 -1
  74. package/dist/renderers/html/Ol.svelte +1 -1
  75. package/dist/renderers/html/Optgroup.svelte +1 -1
  76. package/dist/renderers/html/Option.svelte +1 -1
  77. package/dist/renderers/html/Output.svelte +1 -1
  78. package/dist/renderers/html/P.svelte +1 -1
  79. package/dist/renderers/html/Param.svelte +1 -1
  80. package/dist/renderers/html/Picture.svelte +1 -1
  81. package/dist/renderers/html/Pre.svelte +1 -1
  82. package/dist/renderers/html/Progress.svelte +1 -1
  83. package/dist/renderers/html/S.svelte +1 -1
  84. package/dist/renderers/html/Samp.svelte +1 -1
  85. package/dist/renderers/html/Section.svelte +1 -1
  86. package/dist/renderers/html/Select.svelte +1 -1
  87. package/dist/renderers/html/Small.svelte +1 -1
  88. package/dist/renderers/html/Source.svelte +1 -1
  89. package/dist/renderers/html/Span.svelte +1 -1
  90. package/dist/renderers/html/Strong.svelte +1 -1
  91. package/dist/renderers/html/Sub.svelte +1 -1
  92. package/dist/renderers/html/Summary.svelte +1 -1
  93. package/dist/renderers/html/Table.svelte +1 -1
  94. package/dist/renderers/html/Tbody.svelte +1 -1
  95. package/dist/renderers/html/Td.svelte +1 -1
  96. package/dist/renderers/html/Textarea.svelte +1 -3
  97. package/dist/renderers/html/Tfoot.svelte +1 -1
  98. package/dist/renderers/html/Th.svelte +1 -1
  99. package/dist/renderers/html/Thead.svelte +1 -1
  100. package/dist/renderers/html/Tr.svelte +1 -1
  101. package/dist/renderers/html/Track.svelte +1 -1
  102. package/dist/renderers/html/U.svelte +1 -1
  103. package/dist/renderers/html/Ul.svelte +1 -1
  104. package/dist/renderers/html/Var.svelte +1 -1
  105. package/dist/renderers/html/index.js +108 -2
  106. package/dist/utils/markdown-parser.d.ts +28 -3
  107. package/dist/utils/token-cleanup.d.ts +13 -2
  108. package/dist/utils/token-cleanup.js +191 -52
  109. package/package.json +24 -23
  110. package/dist/renderers/html/Object.svelte +0 -12
  111. package/dist/renderers/html/Object.svelte.d.ts +0 -7
  112. package/dist/renderers/html/Video.svelte +0 -12
  113. package/dist/renderers/html/Video.svelte.d.ts +0 -7
@@ -3,7 +3,7 @@
3
3
 
4
4
  interface Props {
5
5
  children?: Snippet
6
- attributes?: Record<string, any>
6
+ attributes?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
7
7
  }
8
8
 
9
9
  const { children, attributes }: Props = $props()
@@ -3,7 +3,7 @@
3
3
 
4
4
  interface Props {
5
5
  children?: Snippet
6
- attributes?: Record<string, any>
6
+ attributes?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
7
7
  }
8
8
 
9
9
  const { children, attributes }: Props = $props()
@@ -3,7 +3,7 @@
3
3
 
4
4
  interface Props {
5
5
  children?: Snippet
6
- attributes?: Record<string, any>
6
+ attributes?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
7
7
  }
8
8
 
9
9
  const { children, attributes }: Props = $props()
@@ -1,61 +1,167 @@
1
1
  import A from './A.svelte';
2
+ import Abbr from './Abbr.svelte';
3
+ import Address from './Address.svelte';
4
+ import Article from './Article.svelte';
5
+ import Aside from './Aside.svelte';
6
+ import Audio from './Audio.svelte';
7
+ import B from './B.svelte';
8
+ import Bdi from './Bdi.svelte';
9
+ import Bdo from './Bdo.svelte';
2
10
  import Blockquote from './Blockquote.svelte';
11
+ import Button from './Button.svelte';
12
+ import Canvas from './Canvas.svelte';
13
+ import Cite from './Cite.svelte';
3
14
  import Code from './Code.svelte';
15
+ import Datalist from './Datalist.svelte';
16
+ import Dd from './Dd.svelte';
4
17
  import Del from './Del.svelte';
18
+ import Details from './Details.svelte';
19
+ import Dfn from './Dfn.svelte';
20
+ import Dialog from './Dialog.svelte';
21
+ import Div from './Div.svelte';
22
+ import Dl from './Dl.svelte';
23
+ import Dt from './Dt.svelte';
5
24
  import Em from './Em.svelte';
25
+ import Embed from './Embed.svelte';
26
+ import Fieldset from './Fieldset.svelte';
27
+ import Footer from './Footer.svelte';
28
+ import Form from './Form.svelte';
6
29
  import H1 from './H1.svelte';
7
30
  import H2 from './H2.svelte';
8
31
  import H3 from './H3.svelte';
9
32
  import H4 from './H4.svelte';
10
33
  import H5 from './H5.svelte';
11
34
  import H6 from './H6.svelte';
35
+ import Header from './Header.svelte';
36
+ import Hgroup from './Hgroup.svelte';
12
37
  import Hr from './Hr.svelte';
13
38
  import I from './I.svelte';
39
+ import Iframe from './Iframe.svelte';
14
40
  import Img from './Img.svelte';
41
+ import Input from './Input.svelte';
42
+ import Kbd from './Kbd.svelte';
43
+ import Label from './Label.svelte';
44
+ import Legend from './Legend.svelte';
15
45
  import Li from './Li.svelte';
46
+ import Main from './Main.svelte';
47
+ import Mark from './Mark.svelte';
48
+ import Menu from './Menu.svelte';
49
+ import Meter from './Meter.svelte';
50
+ import Nav from './Nav.svelte';
16
51
  import Ol from './Ol.svelte';
52
+ import Optgroup from './Optgroup.svelte';
53
+ import Option from './Option.svelte';
54
+ import Output from './Output.svelte';
17
55
  import P from './P.svelte';
56
+ import Param from './Param.svelte';
57
+ import Picture from './Picture.svelte';
18
58
  import Pre from './Pre.svelte';
59
+ import Progress from './Progress.svelte';
60
+ import S from './S.svelte';
61
+ import Samp from './Samp.svelte';
62
+ import Section from './Section.svelte';
63
+ import Select from './Select.svelte';
64
+ import Small from './Small.svelte';
65
+ import Source from './Source.svelte';
66
+ import Span from './Span.svelte';
19
67
  import Strong from './Strong.svelte';
20
68
  import Sub from './Sub.svelte';
69
+ import Summary from './Summary.svelte';
21
70
  import Sup from './Sup.svelte';
22
71
  import Table from './Table.svelte';
23
72
  import Tbody from './Tbody.svelte';
24
73
  import Td from './Td.svelte';
74
+ import Textarea from './Textarea.svelte';
25
75
  import Tfoot from './Tfoot.svelte';
26
76
  import Th from './Th.svelte';
27
77
  import Thead from './Thead.svelte';
28
78
  import Tr from './Tr.svelte';
79
+ import Track from './Track.svelte';
80
+ import U from './U.svelte';
29
81
  import Ul from './Ul.svelte';
82
+ import Var from './Var.svelte';
30
83
  export const Html = {
31
84
  a: A,
85
+ abbr: Abbr,
86
+ address: Address,
87
+ article: Article,
88
+ aside: Aside,
89
+ audio: Audio,
90
+ b: B,
91
+ bdi: Bdi,
92
+ bdo: Bdo,
32
93
  blockquote: Blockquote,
94
+ button: Button,
95
+ canvas: Canvas,
96
+ cite: Cite,
33
97
  code: Code,
98
+ datalist: Datalist,
99
+ dd: Dd,
34
100
  del: Del,
101
+ details: Details,
102
+ dfn: Dfn,
103
+ dialog: Dialog,
104
+ div: Div,
105
+ dl: Dl,
106
+ dt: Dt,
35
107
  em: Em,
108
+ embed: Embed,
109
+ fieldset: Fieldset,
110
+ footer: Footer,
111
+ form: Form,
36
112
  h1: H1,
37
113
  h2: H2,
38
114
  h3: H3,
39
115
  h4: H4,
40
116
  h5: H5,
41
117
  h6: H6,
118
+ header: Header,
119
+ hgroup: Hgroup,
42
120
  hr: Hr,
43
121
  i: I,
122
+ iframe: Iframe,
44
123
  img: Img,
124
+ input: Input,
125
+ kbd: Kbd,
126
+ label: Label,
127
+ legend: Legend,
45
128
  li: Li,
129
+ main: Main,
130
+ mark: Mark,
131
+ menu: Menu,
132
+ meter: Meter,
133
+ nav: Nav,
46
134
  ol: Ol,
135
+ optgroup: Optgroup,
136
+ option: Option,
137
+ output: Output,
47
138
  p: P,
139
+ param: Param,
140
+ picture: Picture,
48
141
  pre: Pre,
142
+ progress: Progress,
143
+ s: S,
144
+ samp: Samp,
145
+ section: Section,
146
+ select: Select,
147
+ small: Small,
148
+ source: Source,
149
+ span: Span,
49
150
  strong: Strong,
50
151
  sub: Sub,
152
+ summary: Summary,
51
153
  sup: Sup,
52
154
  table: Table,
53
155
  tbody: Tbody,
54
156
  td: Td,
157
+ textarea: Textarea,
158
+ tfoot: Tfoot,
55
159
  th: Th,
56
160
  thead: Thead,
57
161
  tr: Tr,
58
- tfoot: Tfoot,
59
- ul: Ul
162
+ track: Track,
163
+ u: U,
164
+ ul: Ul,
165
+ var: Var
60
166
  };
61
167
  export default Html;
@@ -1,11 +1,36 @@
1
1
  export { default as Slugger } from 'github-slugger';
2
2
  export { Lexer, type Token, type Tokens, type TokensList } from 'marked';
3
- import { type HtmlRenderers } from '../renderers/html/index.js';
4
3
  import type { Component } from 'svelte';
4
+ import { type HtmlRenderers } from '../renderers/html/index.js';
5
+ /**
6
+ * Type definition for markdown renderers
7
+ * Maps each markdown element to its corresponding Svelte component
8
+ */
9
+ export type RendererComponent = Component<any, any, any> | undefined | null;
5
10
  export type Renderers = {
6
11
  html: HtmlRenderers;
7
- } & {
8
- [key: string]: Component<any> | null;
12
+ heading: RendererComponent;
13
+ paragraph: RendererComponent;
14
+ blockquote: RendererComponent;
15
+ code: RendererComponent;
16
+ list: RendererComponent;
17
+ listitem: RendererComponent;
18
+ hr: RendererComponent;
19
+ table: RendererComponent;
20
+ tablehead: RendererComponent;
21
+ tablebody: RendererComponent;
22
+ tablerow: RendererComponent;
23
+ tablecell: RendererComponent;
24
+ text: RendererComponent;
25
+ link: RendererComponent;
26
+ image: RendererComponent;
27
+ em: RendererComponent;
28
+ strong: RendererComponent;
29
+ codespan: RendererComponent;
30
+ br: RendererComponent;
31
+ del: RendererComponent;
32
+ orderedlistitem: RendererComponent;
33
+ unorderedlistitem: RendererComponent;
9
34
  };
10
35
  export declare const defaultRenderers: Renderers;
11
36
  export type SvelteMarkdownOptions = {
@@ -1,6 +1,17 @@
1
- import type { Token, TokensList } from 'marked';
1
+ import type { Token } from 'marked';
2
+ /**
3
+ * Determines if a string contains an HTML opening or closing tag
4
+ * @param raw - The string to check for HTML tags
5
+ * @returns Object containing the tag name and whether it's an opening tag, or null if no tag found
6
+ */
2
7
  export declare const isHtmlOpenTag: (raw: string) => {
3
8
  tag: string;
4
9
  isOpening: boolean;
5
10
  } | null;
6
- export declare const shrinkHtmlTokens: (tokens: Token[] | TokensList) => Token[];
11
+ /**
12
+ * Main function to process and shrink HTML tokens
13
+ * Breaks down complex HTML structures into manageable tokens
14
+ * @param tokens - Array of tokens to process
15
+ * @returns Processed array of tokens with nested structure
16
+ */
17
+ export declare const shrinkHtmlTokens: (tokens: Token[]) => Token[];
@@ -1,72 +1,211 @@
1
- // Cache the regex pattern
1
+ import { Parser } from 'htmlparser2';
2
+ /**
3
+ * Regular expression pattern to match HTML tags
4
+ * Matches both opening and closing tags with optional attributes
5
+ * Example matches: <div>, </div>, <img src="...">, <input type="text"/>
6
+ */
2
7
  const HTML_TAG_PATTERN = /<\/?([a-zA-Z][a-zA-Z0-9-]{0,})(?:\s+[^>]*)?>/;
3
8
  const htmlTagRegex = new RegExp(HTML_TAG_PATTERN);
9
+ /**
10
+ * Determines if a string contains an HTML opening or closing tag
11
+ * @param raw - The string to check for HTML tags
12
+ * @returns Object containing the tag name and whether it's an opening tag, or null if no tag found
13
+ */
4
14
  export const isHtmlOpenTag = (raw) => {
5
- // Use test() first as it's faster than match()
15
+ // First check if the string contains any HTML tags at all (faster than full regex match)
6
16
  if (!htmlTagRegex.test(raw))
7
17
  return null;
18
+ // If we found a tag, extract its name and check if it's an opening tag
8
19
  const match = raw.match(HTML_TAG_PATTERN);
9
20
  if (!match)
10
21
  return null;
11
22
  return { tag: match[1], isOpening: !raw.startsWith('</') };
12
23
  };
24
+ /**
25
+ * Extracts HTML attributes from a tag string
26
+ * @param raw - The raw HTML tag string (e.g., '<div class="example" id="test">')
27
+ * @returns An object containing key-value pairs of attributes
28
+ */
29
+ const extractAttributes = (raw) => {
30
+ const attributes = {};
31
+ // Match pattern: attribute="value" or attribute='value'
32
+ const attributeRegex = /(\w+)=["']([^"']*?)["']/g;
33
+ let match;
34
+ // Continue finding matches until we've processed all attributes
35
+ while ((match = attributeRegex.exec(raw)) !== null) {
36
+ const [, key, value] = match;
37
+ attributes[key] = value.trim();
38
+ }
39
+ return attributes;
40
+ };
41
+ /**
42
+ * Parses an HTML string into an array of tokens
43
+ * Uses htmlparser2 to properly handle nested tags and text content
44
+ * @param html - The HTML string to parse
45
+ * @returns Array of tokens representing the HTML structure
46
+ */
47
+ const parseHtmlBlock = (html) => {
48
+ const tokens = [];
49
+ // Buffer for accumulating text content between tags
50
+ let currentText = '';
51
+ const parser = new Parser({
52
+ // Called when an opening tag is encountered (<div>, <span>, etc.)
53
+ onopentag: (name, attributes) => {
54
+ // If we have accumulated any text, create a text token first
55
+ if (currentText.trim()) {
56
+ tokens.push({
57
+ type: 'text',
58
+ raw: currentText,
59
+ text: currentText
60
+ });
61
+ currentText = '';
62
+ }
63
+ // Create a token for the opening tag with its attributes
64
+ tokens.push({
65
+ type: 'html',
66
+ raw: `<${name}${Object.entries(attributes)
67
+ .map(([key, value]) => ` ${key}="${value}"`)
68
+ .join('')}>`,
69
+ tag: name,
70
+ attributes
71
+ });
72
+ },
73
+ // Called for text content between tags
74
+ ontext: (text) => {
75
+ currentText += text;
76
+ },
77
+ // Called when a closing tag is encountered (</div>, </span>, etc.)
78
+ onclosetag: (name) => {
79
+ // Push any accumulated text before the closing tag
80
+ if (currentText.trim()) {
81
+ tokens.push({
82
+ type: 'text',
83
+ raw: currentText,
84
+ text: currentText
85
+ });
86
+ currentText = '';
87
+ }
88
+ // Create a token for the closing tag
89
+ tokens.push({
90
+ type: 'html',
91
+ raw: `</${name}>`,
92
+ tag: name
93
+ });
94
+ }
95
+ });
96
+ // Process the HTML string
97
+ parser.write(html);
98
+ parser.end();
99
+ return tokens;
100
+ };
101
+ /**
102
+ * Checks if an HTML string contains multiple tags
103
+ * Used to determine if further parsing is needed
104
+ * @param html - The HTML string to check
105
+ * @returns boolean indicating if multiple tags are present
106
+ */
107
+ const containsMultipleTags = (html) => {
108
+ // Count the number of opening tags (excluding self-closing)
109
+ const openingTags = html.match(/<[a-zA-Z][^>]*>/g) || [];
110
+ const closingTags = html.match(/<\/[a-zA-Z][^>]*>/g) || [];
111
+ return openingTags.length > 1 || closingTags.length > 1;
112
+ };
113
+ /**
114
+ * Main function to process and shrink HTML tokens
115
+ * Breaks down complex HTML structures into manageable tokens
116
+ * @param tokens - Array of tokens to process
117
+ * @returns Processed array of tokens with nested structure
118
+ */
13
119
  export const shrinkHtmlTokens = (tokens) => {
14
120
  const result = [];
15
- const len = tokens.length;
16
- for (let i = 0; i < len; i++) {
17
- const currentToken = { ...tokens[i] };
18
- // Handle nested tokens first
19
- if ('tokens' in currentToken && currentToken.tokens) {
20
- currentToken.tokens = shrinkHtmlTokens(currentToken.tokens);
21
- result.push(currentToken);
22
- continue;
121
+ for (const token of tokens) {
122
+ if (token.type === 'html' && containsMultipleTags(token.raw)) {
123
+ // Parse HTML with multiple tags into separate tokens
124
+ result.push(...parseHtmlBlock(token.raw));
23
125
  }
24
- // Check for HTML pattern
25
- if (currentToken.type === 'html') {
26
- const openTag = isHtmlOpenTag(currentToken.raw);
27
- if (openTag?.isOpening) {
28
- // Search forward for the next matching closing tag
29
- let closingIndex = -1;
30
- let depth = 0;
31
- for (let j = i + 1; j < len; j++) {
32
- const potentialTag = isHtmlOpenTag(tokens[j].raw);
33
- if (potentialTag?.tag === openTag.tag) {
34
- if (potentialTag.isOpening) {
35
- depth++;
36
- }
37
- else if (depth === 0) {
38
- closingIndex = j;
39
- break;
40
- }
41
- else {
42
- depth--;
43
- }
44
- }
45
- }
46
- if (closingIndex !== -1) {
47
- // Collect all content between tags
48
- const contentTokens = tokens.slice(i + 1, closingIndex);
49
- result.push({
50
- type: 'html',
51
- raw: currentToken.raw,
52
- text: currentToken.raw.replace(/<|>/g, ''),
53
- tokens: shrinkHtmlTokens(contentTokens),
54
- tag: openTag.tag,
55
- attributes: Object.fromEntries((currentToken.raw.match(/\s+([^>]*)/)?.[1] || '')
56
- .split(/\s+/)
57
- .filter(Boolean)
58
- .map((attr) => {
59
- const [key, value] = attr.split('=');
60
- return [key, value ? value.replace(/['"]/g, '') : true];
61
- }))
62
- });
63
- // Skip ahead past the closing tag
64
- i = closingIndex;
126
+ else {
127
+ result.push(token);
128
+ }
129
+ }
130
+ // Then process the tokens as before
131
+ return processHtmlTokens(result);
132
+ };
133
+ /**
134
+ * Processes HTML tokens to create a nested structure
135
+ * Handles matching opening and closing tags, maintains proper nesting
136
+ * and preserves attributes
137
+ *
138
+ * @param tokens - Array of tokens to process
139
+ * @returns Processed array of tokens with proper nesting structure
140
+ *
141
+ * @example
142
+ * Input tokens: [
143
+ * { type: 'html', raw: '<div>' },
144
+ * { type: 'text', raw: 'Hello' },
145
+ * { type: 'html', raw: '</div>' }
146
+ * ]
147
+ * Output: [
148
+ * { type: 'html', tag: 'div', tokens: [
149
+ * { type: 'text', raw: 'Hello' }
150
+ * ]}
151
+ * ]
152
+ */
153
+ const processHtmlTokens = (tokens) => {
154
+ const result = [];
155
+ // Stack to keep track of opening tags and their positions
156
+ const stack = [];
157
+ for (let i = 0; i < tokens.length; i++) {
158
+ const token = tokens[i];
159
+ // If token contains nested tokens, process them recursively
160
+ if ('tokens' in token && Array.isArray(token.tokens)) {
161
+ token.tokens = processHtmlTokens(token.tokens);
162
+ }
163
+ if (token.type === 'html') {
164
+ const tagInfo = isHtmlOpenTag(token.raw);
165
+ if (!tagInfo) {
166
+ // If we can't parse the tag, just add it as-is
167
+ result.push(token);
168
+ continue;
169
+ }
170
+ if (tagInfo.isOpening) {
171
+ // For opening tags, push to stack and add to result
172
+ stack.push({ tag: tagInfo.tag, startIndex: result.length });
173
+ result.push(token);
174
+ }
175
+ else {
176
+ // For closing tags, try to match with last opening tag
177
+ const lastOpening = stack.pop();
178
+ if (!lastOpening || lastOpening.tag !== tagInfo.tag) {
179
+ // If no matching opening tag, add closing tag as-is
180
+ result.push(token);
65
181
  continue;
66
182
  }
183
+ // Found matching tags - create nested structure
184
+ const startIndex = lastOpening.startIndex;
185
+ // Remove all tokens between opening and closing tags
186
+ const innerTokens = result.splice(startIndex + 1, result.length - startIndex - 1);
187
+ // Remove the opening tag
188
+ const openingToken = result.pop();
189
+ // Extract attributes from opening tag
190
+ const attributes = extractAttributes(openingToken.raw);
191
+ // Create new nested token structure
192
+ result.push({
193
+ type: 'html',
194
+ raw: openingToken.raw,
195
+ tag: tagInfo.tag,
196
+ tokens: processHtmlTokens(innerTokens),
197
+ attributes
198
+ });
67
199
  }
68
200
  }
69
- result.push(currentToken);
201
+ else {
202
+ // Non-HTML tokens are added as-is
203
+ result.push(token);
204
+ }
205
+ }
206
+ // If we have unclosed tags, return original tokens
207
+ if (stack.length > 0) {
208
+ return tokens;
70
209
  }
71
210
  return result;
72
211
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-markdown",
3
3
  "description": "A markdown renderer for Svelte",
4
- "version": "0.6.0",
4
+ "version": "0.6.8",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
7
7
  "build": "vite build && npm run package",
@@ -53,41 +53,42 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "github-slugger": "^2.0.0",
56
- "marked": "^15.0.0"
56
+ "htmlparser2": "^10.0.0",
57
+ "marked": "^15.0.4"
57
58
  },
58
59
  "devDependencies": {
59
- "@eslint/eslintrc": "^3.1.0",
60
- "@eslint/js": "^9.14.0",
60
+ "@eslint/eslintrc": "^3.2.0",
61
+ "@eslint/js": "^9.17.0",
61
62
  "@sveltejs/adapter-auto": "^3.3.1",
62
- "@sveltejs/kit": "^2.8.0",
63
+ "@sveltejs/kit": "^2.15.1",
63
64
  "@sveltejs/package": "^2.3.7",
64
- "@sveltejs/vite-plugin-svelte": "^4.0.0",
65
+ "@sveltejs/vite-plugin-svelte": "^5.0.3",
65
66
  "@testing-library/jest-dom": "^6.6.3",
66
- "@testing-library/svelte": "^5.2.4",
67
+ "@testing-library/svelte": "^5.2.6",
67
68
  "@testing-library/user-event": "^14.5.2",
68
- "@types/node": "^22.9.0",
69
- "@typescript-eslint/eslint-plugin": "^8.14.0",
70
- "@typescript-eslint/parser": "^8.14.0",
71
- "@vitest/coverage-v8": "^2.1.4",
72
- "eslint": "^9.14.0",
69
+ "@types/node": "^22.10.3",
70
+ "@typescript-eslint/eslint-plugin": "^8.19.0",
71
+ "@typescript-eslint/parser": "^8.19.0",
72
+ "@vitest/coverage-v8": "^2.1.8",
73
+ "eslint": "^9.17.0",
73
74
  "eslint-config-prettier": "^9.1.0",
74
- "eslint-plugin-svelte": "^2.46.0",
75
- "globals": "^15.12.0",
75
+ "eslint-plugin-svelte": "^2.46.1",
76
+ "globals": "^15.14.0",
76
77
  "jsdom": "^25.0.1",
77
- "prettier": "^3.3.3",
78
+ "prettier": "^3.4.2",
78
79
  "prettier-plugin-organize-imports": "^4.1.0",
79
- "prettier-plugin-svelte": "^3.2.8",
80
- "prettier-plugin-tailwindcss": "^0.6.8",
80
+ "prettier-plugin-svelte": "^3.3.2",
81
+ "prettier-plugin-tailwindcss": "^0.6.9",
81
82
  "publint": "^0.2.12",
82
- "svelte": "^5.1.15",
83
- "svelte-check": "^4.0.7",
84
- "typescript": "^5.6.3",
85
- "vite": "^5.4.11",
86
- "vitest": "^2.1.4"
83
+ "svelte": "^5.16.0",
84
+ "svelte-check": "^4.1.1",
85
+ "typescript": "^5.7.2",
86
+ "vite": "^6.0.6",
87
+ "vitest": "^2.1.8"
87
88
  },
88
89
  "overrides": {
89
90
  "@sveltejs/kit": {
90
91
  "cookie": "^0.7.0"
91
92
  }
92
93
  }
93
- }
94
+ }
@@ -1,12 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte'
3
-
4
- interface Props {
5
- children?: Snippet
6
- attributes?: Record<string, any>
7
- }
8
-
9
- const { children, attributes }: Props = $props()
10
- </script>
11
-
12
- <object {...attributes}>{@render children?.()}</object>
@@ -1,7 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- declare const Object: import("svelte").Component<{
3
- children?: Snippet;
4
- attributes?: Record<string, any>;
5
- }, {}, "">;
6
- type Object = ReturnType<typeof Object>;
7
- export default Object;
@@ -1,12 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte'
3
-
4
- interface Props {
5
- children?: Snippet
6
- attributes?: Record<string, any>
7
- }
8
-
9
- const { children, attributes }: Props = $props()
10
- </script>
11
-
12
- <video {...attributes}>{@render children?.()}</video>
@@ -1,7 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- declare const Video: import("svelte").Component<{
3
- children?: Snippet;
4
- attributes?: Record<string, any>;
5
- }, {}, "">;
6
- type Video = ReturnType<typeof Video>;
7
- export default Video;