@lobehub/chat 1.82.0 → 1.82.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.cursor/rules/desktop-local-tools-implement.mdc +80 -0
  2. package/.env.desktop +2 -1
  3. package/.github/scripts/pr-comment.js +4 -9
  4. package/CHANGELOG.md +25 -0
  5. package/changelog/v1.json +9 -0
  6. package/locales/ar/electron.json +38 -2
  7. package/locales/ar/plugin.json +31 -31
  8. package/locales/bg-BG/electron.json +38 -2
  9. package/locales/bg-BG/plugin.json +31 -31
  10. package/locales/de-DE/electron.json +38 -2
  11. package/locales/de-DE/plugin.json +3 -8
  12. package/locales/en-US/electron.json +38 -2
  13. package/locales/en-US/plugin.json +3 -8
  14. package/locales/es-ES/electron.json +38 -2
  15. package/locales/es-ES/plugin.json +31 -31
  16. package/locales/fa-IR/electron.json +38 -2
  17. package/locales/fa-IR/plugin.json +31 -31
  18. package/locales/fr-FR/electron.json +38 -2
  19. package/locales/fr-FR/plugin.json +31 -31
  20. package/locales/it-IT/electron.json +38 -2
  21. package/locales/it-IT/plugin.json +31 -31
  22. package/locales/ja-JP/electron.json +38 -2
  23. package/locales/ja-JP/plugin.json +31 -31
  24. package/locales/ko-KR/electron.json +38 -2
  25. package/locales/ko-KR/plugin.json +3 -8
  26. package/locales/nl-NL/electron.json +38 -2
  27. package/locales/nl-NL/plugin.json +31 -31
  28. package/locales/pl-PL/electron.json +38 -2
  29. package/locales/pl-PL/plugin.json +3 -8
  30. package/locales/pt-BR/electron.json +38 -2
  31. package/locales/pt-BR/plugin.json +31 -31
  32. package/locales/ru-RU/electron.json +38 -2
  33. package/locales/ru-RU/plugin.json +31 -31
  34. package/locales/tr-TR/electron.json +38 -2
  35. package/locales/tr-TR/plugin.json +31 -31
  36. package/locales/vi-VN/electron.json +38 -2
  37. package/locales/vi-VN/plugin.json +3 -8
  38. package/locales/zh-CN/electron.json +38 -2
  39. package/locales/zh-CN/plugin.json +14 -9
  40. package/locales/zh-TW/electron.json +38 -2
  41. package/locales/zh-TW/plugin.json +31 -31
  42. package/package.json +1 -1
  43. package/packages/electron-client-ipc/src/events/update.ts +3 -3
  44. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Mode.tsx +222 -0
  45. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Option.tsx +104 -0
  46. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Sync.tsx +42 -0
  47. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Waiting.tsx +203 -0
  48. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/index.tsx +57 -0
  49. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateModal.tsx +242 -0
  50. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateNotification.tsx +193 -0
  51. package/src/app/[variants]/(main)/_layout/Desktop/{Titlebar.tsx → ElectronTitlebar/index.tsx} +15 -1
  52. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/BottomActions.tsx +3 -2
  53. package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +1 -1
  54. package/src/app/[variants]/layout.tsx +2 -1
  55. package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/LocalFile.tsx +65 -0
  56. package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/index.tsx +29 -0
  57. package/src/features/Conversation/components/MarkdownElements/LocalFile/index.ts +16 -0
  58. package/src/features/Conversation/components/MarkdownElements/index.ts +7 -1
  59. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +260 -0
  60. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +204 -0
  61. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +133 -0
  62. package/src/features/Conversation/components/MarkdownElements/type.ts +5 -1
  63. package/src/features/PluginDevModal/MCPManifestForm/ArgsInput.tsx +20 -0
  64. package/src/features/PluginDevModal/MCPManifestForm/MCPTypeSelect.tsx +176 -0
  65. package/src/features/PluginDevModal/{MCPManifestForm.tsx → MCPManifestForm/index.tsx} +92 -30
  66. package/src/libs/mcp/__tests__/__snapshots__/index.test.ts.snap +0 -56
  67. package/src/locales/default/electron.ts +38 -2
  68. package/src/locales/default/plugin.ts +14 -7
  69. package/src/server/modules/ElectronIPCClient/index.ts +36 -0
  70. package/src/server/routers/lambda/session.ts +2 -6
  71. package/src/server/routers/tools/mcp.ts +6 -0
  72. package/src/server/services/file/impls/index.ts +9 -1
  73. package/src/server/services/file/impls/local.test.ts +299 -0
  74. package/src/server/services/file/impls/local.ts +183 -0
  75. package/src/server/services/mcp/index.ts +19 -0
  76. package/src/services/aiModel/index.ts +5 -1
  77. package/src/services/aiProvider/index.ts +5 -1
  78. package/src/services/electron/autoUpdate.ts +4 -0
  79. package/src/services/file/index.ts +5 -1
  80. package/src/services/mcp.ts +13 -2
  81. package/src/services/message/index.ts +5 -1
  82. package/src/services/plugin/index.ts +5 -1
  83. package/src/services/session/index.ts +5 -1
  84. package/src/services/tableViewer/desktop.ts +15 -0
  85. package/src/services/tableViewer/index.ts +4 -1
  86. package/src/services/thread/index.ts +5 -1
  87. package/src/services/topic/index.ts +5 -1
  88. package/src/services/user/index.ts +5 -1
  89. package/src/store/electron/actions/app.ts +59 -0
  90. package/src/store/electron/actions/sync.ts +5 -1
  91. package/src/store/electron/initialState.ts +3 -1
  92. package/src/store/electron/store.ts +6 -1
  93. package/src/store/tool/slices/customPlugin/action.ts +16 -4
  94. package/src/utils/client/GlobalAgentContextManager.ts +85 -0
  95. package/src/utils/promptTemplate.test.ts +78 -0
  96. package/src/utils/promptTemplate.ts +17 -0
