svelte-streamdown 1.0.9 → 2.0.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.
Files changed (37) hide show
  1. package/README.md +144 -135
  2. package/dist/Block.svelte +14 -54
  3. package/dist/Elements/Alert.svelte +1 -2
  4. package/dist/Elements/Code.svelte +2 -4
  5. package/dist/Elements/Code.svelte.d.ts +0 -2
  6. package/dist/Elements/Element.svelte +70 -25
  7. package/dist/Elements/FootnoteRef.svelte +19 -6
  8. package/dist/Elements/Image.svelte +1 -1
  9. package/dist/Elements/Link.svelte +1 -1
  10. package/dist/Elements/Math.svelte +13 -25
  11. package/dist/Elements/Math.svelte.d.ts +0 -2
  12. package/dist/Elements/Mermaid.svelte +105 -115
  13. package/dist/Elements/Mermaid.svelte.d.ts +0 -2
  14. package/dist/Elements/Table.svelte +1 -1
  15. package/dist/Streamdown.d.ts +52 -10
  16. package/dist/Streamdown.js +19 -1
  17. package/dist/Streamdown.svelte +3 -41
  18. package/dist/Streamdown.svelte.d.ts +1 -20
  19. package/dist/marked/index.d.ts +3 -20
  20. package/dist/marked/index.js +27 -19
  21. package/dist/marked/marked-alert.d.ts +2 -2
  22. package/dist/marked/marked-alert.js +6 -19
  23. package/dist/marked/marked-footnotes.js +1 -2
  24. package/dist/marked/marked-list.d.ts +2 -2
  25. package/dist/marked/marked-list.js +28 -11
  26. package/dist/marked/marked-math.js +1 -1
  27. package/dist/marked/marked-subsup.d.ts +1 -1
  28. package/dist/marked/marked-subsup.js +2 -1
  29. package/dist/marked/marked-table.d.ts +59 -0
  30. package/dist/marked/marked-table.js +495 -0
  31. package/dist/theme.d.ts +24 -6
  32. package/dist/theme.js +22 -10
  33. package/dist/utils/panzoom.svelte.d.ts +17 -18
  34. package/dist/utils/panzoom.svelte.js +0 -2
  35. package/dist/utils/useClickOutside.svelte.js +0 -1
  36. package/dist/utils/useKeyDown.svelte.js +4 -1
  37. package/package.json +1 -2
@@ -1,32 +1,40 @@
1
- import { Lexer, marked } from 'marked';
1
+ import { Lexer, Marked } from 'marked';
2
2
  import markedAlert, {} from './marked-alert.js';
3
3
  import markedFootnote, {} from './marked-footnotes.js';
4
4
  import { markedMath } from './marked-math.js';
5
5
  import markedSubSup, {} from './marked-subsup.js';
6
6
  import { markedList } from './marked-list.js';
7
+ import { markedTable } from './marked-table.js';
8
+ const completeLexer = new Marked({
9
+ // Enable GFM features but disable built-in tables
10
+ gfm: true,
11
+ tokenizer: {
12
+ table: () => {
13
+ return false;
14
+ }
15
+ }
16
+ })
17
+ .use(markedAlert())
18
+ .use(markedFootnote())
19
+ .use(markedMath())
20
+ .use(markedSubSup())
21
+ .use(markedList())
22
+ .use(markedTable());
23
+ const blockLexer = new Lexer({
24
+ gfm: true,
25
+ extensions: {
26
+ childTokens: {},
27
+ renderers: {},
28
+ block: [markedFootnote().extensions[0].tokenizer, markedTable().extensions[0].tokenizer]
29
+ }
30
+ });
7
31
  export const lex = (markdown) => {
8
- return marked
9
- .use({
10
- gfm: true
11
- })
12
- .use(markedAlert())
13
- .use(markedFootnote())
14
- .use(markedMath())
15
- .use(markedSubSup())
16
- .use(markedList())
32
+ return completeLexer
17
33
  .lexer(markdown)
18
34
  .filter((token) => token.type !== 'space' && token.type !== 'footnote');
19
35
  };
