remnote-bridge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/connect.d.ts +12 -0
- package/dist/cli/commands/connect.js +124 -0
- package/dist/cli/commands/disconnect.d.ts +11 -0
- package/dist/cli/commands/disconnect.js +100 -0
- package/dist/cli/commands/edit-rem.d.ts +13 -0
- package/dist/cli/commands/edit-rem.js +83 -0
- package/dist/cli/commands/edit-tree.d.ts +14 -0
- package/dist/cli/commands/edit-tree.js +67 -0
- package/dist/cli/commands/health.d.ts +12 -0
- package/dist/cli/commands/health.js +100 -0
- package/dist/cli/commands/install-skill.d.ts +6 -0
- package/dist/cli/commands/install-skill.js +39 -0
- package/dist/cli/commands/read-context.d.ts +20 -0
- package/dist/cli/commands/read-context.js +77 -0
- package/dist/cli/commands/read-globe.d.ts +16 -0
- package/dist/cli/commands/read-globe.js +60 -0
- package/dist/cli/commands/read-rem.d.ts +16 -0
- package/dist/cli/commands/read-rem.js +80 -0
- package/dist/cli/commands/read-tree.d.ts +17 -0
- package/dist/cli/commands/read-tree.js +85 -0
- package/dist/cli/commands/search.d.ts +12 -0
- package/dist/cli/commands/search.js +65 -0
- package/dist/cli/config.d.ts +55 -0
- package/dist/cli/config.js +139 -0
- package/dist/cli/daemon/daemon.d.ts +11 -0
- package/dist/cli/daemon/daemon.js +186 -0
- package/dist/cli/daemon/dev-server.d.ts +26 -0
- package/dist/cli/daemon/dev-server.js +81 -0
- package/dist/cli/daemon/pid.d.ts +34 -0
- package/dist/cli/daemon/pid.js +67 -0
- package/dist/cli/daemon/send-request.d.ts +24 -0
- package/dist/cli/daemon/send-request.js +92 -0
- package/dist/cli/handlers/context-read-handler.d.ts +18 -0
- package/dist/cli/handlers/context-read-handler.js +24 -0
- package/dist/cli/handlers/edit-handler.d.ts +30 -0
- package/dist/cli/handlers/edit-handler.js +133 -0
- package/dist/cli/handlers/globe-read-handler.d.ts +17 -0
- package/dist/cli/handlers/globe-read-handler.js +23 -0
- package/dist/cli/handlers/read-handler.d.ts +16 -0
- package/dist/cli/handlers/read-handler.js +78 -0
- package/dist/cli/handlers/rem-cache.d.ts +19 -0
- package/dist/cli/handlers/rem-cache.js +63 -0
- package/dist/cli/handlers/tree-edit-handler.d.ts +30 -0
- package/dist/cli/handlers/tree-edit-handler.js +188 -0
- package/dist/cli/handlers/tree-parser.d.ts +95 -0
- package/dist/cli/handlers/tree-parser.js +506 -0
- package/dist/cli/handlers/tree-read-handler.d.ts +28 -0
- package/dist/cli/handlers/tree-read-handler.js +53 -0
- package/dist/cli/main.d.ts +7 -0
- package/dist/cli/main.js +300 -0
- package/dist/cli/protocol.d.ts +39 -0
- package/dist/cli/protocol.js +35 -0
- package/dist/cli/server/config-server.d.ts +26 -0
- package/dist/cli/server/config-server.js +363 -0
- package/dist/cli/server/ws-server.d.ts +68 -0
- package/dist/cli/server/ws-server.js +335 -0
- package/dist/cli/utils/output.d.ts +11 -0
- package/dist/cli/utils/output.js +13 -0
- package/dist/mcp/daemon-client.d.ts +31 -0
- package/dist/mcp/daemon-client.js +99 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.js +68 -0
- package/dist/mcp/instructions.d.ts +1 -0
- package/dist/mcp/instructions.js +249 -0
- package/dist/mcp/resources/edit-tree-guide.d.ts +1 -0
- package/dist/mcp/resources/edit-tree-guide.js +197 -0
- package/dist/mcp/resources/error-reference.d.ts +1 -0
- package/dist/mcp/resources/error-reference.js +132 -0
- package/dist/mcp/resources/outline-format.d.ts +1 -0
- package/dist/mcp/resources/outline-format.js +104 -0
- package/dist/mcp/resources/rem-object-fields.d.ts +1 -0
- package/dist/mcp/resources/rem-object-fields.js +331 -0
- package/dist/mcp/resources/separator-flashcard.d.ts +1 -0
- package/dist/mcp/resources/separator-flashcard.js +120 -0
- package/dist/mcp/tools/edit-tools.d.ts +5 -0
- package/dist/mcp/tools/edit-tools.js +47 -0
- package/dist/mcp/tools/infra-tools.d.ts +5 -0
- package/dist/mcp/tools/infra-tools.js +43 -0
- package/dist/mcp/tools/read-tools.d.ts +5 -0
- package/dist/mcp/tools/read-tools.js +195 -0
- package/dist/mcp/types.d.ts +12 -0
- package/dist/mcp/types.js +4 -0
- package/docs/instruction/connect.md +158 -0
- package/docs/instruction/disconnect.md +146 -0
- package/docs/instruction/edit-rem.md +509 -0
- package/docs/instruction/edit-tree.md +419 -0
- package/docs/instruction/health.md +159 -0
- package/docs/instruction/overall.md +751 -0
- package/docs/instruction/read-context.md +353 -0
- package/docs/instruction/read-globe.md +206 -0
- package/docs/instruction/read-rem.md +476 -0
- package/docs/instruction/read-tree.md +428 -0
- package/docs/instruction/search.md +196 -0
- package/package.json +41 -0
- package/remnote-plugin/package.json +48 -0
- package/remnote-plugin/postcss.config.js +5 -0
- package/remnote-plugin/public/bridge-icon.svg +8 -0
- package/remnote-plugin/public/manifest.json +22 -0
- package/remnote-plugin/src/bridge/message-router.ts +57 -0
- package/remnote-plugin/src/bridge/websocket-client.ts +245 -0
- package/remnote-plugin/src/index.css +1 -0
- package/remnote-plugin/src/services/breadcrumb.ts +26 -0
- package/remnote-plugin/src/services/create-rem.ts +59 -0
- package/remnote-plugin/src/services/delete-rem.ts +29 -0
- package/remnote-plugin/src/services/index.ts +16 -0
- package/remnote-plugin/src/services/move-rem.ts +39 -0
- package/remnote-plugin/src/services/powerup-filter.ts +31 -0
- package/remnote-plugin/src/services/read-context.ts +368 -0
- package/remnote-plugin/src/services/read-globe.ts +197 -0
- package/remnote-plugin/src/services/read-rem.ts +284 -0
- package/remnote-plugin/src/services/read-tree.ts +222 -0
- package/remnote-plugin/src/services/rem-builder.ts +124 -0
- package/remnote-plugin/src/services/reorder-children.ts +61 -0
- package/remnote-plugin/src/services/search.ts +56 -0
- package/remnote-plugin/src/services/write-rem-fields.ts +254 -0
- package/remnote-plugin/src/settings.ts +12 -0
- package/remnote-plugin/src/style.css +45 -0
- package/remnote-plugin/src/test-scripts/AGENTS.md +46 -0
- package/remnote-plugin/src/test-scripts/test-actions.ts +230 -0
- package/remnote-plugin/src/test-scripts/test-powerup-rendering.ts +722 -0
- package/remnote-plugin/src/test-scripts/test-rem-type-mapping.ts +283 -0
- package/remnote-plugin/src/test-scripts/test-richtext-builder.ts +207 -0
- package/remnote-plugin/src/test-scripts/test-richtext-matrix.ts +332 -0
- package/remnote-plugin/src/test-scripts/test-richtext-remaining.ts +245 -0
- package/remnote-plugin/src/test-scripts/test-rw-fields.ts +399 -0
- package/remnote-plugin/src/types.ts +419 -0
- package/remnote-plugin/src/utils/elision.ts +45 -0
- package/remnote-plugin/src/utils/index.ts +10 -0
- package/remnote-plugin/src/utils/tree-serializer.ts +269 -0
- package/remnote-plugin/src/widgets/bridge_widget.tsx +170 -0
- package/remnote-plugin/src/widgets/index.tsx +82 -0
- package/remnote-plugin/tailwind.config.js +7 -0
- package/remnote-plugin/tsconfig.json +21 -0
- package/remnote-plugin/webpack.config.js +125 -0
- package/skill/SKILL.md +428 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rem 类型 → Markdown 映射探测脚本
|
|
3
|
+
*
|
|
4
|
+
* 探测目标:
|
|
5
|
+
* 1. 各种闪卡类型(Concept ::、Descriptor ;;、Multi-line >>>)对应 Rem 对象的哪些字段值
|
|
6
|
+
* 2. toMarkdown() 对各类型 text/backText 的输出
|
|
7
|
+
* 3. parseFromMarkdown() 对各种分隔符的解析行为
|
|
8
|
+
* 4. Multi-line 子行在 Rem 对象里的表示方式
|
|
9
|
+
*
|
|
10
|
+
* 运行方式:在 widgets/index.tsx 中取消注释 import 和调用,刷新 RemNote
|
|
11
|
+
* 结果写入:"mcp 测试" 文档下的 "类型映射探测" wrapper Rem
|
|
12
|
+
*/
|
|
13
|
+
import type { ReactRNPlugin, PluginRem as Rem } from '@remnote/plugin-sdk';
|
|
14
|
+
|
|
15
|
+
async function delay(ms: number) {
|
|
16
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 安全地将任意值序列化为可读字符串 */
|
|
20
|
+
function safeStringify(val: unknown): string {
|
|
21
|
+
if (val === undefined) return 'undefined';
|
|
22
|
+
if (val === null) return 'null';
|
|
23
|
+
if (typeof val === 'string') return val;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.stringify(val, null, 2);
|
|
26
|
+
} catch {
|
|
27
|
+
return String(val);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function runRemTypeMappingTest(plugin: ReactRNPlugin): Promise<void> {
|
|
32
|
+
const SESSION_KEY = 'rem-type-mapping-v1-ran';
|
|
33
|
+
const alreadyRan = await plugin.storage.getSession(SESSION_KEY);
|
|
34
|
+
if (alreadyRan) {
|
|
35
|
+
console.log('[TYPE-MAPPING] 已运行过,跳过');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
await plugin.storage.setSession(SESSION_KEY, true);
|
|
39
|
+
|
|
40
|
+
console.log('[TYPE-MAPPING] ========== 开始 Rem 类型映射探测 ==========');
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// ── 找到 "mcp 测试" 文档 ──
|
|
44
|
+
const allRem = await plugin.rem.getAll();
|
|
45
|
+
let parentDoc: Rem | undefined;
|
|
46
|
+
for (const r of allRem) {
|
|
47
|
+
const t = r.text ? await plugin.richText.toString(r.text) : '';
|
|
48
|
+
if (t.trim() === 'mcp 测试') { parentDoc = r; break; }
|
|
49
|
+
}
|
|
50
|
+
if (!parentDoc) {
|
|
51
|
+
console.error('[TYPE-MAPPING] 找不到 "mcp 测试" 文档');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── 创建 wrapper Rem ──
|
|
56
|
+
const wrapper = await plugin.rem.createRem();
|
|
57
|
+
if (!wrapper) return;
|
|
58
|
+
await wrapper.setParent(parentDoc._id, 0);
|
|
59
|
+
await wrapper.setHighlightColor('Blue');
|
|
60
|
+
await wrapper.setText(['🔬 类型映射探测 (', new Date().toISOString().slice(0, 19), ')']);
|
|
61
|
+
await delay(300);
|
|
62
|
+
|
|
63
|
+
let pos = 0;
|
|
64
|
+
const results: string[] = [];
|
|
65
|
+
|
|
66
|
+
// ═══════════════════════════════════════════════════
|
|
67
|
+
// Part 1: 用各种分隔符创建 Rem,读取字段值
|
|
68
|
+
// ═══════════════════════════════════════════════════
|
|
69
|
+
|
|
70
|
+
const sectionLabel1 = await plugin.rem.createRem();
|
|
71
|
+
if (!sectionLabel1) return;
|
|
72
|
+
await sectionLabel1.setParent(wrapper._id, pos++);
|
|
73
|
+
await sectionLabel1.setText(['═══ Part 1: 各类型 Rem 的字段值 ═══']);
|
|
74
|
+
await delay(200);
|
|
75
|
+
|
|
76
|
+
// 测试用例:各种分隔符的 Markdown 输入
|
|
77
|
+
const testCases = [
|
|
78
|
+
{ label: '普通行', markdown: '这是一个普通行' },
|
|
79
|
+
{ label: 'Concept (::)', markdown: '线性回归 :: 最基本的回归模型' },
|
|
80
|
+
{ label: 'Descriptor (;;)', markdown: '细胞核 ;; 控制细胞活动的中心' },
|
|
81
|
+
{ label: 'Forward (>>)', markdown: '什么是过拟合? >> 模型在训练数据上表现好但泛化能力差' },
|
|
82
|
+
{ label: 'Backward (<<)', markdown: '模型泛化能力差 << 什么是过拟合?' },
|
|
83
|
+
{ label: 'Bidirectional (<>)', markdown: 'DNA <> 脱氧核糖核酸' },
|
|
84
|
+
{ label: 'Multi-line (>>>)', markdown: '什么是机器学习? >>>' },
|
|
85
|
+
{ label: 'Cloze ({{}})', markdown: '光合作用将{{光能}}转化为{{化学能}}' },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
for (const tc of testCases) {
|
|
89
|
+
console.log(`[TYPE-MAPPING] 创建: ${tc.label}`);
|
|
90
|
+
|
|
91
|
+
// 用 createSingleRemWithMarkdown 创建
|
|
92
|
+
const rem = await plugin.rem.createSingleRemWithMarkdown(tc.markdown, wrapper._id);
|
|
93
|
+
if (!rem) {
|
|
94
|
+
results.push(`❌ ${tc.label}: createSingleRemWithMarkdown 返回 undefined`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
await delay(300);
|
|
98
|
+
|
|
99
|
+
// 重新获取(确保数据刷新)
|
|
100
|
+
const fresh = await plugin.rem.findOne(rem._id);
|
|
101
|
+
if (!fresh) {
|
|
102
|
+
results.push(`❌ ${tc.label}: findOne 返回 undefined`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 读取关键字段
|
|
107
|
+
const type = fresh.type;
|
|
108
|
+
const textRaw = fresh.text;
|
|
109
|
+
const backTextRaw = fresh.backText;
|
|
110
|
+
const children = fresh.children;
|
|
111
|
+
|
|
112
|
+
// toMarkdown 转换
|
|
113
|
+
const textMd = textRaw ? await plugin.richText.toMarkdown(textRaw) : '(empty)';
|
|
114
|
+
const backTextMd = backTextRaw ? await plugin.richText.toMarkdown(backTextRaw) : '(empty)';
|
|
115
|
+
|
|
116
|
+
// toString 转换(作为对照)
|
|
117
|
+
const textStr = textRaw ? await plugin.richText.toString(textRaw) : '(empty)';
|
|
118
|
+
const backTextStr = backTextRaw ? await plugin.richText.toString(backTextRaw) : '(empty)';
|
|
119
|
+
|
|
120
|
+
// 额外状态
|
|
121
|
+
const isCardItem = await fresh.isCardItem();
|
|
122
|
+
|
|
123
|
+
const result = [
|
|
124
|
+
`【${tc.label}】`,
|
|
125
|
+
` input: "${tc.markdown}"`,
|
|
126
|
+
` type: ${type} (${type === 1 ? 'CONCEPT' : type === 2 ? 'DESCRIPTOR' : type === 6 ? 'PORTAL' : 'DEFAULT'})`,
|
|
127
|
+
` text (raw): ${safeStringify(textRaw)}`,
|
|
128
|
+
` text (toMarkdown): "${textMd}"`,
|
|
129
|
+
` text (toString): "${textStr}"`,
|
|
130
|
+
` backText (raw): ${safeStringify(backTextRaw)}`,
|
|
131
|
+
` backText (toMarkdown): "${backTextMd}"`,
|
|
132
|
+
` backText (toString): "${backTextStr}"`,
|
|
133
|
+
` children: ${safeStringify(children)}`,
|
|
134
|
+
` isCardItem: ${isCardItem}`,
|
|
135
|
+
].join('\n');
|
|
136
|
+
|
|
137
|
+
results.push(result);
|
|
138
|
+
console.log(`[TYPE-MAPPING] ${result}`);
|
|
139
|
+
|
|
140
|
+
// 写入结果 Rem
|
|
141
|
+
const resultRem = await plugin.rem.createRem();
|
|
142
|
+
if (resultRem) {
|
|
143
|
+
await resultRem.setParent(wrapper._id, pos++);
|
|
144
|
+
await resultRem.setText([`${tc.label}: type=${type}, text="${textStr}", back="${backTextStr}"`]);
|
|
145
|
+
await delay(100);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ═══════════════════════════════════════════════════
|
|
150
|
+
// Part 2: Multi-line 子行探测
|
|
151
|
+
// ═══════════════════════════════════════════════════
|
|
152
|
+
|
|
153
|
+
const sectionLabel2 = await plugin.rem.createRem();
|
|
154
|
+
if (!sectionLabel2) return;
|
|
155
|
+
await sectionLabel2.setParent(wrapper._id, pos++);
|
|
156
|
+
await sectionLabel2.setText(['═══ Part 2: Multi-line 子行结构探测 ═══']);
|
|
157
|
+
await delay(200);
|
|
158
|
+
|
|
159
|
+
// 创建一个 Multi-line Rem 并手动添加子行
|
|
160
|
+
const mlParent = await plugin.rem.createSingleRemWithMarkdown('多行测试问题 >>>', wrapper._id);
|
|
161
|
+
if (mlParent) {
|
|
162
|
+
await delay(300);
|
|
163
|
+
|
|
164
|
+
// 添加子行
|
|
165
|
+
const child1 = await plugin.rem.createRem();
|
|
166
|
+
if (child1) {
|
|
167
|
+
await child1.setText(['这是第一个答案行']);
|
|
168
|
+
await child1.setParent(mlParent._id, 0);
|
|
169
|
+
await delay(200);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const child2 = await plugin.rem.createRem();
|
|
173
|
+
if (child2) {
|
|
174
|
+
await child2.setText(['这是第二个答案行']);
|
|
175
|
+
await child2.setParent(mlParent._id, 1);
|
|
176
|
+
await delay(200);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 重新读取 Multi-line 父 Rem
|
|
180
|
+
const mlFresh = await plugin.rem.findOne(mlParent._id);
|
|
181
|
+
if (mlFresh) {
|
|
182
|
+
const mlType = mlFresh.type;
|
|
183
|
+
const mlText = mlFresh.text ? await plugin.richText.toString(mlFresh.text) : '(empty)';
|
|
184
|
+
const mlBack = mlFresh.backText ? await plugin.richText.toString(mlFresh.backText) : '(empty)';
|
|
185
|
+
const mlChildren = mlFresh.children;
|
|
186
|
+
|
|
187
|
+
// 读取子行的 isCardItem 状态
|
|
188
|
+
const childResults: string[] = [];
|
|
189
|
+
if (mlChildren) {
|
|
190
|
+
for (let i = 0; i < mlChildren.length; i++) {
|
|
191
|
+
const childRem = await plugin.rem.findOne(mlChildren[i]);
|
|
192
|
+
if (childRem) {
|
|
193
|
+
const cType = childRem.type;
|
|
194
|
+
const cText = childRem.text ? await plugin.richText.toString(childRem.text) : '(empty)';
|
|
195
|
+
const cIsCardItem = await childRem.isCardItem();
|
|
196
|
+
childResults.push(` child[${i}]: type=${cType}, text="${cText}", isCardItem=${cIsCardItem}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const mlResult = [
|
|
202
|
+
'【Multi-line 子行结构】',
|
|
203
|
+
` parent type: ${mlType}`,
|
|
204
|
+
` parent text: "${mlText}"`,
|
|
205
|
+
` parent backText: "${mlBack}"`,
|
|
206
|
+
` parent children IDs: ${safeStringify(mlChildren)}`,
|
|
207
|
+
...childResults,
|
|
208
|
+
].join('\n');
|
|
209
|
+
|
|
210
|
+
results.push(mlResult);
|
|
211
|
+
console.log(`[TYPE-MAPPING] ${mlResult}`);
|
|
212
|
+
|
|
213
|
+
const mlResultRem = await plugin.rem.createRem();
|
|
214
|
+
if (mlResultRem) {
|
|
215
|
+
await mlResultRem.setParent(wrapper._id, pos++);
|
|
216
|
+
await mlResultRem.setText([`Multi-line: type=${mlType}, children=${mlChildren?.length ?? 0}`]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ═══════════════════════════════════════════════════
|
|
222
|
+
// Part 3: parseFromMarkdown 解析行为
|
|
223
|
+
// ═══════════════════════════════════════════════════
|
|
224
|
+
|
|
225
|
+
const sectionLabel3 = await plugin.rem.createRem();
|
|
226
|
+
if (!sectionLabel3) return;
|
|
227
|
+
await sectionLabel3.setParent(wrapper._id, pos++);
|
|
228
|
+
await sectionLabel3.setText(['═══ Part 3: parseFromMarkdown 解析行为 ═══']);
|
|
229
|
+
await delay(200);
|
|
230
|
+
|
|
231
|
+
const parseTests = [
|
|
232
|
+
'普通文本',
|
|
233
|
+
'概念 :: 定义',
|
|
234
|
+
'描述 ;; 解释',
|
|
235
|
+
'问题 >> 答案',
|
|
236
|
+
'答案 << 问题',
|
|
237
|
+
'左 <> 右',
|
|
238
|
+
'多行问题 >>>',
|
|
239
|
+
'**粗体** 和 *斜体*',
|
|
240
|
+
'带 [[引用]] 的文本',
|
|
241
|
+
'带 {{cloze}} 的文本',
|
|
242
|
+
'$E = mc^2$',
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
for (const input of parseTests) {
|
|
246
|
+
const parsed = await plugin.richText.parseFromMarkdown(input);
|
|
247
|
+
const parsedStr = safeStringify(parsed);
|
|
248
|
+
|
|
249
|
+
const parseResult = `parseFromMarkdown("${input}") → ${parsedStr}`;
|
|
250
|
+
results.push(parseResult);
|
|
251
|
+
console.log(`[TYPE-MAPPING] ${parseResult}`);
|
|
252
|
+
|
|
253
|
+
const parseRem = await plugin.rem.createRem();
|
|
254
|
+
if (parseRem) {
|
|
255
|
+
await parseRem.setParent(wrapper._id, pos++);
|
|
256
|
+
// 截断过长的结果
|
|
257
|
+
const shortResult = parsedStr.length > 100 ? parsedStr.slice(0, 100) + '...' : parsedStr;
|
|
258
|
+
await parseRem.setText([`parse("${input}") → ${shortResult}`]);
|
|
259
|
+
await delay(100);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ═══════════════════════════════════════════════════
|
|
264
|
+
// Part 4: 汇总 JSON 结果
|
|
265
|
+
// ═══════════════════════════════════════════════════
|
|
266
|
+
|
|
267
|
+
const jsonSummary = await plugin.rem.createRem();
|
|
268
|
+
if (jsonSummary) {
|
|
269
|
+
await jsonSummary.setParent(wrapper._id, pos++);
|
|
270
|
+
await jsonSummary.setIsCode(true);
|
|
271
|
+
const fullJson = JSON.stringify({ timestamp: new Date().toISOString(), results }, null, 2);
|
|
272
|
+
await jsonSummary.setText([fullJson]);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── 完成标记 ──
|
|
276
|
+
await wrapper.setText(['✅ 类型映射探测完成 (', new Date().toISOString().slice(0, 19), ')']);
|
|
277
|
+
console.log('[TYPE-MAPPING] ========== 探测完成 ==========');
|
|
278
|
+
console.log('[TYPE-MAPPING] 完整结果:\n' + results.join('\n\n'));
|
|
279
|
+
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.error('[TYPE-MAPPING] 错误:', err);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
<<<<<<< ours
|
|
3
|
+
* RichText Builder 探测脚本(恢复占位)
|
|
4
|
+
*
|
|
5
|
+
* 说明:该文件在本地历史中被误删后无法从 git 精确回滚。
|
|
6
|
+
* 当前先恢复脚本入口,避免 import 失效;具体探测逻辑可按需补回。
|
|
7
|
+
*/
|
|
8
|
+
import type { ReactRNPlugin } from '@remnote/plugin-sdk';
|
|
9
|
+
|
|
10
|
+
export async function runRichTextBuilderTest(plugin: ReactRNPlugin): Promise<void> {
|
|
11
|
+
const alreadyRan = await plugin.storage.getSession('richtext-builder-test-v1-ran');
|
|
12
|
+
if (alreadyRan) {
|
|
13
|
+
console.log('[RICHTEXT-BUILDER-TEST] 已运行过,跳过');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await plugin.storage.setSession('richtext-builder-test-v1-ran', true);
|
|
17
|
+
console.warn('[RICHTEXT-BUILDER-TEST] 脚本主体待补回(文件已恢复占位)');
|
|
18
|
+
=======
|
|
19
|
+
* RichText Builder API 输出探测测试
|
|
20
|
+
*
|
|
21
|
+
* 目的:探测 plugin.richText.video() 和 plugin.richText.audio() Builder API
|
|
22
|
+
* 实际产出的 JSON 结构,看看跟手拼的 { i: "a" } 有什么区别。
|
|
23
|
+
*
|
|
24
|
+
* 背景:手拼 { i: "a", url: "..." } 通过 setText() 写入时,SDK 一律拒绝(Invalid input)。
|
|
25
|
+
* 但 SDK 提供了 Builder API,需要探测 Builder 产出的结构能否被 setText() 接受。
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactRNPlugin, PluginRem as Rem } from '@remnote/plugin-sdk';
|
|
28
|
+
|
|
29
|
+
async function delay(ms: number) {
|
|
30
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const LOG_TAG = '[RICHTEXT-BUILDER-TEST]';
|
|
34
|
+
|
|
35
|
+
export async function runRichTextBuilderTest(plugin: ReactRNPlugin): Promise<void> {
|
|
36
|
+
// 防重复运行
|
|
37
|
+
const alreadyRan = await plugin.storage.getSession('richtext-builder-test-v1-ran');
|
|
38
|
+
if (alreadyRan) {
|
|
39
|
+
console.log(`${LOG_TAG} 已运行过,跳过`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
await plugin.storage.setSession('richtext-builder-test-v1-ran', true);
|
|
43
|
+
|
|
44
|
+
console.log(`${LOG_TAG} ========== 开始 RichText Builder 输出探测 ==========`);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// ── 找到 "mcp 测试" 文档 ──
|
|
48
|
+
const allRem = await plugin.rem.getAll();
|
|
49
|
+
let parentDoc: Rem | undefined;
|
|
50
|
+
for (const r of allRem) {
|
|
51
|
+
const t = r.text ? await plugin.richText.toString(r.text) : '';
|
|
52
|
+
if (t.trim() === 'mcp 测试') { parentDoc = r; break; }
|
|
53
|
+
}
|
|
54
|
+
if (!parentDoc) {
|
|
55
|
+
console.error(`${LOG_TAG} 找不到 "mcp 测试" 文档`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let pos = 0;
|
|
60
|
+
|
|
61
|
+
// ── 标题 Rem ──
|
|
62
|
+
const titleRem = await plugin.rem.createRem();
|
|
63
|
+
if (!titleRem) return;
|
|
64
|
+
await titleRem.setParent(parentDoc._id, pos++);
|
|
65
|
+
await titleRem.setHighlightColor('Yellow');
|
|
66
|
+
await titleRem.setText(['===== RichText Builder 输出探测 =====']);
|
|
67
|
+
await delay(300);
|
|
68
|
+
|
|
69
|
+
// ═══════════════════════════════════════════════════
|
|
70
|
+
// 1. 探测 plugin.richText.video() 产出
|
|
71
|
+
// ═══════════════════════════════════════════════════
|
|
72
|
+
console.log(`${LOG_TAG} --- 1. 探测 video() Builder ---`);
|
|
73
|
+
|
|
74
|
+
const videoRichText = plugin.richText.video('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
|
75
|
+
const videoValue = await videoRichText.value();
|
|
76
|
+
|
|
77
|
+
console.log(`${LOG_TAG} video() .value() 类型:`, typeof videoValue);
|
|
78
|
+
console.log(`${LOG_TAG} video() .value() 是数组:`, Array.isArray(videoValue));
|
|
79
|
+
console.log(`${LOG_TAG} video() .value() JSON:`, JSON.stringify(videoValue, null, 2));
|
|
80
|
+
console.log(`${LOG_TAG} video() .value() 紧凑 JSON:`, JSON.stringify(videoValue));
|
|
81
|
+
|
|
82
|
+
// 把 video Builder 结果写入一个 Rem
|
|
83
|
+
const videoResultRem = await plugin.rem.createRem();
|
|
84
|
+
if (videoResultRem) {
|
|
85
|
+
await videoResultRem.setParent(parentDoc._id, pos++);
|
|
86
|
+
await videoResultRem.setText([`video() Builder 产出 JSON: ${JSON.stringify(videoValue)}`]);
|
|
87
|
+
await delay(300);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ═══════════════════════════════════════════════════
|
|
91
|
+
// 2. 探测 plugin.richText.audio() 产出
|
|
92
|
+
// ═══════════════════════════════════════════════════
|
|
93
|
+
console.log(`${LOG_TAG} --- 2. 探测 audio() Builder ---`);
|
|
94
|
+
|
|
95
|
+
const audioRichText = plugin.richText.audio('https://example.com/test.mp3');
|
|
96
|
+
const audioValue = await audioRichText.value();
|
|
97
|
+
|
|
98
|
+
console.log(`${LOG_TAG} audio() .value() 类型:`, typeof audioValue);
|
|
99
|
+
console.log(`${LOG_TAG} audio() .value() 是数组:`, Array.isArray(audioValue));
|
|
100
|
+
console.log(`${LOG_TAG} audio() .value() JSON:`, JSON.stringify(audioValue, null, 2));
|
|
101
|
+
console.log(`${LOG_TAG} audio() .value() 紧凑 JSON:`, JSON.stringify(audioValue));
|
|
102
|
+
|
|
103
|
+
// 把 audio Builder 结果写入一个 Rem
|
|
104
|
+
const audioResultRem = await plugin.rem.createRem();
|
|
105
|
+
if (audioResultRem) {
|
|
106
|
+
await audioResultRem.setParent(parentDoc._id, pos++);
|
|
107
|
+
await audioResultRem.setText([`audio() Builder 产出 JSON: ${JSON.stringify(audioValue)}`]);
|
|
108
|
+
await delay(300);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ═══════════════════════════════════════════════════
|
|
112
|
+
// 3. 探测 Builder 对象的其他属性
|
|
113
|
+
// ═══════════════════════════════════════════════════
|
|
114
|
+
console.log(`${LOG_TAG} --- 3. 探测 Builder 对象属性 ---`);
|
|
115
|
+
|
|
116
|
+
// 探测 video RichText 对象本身
|
|
117
|
+
console.log(`${LOG_TAG} video RichText 对象 keys:`, Object.keys(videoRichText));
|
|
118
|
+
console.log(`${LOG_TAG} video RichText 对象 prototype:`, Object.getOwnPropertyNames(Object.getPrototypeOf(videoRichText)));
|
|
119
|
+
|
|
120
|
+
// 探测 audio RichText 对象本身
|
|
121
|
+
console.log(`${LOG_TAG} audio RichText 对象 keys:`, Object.keys(audioRichText));
|
|
122
|
+
|
|
123
|
+
// ═══════════════════════════════════════════════════
|
|
124
|
+
// 4. 对比测试:用 Builder 产出的结构通过 setText() 回写
|
|
125
|
+
// ═══════════════════════════════════════════════════
|
|
126
|
+
console.log(`${LOG_TAG} --- 4. 对比测试:Builder 产出 → setText() 回写 ---`);
|
|
127
|
+
|
|
128
|
+
// 4a. 尝试用 video Builder 的 .value() 直接 setText()
|
|
129
|
+
const videoWriteRem = await plugin.rem.createRem();
|
|
130
|
+
if (videoWriteRem) {
|
|
131
|
+
await videoWriteRem.setParent(parentDoc._id, pos++);
|
|
132
|
+
try {
|
|
133
|
+
await videoWriteRem.setText(videoValue);
|
|
134
|
+
console.log(`${LOG_TAG} 4a. video .value() → setText() 成功!`);
|
|
135
|
+
// 读回验证
|
|
136
|
+
const readBack = videoWriteRem.text;
|
|
137
|
+
console.log(`${LOG_TAG} 4a. 回读 text:`, JSON.stringify(readBack));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.error(`${LOG_TAG} 4a. video .value() → setText() 失败:`, err);
|
|
140
|
+
await videoWriteRem.setText([`video setText 失败: ${String(err)}`]);
|
|
141
|
+
}
|
|
142
|
+
await delay(300);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 4b. 尝试用 audio Builder 的 .value() 直接 setText()
|
|
146
|
+
const audioWriteRem = await plugin.rem.createRem();
|
|
147
|
+
if (audioWriteRem) {
|
|
148
|
+
await audioWriteRem.setParent(parentDoc._id, pos++);
|
|
149
|
+
try {
|
|
150
|
+
await audioWriteRem.setText(audioValue);
|
|
151
|
+
console.log(`${LOG_TAG} 4b. audio .value() → setText() 成功!`);
|
|
152
|
+
// 读回验证
|
|
153
|
+
const readBack = audioWriteRem.text;
|
|
154
|
+
console.log(`${LOG_TAG} 4b. 回读 text:`, JSON.stringify(readBack));
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error(`${LOG_TAG} 4b. audio .value() → setText() 失败:`, err);
|
|
157
|
+
await audioWriteRem.setText([`audio setText 失败: ${String(err)}`]);
|
|
158
|
+
}
|
|
159
|
+
await delay(300);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 4c. 尝试用 Builder 的 .value() 中的单个元素手拼 setText()
|
|
163
|
+
// (如果 .value() 是数组,取出每个元素尝试)
|
|
164
|
+
if (Array.isArray(videoValue) && videoValue.length > 0) {
|
|
165
|
+
const singleElement = videoValue[0];
|
|
166
|
+
console.log(`${LOG_TAG} 4c. video 单个元素:`, JSON.stringify(singleElement));
|
|
167
|
+
|
|
168
|
+
const singleWriteRem = await plugin.rem.createRem();
|
|
169
|
+
if (singleWriteRem) {
|
|
170
|
+
await singleWriteRem.setParent(parentDoc._id, pos++);
|
|
171
|
+
try {
|
|
172
|
+
// 用完全相同的结构手拼
|
|
173
|
+
const handCrafted = JSON.parse(JSON.stringify(singleElement));
|
|
174
|
+
await singleWriteRem.setText([handCrafted]);
|
|
175
|
+
console.log(`${LOG_TAG} 4c. 手拼单元素 → setText() 成功!`);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error(`${LOG_TAG} 4c. 手拼单元素 → setText() 失败:`, err);
|
|
178
|
+
await singleWriteRem.setText([`手拼单元素 setText 失败: ${String(err)}`]);
|
|
179
|
+
}
|
|
180
|
+
await delay(300);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ═══════════════════════════════════════════════════
|
|
185
|
+
// 5. 探测其他可能有用的 Builder 方法
|
|
186
|
+
// ═══════════════════════════════════════════════════
|
|
187
|
+
console.log(`${LOG_TAG} --- 5. 列举 plugin.richText 上的所有方法 ---`);
|
|
188
|
+
|
|
189
|
+
const rtKeys = Object.getOwnPropertyNames(Object.getPrototypeOf(plugin.richText));
|
|
190
|
+
console.log(`${LOG_TAG} plugin.richText 方法列表:`, rtKeys);
|
|
191
|
+
|
|
192
|
+
// 完成标记
|
|
193
|
+
const doneRem = await plugin.rem.createRem();
|
|
194
|
+
if (doneRem) {
|
|
195
|
+
await doneRem.setParent(parentDoc._id, pos++);
|
|
196
|
+
await doneRem.setHighlightColor('Green');
|
|
197
|
+
await doneRem.setText(['===== RichText Builder 测试完成 =====']);
|
|
198
|
+
await delay(200);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log(`${LOG_TAG} ========== 测试完成,请检查控制台输出 ==========`);
|
|
202
|
+
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error(`${LOG_TAG} 顶层错误:`, err);
|
|
205
|
+
}
|
|
206
|
+
>>>>>>> theirs
|
|
207
|
+
}
|