@@ -0,0 +1,260 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`createRemarkSelfClosingTagPlugin > should handle tag within a list item and generate snapshot 1`] = `
4
+ {
5
+ "children": [
6
+ {
7
+ "children": [
8
+ {
9
+ "checked": null,
10
+ "children": [
11
+ {
12
+ "children": [
13
+ {
14
+ "position": {
15
+ "end": {
16
+ "column": 26,
17
+ "line": 2,
18
+ "offset": 26,
19
+ },
20
+ "start": {
21
+ "column": 4,
22
+ "line": 2,
23
+ "offset": 4,
24
+ },
25
+ },
26
+ "type": "text",
27
+ "value": "文件名:飞机全书 一部明晰可见的历史.pdf",
28
+ },
29
+ ],
30
+ "position": {
31
+ "end": {
32
+ "column": 26,
33
+ "line": 2,
34
+ "offset": 26,
35
+ },
36
+ "start": {
37
+ "column": 4,
38
+ "line": 2,
39
+ "offset": 4,
40
+ },
41
+ },
42
+ "type": "paragraph",
43
+ },
44
+ {
45
+ "children": [
46
+ {
47
+ "checked": null,
48
+ "children": [
49
+ {
50
+ "children": [
51
+ {
52
+ "position": {
53
+ "end": {
54
+ "column": 10,
55
+ "line": 3,
56
+ "offset": 36,
57
+ },
58
+ "start": {
59
+ "column": 6,
60
+ "line": 3,
61
+ "offset": 32,
62
+ },
63
+ },
64
+ "type": "text",
65
+ "value": "路径1:",
66
+ },
67
+ {
68
+ "data": {
69
+ "hName": "localFile",
70
+ "hProperties": {
71
+ "name": "飞机全书 一部明晰可见的历史.pdf",
72
+ "path": "/Users/abc/Zotero/storage/ASBMAURK/飞机全书 一部明晰可见的历史.pdf",
73
+ },
74
+ },
75
+ "type": "localFile",
76
+ },
77
+ ],
78
+ "position": {
79
+ "end": {
80
+ "column": 110,
81
+ "line": 3,
82
+ "offset": 136,
83
+ },
84
+ "start": {
85
+ "column": 6,
86
+ "line": 3,
87
+ "offset": 32,
88
+ },
89
+ },
90
+ "type": "paragraph",
91
+ },
92
+ ],
93
+ "position": {
94
+ "end": {
95
+ "column": 110,
96
+ "line": 3,
97
+ "offset": 136,
98
+ },
99
+ "start": {
100
+ "column": 4,
101
+ "line": 3,
102
+ "offset": 30,
103
+ },
104
+ },
105
+ "spread": false,
106
+ "type": "listItem",
107
+ },
108
+ {
109
+ "checked": null,
110
+ "children": [
111
+ {
112
+ "children": [
113
+ {
114
+ "position": {
115
+ "end": {
116
+ "column": 56,
117
+ "line": 4,
118
+ "offset": 192,
119
+ },
120
+ "start": {
121
+ "column": 6,
122
+ "line": 4,
123
+ "offset": 142,
124
+ },
125
+ },
126
+ "type": "text",
127
+ "value": "路径2:/Users/abc/Downloads/测试 PDF/飞机全书 一部明晰可见的历史.pdf",
128
+ },
129
+ ],
130
+ "position": {
131
+ "end": {
132
+ "column": 56,
133
+ "line": 4,
134
+ "offset": 192,
135
+ },
136
+ "start": {
137
+ "column": 6,
138
+ "line": 4,
139
+ "offset": 142,
140
+ },
141
+ },
142
+ "type": "paragraph",
143
+ },
144
+ ],
145
+ "position": {
146
+ "end": {
147
+ "column": 56,
148
+ "line": 4,
149
+ "offset": 192,
150
+ },
151
+ "start": {
152
+ "column": 4,
153
+ "line": 4,
154
+ "offset": 140,
155
+ },
156
+ },
157
+ "spread": false,
158
+ "type": "listItem",
159
+ },
160
+ ],
161
+ "ordered": false,
162
+ "position": {
163
+ "end": {
164
+ "column": 56,
165
+ "line": 4,
166
+ "offset": 192,
167
+ },
168
+ "start": {
169
+ "column": 4,
170
+ "line": 3,
171
+ "offset": 30,
172
+ },
173
+ },
174
+ "spread": false,
175
+ "start": null,
176
+ "type": "list",
177
+ },
178
+ ],
179
+ "position": {
180
+ "end": {
181
+ "column": 56,
182
+ "line": 4,
183
+ "offset": 192,
184
+ },
185
+ "start": {
186
+ "column": 1,
187
+ "line": 2,
188
+ "offset": 1,
189
+ },
190
+ },
191
+ "spread": false,
192
+ "type": "listItem",
193
+ },
194
+ ],
195
+ "ordered": true,
196
+ "position": {
197
+ "end": {
198
+ "column": 56,
199
+ "line": 4,
200
+ "offset": 192,
201
+ },
202
+ "start": {
203
+ "column": 1,
204
+ "line": 2,
205
+ "offset": 1,
206
+ },
207
+ },
208
+ "spread": false,
209
+ "start": 1,
210
+ "type": "list",
211
+ },
212
+ {
213
+ "children": [
214
+ {
215
+ "position": {
216
+ "end": {
217
+ "column": 75,
218
+ "line": 6,
219
+ "offset": 268,
220
+ },
221
+ "start": {
222
+ "column": 1,
223
+ "line": 6,
224
+ "offset": 194,
225
+ },
226
+ },
227
+ "type": "text",
228
+ "value": "这是一本 PDF 格式的书,并且在你的 Zotero 和 Downloads 文件夹里都能找到。如果需要进一步操作,比如阅读或者提取内容,可以告诉我",
229
+ },
230
+ ],
231
+ "position": {
232
+ "end": {
233
+ "column": 75,
234
+ "line": 6,
235
+ "offset": 268,
236
+ },
237
+ "start": {
238
+ "column": 1,
239
+ "line": 6,
240
+ "offset": 194,
241
+ },
242
+ },
243
+ "type": "paragraph",
244
+ },
245
+ ],
246
+ "position": {
247
+ "end": {
248
+ "column": 1,
249
+ "line": 7,
250
+ "offset": 269,
251
+ },
252
+ "start": {
253
+ "column": 1,
254
+ "line": 1,
255
+ "offset": 0,
256
+ },
257
+ },
258
+ "type": "root",
259
+ }
260
+ `;
@@ -0,0 +1,204 @@
1
+ import remarkParse from 'remark-parse';
2
+ import { unified } from 'unified';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import { createRemarkSelfClosingTagPlugin } from './createRemarkSelfClosingTagPlugin';
6
+
7
+ // Helper function to process markdown and get the resulting tree
8
+ const processMarkdown = (markdown: string, tagName: string) => {
9
+ const processor = unified().use(remarkParse).use(createRemarkSelfClosingTagPlugin(tagName));
10
+
11
+ const tree = processor.parse(markdown);
12
+ return processor.runSync(tree);
13
+ };
14
+
15
+ describe('createRemarkSelfClosingTagPlugin', () => {
16
+ const tagName = 'localFile';
17
+
18
+ it('should replace a single self-closing tag (parsed as HTML) with a custom node', () => {
19
+ const markdown = `<${tagName} name="test.txt" path="/path/to/test.txt" />`;
20
+ const tree = processMarkdown(markdown, tagName);
21
+
22
+ expect(tree.children).toHaveLength(1);
23
+ const node = tree.children[0];
24
+ expect(node.type).toBe(tagName);
25
+ expect(node.data?.hProperties).toEqual({
26
+ name: 'test.txt',
27
+ path: '/path/to/test.txt',
28
+ });
29
+ expect(node.data?.hName).toBe(tagName);
30
+ });
31
+
32
+ it('should handle boolean attributes in a standalone tag', () => {
33
+ const markdown = `<${tagName} name="docs" path="/path/to/docs" isDirectory />`;
34
+ const tree = processMarkdown(markdown, tagName);
35
+
36
+ expect(tree.children).toHaveLength(1);
37
+ const node = tree.children[0];
38
+ expect(node.type).toBe(tagName);
39
+ expect(node.data?.hProperties).toEqual({
40
+ name: 'docs',
41
+ path: '/path/to/docs',
42
+ isDirectory: true,
43
+ });
44
+ expect(node.data?.hName).toBe(tagName);
45
+ });
46
+
47
+ it('should handle tags surrounded by text (parsed within paragraph)', () => {
48
+ const markdown = `Here is a file: <${tagName} name="report.pdf" path="report.pdf" /> Please review.`;
49
+ const tree = processMarkdown(markdown, tagName);
50
+
51
+ expect(tree.children).toHaveLength(1);
52
+ expect(tree.children[0].type).toBe('paragraph');
53
+
54
+ const paragraphChildren = tree.children[0].children;
55
+ expect(paragraphChildren).toHaveLength(3);
56
+
57
+ expect(paragraphChildren[0].type).toBe('text');
58
+ expect(paragraphChildren[0].value).toBe('Here is a file: ');
59
+
60
+ const tagNode = paragraphChildren[1];
61
+ expect(tagNode.type).toBe(tagName);
62
+ expect(tagNode.data?.hProperties).toEqual({
63
+ name: 'report.pdf',
64
+ path: 'report.pdf',
65
+ });
66
+ expect(tagNode.data?.hName).toBe(tagName);
67
+
68
+ expect(paragraphChildren[2].type).toBe('text');
69
+ expect(paragraphChildren[2].value).toBe(' Please review.');
70
+ });
71
+
72
+ it('should handle multiple tags within the same text block', () => {
73
+ const markdown = `File 1: <${tagName} name="a.txt" path="a" /> and File 2: <${tagName} name="b.txt" path="b" isDirectory />`;
74
+ const tree = processMarkdown(markdown, tagName);
75
+
76
+ expect(tree.children).toHaveLength(1);
77
+ expect(tree.children[0].type).toBe('paragraph');
78
+
79
+ const paragraphChildren = tree.children[0].children;
80
+ expect(paragraphChildren).toHaveLength(4);
81
+
82
+ expect(paragraphChildren[0].value).toBe('File 1: ');
83
+
84
+ const tagNode1 = paragraphChildren[1];
85
+ expect(tagNode1.type).toBe(tagName);
86
+ expect(tagNode1.data?.hProperties).toEqual({ name: 'a.txt', path: 'a' });
87
+ expect(tagNode1.data?.hName).toBe(tagName);
88
+
89
+ expect(paragraphChildren[2].value).toBe(' and File 2: ');
90
+
91
+ const tagNode2 = paragraphChildren[3];
92
+ expect(tagNode2.type).toBe(tagName);
93
+ expect(tagNode2.data?.hProperties).toEqual({
94
+ name: 'b.txt',
95
+ path: 'b',
96
+ isDirectory: true,
97
+ });
98
+ expect(tagNode2.data?.hName).toBe(tagName);
99
+ });
100
+
101
+ it('should handle standalone tags with no attributes', () => {
102
+ const markdown = `<${tagName} />`;
103
+ const tree = processMarkdown(markdown, tagName);
104
+
105
+ expect(tree.children).toHaveLength(1);
106
+ const node = tree.children[0];
107
+ expect(node.type).toBe(tagName);
108
+ expect(node.data?.hProperties).toEqual({});
109
+ expect(node.data?.hName).toBe(tagName);
110
+ });
111
+
112
+ it('should ignore tags with different names (parsed within a paragraph)', () => {
113
+ const markdown = `<other_tag name="ignore_me" /> <${tagName} name="process_me" path="/p" />`;
114
+ const tree = processMarkdown(markdown, tagName);
115
+
116
+ expect(tree.children).toHaveLength(1);
117
+ expect(tree.children[0].type).toBe('paragraph');
118
+
119
+ const paragraphChildren = tree.children[0].children;
120
+ expect(paragraphChildren).toHaveLength(2);
121
+
122
+ expect(paragraphChildren[0].type).toBe('text');
123
+ expect(paragraphChildren[0].value).toBe('<other_tag name="ignore_me" /> ');
124
+
125
+ const tagNode = paragraphChildren[1];
126
+ expect(tagNode.type).toBe(tagName);
127
+ expect(tagNode.data?.hProperties).toEqual({ name: 'process_me', path: '/p' });
128
+ expect(tagNode.data?.hName).toBe(tagName);
129
+ });
130
+
131
+ it('should not modify markdown without the target tag', () => {
132
+ const markdown = 'This is just regular text.';
133
+ const tree = processMarkdown(markdown, tagName);
134
+ const originalTree = unified().use(remarkParse).parse(markdown);
135
+
136
+ expect(tree).toEqual(originalTree);
137
+ });
138
+
139
+ it('should work with a different tag name provided to the creator', () => {
140
+ const otherTagName = 'customData';
141
+ const markdown = `Data: <${otherTagName} id="123" value="abc" active />`;
142
+ const tree = processMarkdown(markdown, otherTagName);
143
+
144
+ expect(tree.children).toHaveLength(1);
145
+ expect(tree.children[0].type).toBe('paragraph');
146
+ const paragraphChildren = tree.children[0].children;
147
+ expect(paragraphChildren).toHaveLength(2);
148
+
149
+ expect(paragraphChildren[0].value).toBe('Data: ');
150
+
151
+ const tagNode = paragraphChildren[1];
152
+ expect(tagNode.type).toBe(otherTagName);
153
+ expect(tagNode.data?.hProperties).toEqual({ id: '123', value: 'abc', active: true });
154
+ expect(tagNode.data?.hName).toBe(otherTagName);
155
+ });
156
+
157
+ it('should handle tag at the beginning of the text', () => {
158
+ const markdown = `<${tagName} name="start.log" path="/logs/start.log" /> Log started.`;
159
+ const tree = processMarkdown(markdown, tagName);
160
+
161
+ expect(tree.children).toHaveLength(1);
162
+ expect(tree.children[0].type).toBe('paragraph');
163
+ const paragraphChildren = tree.children[0].children;
164
+ expect(paragraphChildren).toHaveLength(2);
165
+
166
+ const tagNode = paragraphChildren[0];
167
+ expect(tagNode.type).toBe(tagName);
168
+ expect(tagNode.data?.hProperties).toEqual({ name: 'start.log', path: '/logs/start.log' });
169
+ expect(tagNode.data?.hName).toBe(tagName);
170
+
171
+ expect(paragraphChildren[1].type).toBe('text');
172
+ expect(paragraphChildren[1].value).toBe(' Log started.');
173
+ });
174
+
175
+ it('should handle tag at the end of the text', () => {
176
+ const markdown = `Log ended: <${tagName} name="end.log" path="/logs/end.log" />`;
177
+ const tree = processMarkdown(markdown, tagName);
178
+
179
+ expect(tree.children).toHaveLength(1);
180
+ expect(tree.children[0].type).toBe('paragraph');
181
+ const paragraphChildren = tree.children[0].children;
182
+ expect(paragraphChildren).toHaveLength(2);
183
+
184
+ expect(paragraphChildren[0].type).toBe('text');
185
+ expect(paragraphChildren[0].value).toBe('Log ended: ');
186
+
187
+ const tagNode = paragraphChildren[1];
188
+ expect(tagNode.type).toBe(tagName);
189
+ expect(tagNode.data?.hProperties).toEqual({ name: 'end.log', path: '/logs/end.log' });
190
+ expect(tagNode.data?.hName).toBe(tagName);
191
+ });
192
+
193
+ it('should handle tag within a list item and generate snapshot', () => {
194
+ const markdown = `
195
+ 1. 文件名:飞机全书 一部明晰可见的历史.pdf
196
+ - 路径1:<${tagName} name="飞机全书 一部明晰可见的历史.pdf" path="/Users/abc/Zotero/storage/ASBMAURK/飞机全书 一部明晰可见的历史.pdf" />
197
+ - 路径2:/Users/abc/Downloads/测试 PDF/飞机全书 一部明晰可见的历史.pdf
198
+
199
+ 这是一本 PDF 格式的书,并且在你的 Zotero 和 Downloads 文件夹里都能找到。如果需要进一步操作,比如阅读或者提取内容,可以告诉我
200
+ `;
201
+ const tree = processMarkdown(markdown, tagName);
202
+ expect(tree).toMatchSnapshot();
203
+ });
204
+ });
@@ -0,0 +1,133 @@
1
+ import debug from 'debug';
2
+ import type { Plugin } from 'unified';
3
+ import { SKIP, visit } from 'unist-util-visit';
4
+
5
+ // 创建 debugger 实例
6
+ const log = debug('lobe-markdown:remark-plugin:self-closing');
7
+
8
+ // Regex to parse attributes from a string
9
+ // Handles keys, keys with quoted values (double or single), and boolean keys
10
+ const attributeRegex = /([\w-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
11
+
12
+ // Helper function to parse the attribute string into an object
13
+ const parseAttributes = (attributeString: string): Record<string, string | boolean> => {
14
+ const attributes: Record<string, string | boolean> = {};
15
+ let match;
16
+ while ((match = attributeRegex.exec(attributeString)) !== null) {
17
+ const [, key, valueDouble, valueSingle, valueUnquoted] = match;
18
+ // If any value group is captured, use it, otherwise treat as boolean true
19
+ attributes[key] = valueDouble ?? valueSingle ?? valueUnquoted ?? true;
20
+ }
21
+ return attributes;
22
+ };
23
+
24
+ export const createRemarkSelfClosingTagPlugin =
25
+ (tagName: string): Plugin<[], any> =>
26
+ () => {
27
+ // Regex for the specific tag, ensure it matches the entire string for HTML check
28
+ const exactTagRegex = new RegExp(`^<${tagName}(\\s+[^>]*?)?\\s*\\/>$`);
29
+ // Regex for finding tags within text
30
+ const textTagRegex = new RegExp(`<${tagName}(\\s+[^>]*?)?\\s*\\/>`, 'g');
31
+
32
+ return (tree) => {
33
+ // --- DEBUG LOG START (Before Visit) ---
34
+ log('Plugin execution start for tag: %s', tagName);
35
+ log('Tree: %o', tree);
36
+ log('Tree type: %s', tree?.type);
37
+ log('Tree children count: %d', tree?.children?.length);
38
+ if (!tree || !Array.isArray(tree.children)) {
39
+ log('ERROR: Invalid Tree Structure Detected Before Visit! %o', tree);
40
+ } else {
41
+ const hasUndefinedChild = tree.children.includes(undefined);
42
+ if (hasUndefinedChild) {
43
+ log('ERROR: Tree contains undefined children Before Visit!');
44
+ log(
45
+ 'Children types: %o',
46
+ tree.children.map((c: any) => c?.type),
47
+ );
48
+ }
49
+ }
50
+ log('---------------------------------------------------');
51
+ // --- DEBUG LOG END (Before Visit) ---
52
+
53
+ // 1. Visit HTML nodes first for exact matches
54
+ // @ts-ignore
55
+ visit(tree, 'html', (node, index: number, parent) => {
56
+ log('>>> Visiting HTML node: %s', node.value);
57
+ const match = node.value.match(exactTagRegex);
58
+
59
+ if (match && parent && typeof index === 'number') {
60
+ const [, attributesString] = match;
61
+ const properties = attributesString ? parseAttributes(attributesString.trim()) : {};
62
+
63
+ const newNode = {
64
+ data: {
65
+ hName: tagName,
66
+ hProperties: properties,
67
+ },
68
+ type: tagName,
69
+ };
70
+
71
+ log('Replacing HTML node at index %d with %s node: %o', index, tagName, newNode);
72
+ parent.children.splice(index, 1, newNode);
73
+ return [SKIP, index + 1]; // Skip the node we just inserted
74
+ }
75
+ });
76
+
77
+ // 2. Visit Text nodes for inline matches
78
+ // @ts-ignore
79
+ visit(tree, 'text', (node: any, index: number, parent) => {
80
+ log('>>> Visiting Text node: "%s"', node.value);
81
+
82
+ if (!parent || typeof index !== 'number' || !node.value?.includes(`<${tagName}`)) {
83
+ return; // Quick exit if tag isn't possibly present
84
+ }
85
+
86
+ const text = node.value;
87
+ let lastIndex = 0;
88
+ const newChildren = [];
89
+ let match;
90
+
91
+ textTagRegex.lastIndex = 0; // Reset regex state
92
+
93
+ while ((match = textTagRegex.exec(text)) !== null) {
94
+ const [fullMatch, attributesString] = match;
95
+ const matchIndex = match.index;
96
+
97
+ // Add text before the match
98
+ if (matchIndex > lastIndex) {
99
+ newChildren.push({ type: 'text', value: text.slice(lastIndex, matchIndex) });
100
+ }
101
+
102
+ // Parse attributes and create the new node
103
+ const properties = attributesString ? parseAttributes(attributesString.trim()) : {};
104
+ newChildren.push({
105
+ data: {
106
+ hName: tagName,
107
+ hProperties: properties,
108
+ },
109
+ type: tagName,
110
+ });
111
+
112
+ lastIndex = matchIndex + fullMatch.length;
113
+ }
114
+
115
+ // If matches were found, replace the original text node
116
+ if (newChildren.length > 0) {
117
+ // Add any remaining text after the last match
118
+ if (lastIndex < text.length) {
119
+ newChildren.push({ type: 'text', value: text.slice(lastIndex) });
120
+ }
121
+
122
+ // --- DEBUG LOG START (Before Splice - Text Node) ---
123
+ log('--- Replacing Text Node Content ---');
124
+ log('Original text node index: %d', index);
125
+ log('-----------------------------------');
126
+ // --- DEBUG LOG END (Before Splice - Text Node) ---
127
+
128
+ parent.children.splice(index, 1, ...newChildren);
129
+ return [SKIP, index + newChildren.length]; // Skip new nodes
130
+ }
131
+ });
132
+ };
133
+ };
@@ -1,8 +1,12 @@
1
1
  import { FC, ReactNode } from 'react';
2
2
 
3
- export interface MarkdownElementProps {
3
+ export interface MarkdownElementProps<T = any> {
4
4
  children: ReactNode;
5
5
  id: string;
6
+ node: {
7
+ properties: T;
8
+ };
9
+ tagName: string;
6
10
  type: string;
7
11
  }
8
12
 
@@ -0,0 +1,20 @@
1
+ import { Input, InputProps } from 'antd';
2
+ import { memo } from 'react';
3
+
4
+ interface ArgsInputProps extends Omit<InputProps, 'value' | 'onChange'> {
5
+ onChange?: (value: string[]) => void;
6
+ value?: string[];
7
+ }
8
+
9
+ const ArgsInput = memo<ArgsInputProps>(({ value, onChange, ...res }) => {
10
+ return (
11
+ <Input
12
+ onChange={(e) => {
13
+ onChange?.([e.target.value]);
14
+ }}
15
+ value={value?.[0]}
16
+ {...res}
17
+ />
18
+ );
19
+ });
20
+ export default ArgsInput;