svelte-streamdown 2.3.0 → 2.3.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/README.md +102 -87
- package/dist/Block.svelte +1 -1
- package/dist/Elements/Element.svelte +12 -2
- package/dist/Elements/index.d.ts +0 -1
- package/dist/Elements/index.js +0 -1
- package/dist/Streamdown.svelte +9 -11
- package/dist/context.svelte.d.ts +8 -2
- package/dist/context.svelte.js +4 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/marked/index.d.ts +15 -3
- package/dist/marked/index.js +21 -33
- package/dist/marked/marked-alert.d.ts +2 -8
- package/dist/marked/marked-alert.js +14 -20
- package/dist/marked/marked-br.d.ts +2 -8
- package/dist/marked/marked-br.js +15 -21
- package/dist/marked/marked-footnotes.d.ts +2 -9
- package/dist/marked/marked-footnotes.js +55 -58
- package/dist/marked/marked-hr.d.ts +2 -8
- package/dist/marked/marked-hr.js +18 -24
- package/dist/marked/marked-list.d.ts +3 -9
- package/dist/marked/marked-list.js +176 -182
- package/dist/marked/marked-math.d.ts +2 -9
- package/dist/marked/marked-math.js +110 -64
- package/dist/marked/marked-subsup.d.ts +3 -9
- package/dist/marked/marked-subsup.js +38 -43
- package/dist/marked/marked-table.d.ts +2 -9
- package/dist/marked/marked-table.js +114 -123
- package/dist/theme.js +2 -2
- package/package.json +1 -2
|
@@ -4,75 +4,121 @@ const blockRule = /^(\$\$)(?:\n((?:\\[\s\S]|[^\\])+?)\n\1(?:\n|$)|([^$\n]+?)\1(?
|
|
|
4
4
|
// Inline math: handles both single ($) and double ($$) dollar delimiters
|
|
5
5
|
// Avoids matching currency by checking context and requiring proper content
|
|
6
6
|
const inlineRule = /^(\${1,2})(?!\$)((?:[^$\n]|\\\$)*?)\1(?!\d)/;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
// Enhanced currency detection patterns
|
|
8
|
+
const currencyPatterns = {
|
|
9
|
+
// Simple price patterns: $123, $123.45, $1,234.56
|
|
10
|
+
simplePrice: /^\d{1,3}(?:,\d{3})*(?:\.\d{2})?$/,
|
|
11
|
+
// Multiple prices or numbers: "123, 456", "123.45, 678.90", "123 or 456"
|
|
12
|
+
multipleNumbers: /^\d+(?:[.,]\d+)*(?:\s*[,;]\s*\d+(?:[.,]\d+)*)+$/,
|
|
13
|
+
// Price ranges: "123-456", "123 - 456", "123 to 456"
|
|
14
|
+
priceRange: /^\d+(?:\.\d{2})?\s*(?:-|to|or)\s*\d+(?:\.\d{2})?$/i,
|
|
15
|
+
// Common currency words nearby (check surrounding context)
|
|
16
|
+
currencyContext: /(?:price|cost|dollar|euro|pound|yen|currency|pay|buy|sell|expensive|cheap)/i
|
|
17
|
+
};
|
|
18
|
+
export const markedMath = [
|
|
19
|
+
{
|
|
20
|
+
name: 'math',
|
|
21
|
+
level: 'block',
|
|
22
|
+
tokenizer(src) {
|
|
23
|
+
const match = src.match(blockRule);
|
|
24
|
+
if (match) {
|
|
25
|
+
// match[2] is multiline format, match[3] is single-line format
|
|
26
|
+
const content = (match[2] || match[3]).trim();
|
|
27
|
+
return {
|
|
28
|
+
type: 'math',
|
|
29
|
+
isInline: false,
|
|
30
|
+
displayMode: true,
|
|
31
|
+
raw: match[0],
|
|
32
|
+
text: content
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'math',
|
|
39
|
+
level: 'inline',
|
|
40
|
+
start(src) {
|
|
41
|
+
let index = 0;
|
|
42
|
+
let searchSrc = src;
|
|
43
|
+
while (searchSrc) {
|
|
44
|
+
const dollarIndex = searchSrc.indexOf('$');
|
|
45
|
+
if (dollarIndex === -1) {
|
|
46
|
+
return;
|
|
26
47
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let index = 0;
|
|
33
|
-
let searchSrc = src;
|
|
34
|
-
while (searchSrc) {
|
|
35
|
-
const dollarIndex = searchSrc.indexOf('$');
|
|
36
|
-
if (dollarIndex === -1) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const currentIndex = index + dollarIndex;
|
|
40
|
-
const possibleMath = src.substring(currentIndex);
|
|
41
|
-
// Check if this could be math (not currency)
|
|
42
|
-
if (possibleMath.match(inlineRule)) {
|
|
43
|
-
// Additional check: avoid currency patterns like $5.00
|
|
44
|
-
const beforeChar = currentIndex > 0 ? src[currentIndex - 1] : '';
|
|
45
|
-
const afterDollar = possibleMath.substring(1, 6); // Check first few chars after $
|
|
46
|
-
// Skip if it looks like currency (digit immediately after $ or decimal pattern)
|
|
47
|
-
if (!/^\d+(\.\d{2})?\s/.test(afterDollar)) {
|
|
48
|
-
return currentIndex;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
index += dollarIndex + 1;
|
|
52
|
-
searchSrc = src.substring(index);
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
tokenizer(src) {
|
|
56
|
-
const match = src.match(inlineRule);
|
|
48
|
+
const currentIndex = index + dollarIndex;
|
|
49
|
+
const possibleMath = src.substring(currentIndex);
|
|
50
|
+
// Check if this could be math (not currency)
|
|
51
|
+
if (possibleMath.match(inlineRule)) {
|
|
52
|
+
const match = possibleMath.match(inlineRule);
|
|
57
53
|
if (match) {
|
|
58
|
-
// Additional validation: avoid currency patterns
|
|
59
54
|
const content = match[2];
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
const dollarCount = match[1]; // '$' or '$$'
|
|
56
|
+
// Only apply currency detection to single dollars
|
|
57
|
+
// Double dollars ($$) indicate explicit math intent
|
|
58
|
+
if (dollarCount === '$' && isCurrencyPattern(content, src, currentIndex)) {
|
|
59
|
+
// This looks like currency with single dollars, skip it
|
|
60
|
+
index += dollarIndex + 1;
|
|
61
|
+
searchSrc = src.substring(index);
|
|
62
|
+
continue;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
const isDisplayMode = match[1] === '$$';
|
|
66
|
-
return {
|
|
67
|
-
type: 'math',
|
|
68
|
-
isInline: !isDisplayMode,
|
|
69
|
-
displayMode: isDisplayMode,
|
|
70
|
-
raw: match[0],
|
|
71
|
-
text: content.trim()
|
|
72
|
-
};
|
|
64
|
+
return currentIndex;
|
|
73
65
|
}
|
|
74
66
|
}
|
|
67
|
+
index += dollarIndex + 1;
|
|
68
|
+
searchSrc = src.substring(index);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
tokenizer(src) {
|
|
72
|
+
const match = src.match(inlineRule);
|
|
73
|
+
if (match) {
|
|
74
|
+
const content = match[2];
|
|
75
|
+
const dollarCount = match[1]; // '$' or '$$'
|
|
76
|
+
const isDisplayMode = dollarCount === '$$';
|
|
77
|
+
// Only apply currency detection to single dollars
|
|
78
|
+
// Double dollars ($$) indicate explicit math intent
|
|
79
|
+
if (dollarCount === '$' && isCurrencyPattern(content, src, 0)) {
|
|
80
|
+
// This looks like currency with single dollars, skip it
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
type: 'math',
|
|
85
|
+
isInline: true, // Inline tokenizer always produces inline math
|
|
86
|
+
displayMode: isDisplayMode, // $$ = display mode styling, $ = inline styling
|
|
87
|
+
raw: match[0],
|
|
88
|
+
text: content.trim()
|
|
89
|
+
};
|
|
75
90
|
}
|
|
76
|
-
|
|
77
|
-
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
// Helper function to detect currency patterns
|
|
95
|
+
function isCurrencyPattern(content, fullSrc, dollarIndex) {
|
|
96
|
+
const trimmedContent = content.trim();
|
|
97
|
+
// Check for simple price patterns
|
|
98
|
+
if (currencyPatterns.simplePrice.test(trimmedContent)) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
// Check for multiple numbers/prices pattern (like "199, 199")
|
|
102
|
+
if (currencyPatterns.multipleNumbers.test(trimmedContent)) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
// Check for price ranges
|
|
106
|
+
if (currencyPatterns.priceRange.test(trimmedContent)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
// Check surrounding context for currency-related words
|
|
110
|
+
const contextStart = Math.max(0, dollarIndex - 50);
|
|
111
|
+
const contextEnd = Math.min(fullSrc.length, dollarIndex + content.length + 50);
|
|
112
|
+
const context = fullSrc.substring(contextStart, contextEnd);
|
|
113
|
+
if (currencyPatterns.currencyContext.test(context)) {
|
|
114
|
+
// If currency context is found and content is purely numeric, likely currency
|
|
115
|
+
if (/^\d+(?:[.,]\d+)*$/.test(trimmedContent)) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Additional check: if content is just numbers with common currency formatting
|
|
120
|
+
if (/^\d{1,3}(?:,\d{3})*(?:\.\d{1,2})?$/.test(trimmedContent)) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
78
124
|
}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare
|
|
3
|
-
|
|
4
|
-
name: string;
|
|
5
|
-
level: 'inline';
|
|
6
|
-
tokenizer: TokenizerExtensionFunction;
|
|
7
|
-
start?: TokenizerStartFunction;
|
|
8
|
-
}[];
|
|
9
|
-
};
|
|
1
|
+
import type { Extension } from './index.js';
|
|
2
|
+
export declare const markedSub: Extension;
|
|
3
|
+
export declare const markedSup: Extension;
|
|
10
4
|
/**
|
|
11
5
|
* Represents a subscript token.
|
|
12
6
|
*/
|
|
@@ -1,45 +1,40 @@
|
|
|
1
1
|
const subRule = /^~([^~\s](?:[^~]*[^~\s])?)~/; // ~text~
|
|
2
2
|
const supRule = /^\^([^\^\s](?:[^\^]*[^\^\s])?)\^/; // ^text^
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
]
|
|
44
|
-
};
|
|
45
|
-
}
|
|
3
|
+
export const markedSub = {
|
|
4
|
+
name: 'sub',
|
|
5
|
+
level: 'inline',
|
|
6
|
+
start(src) {
|
|
7
|
+
const i = src.indexOf('~');
|
|
8
|
+
return i === -1 ? undefined : i;
|
|
9
|
+
},
|
|
10
|
+
tokenizer(src) {
|
|
11
|
+
const match = src.match(subRule);
|
|
12
|
+
if (match) {
|
|
13
|
+
return {
|
|
14
|
+
type: 'sub',
|
|
15
|
+
raw: match[0],
|
|
16
|
+
text: match[1],
|
|
17
|
+
tokens: this.lexer.inlineTokens(match[1])
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const markedSup = {
|
|
23
|
+
name: 'sup',
|
|
24
|
+
level: 'inline',
|
|
25
|
+
start(src) {
|
|
26
|
+
const i = src.indexOf('^');
|
|
27
|
+
return i === -1 ? undefined : i;
|
|
28
|
+
},
|
|
29
|
+
tokenizer(src) {
|
|
30
|
+
const match = src.match(supRule);
|
|
31
|
+
if (match) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'sup',
|
|
34
|
+
raw: match[0],
|
|
35
|
+
text: match[1],
|
|
36
|
+
tokens: this.lexer.inlineTokens(match[1])
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Extension } from './index.js';
|
|
2
2
|
export interface SpanTableOptions {
|
|
3
3
|
useTheadTbody?: boolean;
|
|
4
4
|
useTfoot?: boolean;
|
|
@@ -56,12 +56,5 @@ type WorkingRow = WorkingCell[];
|
|
|
56
56
|
export declare const DEFAULT_OPTIONS: Required<SpanTableOptions>;
|
|
57
57
|
export declare const getTableCell: (text: string, cell: BaseCell, type: "th" | "td", align: string | null) => string;
|
|
58
58
|
export declare const splitCells: (tableRow: string, count: number | null, prevRow?: WorkingRow | null, maxColspan?: number | null) => WorkingRow;
|
|
59
|
-
export declare
|
|
60
|
-
extensions: {
|
|
61
|
-
name: string;
|
|
62
|
-
level: 'block' | 'inline';
|
|
63
|
-
start: TokenizerStartFunction;
|
|
64
|
-
tokenizer: TokenizerExtensionFunction;
|
|
65
|
-
}[];
|
|
66
|
-
};
|
|
59
|
+
export declare const markedTable: Extension;
|
|
67
60
|
export {};
|
|
@@ -116,8 +116,7 @@ const processSpans = (cells, count, prevRow = [], maxColspan = null) => {
|
|
|
116
116
|
let cellText = cell.text;
|
|
117
117
|
// Handle Rowspan - cells ending with ^ (but not superscript ^text^)
|
|
118
118
|
// Check if it's a rowspan indicator (single ^ at end) vs superscript (^text^)
|
|
119
|
-
const isRowspanIndicator = cellText.slice(-1) === '^' &&
|
|
120
|
-
!cellText.match(/\^[^^\n\r]+\^$/); // Not a superscript pattern ^text^
|
|
119
|
+
const isRowspanIndicator = cellText.slice(-1) === '^' && !cellText.match(/\^[^^\n\r]+\^$/); // Not a superscript pattern ^text^
|
|
121
120
|
if (isRowspanIndicator && prevRow.length > 0) {
|
|
122
121
|
// Clean the ^ indicator from the cell text
|
|
123
122
|
cell.text = cellText.slice(0, -1).trim();
|
|
@@ -374,129 +373,121 @@ function processRows(headerRows, bodyRows, alignment, colCount, lexer, maxColspa
|
|
|
374
373
|
}
|
|
375
374
|
return tokens;
|
|
376
375
|
}
|
|
376
|
+
const { detectFooter, maxColspan } = DEFAULT_OPTIONS;
|
|
377
377
|
// Adds support for extended tables in marked with row spanning, column spanning,
|
|
378
378
|
// multi-row headers, and column alignment
|
|
379
|
-
export
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
379
|
+
export const markedTable = {
|
|
380
|
+
name: 'table',
|
|
381
|
+
level: 'block',
|
|
382
|
+
start(src) {
|
|
383
|
+
// Check for table with potential header alignment
|
|
384
|
+
let match = src.match(/^\n *([^\n ].*\|.*)\n/);
|
|
385
|
+
if (match)
|
|
386
|
+
return match.index;
|
|
387
|
+
// Check for simple table without header alignment
|
|
388
|
+
match = src.match(/^\n *(\|.*\|)\n/);
|
|
389
|
+
if (match)
|
|
390
|
+
return match.index;
|
|
391
|
+
return undefined;
|
|
392
|
+
},
|
|
393
|
+
tokenizer(src) {
|
|
394
|
+
// Try to match table with header and alignment first
|
|
395
|
+
let regex = new RegExp('^' +
|
|
396
|
+
'([^\\n ].*\\|.*\\n(?: *[^\\s].*\\n)*?)' + // Header
|
|
397
|
+
' {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)(?:\\| *)?' + // Header Align
|
|
398
|
+
'(?:\\n((?:(?! *\\n| {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})' + // Body Cells
|
|
399
|
+
'(?:\\n+|$)| {0,3}#{1,6} | {0,3}>| {4}[^\\n]| {0,3}(?:`{3,}' +
|
|
400
|
+
'(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n| {0,3}(?:[*+-]|1[.)]) |' +
|
|
401
|
+
'<\\/?(?:address|article|aside|base|basefont|blockquote|body|' +
|
|
402
|
+
'caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\\n|\\/?>)|<(?:script|pre|style|textarea|!--)).*(?:\\n|$))*)\\n*|$)');
|
|
403
|
+
let cap = regex.exec(src);
|
|
404
|
+
let hasHeaderAlignment = true;
|
|
405
|
+
// If no match with header alignment, try table without header alignment
|
|
406
|
+
if (!cap) {
|
|
407
|
+
// Simple regex for tables without header alignment
|
|
408
|
+
regex = /^(\|.*\|(?:\n\|.*\|)*)/;
|
|
409
|
+
cap = regex.exec(src);
|
|
410
|
+
hasHeaderAlignment = false;
|
|
411
|
+
}
|
|
412
|
+
if (!cap)
|
|
413
|
+
return undefined;
|
|
414
|
+
// Combine all captured groups to get complete table rows
|
|
415
|
+
let allTableContent = cap[1]; // Headers
|
|
416
|
+
if (cap[2])
|
|
417
|
+
allTableContent += '\n' + cap[2]; // Alignment row
|
|
418
|
+
if (cap[3])
|
|
419
|
+
allTableContent += '\n' + cap[3]; // Body rows
|
|
420
|
+
const allRows = allTableContent.replace(/\n$/, '').split('\n');
|
|
421
|
+
let headerRows = [];
|
|
422
|
+
let bodyRows = [];
|
|
423
|
+
let alignRow = [];
|
|
424
|
+
let alignment = [];
|
|
425
|
+
let colCount = 0;
|
|
426
|
+
if (hasHeaderAlignment) {
|
|
427
|
+
// Traditional table with header and alignment
|
|
428
|
+
// Parse all rows and identify which are headers vs body
|
|
429
|
+
let headerEndIndex = -1;
|
|
430
|
+
// Find the FIRST alignment row (contains dashes/underscores/asterisks)
|
|
431
|
+
for (let i = 0; i < allRows.length; i++) {
|
|
432
|
+
const row = allRows[i].trim();
|
|
433
|
+
const isAlignment = /^ *(\| *)?:?-+:? *(\| *:?-+:? *)*(\| *)?$/.test(row);
|
|
434
|
+
// Check if this row matches alignment pattern (contains only |, spaces, and alignment chars)
|
|
435
|
+
if (isAlignment) {
|
|
436
|
+
headerEndIndex = i;
|
|
437
|
+
alignRow = row.replace(/^ *\| *| *\| *$/g, '').split(/ *\| */);
|
|
438
|
+
break; // Stop at the first alignment row
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (headerEndIndex === -1) {
|
|
442
|
+
// No alignment row found, treat as simple table
|
|
443
|
+
bodyRows = allRows;
|
|
444
|
+
colCount = bodyRows[0].split('|').filter((cell) => cell.trim() !== '').length;
|
|
445
|
+
alignment = new Array(colCount).fill(null);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
// Found alignment row, split headers and body
|
|
449
|
+
// Filter out empty rows and the alignment row itself from headers
|
|
450
|
+
headerRows = allRows.slice(0, headerEndIndex).filter((row) => row.trim() !== '');
|
|
451
|
+
bodyRows = headerEndIndex + 1 < allRows.length ? allRows.slice(headerEndIndex + 1) : [];
|
|
452
|
+
// Use alignment row length as the authoritative column count
|
|
453
|
+
colCount = alignRow.length;
|
|
454
|
+
// Validate that we have a reasonable table structure
|
|
455
|
+
if (colCount === 0)
|
|
396
456
|
return undefined;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
let allTableContent = cap[1]; // Headers
|
|
421
|
-
if (cap[2])
|
|
422
|
-
allTableContent += '\n' + cap[2]; // Alignment row
|
|
423
|
-
if (cap[3])
|
|
424
|
-
allTableContent += '\n' + cap[3]; // Body rows
|
|
425
|
-
const allRows = allTableContent.replace(/\n$/, '').split('\n');
|
|
426
|
-
let headerRows = [];
|
|
427
|
-
let bodyRows = [];
|
|
428
|
-
let alignRow = [];
|
|
429
|
-
let alignment = [];
|
|
430
|
-
let colCount = 0;
|
|
431
|
-
if (hasHeaderAlignment) {
|
|
432
|
-
// Traditional table with header and alignment
|
|
433
|
-
// Parse all rows and identify which are headers vs body
|
|
434
|
-
let headerEndIndex = -1;
|
|
435
|
-
// Find the FIRST alignment row (contains dashes/underscores/asterisks)
|
|
436
|
-
for (let i = 0; i < allRows.length; i++) {
|
|
437
|
-
const row = allRows[i].trim();
|
|
438
|
-
const isAlignment = /^ *(\| *)?:?-+:? *(\| *:?-+:? *)*(\| *)?$/.test(row);
|
|
439
|
-
// Check if this row matches alignment pattern (contains only |, spaces, and alignment chars)
|
|
440
|
-
if (isAlignment) {
|
|
441
|
-
headerEndIndex = i;
|
|
442
|
-
alignRow = row.replace(/^ *\| *| *\| *$/g, '').split(/ *\| */);
|
|
443
|
-
break; // Stop at the first alignment row
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
if (headerEndIndex === -1) {
|
|
447
|
-
// No alignment row found, treat as simple table
|
|
448
|
-
bodyRows = allRows;
|
|
449
|
-
colCount = bodyRows[0].split('|').filter((cell) => cell.trim() !== '').length;
|
|
450
|
-
alignment = new Array(colCount).fill(null);
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
// Found alignment row, split headers and body
|
|
454
|
-
// Filter out empty rows and the alignment row itself from headers
|
|
455
|
-
headerRows = allRows.slice(0, headerEndIndex).filter((row) => row.trim() !== '');
|
|
456
|
-
bodyRows =
|
|
457
|
-
headerEndIndex + 1 < allRows.length ? allRows.slice(headerEndIndex + 1) : [];
|
|
458
|
-
// Use alignment row length as the authoritative column count
|
|
459
|
-
colCount = alignRow.length;
|
|
460
|
-
// Validate that we have a reasonable table structure
|
|
461
|
-
if (colCount === 0)
|
|
462
|
-
return null;
|
|
463
|
-
// Process alignment
|
|
464
|
-
alignment = processAlignment(alignRow);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
else {
|
|
468
|
-
// Table without header alignment - treat all rows as body rows
|
|
469
|
-
bodyRows = allRows;
|
|
470
|
-
const firstRowCells = bodyRows[0].split('|').filter((cell) => cell.trim() !== '');
|
|
471
|
-
colCount = firstRowCells.length;
|
|
472
|
-
alignment = new Array(colCount).fill(null); // No alignment for tables without headers
|
|
473
|
-
}
|
|
474
|
-
// Detect footer alignment row pattern in body rows (only for tables with header alignment)
|
|
475
|
-
let shouldDetectFooter = false;
|
|
476
|
-
let processedBodyRows = bodyRows;
|
|
477
|
-
if (detectFooter && hasHeaderAlignment && bodyRows.length > 0) {
|
|
478
|
-
// Check if any row matches the alignment pattern (contains only dashes, pipes, colons, and spaces)
|
|
479
|
-
for (let i = bodyRows.length - 1; i >= 0; i--) {
|
|
480
|
-
const row = bodyRows[i];
|
|
481
|
-
if (/^ *\| *:?-+:? *(\| *:?-+:? *)*\| *$/.test(row)) {
|
|
482
|
-
// Found footer alignment row - remove it and enable footer detection
|
|
483
|
-
shouldDetectFooter = true;
|
|
484
|
-
processedBodyRows = bodyRows.slice(0, i).concat(bodyRows.slice(i + 1));
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
// Process all rows and create table sections
|
|
490
|
-
const tokens = processRows(headerRows, processedBodyRows, alignment, colCount, this.lexer, maxColspan, shouldDetectFooter);
|
|
491
|
-
const item = {
|
|
492
|
-
type: 'table',
|
|
493
|
-
tokens,
|
|
494
|
-
raw: cap[0],
|
|
495
|
-
align: alignment
|
|
496
|
-
};
|
|
497
|
-
return item;
|
|
457
|
+
// Process alignment
|
|
458
|
+
alignment = processAlignment(alignRow);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
// Table without header alignment - treat all rows as body rows
|
|
463
|
+
bodyRows = allRows;
|
|
464
|
+
const firstRowCells = bodyRows[0].split('|').filter((cell) => cell.trim() !== '');
|
|
465
|
+
colCount = firstRowCells.length;
|
|
466
|
+
alignment = new Array(colCount).fill(null); // No alignment for tables without headers
|
|
467
|
+
}
|
|
468
|
+
// Detect footer alignment row pattern in body rows (only for tables with header alignment)
|
|
469
|
+
let shouldDetectFooter = false;
|
|
470
|
+
let processedBodyRows = bodyRows;
|
|
471
|
+
if (detectFooter && hasHeaderAlignment && bodyRows.length > 0) {
|
|
472
|
+
// Check if any row matches the alignment pattern (contains only dashes, pipes, colons, and spaces)
|
|
473
|
+
for (let i = bodyRows.length - 1; i >= 0; i--) {
|
|
474
|
+
const row = bodyRows[i];
|
|
475
|
+
if (/^ *\| *:?-+:? *(\| *:?-+:? *)*\| *$/.test(row)) {
|
|
476
|
+
// Found footer alignment row - remove it and enable footer detection
|
|
477
|
+
shouldDetectFooter = true;
|
|
478
|
+
processedBodyRows = bodyRows.slice(0, i).concat(bodyRows.slice(i + 1));
|
|
479
|
+
break;
|
|
498
480
|
}
|
|
499
481
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
482
|
+
}
|
|
483
|
+
// Process all rows and create table sections
|
|
484
|
+
const tokens = processRows(headerRows, processedBodyRows, alignment, colCount, this.lexer, maxColspan, shouldDetectFooter);
|
|
485
|
+
const item = {
|
|
486
|
+
type: 'table',
|
|
487
|
+
tokens,
|
|
488
|
+
raw: cap[0],
|
|
489
|
+
align: alignment
|
|
490
|
+
};
|
|
491
|
+
return item;
|
|
492
|
+
}
|
|
493
|
+
};
|
package/dist/theme.js
CHANGED
|
@@ -121,7 +121,7 @@ export const theme = {
|
|
|
121
121
|
base: 'italic'
|
|
122
122
|
},
|
|
123
123
|
del: {
|
|
124
|
-
base: '
|
|
124
|
+
base: 'text-gray-600'
|
|
125
125
|
},
|
|
126
126
|
footnoteRef: {
|
|
127
127
|
base: 'text-gray-600 px-1 py-0.5 rounded-md bg-gray-100/80'
|
|
@@ -250,7 +250,7 @@ export const shadcnTheme = {
|
|
|
250
250
|
base: 'italic text-foreground'
|
|
251
251
|
},
|
|
252
252
|
del: {
|
|
253
|
-
base: '
|
|
253
|
+
base: 'text-muted-foreground'
|
|
254
254
|
},
|
|
255
255
|
footnoteRef: {
|
|
256
256
|
base: 'text-muted-foreground px-1 text-sm inline-block rounded-full bg-muted/80 aspect-square border border-border'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-streamdown",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -73,7 +73,6 @@
|
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@floating-ui/dom": "^1.7.4",
|
|
75
75
|
"clsx": "^2.1.1",
|
|
76
|
-
"html-url-attributes": "^3.0.1",
|
|
77
76
|
"katex": "^0.16.22",
|
|
78
77
|
"marked": "^16.2.1",
|
|
79
78
|
"mermaid": "^11.11.0",
|