@antdv-next/x-markdown 0.2.0 → 0.3.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.
- package/dist/XMarkdown/components/AnimationText.vue.d.ts +6 -0
- package/dist/XMarkdown/components/AnimationText.vue_vue_type_script_setup_true_lang.js +58 -2
- package/dist/XMarkdown/components/Section.js +5 -0
- package/dist/XMarkdown/components/Section.vue.d.ts +76 -0
- package/dist/XMarkdown/components/Section.vue_vue_type_script_lang.js +54 -0
- package/dist/XMarkdown/composables/index.d.ts +2 -1
- package/dist/XMarkdown/composables/index.js +3 -2
- package/dist/XMarkdown/composables/useStreaming.d.ts +37 -1
- package/dist/XMarkdown/composables/useStreaming.js +449 -34
- package/dist/XMarkdown/composables/useTypewriter.d.ts +11 -0
- package/dist/XMarkdown/composables/useTypewriter.js +378 -0
- package/dist/XMarkdown/core/Parser.d.ts +15 -0
- package/dist/XMarkdown/core/Parser.js +22 -3
- package/dist/XMarkdown/core/VueRenderer.js +16 -9
- package/dist/XMarkdown/core/detectUnclosedComponentTags.d.ts +12 -0
- package/dist/XMarkdown/core/detectUnclosedComponentTags.js +240 -6
- package/dist/XMarkdown/core/index.js +1 -1
- package/dist/XMarkdown/index.d.ts +6 -2
- package/dist/XMarkdown/index.js +5 -3
- package/dist/XMarkdown/index.vue.d.ts +1 -1
- package/dist/XMarkdown/index.vue_vue_type_script_setup_true_lang.js +64 -30
- package/dist/XMarkdown/interface.d.ts +241 -14
- package/dist/XMarkdown/utils/streaming.d.ts +6 -0
- package/dist/XMarkdown/utils/streaming.js +32 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -3
- package/package.json +1 -1
- package/plugins/Latex/index.js +8 -2
|
@@ -2,10 +2,16 @@ interface Props {
|
|
|
2
2
|
text: string;
|
|
3
3
|
fadeDuration?: number;
|
|
4
4
|
easing?: string;
|
|
5
|
+
splitBy?: "chunk" | "sentence";
|
|
6
|
+
delimiters?: string[];
|
|
7
|
+
maxSentenceChars?: number;
|
|
5
8
|
}
|
|
6
9
|
declare const __VLS_export: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{}>, {
|
|
7
10
|
fadeDuration: number;
|
|
8
11
|
easing: string;
|
|
12
|
+
splitBy: "chunk" | "sentence";
|
|
13
|
+
delimiters: string[];
|
|
14
|
+
maxSentenceChars: number;
|
|
9
15
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
10
16
|
declare const _default: typeof __VLS_export;
|
|
11
17
|
export default _default;
|
|
@@ -5,10 +5,66 @@ var AnimationText_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
|
|
|
5
5
|
props: {
|
|
6
6
|
text: {},
|
|
7
7
|
fadeDuration: { default: 200 },
|
|
8
|
-
easing: { default: "ease-in-out" }
|
|
8
|
+
easing: { default: "ease-in-out" },
|
|
9
|
+
splitBy: { default: "chunk" },
|
|
10
|
+
delimiters: { default: () => [
|
|
11
|
+
"。",
|
|
12
|
+
"!",
|
|
13
|
+
"?",
|
|
14
|
+
".",
|
|
15
|
+
"!",
|
|
16
|
+
"?",
|
|
17
|
+
"\n"
|
|
18
|
+
] },
|
|
19
|
+
maxSentenceChars: { default: 120 }
|
|
9
20
|
},
|
|
10
21
|
setup(__props) {
|
|
11
22
|
const props = __props;
|
|
23
|
+
const endsWithDelimiter = (chunk, delimiters) => chunk.length > 0 && delimiters.includes(chunk[chunk.length - 1]);
|
|
24
|
+
/**
|
|
25
|
+
* Append `newText` sentence-wise: text that continues the current (unfinished)
|
|
26
|
+
* sentence is merged into its chunk so it does not fade in separately, and a
|
|
27
|
+
* new chunk starts only after a delimiter. Text that is already on screen is
|
|
28
|
+
* never moved to another chunk, so nothing that has faded in fades in again.
|
|
29
|
+
*/
|
|
30
|
+
/** Push `text` as units of at most `maxChars`, so a long delimiter-less run
|
|
31
|
+
* still fades in piece by piece. */
|
|
32
|
+
const pushCapped = (chunks, text, maxChars) => {
|
|
33
|
+
for (let start = 0; start < text.length; start += maxChars) chunks.push(text.slice(start, start + maxChars));
|
|
34
|
+
};
|
|
35
|
+
const appendBySentence = (chunks, newText, delimiters, maxChars) => {
|
|
36
|
+
const cap = Math.max(1, maxChars);
|
|
37
|
+
const next = chunks.slice();
|
|
38
|
+
let rest = newText;
|
|
39
|
+
const last = next[next.length - 1];
|
|
40
|
+
if (next.length > 0 && !endsWithDelimiter(last, delimiters) && last.length < cap) {
|
|
41
|
+
let cut = -1;
|
|
42
|
+
for (let i = 0; i < rest.length; i++) if (delimiters.includes(rest[i])) {
|
|
43
|
+
cut = i + 1;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
if (cut === -1) {
|
|
47
|
+
const room = cap - last.length;
|
|
48
|
+
if (rest.length <= room) {
|
|
49
|
+
next[next.length - 1] += rest;
|
|
50
|
+
return next;
|
|
51
|
+
}
|
|
52
|
+
next[next.length - 1] += rest.slice(0, room);
|
|
53
|
+
rest = rest.slice(room);
|
|
54
|
+
pushCapped(next, rest, cap);
|
|
55
|
+
return next;
|
|
56
|
+
}
|
|
57
|
+
next[next.length - 1] += rest.slice(0, cut);
|
|
58
|
+
rest = rest.slice(cut);
|
|
59
|
+
}
|
|
60
|
+
let start = 0;
|
|
61
|
+
for (let i = 0; i < rest.length; i++) if (delimiters.includes(rest[i])) {
|
|
62
|
+
next.push(rest.slice(start, i + 1));
|
|
63
|
+
start = i + 1;
|
|
64
|
+
}
|
|
65
|
+
if (start < rest.length) pushCapped(next, rest.slice(start), cap);
|
|
66
|
+
return next;
|
|
67
|
+
};
|
|
12
68
|
const chunks = ref([]);
|
|
13
69
|
const previousText = ref("");
|
|
14
70
|
function updateChunks(nextText) {
|
|
@@ -20,7 +76,7 @@ var AnimationText_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
|
|
|
20
76
|
}
|
|
21
77
|
const delta = nextText.slice(previousText.value.length);
|
|
22
78
|
if (!delta) return;
|
|
23
|
-
chunks.value = [...chunks.value, delta];
|
|
79
|
+
chunks.value = props.splitBy === "sentence" ? appendBySentence(chunks.value, delta, props.delimiters, props.maxSentenceChars) : [...chunks.value, delta];
|
|
24
80
|
previousText.value = nextText;
|
|
25
81
|
}
|
|
26
82
|
watch(() => props.text, updateChunks, { immediate: true });
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Parser } from '../core/Parser';
|
|
2
|
+
import { VueRenderer } from '../core/VueRenderer';
|
|
3
|
+
/**
|
|
4
|
+
* One section of an incrementally rendered document. Vue skips re-rendering
|
|
5
|
+
* this component while its props are unchanged: while streaming, every
|
|
6
|
+
* section but the last keeps the same source string from chunk to chunk, so
|
|
7
|
+
* marked, DOMPurify, the HTML→vnode conversion and patching run only for the
|
|
8
|
+
* section that is still growing.
|
|
9
|
+
*
|
|
10
|
+
* Renders the parsed vnode directly so the DOM is exactly what rendering the
|
|
11
|
+
* whole document at once would produce.
|
|
12
|
+
*/
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
15
|
+
declare const __VLS_export: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
|
16
|
+
/** Markdown source of this section */
|
|
17
|
+
content: {
|
|
18
|
+
type: StringConstructor;
|
|
19
|
+
required: true;
|
|
20
|
+
};
|
|
21
|
+
parser: {
|
|
22
|
+
type: () => Parser;
|
|
23
|
+
required: true;
|
|
24
|
+
};
|
|
25
|
+
renderer: {
|
|
26
|
+
type: () => VueRenderer;
|
|
27
|
+
required: true;
|
|
28
|
+
};
|
|
29
|
+
/** Whether the streaming tail is appended to this section (only the last one) */
|
|
30
|
+
injectTail: {
|
|
31
|
+
type: BooleanConstructor;
|
|
32
|
+
default: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Bumped when parser/renderer options change in place (the parent keeps
|
|
36
|
+
* both identities stable), so an options change still re-renders every
|
|
37
|
+
* section even though `content` is unchanged.
|
|
38
|
+
*/
|
|
39
|
+
optionsVersion: {
|
|
40
|
+
type: NumberConstructor;
|
|
41
|
+
default: number;
|
|
42
|
+
};
|
|
43
|
+
}>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}>, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
|
46
|
+
/** Markdown source of this section */
|
|
47
|
+
content: {
|
|
48
|
+
type: StringConstructor;
|
|
49
|
+
required: true;
|
|
50
|
+
};
|
|
51
|
+
parser: {
|
|
52
|
+
type: () => Parser;
|
|
53
|
+
required: true;
|
|
54
|
+
};
|
|
55
|
+
renderer: {
|
|
56
|
+
type: () => VueRenderer;
|
|
57
|
+
required: true;
|
|
58
|
+
};
|
|
59
|
+
/** Whether the streaming tail is appended to this section (only the last one) */
|
|
60
|
+
injectTail: {
|
|
61
|
+
type: BooleanConstructor;
|
|
62
|
+
default: boolean;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Bumped when parser/renderer options change in place (the parent keeps
|
|
66
|
+
* both identities stable), so an options change still re-renders every
|
|
67
|
+
* section even though `content` is unchanged.
|
|
68
|
+
*/
|
|
69
|
+
optionsVersion: {
|
|
70
|
+
type: NumberConstructor;
|
|
71
|
+
default: number;
|
|
72
|
+
};
|
|
73
|
+
}>> & Readonly<{}>, {
|
|
74
|
+
injectTail: boolean;
|
|
75
|
+
optionsVersion: number;
|
|
76
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { computed, defineComponent } from "vue";
|
|
2
|
+
//#region src/XMarkdown/components/Section.vue?vue&type=script&lang.ts
|
|
3
|
+
/**
|
|
4
|
+
* One section of an incrementally rendered document. Vue skips re-rendering
|
|
5
|
+
* this component while its props are unchanged: while streaming, every
|
|
6
|
+
* section but the last keeps the same source string from chunk to chunk, so
|
|
7
|
+
* marked, DOMPurify, the HTML→vnode conversion and patching run only for the
|
|
8
|
+
* section that is still growing.
|
|
9
|
+
*
|
|
10
|
+
* Renders the parsed vnode directly so the DOM is exactly what rendering the
|
|
11
|
+
* whole document at once would produce.
|
|
12
|
+
*/
|
|
13
|
+
var Section_vue_vue_type_script_lang_default = defineComponent({
|
|
14
|
+
name: "XMarkdownSection",
|
|
15
|
+
props: {
|
|
16
|
+
/** Markdown source of this section */
|
|
17
|
+
content: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
parser: {
|
|
22
|
+
type: Object,
|
|
23
|
+
required: true
|
|
24
|
+
},
|
|
25
|
+
renderer: {
|
|
26
|
+
type: Object,
|
|
27
|
+
required: true
|
|
28
|
+
},
|
|
29
|
+
/** Whether the streaming tail is appended to this section (only the last one) */
|
|
30
|
+
injectTail: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Bumped when parser/renderer options change in place (the parent keeps
|
|
36
|
+
* both identities stable), so an options change still re-renders every
|
|
37
|
+
* section even though `content` is unchanged.
|
|
38
|
+
*/
|
|
39
|
+
optionsVersion: {
|
|
40
|
+
type: Number,
|
|
41
|
+
default: 0
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
setup(props) {
|
|
45
|
+
const node = computed(() => {
|
|
46
|
+
props.optionsVersion;
|
|
47
|
+
const html = props.parser.parse(props.content, { injectTail: props.injectTail });
|
|
48
|
+
return props.renderer.render(html);
|
|
49
|
+
});
|
|
50
|
+
return () => node.value;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
//#endregion
|
|
54
|
+
export { Section_vue_vue_type_script_lang_default as default };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { useStreaming } from './useStreaming';
|
|
1
|
+
export { useStreaming, useStreamingCore } from './useStreaming';
|
|
2
|
+
export { useTypewriter } from './useTypewriter';
|
|
2
3
|
export { useParser } from './useParser';
|
|
3
4
|
export { useRenderer } from './useRenderer';
|
|
4
5
|
export { useTail } from './useTail';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useStreaming } from "./useStreaming.js";
|
|
1
|
+
import { useStreaming, useStreamingCore } from "./useStreaming.js";
|
|
2
|
+
import useTypewriter from "./useTypewriter.js";
|
|
2
3
|
import { useParser } from "./useParser.js";
|
|
3
4
|
import { useRenderer } from "./useRenderer.js";
|
|
4
5
|
import { useTail } from "./useTail.js";
|
|
5
|
-
export { useParser, useRenderer, useStreaming, useTail };
|
|
6
|
+
export { useParser, useRenderer, useStreaming, useStreamingCore, useTail, useTypewriter };
|
|
@@ -1,6 +1,42 @@
|
|
|
1
|
+
import { Token } from 'marked';
|
|
1
2
|
import { Component, Ref } from 'vue';
|
|
2
3
|
import { StreamingOption } from '../interface';
|
|
3
|
-
|
|
4
|
+
/** Sections shorter than this are merged into the next one. */
|
|
5
|
+
export declare const DEFAULT_MIN_SECTION_CHARS = 200;
|
|
6
|
+
/**
|
|
7
|
+
* The splitter's oracle: a block-level lex of the *preprocessed* source by the
|
|
8
|
+
* renderer's own marked. Callers hand over `Parser.lex`.
|
|
9
|
+
*/
|
|
10
|
+
type SectionLexer = (markdown: string) => readonly Token[];
|
|
11
|
+
export interface StreamingResult {
|
|
12
|
+
/** The markdown to parse: committed text plus the placeholder for the pending token */
|
|
13
|
+
output: Ref<string>;
|
|
14
|
+
/**
|
|
15
|
+
* `output` split at section boundaries when `streaming.incremental` is on
|
|
16
|
+
* and at least one boundary exists; `null` means render `output` as a whole.
|
|
17
|
+
* Joining the sections always gives back `output`.
|
|
18
|
+
*/
|
|
19
|
+
sections: Ref<string[] | null>;
|
|
20
|
+
reset: () => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Streaming state machine plus, when `streaming.incremental` is on, the
|
|
24
|
+
* section boundaries the renderer can memoise on. `useStreaming` is the
|
|
25
|
+
* backwards-compatible view of this composable.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useStreamingCore(content: Ref<string>, streaming: Ref<boolean | StreamingOption | undefined>, components: Ref<Record<string, Component> | undefined> | undefined,
|
|
28
|
+
/**
|
|
29
|
+
* Lexer used to decide section boundaries. Must be the renderer's own marked
|
|
30
|
+
* (`Parser.lex`), so `config.extensions` and the parser's preprocessing
|
|
31
|
+
* (`protectCustomTags`, `escapeRawHtml`) are honoured — a different grammar
|
|
32
|
+
* answers about a different document and yields wrong boundaries. Required
|
|
33
|
+
* rather than defaulted: a silent GFM fallback here would be wrong by
|
|
34
|
+
* construction for any configured renderer. The sections-blind
|
|
35
|
+
* `useStreaming` wrapper passes the fallback explicitly.
|
|
36
|
+
*/
|
|
37
|
+
lex: SectionLexer): StreamingResult;
|
|
38
|
+
export declare function useStreaming(content: Ref<string>, streaming: Ref<boolean | StreamingOption | undefined>, components?: Ref<Record<string, Component> | undefined>): {
|
|
4
39
|
processedContent: Ref<string, string>;
|
|
5
40
|
reset: () => void;
|
|
6
41
|
};
|
|
42
|
+
export {};
|