remnote-bridge 0.1.15 → 0.1.17
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/README.md +13 -2
- package/README.zh-CN.md +13 -2
- package/dist/cli/commands/edit-tree.js +12 -1
- package/dist/cli/commands/read-context.js +6 -4
- package/dist/cli/config.js +1 -1
- package/dist/cli/handlers/edit-handler.js +63 -16
- package/dist/cli/handlers/tree-edit-handler.js +3 -1
- package/dist/cli/handlers/tree-parser.js +17 -0
- package/dist/cli/main.js +1 -1
- package/dist/cli/server/config-server.js +1 -1
- package/dist/mcp/instructions.js +12 -8
- package/dist/mcp/tools/edit-tools.js +9 -4
- package/dist/mcp/tools/read-tools.js +8 -4
- package/package.json +1 -1
- package/remnote-plugin/dist/index-sandbox.js +19 -19
- package/remnote-plugin/dist/index.js +19 -19
- package/remnote-plugin/src/services/read-context.ts +1 -1
- package/remnote-plugin/src/services/rem-builder.ts +8 -2
- package/remnote-plugin/src/types.ts +59 -51
- package/remnote-plugin/src/utils/tree-serializer.ts +10 -0
- package/skills/remnote-bridge/SKILL.md +19 -8
- package/skills/remnote-bridge/instructions/edit-rem.md +44 -15
- package/skills/remnote-bridge/instructions/edit-tree.md +11 -1
- package/skills/remnote-bridge/instructions/overall.md +43 -19
- package/skills/remnote-bridge/instructions/read-context.md +10 -5
- package/skills/remnote-bridge-test/SKILL.md +3 -0
- package/skills/remnote-bridge-test/references/regression-suite.md +64 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 依赖方向:services/rem-builder → utils/tree-serializer(单向)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { ReactRNPlugin, PluginRem as Rem } from '@remnote/plugin-sdk';
|
|
10
|
+
import type { ReactRNPlugin, PluginRem as Rem, RichTextInterface } from '@remnote/plugin-sdk';
|
|
11
11
|
import type { SerializableRem } from '../utils/tree-serializer';
|
|
12
12
|
import { filterNoisyTags } from './powerup-filter';
|
|
13
13
|
|
|
@@ -21,7 +21,7 @@ export async function safeToMarkdown(
|
|
|
21
21
|
richText: unknown[],
|
|
22
22
|
): Promise<string> {
|
|
23
23
|
try {
|
|
24
|
-
return await plugin.richText.toMarkdown(richText);
|
|
24
|
+
return await plugin.richText.toMarkdown(richText as RichTextInterface);
|
|
25
25
|
} catch {
|
|
26
26
|
return richTextFallback(richText);
|
|
27
27
|
}
|
|
@@ -93,6 +93,8 @@ export async function buildFullSerializableRem(
|
|
|
93
93
|
isTodo,
|
|
94
94
|
todoStatus,
|
|
95
95
|
isCode,
|
|
96
|
+
isQuote,
|
|
97
|
+
isListItem,
|
|
96
98
|
hasDvPowerup,
|
|
97
99
|
portalIncludedRems,
|
|
98
100
|
] = await Promise.all([
|
|
@@ -114,6 +116,8 @@ export async function buildFullSerializableRem(
|
|
|
114
116
|
rem.isTodo(),
|
|
115
117
|
rem.getTodoStatus(),
|
|
116
118
|
rem.isCode(),
|
|
119
|
+
rem.isQuote(),
|
|
120
|
+
rem.isListItem(),
|
|
117
121
|
rem.hasPowerup('dv'),
|
|
118
122
|
rem.type === 6 ? rem.getPortalDirectlyIncludedRem() : Promise.resolve([]),
|
|
119
123
|
]);
|
|
@@ -151,6 +155,8 @@ export async function buildFullSerializableRem(
|
|
|
151
155
|
isTodo,
|
|
152
156
|
todoStatus: (todoStatus as 'Finished' | 'Unfinished' | null) ?? null,
|
|
153
157
|
isCode,
|
|
158
|
+
isQuote,
|
|
159
|
+
isListItem,
|
|
154
160
|
isDivider,
|
|
155
161
|
isTopLevel: rem.parent === null,
|
|
156
162
|
};
|
|
@@ -97,13 +97,20 @@ export type PropertyTypeValue =
|
|
|
97
97
|
|
|
98
98
|
// ─── RemObject 主体 ─────────────────────────────────────────
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* RemObject 字段注释标记说明:
|
|
102
|
+
* - [R] 只读 / [RW] 可读写 / [R-F] 只读+默认过滤
|
|
103
|
+
* - 🛡️D2 = 参与防线2语义比较(变化 → 硬拒绝)
|
|
104
|
+
* - ⚠️D2 = 敏感元数据(变化 → 放行 + 专门警告)
|
|
105
|
+
* - ⊘D2 = 普通元数据(变化 → 放行 + 统一警告)
|
|
106
|
+
*/
|
|
100
107
|
export interface RemObject {
|
|
101
108
|
|
|
102
109
|
// ══════════════════════════════════════════════════════════
|
|
103
110
|
// 核心标识
|
|
104
111
|
// ══════════════════════════════════════════════════════════
|
|
105
112
|
|
|
106
|
-
/** [R] Rem 唯一 ID。SDK 直接属性 _id */
|
|
113
|
+
/** [R] ⊘D2 Rem 唯一 ID。SDK 直接属性 _id */
|
|
107
114
|
id: string;
|
|
108
115
|
|
|
109
116
|
// ══════════════════════════════════════════════════════════
|
|
@@ -111,12 +118,12 @@ export interface RemObject {
|
|
|
111
118
|
// ══════════════════════════════════════════════════════════
|
|
112
119
|
|
|
113
120
|
/**
|
|
114
|
-
* [RW] ✅ 正面文本(RichText 数组)。SDK: text / setText()
|
|
121
|
+
* [RW] 🛡️D2 ✅ 正面文本(RichText 数组)。SDK: text / setText()
|
|
115
122
|
* UI 行为:文本内容立即更新显示,无格式副作用
|
|
116
123
|
*/
|
|
117
124
|
text: RichText;
|
|
118
125
|
/**
|
|
119
|
-
* [RW] ✅ 背面文本。SDK: backText / setBackText()
|
|
126
|
+
* [RW] 🛡️D2 ✅ 背面文本。SDK: backText / setBackText()
|
|
120
127
|
* UI 行为:设值后 Rem 显示为 "正面文本 → 背面文本" 格式(箭头分隔符)
|
|
121
128
|
* 默认 null(无背面);设值即产生闪卡正反面结构
|
|
122
129
|
* 写入语义:null → setBackText([])(SDK 接受 undefined | RichTextInterface,空数组清除背面)
|
|
@@ -129,14 +136,14 @@ export interface RemObject {
|
|
|
129
136
|
// ══════════════════════════════════════════════════════════
|
|
130
137
|
|
|
131
138
|
/**
|
|
132
|
-
* [RW] ✅ Rem 类型。SDK: type, getType() / setType(SetRemType)
|
|
139
|
+
* [RW] 🛡️D2 ✅ Rem 类型。SDK: type, getType() / setType(SetRemType)
|
|
133
140
|
* UI 行为:CONCEPT → 文字变粗体;DESCRIPTOR → 保持正常字重(与默认无视觉差异)
|
|
134
141
|
* SetRemType 不含 PORTAL(6),Portal 只能通过 createPortal() 创建
|
|
135
142
|
* 底层机制:纯字段修改,不涉及 Powerup Tag 注入或隐藏子 Rem
|
|
136
143
|
*/
|
|
137
144
|
type: RemTypeValue;
|
|
138
145
|
/**
|
|
139
|
-
* [RW] ✅ 是否作为独立文档页面打开。SDK: isDocument() / setIsDocument()
|
|
146
|
+
* [RW] 🛡️D2 ✅ 是否作为独立文档页面打开。SDK: isDocument() / setIsDocument()
|
|
140
147
|
* UI 行为:bullet (•) 变为文档页面图标(小方块),Rem 可作为独立页面打开
|
|
141
148
|
* 独立于 type,CONCEPT Rem 可以同时是 Document
|
|
142
149
|
* 底层机制:Powerup — 注入"文档" Tag + 自动创建 [Status];;[Draft] descriptor 子 Rem
|
|
@@ -148,11 +155,12 @@ export interface RemObject {
|
|
|
148
155
|
// ══════════════════════════════════════════════════════════
|
|
149
156
|
|
|
150
157
|
/**
|
|
151
|
-
* [RW] ✅ 父 Rem ID。null 表示顶级。SDK: parent / setParent(parent, position?)
|
|
158
|
+
* [RW] ⚠️D2 ✅ 父 Rem ID。null 表示顶级。SDK: parent / setParent(parent, position?)
|
|
152
159
|
* UI 行为:Rem 从原位置消失,出现在新父级的子列表中
|
|
160
|
+
* 防线2:结构操作后常变化,放行但输出专门警告
|
|
153
161
|
*/
|
|
154
162
|
parent: string | null;
|
|
155
|
-
/** [R] 子 Rem ID 有序数组。SDK 直接属性 children(修改子的 parent 间接改变) */
|
|
163
|
+
/** [R] ⊘D2 子 Rem ID 有序数组。SDK 直接属性 children(修改子的 parent 间接改变) */
|
|
156
164
|
children: string[];
|
|
157
165
|
|
|
158
166
|
// ══════════════════════════════════════════════════════════
|
|
@@ -160,14 +168,14 @@ export interface RemObject {
|
|
|
160
168
|
// ══════════════════════════════════════════════════════════
|
|
161
169
|
|
|
162
170
|
/**
|
|
163
|
-
* [RW] ✅ 标题大小。SDK: getFontSize() / setFontSize()
|
|
171
|
+
* [RW] 🛡️D2 ✅ 标题大小。SDK: getFontSize() / setFontSize()
|
|
164
172
|
* UI 行为:H1 → 超大粗体;H2 → 大粗体(略小于 H1);H3 → 中粗体
|
|
165
173
|
* 默认 null(普通大小);setFontSize(undefined) 恢复
|
|
166
174
|
* 底层机制:Powerup — 注入"标题" Tag + 创建 [Size];;[H1/H2/H3] descriptor 子 Rem
|
|
167
175
|
*/
|
|
168
176
|
fontSize: FontSize | null;
|
|
169
177
|
/**
|
|
170
|
-
* [RW] ✅ 高亮颜色。SDK: getHighlightColor() / setHighlightColor()
|
|
178
|
+
* [RW] 🛡️D2 ✅ 高亮颜色。SDK: getHighlightColor() / setHighlightColor()
|
|
171
179
|
* UI 行为:整行背景变为对应颜色(Red→粉红、Blue→浅蓝),bullet 也着色
|
|
172
180
|
* 默认 null(无高亮)
|
|
173
181
|
* SDK 注意:setHighlightColor() 只能设置颜色,不能清除(null/undefined 均被拒绝)
|
|
@@ -181,47 +189,47 @@ export interface RemObject {
|
|
|
181
189
|
// ══════════════════════════════════════════════════════════
|
|
182
190
|
|
|
183
191
|
/**
|
|
184
|
-
* [RW] ✅ 是否待办。SDK: isTodo() / setIsTodo()
|
|
192
|
+
* [RW] 🛡️D2 ✅ 是否待办。SDK: isTodo() / setIsTodo()
|
|
185
193
|
* UI 行为:文本前出现空心 checkbox(☐);副作用:todoStatus 自动初始化为 "Unfinished"
|
|
186
194
|
* 底层机制:Powerup — 注入"待办" Tag + 自动创建 [Status];;[Unfinished] descriptor 子 Rem
|
|
187
195
|
*/
|
|
188
196
|
isTodo: boolean;
|
|
189
197
|
/**
|
|
190
|
-
* [RW] ✅ 待办完成状态。SDK: getTodoStatus() / setTodoStatus()
|
|
198
|
+
* [RW] 🛡️D2 ✅ 待办完成状态。SDK: getTodoStatus() / setTodoStatus()
|
|
191
199
|
* UI 行为:Finished → checkbox 变蓝色已勾选(☑)+ 文本加删除线
|
|
192
200
|
* 前提:需先 setIsTodo(true),否则无意义
|
|
193
201
|
* 写入语义:null → 跳过(清除 todo 状态应通过 isTodo=false 实现,SDK 不接受 null)
|
|
194
202
|
*/
|
|
195
203
|
todoStatus: TodoStatus | null;
|
|
196
204
|
/**
|
|
197
|
-
* [RW] ✅ 是否代码块。SDK: isCode() / setIsCode()
|
|
205
|
+
* [RW] 🛡️D2 ✅ 是否代码块。SDK: isCode() / setIsCode()
|
|
198
206
|
* UI 行为:Rem 变为代码块容器——等宽字体、灰色背景、块级缩进
|
|
199
207
|
* 底层机制:Powerup — 注入"代码" Tag,无参数子 Rem
|
|
200
208
|
*/
|
|
201
209
|
isCode: boolean;
|
|
202
210
|
/**
|
|
203
|
-
* [RW] ✅ 是否引用块。SDK: isQuote() / setIsQuote()
|
|
211
|
+
* [RW] 🛡️D2 ✅ 是否引用块。SDK: isQuote() / setIsQuote()
|
|
204
212
|
* UI 行为:左侧出现灰色竖线 + 行背景变浅灰(经典 blockquote 样式)
|
|
205
213
|
* 底层机制:Powerup — 注入"引用" Tag,无参数子 Rem
|
|
206
214
|
*/
|
|
207
215
|
isQuote: boolean;
|
|
208
216
|
/**
|
|
209
|
-
* [RW] ✅ 是否列表项。SDK: isListItem() / setIsListItem()
|
|
217
|
+
* [RW] 🛡️D2 ✅ 是否列表项。SDK: isListItem() / setIsListItem()
|
|
210
218
|
* UI 行为:bullet (•) 变为数字编号 "1."(有序列表样式)
|
|
211
219
|
* 底层机制:Powerup — 注入"列表项" Tag,无参数子 Rem
|
|
212
220
|
*/
|
|
213
221
|
isListItem: boolean;
|
|
214
222
|
/**
|
|
215
|
-
* [RW] ✅ 是否卡片项。SDK: isCardItem() / setIsCardItem()
|
|
223
|
+
* [RW] 🛡️D2 ✅ 是否卡片项。SDK: isCardItem() / setIsCardItem()
|
|
216
224
|
* UI:无明显变化。功能:标记 Rem 以卡片样式显示(类似看板布局),
|
|
217
225
|
* 而非默认项目符号列表,在 RemNote 的 Card View 中生效
|
|
218
226
|
* 底层机制:Powerup — 注入"卡片条目" Tag (MultiLineCard),无参数子 Rem
|
|
219
227
|
*/
|
|
220
228
|
isCardItem: boolean;
|
|
221
|
-
/** [R] 是否表格。SDK: isTable()(无 setIsTable,只有 setTableFilter) */
|
|
229
|
+
/** [R] 🛡️D2 是否表格。SDK: isTable()(无 setIsTable,只有 setTableFilter) */
|
|
222
230
|
isTable: boolean;
|
|
223
231
|
/**
|
|
224
|
-
* [RW] ✅ 是否 Powerup 插槽。SDK: isSlot() / setIsSlot()
|
|
232
|
+
* [RW] 🛡️D2 ✅ 是否 Powerup 插槽。SDK: isSlot() / setIsSlot()
|
|
225
233
|
* UI:bullet 变为方形图标(☐)。功能:标记 Rem 为 Powerup 的数据插槽(slot),
|
|
226
234
|
* Powerup 注册时通过 slots 配置定义,用于存储键值对数据(值为 RichText)。
|
|
227
235
|
* 通过 getPowerupProperty(code, slot) / setPowerupProperty() 读写
|
|
@@ -232,7 +240,7 @@ export interface RemObject {
|
|
|
232
240
|
*/
|
|
233
241
|
isSlot: boolean;
|
|
234
242
|
/**
|
|
235
|
-
* [RW] ✅ 是否 Tag 属性(表格列)。SDK: isProperty() / setIsProperty()
|
|
243
|
+
* [RW] 🛡️D2 ✅ 是否 Tag 属性(表格列)。SDK: isProperty() / setIsProperty()
|
|
236
244
|
* UI:bullet 变为方形图标(☐,与 isSlot 相同)。功能:标记 Rem 为父级 Tag 的
|
|
237
245
|
* 结构化属性列,可通过 getPropertyType() 指定数据类型(text/number/date/checkbox/
|
|
238
246
|
* single_select/multi_select/url/image 等),通过 getTagPropertyValue(propertyId) /
|
|
@@ -240,31 +248,31 @@ export interface RemObject {
|
|
|
240
248
|
* 底层机制:与 isSlot 完全相同 — 注入同一个"模板插槽" Tag (vD8KGEg5dkj9bzkRn)
|
|
241
249
|
*/
|
|
242
250
|
isProperty: boolean;
|
|
243
|
-
/** [R-F] 是否 Powerup。SDK: isPowerup()(写入用 addPowerup/removePowerup,参数化)。Powerup 系统标识 */
|
|
251
|
+
/** [R-F] ⊘D2 是否 Powerup。SDK: isPowerup()(写入用 addPowerup/removePowerup,参数化)。Powerup 系统标识 */
|
|
244
252
|
isPowerup: boolean;
|
|
245
|
-
/** [R-F] 是否 Powerup 枚举。SDK: isPowerupEnum()。Powerup 细分类型 */
|
|
253
|
+
/** [R-F] ⊘D2 是否 Powerup 枚举。SDK: isPowerupEnum()。Powerup 细分类型 */
|
|
246
254
|
isPowerupEnum: boolean;
|
|
247
|
-
/** [R-F] 是否 Powerup 属性。SDK: isPowerupProperty()。Powerup 细分类型 */
|
|
255
|
+
/** [R-F] ⊘D2 是否 Powerup 属性。SDK: isPowerupProperty()。Powerup 细分类型 */
|
|
248
256
|
isPowerupProperty: boolean;
|
|
249
|
-
/** [R-F] 是否 Powerup 属性列表项。SDK: isPowerupPropertyListItem()。Powerup 细分类型 */
|
|
257
|
+
/** [R-F] ⊘D2 是否 Powerup 属性列表项。SDK: isPowerupPropertyListItem()。Powerup 细分类型 */
|
|
250
258
|
isPowerupPropertyListItem: boolean;
|
|
251
|
-
/** [R-F] 是否 Powerup 插槽。SDK: isPowerupSlot()。Powerup 细分类型 */
|
|
259
|
+
/** [R-F] ⊘D2 是否 Powerup 插槽。SDK: isPowerupSlot()。Powerup 细分类型 */
|
|
252
260
|
isPowerupSlot: boolean;
|
|
253
261
|
|
|
254
262
|
// ══════════════════════════════════════════════════════════
|
|
255
263
|
// Portal 专用
|
|
256
264
|
// ══════════════════════════════════════════════════════════
|
|
257
265
|
|
|
258
|
-
/** [R] Portal 子类型。仅 type === 'portal' 时有值。SDK: getPortalType() */
|
|
266
|
+
/** [R] 🛡️D2 Portal 子类型。仅 type === 'portal' 时有值。SDK: getPortalType() */
|
|
259
267
|
portalType: PortalType | null;
|
|
260
|
-
/** [Portal-W] Portal 直接包含的 Rem ID 数组。SDK: getPortalDirectlyIncludedRem() / addToPortal() / removeFromPortal()。写入时使用 diff 机制。仅 type === 'portal' 时可修改 */
|
|
268
|
+
/** [Portal-W] 🛡️D2 Portal 直接包含的 Rem ID 数组。SDK: getPortalDirectlyIncludedRem() / addToPortal() / removeFromPortal()。写入时使用 diff 机制。仅 type === 'portal' 时可修改 */
|
|
261
269
|
portalDirectlyIncludedRem: string[];
|
|
262
270
|
|
|
263
271
|
// ══════════════════════════════════════════════════════════
|
|
264
272
|
// 属性类型(当此 Rem 是 tag/powerup 的属性时)
|
|
265
273
|
// ══════════════════════════════════════════════════════════
|
|
266
274
|
|
|
267
|
-
/** [R] 属性数据类型。SDK: getPropertyType() */
|
|
275
|
+
/** [R] 🛡️D2 属性数据类型。SDK: getPropertyType() */
|
|
268
276
|
propertyType: PropertyTypeValue | null;
|
|
269
277
|
|
|
270
278
|
// ══════════════════════════════════════════════════════════
|
|
@@ -272,14 +280,14 @@ export interface RemObject {
|
|
|
272
280
|
// ══════════════════════════════════════════════════════════
|
|
273
281
|
|
|
274
282
|
/**
|
|
275
|
-
* [RW] ✅ 是否启用间隔重复练习。SDK: getEnablePractice() / setEnablePractice()
|
|
283
|
+
* [RW] 🛡️D2 ✅ 是否启用间隔重复练习。SDK: getEnablePractice() / setEnablePractice()
|
|
276
284
|
* UI:无明显变化。功能:为 true 时,RemNote 根据 Rem 的 text/backText 结构
|
|
277
285
|
* 自动生成闪卡并纳入间隔重复调度。setType(CONCEPT) 可能自动置为 true
|
|
278
286
|
* 底层机制:纯字段修改,不涉及 Powerup Tag 注入或隐藏子 Rem
|
|
279
287
|
*/
|
|
280
288
|
enablePractice: boolean;
|
|
281
289
|
/**
|
|
282
|
-
* [RW] ✅ 闪卡练习方向。SDK: getPracticeDirection() / setPracticeDirection()
|
|
290
|
+
* [RW] 🛡️D2 ✅ 闪卡练习方向。SDK: getPracticeDirection() / setPracticeDirection()
|
|
283
291
|
* UI:无明显变化。功能:控制闪卡生成方向——forward=正面→背面,
|
|
284
292
|
* backward=背面→正面,both=双向生成,none=不生成闪卡
|
|
285
293
|
* 底层机制:纯字段修改,不涉及 Powerup Tag 注入或隐藏子 Rem
|
|
@@ -291,7 +299,7 @@ export interface RemObject {
|
|
|
291
299
|
// ══════════════════════════════════════════════════════════
|
|
292
300
|
|
|
293
301
|
/**
|
|
294
|
-
* [RW] ✅ 标签 Rem ID 数组。SDK: getTagRems() / addTag() / removeTag()
|
|
302
|
+
* [RW] 🛡️D2 ✅ 标签 Rem ID 数组。SDK: getTagRems() / addTag() / removeTag()
|
|
295
303
|
* UI 行为:行右侧出现标签徽章(圆角矩形,显示标签名 + × 删除按钮)
|
|
296
304
|
* setType(CONCEPT) 等操作可能自动添加系统标签
|
|
297
305
|
* 注意:系统 Powerup Tag 会混入此数组(如 setIsCode 注入的"代码" Tag),
|
|
@@ -299,12 +307,12 @@ export interface RemObject {
|
|
|
299
307
|
*/
|
|
300
308
|
tags: string[];
|
|
301
309
|
/**
|
|
302
|
-
* [RW] ✅ 来源 Rem ID 数组。SDK: getSources() / addSource() / removeSource()
|
|
310
|
+
* [RW] 🛡️D2 ✅ 来源 Rem ID 数组。SDK: getSources() / addSource() / removeSource()
|
|
303
311
|
* UI 行为:Rem 下方出现来源引用子元素(灰色圆角框,显示来源 Rem 名 + ↗ 图标)
|
|
304
312
|
*/
|
|
305
313
|
sources: string[];
|
|
306
314
|
/**
|
|
307
|
-
* [R] 别名 Rem ID 数组。SDK: getAliases()
|
|
315
|
+
* [R] 🛡️D2 别名 Rem ID 数组。SDK: getAliases()
|
|
308
316
|
* 写入接口 getOrCreateAliasWithText(text) 需要文本参数(非 ID),与 RemObject 的 ID 数组形式不匹配。
|
|
309
317
|
* v1 标记为只读,后续可提供独立命令 `add-alias <remId> <text>`
|
|
310
318
|
*/
|
|
@@ -314,37 +322,37 @@ export interface RemObject {
|
|
|
314
322
|
// 关联 — 引用关系(ID 数组)
|
|
315
323
|
// ══════════════════════════════════════════════════════════
|
|
316
324
|
|
|
317
|
-
/** [R] 本 Rem 引用的其他 Rem ID 数组。SDK: remsBeingReferenced() */
|
|
325
|
+
/** [R] 🛡️D2 本 Rem 引用的其他 Rem ID 数组。SDK: remsBeingReferenced() */
|
|
318
326
|
remsBeingReferenced: string[];
|
|
319
|
-
/** [R-F] 本 Rem 深层引用的 Rem ID 数组。SDK: deepRemsBeingReferenced()。可由 remsBeingReferenced 递归获取 */
|
|
327
|
+
/** [R-F] ⊘D2 本 Rem 深层引用的 Rem ID 数组。SDK: deepRemsBeingReferenced()。可由 remsBeingReferenced 递归获取 */
|
|
320
328
|
deepRemsBeingReferenced: string[];
|
|
321
|
-
/** [R] 引用本 Rem 的 Rem ID 数组(反向链接)。SDK: remsReferencingThis() */
|
|
329
|
+
/** [R] ⊘D2 引用本 Rem 的 Rem ID 数组(反向链接)。SDK: remsReferencingThis() */
|
|
322
330
|
remsReferencingThis: string[];
|
|
323
331
|
|
|
324
332
|
// ══════════════════════════════════════════════════════════
|
|
325
333
|
// 关联 — 标签体系(ID 数组)
|
|
326
334
|
// ══════════════════════════════════════════════════════════
|
|
327
335
|
|
|
328
|
-
/** [R] 被本 Rem 标记的 Rem ID 数组(本 Rem 作为 tag 时)。SDK: taggedRem() */
|
|
336
|
+
/** [R] ⊘D2 被本 Rem 标记的 Rem ID 数组(本 Rem 作为 tag 时)。SDK: taggedRem() */
|
|
329
337
|
taggedRem: string[];
|
|
330
|
-
/** [R-F] 祖先标签 Rem ID 数组。SDK: ancestorTagRem()。标签继承链,低频 */
|
|
338
|
+
/** [R-F] ⊘D2 祖先标签 Rem ID 数组。SDK: ancestorTagRem()。标签继承链,低频 */
|
|
331
339
|
ancestorTagRem: string[];
|
|
332
|
-
/** [R-F] 后代标签 Rem ID 数组。SDK: descendantTagRem()。标签继承链,低频 */
|
|
340
|
+
/** [R-F] ⊘D2 后代标签 Rem ID 数组。SDK: descendantTagRem()。标签继承链,低频 */
|
|
333
341
|
descendantTagRem: string[];
|
|
334
342
|
|
|
335
343
|
// ══════════════════════════════════════════════════════════
|
|
336
344
|
// 关联 — 层级遍历(ID 数组)
|
|
337
345
|
// ══════════════════════════════════════════════════════════
|
|
338
346
|
|
|
339
|
-
/** [R] 所有后代 Rem ID 数组。SDK: getDescendants() */
|
|
347
|
+
/** [R] ⊘D2 所有后代 Rem ID 数组。SDK: getDescendants() */
|
|
340
348
|
descendants: string[];
|
|
341
|
-
/** [R] 兄弟 Rem ID 数组。SDK: siblingRem() */
|
|
349
|
+
/** [R] ⊘D2 兄弟 Rem ID 数组。SDK: siblingRem() */
|
|
342
350
|
siblingRem: string[];
|
|
343
|
-
/** [R-F] 包含的 Portal 和文档 Rem ID 数组。SDK: portalsAndDocumentsIn()。使用场景有限 */
|
|
351
|
+
/** [R-F] ⊘D2 包含的 Portal 和文档 Rem ID 数组。SDK: portalsAndDocumentsIn()。使用场景有限 */
|
|
344
352
|
portalsAndDocumentsIn: string[];
|
|
345
|
-
/** [R-F] 文档/Portal 中所有 Rem ID 数组。SDK: allRemInDocumentOrPortal()。可能数据量大 */
|
|
353
|
+
/** [R-F] ⊘D2 文档/Portal 中所有 Rem ID 数组。SDK: allRemInDocumentOrPortal()。可能数据量大 */
|
|
346
354
|
allRemInDocumentOrPortal: string[];
|
|
347
|
-
/** [R-F] 文件夹队列中的 Rem ID 数组。SDK: allRemInFolderQueue()。场景有限 */
|
|
355
|
+
/** [R-F] ⊘D2 文件夹队列中的 Rem ID 数组。SDK: allRemInFolderQueue()。场景有限 */
|
|
348
356
|
allRemInFolderQueue: string[];
|
|
349
357
|
|
|
350
358
|
// ══════════════════════════════════════════════════════════
|
|
@@ -352,36 +360,36 @@ export interface RemObject {
|
|
|
352
360
|
// ══════════════════════════════════════════════════════════
|
|
353
361
|
|
|
354
362
|
/**
|
|
355
|
-
* [RW] ✅ 在兄弟间的位置(0 起始)。SDK: positionAmongstSiblings() / setParent(parent, position)
|
|
363
|
+
* [RW] ⊘D2 ✅ 在兄弟间的位置(0 起始)。SDK: positionAmongstSiblings() / setParent(parent, position)
|
|
356
364
|
* UI 行为:Rem 在父级子列表中的显示位置改变(测试:A→B→C 变为 B→C→A)
|
|
357
365
|
* position 超过实际数量会被钳位到末尾;位置相对于父 Rem 的全部 children
|
|
358
366
|
*/
|
|
359
367
|
positionAmongstSiblings: number | null;
|
|
360
|
-
/** [R-F] 搜索中被选次数。SDK: timesSelectedInSearch()。统计数据,低频 */
|
|
368
|
+
/** [R-F] ⊘D2 搜索中被选次数。SDK: timesSelectedInSearch()。统计数据,低频 */
|
|
361
369
|
timesSelectedInSearch: number;
|
|
362
|
-
/** [R-F] 上次移动时间(毫秒时间戳)。SDK: getLastTimeMovedTo()。过于细粒度 */
|
|
370
|
+
/** [R-F] ⊘D2 上次移动时间(毫秒时间戳)。SDK: getLastTimeMovedTo()。过于细粒度 */
|
|
363
371
|
lastTimeMovedTo: number;
|
|
364
|
-
/** [R-F] Schema 版本号。SDK: getSchemaVersion()。内部版本号 */
|
|
372
|
+
/** [R-F] ⊘D2 Schema 版本号。SDK: getSchemaVersion()。内部版本号 */
|
|
365
373
|
schemaVersion: number;
|
|
366
374
|
|
|
367
375
|
// ══════════════════════════════════════════════════════════
|
|
368
376
|
// 队列视图
|
|
369
377
|
// ══════════════════════════════════════════════════════════
|
|
370
378
|
|
|
371
|
-
/** [R-F] 嵌入式队列视图模式。SDK: embeddedQueueViewMode()。场景有限 */
|
|
379
|
+
/** [R-F] ⊘D2 嵌入式队列视图模式。SDK: embeddedQueueViewMode()。场景有限 */
|
|
372
380
|
embeddedQueueViewMode: boolean;
|
|
373
381
|
|
|
374
382
|
// ══════════════════════════════════════════════════════════
|
|
375
383
|
// 元数据 / 时间戳
|
|
376
384
|
// ══════════════════════════════════════════════════════════
|
|
377
385
|
|
|
378
|
-
/** [R] 创建时间(毫秒时间戳)。SDK 直接属性 createdAt */
|
|
386
|
+
/** [R] ⊘D2 创建时间(毫秒时间戳)。SDK 直接属性 createdAt */
|
|
379
387
|
createdAt: number;
|
|
380
|
-
/** [R] 最后更新时间(毫秒时间戳)。SDK 直接属性 updatedAt */
|
|
388
|
+
/** [R] ⊘D2 最后更新时间(毫秒时间戳)。SDK 直接属性 updatedAt */
|
|
381
389
|
updatedAt: number;
|
|
382
|
-
/** [R-F] 本地最后更新时间(毫秒时间戳)。SDK 直接属性 localUpdatedAt。与 updatedAt 重叠 */
|
|
390
|
+
/** [R-F] ⊘D2 本地最后更新时间(毫秒时间戳)。SDK 直接属性 localUpdatedAt。与 updatedAt 重叠 */
|
|
383
391
|
localUpdatedAt: number;
|
|
384
|
-
/** [R-F] 上次练习时间(毫秒时间戳)。SDK: getLastPracticed()。间隔重复内部数据 */
|
|
392
|
+
/** [R-F] ⊘D2 上次练习时间(毫秒时间戳)。SDK: getLastPracticed()。间隔重复内部数据 */
|
|
385
393
|
lastPracticed: number;
|
|
386
394
|
}
|
|
387
395
|
|
|
@@ -65,6 +65,8 @@ export interface SerializableRem {
|
|
|
65
65
|
isTodo: boolean;
|
|
66
66
|
todoStatus: 'Finished' | 'Unfinished' | null;
|
|
67
67
|
isCode: boolean;
|
|
68
|
+
isQuote: boolean;
|
|
69
|
+
isListItem: boolean;
|
|
68
70
|
isDivider: boolean;
|
|
69
71
|
/** 是否为知识库顶级 Rem(无父节点) */
|
|
70
72
|
isTopLevel?: boolean;
|
|
@@ -131,6 +133,12 @@ function buildLineContent(rem: SerializableRem): string {
|
|
|
131
133
|
// Code 包裹(最内层)
|
|
132
134
|
if (rem.isCode) baseContent = '`' + baseContent + '`';
|
|
133
135
|
|
|
136
|
+
// ListItem 前缀(有序列表)
|
|
137
|
+
if (rem.isListItem) baseContent = '1. ' + baseContent;
|
|
138
|
+
|
|
139
|
+
// Quote 前缀(引用块)
|
|
140
|
+
if (rem.isQuote) baseContent = '> ' + baseContent;
|
|
141
|
+
|
|
134
142
|
// Todo 前缀
|
|
135
143
|
if (rem.isTodo) {
|
|
136
144
|
const cb = rem.todoStatus === 'Finished' ? '- [x] ' : '- [ ] ';
|
|
@@ -207,6 +215,8 @@ export function createMinimalSerializableRem(
|
|
|
207
215
|
isTodo: false,
|
|
208
216
|
todoStatus: null,
|
|
209
217
|
isCode: false,
|
|
218
|
+
isQuote: false,
|
|
219
|
+
isListItem: false,
|
|
210
220
|
isDivider: false,
|
|
211
221
|
...overrides,
|
|
212
222
|
};
|
|
@@ -196,7 +196,7 @@ Portal 的编辑同步意味着修改一处会影响另一处。Portal 引用的
|
|
|
196
196
|
|
|
197
197
|
#### 序列化确定性
|
|
198
198
|
|
|
199
|
-
RichText 对象内部按 **key 字母序排列**(`sortRichTextKeys()`),确保 JSON 始终一致。`_id` 中的 `_`(U+005F)排在所有小写字母(`a`=U+0061)之前。防线 2
|
|
199
|
+
RichText 对象内部按 **key 字母序排列**(`sortRichTextKeys()`),确保 JSON 始终一致。`_id` 中的 `_`(U+005F)排在所有小写字母(`a`=U+0061)之前。防线 2(语义并发检测)依赖此确定性序列化来逐字段比较缓存与最新数据。
|
|
200
200
|
|
|
201
201
|
### Powerup 机制与噪音过滤
|
|
202
202
|
|
|
@@ -232,12 +232,12 @@ RemNote 格式设置(fontSize、highlightColor 等)底层通过 Powerup 机
|
|
|
232
232
|
| 场景 | 命令 | 特点 |
|
|
233
233
|
|:-----|:-----|:-----|
|
|
234
234
|
| 知识库有什么 | `read-globe` | 仅 Document 层级,**无缓存**,最小序列化(无 backText/箭头) |
|
|
235
|
-
|
|
|
236
|
-
|
|
|
235
|
+
| 当前页面内容 | `read-context` | 均匀展开(默认,推荐——只需有打开页面即可),**无缓存** |
|
|
236
|
+
| 我光标在哪 / 正在编辑的 Rem | `read-context --mode focus` | 鱼眼视图(需光标在某 Rem 上,否则报错),**无缓存** |
|
|
237
237
|
| 展开某主题细节 | `read-tree <id>` | 完整子树,**有缓存**供 edit-tree |
|
|
238
238
|
| 展开子树 + 每个节点属性 | `read-rem-in-tree <id>` | 大纲 + RemObject,**双重缓存**供 edit-tree 和 edit-rem |
|
|
239
239
|
|
|
240
|
-
**重要**:read-globe、read-context、search 都**不写入缓存**,不能替代 read-tree/read-rem/read-rem-in-tree 作为 edit 的前置条件。read-context 需要用户在 RemNote
|
|
240
|
+
**重要**:read-globe、read-context、search 都**不写入缓存**,不能替代 read-tree/read-rem/read-rem-in-tree 作为 edit 的前置条件。read-context 需要用户在 RemNote 中有打开页面(page 模式,默认)或有焦点(focus 模式)。
|
|
241
241
|
|
|
242
242
|
### 修改:用户想改什么?
|
|
243
243
|
|
|
@@ -409,9 +409,14 @@ search 调用 RemNote SDK 官方搜索方法,其分词基于空格分割。**
|
|
|
409
409
|
|
|
410
410
|
必须先 read 再 edit。未缓存的 Rem 不允许编辑。
|
|
411
411
|
|
|
412
|
-
### 防线 2
|
|
412
|
+
### 防线 2:语义并发检测(三层字段分类)
|
|
413
413
|
|
|
414
|
-
edit 时从 SDK
|
|
414
|
+
edit 时从 SDK 重新读取最新数据,逐字段与缓存比较并按三层分类处理:
|
|
415
|
+
- **语义字段**(text/type/tags/highlightColor 等)变化 → **硬拒绝**,不更新缓存,迫使 Agent 重新 read
|
|
416
|
+
- **敏感元数据**(parent)变化 → **放行** + warnings 返回 `"⚠️ parent has changed (was: X, now: Y)..."`,静默刷新缓存
|
|
417
|
+
- **普通元数据**(positionAmongstSiblings/updatedAt/siblingRem 等)变化 → **放行** + warnings 返回 `"ℹ️ Metadata fields changed since last read: ..."`,静默刷新缓存
|
|
418
|
+
|
|
419
|
+
这意味着 edit-tree 移动/重排 Rem 后,可直接 edit-rem 修改受影响节点,无需重新 read。
|
|
415
420
|
|
|
416
421
|
### 防线 3:str_replace 精确匹配(仅 edit-tree)
|
|
417
422
|
|
|
@@ -424,7 +429,9 @@ edit-rem 使用字段直接修改(changes 对象),不经过 str_replace,
|
|
|
424
429
|
| 场景 | 缓存行为 | Agent 操作 |
|
|
425
430
|
|:-----|:---------|:-----------|
|
|
426
431
|
| 写入成功 | 从 SDK 重新读取 → 更新缓存 | 可继续编辑 |
|
|
427
|
-
|
|
|
432
|
+
| 仅元数据变化(位置/时间戳等) | 静默刷新缓存并放行 | 可继续编辑(返回 warnings) |
|
|
433
|
+
| 语义字段冲突(text/type 等) | **不更新缓存** | 必须重新 read |
|
|
434
|
+
| 部分写入失败 | **不更新缓存** | 必须重新 read |
|
|
428
435
|
| 枚举值非法(edit-rem) | 缓存保持不变 | 检查允许的值范围后重试 |
|
|
429
436
|
| 防线 3 拒绝 / JSON 语法错误(edit-tree) | 缓存保持不变 | 调整 oldStr/newStr 后**直接重试** |
|
|
430
437
|
| 操作执行中异常(edit-tree) | 已执行的操作保留(**无回滚**),不更新缓存 | 必须重新 read-tree |
|
|
@@ -451,6 +458,8 @@ read-tree / read-globe / read-context 输出 Markdown 大纲,edit-tree 基于
|
|
|
451
458
|
|:-----|:-----|
|
|
452
459
|
| `# ` / `## ` / `### ` | H1/H2/H3 标题 |
|
|
453
460
|
| `- [ ] ` / `- [x] ` | 未完成/已完成待办 |
|
|
461
|
+
| `> ` | 引用块 |
|
|
462
|
+
| `1. ` | 有序列表项 |
|
|
454
463
|
| `` `...` `` | 代码块 |
|
|
455
464
|
| `---` | 分隔线 |
|
|
456
465
|
|
|
@@ -521,6 +530,8 @@ read-tree / read-globe / read-context 输出 Markdown 大纲,edit-tree 基于
|
|
|
521
530
|
新闪卡 → 答案
|
|
522
531
|
问题 ↔ 回答
|
|
523
532
|
- [ ] 新待办
|
|
533
|
+
> 引用内容
|
|
534
|
+
1. 列表项
|
|
524
535
|
`代码块`
|
|
525
536
|
```
|
|
526
537
|
|
|
@@ -650,7 +661,7 @@ aliases, descendants, siblingRem, isTable, portalType, propertyType
|
|
|
650
661
|
| 守护进程未运行 | 未 connect 或已超时 | `connect` |
|
|
651
662
|
| Plugin 未连接 | RemNote 未打开 | 打开 RemNote(health 三层:daemon→Plugin→SDK 链式依赖) |
|
|
652
663
|
| has not been read yet | 未先 read | 执行对应 read 命令(search 结果不算 read!) |
|
|
653
|
-
| has been modified since last read |
|
|
664
|
+
| has been modified since last read | 语义字段被外部修改(元数据变化不触发此错误,只返回 warnings) | 重新 read(必须,不可直接重试) |
|
|
654
665
|
| Invalid value for 'field' | 枚举字段值不合法(edit-rem) | 检查该字段允许的值范围 |
|
|
655
666
|
| old_str not found | oldStr 不精确(edit-tree) | 检查引号、空格、换行 |
|
|
656
667
|
| old_str matches N locations | oldStr 不够具体(edit-tree) | 扩大 oldStr 范围,包含更多上下文 |
|
|
@@ -225,28 +225,55 @@ if cache.get('rem:' + remId) === null:
|
|
|
225
225
|
|
|
226
226
|
**恢复方式**:执行 `read-rem <remId>` 后重试。
|
|
227
227
|
|
|
228
|
-
### 防线 2
|
|
228
|
+
### 防线 2:语义并发检测(三层字段分类)
|
|
229
229
|
|
|
230
230
|
**目的**:检测自上次 read 以来,Rem 是否被外部修改(如用户在 RemNote UI 中编辑、其他 Agent 修改等)。
|
|
231
231
|
|
|
232
|
+
**核心机制**:将 RemObject 所有字段逐个比较,按差异字段所属层级决定处理方式:
|
|
233
|
+
|
|
232
234
|
```
|
|
233
235
|
currentRemObject = forwardToPlugin('read_rem', { remId })
|
|
234
|
-
|
|
235
|
-
|
|
236
|
+
{ semantic, warned, ignored } = classifyDiff(cachedObj, currentRemObject)
|
|
237
|
+
|
|
238
|
+
if semantic.length > 0:
|
|
239
|
+
// 第1层:语义字段冲突 → 硬拒绝,不更新缓存
|
|
240
|
+
throw "Rem {remId} has been modified since last read."
|
|
241
|
+
|
|
242
|
+
if warned.length > 0:
|
|
243
|
+
// 第2层:parent 变化 → 放行 + 专门警告
|
|
244
|
+
warnings.push("⚠️ parent has changed (was: {old}, now: {new}). ...")
|
|
236
245
|
|
|
237
|
-
if
|
|
238
|
-
//
|
|
239
|
-
|
|
246
|
+
if ignored.length > 0:
|
|
247
|
+
// 第3层:普通元数据变化 → 放行 + 统一警告
|
|
248
|
+
warnings.push("ℹ️ Metadata fields changed since last read: {fields}. ...")
|
|
249
|
+
|
|
250
|
+
if warned.length > 0 || ignored.length > 0:
|
|
251
|
+
// 静默刷新缓存(保持新鲜度)
|
|
252
|
+
cache.set('rem:' + remId, currentRemObject)
|
|
240
253
|
```
|
|
241
254
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
255
|
+
**三层字段分类**:
|
|
256
|
+
|
|
257
|
+
| 层级 | 包含字段 | 变化时行为 |
|
|
258
|
+
|:-----|:---------|:----------|
|
|
259
|
+
| 第1层:语义字段 | text, backText, type, tags, highlightColor, fontSize 等(不在下面两层的所有字段) | **硬拒绝**,不更新缓存 |
|
|
260
|
+
| 第2层:敏感元数据 | parent | **放行** + `⚠️ parent has changed (was: X, now: Y)...` 警告 + 刷新缓存 |
|
|
261
|
+
| 第3层:普通元数据 | positionAmongstSiblings, updatedAt, siblingRem, children, descendants 等 23 个字段 | **放行** + `ℹ️ Metadata fields changed since last read: ...` 警告 + 刷新缓存 |
|
|
246
262
|
|
|
247
|
-
|
|
263
|
+
**设计意义**:`edit_tree` 移动/重排 Rem 后,可以直接 `edit_rem` 修改受影响节点的文本格式,无需逐个重新 `read_rem`。只有真正的内容/属性并发冲突才会被拦截。
|
|
248
264
|
|
|
249
|
-
|
|
265
|
+
**warnings 输出示例**:
|
|
266
|
+
|
|
267
|
+
| 场景 | warnings |
|
|
268
|
+
|:-----|:---------|
|
|
269
|
+
| 无外部变化 | `[]` |
|
|
270
|
+
| edit_tree 重排后 | `["ℹ️ Metadata fields changed since last read: positionAmongstSiblings. This is expected after structural operations. Proceeding with edit."]` |
|
|
271
|
+
| edit_tree 移动后 | `["⚠️ parent has changed (was: oldId, now: newId). The Rem has been moved to a different parent since last read. Proceeding with edit.", "ℹ️ Metadata fields changed since last read: siblingRem, positionAmongstSiblings, updatedAt. ..."]` |
|
|
272
|
+
| 语义字段被外部修改 | 不返回(抛异常 `"has been modified since last read"`) |
|
|
273
|
+
|
|
274
|
+
**恢复方式**(仅语义冲突需要):执行 `read-rem <remId>` 获取最新状态后重试。
|
|
275
|
+
|
|
276
|
+
### 防线判断树
|
|
250
277
|
|
|
251
278
|
```
|
|
252
279
|
edit-rem(remId, changes)
|
|
@@ -255,9 +282,11 @@ edit-rem(remId, changes)
|
|
|
255
282
|
│ ├─ 否 → ERROR: "has not been read yet"
|
|
256
283
|
│ └─ 是 → 继续
|
|
257
284
|
│
|
|
258
|
-
├─ 防线 2:
|
|
259
|
-
│ ├─
|
|
260
|
-
│
|
|
285
|
+
├─ 防线 2: 三层字段分类比较
|
|
286
|
+
│ ├─ 语义字段变化 → ERROR: "has been modified since last read"
|
|
287
|
+
│ ├─ 仅 parent 变化 → 放行(warnings += ⚠️ parent 警告,刷新缓存)
|
|
288
|
+
│ ├─ 仅元数据变化 → 放行(warnings += ℹ️ 元数据警告,刷新缓存)
|
|
289
|
+
│ └─ 无变化 → 继续
|
|
261
290
|
│
|
|
262
291
|
├─ 字段分类
|
|
263
292
|
│ ├─ 只读字段 → 警告
|
|
@@ -251,10 +251,20 @@ newStr: " 新增节点\n 子节点 A <!--idA-->"
|
|
|
251
251
|
| `### text` | 创建 H3 标题 |
|
|
252
252
|
| `- [ ] text` | 创建未完成待办 |
|
|
253
253
|
| `- [x] text` | 创建已完成待办 |
|
|
254
|
+
| `1. text` | 创建有序列表项 |
|
|
254
255
|
| `` `text` `` | 创建代码块 |
|
|
255
256
|
| `---` | 创建分隔线 |
|
|
256
257
|
|
|
257
|
-
前缀可组合叠加,解析顺序为 Header → Todo → Code。例如 `## - [ ] text` 创建 H2 + 未完成待办。
|
|
258
|
+
前缀可组合叠加,解析顺序为 Header → Todo → Quote → ListItem → Code。例如 `## - [ ] text` 创建 H2 + 未完成待办。
|
|
259
|
+
|
|
260
|
+
> **⚠️ 有序列表必须用 `1. ` 前缀**
|
|
261
|
+
>
|
|
262
|
+
> RemNote 的有序列表采用 Lazy Numbering 风格——所有列表项统一写 `1. `,RemNote 按层级自动编号为 1./2./3./A./B./I./II. 等。
|
|
263
|
+
>
|
|
264
|
+
> - **正确**:`1. 第一项`、`1. 第二项`、`1. 第三项`(全部用 `1. `)
|
|
265
|
+
> - **错误**:`2. 第二项`、`3. 第三项`(手动编号无意义,RemNote 会忽略)
|
|
266
|
+
>
|
|
267
|
+
> 系统会容错处理 `2. `~`9. ` 前缀(自动归一化为 `isListItem=true` 并返回 `templateWarnings` 警告),但 `10. ` 及以上不会被识别为有序列表,而是作为纯文本保留。
|
|
258
268
|
|
|
259
269
|
#### 新增行的箭头分隔符
|
|
260
270
|
|