@antdv-next/x-markdown 0.1.2 → 0.1.4
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 +45 -13
- package/dist/XMarkdown/components/AnimationText.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/XMarkdown/components/DebugPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/XMarkdown/components/TailIndicator.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/XMarkdown/composables/useStreaming.js +55 -34
- package/dist/XMarkdown/core/VueRenderer.js +19 -4
- package/dist/XMarkdown/core/detectUnclosedComponentTags.js +1 -1
- package/dist/XMarkdown/index.vue_vue_type_script_setup_true_lang.js +7 -1
- package/dist/XMarkdown/interface.d.ts +19 -0
- package/package.json +1 -1
- package/plugins/Latex/index.js +13 -4
package/README.md
CHANGED
|
@@ -105,19 +105,51 @@ import "@antdv-next/x-markdown/themes/light.css";
|
|
|
105
105
|
|
|
106
106
|
### Props
|
|
107
107
|
|
|
108
|
-
| Property | Description
|
|
109
|
-
| ------------------------ |
|
|
110
|
-
| content | Markdown content to render
|
|
111
|
-
| components | Map HTML nodes to custom Vue components
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
108
|
+
| Property | Description | Type | Default |
|
|
109
|
+
| ------------------------ | --------------------------------------------------- | ----------------------------------------- | --------------- |
|
|
110
|
+
| content | Markdown content to render | `string` | - |
|
|
111
|
+
| components | Map HTML nodes to custom Vue components | `Record<string, Component>` | - |
|
|
112
|
+
| componentsProps | Extra props passed to custom components by tag name | `Record<string, Record<string, unknown>>` | - |
|
|
113
|
+
| streaming | Streaming behavior config | `StreamingOption` | - |
|
|
114
|
+
| config | Marked parse config | `MarkedConfig` | `{ gfm: true }` |
|
|
115
|
+
| className | Extra CSS class for root container | `string` | - |
|
|
116
|
+
| style | Inline styles for root container | `Record<string, string>` | - |
|
|
117
|
+
| paragraphTag | HTML tag for paragraphs | `string` | `'p'` |
|
|
118
|
+
| openLinksInNewTab | Add `target="_blank"` to all links | `boolean` | `true` |
|
|
119
|
+
| protectCustomTagNewlines | Preserve newlines inside custom tags | `boolean` | `true` |
|
|
120
|
+
| escapeRawHtml | Escape raw HTML as plain text | `boolean` | `false` |
|
|
121
|
+
| debug | Enable debug mode | `boolean` | `false` |
|
|
122
|
+
|
|
123
|
+
### Passing extra props to custom components
|
|
124
|
+
|
|
125
|
+
Custom components often need business data (theme, callbacks, etc.). Passing it via an inline wrapper function creates a new component on every render and forces the subtree to be re-created, losing internal state and hurting performance in streaming scenarios. Use `componentsProps` to pass extra props while keeping component references stable:
|
|
126
|
+
|
|
127
|
+
```vue
|
|
128
|
+
<script setup>
|
|
129
|
+
import { computed, ref } from "vue";
|
|
130
|
+
import { XMarkdown } from "@antdv-next/x-markdown";
|
|
131
|
+
|
|
132
|
+
const theme = ref("dark");
|
|
133
|
+
const components = { "custom-chart": CustomChart };
|
|
134
|
+
// Keep the reference stable — an inline object literal invalidates the render
|
|
135
|
+
// cache and re-parses the whole tree on every render.
|
|
136
|
+
const componentsProps = computed(() => ({
|
|
137
|
+
"custom-chart": { theme: theme.value, onSelect },
|
|
138
|
+
}));
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<template>
|
|
142
|
+
<XMarkdown :components="components" :componentsProps="componentsProps" />
|
|
143
|
+
</template>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`componentsProps` is keyed by tag name. Its props are merged with the parsed HTML attributes and passed to the component:
|
|
147
|
+
|
|
148
|
+
- On conflict `componentsProps` wins — a `title` in `componentsProps` overrides `title="..."` from the HTML.
|
|
149
|
+
- `class` / `className` is the exception: both sides are concatenated, with the `componentsProps` class name first (merged into `class`).
|
|
150
|
+
- Internally computed props — `streamStatus`, `domNode`, `children` (plus `lang` and `block` for `code`) — cannot be overridden and are ignored if present in `componentsProps`.
|
|
151
|
+
|
|
152
|
+
When `componentsProps` changes, the component receives a normal props update without being remounted. Like `components`, it takes part in the render cache, so passing an inline object literal invalidates that cache on every render and re-parses the whole tree — keep the reference stable (e.g. with `computed`) as shown above.
|
|
121
153
|
|
|
122
154
|
### StreamingOption
|
|
123
155
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment, computed, createElementBlock, defineComponent, normalizeStyle, openBlock, ref, renderList, toDisplayString, watch } from "vue";
|
|
2
2
|
//#region src/XMarkdown/components/AnimationText.vue?vue&type=script&setup=true&lang.ts
|
|
3
|
-
var AnimationText_vue_vue_type_script_setup_true_lang_default =
|
|
3
|
+
var AnimationText_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
4
4
|
__name: "AnimationText",
|
|
5
5
|
props: {
|
|
6
6
|
text: {},
|
|
@@ -10,7 +10,7 @@ var _hoisted_4 = {
|
|
|
10
10
|
};
|
|
11
11
|
var _hoisted_5 = ["d"];
|
|
12
12
|
var maxSnapshots = 60;
|
|
13
|
-
var DebugPanel_vue_vue_type_script_setup_true_lang_default =
|
|
13
|
+
var DebugPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
14
14
|
__name: "DebugPanel",
|
|
15
15
|
props: { className: {} },
|
|
16
16
|
setup(__props) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createElementBlock, defineComponent, openBlock, toDisplayString } from "vue";
|
|
2
2
|
//#region src/XMarkdown/components/TailIndicator.vue?vue&type=script&setup=true&lang.ts
|
|
3
3
|
var _hoisted_1 = { class: "xmd-tail" };
|
|
4
|
-
var TailIndicator_vue_vue_type_script_setup_true_lang_default =
|
|
4
|
+
var TailIndicator_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
5
5
|
__name: "TailIndicator",
|
|
6
6
|
props: { content: { default: "▋" } },
|
|
7
7
|
setup(__props) {
|
|
@@ -82,11 +82,21 @@ var recognize = (cache, tokenType) => {
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
var recognizeHandlers = Object.values(tokenRecognizerMap).filter((recognizer) => Boolean(recognizer));
|
|
85
|
+
var getInitialFenceState = () => ({
|
|
86
|
+
inFenced: false,
|
|
87
|
+
fenceChar: "",
|
|
88
|
+
fenceLen: 0,
|
|
89
|
+
lineFenceChar: "",
|
|
90
|
+
lineFenceLen: 0,
|
|
91
|
+
lineFenceRunEnded: false,
|
|
92
|
+
lineTailBlank: true
|
|
93
|
+
});
|
|
85
94
|
var getInitialCache = () => ({
|
|
86
95
|
pending: "",
|
|
87
96
|
token: StreamCacheTokenType.Text,
|
|
88
97
|
processedLength: 0,
|
|
89
|
-
completeMarkdown: ""
|
|
98
|
+
completeMarkdown: "",
|
|
99
|
+
fence: getInitialFenceState()
|
|
90
100
|
});
|
|
91
101
|
var commitCache = (cache) => {
|
|
92
102
|
if (cache.pending) {
|
|
@@ -95,33 +105,42 @@ var commitCache = (cache) => {
|
|
|
95
105
|
}
|
|
96
106
|
cache.token = StreamCacheTokenType.Text;
|
|
97
107
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
if (char === fenceChar && len >= fenceLen && /^\s*$/.test(after) && (isFinalChunk || i < lines.length - 1)) {
|
|
118
|
-
inFenced = false;
|
|
119
|
-
fenceChar = "";
|
|
120
|
-
fenceLen = 0;
|
|
108
|
+
/**
|
|
109
|
+
* Incremental fenced-code-block state over the processed text, updated in O(1)
|
|
110
|
+
* per character. Recomputing over the full accumulated text on every character
|
|
111
|
+
* is O(N²) and freezes the page on long single-line content such as base64
|
|
112
|
+
* image data URIs.
|
|
113
|
+
*/
|
|
114
|
+
var feedFenceState = (fence, char) => {
|
|
115
|
+
if (char === "\n") {
|
|
116
|
+
if (fence.lineFenceLen >= 3) {
|
|
117
|
+
if (!fence.inFenced) {
|
|
118
|
+
fence.inFenced = true;
|
|
119
|
+
fence.fenceChar = fence.lineFenceChar;
|
|
120
|
+
fence.fenceLen = fence.lineFenceLen;
|
|
121
|
+
} else if (fence.lineFenceChar === fence.fenceChar && fence.lineFenceLen >= fence.fenceLen && fence.lineTailBlank) {
|
|
122
|
+
fence.inFenced = false;
|
|
123
|
+
fence.fenceChar = "";
|
|
124
|
+
fence.fenceLen = 0;
|
|
125
|
+
}
|
|
121
126
|
}
|
|
127
|
+
fence.lineFenceChar = "";
|
|
128
|
+
fence.lineFenceLen = 0;
|
|
129
|
+
fence.lineFenceRunEnded = false;
|
|
130
|
+
fence.lineTailBlank = true;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!fence.lineFenceRunEnded) if (fence.lineFenceLen === 0 && (char === "`" || char === "~")) {
|
|
134
|
+
fence.lineFenceChar = char;
|
|
135
|
+
fence.lineFenceLen = 1;
|
|
136
|
+
} else if (fence.lineFenceLen > 0 && char === fence.lineFenceChar) fence.lineFenceLen += 1;
|
|
137
|
+
else {
|
|
138
|
+
fence.lineFenceRunEnded = true;
|
|
139
|
+
fence.lineTailBlank = fence.lineTailBlank && /\s/.test(char);
|
|
122
140
|
}
|
|
123
|
-
|
|
141
|
+
else fence.lineTailBlank = fence.lineTailBlank && /\s/.test(char);
|
|
124
142
|
};
|
|
143
|
+
var isInCodeBlock = (fence) => fence.inFenced || fence.lineFenceLen >= 3;
|
|
125
144
|
var sanitizeForURIComponent = (input) => {
|
|
126
145
|
let result = "";
|
|
127
146
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -144,7 +163,7 @@ var safeEncodeURIComponent = (str) => {
|
|
|
144
163
|
}
|
|
145
164
|
};
|
|
146
165
|
function useStreaming(content, streaming, components) {
|
|
147
|
-
|
|
166
|
+
let streamCache = getInitialCache();
|
|
148
167
|
const processedContent = ref("");
|
|
149
168
|
function handleIncompleteMarkdown(cache, opts) {
|
|
150
169
|
const { token, pending } = cache;
|
|
@@ -158,12 +177,12 @@ function useStreaming(content, streaming, components) {
|
|
|
158
177
|
function processStreaming(text, opts) {
|
|
159
178
|
if (!text) {
|
|
160
179
|
processedContent.value = "";
|
|
161
|
-
streamCache
|
|
180
|
+
streamCache = getInitialCache();
|
|
162
181
|
return;
|
|
163
182
|
}
|
|
164
|
-
const expectedPrefix = streamCache.
|
|
165
|
-
if (!text.startsWith(expectedPrefix)) streamCache
|
|
166
|
-
const cache = streamCache
|
|
183
|
+
const expectedPrefix = streamCache.completeMarkdown + streamCache.pending;
|
|
184
|
+
if (!text.startsWith(expectedPrefix)) streamCache = getInitialCache();
|
|
185
|
+
const cache = streamCache;
|
|
167
186
|
const chunk = text.slice(cache.processedLength);
|
|
168
187
|
if (!chunk) {
|
|
169
188
|
const incompletePlaceholder = handleIncompleteMarkdown(cache, opts);
|
|
@@ -173,7 +192,8 @@ function useStreaming(content, streaming, components) {
|
|
|
173
192
|
cache.processedLength += chunk.length;
|
|
174
193
|
for (const char of chunk) {
|
|
175
194
|
cache.pending += char;
|
|
176
|
-
|
|
195
|
+
feedFenceState(cache.fence, char);
|
|
196
|
+
if (isInCodeBlock(cache.fence)) {
|
|
177
197
|
commitCache(cache);
|
|
178
198
|
continue;
|
|
179
199
|
}
|
|
@@ -189,18 +209,19 @@ function useStreaming(content, streaming, components) {
|
|
|
189
209
|
processedContent.value = cache.completeMarkdown + (incompletePlaceholder || "");
|
|
190
210
|
}
|
|
191
211
|
function reset() {
|
|
192
|
-
streamCache
|
|
212
|
+
streamCache = getInitialCache();
|
|
193
213
|
processedContent.value = "";
|
|
194
214
|
}
|
|
195
215
|
watch([content, () => streaming.value?.hasNextChunk], ([newContent, hasNextChunk]) => {
|
|
196
216
|
const opts = streaming.value;
|
|
197
217
|
if (!Boolean(hasNextChunk)) {
|
|
198
218
|
processedContent.value = newContent;
|
|
199
|
-
streamCache
|
|
219
|
+
streamCache = {
|
|
200
220
|
pending: "",
|
|
201
221
|
token: StreamCacheTokenType.Text,
|
|
202
222
|
processedLength: newContent.length,
|
|
203
|
-
completeMarkdown: newContent
|
|
223
|
+
completeMarkdown: newContent,
|
|
224
|
+
fence: getInitialFenceState()
|
|
204
225
|
};
|
|
205
226
|
return;
|
|
206
227
|
}
|
|
@@ -55,6 +55,7 @@ var VueRenderer = class {
|
|
|
55
55
|
constructor(options = {}) {
|
|
56
56
|
this.options = {
|
|
57
57
|
components: options.components ?? {},
|
|
58
|
+
componentsProps: options.componentsProps ?? {},
|
|
58
59
|
enableAnimation: options.enableAnimation ?? true,
|
|
59
60
|
animationConfig: {
|
|
60
61
|
fadeDuration: options.animationConfig?.fadeDuration ?? DEFAULT_ANIMATION_DURATION,
|
|
@@ -71,7 +72,7 @@ var VueRenderer = class {
|
|
|
71
72
|
return h(Fragment, nodes);
|
|
72
73
|
}
|
|
73
74
|
sanitize(html) {
|
|
74
|
-
const allowedTags = [
|
|
75
|
+
const allowedTags = [.../* @__PURE__ */ new Set([
|
|
75
76
|
...Object.keys(this.options.components),
|
|
76
77
|
"p",
|
|
77
78
|
"br",
|
|
@@ -183,12 +184,17 @@ var VueRenderer = class {
|
|
|
183
184
|
}
|
|
184
185
|
extractComponentProps(element, tagName, unclosedTags, cidRef) {
|
|
185
186
|
const props = {};
|
|
186
|
-
cidRef.tagIndexes[tagName] = (cidRef.tagIndexes[tagName] ?? 0) + 1;
|
|
187
|
-
const instanceId = getTagInstanceId(tagName, cidRef.tagIndexes[tagName]);
|
|
188
|
-
props.streamStatus = unclosedTags.has(instanceId) ? "loading" : "done";
|
|
189
187
|
Array.from(element.attributes).forEach((attr) => {
|
|
190
188
|
props[attr.name] = attr.value;
|
|
191
189
|
});
|
|
190
|
+
const htmlClass = typeof props.class === "string" ? props.class : "";
|
|
191
|
+
const extraProps = this.options.componentsProps[tagName] ?? {};
|
|
192
|
+
Object.assign(props, extraProps);
|
|
193
|
+
cidRef.tagIndexes[tagName] = (cidRef.tagIndexes[tagName] ?? 0) + 1;
|
|
194
|
+
const instanceId = getTagInstanceId(tagName, cidRef.tagIndexes[tagName]);
|
|
195
|
+
props.streamStatus = unclosedTags.has(instanceId) ? "loading" : "done";
|
|
196
|
+
props.domNode = element;
|
|
197
|
+
delete props.children;
|
|
192
198
|
if (tagName === "code") {
|
|
193
199
|
props.block = element.getAttribute("data-block") === "true";
|
|
194
200
|
props.streamStatus = element.getAttribute("data-state") === "loading" ? "loading" : "done";
|
|
@@ -197,7 +203,12 @@ var VueRenderer = class {
|
|
|
197
203
|
const langFromClass = className.match(/(?:^|\s)language-([^\s]+)/)?.[1] ?? className.match(/(?:^|\s)lang-([^\s]+)/)?.[1] ?? "";
|
|
198
204
|
const lang = langFromData || langFromClass;
|
|
199
205
|
if (lang) props.lang = lang;
|
|
206
|
+
else delete props.lang;
|
|
200
207
|
}
|
|
208
|
+
const extraClass = extraProps.className ?? extraProps.classname ?? extraProps.class;
|
|
209
|
+
if (typeof extraClass === "string") props.class = [extraClass, htmlClass].filter(Boolean).join(" ").trim();
|
|
210
|
+
delete props.className;
|
|
211
|
+
delete props.classname;
|
|
201
212
|
return props;
|
|
202
213
|
}
|
|
203
214
|
wrapWithAnimation(text) {
|
|
@@ -212,6 +223,10 @@ var VueRenderer = class {
|
|
|
212
223
|
...this.options.components,
|
|
213
224
|
...options.components
|
|
214
225
|
};
|
|
226
|
+
if (options.componentsProps) this.options.componentsProps = {
|
|
227
|
+
...this.options.componentsProps,
|
|
228
|
+
...options.componentsProps
|
|
229
|
+
};
|
|
215
230
|
if (options.enableAnimation !== void 0) this.options.enableAnimation = options.enableAnimation;
|
|
216
231
|
if (options.animationConfig) this.options.animationConfig = {
|
|
217
232
|
...this.options.animationConfig,
|
|
@@ -6,11 +6,12 @@ import { VueRenderer } from "./core/VueRenderer.js";
|
|
|
6
6
|
import { useTail } from "./composables/useTail.js";
|
|
7
7
|
import { computed, createBlock, createCommentVNode, createElementBlock, createVNode, defineComponent, h, normalizeClass, normalizeStyle, openBlock, ref, shallowRef, unref, watch } from "vue";
|
|
8
8
|
//#region src/XMarkdown/index.vue?vue&type=script&setup=true&lang.ts
|
|
9
|
-
var index_vue_vue_type_script_setup_true_lang_default =
|
|
9
|
+
var index_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
10
10
|
__name: "index",
|
|
11
11
|
props: {
|
|
12
12
|
content: { default: "" },
|
|
13
13
|
components: { default: () => ({}) },
|
|
14
|
+
componentsProps: {},
|
|
14
15
|
streaming: { default: void 0 },
|
|
15
16
|
config: { default: () => ({ gfm: true }) },
|
|
16
17
|
debug: {
|
|
@@ -75,6 +76,7 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
|
|
|
75
76
|
}));
|
|
76
77
|
const renderer = shallowRef(new VueRenderer({
|
|
77
78
|
components: mergedComponents.value,
|
|
79
|
+
componentsProps: props.componentsProps,
|
|
78
80
|
enableAnimation: props.streaming?.enableAnimation ?? true,
|
|
79
81
|
animationConfig: props.streaming?.animationConfig
|
|
80
82
|
}));
|
|
@@ -116,6 +118,10 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
|
|
|
116
118
|
renderer.value.setOptions({ components: newComponents });
|
|
117
119
|
bumpOptionsVersion();
|
|
118
120
|
}, { deep: true });
|
|
121
|
+
watch(() => props.componentsProps, (newComponentsProps) => {
|
|
122
|
+
renderer.value.setOptions({ componentsProps: newComponentsProps });
|
|
123
|
+
bumpOptionsVersion();
|
|
124
|
+
}, { deep: true });
|
|
119
125
|
watch(() => props.streaming, (newStreaming) => {
|
|
120
126
|
renderer.value.setOptions({
|
|
121
127
|
enableAnimation: newStreaming?.enableAnimation ?? true,
|
|
@@ -10,11 +10,24 @@ export declare enum StreamCacheTokenType {
|
|
|
10
10
|
Table = "table",
|
|
11
11
|
InlineCode = "inline-code"
|
|
12
12
|
}
|
|
13
|
+
export interface FenceState {
|
|
14
|
+
/** Inside an open fence, considering completed lines only */
|
|
15
|
+
inFenced: boolean;
|
|
16
|
+
fenceChar: string;
|
|
17
|
+
fenceLen: number;
|
|
18
|
+
/** Leading `` ` ``/`~` run of the current (possibly incomplete) line */
|
|
19
|
+
lineFenceChar: string;
|
|
20
|
+
lineFenceLen: number;
|
|
21
|
+
lineFenceRunEnded: boolean;
|
|
22
|
+
/** Whether every char after the leading run is whitespace (closing fences allow only whitespace) */
|
|
23
|
+
lineTailBlank: boolean;
|
|
24
|
+
}
|
|
13
25
|
export interface StreamCache {
|
|
14
26
|
pending: string;
|
|
15
27
|
token: StreamCacheTokenType;
|
|
16
28
|
processedLength: number;
|
|
17
29
|
completeMarkdown: string;
|
|
30
|
+
fence: FenceState;
|
|
18
31
|
}
|
|
19
32
|
export interface StreamingOption {
|
|
20
33
|
hasNextChunk: boolean;
|
|
@@ -32,6 +45,11 @@ export interface StreamingOption {
|
|
|
32
45
|
export interface XMarkdownProps {
|
|
33
46
|
content?: string;
|
|
34
47
|
components?: Record<string, Component>;
|
|
48
|
+
/**
|
|
49
|
+
* 按标签名向 `components` 中的自定义组件传递额外的 props,使组件引用保持稳定,避免内联函数导致的重复挂载
|
|
50
|
+
* Extra props passed to custom components in `components` by tag name, keeping component references stable and avoiding remounts caused by inline functions
|
|
51
|
+
*/
|
|
52
|
+
componentsProps?: Record<string, Record<string, unknown>>;
|
|
35
53
|
streaming?: StreamingOption;
|
|
36
54
|
config?: MarkedConfig;
|
|
37
55
|
debug?: boolean;
|
|
@@ -78,6 +96,7 @@ export interface ParserOptions {
|
|
|
78
96
|
}
|
|
79
97
|
export interface RendererOptions {
|
|
80
98
|
components?: Record<string, Component>;
|
|
99
|
+
componentsProps?: Record<string, Record<string, unknown>>;
|
|
81
100
|
enableAnimation?: boolean;
|
|
82
101
|
animationConfig?: {
|
|
83
102
|
fadeDuration?: number;
|
package/package.json
CHANGED
package/plugins/Latex/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import katex from "katex";
|
|
2
2
|
import "katex/dist/katex.min.css";
|
|
3
3
|
//#region src/plugins/Latex/index.ts
|
|
4
|
-
var
|
|
4
|
+
var inlineDollarRule = /^(\${1,2})((?:\\.|[^\\$]){1,10000}?)\1/;
|
|
5
|
+
var inlineRuleNonStandard = /^(?:\\\(([\s\S]{1,10000}?)\\\)|\\\[((?:\\.|[^\\]){1,10000}?)\\\])/;
|
|
5
6
|
var blockRule = /^(\${1,2})\n([\s\S]{1,10000}?)\n\1(?:\s*(?:\n|$))|^\\\[((?:\\.|[^\\]){1,10000}?)\\\]/;
|
|
6
7
|
function replaceAlign(text) {
|
|
7
8
|
return text ? text.replace(/\{align\*\}/g, "{aligned}") : text;
|
|
8
9
|
}
|
|
10
|
+
function isValidInlineDollarMatch(match, src) {
|
|
11
|
+
const [, delimiter, text] = match;
|
|
12
|
+
if (delimiter === "$$") return true;
|
|
13
|
+
return !/^\s|\s$/.test(text) && !/^\d/.test(src.slice(match[0].length));
|
|
14
|
+
}
|
|
9
15
|
function createRenderer(options, newlineAfter) {
|
|
10
16
|
return (token) => katex.renderToString(token.text, {
|
|
11
17
|
...options,
|
|
@@ -25,10 +31,13 @@ function inlineKatex(renderer, replaceAlignStart) {
|
|
|
25
31
|
return indices.length > 0 ? Math.min(...indices) : void 0;
|
|
26
32
|
},
|
|
27
33
|
tokenizer(src) {
|
|
28
|
-
const
|
|
34
|
+
const dollarMatch = src.match(inlineDollarRule);
|
|
35
|
+
if (dollarMatch && !isValidInlineDollarMatch(dollarMatch, src)) return;
|
|
36
|
+
const nonStandardMatch = src.match(inlineRuleNonStandard);
|
|
37
|
+
const match = dollarMatch || nonStandardMatch;
|
|
29
38
|
if (!match) return;
|
|
30
|
-
const rawText =
|
|
31
|
-
const text = replaceAlignStart ? replaceAlign(rawText) : rawText;
|
|
39
|
+
const rawText = dollarMatch?.[2] || nonStandardMatch?.[1] || nonStandardMatch?.[2] || "";
|
|
40
|
+
const text = replaceAlignStart ? replaceAlign(rawText.trim()) : rawText.trim();
|
|
32
41
|
return {
|
|
33
42
|
type: "inlineKatex",
|
|
34
43
|
raw: match[0],
|