svelte-streamdown 0.0.1
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 +58 -0
- package/dist/Block.svelte +34 -0
- package/dist/Block.svelte.d.ts +6 -0
- package/dist/Elements/A.svelte +48 -0
- package/dist/Elements/A.svelte.d.ts +4 -0
- package/dist/Elements/Alert.svelte +78 -0
- package/dist/Elements/Alert.svelte.d.ts +4 -0
- package/dist/Elements/Blockquote.svelte +26 -0
- package/dist/Elements/Blockquote.svelte.d.ts +4 -0
- package/dist/Elements/Code.svelte +123 -0
- package/dist/Elements/Code.svelte.d.ts +4 -0
- package/dist/Elements/Del.svelte +24 -0
- package/dist/Elements/Del.svelte.d.ts +4 -0
- package/dist/Elements/Element.svelte +122 -0
- package/dist/Elements/Element.svelte.d.ts +12 -0
- package/dist/Elements/Em.svelte +23 -0
- package/dist/Elements/Em.svelte.d.ts +4 -0
- package/dist/Elements/H1.svelte +23 -0
- package/dist/Elements/H1.svelte.d.ts +4 -0
- package/dist/Elements/H2.svelte +23 -0
- package/dist/Elements/H2.svelte.d.ts +4 -0
- package/dist/Elements/H3.svelte +23 -0
- package/dist/Elements/H3.svelte.d.ts +4 -0
- package/dist/Elements/H4.svelte +23 -0
- package/dist/Elements/H4.svelte.d.ts +4 -0
- package/dist/Elements/H5.svelte +23 -0
- package/dist/Elements/H5.svelte.d.ts +4 -0
- package/dist/Elements/H6.svelte +23 -0
- package/dist/Elements/H6.svelte.d.ts +4 -0
- package/dist/Elements/Hr.svelte +20 -0
- package/dist/Elements/Hr.svelte.d.ts +4 -0
- package/dist/Elements/Image.svelte +49 -0
- package/dist/Elements/Image.svelte.d.ts +4 -0
- package/dist/Elements/InlineCode.svelte +23 -0
- package/dist/Elements/InlineCode.svelte.d.ts +4 -0
- package/dist/Elements/Li.svelte +23 -0
- package/dist/Elements/Li.svelte.d.ts +4 -0
- package/dist/Elements/Math.svelte +73 -0
- package/dist/Elements/Math.svelte.d.ts +5 -0
- package/dist/Elements/Mermaid.svelte +80 -0
- package/dist/Elements/Mermaid.svelte.d.ts +4 -0
- package/dist/Elements/Ol.svelte +23 -0
- package/dist/Elements/Ol.svelte.d.ts +4 -0
- package/dist/Elements/P.svelte +23 -0
- package/dist/Elements/P.svelte.d.ts +4 -0
- package/dist/Elements/Slot.svelte +19 -0
- package/dist/Elements/Slot.svelte.d.ts +9 -0
- package/dist/Elements/Strong.svelte +23 -0
- package/dist/Elements/Strong.svelte.d.ts +4 -0
- package/dist/Elements/Sub.svelte +23 -0
- package/dist/Elements/Sub.svelte.d.ts +4 -0
- package/dist/Elements/Sup.svelte +23 -0
- package/dist/Elements/Sup.svelte.d.ts +4 -0
- package/dist/Elements/Table.svelte +25 -0
- package/dist/Elements/Table.svelte.d.ts +4 -0
- package/dist/Elements/Tbody.svelte +23 -0
- package/dist/Elements/Tbody.svelte.d.ts +4 -0
- package/dist/Elements/Td.svelte +23 -0
- package/dist/Elements/Td.svelte.d.ts +4 -0
- package/dist/Elements/Th.svelte +23 -0
- package/dist/Elements/Th.svelte.d.ts +4 -0
- package/dist/Elements/Thead.svelte +23 -0
- package/dist/Elements/Thead.svelte.d.ts +4 -0
- package/dist/Elements/Tr.svelte +23 -0
- package/dist/Elements/Tr.svelte.d.ts +4 -0
- package/dist/Elements/Ul.svelte +23 -0
- package/dist/Elements/Ul.svelte.d.ts +4 -0
- package/dist/Elements/element.d.ts +8 -0
- package/dist/Elements/element.js +1 -0
- package/dist/Elements/index.d.ts +34 -0
- package/dist/Elements/index.js +34 -0
- package/dist/Streamdown.d.ts +83 -0
- package/dist/Streamdown.js +1 -0
- package/dist/Streamdown.svelte +128 -0
- package/dist/Streamdown.svelte.d.ts +18 -0
- package/dist/hightlighter.svelte.d.ts +25 -0
- package/dist/hightlighter.svelte.js +413 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/theme.d.ts +247 -0
- package/dist/theme.js +138 -0
- package/dist/utils/alerts.d.ts +12 -0
- package/dist/utils/alerts.js +207 -0
- package/dist/utils/bind.d.ts +1 -0
- package/dist/utils/bind.js +6 -0
- package/dist/utils/copy.svelte.d.ts +7 -0
- package/dist/utils/copy.svelte.js +35 -0
- package/dist/utils/parse-blocks.d.ts +1 -0
- package/dist/utils/parse-blocks.js +40 -0
- package/dist/utils/parse-incomplete-markdown.d.ts +1 -0
- package/dist/utils/parse-incomplete-markdown.js +442 -0
- package/dist/utils/parse.d.ts +3 -0
- package/dist/utils/parse.js +144 -0
- package/dist/utils/save.d.ts +1 -0
- package/dist/utils/save.js +11 -0
- package/dist/utils/url.d.ts +2 -0
- package/dist/utils/url.js +71 -0
- package/package.json +92 -0
package/dist/theme.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
export const cn = (...inputs) => twMerge(clsx(inputs));
|
|
4
|
+
export const theme = {
|
|
5
|
+
a: {
|
|
6
|
+
base: 'text-blue-600 font-medium underline',
|
|
7
|
+
blocked: 'text-gray-500'
|
|
8
|
+
},
|
|
9
|
+
h1: {
|
|
10
|
+
base: 'mt-6 mb-2 text-3xl font-semibold'
|
|
11
|
+
},
|
|
12
|
+
h2: {
|
|
13
|
+
base: 'mt-6 mb-2 text-2xl font-semibold'
|
|
14
|
+
},
|
|
15
|
+
h3: {
|
|
16
|
+
base: 'mt-6 mb-2 text-xl font-semibold'
|
|
17
|
+
},
|
|
18
|
+
h4: {
|
|
19
|
+
base: 'mt-6 mb-2 text-lg font-semibold'
|
|
20
|
+
},
|
|
21
|
+
h5: {
|
|
22
|
+
base: 'mt-6 mb-2 text-base font-semibold'
|
|
23
|
+
},
|
|
24
|
+
h6: {
|
|
25
|
+
base: 'mt-6 mb-2 text-sm font-semibold'
|
|
26
|
+
},
|
|
27
|
+
p: {
|
|
28
|
+
base: ''
|
|
29
|
+
},
|
|
30
|
+
ul: {
|
|
31
|
+
base: 'ml-4 list-outside list-disc whitespace-normal'
|
|
32
|
+
},
|
|
33
|
+
ol: {
|
|
34
|
+
base: 'ml-4 list-outside list-decimal whitespace-normal'
|
|
35
|
+
},
|
|
36
|
+
li: {
|
|
37
|
+
base: 'py-1'
|
|
38
|
+
},
|
|
39
|
+
code: {
|
|
40
|
+
base: 'bg-gray-100 rounded p-2 font-mono text-sm',
|
|
41
|
+
container: 'my-4 w-full overflow-hidden rounded-xl border border-gray-200 flex flex-col',
|
|
42
|
+
header: 'flex items-center justify-between bg-gray-100/80 p-2 pb-0 text-gray-600 text-xs',
|
|
43
|
+
button: 'cursor-pointer p-1 text-gray-600 transition-all hover:text-gray-900 size-6 p-1',
|
|
44
|
+
language: 'ml-1 font-mono lowercase',
|
|
45
|
+
skeleton: 'rounded-md font-mono text-transparent bg-gray-200 whitespace-nowrap inline-block',
|
|
46
|
+
pre: 'overflow-x-auto font-mono p-0 bg-gray-100/40'
|
|
47
|
+
},
|
|
48
|
+
inlineCode: {
|
|
49
|
+
base: 'bg-gray-100 rounded px-1.5 py-0.5 font-mono text-sm'
|
|
50
|
+
},
|
|
51
|
+
img: {
|
|
52
|
+
container: 'group relative my-4 mx-auto w-fit block',
|
|
53
|
+
base: 'max-w-full rounded-lg',
|
|
54
|
+
downloadButton: 'absolute right-2 bottom-2 flex h-8 w-8 cursor-pointer items-center justify-center rounded-md border border-gray-200 bg-white/90 shadow-sm backdrop-blur-sm transition-all duration-200 hover:bg-white opacity-0 group-hover:opacity-100'
|
|
55
|
+
},
|
|
56
|
+
pre: {
|
|
57
|
+
base: 'overflow-x-auto font-mono text-xs p-4 bg-gray-100/40'
|
|
58
|
+
},
|
|
59
|
+
blockquote: {
|
|
60
|
+
base: 'border-gray-600/30 text-gray-600 my-4 border-l-4 pl-4 italic'
|
|
61
|
+
},
|
|
62
|
+
alert: {
|
|
63
|
+
base: ' relative my-4 border-l-4 p-4',
|
|
64
|
+
title: 'text-sm font-semibold flex items-center gap-2 mb-2 capitalize',
|
|
65
|
+
icon: 'size-5',
|
|
66
|
+
note: '[&>[data-alert-title]]:text-blue-600 border-blue-600 stroke-blue-600',
|
|
67
|
+
tip: '[&>[data-alert-title]]:text-green-600 border-green-600 stroke-green-600',
|
|
68
|
+
warning: '[&>[data-alert-title]]:text-yellow-600 border-yellow-600 stroke-yellow-600',
|
|
69
|
+
caution: '[&>[data-alert-title]]:text-red-600 border-red-600 stroke-red-600',
|
|
70
|
+
important: '[&>[data-alert-title]]:text-purple-600 border-purple-600 stroke-purple-600'
|
|
71
|
+
},
|
|
72
|
+
table: {
|
|
73
|
+
base: 'border-gray-200 w-full border-collapse border',
|
|
74
|
+
container: 'my-4 flex flex-col space-y-2'
|
|
75
|
+
},
|
|
76
|
+
thead: {
|
|
77
|
+
base: 'bg-gray-100/80'
|
|
78
|
+
},
|
|
79
|
+
tbody: {
|
|
80
|
+
base: 'divide-gray-200 bg-gray-100/40 divide-y'
|
|
81
|
+
},
|
|
82
|
+
tr: {
|
|
83
|
+
base: 'border-gray-200 border-b'
|
|
84
|
+
},
|
|
85
|
+
th: {
|
|
86
|
+
base: 'px-4 py-2 text-left text-sm font-semibold whitespace-nowrap'
|
|
87
|
+
},
|
|
88
|
+
td: {
|
|
89
|
+
base: 'px-4 py-2 text-sm'
|
|
90
|
+
},
|
|
91
|
+
sup: {
|
|
92
|
+
base: 'text-sm'
|
|
93
|
+
},
|
|
94
|
+
sub: {
|
|
95
|
+
base: 'text-sm'
|
|
96
|
+
},
|
|
97
|
+
hr: {
|
|
98
|
+
base: 'border-gray-200 my-6'
|
|
99
|
+
},
|
|
100
|
+
strong: {
|
|
101
|
+
base: 'font-semibold'
|
|
102
|
+
},
|
|
103
|
+
mermaid: {
|
|
104
|
+
base: 'group relative my-4 h-auto rounded-xl border border-gray-200 p-4',
|
|
105
|
+
downloadButton: 'cursor-pointer p-1 text-gray-600 transition-all hover:text-gray-900'
|
|
106
|
+
},
|
|
107
|
+
math: {
|
|
108
|
+
base: ''
|
|
109
|
+
},
|
|
110
|
+
inlineMath: {
|
|
111
|
+
base: ''
|
|
112
|
+
},
|
|
113
|
+
em: {
|
|
114
|
+
base: 'font-italic'
|
|
115
|
+
},
|
|
116
|
+
ins: {
|
|
117
|
+
base: 'underline'
|
|
118
|
+
},
|
|
119
|
+
del: {
|
|
120
|
+
base: 'line-through'
|
|
121
|
+
},
|
|
122
|
+
small: {
|
|
123
|
+
base: 'text-sm'
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
export const mergeTheme = (customTheme) => {
|
|
127
|
+
if (!customTheme)
|
|
128
|
+
return theme;
|
|
129
|
+
const baseTheme = { ...theme };
|
|
130
|
+
for (const key in customTheme) {
|
|
131
|
+
for (const subKey in customTheme[key]) {
|
|
132
|
+
Object.assign(baseTheme[key], {
|
|
133
|
+
[subKey]: cn(baseTheme[key], customTheme[key])
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return baseTheme;
|
|
138
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Root, ElementContent } from 'hast';
|
|
2
|
+
export interface IAlert {
|
|
3
|
+
keyword: string;
|
|
4
|
+
}
|
|
5
|
+
export type DefaultBuildType = (alertOptions: IAlert, originalChildren: ElementContent[]) => ElementContent | null;
|
|
6
|
+
export interface IOptions {
|
|
7
|
+
alerts: IAlert[];
|
|
8
|
+
supportLegacy?: boolean;
|
|
9
|
+
build?: DefaultBuildType;
|
|
10
|
+
}
|
|
11
|
+
export declare const rehypeGithubAlerts: (options: IOptions) => (tree: Root) => void;
|
|
12
|
+
export declare const defaultBuild: DefaultBuildType;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { SKIP, visit } from 'unist-util-visit';
|
|
2
|
+
import { isElement } from 'hast-util-is-element';
|
|
3
|
+
let internalOptions;
|
|
4
|
+
export const rehypeGithubAlerts = (options) => {
|
|
5
|
+
const defaultOptions = {
|
|
6
|
+
supportLegacy: false,
|
|
7
|
+
alerts: [
|
|
8
|
+
{
|
|
9
|
+
keyword: 'NOTE'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
keyword: 'IMPORTANT'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
keyword: 'WARNING'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
keyword: 'TIP'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
keyword: 'CAUTION'
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
internalOptions = Object.assign({}, defaultOptions, options);
|
|
26
|
+
return (tree) => {
|
|
27
|
+
visit(tree, 'element', (node, index, parent) => {
|
|
28
|
+
create(node, index, parent);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const create = (node, index, parent) => {
|
|
33
|
+
// check if main element is a blockquote
|
|
34
|
+
if (node.tagName !== 'blockquote') {
|
|
35
|
+
return [SKIP];
|
|
36
|
+
}
|
|
37
|
+
// make sure the blockquote is not empty
|
|
38
|
+
if (node.children.length < 1) {
|
|
39
|
+
return [SKIP];
|
|
40
|
+
}
|
|
41
|
+
// find the first paragraph inside of the blockquote
|
|
42
|
+
const firstParagraph = node.children.find((child) => {
|
|
43
|
+
return child.type === 'element' && child.tagName === 'p';
|
|
44
|
+
});
|
|
45
|
+
// typescript type guard
|
|
46
|
+
// make sure the first paragraph is a valid element
|
|
47
|
+
if (!isElement(firstParagraph)) {
|
|
48
|
+
return [SKIP];
|
|
49
|
+
}
|
|
50
|
+
// try to find the alert type
|
|
51
|
+
const headerData = extractHeaderData(firstParagraph);
|
|
52
|
+
// typescript type guard
|
|
53
|
+
if (headerData === null) {
|
|
54
|
+
return [SKIP];
|
|
55
|
+
}
|
|
56
|
+
// if the first line contains more than the type
|
|
57
|
+
// drop out of rendering as alert, this is what
|
|
58
|
+
// GitHub does (as of Mar. 2024)
|
|
59
|
+
if (headerData.rest.trim() !== '') {
|
|
60
|
+
if (!headerData.rest.startsWith('\n') && !headerData.rest.startsWith('\r')) {
|
|
61
|
+
return [SKIP];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// try to find options matching the alert keyword
|
|
65
|
+
const alertOptions = getAlertOptions(headerData.alertType);
|
|
66
|
+
if (alertOptions === null) {
|
|
67
|
+
return [SKIP];
|
|
68
|
+
}
|
|
69
|
+
// make sure we have parent element and an index
|
|
70
|
+
// or we won't be able to replace the blockquote
|
|
71
|
+
// with the new alert element
|
|
72
|
+
if (!parent || typeof index !== 'number') {
|
|
73
|
+
return [SKIP];
|
|
74
|
+
}
|
|
75
|
+
// use a build to convert the blockquote into an alert
|
|
76
|
+
const build = internalOptions.build ?? defaultBuild;
|
|
77
|
+
const alertBodyChildren = [];
|
|
78
|
+
// for alerts the blockquote first element is always
|
|
79
|
+
// a paragraph but it can have more children than just
|
|
80
|
+
// the alert type text node
|
|
81
|
+
const remainingFirstParagraphChildren = firstParagraph.children.slice(1, firstParagraph.children.length);
|
|
82
|
+
const newFirstParagraphChildren = [];
|
|
83
|
+
// remove the first line break from rest if there is one
|
|
84
|
+
const rest = headerData.rest.replace(/^(\r\n|\r|\n)/, '');
|
|
85
|
+
// if the rest is empty and the first paragraph has no children
|
|
86
|
+
// this is a special github case (as of Mar. 2024)
|
|
87
|
+
// where the alert is only the type (no alert body)
|
|
88
|
+
// in this case github keeps the original blockquote
|
|
89
|
+
if (rest === '' && remainingFirstParagraphChildren.length === 0 && node.children.length < 4) {
|
|
90
|
+
return [SKIP];
|
|
91
|
+
}
|
|
92
|
+
if (remainingFirstParagraphChildren.length > 0) {
|
|
93
|
+
// if the alert type has a hard line break we remove it
|
|
94
|
+
// to not start the alert with a blank line
|
|
95
|
+
// meaning we start the slice at 2 to not take
|
|
96
|
+
// the br element and new line text nodes
|
|
97
|
+
if (remainingFirstParagraphChildren[0].type === 'element' &&
|
|
98
|
+
remainingFirstParagraphChildren[0].tagName === 'br') {
|
|
99
|
+
const remainingChildrenWithoutLineBreak = remainingFirstParagraphChildren.slice(2, firstParagraph.children.length);
|
|
100
|
+
newFirstParagraphChildren.push(...remainingChildrenWithoutLineBreak);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// if the first line of the blockquote has no hard line break
|
|
104
|
+
// after the alert type but some text, then both the type
|
|
105
|
+
// and the text will be in a single text node
|
|
106
|
+
// headerData rest contains the remaining text without the alert type
|
|
107
|
+
if (rest !== '') {
|
|
108
|
+
const restAsTextNode = {
|
|
109
|
+
type: 'text',
|
|
110
|
+
value: rest
|
|
111
|
+
};
|
|
112
|
+
remainingFirstParagraphChildren.unshift(restAsTextNode);
|
|
113
|
+
}
|
|
114
|
+
// if no hard line break (br) take all the remaining
|
|
115
|
+
// and add them to new paragraph to mimic the initial structure
|
|
116
|
+
newFirstParagraphChildren.push(...remainingFirstParagraphChildren);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
if (rest !== '') {
|
|
121
|
+
const restAsTextNode = {
|
|
122
|
+
type: 'text',
|
|
123
|
+
value: rest
|
|
124
|
+
};
|
|
125
|
+
newFirstParagraphChildren.push(restAsTextNode);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (newFirstParagraphChildren.length > 0) {
|
|
129
|
+
const paragraphElement = {
|
|
130
|
+
type: 'element',
|
|
131
|
+
tagName: 'p',
|
|
132
|
+
properties: {},
|
|
133
|
+
children: newFirstParagraphChildren
|
|
134
|
+
};
|
|
135
|
+
alertBodyChildren.push(paragraphElement);
|
|
136
|
+
}
|
|
137
|
+
// outside of the first paragraph there may also be children
|
|
138
|
+
// we add them too back into the alert body
|
|
139
|
+
if (node.children.length > 2) {
|
|
140
|
+
alertBodyChildren.push(...node.children.slice(2, node.children.length));
|
|
141
|
+
}
|
|
142
|
+
const alertElement = build(alertOptions, alertBodyChildren);
|
|
143
|
+
// replace the original blockquote with the
|
|
144
|
+
// new alert element and its children
|
|
145
|
+
if (alertElement !== null) {
|
|
146
|
+
parent.children[index] = alertElement;
|
|
147
|
+
}
|
|
148
|
+
return [SKIP];
|
|
149
|
+
};
|
|
150
|
+
export const defaultBuild = (alertOptions, originalChildren) => {
|
|
151
|
+
// Filter out any title paragraphs that might contain icons or titles
|
|
152
|
+
const filteredChildren = originalChildren.filter((child) => {
|
|
153
|
+
if (child.type === 'element' && child.tagName === 'p') {
|
|
154
|
+
const className = child.properties?.className;
|
|
155
|
+
// Remove paragraphs with markdown-alert-title class
|
|
156
|
+
if (className?.includes('markdown-alert-title')) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
});
|
|
162
|
+
const alert = {
|
|
163
|
+
type: 'element',
|
|
164
|
+
tagName: 'alert',
|
|
165
|
+
properties: {
|
|
166
|
+
className: ['markdown-alert', `markdown-alert-${alertOptions.keyword.toLowerCase()}`]
|
|
167
|
+
},
|
|
168
|
+
children: filteredChildren
|
|
169
|
+
};
|
|
170
|
+
return alert;
|
|
171
|
+
};
|
|
172
|
+
const extractHeaderData = (paragraph) => {
|
|
173
|
+
const header = paragraph.children[0];
|
|
174
|
+
let alertType;
|
|
175
|
+
let rest = '';
|
|
176
|
+
if (internalOptions.supportLegacy) {
|
|
177
|
+
if (header.type === 'element' && header.tagName === 'strong') {
|
|
178
|
+
if (header.children[0].type === 'text') {
|
|
179
|
+
alertType = header.children[0].value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (header.type === 'text') {
|
|
184
|
+
const match = /\[!(.*?)\]/.exec(header.value);
|
|
185
|
+
if (!match?.input) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
if (match.input.length > match[0].length) {
|
|
189
|
+
// if in markdown there are no two spaces at the end
|
|
190
|
+
// then in html there will be no line break
|
|
191
|
+
// this means in the first line there will be more
|
|
192
|
+
// content than just the alert type
|
|
193
|
+
rest = match.input.replace(match[0], '');
|
|
194
|
+
}
|
|
195
|
+
alertType = match[1];
|
|
196
|
+
}
|
|
197
|
+
if (typeof alertType === 'undefined') {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return { alertType, rest };
|
|
201
|
+
};
|
|
202
|
+
const getAlertOptions = (alertType) => {
|
|
203
|
+
const alertOptions = internalOptions.alerts.find((alert) => {
|
|
204
|
+
return alertType.toUpperCase() === alert.keyword.toUpperCase();
|
|
205
|
+
});
|
|
206
|
+
return alertOptions ?? null;
|
|
207
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const bind: (ref: any, props: Record<string, any>) => void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { onDestroy } from 'svelte';
|
|
2
|
+
export const useCopy = (opts) => {
|
|
3
|
+
let isCopied = $state(false);
|
|
4
|
+
let timeoutId;
|
|
5
|
+
const copy = async () => {
|
|
6
|
+
if (typeof window === 'undefined' || !navigator?.clipboard?.writeText) {
|
|
7
|
+
console.error('Clipboard API not available');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
if (!isCopied) {
|
|
12
|
+
await navigator.clipboard.writeText(opts.content);
|
|
13
|
+
isCopied = true;
|
|
14
|
+
if (timeoutId)
|
|
15
|
+
clearTimeout(timeoutId);
|
|
16
|
+
timeoutId = window.setTimeout(() => {
|
|
17
|
+
isCopied = false;
|
|
18
|
+
}, opts.timeout ?? 2000);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error('Failed to copy to clipboard:', error);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
onDestroy(() => {
|
|
26
|
+
if (timeoutId)
|
|
27
|
+
clearTimeout(timeoutId);
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
get isCopied() {
|
|
31
|
+
return isCopied;
|
|
32
|
+
},
|
|
33
|
+
copy
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const parseMarkdownIntoBlocks: (markdown: string) => string[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { marked } from 'marked';
|
|
2
|
+
export const parseMarkdownIntoBlocks = (markdown) => {
|
|
3
|
+
const tokens = marked.use({ gfm: true }).lexer(markdown);
|
|
4
|
+
const blocks = tokens.map((token) => token.raw);
|
|
5
|
+
// Post-process to merge consecutive blocks that are part of the same math block
|
|
6
|
+
const mergedBlocks = [];
|
|
7
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
8
|
+
const currentBlock = blocks[i];
|
|
9
|
+
// Check if this is a standalone $$ that might be a closing delimiter
|
|
10
|
+
if (currentBlock.trim() === '$$' && mergedBlocks.length > 0) {
|
|
11
|
+
const previousBlock = mergedBlocks.at(-1);
|
|
12
|
+
// Check if the previous block starts with $$ but doesn't end with $$
|
|
13
|
+
const prevStartsWith$$ = previousBlock?.trimStart().startsWith('$$');
|
|
14
|
+
const prevDollarCount = (previousBlock?.match(/\$\$/g) || []).length;
|
|
15
|
+
// If previous block has odd number of $$ and starts with $$, merge them
|
|
16
|
+
if (prevStartsWith$$ && prevDollarCount % 2 === 1) {
|
|
17
|
+
mergedBlocks[mergedBlocks.length - 1] = previousBlock + currentBlock;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Check if current block ends with $$ and previous block started with $$ but didn't close
|
|
22
|
+
if (mergedBlocks.length > 0 && currentBlock.trimEnd().endsWith('$$')) {
|
|
23
|
+
const previousBlock = mergedBlocks.at(-1);
|
|
24
|
+
const prevStartsWith$$ = previousBlock?.trimStart().startsWith('$$');
|
|
25
|
+
const prevDollarCount = (previousBlock?.match(/\$\$/g) || []).length;
|
|
26
|
+
const currDollarCount = (currentBlock.match(/\$\$/g) || []).length;
|
|
27
|
+
// If previous block has unclosed math (odd $$) and current block ends with $$
|
|
28
|
+
// AND current block doesn't start with $$, it's likely a continuation
|
|
29
|
+
if (prevStartsWith$$ &&
|
|
30
|
+
prevDollarCount % 2 === 1 &&
|
|
31
|
+
!currentBlock.trimStart().startsWith('$$') &&
|
|
32
|
+
currDollarCount === 1) {
|
|
33
|
+
mergedBlocks[mergedBlocks.length - 1] = previousBlock + currentBlock;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
mergedBlocks.push(currentBlock);
|
|
38
|
+
}
|
|
39
|
+
return mergedBlocks;
|
|
40
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const parseIncompleteMarkdown: (text: string) => string;
|