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,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RichText 字段×元素类型 交叉兼容性矩阵测试
|
|
3
|
+
*
|
|
4
|
+
* 测试 7 种 i 类型 × 12 种格式化字段 = 84 个组合
|
|
5
|
+
* 记录每个组合:✅ 写入+保留 / ❌ SDK 拒绝 / ⚠️ 写入但字段丢失
|
|
6
|
+
*
|
|
7
|
+
* 跳过:i:"m"(已全覆盖)、i:"fi"(SDK 拒绝写入)
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactRNPlugin, PluginRem as Rem } from '@remnote/plugin-sdk';
|
|
10
|
+
|
|
11
|
+
async function delay(ms: number) {
|
|
12
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const LOG = '[RICHTEXT-MATRIX]';
|
|
16
|
+
const SESSION_KEY = 'richtext-matrix-v1-ran';
|
|
17
|
+
|
|
18
|
+
// ── 格式化字段定义 ──
|
|
19
|
+
|
|
20
|
+
interface FieldDef {
|
|
21
|
+
key: string;
|
|
22
|
+
value: any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const FORMAT_FIELDS: FieldDef[] = [
|
|
26
|
+
{ key: 'b', value: true },
|
|
27
|
+
{ key: 'l', value: true },
|
|
28
|
+
{ key: 'u', value: true },
|
|
29
|
+
{ key: 'h', value: 4 }, // Green highlight
|
|
30
|
+
{ key: 'tc', value: 6 }, // Blue text color
|
|
31
|
+
{ key: 'q', value: true }, // inline code
|
|
32
|
+
{ key: 'code', value: true },
|
|
33
|
+
{ key: 'cId', value: 'test-cloze-matrix' },
|
|
34
|
+
{ key: 'qId', value: '__PLACEHOLDER__' }, // will be replaced with real Rem ID
|
|
35
|
+
{ key: 'iUrl', value: 'https://example.com/matrix-test' },
|
|
36
|
+
{ key: 'block', value: true },
|
|
37
|
+
{ key: 'title', value: '矩阵测试标题' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// ── 元素类型定义 ──
|
|
41
|
+
|
|
42
|
+
interface ElementTypeDef {
|
|
43
|
+
label: string;
|
|
44
|
+
iValue: string;
|
|
45
|
+
buildBase: (realRemId: string) => Record<string, any>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ELEMENT_TYPES: ElementTypeDef[] = [
|
|
49
|
+
{
|
|
50
|
+
label: 'q (Rem引用)',
|
|
51
|
+
iValue: 'q',
|
|
52
|
+
buildBase: (realRemId) => ({
|
|
53
|
+
i: 'q', _id: realRemId, content: false, pin: false,
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: 'i (图片)',
|
|
58
|
+
iValue: 'i',
|
|
59
|
+
buildBase: () => ({
|
|
60
|
+
i: 'i', url: 'https://via.placeholder.com/1x1',
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
label: 'a (音频)',
|
|
65
|
+
iValue: 'a',
|
|
66
|
+
buildBase: () => ({
|
|
67
|
+
i: 'a', url: 'https://example.com/test.mp3', onlyAudio: true,
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
label: 'x (LaTeX)',
|
|
72
|
+
iValue: 'x',
|
|
73
|
+
buildBase: () => ({
|
|
74
|
+
i: 'x', text: 'E=mc^2',
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
label: 's (分隔符)',
|
|
79
|
+
iValue: 's',
|
|
80
|
+
buildBase: () => ({
|
|
81
|
+
i: 's',
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'n (注释)',
|
|
86
|
+
iValue: 'n',
|
|
87
|
+
buildBase: () => ({
|
|
88
|
+
i: 'n', text: 'annotation', url: 'https://example.com',
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
label: 'g (GlobalName)',
|
|
93
|
+
iValue: 'g',
|
|
94
|
+
buildBase: (realRemId) => ({
|
|
95
|
+
i: 'g', _id: realRemId,
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
// ── 结果类型 ──
|
|
101
|
+
type CellResult = 'OK' | 'ERROR' | 'LOST';
|
|
102
|
+
|
|
103
|
+
interface MatrixResult {
|
|
104
|
+
elementType: string;
|
|
105
|
+
iValue: string;
|
|
106
|
+
field: string;
|
|
107
|
+
fieldValue: any;
|
|
108
|
+
result: CellResult;
|
|
109
|
+
detail: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function runRichTextMatrixTest(plugin: ReactRNPlugin): Promise<void> {
|
|
113
|
+
const alreadyRan = await plugin.storage.getSession(SESSION_KEY);
|
|
114
|
+
if (alreadyRan) {
|
|
115
|
+
console.log(`${LOG} 已运行过,跳过`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await plugin.storage.setSession(SESSION_KEY, true);
|
|
119
|
+
|
|
120
|
+
console.log(`${LOG} ========== 开始交叉兼容性矩阵测试 ==========`);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// ── 找到 "mcp 测试" 文档 ──
|
|
124
|
+
const allRem = await plugin.rem.getAll();
|
|
125
|
+
let parentDoc: Rem | undefined;
|
|
126
|
+
for (const r of allRem) {
|
|
127
|
+
const t = r.text ? await plugin.richText.toString(r.text) : '';
|
|
128
|
+
if (t.trim() === 'mcp 测试') { parentDoc = r; break; }
|
|
129
|
+
}
|
|
130
|
+
if (!parentDoc) {
|
|
131
|
+
console.error(`${LOG} 找不到 "mcp 测试" 文档`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── 创建包装 Rem 隔离测试区域 ──
|
|
136
|
+
const wrapper = await plugin.rem.createRem();
|
|
137
|
+
if (!wrapper) return;
|
|
138
|
+
await wrapper.setParent(parentDoc._id, 0);
|
|
139
|
+
await wrapper.setHighlightColor('Purple');
|
|
140
|
+
await wrapper.setText(['===== RichText 交叉兼容性矩阵测试 =====']);
|
|
141
|
+
await delay(300);
|
|
142
|
+
|
|
143
|
+
const wId = wrapper._id;
|
|
144
|
+
let pos = 0;
|
|
145
|
+
|
|
146
|
+
// ── 创建辅助 Rem(用于需要真实 Rem ID 的元素类型)──
|
|
147
|
+
const helperRem = await plugin.rem.createRem();
|
|
148
|
+
if (!helperRem) return;
|
|
149
|
+
await helperRem.setParent(wId, pos++);
|
|
150
|
+
await helperRem.setText(['辅助 Rem(用于 q/_id 和 g/_id 引用)']);
|
|
151
|
+
await delay(200);
|
|
152
|
+
const realRemId = helperRem._id;
|
|
153
|
+
|
|
154
|
+
// 替换 qId 字段的占位符
|
|
155
|
+
const fields = FORMAT_FIELDS.map((f) =>
|
|
156
|
+
f.key === 'qId' ? { ...f, value: realRemId } : f,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// ── 收集所有结果 ──
|
|
160
|
+
const allResults: MatrixResult[] = [];
|
|
161
|
+
|
|
162
|
+
// ── 外层循环:元素类型 ──
|
|
163
|
+
for (const elemType of ELEMENT_TYPES) {
|
|
164
|
+
console.log(`${LOG} --- 测试元素类型: ${elemType.label} ---`);
|
|
165
|
+
|
|
166
|
+
const typeHeader = await plugin.rem.createRem();
|
|
167
|
+
if (typeHeader) {
|
|
168
|
+
await typeHeader.setParent(wId, pos++);
|
|
169
|
+
await typeHeader.setHighlightColor('Blue');
|
|
170
|
+
await typeHeader.setText([`▶ ${elemType.label}`]);
|
|
171
|
+
await delay(100);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── 内层循环:格式化字段 ──
|
|
175
|
+
for (const field of fields) {
|
|
176
|
+
const combo = `${elemType.iValue}+${field.key}`;
|
|
177
|
+
console.log(`${LOG} 测试: ${combo}`);
|
|
178
|
+
|
|
179
|
+
const base = elemType.buildBase(realRemId);
|
|
180
|
+
const testObj = { ...base, [field.key]: field.value };
|
|
181
|
+
|
|
182
|
+
let result: CellResult;
|
|
183
|
+
let detail: string;
|
|
184
|
+
|
|
185
|
+
// 创建测试 Rem
|
|
186
|
+
const testRem = await plugin.rem.createRem();
|
|
187
|
+
if (!testRem) {
|
|
188
|
+
result = 'ERROR';
|
|
189
|
+
detail = '无法创建 Rem';
|
|
190
|
+
allResults.push({
|
|
191
|
+
elementType: elemType.label,
|
|
192
|
+
iValue: elemType.iValue,
|
|
193
|
+
field: field.key,
|
|
194
|
+
fieldValue: field.value,
|
|
195
|
+
result,
|
|
196
|
+
detail,
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
await testRem.setParent(wId, pos++);
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
// 写入
|
|
204
|
+
await testRem.setText([testObj] as any);
|
|
205
|
+
await delay(200);
|
|
206
|
+
|
|
207
|
+
// 回读
|
|
208
|
+
const refetch = await plugin.rem.findOne(testRem._id);
|
|
209
|
+
const readBack = refetch ? refetch.text : testRem.text;
|
|
210
|
+
|
|
211
|
+
// 检查字段是否保留
|
|
212
|
+
let fieldRetained = false;
|
|
213
|
+
if (Array.isArray(readBack)) {
|
|
214
|
+
for (const el of readBack) {
|
|
215
|
+
if (el && typeof el === 'object' && field.key in el) {
|
|
216
|
+
// 对于 boolean 字段,检查值是否为 true
|
|
217
|
+
const val = (el as any)[field.key];
|
|
218
|
+
if (typeof field.value === 'boolean') {
|
|
219
|
+
fieldRetained = val === field.value;
|
|
220
|
+
} else {
|
|
221
|
+
fieldRetained = val !== undefined && val !== null;
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (fieldRetained) {
|
|
229
|
+
result = 'OK';
|
|
230
|
+
detail = `写入+保留 → ${JSON.stringify(readBack)}`;
|
|
231
|
+
} else {
|
|
232
|
+
result = 'LOST';
|
|
233
|
+
detail = `写入成功但字段丢失 → ${JSON.stringify(readBack)}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(`${LOG} ${combo}: ${result} — ${detail}`);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
result = 'ERROR';
|
|
239
|
+
detail = `SDK 拒绝: ${String(err)}`;
|
|
240
|
+
console.error(`${LOG} ${combo}: ERROR —`, err);
|
|
241
|
+
|
|
242
|
+
// 写入错误提示到 Rem(避免空 Rem)
|
|
243
|
+
try {
|
|
244
|
+
await testRem.setText([`${combo} 失败: ${String(err).slice(0, 100)}`]);
|
|
245
|
+
} catch {
|
|
246
|
+
// ignore
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
await delay(100);
|
|
250
|
+
|
|
251
|
+
// 写结果到子 Rem
|
|
252
|
+
const resultRem = await plugin.rem.createRem();
|
|
253
|
+
if (resultRem) {
|
|
254
|
+
await resultRem.setParent(wId, pos++);
|
|
255
|
+
const icon = result === 'OK' ? '✅' : result === 'ERROR' ? '❌' : '⚠️';
|
|
256
|
+
await resultRem.setText([`${icon} ${combo}: ${result} — ${detail.slice(0, 200)}`]);
|
|
257
|
+
await delay(50);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
allResults.push({
|
|
261
|
+
elementType: elemType.label,
|
|
262
|
+
iValue: elemType.iValue,
|
|
263
|
+
field: field.key,
|
|
264
|
+
fieldValue: field.value,
|
|
265
|
+
result,
|
|
266
|
+
detail,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── 汇总结果 ──
|
|
272
|
+
|
|
273
|
+
// 构建矩阵表
|
|
274
|
+
const matrix: Record<string, Record<string, CellResult>> = {};
|
|
275
|
+
for (const r of allResults) {
|
|
276
|
+
if (!matrix[r.iValue]) matrix[r.iValue] = {};
|
|
277
|
+
matrix[r.iValue][r.field] = r.result;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 统计
|
|
281
|
+
const stats = {
|
|
282
|
+
total: allResults.length,
|
|
283
|
+
ok: allResults.filter((r) => r.result === 'OK').length,
|
|
284
|
+
error: allResults.filter((r) => r.result === 'ERROR').length,
|
|
285
|
+
lost: allResults.filter((r) => r.result === 'LOST').length,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
console.log(`${LOG} ========== 汇总 ==========`);
|
|
289
|
+
console.log(`${LOG} 总计: ${stats.total}, OK: ${stats.ok}, ERROR: ${stats.error}, LOST: ${stats.lost}`);
|
|
290
|
+
console.log(`${LOG} 矩阵:`, JSON.stringify(matrix, null, 2));
|
|
291
|
+
|
|
292
|
+
// 写入汇总 JSON Rem
|
|
293
|
+
const summaryRem = await plugin.rem.createRem();
|
|
294
|
+
if (summaryRem) {
|
|
295
|
+
await summaryRem.setParent(wId, pos++);
|
|
296
|
+
await summaryRem.setHighlightColor('Green');
|
|
297
|
+
|
|
298
|
+
const summaryJson = JSON.stringify({
|
|
299
|
+
testName: 'richtext-field-element-matrix',
|
|
300
|
+
timestamp: new Date().toISOString(),
|
|
301
|
+
stats,
|
|
302
|
+
matrix,
|
|
303
|
+
fieldList: fields.map((f) => f.key),
|
|
304
|
+
elementList: ELEMENT_TYPES.map((e) => e.iValue),
|
|
305
|
+
details: allResults.map((r) => ({
|
|
306
|
+
elem: r.iValue,
|
|
307
|
+
field: r.field,
|
|
308
|
+
result: r.result,
|
|
309
|
+
detail: r.detail.slice(0, 300),
|
|
310
|
+
})),
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
await summaryRem.setText([`矩阵汇总 JSON: ${summaryJson}`]);
|
|
314
|
+
await delay(200);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 完成标记
|
|
318
|
+
const doneRem = await plugin.rem.createRem();
|
|
319
|
+
if (doneRem) {
|
|
320
|
+
await doneRem.setParent(wId, pos++);
|
|
321
|
+
await doneRem.setHighlightColor('Green');
|
|
322
|
+
await doneRem.setText([
|
|
323
|
+
`===== 矩阵测试完成 ===== ` +
|
|
324
|
+
`✅${stats.ok} ❌${stats.error} ⚠️${stats.lost} / 共${stats.total}`,
|
|
325
|
+
]);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
console.log(`${LOG} ========== 矩阵测试全部完成 ==========`);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
console.error(`${LOG} 顶层错误:`, err);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RichText 剩余未测字段验证 v2
|
|
3
|
+
*
|
|
4
|
+
* 改进:
|
|
5
|
+
* - 用 applyTextFormatToRange + "cloze" 创建真正的 cloze,再回读真实 cId 格式
|
|
6
|
+
* - 每个测试的回读 JSON 写入结果 Rem(不依赖 console.log)
|
|
7
|
+
* - 用包装 Rem 隔离,避免跟其他测试交错
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactRNPlugin, PluginRem as Rem } from '@remnote/plugin-sdk';
|
|
10
|
+
|
|
11
|
+
async function delay(ms: number) {
|
|
12
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const LOG = '[RICHTEXT-REMAINING-V2]';
|
|
16
|
+
|
|
17
|
+
export async function runRichTextRemainingTest(plugin: ReactRNPlugin): Promise<void> {
|
|
18
|
+
const alreadyRan = await plugin.storage.getSession('richtext-remaining-v2-ran');
|
|
19
|
+
if (alreadyRan) {
|
|
20
|
+
console.log(`${LOG} 已运行过,跳过`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await plugin.storage.setSession('richtext-remaining-v2-ran', true);
|
|
24
|
+
|
|
25
|
+
console.log(`${LOG} ========== 开始 v2 验证 ==========`);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// ── 找到 "mcp 测试" 文档 ──
|
|
29
|
+
const allRem = await plugin.rem.getAll();
|
|
30
|
+
let parentDoc: Rem | undefined;
|
|
31
|
+
for (const r of allRem) {
|
|
32
|
+
const t = r.text ? await plugin.richText.toString(r.text) : '';
|
|
33
|
+
if (t.trim() === 'mcp 测试') { parentDoc = r; break; }
|
|
34
|
+
}
|
|
35
|
+
if (!parentDoc) {
|
|
36
|
+
console.error(`${LOG} 找不到 "mcp 测试" 文档`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── 创建包装 Rem 隔离测试区域 ──
|
|
41
|
+
const wrapper = await plugin.rem.createRem();
|
|
42
|
+
if (!wrapper) return;
|
|
43
|
+
await wrapper.setParent(parentDoc._id, 0);
|
|
44
|
+
await wrapper.setHighlightColor('Yellow');
|
|
45
|
+
await wrapper.setText(['===== RichText 剩余字段验证 v2 =====']);
|
|
46
|
+
await delay(300);
|
|
47
|
+
|
|
48
|
+
const wId = wrapper._id;
|
|
49
|
+
let pos = 0;
|
|
50
|
+
|
|
51
|
+
// 辅助:创建 Rem + 写入 + 回读 + 写结果
|
|
52
|
+
async function testWrite(
|
|
53
|
+
label: string,
|
|
54
|
+
richText: any[],
|
|
55
|
+
): Promise<{ ok: boolean; readBack: any }> {
|
|
56
|
+
const testRem = await plugin.rem.createRem();
|
|
57
|
+
if (!testRem) return { ok: false, readBack: null };
|
|
58
|
+
await testRem.setParent(wId, pos++);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await testRem.setText(richText);
|
|
62
|
+
await delay(200);
|
|
63
|
+
// 回读
|
|
64
|
+
const refetch = await plugin.rem.findOne(testRem._id);
|
|
65
|
+
const readBack = refetch ? refetch.text : testRem.text;
|
|
66
|
+
console.log(`${LOG} ${label} → OK, 回读:`, JSON.stringify(readBack));
|
|
67
|
+
|
|
68
|
+
// 结果 Rem
|
|
69
|
+
const resultRem = await plugin.rem.createRem();
|
|
70
|
+
if (resultRem) {
|
|
71
|
+
await resultRem.setParent(wId, pos++);
|
|
72
|
+
await resultRem.setText([`↳ ${label} 回读: ${JSON.stringify(readBack)}`]);
|
|
73
|
+
}
|
|
74
|
+
await delay(200);
|
|
75
|
+
return { ok: true, readBack };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error(`${LOG} ${label} → 失败:`, err);
|
|
78
|
+
await testRem.setText([`${label} 失败: ${String(err)}`]);
|
|
79
|
+
await delay(200);
|
|
80
|
+
return { ok: false, readBack: null };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ═══════════════════════════════════════════════════
|
|
85
|
+
// 0. 探测真实 cloze:先写纯文本,再用 applyTextFormatToRange 加 cloze,再回读
|
|
86
|
+
// ═══════════════════════════════════════════════════
|
|
87
|
+
console.log(`${LOG} --- 0. 探测真实 cloze ID 格式 ---`);
|
|
88
|
+
const clozeProbe = await plugin.rem.createRem();
|
|
89
|
+
if (clozeProbe) {
|
|
90
|
+
await clozeProbe.setParent(wId, pos++);
|
|
91
|
+
await clozeProbe.setText(['探测用文本:ABCDEFGH']);
|
|
92
|
+
await delay(300);
|
|
93
|
+
|
|
94
|
+
// 对 "BCDE"(位置 5-9)加 cloze 格式
|
|
95
|
+
const originalText = clozeProbe.text;
|
|
96
|
+
if (originalText) {
|
|
97
|
+
try {
|
|
98
|
+
const formatted = await plugin.richText.applyTextFormatToRange(
|
|
99
|
+
originalText, 5, 9, 'cloze' as any
|
|
100
|
+
);
|
|
101
|
+
console.log(`${LOG} applyTextFormatToRange cloze 结果:`, JSON.stringify(formatted));
|
|
102
|
+
// 写回
|
|
103
|
+
await clozeProbe.setText(formatted);
|
|
104
|
+
await delay(300);
|
|
105
|
+
// 回读
|
|
106
|
+
const refetch = await plugin.rem.findOne(clozeProbe._id);
|
|
107
|
+
const readBack = refetch ? refetch.text : clozeProbe.text;
|
|
108
|
+
console.log(`${LOG} cloze 回读:`, JSON.stringify(readBack));
|
|
109
|
+
|
|
110
|
+
const probeResult = await plugin.rem.createRem();
|
|
111
|
+
if (probeResult) {
|
|
112
|
+
await probeResult.setParent(wId, pos++);
|
|
113
|
+
await probeResult.setText([`↳ 真实 cloze JSON: ${JSON.stringify(readBack)}`]);
|
|
114
|
+
}
|
|
115
|
+
await delay(200);
|
|
116
|
+
|
|
117
|
+
// 提取真实 cloze ID
|
|
118
|
+
let realClozeId: string | undefined;
|
|
119
|
+
if (Array.isArray(readBack)) {
|
|
120
|
+
for (const el of readBack) {
|
|
121
|
+
if (el && typeof el === 'object' && 'cId' in el) {
|
|
122
|
+
realClozeId = (el as any).cId;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const idInfoRem = await plugin.rem.createRem();
|
|
129
|
+
if (idInfoRem) {
|
|
130
|
+
await idInfoRem.setParent(wId, pos++);
|
|
131
|
+
await idInfoRem.setText([
|
|
132
|
+
`↳ 真实 cloze ID: "${realClozeId}" ` +
|
|
133
|
+
`(类型:${typeof realClozeId}, 长度:${realClozeId?.length})`,
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
await delay(200);
|
|
137
|
+
|
|
138
|
+
// ── 用真实 ID 格式手拼 cloze 写入 ──
|
|
139
|
+
if (realClozeId) {
|
|
140
|
+
await testWrite('0b. 真实ID手拼cloze', [
|
|
141
|
+
'手拼(真实ID): ',
|
|
142
|
+
{ i: 'm', text: '真实ID填空', cId: realClozeId } as any,
|
|
143
|
+
' 后续',
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(`${LOG} applyTextFormatToRange 失败:`, err);
|
|
148
|
+
const errRem = await plugin.rem.createRem();
|
|
149
|
+
if (errRem) {
|
|
150
|
+
await errRem.setParent(wId, pos++);
|
|
151
|
+
await errRem.setText([`0. applyTextFormatToRange 失败: ${String(err)}`]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ═══════════════════════════════════════════════════
|
|
158
|
+
// 1. cId — 用随便编的 ID
|
|
159
|
+
// ═══════════════════════════════════════════════════
|
|
160
|
+
await testWrite('1. cId(随编ID)', [
|
|
161
|
+
'填空(随编): ',
|
|
162
|
+
{ i: 'm', text: '答案B', cId: 'my-fake-cloze-999' } as any,
|
|
163
|
+
' 后续',
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
// ═══════════════════════════════════════════════════
|
|
167
|
+
// 2. hiddenCloze + cId
|
|
168
|
+
// ═══════════════════════════════════════════════════
|
|
169
|
+
await testWrite('2. hiddenCloze', [
|
|
170
|
+
'hidden: ',
|
|
171
|
+
{ i: 'm', text: '隐藏填空', cId: 'hc-001', hiddenCloze: true } as any,
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
// ═══════════════════════════════════════════════════
|
|
175
|
+
// 3. revealedCloze + cId
|
|
176
|
+
// ═══════════════════════════════════════════════════
|
|
177
|
+
await testWrite('3. revealedCloze', [
|
|
178
|
+
'revealed: ',
|
|
179
|
+
{ i: 'm', text: '揭示填空', cId: 'rc-001', revealedCloze: true } as any,
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
// ═══════════════════════════════════════════════════
|
|
183
|
+
// 4. block = true(引用块)
|
|
184
|
+
// ═══════════════════════════════════════════════════
|
|
185
|
+
await testWrite('4. block=true', [
|
|
186
|
+
{ i: 'm', text: '这应该是引用块', block: true } as any,
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
// ═══════════════════════════════════════════════════
|
|
190
|
+
// 5. qId — 行内引用链接
|
|
191
|
+
// ═══════════════════════════════════════════════════
|
|
192
|
+
const qIdTarget = await plugin.rem.createRem();
|
|
193
|
+
if (qIdTarget) {
|
|
194
|
+
await qIdTarget.setParent(wId, pos++);
|
|
195
|
+
await qIdTarget.setText(['qId 引用目标 Rem']);
|
|
196
|
+
await delay(200);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await testWrite('5. qId', [
|
|
200
|
+
'行内引用: ',
|
|
201
|
+
{ i: 'm', text: '这是qId链接', qId: qIdTarget?._id || 'fake' } as any,
|
|
202
|
+
' 后续',
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
// ═══════════════════════════════════════════════════
|
|
206
|
+
// 6. title
|
|
207
|
+
// ═══════════════════════════════════════════════════
|
|
208
|
+
await testWrite('6. title', [
|
|
209
|
+
{ i: 'm', text: '有title属性的文本', title: '我是标题' } as any,
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
// ═══════════════════════════════════════════════════
|
|
213
|
+
// 7. i:"g" GlobalName
|
|
214
|
+
// ═══════════════════════════════════════════════════
|
|
215
|
+
await testWrite('7a. i:g(有_id)', [
|
|
216
|
+
'GlobalName: ',
|
|
217
|
+
{ i: 'g', _id: qIdTarget?._id || null } as any,
|
|
218
|
+
]);
|
|
219
|
+
await testWrite('7b. i:g(null)', [
|
|
220
|
+
'GlobalName(null): ',
|
|
221
|
+
{ i: 'g', _id: null } as any,
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
// ═══════════════════════════════════════════════════
|
|
225
|
+
// 8. i:"fi" FlashcardIcon
|
|
226
|
+
// ═══════════════════════════════════════════════════
|
|
227
|
+
await testWrite('8. i:fi', [
|
|
228
|
+
'闪卡图标: ',
|
|
229
|
+
{ i: 'fi' } as any,
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
// ── 完成 ──
|
|
233
|
+
const doneRem = await plugin.rem.createRem();
|
|
234
|
+
if (doneRem) {
|
|
235
|
+
await doneRem.setParent(wId, pos++);
|
|
236
|
+
await doneRem.setHighlightColor('Green');
|
|
237
|
+
await doneRem.setText(['===== v2 验证完成 =====']);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log(`${LOG} ========== v2 全部完成 ==========`);
|
|
241
|
+
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.error(`${LOG} 顶层错误:`, err);
|
|
244
|
+
}
|
|
245
|
+
}
|