@humanspeak/svelte-markdown 0.8.8 → 0.8.9

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.
@@ -109,44 +109,13 @@
109
109
  {#each row ?? [] as cells, i (i)}
110
110
  {@const { align: _align, ...cellRest } = rest}
111
111
  <renderers.tablecell
112
+ {...cellRest}
112
113
  header={false}
113
114
  align={(rest.align as string[])[i]}
114
- {...cellRest}
115
115
  >
116
- {#if cells.tokens?.[0]?.type === 'html'}
117
- {@const token = cells.tokens[0] as Token & {
118
- tag: string
119
- tokens?: Token[]
120
- }}
121
- {@const { tag, ...localRest } = token}
122
- {@const htmlTag = tag as keyof typeof Html}
123
- {#if renderers.html && htmlTag in renderers.html}
124
- {@const HtmlComponent =
125
- renderers.html[
126
- htmlTag as keyof typeof renderers.html
127
- ]}
128
- {#if HtmlComponent}
129
- <HtmlComponent {...token}>
130
- {#if token.tokens?.length}
131
- <Parser
132
- tokens={token.tokens}
133
- {renderers}
134
- {...Object.fromEntries(
135
- Object.entries(
136
- localRest
137
- ).filter(
138
- ([key]) =>
139
- key !== 'attributes'
140
- )
141
- )}
142
- />
143
- {/if}
144
- </HtmlComponent>
145
- {/if}
146
- {/if}
147
- {:else}
148
- <Parser tokens={cells.tokens} {renderers} />
149
- {/if}
116
+ {#each cells.tokens ?? [] as cellToken, index (index)}
117
+ <Parser {...cellRest} {...cellToken} {renderers} />
118
+ {/each}
150
119
  </renderers.tablecell>
151
120
  {/each}
152
121
  </renderers.tablerow>
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ attributes?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
4
+ }
5
+
6
+ const { attributes = {} }: Props = $props()
7
+ </script>
8
+
9
+ <br {...attributes} />
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ attributes?: Record<string, any>;
3
+ }
4
+ declare const Br: import("svelte").Component<Props, {}, "">;
5
+ type Br = ReturnType<typeof Br>;
6
+ export default Br;
@@ -8,6 +8,7 @@ import B from './B.svelte';
8
8
  import Bdi from './Bdi.svelte';
9
9
  import Bdo from './Bdo.svelte';
10
10
  import Blockquote from './Blockquote.svelte';
11
+ import Br from './Br.svelte';
11
12
  import Button from './Button.svelte';
12
13
  import Canvas from './Canvas.svelte';
13
14
  import Cite from './Cite.svelte';
@@ -91,6 +92,7 @@ export const Html = {
91
92
  bdi: Bdi,
92
93
  bdo: Bdo,
93
94
  blockquote: Blockquote,
95
+ br: Br,
94
96
  button: Button,
95
97
  canvas: Canvas,
96
98
  cite: Cite,
@@ -94,6 +94,7 @@ export declare const containsMultipleTags: (html: string) => boolean;
94
94
  *
95
95
  * Key features:
96
96
  * - Breaks down complex HTML structures into atomic tokens
97
+ * - Formats self-closing tags with proper syntax (e.g., <br> -> <br/>)
97
98
  * - Maintains attribute information
98
99
  * - Preserves proper nesting relationships
99
100
  * - Handles malformed HTML gracefully
@@ -12,6 +12,11 @@ import * as htmlparser2 from 'htmlparser2';
12
12
  */
13
13
  const HTML_TAG_PATTERN = /<\/?([a-zA-Z][a-zA-Z0-9-]{0,})(?:\s+[^>]*)?>/;
14
14
  const htmlTagRegex = new RegExp(HTML_TAG_PATTERN);
15
+ /**
16
+ * Regex pattern for self-closing HTML tags.
17
+ * @const {RegExp}
18
+ */
19
+ const SELF_CLOSING_TAGS = /^(br|hr|img|input|link|meta|area|base|col|embed|keygen|param|source|track|wbr)$/i;
15
20
  /**
16
21
  * Analyzes a string to determine if it contains an HTML tag and its characteristics.
17
22
  *
@@ -37,6 +42,33 @@ export const isHtmlOpenTag = (raw) => {
37
42
  return null;
38
43
  return { tag: match[1], isOpening: !raw.startsWith('</') };
39
44
  };
45
+ /**
46
+ * Formats individual HTML tokens to ensure self-closing tags are properly formatted.
47
+ * This handles cases like <br> -> <br/> without affecting the token structure.
48
+ *
49
+ * @param {Token} token - HTML token to format
50
+ * @returns {Token} Formatted token with proper self-closing syntax
51
+ */
52
+ const formatSelfClosingHtmlToken = (token) => {
53
+ // Extract tag name from raw HTML
54
+ const tagMatch = token.raw.match(/<\/?([a-zA-Z][a-zA-Z0-9-]*)/i);
55
+ if (!tagMatch)
56
+ return token;
57
+ const tagName = tagMatch[1];
58
+ if (!SELF_CLOSING_TAGS.test(tagName))
59
+ return token;
60
+ // If it's a self-closing tag and doesn't already end with />, format it properly
61
+ if (!token.raw.endsWith('/>')) {
62
+ const formattedRaw = token.raw.replace(/\s*>$/, '/>');
63
+ return {
64
+ ...token,
65
+ raw: formattedRaw,
66
+ tag: tagName,
67
+ attributes: extractAttributes(token.raw)
68
+ };
69
+ }
70
+ return token;
71
+ };
40
72
  /**
41
73
  * Parses HTML attributes from a tag string into a structured object.
42
74
  * Handles both single and double quoted attributes.
@@ -117,7 +149,6 @@ export const extractAttributes = (raw) => {
117
149
  export const parseHtmlBlock = (html) => {
118
150
  const tokens = [];
119
151
  let currentText = '';
120
- const selfClosingTags = /^(br|hr|img|input|link|meta|area|base|col|embed|keygen|param|source|track|wbr)$/i;
121
152
  const openTags = [];
122
153
  const parser = new htmlparser2.Parser({
123
154
  onopentag: (name, attributes) => {
@@ -129,8 +160,7 @@ export const parseHtmlBlock = (html) => {
129
160
  });
130
161
  currentText = '';
131
162
  }
132
- openTags.push(name);
133
- if (selfClosingTags.test(name)) {
163
+ if (SELF_CLOSING_TAGS.test(name)) {
134
164
  tokens.push({
135
165
  type: 'html',
136
166
  raw: `<${name}${Object.entries(attributes)
@@ -141,6 +171,7 @@ export const parseHtmlBlock = (html) => {
141
171
  });
142
172
  }
143
173
  else {
174
+ openTags.push(name);
144
175
  tokens.push({
145
176
  type: 'html',
146
177
  raw: `<${name}${Object.entries(attributes)
@@ -165,7 +196,7 @@ export const parseHtmlBlock = (html) => {
165
196
  }
166
197
  // Only add closing tag if we found its opening tag
167
198
  // and it's not a self-closing tag
168
- if (openTags.includes(name) && !selfClosingTags.test(name)) {
199
+ if (openTags.includes(name) && !SELF_CLOSING_TAGS.test(name)) {
169
200
  if (html.includes(`</${name}>`)) {
170
201
  tokens.push({
171
202
  type: 'html',
@@ -219,6 +250,7 @@ export const containsMultipleTags = (html) => {
219
250
  *
220
251
  * Key features:
221
252
  * - Breaks down complex HTML structures into atomic tokens
253
+ * - Formats self-closing tags with proper syntax (e.g., <br> -> <br/>)
222
254
  * - Maintains attribute information
223
255
  * - Preserves proper nesting relationships
224
256
  * - Handles malformed HTML gracefully
@@ -251,6 +283,7 @@ export const shrinkHtmlTokens = (tokens) => {
251
283
  else if (token.type === 'table') {
252
284
  // Process header cells
253
285
  if (token.header) {
286
+ // @ts-expect-error: expected any
254
287
  token.header = token.header.map((cell) => ({
255
288
  ...cell,
256
289
  tokens: cell.tokens ? shrinkHtmlTokens(cell.tokens) : []
@@ -258,7 +291,10 @@ export const shrinkHtmlTokens = (tokens) => {
258
291
  }
259
292
  // Process row cells
260
293
  if (token.rows) {
261
- token.rows = token.rows.map((row) => row.map((cell) => ({
294
+ // @ts-expect-error: expected any
295
+ token.rows = token.rows.map((row) =>
296
+ // @ts-expect-error: expected any
297
+ row.map((cell) => ({
262
298
  ...cell,
263
299
  tokens: cell.tokens ? shrinkHtmlTokens(cell.tokens) : []
264
300
  })));
@@ -269,6 +305,11 @@ export const shrinkHtmlTokens = (tokens) => {
269
305
  // Parse HTML with multiple tags into separate tokens
270
306
  result.push(...parseHtmlBlock(token.raw));
271
307
  }
308
+ else if (token.type === 'html') {
309
+ // Format self-closing tags properly (e.g., <br> -> <br/>)
310
+ const formattedToken = formatSelfClosingHtmlToken(token);
311
+ result.push(formattedToken);
312
+ }
272
313
  else {
273
314
  result.push(token);
274
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-markdown",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "A powerful, customizable markdown renderer for Svelte with TypeScript support",
5
5
  "keywords": [
6
6
  "svelte",
@@ -77,47 +77,47 @@
77
77
  "dependencies": {
78
78
  "github-slugger": "^2.0.0",
79
79
  "htmlparser2": "^10.0.0",
80
- "marked": "^16.0.0"
80
+ "marked": "^16.1.1"
81
81
  },
82
82
  "devDependencies": {
83
83
  "@eslint/compat": "^1.3.1",
84
- "@eslint/js": "^9.31.0",
84
+ "@eslint/js": "^9.32.0",
85
85
  "@playwright/test": "^1.54.1",
86
86
  "@sveltejs/adapter-auto": "^6.0.1",
87
- "@sveltejs/kit": "^2.22.5",
88
- "@sveltejs/package": "^2.3.12",
89
- "@sveltejs/vite-plugin-svelte": "^6.0.0",
90
- "@testing-library/jest-dom": "^6.6.3",
87
+ "@sveltejs/kit": "^2.26.1",
88
+ "@sveltejs/package": "^2.4.0",
89
+ "@sveltejs/vite-plugin-svelte": "^6.1.0",
90
+ "@testing-library/jest-dom": "^6.6.4",
91
91
  "@testing-library/svelte": "^5.2.8",
92
92
  "@testing-library/user-event": "^14.6.1",
93
- "@types/node": "^24.0.13",
94
- "@typescript-eslint/eslint-plugin": "^8.36.0",
95
- "@typescript-eslint/parser": "^8.36.0",
93
+ "@types/node": "^24.1.0",
94
+ "@typescript-eslint/eslint-plugin": "^8.38.0",
95
+ "@typescript-eslint/parser": "^8.38.0",
96
96
  "@vitest/coverage-v8": "^3.2.4",
97
- "eslint": "^9.31.0",
98
- "eslint-config-prettier": "^10.1.5",
97
+ "eslint": "^9.32.0",
98
+ "eslint-config-prettier": "^10.1.8",
99
99
  "eslint-plugin-import": "^2.32.0",
100
- "eslint-plugin-svelte": "^3.10.1",
100
+ "eslint-plugin-svelte": "^3.11.0",
101
101
  "eslint-plugin-unused-imports": "^4.1.4",
102
102
  "globals": "^16.3.0",
103
103
  "jsdom": "^26.1.0",
104
104
  "prettier": "^3.6.2",
105
- "prettier-plugin-organize-imports": "^4.1.0",
105
+ "prettier-plugin-organize-imports": "^4.2.0",
106
106
  "prettier-plugin-svelte": "^3.4.0",
107
107
  "prettier-plugin-tailwindcss": "^0.6.14",
108
108
  "publint": "^0.3.12",
109
- "svelte": "^5.35.6",
110
- "svelte-check": "^4.2.2",
109
+ "svelte": "^5.37.0",
110
+ "svelte-check": "^4.3.0",
111
111
  "typescript": "^5.8.3",
112
- "typescript-eslint": "^8.36.0",
113
- "vite": "^7.0.4",
112
+ "typescript-eslint": "^8.38.0",
113
+ "vite": "^7.0.6",
114
114
  "vitest": "^3.2.4"
115
115
  },
116
116
  "peerDependencies": {
117
117
  "svelte": "^5.0.0"
118
118
  },
119
119
  "volta": {
120
- "node": "22.17.0"
120
+ "node": "22.17.1"
121
121
  },
122
122
  "publishConfig": {
123
123
  "access": "public"