20
36
  export const parseBlocks = (markdown) => {
21
- const lexer = new Lexer({
22
- gfm: true,
23
- extensions: {
24
- childTokens: {},
25
- renderers: {},
26
- block: [markedFootnote().extensions[0].tokenizer]
27
- }
28
- });
29
- return lexer.blockTokens(markdown, []).reduce((acc, block) => {
37
+ return blockLexer.blockTokens(markdown, []).reduce((acc, block) => {
30
38
  if (block.type === 'space' || block.type === 'footnote') {
31
39
  return acc;
32
40
  }
@@ -1,4 +1,4 @@
1
- import type { Token, Tokenizer } from 'marked';
1
+ import type { Tokenizer, Tokens } from 'marked';
2
2
  import type { TokenizerExtensionFunction } from 'marked';
3
3
  type variantType = 'note' | 'tip' | 'important' | 'warning' | 'caution';
4
4
  export declare function createSyntaxPattern(type: variantType): string;
@@ -9,7 +9,7 @@ export default function markedAlert(): {
9
9
  tokenizer: TokenizerExtensionFunction;
10
10
  }>;
11
11
  };
12
- export declare function processAlertToken(token: Token, tokenizer: Tokenizer): void;
12
+ export declare function processAlertToken(token: Tokens.Blockquote, tokenizer: Tokenizer): void;
13
13
  export type AlertToken = {
14
14
  type: 'alert';
15
15
  variant: variantType;
@@ -1,7 +1,10 @@
1
1
  import { Lexer } from 'marked';
2
2
  const variants = ['note', 'tip', 'important', 'warning', 'caution'];
3
+ // export function createSyntaxPattern(type: variantType): string {
4
+ // return `^(?:\\[!${type.toUpperCase()}])\\s*?\n*`;
5
+ // }
3
6
  export function createSyntaxPattern(type) {
4
- return `^(?:\\[!${type.toUpperCase()}])\\s*?\n*`;
7
+ return `^\\s*\\[!${type.toUpperCase()}\\]\\s+`;
5
8
  }
6
9
  export default function markedAlert() {
7
10
  const defaultLexer = new Lexer({ gfm: true });
@@ -27,25 +30,9 @@ export function processAlertToken(token, tokenizer) {
27
30
  const matchedVariant = variants.find((type) => new RegExp(createSyntaxPattern(type)).test(('text' in token && token.text) || ''));
28
31
  if (!matchedVariant)
29
32
  return;
30
- const typeRegexp = new RegExp(createSyntaxPattern(matchedVariant));
31
- // Transform the token into an alert token
32
- Object.assign(token, {
33
- type: 'alert',
34
- variant: matchedVariant
35
- });
36
- const firstLine = ('tokens' in token && token.tokens?.[0]);
37
- const firstLineText = firstLine.raw?.replace(typeRegexp, '').trim();
38
- const lines = token.raw
39
- .split('>')
40
- .filter((line) => line.trim().replace(typeRegexp, '').length > 0);
41
- const tokens = lines.map((line) => {
42
- const lineTokens = tokenizer.lexer.inlineTokens(line);
43
- return {
44
- type: 'paragraph',
45
- tokens: lineTokens
46
- };
33
+ const tokens = token.tokens.map((token) => {
34
+ return tokenizer.lexer.blockTokens(token.raw.replaceAll(`[!${matchedVariant.toUpperCase()}]`, '').trim(), [])[0];
47
35
  });
48
- // Transform the token into an alert token
49
36
  Object.assign(token, {
50
37
  type: 'alert',
51
38
  variant: matchedVariant,
@@ -1,5 +1,5 @@
1
1
  import { lex } from './index.js';
2
- import { StreamdownContext } from '../Streamdown.svelte';
2
+ import { StreamdownContext } from '../Streamdown.js';
3
3
  import { getContext } from 'svelte';
4
4
  const safeGetContext = () => {
5
5
  try {
@@ -72,7 +72,6 @@ export default function markedFootnote() {
72
72
  if (match) {
73
73
  const [raw, label] = match;
74
74
  const footnote = maps.footnotes.get(label);
75
- console.log({ footnote });
76
75
  const token = {
77
76
  type: 'footnoteRef',
78
77
  raw,
@@ -16,9 +16,9 @@ export interface ListToken {
16
16
  type: 'list';
17
17
  raw: string;
18
18
  ordered: boolean;
19
- listType: 'decimal' | 'lower-alpha' | 'upper-alpha' | 'lower-roman' | 'upper-roman';
19
+ listType: 'decimal' | 'lower-alpha' | 'upper-alpha' | 'lower-roman' | 'upper-roman' | null;
20
20
  loose: boolean;
21
- items: ListItemToken[];
21
+ tokens: ListItemToken[];
22
22
  }
23
23
  /**
24
24
  * Token representing an item in an extended list.
@@ -27,25 +27,28 @@ export const romanLower = '(?:c|xc|l?x{0,3}(?:ix|iv|v?i{0,3}))';
27
27
  export const bulletPattern = `(?:[*+-]|(?:\\d{1,9}|[a-zA-Z]|${romanUpper}|${romanLower})[.)])`;
28
28
  export const rule = `^( {0,3}${bulletPattern})([ \\t][^\\n]+?)?(?:\\n|$)`;
29
29
  function finalizeList(list, lexer) {
30
- if (list.items.length === 0)
30
+ if (list.tokens.length === 0)
31
31
  return;
32
32
  // Trim trailing newline from last item
33
- const lastItem = list.items[list.items.length - 1];
33
+ const lastItem = list.tokens[list.tokens.length - 1];
34
34
  lastItem.raw = lastItem.raw.trimEnd();
35
35
  lastItem.text = lastItem.text.trimEnd();
36
36
  list.raw = list.raw.trimEnd();
37
37
  // Handle child tokens
38
- for (const item of list.items) {
38
+ for (const item of list.tokens) {
39
39
  lexer.state.top = false;
40
40
  item.tokens = lexer.blockTokens(item.text, []);
41
41
  }
42
42
  // Mark list as loose if needed
43
43
  if (list.loose) {
44
- for (const item of list.items) {
44
+ for (const item of list.tokens) {
45
45
  item.loose = true;
46
46
  }
47
47
  }
48
48
  }
49
+ function escapeForRegex(s) {
50
+ return s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
51
+ }
49
52
  export function markedList() {
50
53
  return {
51
54
  extensions: [
@@ -60,7 +63,7 @@ export function markedList() {
60
63
  const isOrdered = bullet !== '*' && bullet !== '-' && bullet !== '+';
61
64
  let bull;
62
65
  let type = '';
63
- let expectedValue = 1;
66
+ let expectedValue = null;
64
67
  // Detect list type (Roman, alphabetic, numeric)
65
68
  if (isOrdered) {
66
69
  if (bullet.match(new RegExp(`^${romanUpper}[.)]$`))) {
@@ -86,14 +89,15 @@ export function markedList() {
86
89
  }
87
90
  else {
88
91
  bull = this.lexer.options.pedantic ? bullet : '[*+-]';
92
+ bull = this.lexer.options.pedantic ? escapeForRegex(bullet) : '[*+-]';
89
93
  }
90
94
  const list = {
91
95
  type: 'list',
92
96
  raw: '',
93
97
  ordered: isOrdered,
94
- listType: type,
98
+ listType: isOrdered ? type : null,
95
99
  loose: false,
96
- items: []
100
+ tokens: []
97
101
  };
98
102
  // Get next list item
99
103
  const itemRegex = new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`);
@@ -189,7 +193,21 @@ export function markedList() {
189
193
  else if (type === 'lower-roman' || type === 'upper-roman') {
190
194
  value = romanToInt(bullet.slice(0, -1));
191
195
  }
192
- list.items.push({
196
+ // Handle expectedValue initialization and validation
197
+ let skipped = false;
198
+ if (isOrdered) {
199
+ if (expectedValue === null) {
200
+ // First item: set expectedValue to this item's value (or 1 if parsing failed)
201
+ expectedValue = value ?? 1;
202
+ }
203
+ else {
204
+ // Subsequent items: check if value matches expected
205
+ skipped = value !== null && value !== expectedValue;
206
+ // Increment expectedValue for next item
207
+ expectedValue += 1;
208
+ }
209
+ }
210
+ list.tokens.push({
193
211
  type: 'list_item',
194
212
  raw,
195
213
  task: !!isTask,
@@ -197,13 +215,12 @@ export function markedList() {
197
215
  loose: false,
198
216
  text: itemContents,
199
217
  value,
200
- skipped: isOrdered && value !== expectedValue,
218
+ skipped,
201
219
  tokens: []
202
220
  });
203
- expectedValue = (value ?? 0) + 1;
204
221
  list.raw += raw;
205
222
  }
206
- if (list.items.length === 0)
223
+ if (list.tokens.length === 0)
207
224
  return null;
208
225
  // Finalize the list
209
226
  finalizeList(list, this.lexer);
@@ -1,6 +1,6 @@
1
1
  const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/;
2
2
  const inlineRuleNonStandard = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1/; // Non-standard, even if there are no spaces before and after $ or $$, try to parse
3
- const blockRule = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/;
3
+ const blockRule = /^(\${1,2})\n((?:\\[\s\S]|[^\\])+?)\n\1(?:\n|$)/;
4
4
  export function markedMath() {
5
5
  return {
6
6
  extensions: [
@@ -4,7 +4,7 @@ export default function markedSubSup(): {
4
4
  name: string;
5
5
  level: 'inline';
6
6
  tokenizer: TokenizerExtensionFunction;
7
- start?: (src: string) => number | void;
7
+ start?: (src: string) => number | undefined;
8
8
  }[];
9
9
  };
10
10
  /**
@@ -7,7 +7,8 @@ export default function markedSubSup() {
7
7
  name: 'sub',
8
8
  level: 'inline',
9
9
  start(src) {
10
- return src.indexOf('~');
10
+ const i = src.indexOf('~');
11
+ return i === -1 ? undefined : i;
11
12
  },
12
13
  tokenizer(src) {
13
14
  const match = src.match(subRule);
@@ -0,0 +1,59 @@
1
+ import type { MarkedExtension } from 'marked';
2
+ export interface SpanTableOptions {
3
+ useTheadTbody?: boolean;
4
+ useTfoot?: boolean;
5
+ detectFooter?: boolean;
6
+ maxColspan?: number | null;
7
+ handleComplexSpans?: boolean;
8
+ }
9
+ interface BaseCell {
10
+ rowspan: number;
11
+ colspan: number;
12
+ text: string;
13
+ position?: number;
14
+ tokens?: unknown[];
15
+ rowSpanTarget?: BaseCell;
16
+ complexRowSpan?: boolean;
17
+ relatedCell?: BaseCell;
18
+ align?: string | null;
19
+ }
20
+ export interface TH extends BaseCell {
21
+ type: 'th';
22
+ }
23
+ export interface TD extends BaseCell {
24
+ type: 'td';
25
+ }
26
+ export interface THeadRow {
27
+ type: 'tr';
28
+ tokens: TH[];
29
+ }
30
+ export interface TRow {
31
+ type: 'tr';
32
+ tokens: TD[];
33
+ }
34
+ export interface THead {
35
+ type: 'thead';
36
+ tokens: THeadRow[];
37
+ }
38
+ export interface TBody {
39
+ type: 'tbody';
40
+ tokens: TRow[];
41
+ }
42
+ export interface TFoot {
43
+ type: 'tfoot';
44
+ tokens: TRow[];
45
+ }
46
+ export type TableSection = THead | TBody | TFoot;
47
+ export interface TableToken {
48
+ type: 'table';
49
+ tokens: TableSection[];
50
+ raw: string;
51
+ }
52
+ interface WorkingCell extends BaseCell {
53
+ }
54
+ type WorkingRow = WorkingCell[];
55
+ export declare const DEFAULT_OPTIONS: Required<SpanTableOptions>;
56
+ export declare const getTableCell: (text: string, cell: BaseCell, type: "th" | "td", align: string | null) => string;
57
+ export declare const splitCells: (tableRow: string, count: number | null, prevRow?: WorkingRow | null, maxColspan?: number | null) => WorkingRow;
58
+ export declare function markedTable(options?: SpanTableOptions): MarkedExtension;
59
+ export {};