@shijiu/jsview-vue-samples 2.3.151-test.0 → 3.0.0-test.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.
Files changed (228) hide show
  1. package/ABImageAlt/App.vue +114 -0
  2. package/ABImageAlt/Item.vue +133 -0
  3. package/{MetroWidgetDemos/SkeletonDiagram/assets/imageList.json → ABImageAlt/assets/imageList.js} +9 -1
  4. package/ABImageAlt/data.js +17 -0
  5. package/AI_Check_Rules.txt +5 -0
  6. package/AnimPicture/App.vue +20 -10
  7. package/ApicSwitch/App.vue +90 -0
  8. package/ApicSwitch/TabItem.vue +65 -0
  9. package/ApicSwitch/WebpShow.vue +24 -0
  10. package/ApicSwitch/data.js +50 -0
  11. package/Basic/AI_skills_update.md +1 -0
  12. package/Basic/components/div/ColorFormatTest.vue +93 -0
  13. package/Basic/components/div/DivRadius.vue +97 -15
  14. package/Basic/components/div/DivTag3Group.vue +30 -0
  15. package/Basic/components/img/ImageType.vue +65 -0
  16. package/Basic/components/panel/Panel2.vue +13 -1
  17. package/Basic/components/text/TextAlign.vue +7 -1
  18. package/BlobLoadTest/App.vue +201 -0
  19. package/BreakLinesApi/App.vue +82 -0
  20. package/CanvasDrawChart/App.vue +11 -0
  21. package/CanvasDrawChart/Graph1.vue +104 -0
  22. package/CanvasDrawChart/Graph2.vue +115 -0
  23. package/CssPreprocessor/Scss/PanelData.js +1 -1
  24. package/CssPreprocessor/Scss/components/scss-group4/ScssMaps.vue +4 -3
  25. package/CssPreprocessor/Scss/components/scss-group5/ScssImporting.vue +2 -2
  26. package/DemoForOperator/Bounce/FreeMoveBuilder.js +1 -1
  27. package/DemoForOperator/Firework1/App.vue +12 -1
  28. package/DemoForOperator/Firework1/Fireworks.vue +18 -0
  29. package/DemoForOperator/FlipPage/FlipPage/FlipPage.vue +1 -0
  30. package/DemoForOperator/Focus/Alpha/Item.vue +1 -0
  31. package/DemoForOperator/Focus/Light/Item.vue +1 -0
  32. package/DemoForOperator/Focus/Normal/Item.vue +1 -0
  33. package/DemoForOperator/FrameShadow/FrameShadow.vue +1 -1
  34. package/DemoForOperator/Genie/App.vue +20 -6
  35. package/DemoForOperator/Genie/App2.vue +61 -0
  36. package/DemoForOperator/Genie/geniePakcer/GenieImage.vue +298 -0
  37. package/DemoForOperator/Genie/geniePakcer/GenieSlot.vue +292 -0
  38. package/DemoForOperator/Genie/geniePakcer/GenieTools.ts +463 -0
  39. package/DemoForOperator/Jigsaw/JigsawFull.vue +12 -7
  40. package/DemoForOperator/Jigsaw/JigsawSingle.vue +13 -7
  41. package/DemoForOperator/LongChatBox/App.vue +13 -13
  42. package/DemoForOperator/LongChatBox/Bubble.vue +78 -66
  43. package/DemoForOperator/LongChatBox/LongChat.vue +68 -36
  44. package/DemoForOperator/LongChatBox/testData.js +7 -44
  45. package/DemoForOperator/Sound/Bounce/FreeMoveBuilder.js +1 -1
  46. package/DemoForOperator/routeList.js +8 -2
  47. package/DemoHomepage/App.vue +74 -1
  48. package/DemoHomepage/components/BodyFrame.vue +1 -0
  49. package/DemoHomepage/components/TabFrame.vue +1 -1
  50. package/DemoHomepage/router.js +723 -160
  51. package/DemoHomepage/views/Homepage.vue +60 -2
  52. package/DemoHomepage/watchTest.vue +50 -0
  53. package/FilterDemo/AnimatePic.vue +63 -17
  54. package/FilterDemo/App.vue +3 -3
  55. package/FlexCellDemo/AI_skills_update.md +4 -0
  56. package/FlexCellDemo/TestFrame1.vue +12 -2
  57. package/FlexCellDemo/TestFrame2.vue +10 -1
  58. package/FlexCellDemo/TestFrame3.vue +114 -59
  59. package/FpsLimit/App.vue +102 -0
  60. package/FreeMoveChildAttract/App.vue +18 -8
  61. package/FreeMoveLink/App.vue +49 -20
  62. package/Hover/App.vue +144 -0
  63. package/HttpRequestSSE/SSEReader.js +200 -0
  64. package/ImpactStop/App.vue +2 -2
  65. package/Input/FullKeyboard.vue +3 -3
  66. package/Input/InputPanel.vue +63 -3
  67. package/JsvLine/App.vue +53 -38
  68. package/LatexDemo/App.vue +3 -1
  69. package/LatexFormula/App.vue +196 -0
  70. package/LongImage/App.vue +1 -1
  71. package/LongImage/LongImageScroll.vue +100 -47
  72. package/LongImage/Scroll.vue +28 -9
  73. package/LongText/LongTextScroll.vue +1 -0
  74. package/Markdown/App.vue +36 -0
  75. package/Markdown/Bubble.vue +109 -0
  76. package/Markdown/LongChat.vue +206 -0
  77. package/Markdown/Markdown/Markdown.vue +156 -0
  78. package/Markdown/Markdown/index.ts +1 -0
  79. package/Markdown/Markdown/marked/LICENSE.md +45 -0
  80. package/Markdown/Markdown/marked/index.ts +2 -0
  81. package/Markdown/Markdown/marked/marked.d.ts +756 -0
  82. package/Markdown/Markdown/marked/marked.js +71 -0
  83. package/Markdown/Markdown/parser.ts +1365 -0
  84. package/Markdown/data.js +581 -0
  85. package/MetroWidgetDemos/AI_skills_update.md +2 -0
  86. package/MetroWidgetDemos/ListExpand/ChildItem.vue +130 -0
  87. package/MetroWidgetDemos/ListExpand/ExpandItem.vue +375 -0
  88. package/MetroWidgetDemos/ListExpand/ExpandItem1.vue +403 -0
  89. package/MetroWidgetDemos/ListExpand/assets/arrow-down.png +0 -0
  90. package/MetroWidgetDemos/ListExpand/assets/up-arrow.png +0 -0
  91. package/MetroWidgetDemos/ListExpand/components/WidgetListHandler.vue +150 -0
  92. package/MetroWidgetDemos/ListExpand/index.vue +88 -0
  93. package/MetroWidgetDemos/ListExpand/list.js +2421 -0
  94. package/MetroWidgetDemos/RefreshDemo/App.vue +14 -1
  95. package/MetroWidgetDemos/RenderAccelerate/App.vue +142 -0
  96. package/MetroWidgetDemos/RenderAccelerate/AppPage.vue +78 -0
  97. package/MetroWidgetDemos/RenderAccelerate/AppTab.vue +62 -0
  98. package/MetroWidgetDemos/RenderAccelerate/ContentItem.vue +409 -0
  99. package/MetroWidgetDemos/RenderAccelerate/Item.vue +67 -0
  100. package/MetroWidgetDemos/RenderAccelerate/TabItem.vue +100 -0
  101. package/MetroWidgetDemos/RenderAccelerate/ViewSwiper.vue +215 -0
  102. package/MetroWidgetDemos/RenderAccelerate/WidgetItem.vue +107 -0
  103. package/MetroWidgetDemos/SkeletonDiagram/App.vue +35 -8
  104. package/MetroWidgetDemos/SkeletonDiagram/Item.vue +11 -2
  105. package/MetroWidgetDemos/SkeletonDiagram/assets/imageList.js +245 -0
  106. package/MetroWidgetDemos/SkeletonDiagram/data.js +3 -3
  107. package/MetroWidgetDemos/SpatialNav/App.vue +177 -0
  108. package/MetroWidgetDemos/SpatialNav/Buttons.vue +83 -0
  109. package/MetroWidgetDemos/SpatialNav/CustomFocus.vue +57 -0
  110. package/MetroWidgetDemos/{TripleWidget → SpatialNav}/Item.vue +1 -8
  111. package/MetroWidgetDemos/{TripleWidget/WidgetItem.vue → SpatialNav/SimpleFloor.vue} +14 -45
  112. package/MetroWidgetDemos/SpatialNav/StepMw.vue +113 -0
  113. package/MetroWidgetDemos/SpatialNav/TabContent/TabContent.vue +185 -0
  114. package/MetroWidgetDemos/SpatialNav/TripleSection/TripleSection.vue +69 -0
  115. package/MetroWidgetDemos/SpatialNav/TripleSection/WidgetItem.vue +100 -0
  116. package/MetroWidgetDemos/SpatialNav/TvSection/List.vue +75 -0
  117. package/MetroWidgetDemos/SpatialNav/TvSection/TvSection.vue +91 -0
  118. package/MetroWidgetDemos/basic2/App.vue +407 -0
  119. package/MetroWidgetDemos/basic2/Item.vue +68 -0
  120. package/MetroWidgetDemos/direction/App.vue +22 -0
  121. package/MetroWidgetDemos/gazeFocusDiff/App.vue +126 -0
  122. package/MetroWidgetDemos/gazeFocusDiff/Item.vue +87 -0
  123. package/MetroWidgetDemos/minimalUsage/App.vue +66 -0
  124. package/MetroWidgetDemos/minimalUsage/Item.vue +54 -0
  125. package/MetroWidgetDemos/ninePatchFocusPage/App.vue +22 -7
  126. package/MetroWidgetDemos/ninePatchFocusPage/Item.vue +7 -5
  127. package/MetroWidgetDemos/ninePatchFocusPage/focusConstants.js +2 -0
  128. package/MetroWidgetDemos/routeList.js +203 -12
  129. package/MetroWidgetDemos/slideSetting/App.vue +288 -99
  130. package/MetroWidgetDemos/zIndex/App.vue +117 -0
  131. package/MetroWidgetDemos/zIndex/Item.vue +61 -0
  132. package/NinePatchTester/App.vue +24 -31
  133. package/PreDecode/App.vue +140 -0
  134. package/ReactiveTest/App.vue +115 -0
  135. package/ReactiveTest/Item.vue +92 -0
  136. package/ReactiveTest/assets/imageList.js +245 -0
  137. package/ReactiveTest/component/TestSmartDiv.vue +50 -0
  138. package/ReactiveTest/component/TestSmartDivSrcList.vue +74 -0
  139. package/ReactiveTest/component/TestSmartImage.vue +46 -0
  140. package/ReactiveTest/component/TestSmartImageSrcList.vue +90 -0
  141. package/ReactiveTest/component/TestSmartImageStyle.vue +41 -0
  142. package/ReactiveTest/data.js +49 -0
  143. package/ScreenToBlob/App.vue +250 -0
  144. package/SecTorTest/App.vue +9 -3
  145. package/SpringFestival/SpringFestivalScene/FreeMoveBuilder.js +3 -3
  146. package/SyncDecode/App.vue +137 -0
  147. package/TextSizeLimit/App.vue +211 -0
  148. package/TransitPage/App.vue +2 -0
  149. package/assets/logo.png +0 -0
  150. package/package.json +5 -5
  151. package/DemoForOperator/Genie/geniePakcer/Genie.vue +0 -741
  152. package/DemoForOperator/LongChatBox/TextManager.ts +0 -147
  153. package/DemoForOperator/LongChatBox/VirtualList.vue +0 -298
  154. package/DemoForOperator/LongChatBox/utile.js +0 -331
  155. package/DivMetroPerformance/App.vue +0 -157
  156. package/DivMetroPerformance/Item.vue +0 -58
  157. package/DivMetroPerformance/assets/bg.jpg +0 -0
  158. package/DivMetroPerformance/assets/coupon_content.png +0 -0
  159. package/DivMetroPerformance/assets/coupon_left.png +0 -0
  160. package/DivMetroPerformance/assets/coupon_mid.png +0 -0
  161. package/DivMetroPerformance/assets/coupon_right.png +0 -0
  162. package/DivMetroPerformance/assets/focus_border.png +0 -0
  163. package/DivMetroPerformance/assets/holder_logo.png +0 -0
  164. package/DivMetroPerformance/assets/jrbm.png +0 -0
  165. package/DivMetroPerformance/assets/line_left.png +0 -0
  166. package/DivMetroPerformance/assets/line_mid.png +0 -0
  167. package/DivMetroPerformance/assets/line_right.png +0 -0
  168. package/DivMetroPerformance/assets/loading.png +0 -0
  169. package/DivMetroPerformance/assets/logo.png +0 -0
  170. package/DivMetroPerformance/assets/mcjx.png +0 -0
  171. package/DivMetroPerformance/assets/tao.png +0 -0
  172. package/DivMetroPerformance/assets/tmall.png +0 -0
  173. package/DivMetroPerformance/border.png +0 -0
  174. package/DivMetroPerformance/components/ContentItem.vue +0 -384
  175. package/DivMetroPerformance/components/MyTab.vue +0 -129
  176. package/DivMetroPerformance/data.js +0 -124
  177. package/DivMetroPerformance/utils/GridItem.vue +0 -28
  178. package/DivMetroPerformance/utils/GridPlate.vue +0 -85
  179. package/MediaDemo/App.vue +0 -127
  180. package/MediaDemo/assets/audio-poster.png +0 -0
  181. package/MediaDemo/components/Button.vue +0 -69
  182. package/MediaDemo/components/Controllor.vue +0 -286
  183. package/MediaDemo/components/StatusBar.vue +0 -100
  184. package/MediaDemo/components/frames/AudioFrame.vue +0 -39
  185. package/MediaDemo/components/frames/AudioPoster.vue +0 -48
  186. package/MediaDemo/components/frames/MediaFrame.vue +0 -153
  187. package/MediaDemo/components/frames/VideoFrame.vue +0 -39
  188. package/MetroWidgetDemos/TripleWidget/App.vue +0 -87
  189. package/MetroWidgetDemos/TripleWidget/SWidgetItem.vue +0 -99
  190. package/Parkour/App.vue +0 -13
  191. package/Parkour/Common/Context.js +0 -21
  192. package/Parkour/Common/MatchmanInfo.js +0 -62
  193. package/Parkour/Common/Random.js +0 -61
  194. package/Parkour/Common/Sound.js +0 -50
  195. package/Parkour/appConfig/HOW_TO_CONFIG.md +0 -20
  196. package/Parkour/appConfig/app.config.mjs +0 -5
  197. package/Parkour/appConfig/app_sign_private_key_sample.crt +0 -28
  198. package/Parkour/appConfig/app_sign_public_key_sample.pem +0 -9
  199. package/Parkour/appConfig/jsview.config.mjs +0 -39
  200. package/Parkour/assets/Bgimages/bg1.png +0 -0
  201. package/Parkour/assets/Bgimages/bg2.png +0 -0
  202. package/Parkour/assets/Bgimages/bg3.png +0 -0
  203. package/Parkour/assets/Bgimages/bg4.png +0 -0
  204. package/Parkour/assets/Bgimages/bg5.png +0 -0
  205. package/Parkour/assets/audio/jump.mp3 +0 -0
  206. package/Parkour/assets/audio/lose.mp3 +0 -0
  207. package/Parkour/assets/role_skin1/fail.json +0 -44
  208. package/Parkour/assets/role_skin1/fail.png +0 -0
  209. package/Parkour/assets/role_skin1/jump_down.json +0 -20
  210. package/Parkour/assets/role_skin1/jump_down.png +0 -0
  211. package/Parkour/assets/role_skin1/jump_up.json +0 -44
  212. package/Parkour/assets/role_skin1/jump_up.png +0 -0
  213. package/Parkour/assets/role_skin1/roll.json +0 -44
  214. package/Parkour/assets/role_skin1/roll.png +0 -0
  215. package/Parkour/assets/role_skin1/run.json +0 -52
  216. package/Parkour/assets/role_skin1/run.png +0 -0
  217. package/Parkour/components/Backdrop.vue +0 -61
  218. package/Parkour/components/GameSence.vue +0 -602
  219. package/Parkour/components/Matchman.vue +0 -85
  220. package/ThrowMoveDemo/AccelerateDemo.vue +0 -85
  221. package/ThrowMoveDemo/App.vue +0 -104
  222. package/ThrowMoveDemo/LRParabolicDemo.vue +0 -101
  223. package/ThrowMoveDemo/TargetDemo.vue +0 -87
  224. package/ThrowMoveDemo/UDParabolicDemo.vue +0 -92
  225. /package/{AnimPicture/assets → assets}/animated_webp.webp +0 -0
  226. /package/{AnimPicture/assets → assets}/ball_3.webp +0 -0
  227. /package/{AnimPicture/assets → assets}/girl_run.gif +0 -0
  228. /package/{AnimPicture/assets → assets}/quan.webp +0 -0
