@antdv-next/x-markdown 0.1.2 → 0.1.3

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 (32) hide show
  1. package/dist/XMarkdown/components/AnimationText.vue_vue_type_script_setup_true_lang.js +1 -1
  2. package/dist/XMarkdown/components/DebugPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  3. package/dist/XMarkdown/components/TailIndicator.vue_vue_type_script_setup_true_lang.js +1 -1
  4. package/dist/XMarkdown/composables/__tests__/useStreaming.test.d.ts +1 -0
  5. package/dist/XMarkdown/composables/__tests__/useStreaming.test.js +118 -0
  6. package/dist/XMarkdown/composables/useStreaming.js +48 -27
  7. package/dist/XMarkdown/core/VueRenderer.js +1 -1
  8. package/dist/XMarkdown/core/detectUnclosedComponentTags.js +1 -1
  9. package/dist/XMarkdown/index.vue_vue_type_script_setup_true_lang.js +1 -1
  10. package/dist/XMarkdown/interface.d.ts +13 -0
  11. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/expect/index.js +1467 -0
  12. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/pretty-format/index.js +884 -0
  13. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js +1513 -0
  14. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/snapshot/index.js +661 -0
  15. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/spy/index.js +384 -0
  16. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/chunk-pathe.M-eThtNZ.js +80 -0
  17. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/diff.js +1304 -0
  18. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/display.js +556 -0
  19. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/error.js +27 -0
  20. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/helpers.js +179 -0
  21. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/offset.js +25 -0
  22. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/serialize.js +75 -0
  23. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/source-map.js +371 -0
  24. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/utils/timers.js +35 -0
  25. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/chunks/_commonjsHelpers.D26ty3Ew.js +4 -0
  26. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/chunks/rpc.MzXet3jl.js +50 -0
  27. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/chunks/test.CBQUpOM3.js +2645 -0
  28. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/chunks/utils.BX5Fg8C4.js +42 -0
  29. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/vendor/chai.js +2872 -0
  30. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/vendor/magic-string.js +1009 -0
  31. package/dist/node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/vendor/tinyrainbow.js +84 -0
  32. package/package.json +1 -1
