affine-mcp-server 1.7.0 → 1.7.2
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 +14 -2
- package/dist/tools/docs.js +367 -86
- package/dist/tools/workspaces.js +3 -3
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A Model Context Protocol (MCP) server that integrates with AFFiNE (self‑hosted or cloud). It exposes AFFiNE workspaces and documents to AI assistants over stdio (default) or HTTP (`/mcp`).
|
|
4
4
|
|
|
5
|
-
[](https://github.com/dawncr0w/affine-mcp-server/releases)
|
|
6
6
|
[](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
7
7
|
[](https://github.com/dawncr0w/affine-mcp-server/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
@@ -19,7 +19,7 @@ A Model Context Protocol (MCP) server that integrates with AFFiNE (self‑hosted
|
|
|
19
19
|
- Tools: 43 focused tools with WebSocket-based document editing
|
|
20
20
|
- Status: Active
|
|
21
21
|
|
|
22
|
-
> New in v1.7.
|
|
22
|
+
> New in v1.7.2: Fixed tag visibility parity in AFFiNE Web/App for MCP-created tags and hardened Docker E2E startup reliability with retry/diagnostics.
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
@@ -409,6 +409,18 @@ Workspace visibility
|
|
|
409
409
|
|
|
410
410
|
## Version History
|
|
411
411
|
|
|
412
|
+
### 1.7.2 (2026‑03‑04)
|
|
413
|
+
- Fixed MCP tag persistence to use AFFiNE canonical tag option IDs so tags are visible in Web/App UI
|
|
414
|
+
- Added backward-compatible tag normalization for legacy string tag entries
|
|
415
|
+
- Added tag visibility regression coverage (`tests/test-tag-visibility.mjs`, `tests/playwright/verify-tag-visibility.pw.ts`)
|
|
416
|
+
- Hardened E2E credential bootstrap with configurable health retries, retry attempts, and Docker diagnostics on failure
|
|
417
|
+
- Verified CI gates (`validate`, `e2e`) for PR #46 and local `npm run ci`
|
|
418
|
+
|
|
419
|
+
### 1.7.1 (2026‑03‑03)
|
|
420
|
+
- Fixed MCP-created document structure parity with AFFiNE UI (`sys:parent` handling)
|
|
421
|
+
- Fixed callout text rendering parity in AFFiNE UI for MCP-created blocks
|
|
422
|
+
- Added regression assertions for visibility-sensitive document creation paths
|
|
423
|
+
|
|
412
424
|
### 1.7.0 (2026‑02‑27)
|
|
413
425
|
- Added Streamable HTTP MCP support on `/mcp` for remote hosting while keeping legacy SSE compatibility paths (`/sse`, `/messages`)
|
|
414
426
|
- Added HTTP deployment controls: `AFFINE_MCP_HTTP_HOST`, `AFFINE_MCP_HTTP_TOKEN`, `AFFINE_MCP_HTTP_ALLOWED_ORIGINS`, `AFFINE_MCP_HTTP_ALLOW_ALL_ORIGINS`
|
package/dist/tools/docs.js
CHANGED
|
@@ -118,6 +118,11 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
118
118
|
}
|
|
119
119
|
return normalized;
|
|
120
120
|
}
|
|
121
|
+
const TAG_OPTION_COLORS = [
|
|
122
|
+
"var(--affine-tag-blue)", "var(--affine-tag-green)", "var(--affine-tag-red)",
|
|
123
|
+
"var(--affine-tag-orange)", "var(--affine-tag-purple)", "var(--affine-tag-yellow)",
|
|
124
|
+
"var(--affine-tag-teal)", "var(--affine-tag-pink)", "var(--affine-tag-gray)",
|
|
125
|
+
];
|
|
121
126
|
function getStringArray(value) {
|
|
122
127
|
if (!(value instanceof Y.Array)) {
|
|
123
128
|
return [];
|
|
@@ -146,23 +151,235 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
146
151
|
target.set(key, next);
|
|
147
152
|
return next;
|
|
148
153
|
}
|
|
149
|
-
function
|
|
150
|
-
const
|
|
151
|
-
|
|
154
|
+
function getYMap(target, key) {
|
|
155
|
+
const value = target.get(key);
|
|
156
|
+
if (!(value instanceof Y.Map)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
152
160
|
}
|
|
153
|
-
function
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
function ensureYMap(target, key) {
|
|
162
|
+
const current = getYMap(target, key);
|
|
163
|
+
if (current) {
|
|
164
|
+
return current;
|
|
165
|
+
}
|
|
166
|
+
const next = new Y.Map();
|
|
167
|
+
target.set(key, next);
|
|
168
|
+
return next;
|
|
169
|
+
}
|
|
170
|
+
function getWorkspaceTagOptionsArray(meta) {
|
|
171
|
+
const properties = getYMap(meta, "properties");
|
|
172
|
+
if (!properties) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const tags = getYMap(properties, "tags");
|
|
176
|
+
if (!tags) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const options = tags.get("options");
|
|
180
|
+
if (!(options instanceof Y.Array)) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return options;
|
|
184
|
+
}
|
|
185
|
+
function ensureWorkspaceTagOptionsArray(meta) {
|
|
186
|
+
const properties = ensureYMap(meta, "properties");
|
|
187
|
+
const tags = ensureYMap(properties, "tags");
|
|
188
|
+
const existing = tags.get("options");
|
|
189
|
+
if (existing instanceof Y.Array) {
|
|
190
|
+
return existing;
|
|
191
|
+
}
|
|
192
|
+
const next = new Y.Array();
|
|
193
|
+
tags.set("options", next);
|
|
194
|
+
return next;
|
|
195
|
+
}
|
|
196
|
+
function asNumberOrNull(value) {
|
|
197
|
+
return typeof value === "number" ? value : null;
|
|
198
|
+
}
|
|
199
|
+
function parseWorkspaceTagOption(raw) {
|
|
200
|
+
let id;
|
|
201
|
+
let value;
|
|
202
|
+
let color;
|
|
203
|
+
let createDate;
|
|
204
|
+
let updateDate;
|
|
205
|
+
if (raw instanceof Y.Map) {
|
|
206
|
+
id = raw.get("id");
|
|
207
|
+
value = raw.get("value");
|
|
208
|
+
color = raw.get("color");
|
|
209
|
+
createDate = raw.get("createDate");
|
|
210
|
+
updateDate = raw.get("updateDate");
|
|
211
|
+
}
|
|
212
|
+
else if (raw && typeof raw === "object") {
|
|
213
|
+
id = raw.id;
|
|
214
|
+
value = raw.value;
|
|
215
|
+
color = raw.color;
|
|
216
|
+
createDate = raw.createDate;
|
|
217
|
+
updateDate = raw.updateDate;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (typeof id !== "string" || id.trim().length === 0) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
id,
|
|
230
|
+
value,
|
|
231
|
+
color: typeof color === "string" && color.trim().length > 0 ? color : TAG_OPTION_COLORS[0],
|
|
232
|
+
createDate: asNumberOrNull(createDate),
|
|
233
|
+
updateDate: asNumberOrNull(updateDate),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function getWorkspaceTagOptions(meta) {
|
|
237
|
+
const options = getWorkspaceTagOptionsArray(meta);
|
|
238
|
+
if (!options) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
const parsed = [];
|
|
242
|
+
options.forEach((raw) => {
|
|
243
|
+
const option = parseWorkspaceTagOption(raw);
|
|
244
|
+
if (option) {
|
|
245
|
+
parsed.push(option);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return parsed;
|
|
249
|
+
}
|
|
250
|
+
function getWorkspaceTagOptionMaps(meta) {
|
|
251
|
+
const options = getWorkspaceTagOptions(meta);
|
|
252
|
+
const byId = new Map();
|
|
253
|
+
const byValueLower = new Map();
|
|
254
|
+
for (const option of options) {
|
|
255
|
+
if (!byId.has(option.id)) {
|
|
256
|
+
byId.set(option.id, option);
|
|
257
|
+
}
|
|
258
|
+
const key = option.value.toLocaleLowerCase();
|
|
259
|
+
if (!byValueLower.has(key)) {
|
|
260
|
+
byValueLower.set(key, option);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return { options, byId, byValueLower };
|
|
264
|
+
}
|
|
265
|
+
function resolveTagLabels(tagEntries, byId) {
|
|
266
|
+
const deduped = new Set();
|
|
267
|
+
const resolved = [];
|
|
268
|
+
for (const entry of tagEntries) {
|
|
269
|
+
const raw = entry.trim();
|
|
270
|
+
if (!raw) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const option = byId.get(raw);
|
|
274
|
+
const label = (option ? option.value : raw).trim();
|
|
275
|
+
if (!label) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const dedupeKey = label.toLocaleLowerCase();
|
|
279
|
+
if (deduped.has(dedupeKey)) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
deduped.add(dedupeKey);
|
|
283
|
+
resolved.push(label);
|
|
284
|
+
}
|
|
285
|
+
return resolved;
|
|
286
|
+
}
|
|
287
|
+
function ensureWorkspaceTagOption(meta, tag) {
|
|
288
|
+
const normalizedTag = normalizeTag(tag);
|
|
289
|
+
const maps = getWorkspaceTagOptionMaps(meta);
|
|
290
|
+
const existing = maps.byValueLower.get(normalizedTag.toLocaleLowerCase());
|
|
291
|
+
if (existing) {
|
|
292
|
+
return { option: existing, created: false };
|
|
293
|
+
}
|
|
294
|
+
const optionsArray = ensureWorkspaceTagOptionsArray(meta);
|
|
295
|
+
const color = TAG_OPTION_COLORS[maps.options.length % TAG_OPTION_COLORS.length];
|
|
296
|
+
const now = Date.now();
|
|
297
|
+
const option = {
|
|
298
|
+
id: generateId(),
|
|
299
|
+
value: normalizedTag,
|
|
300
|
+
color,
|
|
301
|
+
createDate: now,
|
|
302
|
+
updateDate: now,
|
|
303
|
+
};
|
|
304
|
+
const optionMap = new Y.Map();
|
|
305
|
+
optionMap.set("id", option.id);
|
|
306
|
+
optionMap.set("value", option.value);
|
|
307
|
+
optionMap.set("color", option.color);
|
|
308
|
+
optionMap.set("createDate", now);
|
|
309
|
+
optionMap.set("updateDate", now);
|
|
310
|
+
optionsArray.push([optionMap]);
|
|
311
|
+
return { option, created: true };
|
|
312
|
+
}
|
|
313
|
+
function collectMatchingTagIndexes(tags, requestedTag, option, ignoreCase) {
|
|
314
|
+
const normalizedRequested = ignoreCase ? requestedTag.toLocaleLowerCase() : requestedTag;
|
|
315
|
+
const normalizedOptionId = option
|
|
316
|
+
? (ignoreCase ? option.id.toLocaleLowerCase() : option.id)
|
|
317
|
+
: null;
|
|
318
|
+
const normalizedOptionValue = option
|
|
319
|
+
? (ignoreCase ? option.value.toLocaleLowerCase() : option.value)
|
|
320
|
+
: null;
|
|
321
|
+
const indexes = [];
|
|
322
|
+
tags.forEach((entry, index) => {
|
|
323
|
+
if (typeof entry !== "string") {
|
|
158
324
|
return;
|
|
159
325
|
}
|
|
160
326
|
const current = ignoreCase ? entry.toLocaleLowerCase() : entry;
|
|
161
|
-
if (current ===
|
|
162
|
-
|
|
327
|
+
if (current === normalizedRequested ||
|
|
328
|
+
(normalizedOptionId && current === normalizedOptionId) ||
|
|
329
|
+
(normalizedOptionValue && current === normalizedOptionValue)) {
|
|
330
|
+
indexes.push(index);
|
|
163
331
|
}
|
|
164
332
|
});
|
|
165
|
-
return
|
|
333
|
+
return indexes;
|
|
334
|
+
}
|
|
335
|
+
function deleteArrayIndexes(arr, indexes) {
|
|
336
|
+
if (indexes.length === 0) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
const sorted = [...indexes].sort((a, b) => b - a);
|
|
340
|
+
for (const index of sorted) {
|
|
341
|
+
arr.delete(index, 1);
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
function syncTagArrayToOption(tags, requestedTag, option) {
|
|
346
|
+
const optionId = option.id.toLocaleLowerCase();
|
|
347
|
+
const optionValue = option.value.toLocaleLowerCase();
|
|
348
|
+
const requested = requestedTag.toLocaleLowerCase();
|
|
349
|
+
let existed = false;
|
|
350
|
+
let hasCanonicalId = false;
|
|
351
|
+
const removeIndexes = [];
|
|
352
|
+
tags.forEach((entry, index) => {
|
|
353
|
+
if (typeof entry !== "string") {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const current = entry.toLocaleLowerCase();
|
|
357
|
+
const matched = current === optionId || current === optionValue || current === requested;
|
|
358
|
+
if (!matched) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
existed = true;
|
|
362
|
+
if (current === optionId) {
|
|
363
|
+
if (hasCanonicalId) {
|
|
364
|
+
removeIndexes.push(index);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
hasCanonicalId = true;
|
|
368
|
+
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
removeIndexes.push(index);
|
|
372
|
+
});
|
|
373
|
+
let changed = deleteArrayIndexes(tags, removeIndexes);
|
|
374
|
+
if (!hasCanonicalId) {
|
|
375
|
+
tags.push([option.id]);
|
|
376
|
+
changed = true;
|
|
377
|
+
}
|
|
378
|
+
return { existed, changed };
|
|
379
|
+
}
|
|
380
|
+
function hasTag(tagValues, tag, ignoreCase) {
|
|
381
|
+
const normalizedTag = ignoreCase ? tag.toLocaleLowerCase() : tag;
|
|
382
|
+
return tagValues.some((entry) => (ignoreCase ? entry.toLocaleLowerCase() : entry) === normalizedTag);
|
|
166
383
|
}
|
|
167
384
|
function getWorkspacePageEntries(meta) {
|
|
168
385
|
const pages = meta.get("pages");
|
|
@@ -219,7 +436,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
219
436
|
const noteId = generateId();
|
|
220
437
|
const note = new Y.Map();
|
|
221
438
|
setSysFields(note, noteId, "affine:note");
|
|
222
|
-
note.set("sys:parent",
|
|
439
|
+
note.set("sys:parent", null);
|
|
223
440
|
note.set("sys:children", new Y.Array());
|
|
224
441
|
note.set("prop:xywh", "[0,0,800,95]");
|
|
225
442
|
note.set("prop:index", "a0");
|
|
@@ -251,7 +468,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
251
468
|
const surfaceId = generateId();
|
|
252
469
|
const surface = new Y.Map();
|
|
253
470
|
setSysFields(surface, surfaceId, "affine:surface");
|
|
254
|
-
surface.set("sys:parent",
|
|
471
|
+
surface.set("sys:parent", null);
|
|
255
472
|
surface.set("sys:children", new Y.Array());
|
|
256
473
|
const elements = new Y.Map();
|
|
257
474
|
elements.set("type", "$blocksuite:internal:native$");
|
|
@@ -601,6 +818,30 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
601
818
|
});
|
|
602
819
|
return index;
|
|
603
820
|
}
|
|
821
|
+
function findParentIdByChild(blocks, childId) {
|
|
822
|
+
for (const [id, value] of blocks) {
|
|
823
|
+
if (!(value instanceof Y.Map)) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
const childIds = childIdsFrom(value.get("sys:children"));
|
|
827
|
+
if (childIds.includes(childId)) {
|
|
828
|
+
return String(id);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
function resolveBlockParentId(blocks, blockId) {
|
|
834
|
+
const block = findBlockById(blocks, blockId);
|
|
835
|
+
if (!block) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
const rawParentId = block.get("sys:parent");
|
|
839
|
+
if (typeof rawParentId === "string" && rawParentId.trim().length > 0) {
|
|
840
|
+
return rawParentId;
|
|
841
|
+
}
|
|
842
|
+
// AFFiNE UI commonly stores sys:parent as null and derives hierarchy from sys:children.
|
|
843
|
+
return findParentIdByChild(blocks, blockId);
|
|
844
|
+
}
|
|
604
845
|
function resolveInsertContext(blocks, normalized) {
|
|
605
846
|
const placement = normalized.placement;
|
|
606
847
|
let parentId;
|
|
@@ -612,8 +853,8 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
612
853
|
const referenceBlock = findBlockById(blocks, referenceBlockId);
|
|
613
854
|
if (!referenceBlock)
|
|
614
855
|
throw new Error(`placement.afterBlockId '${referenceBlockId}' was not found.`);
|
|
615
|
-
const refParentId =
|
|
616
|
-
if (
|
|
856
|
+
const refParentId = resolveBlockParentId(blocks, referenceBlockId);
|
|
857
|
+
if (!refParentId) {
|
|
617
858
|
throw new Error(`Block '${referenceBlockId}' has no parent.`);
|
|
618
859
|
}
|
|
619
860
|
parentId = refParentId;
|
|
@@ -624,8 +865,8 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
624
865
|
const referenceBlock = findBlockById(blocks, referenceBlockId);
|
|
625
866
|
if (!referenceBlock)
|
|
626
867
|
throw new Error(`placement.beforeBlockId '${referenceBlockId}' was not found.`);
|
|
627
|
-
const refParentId =
|
|
628
|
-
if (
|
|
868
|
+
const refParentId = resolveBlockParentId(blocks, referenceBlockId);
|
|
869
|
+
if (!refParentId) {
|
|
629
870
|
throw new Error(`Block '${referenceBlockId}' has no parent.`);
|
|
630
871
|
}
|
|
631
872
|
parentId = refParentId;
|
|
@@ -688,16 +929,17 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
688
929
|
}
|
|
689
930
|
return { parentId, parentBlock, children, insertIndex };
|
|
690
931
|
}
|
|
691
|
-
function createBlock(
|
|
932
|
+
function createBlock(normalized) {
|
|
692
933
|
const blockId = generateId();
|
|
693
934
|
const block = new Y.Map();
|
|
694
935
|
const content = normalized.text;
|
|
936
|
+
// Keep parity with AFFiNE UI-created docs: sys:parent stays null and hierarchy is represented by sys:children.
|
|
695
937
|
switch (normalized.type) {
|
|
696
938
|
case "paragraph":
|
|
697
939
|
case "heading":
|
|
698
940
|
case "quote": {
|
|
699
941
|
setSysFields(block, blockId, "affine:paragraph");
|
|
700
|
-
block.set("sys:parent",
|
|
942
|
+
block.set("sys:parent", null);
|
|
701
943
|
block.set("sys:children", new Y.Array());
|
|
702
944
|
const blockType = normalized.type === "heading"
|
|
703
945
|
? `h${normalized.headingLevel}`
|
|
@@ -710,7 +952,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
710
952
|
}
|
|
711
953
|
case "list": {
|
|
712
954
|
setSysFields(block, blockId, "affine:list");
|
|
713
|
-
block.set("sys:parent",
|
|
955
|
+
block.set("sys:parent", null);
|
|
714
956
|
block.set("sys:children", new Y.Array());
|
|
715
957
|
block.set("prop:type", normalized.listStyle);
|
|
716
958
|
block.set("prop:checked", normalized.listStyle === "todo" ? normalized.checked : false);
|
|
@@ -719,7 +961,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
719
961
|
}
|
|
720
962
|
case "code": {
|
|
721
963
|
setSysFields(block, blockId, "affine:code");
|
|
722
|
-
block.set("sys:parent",
|
|
964
|
+
block.set("sys:parent", null);
|
|
723
965
|
block.set("sys:children", new Y.Array());
|
|
724
966
|
block.set("prop:language", normalized.language);
|
|
725
967
|
if (normalized.caption) {
|
|
@@ -730,22 +972,35 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
730
972
|
}
|
|
731
973
|
case "divider": {
|
|
732
974
|
setSysFields(block, blockId, "affine:divider");
|
|
733
|
-
block.set("sys:parent",
|
|
975
|
+
block.set("sys:parent", null);
|
|
734
976
|
block.set("sys:children", new Y.Array());
|
|
735
977
|
return { blockId, block, flavour: "affine:divider" };
|
|
736
978
|
}
|
|
737
979
|
case "callout": {
|
|
738
980
|
setSysFields(block, blockId, "affine:callout");
|
|
739
|
-
block.set("sys:parent",
|
|
740
|
-
|
|
981
|
+
block.set("sys:parent", null);
|
|
982
|
+
const calloutChildren = new Y.Array();
|
|
983
|
+
const textBlockId = generateId();
|
|
984
|
+
const textBlock = new Y.Map();
|
|
985
|
+
setSysFields(textBlock, textBlockId, "affine:paragraph");
|
|
986
|
+
textBlock.set("sys:parent", null);
|
|
987
|
+
textBlock.set("sys:children", new Y.Array());
|
|
988
|
+
textBlock.set("prop:type", "text");
|
|
989
|
+
textBlock.set("prop:text", makeText(content));
|
|
990
|
+
calloutChildren.push([textBlockId]);
|
|
991
|
+
block.set("sys:children", calloutChildren);
|
|
741
992
|
block.set("prop:icon", { type: "emoji", unicode: "💡" });
|
|
742
993
|
block.set("prop:backgroundColorName", "grey");
|
|
743
|
-
|
|
744
|
-
|
|
994
|
+
return {
|
|
995
|
+
blockId,
|
|
996
|
+
block,
|
|
997
|
+
flavour: "affine:callout",
|
|
998
|
+
extraBlocks: [{ blockId: textBlockId, block: textBlock }],
|
|
999
|
+
};
|
|
745
1000
|
}
|
|
746
1001
|
case "latex": {
|
|
747
1002
|
setSysFields(block, blockId, "affine:latex");
|
|
748
|
-
block.set("sys:parent",
|
|
1003
|
+
block.set("sys:parent", null);
|
|
749
1004
|
block.set("sys:children", new Y.Array());
|
|
750
1005
|
block.set("prop:xywh", "[0,0,16,16]");
|
|
751
1006
|
block.set("prop:index", "a0");
|
|
@@ -757,7 +1012,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
757
1012
|
}
|
|
758
1013
|
case "table": {
|
|
759
1014
|
setSysFields(block, blockId, "affine:table");
|
|
760
|
-
block.set("sys:parent",
|
|
1015
|
+
block.set("sys:parent", null);
|
|
761
1016
|
block.set("sys:children", new Y.Array());
|
|
762
1017
|
const rows = {};
|
|
763
1018
|
const columns = {};
|
|
@@ -792,7 +1047,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
792
1047
|
}
|
|
793
1048
|
case "bookmark": {
|
|
794
1049
|
setSysFields(block, blockId, "affine:bookmark");
|
|
795
|
-
block.set("sys:parent",
|
|
1050
|
+
block.set("sys:parent", null);
|
|
796
1051
|
block.set("sys:children", new Y.Array());
|
|
797
1052
|
block.set("prop:style", normalized.bookmarkStyle);
|
|
798
1053
|
block.set("prop:url", normalized.url);
|
|
@@ -810,7 +1065,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
810
1065
|
}
|
|
811
1066
|
case "image": {
|
|
812
1067
|
setSysFields(block, blockId, "affine:image");
|
|
813
|
-
block.set("sys:parent",
|
|
1068
|
+
block.set("sys:parent", null);
|
|
814
1069
|
block.set("sys:children", new Y.Array());
|
|
815
1070
|
block.set("prop:caption", normalized.caption ?? "");
|
|
816
1071
|
block.set("prop:sourceId", normalized.sourceId);
|
|
@@ -825,7 +1080,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
825
1080
|
}
|
|
826
1081
|
case "attachment": {
|
|
827
1082
|
setSysFields(block, blockId, "affine:attachment");
|
|
828
|
-
block.set("sys:parent",
|
|
1083
|
+
block.set("sys:parent", null);
|
|
829
1084
|
block.set("sys:children", new Y.Array());
|
|
830
1085
|
block.set("prop:name", normalized.name);
|
|
831
1086
|
block.set("prop:size", normalized.size);
|
|
@@ -843,7 +1098,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
843
1098
|
}
|
|
844
1099
|
case "embed_youtube": {
|
|
845
1100
|
setSysFields(block, blockId, "affine:embed-youtube");
|
|
846
|
-
block.set("sys:parent",
|
|
1101
|
+
block.set("sys:parent", null);
|
|
847
1102
|
block.set("sys:children", new Y.Array());
|
|
848
1103
|
block.set("prop:index", "a0");
|
|
849
1104
|
block.set("prop:xywh", "[0,0,0,0]");
|
|
@@ -863,7 +1118,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
863
1118
|
}
|
|
864
1119
|
case "embed_github": {
|
|
865
1120
|
setSysFields(block, blockId, "affine:embed-github");
|
|
866
|
-
block.set("sys:parent",
|
|
1121
|
+
block.set("sys:parent", null);
|
|
867
1122
|
block.set("sys:children", new Y.Array());
|
|
868
1123
|
block.set("prop:index", "a0");
|
|
869
1124
|
block.set("prop:xywh", "[0,0,0,0]");
|
|
@@ -887,7 +1142,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
887
1142
|
}
|
|
888
1143
|
case "embed_figma": {
|
|
889
1144
|
setSysFields(block, blockId, "affine:embed-figma");
|
|
890
|
-
block.set("sys:parent",
|
|
1145
|
+
block.set("sys:parent", null);
|
|
891
1146
|
block.set("sys:children", new Y.Array());
|
|
892
1147
|
block.set("prop:index", "a0");
|
|
893
1148
|
block.set("prop:xywh", "[0,0,0,0]");
|
|
@@ -902,7 +1157,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
902
1157
|
}
|
|
903
1158
|
case "embed_loom": {
|
|
904
1159
|
setSysFields(block, blockId, "affine:embed-loom");
|
|
905
|
-
block.set("sys:parent",
|
|
1160
|
+
block.set("sys:parent", null);
|
|
906
1161
|
block.set("sys:children", new Y.Array());
|
|
907
1162
|
block.set("prop:index", "a0");
|
|
908
1163
|
block.set("prop:xywh", "[0,0,0,0]");
|
|
@@ -919,7 +1174,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
919
1174
|
}
|
|
920
1175
|
case "embed_html": {
|
|
921
1176
|
setSysFields(block, blockId, "affine:embed-html");
|
|
922
|
-
block.set("sys:parent",
|
|
1177
|
+
block.set("sys:parent", null);
|
|
923
1178
|
block.set("sys:children", new Y.Array());
|
|
924
1179
|
block.set("prop:index", "a0");
|
|
925
1180
|
block.set("prop:xywh", "[0,0,0,0]");
|
|
@@ -933,7 +1188,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
933
1188
|
}
|
|
934
1189
|
case "embed_linked_doc": {
|
|
935
1190
|
setSysFields(block, blockId, "affine:embed-linked-doc");
|
|
936
|
-
block.set("sys:parent",
|
|
1191
|
+
block.set("sys:parent", null);
|
|
937
1192
|
block.set("sys:children", new Y.Array());
|
|
938
1193
|
block.set("prop:index", "a0");
|
|
939
1194
|
block.set("prop:xywh", "[0,0,0,0]");
|
|
@@ -949,7 +1204,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
949
1204
|
}
|
|
950
1205
|
case "embed_synced_doc": {
|
|
951
1206
|
setSysFields(block, blockId, "affine:embed-synced-doc");
|
|
952
|
-
block.set("sys:parent",
|
|
1207
|
+
block.set("sys:parent", null);
|
|
953
1208
|
block.set("sys:children", new Y.Array());
|
|
954
1209
|
block.set("prop:index", "a0");
|
|
955
1210
|
block.set("prop:xywh", "[0,0,800,100]");
|
|
@@ -966,7 +1221,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
966
1221
|
}
|
|
967
1222
|
case "embed_iframe": {
|
|
968
1223
|
setSysFields(block, blockId, "affine:embed-iframe");
|
|
969
|
-
block.set("sys:parent",
|
|
1224
|
+
block.set("sys:parent", null);
|
|
970
1225
|
block.set("sys:children", new Y.Array());
|
|
971
1226
|
block.set("prop:index", "a0");
|
|
972
1227
|
block.set("prop:xywh", "[0,0,0,0]");
|
|
@@ -983,7 +1238,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
983
1238
|
}
|
|
984
1239
|
case "database": {
|
|
985
1240
|
setSysFields(block, blockId, "affine:database");
|
|
986
|
-
block.set("sys:parent",
|
|
1241
|
+
block.set("sys:parent", null);
|
|
987
1242
|
block.set("sys:children", new Y.Array());
|
|
988
1243
|
// Create a default table view so AFFiNE UI renders the database
|
|
989
1244
|
const defaultView = new Y.Map();
|
|
@@ -1008,7 +1263,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1008
1263
|
// AFFiNE 0.26.x currently crashes on raw affine:data-view render path.
|
|
1009
1264
|
// Keep API compatibility for type="data_view" by mapping it to the stable database block.
|
|
1010
1265
|
setSysFields(block, blockId, "affine:database");
|
|
1011
|
-
block.set("sys:parent",
|
|
1266
|
+
block.set("sys:parent", null);
|
|
1012
1267
|
block.set("sys:children", new Y.Array());
|
|
1013
1268
|
const dvDefaultView = new Y.Map();
|
|
1014
1269
|
dvDefaultView.set("id", generateId());
|
|
@@ -1030,7 +1285,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1030
1285
|
}
|
|
1031
1286
|
case "surface_ref": {
|
|
1032
1287
|
setSysFields(block, blockId, "affine:surface-ref");
|
|
1033
|
-
block.set("sys:parent",
|
|
1288
|
+
block.set("sys:parent", null);
|
|
1034
1289
|
block.set("sys:children", new Y.Array());
|
|
1035
1290
|
block.set("prop:reference", normalized.reference);
|
|
1036
1291
|
block.set("prop:caption", normalized.caption ?? "");
|
|
@@ -1040,7 +1295,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1040
1295
|
}
|
|
1041
1296
|
case "frame": {
|
|
1042
1297
|
setSysFields(block, blockId, "affine:frame");
|
|
1043
|
-
block.set("sys:parent",
|
|
1298
|
+
block.set("sys:parent", null);
|
|
1044
1299
|
block.set("sys:children", new Y.Array());
|
|
1045
1300
|
block.set("prop:title", makeText(content || "Frame"));
|
|
1046
1301
|
block.set("prop:background", normalized.background);
|
|
@@ -1053,7 +1308,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1053
1308
|
}
|
|
1054
1309
|
case "edgeless_text": {
|
|
1055
1310
|
setSysFields(block, blockId, "affine:edgeless-text");
|
|
1056
|
-
block.set("sys:parent",
|
|
1311
|
+
block.set("sys:parent", null);
|
|
1057
1312
|
block.set("sys:children", new Y.Array());
|
|
1058
1313
|
block.set("prop:xywh", `[0,0,${normalized.width},${normalized.height}]`);
|
|
1059
1314
|
block.set("prop:index", "a0");
|
|
@@ -1071,7 +1326,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1071
1326
|
}
|
|
1072
1327
|
case "note": {
|
|
1073
1328
|
setSysFields(block, blockId, "affine:note");
|
|
1074
|
-
block.set("sys:parent",
|
|
1329
|
+
block.set("sys:parent", null);
|
|
1075
1330
|
block.set("sys:children", new Y.Array());
|
|
1076
1331
|
block.set("prop:xywh", `[0,0,${normalized.width},${normalized.height}]`);
|
|
1077
1332
|
block.set("prop:background", normalized.background);
|
|
@@ -1110,8 +1365,13 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1110
1365
|
const prevSV = Y.encodeStateVector(doc);
|
|
1111
1366
|
const blocks = doc.getMap("blocks");
|
|
1112
1367
|
const context = resolveInsertContext(blocks, normalized);
|
|
1113
|
-
const { blockId, block, flavour, blockType } = createBlock(
|
|
1368
|
+
const { blockId, block, flavour, blockType, extraBlocks } = createBlock(normalized);
|
|
1114
1369
|
blocks.set(blockId, block);
|
|
1370
|
+
if (Array.isArray(extraBlocks)) {
|
|
1371
|
+
for (const extra of extraBlocks) {
|
|
1372
|
+
blocks.set(extra.blockId, extra.block);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1115
1375
|
if (context.insertIndex >= context.children.length) {
|
|
1116
1376
|
context.children.push([blockId]);
|
|
1117
1377
|
}
|
|
@@ -1330,9 +1590,9 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1330
1590
|
}
|
|
1331
1591
|
return tableData;
|
|
1332
1592
|
}
|
|
1333
|
-
function collectDocForMarkdown(doc) {
|
|
1593
|
+
function collectDocForMarkdown(doc, tagOptionsById = new Map()) {
|
|
1334
1594
|
const meta = doc.getMap("meta");
|
|
1335
|
-
const tags = getStringArray(getTagArray(meta));
|
|
1595
|
+
const tags = resolveTagLabels(getStringArray(getTagArray(meta)), tagOptionsById);
|
|
1336
1596
|
const blocks = doc.getMap("blocks");
|
|
1337
1597
|
const pageId = findBlockIdByFlavour(blocks, "affine:page");
|
|
1338
1598
|
const noteId = findBlockIdByFlavour(blocks, "affine:note");
|
|
@@ -1444,8 +1704,13 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1444
1704
|
try {
|
|
1445
1705
|
const normalized = normalizeAppendBlockInput(appendInput);
|
|
1446
1706
|
const context = resolveInsertContext(blocks, normalized);
|
|
1447
|
-
const { blockId, block } = createBlock(
|
|
1707
|
+
const { blockId, block, extraBlocks } = createBlock(normalized);
|
|
1448
1708
|
blocks.set(blockId, block);
|
|
1709
|
+
if (Array.isArray(extraBlocks)) {
|
|
1710
|
+
for (const extra of extraBlocks) {
|
|
1711
|
+
blocks.set(extra.blockId, extra.block);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1449
1714
|
if (context.insertIndex >= context.children.length) {
|
|
1450
1715
|
context.children.push([blockId]);
|
|
1451
1716
|
}
|
|
@@ -1499,7 +1764,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1499
1764
|
const surfaceId = generateId();
|
|
1500
1765
|
const surface = new Y.Map();
|
|
1501
1766
|
setSysFields(surface, surfaceId, "affine:surface");
|
|
1502
|
-
surface.set("sys:parent",
|
|
1767
|
+
surface.set("sys:parent", null);
|
|
1503
1768
|
surface.set("sys:children", new Y.Array());
|
|
1504
1769
|
const elements = new Y.Map();
|
|
1505
1770
|
elements.set("type", "$blocksuite:internal:native$");
|
|
@@ -1510,7 +1775,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1510
1775
|
const noteId = generateId();
|
|
1511
1776
|
const note = new Y.Map();
|
|
1512
1777
|
setSysFields(note, noteId, "affine:note");
|
|
1513
|
-
note.set("sys:parent",
|
|
1778
|
+
note.set("sys:parent", null);
|
|
1514
1779
|
note.set("prop:displayMode", "both");
|
|
1515
1780
|
note.set("prop:xywh", "[0,0,800,95]");
|
|
1516
1781
|
note.set("prop:index", "a0");
|
|
@@ -1527,7 +1792,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1527
1792
|
const paraId = generateId();
|
|
1528
1793
|
const para = new Y.Map();
|
|
1529
1794
|
setSysFields(para, paraId, "affine:paragraph");
|
|
1530
|
-
para.set("sys:parent",
|
|
1795
|
+
para.set("sys:parent", null);
|
|
1531
1796
|
para.set("sys:children", new Y.Array());
|
|
1532
1797
|
para.set("prop:type", "text");
|
|
1533
1798
|
const paragraphText = new Y.Text();
|
|
@@ -1592,8 +1857,10 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1592
1857
|
Y.applyUpdate(wsDoc, Buffer.from(snapshot.missing, "base64"));
|
|
1593
1858
|
const meta = wsDoc.getMap("meta");
|
|
1594
1859
|
const pages = getWorkspacePageEntries(meta);
|
|
1860
|
+
const { byId } = getWorkspaceTagOptionMaps(meta);
|
|
1595
1861
|
for (const page of pages) {
|
|
1596
|
-
|
|
1862
|
+
const tagEntries = getStringArray(page.tagsArray);
|
|
1863
|
+
tagsByDocId.set(page.id, resolveTagLabels(tagEntries, byId));
|
|
1597
1864
|
}
|
|
1598
1865
|
}
|
|
1599
1866
|
}
|
|
@@ -1652,9 +1919,10 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1652
1919
|
Y.applyUpdate(wsDoc, Buffer.from(snapshot.missing, "base64"));
|
|
1653
1920
|
const meta = wsDoc.getMap("meta");
|
|
1654
1921
|
const pages = getWorkspacePageEntries(meta);
|
|
1922
|
+
const { options, byId } = getWorkspaceTagOptionMaps(meta);
|
|
1655
1923
|
const tagCounts = new Map();
|
|
1656
|
-
for (const
|
|
1657
|
-
const normalized =
|
|
1924
|
+
for (const option of options) {
|
|
1925
|
+
const normalized = option.value.trim();
|
|
1658
1926
|
if (!normalized || tagCounts.has(normalized)) {
|
|
1659
1927
|
continue;
|
|
1660
1928
|
}
|
|
@@ -1662,7 +1930,8 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1662
1930
|
}
|
|
1663
1931
|
for (const page of pages) {
|
|
1664
1932
|
const uniqueTags = new Set();
|
|
1665
|
-
|
|
1933
|
+
const resolved = resolveTagLabels(getStringArray(page.tagsArray), byId);
|
|
1934
|
+
for (const tag of resolved) {
|
|
1666
1935
|
const normalized = tag.trim();
|
|
1667
1936
|
if (!normalized) {
|
|
1668
1937
|
continue;
|
|
@@ -1713,18 +1982,22 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1713
1982
|
Y.applyUpdate(wsDoc, Buffer.from(snapshot.missing, "base64"));
|
|
1714
1983
|
const meta = wsDoc.getMap("meta");
|
|
1715
1984
|
const pages = getWorkspacePageEntries(meta);
|
|
1985
|
+
const { byId } = getWorkspaceTagOptionMaps(meta);
|
|
1716
1986
|
const docs = pages
|
|
1717
1987
|
.map((page) => {
|
|
1718
|
-
const
|
|
1988
|
+
const rawTags = getStringArray(page.tagsArray);
|
|
1989
|
+
const tags = resolveTagLabels(rawTags, byId);
|
|
1719
1990
|
return {
|
|
1720
1991
|
id: page.id,
|
|
1721
1992
|
title: page.title,
|
|
1722
1993
|
createDate: page.createDate,
|
|
1723
1994
|
updatedDate: page.updatedDate,
|
|
1724
1995
|
tags,
|
|
1996
|
+
rawTags,
|
|
1725
1997
|
};
|
|
1726
1998
|
})
|
|
1727
|
-
.filter((page) => hasTag(page.tags, tag, ignoreCase))
|
|
1999
|
+
.filter((page) => hasTag(page.tags, tag, ignoreCase) || hasTag(page.rawTags, tag, ignoreCase))
|
|
2000
|
+
.map(({ rawTags: _rawTags, ...page }) => page);
|
|
1728
2001
|
return text({
|
|
1729
2002
|
workspaceId,
|
|
1730
2003
|
tag,
|
|
@@ -1765,11 +2038,10 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1765
2038
|
Y.applyUpdate(wsDoc, Buffer.from(snapshot.missing, "base64"));
|
|
1766
2039
|
const prevSV = Y.encodeStateVector(wsDoc);
|
|
1767
2040
|
const meta = wsDoc.getMap("meta");
|
|
1768
|
-
const
|
|
1769
|
-
if (
|
|
2041
|
+
const { created } = ensureWorkspaceTagOption(meta, tag);
|
|
2042
|
+
if (!created) {
|
|
1770
2043
|
return text({ workspaceId, tag, created: false });
|
|
1771
2044
|
}
|
|
1772
|
-
registry.push([tag]);
|
|
1773
2045
|
const delta = Y.encodeStateAsUpdate(wsDoc, prevSV);
|
|
1774
2046
|
await pushDocUpdate(socket, workspaceId, workspaceId, Buffer.from(delta).toString("base64"));
|
|
1775
2047
|
return text({ workspaceId, tag, created: true });
|
|
@@ -1809,17 +2081,11 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1809
2081
|
if (!page) {
|
|
1810
2082
|
throw new Error(`docId ${parsed.docId} is not present in workspace ${workspaceId}`);
|
|
1811
2083
|
}
|
|
2084
|
+
const { option, created: optionCreated } = ensureWorkspaceTagOption(wsMeta, tag);
|
|
1812
2085
|
const pageTags = ensureTagArray(page.entry);
|
|
1813
|
-
const
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
}
|
|
1817
|
-
const registry = ensureTagArray(wsMeta);
|
|
1818
|
-
const existedInRegistry = findTagIndex(registry, tag, true) >= 0;
|
|
1819
|
-
if (!existedInRegistry) {
|
|
1820
|
-
registry.push([tag]);
|
|
1821
|
-
}
|
|
1822
|
-
if (!existedInDoc || !existedInRegistry) {
|
|
2086
|
+
const pageSync = syncTagArrayToOption(pageTags, tag, option);
|
|
2087
|
+
const wsChanged = optionCreated || pageSync.changed;
|
|
2088
|
+
if (wsChanged) {
|
|
1823
2089
|
const wsDelta = Y.encodeStateAsUpdate(wsDoc, wsPrevSV);
|
|
1824
2090
|
await pushDocUpdate(socket, workspaceId, workspaceId, Buffer.from(wsDelta).toString("base64"));
|
|
1825
2091
|
}
|
|
@@ -1835,20 +2101,20 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1835
2101
|
const docPrevSV = Y.encodeStateVector(doc);
|
|
1836
2102
|
const docMeta = doc.getMap("meta");
|
|
1837
2103
|
const docTags = ensureTagArray(docMeta);
|
|
1838
|
-
const
|
|
1839
|
-
if (
|
|
1840
|
-
docTags.push([tag]);
|
|
2104
|
+
const docSync = syncTagArrayToOption(docTags, tag, option);
|
|
2105
|
+
if (docSync.changed) {
|
|
1841
2106
|
const docDelta = Y.encodeStateAsUpdate(doc, docPrevSV);
|
|
1842
2107
|
await pushDocUpdate(socket, workspaceId, parsed.docId, Buffer.from(docDelta).toString("base64"));
|
|
1843
2108
|
}
|
|
1844
2109
|
docMetaSynced = true;
|
|
1845
2110
|
}
|
|
2111
|
+
const { byId } = getWorkspaceTagOptionMaps(wsMeta);
|
|
1846
2112
|
return text({
|
|
1847
2113
|
workspaceId,
|
|
1848
2114
|
docId: parsed.docId,
|
|
1849
2115
|
tag,
|
|
1850
|
-
added: !
|
|
1851
|
-
tags: getStringArray(pageTags),
|
|
2116
|
+
added: !pageSync.existed,
|
|
2117
|
+
tags: resolveTagLabels(getStringArray(pageTags), byId),
|
|
1852
2118
|
docMetaSynced,
|
|
1853
2119
|
warning,
|
|
1854
2120
|
});
|
|
@@ -1889,10 +2155,11 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1889
2155
|
if (!page) {
|
|
1890
2156
|
throw new Error(`docId ${parsed.docId} is not present in workspace ${workspaceId}`);
|
|
1891
2157
|
}
|
|
2158
|
+
const option = getWorkspaceTagOptionMaps(wsMeta).byValueLower.get(tag.toLocaleLowerCase()) || null;
|
|
1892
2159
|
const pageTags = ensureTagArray(page.entry);
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
|
|
2160
|
+
const pageTagIndexes = collectMatchingTagIndexes(pageTags, tag, option, true);
|
|
2161
|
+
const pageRemoved = deleteArrayIndexes(pageTags, pageTagIndexes);
|
|
2162
|
+
if (pageRemoved) {
|
|
1896
2163
|
const wsDelta = Y.encodeStateAsUpdate(wsDoc, wsPrevSV);
|
|
1897
2164
|
await pushDocUpdate(socket, workspaceId, workspaceId, Buffer.from(wsDelta).toString("base64"));
|
|
1898
2165
|
}
|
|
@@ -1908,20 +2175,20 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1908
2175
|
const docPrevSV = Y.encodeStateVector(doc);
|
|
1909
2176
|
const docMeta = doc.getMap("meta");
|
|
1910
2177
|
const docTags = ensureTagArray(docMeta);
|
|
1911
|
-
const
|
|
1912
|
-
if (
|
|
1913
|
-
docTags.delete(docTagIndex, 1);
|
|
2178
|
+
const docTagIndexes = collectMatchingTagIndexes(docTags, tag, option, true);
|
|
2179
|
+
if (deleteArrayIndexes(docTags, docTagIndexes)) {
|
|
1914
2180
|
const docDelta = Y.encodeStateAsUpdate(doc, docPrevSV);
|
|
1915
2181
|
await pushDocUpdate(socket, workspaceId, parsed.docId, Buffer.from(docDelta).toString("base64"));
|
|
1916
2182
|
}
|
|
1917
2183
|
docMetaSynced = true;
|
|
1918
2184
|
}
|
|
2185
|
+
const { byId } = getWorkspaceTagOptionMaps(wsMeta);
|
|
1919
2186
|
return text({
|
|
1920
2187
|
workspaceId,
|
|
1921
2188
|
docId: parsed.docId,
|
|
1922
2189
|
tag,
|
|
1923
|
-
removed:
|
|
1924
|
-
tags: getStringArray(pageTags),
|
|
2190
|
+
removed: pageRemoved,
|
|
2191
|
+
tags: resolveTagLabels(getStringArray(pageTags), byId),
|
|
1925
2192
|
docMetaSynced,
|
|
1926
2193
|
warning,
|
|
1927
2194
|
});
|
|
@@ -1966,6 +2233,13 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1966
2233
|
const socket = await connectWorkspaceSocket(wsUrl, cookie, bearer);
|
|
1967
2234
|
try {
|
|
1968
2235
|
await joinWorkspace(socket, workspaceId);
|
|
2236
|
+
let tagOptionsById = new Map();
|
|
2237
|
+
const workspaceSnapshot = await loadDoc(socket, workspaceId, workspaceId);
|
|
2238
|
+
if (workspaceSnapshot.missing) {
|
|
2239
|
+
const workspaceDoc = new Y.Doc();
|
|
2240
|
+
Y.applyUpdate(workspaceDoc, Buffer.from(workspaceSnapshot.missing, "base64"));
|
|
2241
|
+
tagOptionsById = getWorkspaceTagOptionMaps(workspaceDoc.getMap("meta")).byId;
|
|
2242
|
+
}
|
|
1969
2243
|
const snapshot = await loadDoc(socket, workspaceId, parsed.docId);
|
|
1970
2244
|
if (!snapshot.missing) {
|
|
1971
2245
|
return text({
|
|
@@ -1981,7 +2255,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
1981
2255
|
const doc = new Y.Doc();
|
|
1982
2256
|
Y.applyUpdate(doc, Buffer.from(snapshot.missing, "base64"));
|
|
1983
2257
|
const meta = doc.getMap("meta");
|
|
1984
|
-
const tags = getStringArray(getTagArray(meta));
|
|
2258
|
+
const tags = resolveTagLabels(getStringArray(getTagArray(meta)), tagOptionsById);
|
|
1985
2259
|
const blocks = doc.getMap("blocks");
|
|
1986
2260
|
const pageId = findBlockIdByFlavour(blocks, "affine:page");
|
|
1987
2261
|
const noteId = findBlockIdByFlavour(blocks, "affine:note");
|
|
@@ -2190,6 +2464,13 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
2190
2464
|
const socket = await connectWorkspaceSocket(wsUrl, cookie, bearer);
|
|
2191
2465
|
try {
|
|
2192
2466
|
await joinWorkspace(socket, workspaceId);
|
|
2467
|
+
let tagOptionsById = new Map();
|
|
2468
|
+
const workspaceSnapshot = await loadDoc(socket, workspaceId, workspaceId);
|
|
2469
|
+
if (workspaceSnapshot.missing) {
|
|
2470
|
+
const wsDoc = new Y.Doc();
|
|
2471
|
+
Y.applyUpdate(wsDoc, Buffer.from(workspaceSnapshot.missing, "base64"));
|
|
2472
|
+
tagOptionsById = getWorkspaceTagOptionMaps(wsDoc.getMap("meta")).byId;
|
|
2473
|
+
}
|
|
2193
2474
|
const snapshot = await loadDoc(socket, workspaceId, parsed.docId);
|
|
2194
2475
|
if (!snapshot.missing) {
|
|
2195
2476
|
return text({
|
|
@@ -2208,7 +2489,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
2208
2489
|
}
|
|
2209
2490
|
const doc = new Y.Doc();
|
|
2210
2491
|
Y.applyUpdate(doc, Buffer.from(snapshot.missing, "base64"));
|
|
2211
|
-
const collected = collectDocForMarkdown(doc);
|
|
2492
|
+
const collected = collectDocForMarkdown(doc, tagOptionsById);
|
|
2212
2493
|
const rendered = renderBlocksToMarkdown({
|
|
2213
2494
|
rootBlockIds: collected.rootBlockIds,
|
|
2214
2495
|
blocksById: collected.blocksById,
|
package/dist/tools/workspaces.js
CHANGED
|
@@ -58,7 +58,7 @@ function createInitialWorkspaceData(workspaceName = 'New Workspace', avatar = ''
|
|
|
58
58
|
const surfaceBlock = new Y.Map();
|
|
59
59
|
surfaceBlock.set('sys:id', surfaceId);
|
|
60
60
|
surfaceBlock.set('sys:flavour', 'affine:surface');
|
|
61
|
-
surfaceBlock.set('sys:parent',
|
|
61
|
+
surfaceBlock.set('sys:parent', null);
|
|
62
62
|
surfaceBlock.set('sys:children', new Y.Array());
|
|
63
63
|
blocks.set(surfaceId, surfaceBlock);
|
|
64
64
|
pageChildren.push([surfaceId]);
|
|
@@ -67,7 +67,7 @@ function createInitialWorkspaceData(workspaceName = 'New Workspace', avatar = ''
|
|
|
67
67
|
const noteBlock = new Y.Map();
|
|
68
68
|
noteBlock.set('sys:id', noteId);
|
|
69
69
|
noteBlock.set('sys:flavour', 'affine:note');
|
|
70
|
-
noteBlock.set('sys:parent',
|
|
70
|
+
noteBlock.set('sys:parent', null);
|
|
71
71
|
noteBlock.set('prop:displayMode', 'DocAndEdgeless');
|
|
72
72
|
noteBlock.set('prop:xywh', '[0,0,800,600]');
|
|
73
73
|
noteBlock.set('prop:index', 'a0');
|
|
@@ -81,7 +81,7 @@ function createInitialWorkspaceData(workspaceName = 'New Workspace', avatar = ''
|
|
|
81
81
|
const paragraphBlock = new Y.Map();
|
|
82
82
|
paragraphBlock.set('sys:id', paragraphId);
|
|
83
83
|
paragraphBlock.set('sys:flavour', 'affine:paragraph');
|
|
84
|
-
paragraphBlock.set('sys:parent',
|
|
84
|
+
paragraphBlock.set('sys:parent', null);
|
|
85
85
|
paragraphBlock.set('sys:children', new Y.Array());
|
|
86
86
|
paragraphBlock.set('prop:type', 'text');
|
|
87
87
|
const paragraphText = new Y.Text();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "affine-mcp-server",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Model Context Protocol server for AFFiNE - enables AI assistants to interact with AFFiNE workspaces, documents, and collaboration features.",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"test:e2e": "bash tests/run-e2e.sh",
|
|
39
39
|
"test:db-create": "node tests/test-database-creation.mjs",
|
|
40
40
|
"test:bearer": "node tests/test-bearer-auth.mjs",
|
|
41
|
+
"test:tag-visibility": "node tests/test-tag-visibility.mjs",
|
|
41
42
|
"test:playwright": "npx playwright test --config tests/playwright/playwright.config.ts",
|
|
42
43
|
"pack:check": "npm pack --dry-run",
|
|
43
44
|
"ci": "npm run build && npm run test:tool-manifest && npm run pack:check",
|