@@ -0,0 +1,1365 @@
1
+ import { marked, Token, Tokens } from "./marked";
2
+
3
+ import { h, VNode, Ref, ref } from "vue";
4
+ import { JsvTextBox, JsvFlexDiv, JsvLatex, processLatexStr } from "jsview";
5
+
6
+ //禁用setext heading语法
7
+ const tokenizer = {
8
+ lheading(src: string): false | Tokens.Heading | undefined {
9
+ return undefined;
10
+ },
11
+ };
12
+ const inlineMath = {
13
+ name: "inlineMath",
14
+ level: "inline" as const, // 设置为行内级别
15
+ // start 函数帮助 marked 快速定位潜在匹配点,提高性能
16
+ start(src: string) {
17
+ return src.indexOf("\\(");
18
+ },
19
+ tokenizer(src: string, tokens: Token[]) {
20
+ // 使用正则匹配 \( ... \) 格式,注意非贪婪匹配
21
+ const rule = /^\\\(([\s\S]*?)(\\\)|$)/;
22
+ // const rule = /^\\\(([\s\S]+?)\\\)/;
23
+ const match = rule.exec(src);
24
+ if (match) {
25
+ return {
26
+ type: "inlineMath", // token 类型名
27
+ raw: match[0], // 原始文本,包含 \( 和 \)
28
+ text: match[1].trim(), // 实际内容
29
+ tokens: [],
30
+ isComplete: Boolean(match[2]),
31
+ };
32
+ }
33
+ },
34
+ };
35
+
36
+ const blockMath = {
37
+ name: "blockMath",
38
+ level: "block" as const, // 设置为块级
39
+ // start 函数帮助 marked 快速定位潜在匹配点,提高性能
40
+ start(src: string) {
41
+ return src.indexOf("\\[");
42
+ },
43
+ tokenizer(src: string, tokens: Token[]) {
44
+ // 使用正则匹配 \[ ... \] 格式, 出现\[时认为不完整,直到出现\]时认为完整
45
+ const rule = /^\\\[([\s\S]*?)(\\\]|$)/;
46
+ // const rule = /^\\\[([\s\S]+?)\\\]/;
47
+ const match = rule.exec(src);
48
+ if (match) {
49
+ return {
50
+ type: "blockMath", // token 类型名
51
+ raw: match[0], // 原始文本,包含 \( 和 \)
52
+ text: match[1].trim(), // 实际内容
53
+ tokens: [],
54
+ isComplete: Boolean(match[2]),
55
+ };
56
+ }
57
+ },
58
+ };
59
+
60
+ marked.use({ extensions: [inlineMath] });
61
+ marked.use({ extensions: [blockMath] });
62
+ marked.use({
63
+ tokenizer,
64
+ });
65
+
66
+ function getMathNodeLoadingText(type: string): string {
67
+ if (type === "inline") {
68
+ return " ... ";
69
+ } else {
70
+ const num = Math.floor(Date.now() / 300) % 3;
71
+ return "公式生成中" + [".", "..", "..."][num];
72
+ }
73
+ }
74
+
75
+ let enableFormulaLoading = true;
76
+ function showFormulaLoading(token: any) {
77
+ return enableFormulaLoading && !token.isComplete;
78
+ }
79
+
80
+ const inlineTokenToLatexMap: Record<string, (t: Token) => string> = {
81
+ escape: (t: Token) => t.raw,
82
+ html: (t: Token) => t.raw,
83
+ link: (t: Token) => t.raw,
84
+ image: (t: Token) => t.raw,
85
+ codespan: (t: Token) => t.raw,
86
+ strong: (t: Token) => {
87
+ return `\\textb{${inlineTokensToLatex((t as Tokens.Strong).tokens)}}`;
88
+ },
89
+ em: (t: Token) => {
90
+ return `\\texti{${inlineTokensToLatex((t as Tokens.Em).tokens)}}`;
91
+ },
92
+ br: (t: Token) => `\n`,
93
+ del: (t: Token) =>
94
+ `\\textdecoration{line-through}{${inlineTokensToLatex(
95
+ (t as Tokens.Del).tokens
96
+ )}}`,
97
+ text: (t: Token) => {
98
+ if ((t as Tokens.Text).tokens) {
99
+ return inlineTokensToLatex((t as Tokens.Text).tokens);
100
+ } else {
101
+ return t.raw;
102
+ }
103
+ },
104
+ inlineMath: (t: any) => {
105
+ if (showFormulaLoading(t)) {
106
+ return getMathNodeLoadingText("inline");
107
+ } else {
108
+ return t.raw;
109
+ }
110
+ },
111
+ };
112
+
113
+ function inlineTokensToLatex(tokens: Token[] | undefined): string {
114
+ let result = "";
115
+ if (!tokens) {
116
+ return result;
117
+ }
118
+ for (const token of tokens) {
119
+ if (inlineTokenToLatexMap[token.type]) {
120
+ result += inlineTokenToLatexMap[token.type](token);
121
+ } else {
122
+ result += token.raw;
123
+ console.warn(
124
+ "block token in inline token.",
125
+ tokens.reduce((acc, token) => acc + token.raw, "")
126
+ );
127
+ }
128
+ }
129
+ return result;
130
+ }
131
+
132
+ const DefaultFontStyle = {
133
+ p: {
134
+ fontSize: 1.0,
135
+ },
136
+ h1: {
137
+ fontSize: 2.0,
138
+ },
139
+ h2: {
140
+ fontSize: 1.5,
141
+ },
142
+ h3: {
143
+ fontSize: 1.17,
144
+ },
145
+ h4: {
146
+ fontSize: 1.0,
147
+ },
148
+ h5: {
149
+ fontSize: 0.83,
150
+ },
151
+ h6: {
152
+ fontSize: 0.67,
153
+ },
154
+ };
155
+
156
+ interface RenderSharedInfo {
157
+ baseFontSize: number;
158
+ curDepth: number;
159
+ totalWidth: number;
160
+ totalHeight: number;
161
+ tabWidth: number;
162
+ visibleStart: number;
163
+ visibleRange: number;
164
+ enableLatex?: boolean;
165
+ textColor?: string;
166
+ }
167
+
168
+ abstract class BlockNode {
169
+ public children: BlockNode[];
170
+ public parent: BlockNode | undefined = undefined;
171
+ public token: Token;
172
+ public depth: number = 0;
173
+
174
+ public left: number = 0;
175
+ public top: number = 0;
176
+ public width: number = 0;
177
+ public height: number = 0;
178
+
179
+ public startIndex: number = 0;
180
+ public endIndex: number = 0;
181
+
182
+ protected nodeKey: string | undefined = undefined;
183
+ protected visible: Ref<boolean> = ref(true);
184
+ protected onSizedCbk: (width: number, height: number) => void;
185
+
186
+ protected createKey: number;
187
+
188
+ protected isDone: boolean = false;
189
+
190
+ constructor(token: Token, d: number, key: number) {
191
+ this.children = [];
192
+ this.token = token;
193
+ this.depth = d;
194
+ this.onSizedCbk = this.onSized.bind(this);
195
+ this.createKey = key;
196
+ }
197
+
198
+ public abstract render(sharedInfo: RenderSharedInfo): VNode;
199
+
200
+ getChildNodes(sharedInfo: RenderSharedInfo) {
201
+ return this.children.map((child) => child.render(sharedInfo));
202
+ }
203
+
204
+ public getSize() {
205
+ return [this.width, this.height];
206
+ }
207
+
208
+ public addChild(child: BlockNode) {
209
+ this.children.push(child);
210
+ child.parent = this;
211
+ }
212
+
213
+ public insertChild(child: BlockNode, index: number) {
214
+ this.children.splice(index, 0, child);
215
+ child.parent = this;
216
+ }
217
+
218
+ public removeChild(child: BlockNode) {
219
+ const index = this.children.indexOf(child);
220
+ if (index !== -1) {
221
+ this.children.splice(index, 1);
222
+ }
223
+ child.parent = undefined;
224
+ }
225
+
226
+ public removeChildren(children: BlockNode[]) {
227
+ children.forEach((child) => {
228
+ this.removeChild(child);
229
+ });
230
+ }
231
+
232
+ public updateToken(token: Token) {
233
+ this.token = token;
234
+ }
235
+
236
+ public onSized(width: number, height: number) {
237
+ this.width = width;
238
+ this.height = height;
239
+ }
240
+
241
+ public onChildSizeChanged(child: BlockNode) {
242
+ let top = 0;
243
+ let i = 0; //this.children.indexOf(child) + 1;
244
+ for (; i < this.children.length; ++i) {
245
+ const child = this.children[i];
246
+ child.top = top;
247
+ top += child.height;
248
+ }
249
+ this.parent?.onChildSizeChanged(this);
250
+ }
251
+
252
+ public merge(other: BlockNode) {
253
+ if (this.token.type != other.token.type) {
254
+ return false;
255
+ }
256
+ this.updateToken(other.token);
257
+ this.startIndex = other.startIndex;
258
+ this.endIndex = other.endIndex;
259
+ this.depth = other.depth;
260
+ return true;
261
+ }
262
+
263
+ protected getAbsoluteTop() {
264
+ let top = this.top;
265
+ let node: BlockNode | undefined = this;
266
+ while (node.parent) {
267
+ top += node.parent.top;
268
+ node = node.parent;
269
+ }
270
+ return top;
271
+ }
272
+
273
+ protected isVisible(top: number, height: number) {
274
+ //未获取尺寸时认为可见, 为了获得正确尺寸,未描画完前始终展示
275
+ if (this.width <= 0 || this.height <= 0 || height <= 0 || !this.isDone) {
276
+ return true;
277
+ }
278
+ const absoluteTop = this.getAbsoluteTop();
279
+ return absoluteTop + this.height >= top && absoluteTop <= top + height;
280
+ }
281
+
282
+ protected setInvisible() {
283
+ this.visible.value = false;
284
+ this.children.forEach((child) => child.setInvisible());
285
+ }
286
+
287
+ protected checkVisible(top: number, height: number) {
288
+ this.visible.value = this.isVisible(top, height);
289
+ if (!this.visible.value) {
290
+ this.children.forEach((child) => child.setInvisible());
291
+ } else {
292
+ this.children.forEach((child) => child.checkVisible(top, height));
293
+ }
294
+ }
295
+
296
+ public done() {
297
+ this.isDone = true;
298
+ this.children.forEach((child) => child.done());
299
+ }
300
+ }
301
+
302
+ class RootNode extends BlockNode {
303
+ private sharedInfo: RenderSharedInfo | undefined = undefined;
304
+ private visibleStart: number = 0;
305
+ private visibleRange: number = 0;
306
+ private rootVisible: boolean = true;
307
+ private customOnSizeChanged:
308
+ | ((width: number, height: number) => void)
309
+ | undefined = undefined;
310
+ private customOnContentChanged: ((text: string) => void) | undefined =
311
+ undefined;
312
+
313
+ public override render(sharedInfo: RenderSharedInfo) {
314
+ this.sharedInfo = sharedInfo;
315
+ this.visibleStart = sharedInfo.visibleStart;
316
+ this.visibleRange = sharedInfo.visibleRange;
317
+ return h(
318
+ JsvFlexDiv,
319
+ {
320
+ key: this.createKey,
321
+ style: {
322
+ width: sharedInfo.totalWidth,
323
+ flexDirection: "column",
324
+ alignItems: "flex-start",
325
+ },
326
+ onSized: this.onSizedCbk,
327
+ },
328
+ () => this.getChildNodes(sharedInfo)
329
+ );
330
+ }
331
+
332
+ public override onChildSizeChanged(child: BlockNode) {
333
+ super.onChildSizeChanged(child);
334
+ if (this.sharedInfo && this.rootVisible) {
335
+ this.checkVisible(this.visibleStart, this.visibleRange);
336
+ }
337
+ //子通知过来时,父的JsvFlexDiv的回调已经调用过了
338
+ this.customOnSizeChanged?.(this.width, this.height);
339
+ }
340
+
341
+ public setVisibleRange(start: number, range: number) {
342
+ this.visibleStart = start;
343
+ this.visibleRange = range;
344
+ if (this.rootVisible) {
345
+ this.checkVisible(start, range);
346
+ }
347
+ }
348
+
349
+ public getLeafNodesByIndexRange(start: number, end: number): BlockNode[] {
350
+ const result: BlockNode[] = [];
351
+
352
+ const traverse = (node: BlockNode) => {
353
+ // 如果是叶节点(没有子节点)
354
+ if (node.children.length === 0) {
355
+ if (node.startIndex < end && node.endIndex > start) {
356
+ result.push(node);
357
+ }
358
+ } else {
359
+ // 非叶节点,递归遍历所有子节点
360
+ node.children.forEach((child) => traverse(child));
361
+ }
362
+ };
363
+ this.children.forEach((child) => traverse(child));
364
+ return result;
365
+ }
366
+
367
+ public registerOnSizeChanged(
368
+ callback: (width: number, height: number) => void
369
+ ) {
370
+ this.customOnSizeChanged = callback;
371
+ }
372
+
373
+ public show() {
374
+ this.rootVisible = true;
375
+ this.checkVisible(this.visibleStart, this.visibleRange);
376
+ }
377
+
378
+ public hide() {
379
+ this.rootVisible = false;
380
+ this.setInvisible();
381
+ }
382
+ }
383
+
384
+ function hasText(token: Token): token is Token & { text: string } {
385
+ if (token == null) {
386
+ return false;
387
+ }
388
+ return "text" in token;
389
+ }
390
+
391
+ function hasTokens(token: Token): token is Token & { tokens: Token[] } {
392
+ if (token == null) {
393
+ return false;
394
+ }
395
+ return "tokens" in token;
396
+ }
397
+
398
+ function isMathNode(token: Token): boolean {
399
+ if (token.type == "inlineMath" || token.type == "blockMath") {
400
+ return true;
401
+ }
402
+ if (hasTokens(token) && token.tokens.length > 0) {
403
+ for (const t of token.tokens) {
404
+ if (t.type == "inlineMath" || t.type == "blockMath") {
405
+ return true;
406
+ } else {
407
+ if (isMathNode(t)) {
408
+ return true;
409
+ }
410
+ }
411
+ }
412
+ }
413
+ return false;
414
+ }
415
+
416
+ //绘制文字的叶节点
417
+ abstract class DrawTextNode extends BlockNode {
418
+ public renderText: Ref<string> = ref("");
419
+ protected cachedStyle: any = undefined;
420
+ protected isMathNode: boolean = false;
421
+ protected textDrawKey: number = 0;
422
+
423
+ constructor(token: Token, d: number, key: number) {
424
+ super(token, d, key);
425
+ this.renderText.value = this.getDrawText();
426
+ this.isMathNode = isMathNode(token);
427
+ }
428
+
429
+ public abstract getDrawText(): string;
430
+ protected abstract getDrawStyleImpl(sharedInfo: RenderSharedInfo): any;
431
+ public getDrawStyle(sharedInfo: RenderSharedInfo): any {
432
+ const style = this.getDrawStyleImpl(sharedInfo);
433
+ if (this.isDone && this.height > 0) {
434
+ style.height = this.height;
435
+ }
436
+ if (
437
+ this.cachedStyle &&
438
+ this.cachedStyle.width == style.width &&
439
+ this.cachedStyle.fontSize == style.fontSize &&
440
+ this.cachedStyle.height == style.height &&
441
+ this.cachedStyle.color == style.color
442
+ ) {
443
+ return this.cachedStyle;
444
+ }
445
+ this.cachedStyle = style;
446
+ return style;
447
+ }
448
+
449
+ public override merge(other: BlockNode) {
450
+ let result = super.merge(other);
451
+ this.isMathNode = isMathNode(this.token);
452
+ return result;
453
+ }
454
+
455
+ public override render(sharedInfo: RenderSharedInfo) {
456
+ if (!this.visible.value && this.width > 0 && this.height > 0) {
457
+ return h("div", {
458
+ style: {
459
+ width: this.width,
460
+ height: this.height,
461
+ // backgroundColor: "#0000ff99",
462
+ },
463
+ });
464
+ }
465
+ const style = this.getDrawStyle(sharedInfo);
466
+
467
+ if (this.isMathNode && sharedInfo.enableLatex) {
468
+ const latexStr = wrapLongPlainTextOutsideMath(
469
+ processLatexStr(
470
+ this.renderText.value.replaceAll("\\textb{", "\\textbf{")
471
+ )!
472
+ );
473
+
474
+ let fixedWidth: number | undefined = undefined;
475
+ let fixedHeight: number | undefined = undefined;
476
+ if (this.isDone) {
477
+ fixedWidth = this.width;
478
+ fixedHeight = this.height;
479
+ };
480
+ return h(
481
+ JsvFlexDiv,
482
+ {
483
+ key: this.createKey,
484
+ onSized: this.onSizedCbk,
485
+ style: {
486
+ width: fixedWidth,
487
+ height: fixedHeight,
488
+ },
489
+ },
490
+ () =>
491
+ h(JsvLatex, {
492
+ latexStr,
493
+ width: style.width,
494
+ fontSize: style.fontSize,
495
+ color: style.color,
496
+ lineSpace: 2,
497
+ })
498
+ );
499
+ }
500
+ // 使用基于节点位置的稳定 key,而不是基于内容
501
+ // 这样即使内容更新,只要节点位置不变,key 就不变,组件会被正确复用
502
+ const { width, ...restStyle } = style;
503
+ return h(
504
+ JsvFlexDiv,
505
+ {
506
+ key: this.createKey,
507
+ onSized: this.onSizedCbk,
508
+ },
509
+ () =>
510
+ h(
511
+ JsvTextBox,
512
+ {
513
+ enableLatex: true,
514
+ style: { ...restStyle },
515
+ maxWidth: style.width,
516
+ },
517
+ () => this.renderText.value
518
+ )
519
+ );
520
+ }
521
+
522
+ public override updateToken(token: Token): void {
523
+ super.updateToken(token);
524
+ const newText = this.getDrawText();
525
+ if (newText !== this.renderText.value) {
526
+ this.renderText.value = newText;
527
+ this.textDrawKey++;
528
+ }
529
+ }
530
+
531
+ public override onSized(width: number, height: number) {
532
+ //JsvFlexDiv的onSized的触发顺序是先父后子,所以在叶节点的onSized中重新计算位置
533
+ super.onSized(width, height);
534
+ this.parent?.onChildSizeChanged(this);
535
+ }
536
+ }
537
+
538
+ //处理一些不支持的标准
539
+ function latexSanitizer(text: string): string {
540
+ //align*环境替换为aligned环境
541
+ text = text
542
+ .replaceAll("\\begin{align*}", "\\begin{aligned}")
543
+ .replaceAll("\\end{align*}", "\\end{aligned}");
544
+ return text;
545
+ }
546
+
547
+ /** 将 \text{...} 内需要转义的 { } \ 转义,避免破坏 LaTeX 结构 */
548
+ function escapeTextContent(s: string): string {
549
+ return s.replace(/\\/g, "\\\\").replace(/{/g, "\\{").replace(/}/g, "\\}");
550
+ }
551
+
552
+ const MIN_PLAIN_LENGTH_FOR_BREAK = 4;
553
+
554
+ /**
555
+ * 在 \( \)、\[ \] 之外的普通文字(非 LaTeX 命令),若长度超过 4 个字符,
556
+ * 用 \breakEverywhere{true}\text{原文字}\breakEverywhere{false} 包裹,便于断行。
557
+ */
558
+ function wrapLongPlainTextOutsideMath(latexStr: string): string {
559
+ const parts: string[] = [];
560
+ let i = 0;
561
+ const n = latexStr.length;
562
+
563
+ while (i < n) {
564
+ const two = () => latexStr.slice(i, i + 2);
565
+
566
+ // 1. 跳过 \( ... \) 整段(含可选的结尾 \))
567
+ if (two() === "\\(") {
568
+ let j = i + 2;
569
+ while (j < n && latexStr.slice(j, j + 2) !== "\\)") j++;
570
+ if (latexStr.slice(j, j + 2) === "\\)") j += 2;
571
+ parts.push(latexStr.slice(i, j));
572
+ i = j;
573
+ continue;
574
+ }
575
+ // 2. 跳过 \[ ... \] 整段(含可选的结尾 \])
576
+ if (two() === "\\[") {
577
+ let j = i + 2;
578
+ while (j < n && latexStr.slice(j, j + 2) !== "\\]") j++;
579
+ if (latexStr.slice(j, j + 2) === "\\]") j += 2;
580
+ parts.push(latexStr.slice(i, j));
581
+ i = j;
582
+ continue;
583
+ }
584
+ // 3. 孤立的 \) 或 \] 原样保留
585
+ if (two() === "\\)" || two() === "\\]") {
586
+ parts.push(two());
587
+ i += 2;
588
+ continue;
589
+ }
590
+ // 4. 当前在“公式外”:遇到反斜杠则整段 LaTeX 命令原样保留(\command 或 \command{...})
591
+ if (latexStr[i] === "\\") {
592
+ let j = i + 1;
593
+ while (j < n && /[a-zA-Z]/.test(latexStr[j])) j++;
594
+ if (j < n && latexStr[j] === "{") {
595
+ let depth = 1;
596
+ j++;
597
+ while (j < n && depth > 0) {
598
+ if (latexStr[j] === "{") depth++;
599
+ else if (latexStr[j] === "}") depth--;
600
+ j++;
601
+ }
602
+ }
603
+ parts.push(latexStr.slice(i, j));
604
+ i = j;
605
+ continue;
606
+ }
607
+ // 5. 普通字符:收集一段连续普通文字(直到遇到 \(、\[ 或 \)
608
+ let runStart = i;
609
+ while (i < n) {
610
+ const t = latexStr.slice(i, i + 2);
611
+ if (t === "\\(" || t === "\\[" || t === "\\)" || t === "\\]") break;
612
+ if (latexStr[i] === "\\") break;
613
+ i++;
614
+ }
615
+ const run = latexStr.slice(runStart, i);
616
+ if (run.length > MIN_PLAIN_LENGTH_FOR_BREAK) {
617
+ parts.push(
618
+ `\\breakEverywhere{true}\\text{${escapeTextContent(
619
+ run
620
+ )}}\\breakEverywhere{false}`
621
+ );
622
+ } else {
623
+ parts.push(run);
624
+ }
625
+ }
626
+
627
+ return parts.join("");
628
+ }
629
+
630
+ //所有带数学公式的,整个节点作为公式处理
631
+ class BlockMathNode extends DrawTextNode {
632
+ public override getDrawText(): string {
633
+ if (hasText(this.token)) {
634
+ if ((this.token as any).isComplete) {
635
+ return latexSanitizer(this.token.text);
636
+ } else {
637
+ return "公式生成中...";
638
+ }
639
+ }
640
+ return "";
641
+ }
642
+
643
+ public override render(sharedInfo: RenderSharedInfo) {
644
+ if (!this.visible.value && this.width > 0 && this.height > 0) {
645
+ return h("div", {
646
+ style: {
647
+ width: this.width,
648
+ height: this.height,
649
+ // backgroundColor: "#0000ff99",
650
+ },
651
+ });
652
+ }
653
+ if (!sharedInfo.enableLatex) {
654
+ //作为普通文本
655
+ return super.render(sharedInfo);
656
+ }
657
+
658
+ const style = this.getDrawStyle(sharedInfo);
659
+ if (showFormulaLoading(this.token)) {
660
+ const { width, ...restStyle } = style;
661
+ return h(
662
+ JsvFlexDiv,
663
+ {
664
+ key: this.createKey,
665
+ onSized: this.onSizedCbk,
666
+ },
667
+ () =>
668
+ h(
669
+ JsvTextBox,
670
+ {
671
+ enableLatex: true,
672
+ style: { ...restStyle },
673
+ maxWidth: style.width,
674
+ },
675
+ () => getMathNodeLoadingText("block")
676
+ )
677
+ );
678
+ } else {
679
+ // 处理文字:\( \)、\[ \] 外且长度超过 4 的普通文字用 \breakEverywhere{true}\text{原文字}\breakEverywhere{false} 包裹
680
+ const latexStr = wrapLongPlainTextOutsideMath(
681
+ processLatexStr(
682
+ this.renderText.value.replaceAll("\\textb{", "\\textbf{")
683
+ )!
684
+ );
685
+ let fixedWidth: number | undefined = undefined;
686
+ let fixedHeight: number | undefined = undefined;
687
+ if (this.isDone) {
688
+ fixedWidth = this.width;
689
+ fixedHeight = this.height;
690
+ };
691
+ return h(
692
+ JsvFlexDiv,
693
+ {
694
+ key: this.createKey,
695
+ onSized: this.onSizedCbk,
696
+ style: {
697
+ width: fixedWidth,
698
+ height: fixedHeight,
699
+ },
700
+ },
701
+ () =>
702
+ h(JsvLatex, {
703
+ latexStr,
704
+ width: style.width,
705
+ fontSize: style.fontSize,
706
+ color: style.color,
707
+ lineSpace: 2,
708
+ })
709
+ );
710
+ }
711
+ }
712
+
713
+ public override getDrawStyleImpl(sharedInfo: RenderSharedInfo): Object {
714
+ return {
715
+ width: sharedInfo.totalWidth - this.depth * sharedInfo.tabWidth,
716
+ fontSize: sharedInfo.baseFontSize * DefaultFontStyle["p"].fontSize,
717
+ color: sharedInfo.textColor,
718
+ };
719
+ }
720
+ }
721
+
722
+ class HeadingNode extends DrawTextNode {
723
+ public override getDrawText(): string {
724
+ return `\\textb{${inlineTokensToLatex(
725
+ (this.token as Tokens.Heading).tokens
726
+ )}}`;
727
+ }
728
+
729
+ public override getDrawStyleImpl(sharedInfo: RenderSharedInfo): Object {
730
+ return {
731
+ width: sharedInfo.totalWidth - this.depth * sharedInfo.tabWidth,
732
+ fontSize:
733
+ sharedInfo.baseFontSize *
734
+ DefaultFontStyle[
735
+ ("h" +
736
+ (this.token as Tokens.Heading)
737
+ .depth) as keyof typeof DefaultFontStyle
738
+ ].fontSize,
739
+ color: sharedInfo.textColor,
740
+ };
741
+ }
742
+ }
743
+
744
+ class ParagraphNode extends DrawTextNode {
745
+ public override getDrawText(): string {
746
+ return inlineTokensToLatex((this.token as Tokens.Paragraph).tokens);
747
+ }
748
+
749
+ public override getDrawStyleImpl(sharedInfo: RenderSharedInfo): Object {
750
+ return {
751
+ width: sharedInfo.totalWidth - this.depth * sharedInfo.tabWidth,
752
+ fontSize: sharedInfo.baseFontSize * DefaultFontStyle["p"].fontSize,
753
+ color: sharedInfo.textColor,
754
+ };
755
+ }
756
+ }
757
+
758
+ class ListNode extends BlockNode {
759
+ public override render(sharedInfo: RenderSharedInfo) {
760
+ if (!this.visible.value && this.width > 0 && this.height > 0) {
761
+ return h("div", {
762
+ style: {
763
+ width: this.width,
764
+ height: this.height,
765
+ // backgroundColor: "#00ff0099",
766
+ },
767
+ });
768
+ }
769
+ const ordered = (this.token as Tokens.List).ordered;
770
+ //创建itemNodes,一个item一个行间距的占位符
771
+ const itemNodes = this.children.map((child, index) => {
772
+ let orderSample = "• ";
773
+ if (ordered) {
774
+ let start = 1;
775
+ try {
776
+ start = parseInt(String((this.token as Tokens.List).start));
777
+ } catch (error) {}
778
+ orderSample = index + start + ". ";
779
+ }
780
+ return h(
781
+ JsvFlexDiv,
782
+ {
783
+ key: this.createKey,
784
+ style: {
785
+ width: sharedInfo.totalWidth,
786
+ flexDirection: "row",
787
+ alignItems: "flex-start",
788
+ },
789
+ },
790
+ () => [
791
+ h(
792
+ "div",
793
+ {
794
+ style: {
795
+ width: sharedInfo.tabWidth,
796
+ textAlign: "right",
797
+ fontSize:
798
+ sharedInfo.baseFontSize * DefaultFontStyle["p"].fontSize,
799
+ },
800
+ },
801
+ orderSample
802
+ ),
803
+ child.render(sharedInfo),
804
+ ]
805
+ );
806
+ });
807
+
808
+ return h(
809
+ JsvFlexDiv,
810
+ {
811
+ key: this.createKey,
812
+ style: {
813
+ width: sharedInfo.totalWidth,
814
+ flexDirection: "column",
815
+ alignItems: "flex-start",
816
+ },
817
+ onSized: this.onSizedCbk,
818
+ },
819
+ () => itemNodes
820
+ );
821
+ }
822
+ }
823
+
824
+ class ListItemNode extends BlockNode {
825
+ public override render(sharedInfo: RenderSharedInfo): VNode {
826
+ //flexDiv bug,如果子节点为空,会影响整体的布局
827
+ if (this.children.length == 0) {
828
+ return h("div", {
829
+ style: {
830
+ width: 0,
831
+ height: 1,
832
+ // backgroundColor: "#0000ff99",
833
+ },
834
+ });
835
+ }
836
+ if (!this.visible.value && this.width > 0 && this.height > 0) {
837
+ return h("div", {
838
+ style: {
839
+ width: this.width,
840
+ height: this.height,
841
+ // backgroundColor: "#0000ff99",
842
+ },
843
+ });
844
+ }
845
+ return h(
846
+ JsvFlexDiv,
847
+ {
848
+ key: this.createKey,
849
+ style: {
850
+ width: sharedInfo.totalWidth,
851
+ flexDirection: "column",
852
+ alignItems: "flex-start",
853
+ },
854
+ onSized: this.onSized.bind(this),
855
+ },
856
+ () => this.getChildNodes(sharedInfo)
857
+ );
858
+ }
859
+ }
860
+
861
+ class TextNode extends DrawTextNode {
862
+ private cachedDrawText: string | undefined = undefined;
863
+ public override getDrawText(): string {
864
+ if ((this.token as Tokens.Text).tokens) {
865
+ this.cachedDrawText = inlineTokensToLatex(
866
+ (this.token as Tokens.Text).tokens
867
+ );
868
+ } else {
869
+ this.cachedDrawText = this.token.raw;
870
+ }
871
+ return this.cachedDrawText;
872
+ }
873
+
874
+ public override getDrawStyleImpl(sharedInfo: RenderSharedInfo): Object {
875
+ return {
876
+ width: sharedInfo.totalWidth - this.depth * sharedInfo.tabWidth,
877
+ fontSize: sharedInfo.baseFontSize * DefaultFontStyle["p"].fontSize,
878
+ color: sharedInfo.textColor,
879
+ };
880
+ }
881
+ }
882
+
883
+ class HrNode extends BlockNode {
884
+ constructor(token: Token, d: number, key: number) {
885
+ super(token, d, key);
886
+ this.height = 8;
887
+ }
888
+ public render(sharedInfo: RenderSharedInfo) {
889
+ this.width = sharedInfo.totalWidth;
890
+
891
+ return h(
892
+ "div",
893
+ {
894
+ style: {
895
+ width: sharedInfo.totalWidth,
896
+ height: this.height,
897
+ },
898
+ },
899
+ h("div", {
900
+ style: {
901
+ top: Math.round((this.height - 2) / 2),
902
+ width: sharedInfo.totalWidth,
903
+ height: 2,
904
+ backgroundColor: "#999999",
905
+ },
906
+ })
907
+ );
908
+ }
909
+ }
910
+
911
+ // class InlineMathNode extends BlockNode {
912
+ // constructor(token: Token, d: number, key: number) {
913
+ // super(token, d, key);
914
+ // }
915
+
916
+ // public render(sharedInfo: RenderSharedInfo) {
917
+ // console.log("InlineMathNode render", this.token.raw);
918
+ // return h(JsvLatex, {
919
+ // latexStr: this.token.raw,
920
+ // width: 1920, //保证单行描画,给足宽度
921
+ // fontSize: sharedInfo.baseFontSize * DefaultFontStyle["p"].fontSize,
922
+ // color: "#000000",
923
+ // });
924
+ // }
925
+ // }
926
+
927
+ class RawNode extends DrawTextNode {
928
+ public override getDrawText(): string {
929
+ return this.token.raw;
930
+ }
931
+
932
+ public override getDrawStyleImpl(sharedInfo: RenderSharedInfo): Object {
933
+ return {
934
+ width: sharedInfo.totalWidth - this.depth * sharedInfo.tabWidth,
935
+ fontSize: sharedInfo.baseFontSize * DefaultFontStyle["p"].fontSize,
936
+ color: sharedInfo.textColor,
937
+ };
938
+ }
939
+ }
940
+
941
+ class SpaceNode extends DrawTextNode {
942
+ public override getDrawText(): string {
943
+ return " ";
944
+ }
945
+
946
+ public override getDrawStyleImpl(sharedInfo: RenderSharedInfo): Object {
947
+ return {
948
+ width: sharedInfo.totalWidth - this.depth * sharedInfo.tabWidth,
949
+ fontSize: sharedInfo.baseFontSize * DefaultFontStyle["p"].fontSize,
950
+ color: sharedInfo.textColor,
951
+ };
952
+ }
953
+ }
954
+
955
+ function getBlockNode(token: Token, depth: number, key: number): BlockNode {
956
+ switch (token.type) {
957
+ case "heading":
958
+ return new HeadingNode(token, depth, key);
959
+ case "paragraph":
960
+ return new ParagraphNode(token, depth, key);
961
+ case "list":
962
+ return new ListNode(token, depth, key);
963
+ case "list_item":
964
+ return new ListItemNode(token, depth, key);
965
+ case "text":
966
+ return new TextNode(token, depth, key);
967
+ case "hr":
968
+ return new HrNode(token, depth, key);
969
+ case "space":
970
+ return new SpaceNode(token, depth, key);
971
+ case "blockquote":
972
+ return new RawNode(token, depth, key);
973
+ case "code":
974
+ return new RawNode(token, depth, key);
975
+ case "table":
976
+ return new RawNode(token, depth, key);
977
+ case "html":
978
+ return new RawNode(token, depth, key);
979
+ case "def":
980
+ return new RawNode(token, depth, key);
981
+ // case "inlineMath":
982
+ // return new InlineMathNode(token, depth, key);
983
+ case "blockMath":
984
+ return new BlockMathNode(token, depth, key);
985
+ default:
986
+ return new RawNode(token, depth, key);
987
+ }
988
+ }
989
+
990
+ function getRangeByChildren(children: BlockNode[]): [number, number] {
991
+ let startIndex = Infinity;
992
+ let endIndex = -Infinity;
993
+ children.forEach((child) => {
994
+ if (child.startIndex < startIndex) {
995
+ startIndex = child.startIndex;
996
+ }
997
+ if (child.endIndex > endIndex) {
998
+ endIndex = child.endIndex;
999
+ }
1000
+ });
1001
+ return [startIndex, endIndex];
1002
+ }
1003
+
1004
+ function getRenderTree(
1005
+ raw: string,
1006
+ tokens: Token[],
1007
+ searchStartIndex: number,
1008
+ depth: number,
1009
+ keyGen: () => number
1010
+ ): BlockNode[] {
1011
+ let innerDepth = depth;
1012
+ const result: BlockNode[] = [];
1013
+ for (const token of tokens) {
1014
+ let node = getBlockNode(token, innerDepth, keyGen());
1015
+ if (token.type == "list") {
1016
+ if ((token as any).items) {
1017
+ innerDepth++;
1018
+ for (const item of (token as any).items) {
1019
+ const itemNode = getBlockNode(item, innerDepth, keyGen());
1020
+ if (item.tokens) {
1021
+ const children =
1022
+ getRenderTree(
1023
+ raw,
1024
+ item.tokens!,
1025
+ searchStartIndex,
1026
+ innerDepth,
1027
+ keyGen
1028
+ ) || [];
1029
+ children.forEach((child) => {
1030
+ itemNode.addChild(child);
1031
+ });
1032
+ const [startIndex, endIndex] = getRangeByChildren(children);
1033
+ itemNode.startIndex = startIndex;
1034
+ itemNode.endIndex = endIndex;
1035
+ }
1036
+ node.addChild(itemNode);
1037
+ }
1038
+ const [startIndex, endIndex] = getRangeByChildren(node.children);
1039
+ node.startIndex = startIndex;
1040
+ node.endIndex = endIndex;
1041
+ innerDepth--;
1042
+ } else {
1043
+ console.warn("list has no items", token);
1044
+ }
1045
+ } else {
1046
+ //叶节点,获取开始和结束索引
1047
+ const startIndex = raw.indexOf(token.raw, searchStartIndex);
1048
+ if (startIndex < 0) {
1049
+ console.warn("token not found in content", token.raw, searchStartIndex);
1050
+ }
1051
+ node.startIndex = startIndex;
1052
+ node.endIndex = startIndex + token.raw.length;
1053
+ searchStartIndex = node.endIndex;
1054
+ }
1055
+ result.push(node);
1056
+ }
1057
+ return result;
1058
+ }
1059
+
1060
+ function mergeNode(oldNode: BlockNode, newNode: BlockNode) {
1061
+ if (oldNode.token.type != newNode.token.type) {
1062
+ return false;
1063
+ }
1064
+ if (oldNode.token.raw == newNode.token.raw) {
1065
+ return true;
1066
+ }
1067
+ // const type = newNode.token.type;
1068
+ // if (type !== "list" && type !== "list_item" && type !== "root") {
1069
+ // //叶节点直接更新
1070
+ // oldNode.merge(newNode);
1071
+ // return true;
1072
+ // }
1073
+ if (newNode.children.length == 0 && oldNode.children.length == 0) {
1074
+ oldNode.merge(newNode);
1075
+ return true;
1076
+ }
1077
+
1078
+ if (newNode.children.length < oldNode.children.length) {
1079
+ const needRemoveChildren = oldNode.children.slice(newNode.children.length);
1080
+ needRemoveChildren.forEach((child) => {
1081
+ oldNode.removeChild(child);
1082
+ });
1083
+ }
1084
+
1085
+ let lastSameItemIndex = -1;
1086
+ for (let i = 0; i < oldNode.children.length; ++i) {
1087
+ let oldChild = oldNode.children[i];
1088
+ let newChild = newNode.children[i];
1089
+ if (
1090
+ newChild &&
1091
+ oldChild.token.type == newChild.token.type &&
1092
+ oldChild.token.raw == newChild.token.raw
1093
+ ) {
1094
+ lastSameItemIndex = i;
1095
+ } else {
1096
+ break;
1097
+ }
1098
+ }
1099
+ const changeFrom = lastSameItemIndex + 1;
1100
+ const changedChildren = oldNode.children.slice(changeFrom);
1101
+ const newChangedChildren = newNode.children.slice(changeFrom);
1102
+ newChangedChildren.forEach((child, index) => {
1103
+ if (index < changedChildren.length) {
1104
+ const merged = mergeNode(changedChildren[index], child);
1105
+ if (!merged) {
1106
+ oldNode.removeChild(changedChildren[index]);
1107
+ oldNode.insertChild(child, index + changeFrom);
1108
+ }
1109
+ } else {
1110
+ //new item
1111
+ oldNode.addChild(child);
1112
+ }
1113
+ });
1114
+ oldNode.merge(newNode);
1115
+ return true;
1116
+ }
1117
+
1118
+
1119
+ //整体替换的函数
1120
+ export const normalizeLatexDelimiters = (text: string | null | undefined) => {
1121
+ if (typeof text !== "string") {
1122
+ return text;
1123
+ }
1124
+
1125
+ // 先处理块公式,避免被行内公式规则误匹配。
1126
+ const blockNormalized = text.replace(/\$\$([\s\S]+?)\$\$/g, (_, expr) => {
1127
+ return `\\[${expr}\\]`;
1128
+ });
1129
+
1130
+ // 匹配未转义的 $...$(不含换行),并保留前导字符。
1131
+ return blockNormalized.replace(/(^|[^\\$])\$([^\n$]+?)\$/g, (_, prefix, expr) => {
1132
+ return `${prefix}\\(${expr}\\)`;
1133
+ });
1134
+ };
1135
+
1136
+ const pattern = /\\([a-zA-Z]+)/;
1137
+
1138
+ export function getMarkdownWriter() {
1139
+ let text = "";
1140
+ let root: BlockNode | undefined = undefined;
1141
+ let oldTokens: Token[] | undefined = undefined;
1142
+ let key = 0;
1143
+
1144
+ const maxCacheLength = 10;
1145
+ let cacheText = "";
1146
+ let pendingStartDelimiter: "$" | "$$" | "" = "";
1147
+ let activeLatexDelimiter: "$" | "$$" | "" = "";
1148
+ let hasPendingBlockCloseDollar = false;
1149
+ let backslashCount = 0;
1150
+
1151
+ const isLatexContent = (text: string) => {
1152
+ return pattern.test(text);
1153
+ };
1154
+
1155
+ const updateBackslashCount = (char: string) => {
1156
+ if (char === "\\") {
1157
+ backslashCount += 1;
1158
+ } else {
1159
+ backslashCount = 0;
1160
+ }
1161
+ };
1162
+
1163
+ const preprocessChunk = (chunk: string, finalize = false) => {
1164
+ let chunkText = "";
1165
+
1166
+ for (let i = 0; i < chunk.length; ++i) {
1167
+ let char = chunk[i];
1168
+ let isEscapedDollar = char === "$" && backslashCount % 2 === 1;
1169
+
1170
+ if (activeLatexDelimiter === "$$" && hasPendingBlockCloseDollar) {
1171
+ if (char === "$" && !isEscapedDollar) {
1172
+ chunkText += "\\]";
1173
+ activeLatexDelimiter = "";
1174
+ hasPendingBlockCloseDollar = false;
1175
+ backslashCount = 0;
1176
+ continue;
1177
+ }
1178
+ chunkText += "$";
1179
+ hasPendingBlockCloseDollar = false;
1180
+ }
1181
+
1182
+ if (activeLatexDelimiter) {
1183
+ if (char === "$" && !isEscapedDollar) {
1184
+ if (activeLatexDelimiter === "$$") {
1185
+ hasPendingBlockCloseDollar = true;
1186
+ backslashCount = 0;
1187
+ continue;
1188
+ }
1189
+ chunkText += "\\)";
1190
+ activeLatexDelimiter = "";
1191
+ backslashCount = 0;
1192
+ continue;
1193
+ }
1194
+ chunkText += char;
1195
+ updateBackslashCount(char);
1196
+ continue;
1197
+ }
1198
+
1199
+ if (pendingStartDelimiter) {
1200
+ if (
1201
+ pendingStartDelimiter === "$" &&
1202
+ cacheText.length === 0 &&
1203
+ char === "$" &&
1204
+ !isEscapedDollar
1205
+ ) {
1206
+ pendingStartDelimiter = "$$";
1207
+ backslashCount = 0;
1208
+ continue;
1209
+ }
1210
+
1211
+ cacheText += char;
1212
+ updateBackslashCount(char);
1213
+
1214
+ if (isLatexContent(cacheText)) {
1215
+ chunkText += pendingStartDelimiter === "$$" ? "\\[" : "\\(";
1216
+ chunkText += cacheText;
1217
+ activeLatexDelimiter = pendingStartDelimiter;
1218
+ pendingStartDelimiter = "";
1219
+ cacheText = "";
1220
+ continue;
1221
+ }
1222
+
1223
+ if (cacheText.length >= maxCacheLength) {
1224
+ chunkText += pendingStartDelimiter + cacheText;
1225
+ pendingStartDelimiter = "";
1226
+ cacheText = "";
1227
+ }
1228
+ continue;
1229
+ }
1230
+
1231
+ if (char === "$" && !isEscapedDollar) {
1232
+ if (chunk[i + 1] === "$") {
1233
+ pendingStartDelimiter = "$$";
1234
+ backslashCount = 0;
1235
+ i += 1;
1236
+ continue;
1237
+ }
1238
+ pendingStartDelimiter = "$";
1239
+ backslashCount = 0;
1240
+ continue;
1241
+ }
1242
+
1243
+ chunkText += char;
1244
+ updateBackslashCount(char);
1245
+ }
1246
+
1247
+ if (finalize) {
1248
+ if (activeLatexDelimiter === "$$" && hasPendingBlockCloseDollar) {
1249
+ chunkText += "$";
1250
+ hasPendingBlockCloseDollar = false;
1251
+ }
1252
+
1253
+ if (pendingStartDelimiter) {
1254
+ chunkText += pendingStartDelimiter + cacheText;
1255
+ pendingStartDelimiter = "";
1256
+ cacheText = "";
1257
+ }
1258
+ }
1259
+
1260
+ return chunkText;
1261
+ };
1262
+
1263
+ const keyGen = () => {
1264
+ return key++;
1265
+ };
1266
+
1267
+ const parseAndSyncRoot = (appendText: string) => {
1268
+ // console.log(`parseAndSyncRoot appendText: ${appendText} root: ${root}`);
1269
+ if (!appendText && root) {
1270
+ return root;
1271
+ }
1272
+
1273
+ text += appendText;
1274
+ let newTokens: Token[] | undefined = undefined;
1275
+ const tokens = marked.lexer(text);
1276
+ let rootChangeFrom = -1;
1277
+ if (oldTokens) {
1278
+ if (oldTokens.length > tokens.length) {
1279
+ rootChangeFrom = tokens.length - 1;
1280
+ newTokens = [tokens[tokens.length - 1]];
1281
+ } else {
1282
+ rootChangeFrom = oldTokens.length - 1;
1283
+ newTokens = tokens.slice(rootChangeFrom);
1284
+ }
1285
+ } else {
1286
+ newTokens = tokens;
1287
+ }
1288
+
1289
+ oldTokens = tokens;
1290
+ if (!newTokens) {
1291
+ return;
1292
+ }
1293
+ const newNodes = getRenderTree(
1294
+ text,
1295
+ newTokens,
1296
+ root && root.children[rootChangeFrom - 1]
1297
+ ? root.children[rootChangeFrom - 1].endIndex
1298
+ : 0,
1299
+ 0,
1300
+ keyGen
1301
+ );
1302
+ // (window as any).newNodes = newNodes;
1303
+ if (!root) {
1304
+ let rootNode = new RootNode(
1305
+ {
1306
+ raw: text,
1307
+ type: "root",
1308
+ tokens: newTokens,
1309
+ },
1310
+ 0,
1311
+ keyGen()
1312
+ );
1313
+ newNodes.forEach((node) => {
1314
+ rootNode.addChild(node);
1315
+ });
1316
+ root = rootNode;
1317
+ } else {
1318
+ if (rootChangeFrom >= 0) {
1319
+ //已经完成的node
1320
+ for (let i = 0; i < rootChangeFrom; ++i) {
1321
+ root.children[i].done();
1322
+ }
1323
+ const needRemove = root.children.splice(
1324
+ rootChangeFrom + newNodes.length
1325
+ );
1326
+ needRemove.forEach((child) => {
1327
+ root!.removeChild(child);
1328
+ });
1329
+ for (let i = 0; i < newNodes.length; ++i) {
1330
+ if (i + rootChangeFrom < root.children.length) {
1331
+ const merged = mergeNode(
1332
+ root.children[i + rootChangeFrom],
1333
+ newNodes[i]
1334
+ );
1335
+ if (!merged) {
1336
+ root.removeChild(root.children[i + rootChangeFrom]);
1337
+ root.insertChild(newNodes[i], i + rootChangeFrom);
1338
+ }
1339
+ } else {
1340
+ root.addChild(newNodes[i]);
1341
+ }
1342
+ }
1343
+ } else {
1344
+ console.warn("update root but rootChangeFrom < 0");
1345
+ }
1346
+ }
1347
+ return root;
1348
+ };
1349
+
1350
+ const writer = ((chunk: string) => {
1351
+ const appendText = preprocessChunk(chunk, false);
1352
+ return parseAndSyncRoot(appendText);
1353
+ }) as ((chunk: string) => BlockNode | undefined) & { flush: () => BlockNode | undefined };
1354
+
1355
+ writer.flush = () => {
1356
+ const appendText = preprocessChunk("", true);
1357
+ return parseAndSyncRoot(appendText);
1358
+ };
1359
+
1360
+ // writer.testPreprcess = (chunk: string, finalize) => {
1361
+ // return preprocessChunk(chunk, finalize);
1362
+ // }
1363
+
1364
+ return writer;
1365
+ }