@@ -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 = /* @__PURE__ */ defineComponent({
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 = /* @__PURE__ */ defineComponent({
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 = /* @__PURE__ */ defineComponent({
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) {
@@ -0,0 +1,118 @@
1
+ import { h as describe, m as it } from "../../../node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js";
2
+ import { g as globalExpect } from "../../../node_modules/.pnpm/@voidzero-dev_vite-plus-test@0.1.14_@types_node@24.12.0_@voidzero-dev_vite-plus-core@0._0c620f79340a656256b2ffe23b3e6dd1/node_modules/@voidzero-dev/vite-plus-test/dist/chunks/test.CBQUpOM3.js";
3
+ import { useStreaming } from "../useStreaming.js";
4
+ import { effectScope, nextTick, ref } from "vue";
5
+ //#region src/XMarkdown/composables/__tests__/useStreaming.test.ts
6
+ /**
7
+ * Drive `useStreaming` with streaming enabled and return the latest
8
+ * `processedContent` value after Vue flushes.
9
+ */
10
+ async function stream(chunks, opts = {}) {
11
+ let result = "";
12
+ const scope = effectScope();
13
+ await scope.run(async () => {
14
+ const content = ref("");
15
+ const { processedContent } = useStreaming(content, ref({ hasNextChunk: opts.hasNextChunk ?? true }));
16
+ await nextTick();
17
+ let acc = "";
18
+ for (const chunk of chunks) {
19
+ acc += chunk;
20
+ content.value = acc;
21
+ await nextTick();
22
+ }
23
+ result = processedContent.value;
24
+ });
25
+ scope.stop();
26
+ return result;
27
+ }
28
+ describe("useStreaming fence correctness", () => {
29
+ it("streams plain text unchanged", async () => {
30
+ globalExpect(await stream(["Hello", " world"])).toBe("Hello world");
31
+ });
32
+ it("opens a fence on a partial last line immediately", async () => {
33
+ globalExpect(await stream(["```js\nconst a = 1;\n"])).toBe("```js\nconst a = 1;\n");
34
+ });
35
+ it("keeps an unclosed fence open for streaming continuation", async () => {
36
+ globalExpect(await stream(["```\ncode here"])).toBe("```\ncode here");
37
+ });
38
+ it("closes a fence once its line is completed by a newline", async () => {
39
+ globalExpect(await stream(["```\ncode\n```\nafter"])).toBe("```\ncode\n```\nafter");
40
+ });
41
+ it("requires the same fence char to close", async () => {
42
+ globalExpect(await stream(["~~~\ncode\n```\nstill in fence"])).toBe("~~~\ncode\n```\nstill in fence");
43
+ });
44
+ it("requires at least the opening run length to close", async () => {
45
+ globalExpect(await stream(["````\ncode\n```\nstill in fence"])).toBe("````\ncode\n```\nstill in fence");
46
+ });
47
+ it("allows a longer closing fence", async () => {
48
+ globalExpect(await stream(["```\ncode\n````\nafter"])).toBe("```\ncode\n````\nafter");
49
+ });
50
+ it("treats a closing fence with trailing whitespace as valid", async () => {
51
+ globalExpect(await stream(["```\ncode\n``` \nafter"])).toBe("```\ncode\n``` \nafter");
52
+ });
53
+ it("does not treat a closing fence with non-whitespace tail as close", async () => {
54
+ globalExpect(await stream(["```\ncode\n```js\nstill in fence"])).toBe("```\ncode\n```js\nstill in fence");
55
+ });
56
+ });
57
+ describe("useStreaming CRLF semantics", () => {
58
+ it("handles CRLF fence open and close", async () => {
59
+ globalExpect(await stream(["```\r\ncode\r\n```\r\nafter"])).toBe("```\r\ncode\r\n```\r\nafter");
60
+ });
61
+ it("handles a trailing CR", async () => {
62
+ globalExpect(await stream(["```\r"])).toBe("```\r");
63
+ });
64
+ });
65
+ describe("useStreaming long single-line content (base64 images)", () => {
66
+ it("streams a large base64 image without quadratic slowdown", async () => {
67
+ const full = `Here is an image:\n\n![chart](data:image/png;base64,${"A".repeat(3e5)})\n\nDone.`;
68
+ const chunkSize = 1e3;
69
+ let result = "";
70
+ const scope = effectScope();
71
+ const start = performance.now();
72
+ await scope.run(async () => {
73
+ const content = ref("");
74
+ const { processedContent } = useStreaming(content, ref({ hasNextChunk: true }));
75
+ await nextTick();
76
+ let acc = "";
77
+ for (let end = chunkSize; end < full.length + chunkSize; end += chunkSize) {
78
+ acc = full.slice(0, Math.min(end, full.length));
79
+ content.value = acc;
80
+ await nextTick();
81
+ }
82
+ result = processedContent.value;
83
+ });
84
+ const elapsed = performance.now() - start;
85
+ scope.stop();
86
+ globalExpect(result).toBe(full);
87
+ globalExpect(elapsed).toBeLessThan(1e4);
88
+ }, 12e4);
89
+ it("keeps fence state correct when a code block follows long content", async () => {
90
+ const full = `![img](data:image/png;base64,${"B".repeat(2e4)})\n\n\`\`\`js\nconst a = 1;\n`;
91
+ let result = "";
92
+ const scope = effectScope();
93
+ await scope.run(async () => {
94
+ const content = ref(full.slice(0, 100));
95
+ const { processedContent } = useStreaming(content, ref({ hasNextChunk: true }));
96
+ await nextTick();
97
+ content.value = full;
98
+ await nextTick();
99
+ result = processedContent.value;
100
+ });
101
+ scope.stop();
102
+ globalExpect(result).toBe(full);
103
+ });
104
+ });
105
+ describe("useStreaming non-streaming passthrough", () => {
106
+ it("returns content verbatim when hasNextChunk is false", async () => {
107
+ let result = "";
108
+ const scope = effectScope();
109
+ await scope.run(async () => {
110
+ const { processedContent } = useStreaming(ref("```\ncode\n```\ntext"), ref({ hasNextChunk: false }));
111
+ await nextTick();
112
+ result = processedContent.value;
113
+ });
114
+ scope.stop();
115
+ globalExpect(result).toBe("```\ncode\n```\ntext");
116
+ });
117
+ });
118
+ //#endregion
@@ -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
- var isInCodeBlock = (text, isFinalChunk = false) => {
99
- const lines = text.split("\n");
100
- let inFenced = false;
101
- let fenceChar = "";
102
- let fenceLen = 0;
103
- for (let i = 0; i < lines.length; i++) {
104
- const rawLine = lines[i];
105
- const match = (rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine).match(/^(`{3,}|~{3,})(.*)$/);
106
- if (!match) continue;
107
- const fence = match[1];
108
- const after = match[2];
109
- const char = fence[0];
110
- const len = fence.length;
111
- if (!inFenced) {
112
- inFenced = true;
113
- fenceChar = char;
114
- fenceLen = len;
115
- continue;
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
- return inFenced;
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++) {
@@ -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
- if (isInCodeBlock(cache.completeMarkdown + cache.pending)) {
195
+ feedFenceState(cache.fence, char);
196
+ if (isInCodeBlock(cache.fence)) {
177
197
  commitCache(cache);
178
198
  continue;
179
199
  }
@@ -200,7 +220,8 @@ function useStreaming(content, streaming, components) {
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
  }
@@ -71,7 +71,7 @@ var VueRenderer = class {
71
71
  return h(Fragment, nodes);
72
72
  }
73
73
  sanitize(html) {
74
- const allowedTags = [...new Set([
74
+ const allowedTags = [.../* @__PURE__ */ new Set([
75
75
  ...Object.keys(this.options.components),
76
76
  "p",
77
77
  "br",
@@ -1,7 +1,7 @@
1
1
  //#region src/XMarkdown/core/detectUnclosedComponentTags.ts
2
2
  var WHITESPACE_REGEX = /\s/;
3
3
  var TAG_NAME_CHAR_REGEX = /[a-zA-Z0-9-]/;
4
- var VOID_ELEMENTS = new Set([
4
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
5
5
  "area",
6
6
  "base",
7
7
  "br",
@@ -6,7 +6,7 @@ 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 = /* @__PURE__ */ defineComponent({
9
+ var index_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
10
10
  __name: "index",
11
11
  props: {
12
12
  content: { default: "" },
@@ -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;