@composer-app/mcp 0.0.4-beta.1 → 0.0.5
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/{chunk-ORB2OJTN.js → chunk-IDX65B2W.js} +1621 -122
- package/dist/cli.js +6 -6
- package/dist/mcp.js +1 -1
- package/package.json +7 -3
|
@@ -33,6 +33,9 @@ function getActivityMap(doc) {
|
|
|
33
33
|
function getActivityStateMap(doc) {
|
|
34
34
|
return doc.getMap("activityState");
|
|
35
35
|
}
|
|
36
|
+
function stateKey(activityId, userId) {
|
|
37
|
+
return `${activityId}:${userId}`;
|
|
38
|
+
}
|
|
36
39
|
function emitActivity(doc, event, opts) {
|
|
37
40
|
if (opts?.silent) return;
|
|
38
41
|
const activity = getActivityMap(doc);
|
|
@@ -65,6 +68,20 @@ function pruneIfOverCap(doc) {
|
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
}
|
|
71
|
+
function markActivityReadForThread(doc, threadId, userId) {
|
|
72
|
+
const activity = getActivityMap(doc);
|
|
73
|
+
const stateMap = getActivityStateMap(doc);
|
|
74
|
+
activity.forEach((value) => {
|
|
75
|
+
const event = value;
|
|
76
|
+
if (event.threadId === threadId) {
|
|
77
|
+
const key = stateKey(event.id, userId);
|
|
78
|
+
const existing = stateMap.get(key);
|
|
79
|
+
if (!existing?.read) {
|
|
80
|
+
stateMap.set(key, { read: true, dismissed: existing?.dismissed ?? false });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
68
85
|
function textPreview(text, maxLen = 80) {
|
|
69
86
|
if (!text) return void 0;
|
|
70
87
|
return text.length > maxLen ? text.slice(0, maxLen) + "\u2026" : text;
|
|
@@ -363,24 +380,1182 @@ var removeAwarenessStates = (awareness, clients, origin) => {
|
|
|
363
380
|
lastUpdated: getUnixTime()
|
|
364
381
|
});
|
|
365
382
|
}
|
|
366
|
-
removed.push(clientID);
|
|
383
|
+
removed.push(clientID);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (removed.length > 0) {
|
|
387
|
+
awareness.emit("change", [{ added: [], updated: [], removed }, origin]);
|
|
388
|
+
awareness.emit("update", [{ added: [], updated: [], removed }, origin]);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/roomState.ts
|
|
393
|
+
import WebSocket from "ws";
|
|
394
|
+
|
|
395
|
+
// ../shared/src/editor-extensions.ts
|
|
396
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
397
|
+
import { Code } from "@tiptap/extension-code";
|
|
398
|
+
import CodeBlock from "@tiptap/extension-code-block";
|
|
399
|
+
|
|
400
|
+
// ../node_modules/@tiptap/extension-blockquote/dist/index.js
|
|
401
|
+
import { mergeAttributes, Node, wrappingInputRule } from "@tiptap/core";
|
|
402
|
+
import { jsx } from "@tiptap/core/jsx-runtime";
|
|
403
|
+
var inputRegex = /^\s*>\s$/;
|
|
404
|
+
var Blockquote = Node.create({
|
|
405
|
+
name: "blockquote",
|
|
406
|
+
addOptions() {
|
|
407
|
+
return {
|
|
408
|
+
HTMLAttributes: {}
|
|
409
|
+
};
|
|
410
|
+
},
|
|
411
|
+
content: "block+",
|
|
412
|
+
group: "block",
|
|
413
|
+
defining: true,
|
|
414
|
+
parseHTML() {
|
|
415
|
+
return [{ tag: "blockquote" }];
|
|
416
|
+
},
|
|
417
|
+
renderHTML({ HTMLAttributes }) {
|
|
418
|
+
return /* @__PURE__ */ jsx("blockquote", { ...mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), children: /* @__PURE__ */ jsx("slot", {}) });
|
|
419
|
+
},
|
|
420
|
+
parseMarkdown: (token, helpers) => {
|
|
421
|
+
var _a;
|
|
422
|
+
const parseBlockChildren = (_a = helpers.parseBlockChildren) != null ? _a : helpers.parseChildren;
|
|
423
|
+
return helpers.createNode("blockquote", void 0, parseBlockChildren(token.tokens || []));
|
|
424
|
+
},
|
|
425
|
+
renderMarkdown: (node, h) => {
|
|
426
|
+
if (!node.content) {
|
|
427
|
+
return "";
|
|
428
|
+
}
|
|
429
|
+
const prefix = ">";
|
|
430
|
+
const result = [];
|
|
431
|
+
node.content.forEach((child, index) => {
|
|
432
|
+
var _a, _b;
|
|
433
|
+
const childContent = (_b = (_a = h.renderChild) == null ? void 0 : _a.call(h, child, index)) != null ? _b : h.renderChildren([child]);
|
|
434
|
+
const lines = childContent.split("\n");
|
|
435
|
+
const linesWithPrefix = lines.map((line) => {
|
|
436
|
+
if (line.trim() === "") {
|
|
437
|
+
return prefix;
|
|
438
|
+
}
|
|
439
|
+
return `${prefix} ${line}`;
|
|
440
|
+
});
|
|
441
|
+
result.push(linesWithPrefix.join("\n"));
|
|
442
|
+
});
|
|
443
|
+
return result.join(`
|
|
444
|
+
${prefix}
|
|
445
|
+
`);
|
|
446
|
+
},
|
|
447
|
+
addCommands() {
|
|
448
|
+
return {
|
|
449
|
+
setBlockquote: () => ({ commands }) => {
|
|
450
|
+
return commands.wrapIn(this.name);
|
|
451
|
+
},
|
|
452
|
+
toggleBlockquote: () => ({ commands }) => {
|
|
453
|
+
return commands.toggleWrap(this.name);
|
|
454
|
+
},
|
|
455
|
+
unsetBlockquote: () => ({ commands }) => {
|
|
456
|
+
return commands.lift(this.name);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
},
|
|
460
|
+
addKeyboardShortcuts() {
|
|
461
|
+
return {
|
|
462
|
+
"Mod-Shift-b": () => this.editor.commands.toggleBlockquote()
|
|
463
|
+
};
|
|
464
|
+
},
|
|
465
|
+
addInputRules() {
|
|
466
|
+
return [
|
|
467
|
+
wrappingInputRule({
|
|
468
|
+
find: inputRegex,
|
|
469
|
+
type: this.type
|
|
470
|
+
})
|
|
471
|
+
];
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// ../node_modules/@tiptap/extension-list/dist/index.js
|
|
476
|
+
import { mergeAttributes as mergeAttributes2, Node as Node2, wrappingInputRule as wrappingInputRule2 } from "@tiptap/core";
|
|
477
|
+
import { mergeAttributes as mergeAttributes22, Node as Node22, renderNestedMarkdownContent } from "@tiptap/core";
|
|
478
|
+
import { Extension } from "@tiptap/core";
|
|
479
|
+
import { getNodeType } from "@tiptap/core";
|
|
480
|
+
import { getNodeAtPosition } from "@tiptap/core";
|
|
481
|
+
import { isAtStartOfNode, isNodeActive } from "@tiptap/core";
|
|
482
|
+
import { getNodeType as getNodeType2 } from "@tiptap/core";
|
|
483
|
+
import { isAtEndOfNode, isNodeActive as isNodeActive2 } from "@tiptap/core";
|
|
484
|
+
import { Extension as Extension2 } from "@tiptap/core";
|
|
485
|
+
import { mergeAttributes as mergeAttributes3, Node as Node3, wrappingInputRule as wrappingInputRule22 } from "@tiptap/core";
|
|
486
|
+
import {
|
|
487
|
+
getRenderedAttributes,
|
|
488
|
+
mergeAttributes as mergeAttributes4,
|
|
489
|
+
Node as Node4,
|
|
490
|
+
renderNestedMarkdownContent as renderNestedMarkdownContent2,
|
|
491
|
+
wrappingInputRule as wrappingInputRule3
|
|
492
|
+
} from "@tiptap/core";
|
|
493
|
+
import { mergeAttributes as mergeAttributes5, Node as Node5, parseIndentedBlocks } from "@tiptap/core";
|
|
494
|
+
var __defProp = Object.defineProperty;
|
|
495
|
+
var __export = (target, all) => {
|
|
496
|
+
for (var name in all)
|
|
497
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
498
|
+
};
|
|
499
|
+
var ListItemName = "listItem";
|
|
500
|
+
var TextStyleName = "textStyle";
|
|
501
|
+
var bulletListInputRegex = /^\s*([-+*])\s$/;
|
|
502
|
+
var BulletList = Node2.create({
|
|
503
|
+
name: "bulletList",
|
|
504
|
+
addOptions() {
|
|
505
|
+
return {
|
|
506
|
+
itemTypeName: "listItem",
|
|
507
|
+
HTMLAttributes: {},
|
|
508
|
+
keepMarks: false,
|
|
509
|
+
keepAttributes: false
|
|
510
|
+
};
|
|
511
|
+
},
|
|
512
|
+
group: "block list",
|
|
513
|
+
content() {
|
|
514
|
+
return `${this.options.itemTypeName}+`;
|
|
515
|
+
},
|
|
516
|
+
parseHTML() {
|
|
517
|
+
return [{ tag: "ul" }];
|
|
518
|
+
},
|
|
519
|
+
renderHTML({ HTMLAttributes }) {
|
|
520
|
+
return ["ul", mergeAttributes2(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
521
|
+
},
|
|
522
|
+
markdownTokenName: "list",
|
|
523
|
+
parseMarkdown: (token, helpers) => {
|
|
524
|
+
if (token.type !== "list" || token.ordered) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
type: "bulletList",
|
|
529
|
+
content: token.items ? helpers.parseChildren(token.items) : []
|
|
530
|
+
};
|
|
531
|
+
},
|
|
532
|
+
renderMarkdown: (node, h) => {
|
|
533
|
+
if (!node.content) {
|
|
534
|
+
return "";
|
|
535
|
+
}
|
|
536
|
+
return h.renderChildren(node.content, "\n");
|
|
537
|
+
},
|
|
538
|
+
markdownOptions: {
|
|
539
|
+
indentsContent: true
|
|
540
|
+
},
|
|
541
|
+
addCommands() {
|
|
542
|
+
return {
|
|
543
|
+
toggleBulletList: () => ({ commands, chain }) => {
|
|
544
|
+
if (this.options.keepAttributes) {
|
|
545
|
+
return chain().toggleList(this.name, this.options.itemTypeName, this.options.keepMarks).updateAttributes(ListItemName, this.editor.getAttributes(TextStyleName)).run();
|
|
546
|
+
}
|
|
547
|
+
return commands.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks);
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
},
|
|
551
|
+
addKeyboardShortcuts() {
|
|
552
|
+
return {
|
|
553
|
+
"Mod-Shift-8": () => this.editor.commands.toggleBulletList()
|
|
554
|
+
};
|
|
555
|
+
},
|
|
556
|
+
addInputRules() {
|
|
557
|
+
let inputRule = wrappingInputRule2({
|
|
558
|
+
find: bulletListInputRegex,
|
|
559
|
+
type: this.type
|
|
560
|
+
});
|
|
561
|
+
if (this.options.keepMarks || this.options.keepAttributes) {
|
|
562
|
+
inputRule = wrappingInputRule2({
|
|
563
|
+
find: bulletListInputRegex,
|
|
564
|
+
type: this.type,
|
|
565
|
+
keepMarks: this.options.keepMarks,
|
|
566
|
+
keepAttributes: this.options.keepAttributes,
|
|
567
|
+
getAttributes: () => {
|
|
568
|
+
return this.editor.getAttributes(TextStyleName);
|
|
569
|
+
},
|
|
570
|
+
editor: this.editor
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return [inputRule];
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
var ListItem = Node22.create({
|
|
577
|
+
name: "listItem",
|
|
578
|
+
addOptions() {
|
|
579
|
+
return {
|
|
580
|
+
HTMLAttributes: {},
|
|
581
|
+
bulletListTypeName: "bulletList",
|
|
582
|
+
orderedListTypeName: "orderedList"
|
|
583
|
+
};
|
|
584
|
+
},
|
|
585
|
+
content: "paragraph block*",
|
|
586
|
+
defining: true,
|
|
587
|
+
parseHTML() {
|
|
588
|
+
return [
|
|
589
|
+
{
|
|
590
|
+
tag: "li"
|
|
591
|
+
}
|
|
592
|
+
];
|
|
593
|
+
},
|
|
594
|
+
renderHTML({ HTMLAttributes }) {
|
|
595
|
+
return ["li", mergeAttributes22(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
596
|
+
},
|
|
597
|
+
markdownTokenName: "list_item",
|
|
598
|
+
parseMarkdown: (token, helpers) => {
|
|
599
|
+
var _a;
|
|
600
|
+
if (token.type !== "list_item") {
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
const parseBlockChildren = (_a = helpers.parseBlockChildren) != null ? _a : helpers.parseChildren;
|
|
604
|
+
let content = [];
|
|
605
|
+
if (token.tokens && token.tokens.length > 0) {
|
|
606
|
+
const hasParagraphTokens = token.tokens.some((t) => t.type === "paragraph");
|
|
607
|
+
if (hasParagraphTokens) {
|
|
608
|
+
content = parseBlockChildren(token.tokens);
|
|
609
|
+
} else {
|
|
610
|
+
const firstToken = token.tokens[0];
|
|
611
|
+
if (firstToken && firstToken.type === "text" && firstToken.tokens && firstToken.tokens.length > 0) {
|
|
612
|
+
const inlineContent = helpers.parseInline(firstToken.tokens);
|
|
613
|
+
content = [
|
|
614
|
+
{
|
|
615
|
+
type: "paragraph",
|
|
616
|
+
content: inlineContent
|
|
617
|
+
}
|
|
618
|
+
];
|
|
619
|
+
if (token.tokens.length > 1) {
|
|
620
|
+
const remainingTokens = token.tokens.slice(1);
|
|
621
|
+
const additionalContent = parseBlockChildren(remainingTokens);
|
|
622
|
+
content.push(...additionalContent);
|
|
623
|
+
}
|
|
624
|
+
} else {
|
|
625
|
+
content = parseBlockChildren(token.tokens);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (content.length === 0) {
|
|
630
|
+
content = [
|
|
631
|
+
{
|
|
632
|
+
type: "paragraph",
|
|
633
|
+
content: []
|
|
634
|
+
}
|
|
635
|
+
];
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
type: "listItem",
|
|
639
|
+
content
|
|
640
|
+
};
|
|
641
|
+
},
|
|
642
|
+
renderMarkdown: (node, h, ctx) => {
|
|
643
|
+
return renderNestedMarkdownContent(
|
|
644
|
+
node,
|
|
645
|
+
h,
|
|
646
|
+
(context) => {
|
|
647
|
+
var _a, _b;
|
|
648
|
+
if (context.parentType === "bulletList") {
|
|
649
|
+
return "- ";
|
|
650
|
+
}
|
|
651
|
+
if (context.parentType === "orderedList") {
|
|
652
|
+
const start = ((_b = (_a = context.meta) == null ? void 0 : _a.parentAttrs) == null ? void 0 : _b.start) || 1;
|
|
653
|
+
return `${start + context.index}. `;
|
|
654
|
+
}
|
|
655
|
+
return "- ";
|
|
656
|
+
},
|
|
657
|
+
ctx
|
|
658
|
+
);
|
|
659
|
+
},
|
|
660
|
+
addKeyboardShortcuts() {
|
|
661
|
+
return {
|
|
662
|
+
Enter: () => this.editor.commands.splitListItem(this.name),
|
|
663
|
+
Tab: () => this.editor.commands.sinkListItem(this.name),
|
|
664
|
+
"Shift-Tab": () => this.editor.commands.liftListItem(this.name)
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
var listHelpers_exports = {};
|
|
669
|
+
__export(listHelpers_exports, {
|
|
670
|
+
findListItemPos: () => findListItemPos,
|
|
671
|
+
getNextListDepth: () => getNextListDepth,
|
|
672
|
+
handleBackspace: () => handleBackspace,
|
|
673
|
+
handleDelete: () => handleDelete,
|
|
674
|
+
hasListBefore: () => hasListBefore,
|
|
675
|
+
hasListItemAfter: () => hasListItemAfter,
|
|
676
|
+
hasListItemBefore: () => hasListItemBefore,
|
|
677
|
+
listItemHasSubList: () => listItemHasSubList,
|
|
678
|
+
nextListIsDeeper: () => nextListIsDeeper,
|
|
679
|
+
nextListIsHigher: () => nextListIsHigher
|
|
680
|
+
});
|
|
681
|
+
var findListItemPos = (typeOrName, state) => {
|
|
682
|
+
const { $from } = state.selection;
|
|
683
|
+
const nodeType = getNodeType(typeOrName, state.schema);
|
|
684
|
+
let currentNode = null;
|
|
685
|
+
let currentDepth = $from.depth;
|
|
686
|
+
let currentPos = $from.pos;
|
|
687
|
+
let targetDepth = null;
|
|
688
|
+
while (currentDepth > 0 && targetDepth === null) {
|
|
689
|
+
currentNode = $from.node(currentDepth);
|
|
690
|
+
if (currentNode.type === nodeType) {
|
|
691
|
+
targetDepth = currentDepth;
|
|
692
|
+
} else {
|
|
693
|
+
currentDepth -= 1;
|
|
694
|
+
currentPos -= 1;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (targetDepth === null) {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
return { $pos: state.doc.resolve(currentPos), depth: targetDepth };
|
|
701
|
+
};
|
|
702
|
+
var getNextListDepth = (typeOrName, state) => {
|
|
703
|
+
const listItemPos = findListItemPos(typeOrName, state);
|
|
704
|
+
if (!listItemPos) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
const [, depth] = getNodeAtPosition(state, typeOrName, listItemPos.$pos.pos + 4);
|
|
708
|
+
return depth;
|
|
709
|
+
};
|
|
710
|
+
var hasListBefore = (editorState, name, parentListTypes) => {
|
|
711
|
+
const { $anchor } = editorState.selection;
|
|
712
|
+
const previousNodePos = Math.max(0, $anchor.pos - 2);
|
|
713
|
+
const previousNode = editorState.doc.resolve(previousNodePos).node();
|
|
714
|
+
if (!previousNode || !parentListTypes.includes(previousNode.type.name)) {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
return true;
|
|
718
|
+
};
|
|
719
|
+
var hasListItemBefore = (typeOrName, state) => {
|
|
720
|
+
var _a;
|
|
721
|
+
const { $anchor } = state.selection;
|
|
722
|
+
const $targetPos = state.doc.resolve($anchor.pos - 2);
|
|
723
|
+
if ($targetPos.index() === 0) {
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
if (((_a = $targetPos.nodeBefore) == null ? void 0 : _a.type.name) !== typeOrName) {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
return true;
|
|
730
|
+
};
|
|
731
|
+
var listItemHasSubList = (typeOrName, state, node) => {
|
|
732
|
+
if (!node) {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
const nodeType = getNodeType2(typeOrName, state.schema);
|
|
736
|
+
let hasSubList = false;
|
|
737
|
+
node.descendants((child) => {
|
|
738
|
+
if (child.type === nodeType) {
|
|
739
|
+
hasSubList = true;
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
return hasSubList;
|
|
743
|
+
};
|
|
744
|
+
var handleBackspace = (editor, name, parentListTypes) => {
|
|
745
|
+
if (editor.commands.undoInputRule()) {
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
if (editor.state.selection.from !== editor.state.selection.to) {
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
if (!isNodeActive(editor.state, name) && hasListBefore(editor.state, name, parentListTypes)) {
|
|
752
|
+
const { $anchor } = editor.state.selection;
|
|
753
|
+
const $listPos = editor.state.doc.resolve($anchor.before() - 1);
|
|
754
|
+
const listDescendants = [];
|
|
755
|
+
$listPos.node().descendants((node, pos) => {
|
|
756
|
+
if (node.type.name === name) {
|
|
757
|
+
listDescendants.push({ node, pos });
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
const lastItem = listDescendants.at(-1);
|
|
761
|
+
if (!lastItem) {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
const $lastItemPos = editor.state.doc.resolve($listPos.start() + lastItem.pos + 1);
|
|
765
|
+
return editor.chain().cut({ from: $anchor.start() - 1, to: $anchor.end() + 1 }, $lastItemPos.end()).joinForward().run();
|
|
766
|
+
}
|
|
767
|
+
if (!isNodeActive(editor.state, name)) {
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
if (!isAtStartOfNode(editor.state)) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
const listItemPos = findListItemPos(name, editor.state);
|
|
774
|
+
if (!listItemPos) {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
const $prev = editor.state.doc.resolve(listItemPos.$pos.pos - 2);
|
|
778
|
+
const prevNode = $prev.node(listItemPos.depth);
|
|
779
|
+
const previousListItemHasSubList = listItemHasSubList(name, editor.state, prevNode);
|
|
780
|
+
if (hasListItemBefore(name, editor.state) && !previousListItemHasSubList) {
|
|
781
|
+
return editor.commands.joinItemBackward();
|
|
782
|
+
}
|
|
783
|
+
return editor.chain().liftListItem(name).run();
|
|
784
|
+
};
|
|
785
|
+
var nextListIsDeeper = (typeOrName, state) => {
|
|
786
|
+
const listDepth = getNextListDepth(typeOrName, state);
|
|
787
|
+
const listItemPos = findListItemPos(typeOrName, state);
|
|
788
|
+
if (!listItemPos || !listDepth) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
if (listDepth > listItemPos.depth) {
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
return false;
|
|
795
|
+
};
|
|
796
|
+
var nextListIsHigher = (typeOrName, state) => {
|
|
797
|
+
const listDepth = getNextListDepth(typeOrName, state);
|
|
798
|
+
const listItemPos = findListItemPos(typeOrName, state);
|
|
799
|
+
if (!listItemPos || !listDepth) {
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
if (listDepth < listItemPos.depth) {
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
return false;
|
|
806
|
+
};
|
|
807
|
+
var handleDelete = (editor, name) => {
|
|
808
|
+
if (!isNodeActive2(editor.state, name)) {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
if (!isAtEndOfNode(editor.state, name)) {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
const { selection } = editor.state;
|
|
815
|
+
const { $from, $to } = selection;
|
|
816
|
+
if (!selection.empty && $from.sameParent($to)) {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
if (nextListIsDeeper(name, editor.state)) {
|
|
820
|
+
return editor.chain().focus(editor.state.selection.from + 4).lift(name).joinBackward().run();
|
|
821
|
+
}
|
|
822
|
+
if (nextListIsHigher(name, editor.state)) {
|
|
823
|
+
return editor.chain().joinForward().joinBackward().run();
|
|
824
|
+
}
|
|
825
|
+
return editor.commands.joinItemForward();
|
|
826
|
+
};
|
|
827
|
+
var hasListItemAfter = (typeOrName, state) => {
|
|
828
|
+
var _a;
|
|
829
|
+
const { $anchor } = state.selection;
|
|
830
|
+
const $targetPos = state.doc.resolve($anchor.pos - $anchor.parentOffset - 2);
|
|
831
|
+
if ($targetPos.index() === $targetPos.parent.childCount - 1) {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
if (((_a = $targetPos.nodeAfter) == null ? void 0 : _a.type.name) !== typeOrName) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
return true;
|
|
838
|
+
};
|
|
839
|
+
var ListKeymap = Extension.create({
|
|
840
|
+
name: "listKeymap",
|
|
841
|
+
addOptions() {
|
|
842
|
+
return {
|
|
843
|
+
listTypes: [
|
|
844
|
+
{
|
|
845
|
+
itemName: "listItem",
|
|
846
|
+
wrapperNames: ["bulletList", "orderedList"]
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
itemName: "taskItem",
|
|
850
|
+
wrapperNames: ["taskList"]
|
|
851
|
+
}
|
|
852
|
+
]
|
|
853
|
+
};
|
|
854
|
+
},
|
|
855
|
+
addKeyboardShortcuts() {
|
|
856
|
+
return {
|
|
857
|
+
Delete: ({ editor }) => {
|
|
858
|
+
let handled = false;
|
|
859
|
+
this.options.listTypes.forEach(({ itemName }) => {
|
|
860
|
+
if (editor.state.schema.nodes[itemName] === void 0) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (handleDelete(editor, itemName)) {
|
|
864
|
+
handled = true;
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
return handled;
|
|
868
|
+
},
|
|
869
|
+
"Mod-Delete": ({ editor }) => {
|
|
870
|
+
let handled = false;
|
|
871
|
+
this.options.listTypes.forEach(({ itemName }) => {
|
|
872
|
+
if (editor.state.schema.nodes[itemName] === void 0) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (handleDelete(editor, itemName)) {
|
|
876
|
+
handled = true;
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
return handled;
|
|
880
|
+
},
|
|
881
|
+
Backspace: ({ editor }) => {
|
|
882
|
+
let handled = false;
|
|
883
|
+
this.options.listTypes.forEach(({ itemName, wrapperNames }) => {
|
|
884
|
+
if (editor.state.schema.nodes[itemName] === void 0) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
if (handleBackspace(editor, itemName, wrapperNames)) {
|
|
888
|
+
handled = true;
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
return handled;
|
|
892
|
+
},
|
|
893
|
+
"Mod-Backspace": ({ editor }) => {
|
|
894
|
+
let handled = false;
|
|
895
|
+
this.options.listTypes.forEach(({ itemName, wrapperNames }) => {
|
|
896
|
+
if (editor.state.schema.nodes[itemName] === void 0) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (handleBackspace(editor, itemName, wrapperNames)) {
|
|
900
|
+
handled = true;
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
return handled;
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
var ORDERED_LIST_ITEM_REGEX = /^(\s*)(\d+)\.\s+(.*)$/;
|
|
909
|
+
var INDENTED_LINE_REGEX = /^\s/;
|
|
910
|
+
function isBlockContentLine(line) {
|
|
911
|
+
const trimmedLine = line.trimStart();
|
|
912
|
+
return /^[-+*]\s+/.test(trimmedLine) || /^\d+\.\s+/.test(trimmedLine) || /^>\s?/.test(trimmedLine) || /^```/.test(trimmedLine) || /^~~~/.test(trimmedLine);
|
|
913
|
+
}
|
|
914
|
+
function splitItemContent(contentLines) {
|
|
915
|
+
const paragraphLines = [];
|
|
916
|
+
const blockLines = [];
|
|
917
|
+
let reachedBlockBoundary = false;
|
|
918
|
+
contentLines.forEach((line) => {
|
|
919
|
+
if (reachedBlockBoundary) {
|
|
920
|
+
blockLines.push(line);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
if (line.trim() === "") {
|
|
924
|
+
reachedBlockBoundary = true;
|
|
925
|
+
blockLines.push(line);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
if (paragraphLines.length > 0 && isBlockContentLine(line)) {
|
|
929
|
+
reachedBlockBoundary = true;
|
|
930
|
+
blockLines.push(line);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
paragraphLines.push(line);
|
|
934
|
+
});
|
|
935
|
+
return {
|
|
936
|
+
paragraphLines,
|
|
937
|
+
blockLines
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function collectOrderedListItems(lines) {
|
|
941
|
+
const listItems = [];
|
|
942
|
+
let currentLineIndex = 0;
|
|
943
|
+
let consumed = 0;
|
|
944
|
+
while (currentLineIndex < lines.length) {
|
|
945
|
+
const line = lines[currentLineIndex];
|
|
946
|
+
const match = line.match(ORDERED_LIST_ITEM_REGEX);
|
|
947
|
+
if (!match) {
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
const [, indent, number, content] = match;
|
|
951
|
+
const indentLevel = indent.length;
|
|
952
|
+
const itemContentLines = [content];
|
|
953
|
+
let nextLineIndex = currentLineIndex + 1;
|
|
954
|
+
const itemLines = [line];
|
|
955
|
+
let sawBlankLine = false;
|
|
956
|
+
while (nextLineIndex < lines.length) {
|
|
957
|
+
const nextLine = lines[nextLineIndex];
|
|
958
|
+
const nextMatch = nextLine.match(ORDERED_LIST_ITEM_REGEX);
|
|
959
|
+
if (nextMatch) {
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
if (nextLine.trim() === "") {
|
|
963
|
+
itemLines.push(nextLine);
|
|
964
|
+
itemContentLines.push("");
|
|
965
|
+
sawBlankLine = true;
|
|
966
|
+
nextLineIndex += 1;
|
|
967
|
+
} else if (nextLine.match(INDENTED_LINE_REGEX)) {
|
|
968
|
+
itemLines.push(nextLine);
|
|
969
|
+
itemContentLines.push(nextLine.slice(indentLevel + 2));
|
|
970
|
+
nextLineIndex += 1;
|
|
971
|
+
} else {
|
|
972
|
+
if (sawBlankLine) {
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
itemLines.push(nextLine);
|
|
976
|
+
itemContentLines.push(nextLine);
|
|
977
|
+
nextLineIndex += 1;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
listItems.push({
|
|
981
|
+
indent: indentLevel,
|
|
982
|
+
number: parseInt(number, 10),
|
|
983
|
+
content: itemContentLines.join("\n").trim(),
|
|
984
|
+
contentLines: itemContentLines,
|
|
985
|
+
raw: itemLines.join("\n")
|
|
986
|
+
});
|
|
987
|
+
consumed = nextLineIndex;
|
|
988
|
+
currentLineIndex = nextLineIndex;
|
|
989
|
+
}
|
|
990
|
+
return [listItems, consumed];
|
|
991
|
+
}
|
|
992
|
+
function buildNestedStructure(items, baseIndent, lexer) {
|
|
993
|
+
const result = [];
|
|
994
|
+
let currentIndex = 0;
|
|
995
|
+
while (currentIndex < items.length) {
|
|
996
|
+
const item = items[currentIndex];
|
|
997
|
+
if (item.indent === baseIndent) {
|
|
998
|
+
const { paragraphLines, blockLines } = splitItemContent(item.contentLines);
|
|
999
|
+
const mainText = paragraphLines.join("\n").trim();
|
|
1000
|
+
const tokens = [];
|
|
1001
|
+
if (mainText) {
|
|
1002
|
+
tokens.push({
|
|
1003
|
+
type: "paragraph",
|
|
1004
|
+
raw: mainText,
|
|
1005
|
+
tokens: lexer.inlineTokens(mainText)
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
const additionalContent = blockLines.join("\n").trim();
|
|
1009
|
+
if (additionalContent) {
|
|
1010
|
+
const blockTokens = lexer.blockTokens(additionalContent);
|
|
1011
|
+
tokens.push(...blockTokens);
|
|
1012
|
+
}
|
|
1013
|
+
let lookAheadIndex = currentIndex + 1;
|
|
1014
|
+
const nestedItems = [];
|
|
1015
|
+
while (lookAheadIndex < items.length && items[lookAheadIndex].indent > baseIndent) {
|
|
1016
|
+
nestedItems.push(items[lookAheadIndex]);
|
|
1017
|
+
lookAheadIndex += 1;
|
|
1018
|
+
}
|
|
1019
|
+
if (nestedItems.length > 0) {
|
|
1020
|
+
const nextIndent = Math.min(...nestedItems.map((nestedItem) => nestedItem.indent));
|
|
1021
|
+
const nestedListItems = buildNestedStructure(nestedItems, nextIndent, lexer);
|
|
1022
|
+
tokens.push({
|
|
1023
|
+
type: "list",
|
|
1024
|
+
ordered: true,
|
|
1025
|
+
start: nestedItems[0].number,
|
|
1026
|
+
items: nestedListItems,
|
|
1027
|
+
raw: nestedItems.map((nestedItem) => nestedItem.raw).join("\n")
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
result.push({
|
|
1031
|
+
type: "list_item",
|
|
1032
|
+
raw: item.raw,
|
|
1033
|
+
tokens
|
|
1034
|
+
});
|
|
1035
|
+
currentIndex = lookAheadIndex;
|
|
1036
|
+
} else {
|
|
1037
|
+
currentIndex += 1;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return result;
|
|
1041
|
+
}
|
|
1042
|
+
function parseListItems(items, helpers) {
|
|
1043
|
+
return items.map((item) => {
|
|
1044
|
+
if (item.type !== "list_item") {
|
|
1045
|
+
return helpers.parseChildren([item])[0];
|
|
1046
|
+
}
|
|
1047
|
+
const content = [];
|
|
1048
|
+
if (item.tokens && item.tokens.length > 0) {
|
|
1049
|
+
item.tokens.forEach((itemToken) => {
|
|
1050
|
+
if (itemToken.type === "paragraph" || itemToken.type === "list" || itemToken.type === "blockquote" || itemToken.type === "code") {
|
|
1051
|
+
content.push(...helpers.parseChildren([itemToken]));
|
|
1052
|
+
} else if (itemToken.type === "text" && itemToken.tokens) {
|
|
1053
|
+
const inlineContent = helpers.parseChildren([itemToken]);
|
|
1054
|
+
content.push({
|
|
1055
|
+
type: "paragraph",
|
|
1056
|
+
content: inlineContent
|
|
1057
|
+
});
|
|
1058
|
+
} else {
|
|
1059
|
+
const parsed = helpers.parseChildren([itemToken]);
|
|
1060
|
+
if (parsed.length > 0) {
|
|
1061
|
+
content.push(...parsed);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
return {
|
|
1067
|
+
type: "listItem",
|
|
1068
|
+
content
|
|
1069
|
+
};
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
var ListItemName2 = "listItem";
|
|
1073
|
+
var TextStyleName2 = "textStyle";
|
|
1074
|
+
var orderedListInputRegex = /^(\d+)\.\s$/;
|
|
1075
|
+
var OrderedList = Node3.create({
|
|
1076
|
+
name: "orderedList",
|
|
1077
|
+
addOptions() {
|
|
1078
|
+
return {
|
|
1079
|
+
itemTypeName: "listItem",
|
|
1080
|
+
HTMLAttributes: {},
|
|
1081
|
+
keepMarks: false,
|
|
1082
|
+
keepAttributes: false
|
|
1083
|
+
};
|
|
1084
|
+
},
|
|
1085
|
+
group: "block list",
|
|
1086
|
+
content() {
|
|
1087
|
+
return `${this.options.itemTypeName}+`;
|
|
1088
|
+
},
|
|
1089
|
+
addAttributes() {
|
|
1090
|
+
return {
|
|
1091
|
+
start: {
|
|
1092
|
+
default: 1,
|
|
1093
|
+
parseHTML: (element) => {
|
|
1094
|
+
return element.hasAttribute("start") ? parseInt(element.getAttribute("start") || "", 10) : 1;
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1097
|
+
type: {
|
|
1098
|
+
default: null,
|
|
1099
|
+
parseHTML: (element) => element.getAttribute("type")
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
},
|
|
1103
|
+
parseHTML() {
|
|
1104
|
+
return [
|
|
1105
|
+
{
|
|
1106
|
+
tag: "ol"
|
|
1107
|
+
}
|
|
1108
|
+
];
|
|
1109
|
+
},
|
|
1110
|
+
renderHTML({ HTMLAttributes }) {
|
|
1111
|
+
const { start, ...attributesWithoutStart } = HTMLAttributes;
|
|
1112
|
+
return start === 1 ? ["ol", mergeAttributes3(this.options.HTMLAttributes, attributesWithoutStart), 0] : ["ol", mergeAttributes3(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
1113
|
+
},
|
|
1114
|
+
markdownTokenName: "list",
|
|
1115
|
+
parseMarkdown: (token, helpers) => {
|
|
1116
|
+
if (token.type !== "list" || !token.ordered) {
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
const startValue = token.start || 1;
|
|
1120
|
+
const content = token.items ? parseListItems(token.items, helpers) : [];
|
|
1121
|
+
if (startValue !== 1) {
|
|
1122
|
+
return {
|
|
1123
|
+
type: "orderedList",
|
|
1124
|
+
attrs: { start: startValue },
|
|
1125
|
+
content
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
type: "orderedList",
|
|
1130
|
+
content
|
|
1131
|
+
};
|
|
1132
|
+
},
|
|
1133
|
+
renderMarkdown: (node, h) => {
|
|
1134
|
+
if (!node.content) {
|
|
1135
|
+
return "";
|
|
1136
|
+
}
|
|
1137
|
+
return h.renderChildren(node.content, "\n");
|
|
1138
|
+
},
|
|
1139
|
+
markdownTokenizer: {
|
|
1140
|
+
name: "orderedList",
|
|
1141
|
+
level: "block",
|
|
1142
|
+
start: (src) => {
|
|
1143
|
+
const match = src.match(/^(\s*)(\d+)\.\s+/);
|
|
1144
|
+
const index = match == null ? void 0 : match.index;
|
|
1145
|
+
return index !== void 0 ? index : -1;
|
|
1146
|
+
},
|
|
1147
|
+
tokenize: (src, _tokens, lexer) => {
|
|
1148
|
+
var _a;
|
|
1149
|
+
const lines = src.split("\n");
|
|
1150
|
+
const [listItems, consumed] = collectOrderedListItems(lines);
|
|
1151
|
+
if (listItems.length === 0) {
|
|
1152
|
+
return void 0;
|
|
1153
|
+
}
|
|
1154
|
+
const items = buildNestedStructure(listItems, 0, lexer);
|
|
1155
|
+
if (items.length === 0) {
|
|
1156
|
+
return void 0;
|
|
1157
|
+
}
|
|
1158
|
+
const startValue = ((_a = listItems[0]) == null ? void 0 : _a.number) || 1;
|
|
1159
|
+
return {
|
|
1160
|
+
type: "list",
|
|
1161
|
+
ordered: true,
|
|
1162
|
+
start: startValue,
|
|
1163
|
+
items,
|
|
1164
|
+
raw: lines.slice(0, consumed).join("\n")
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
},
|
|
1168
|
+
markdownOptions: {
|
|
1169
|
+
indentsContent: true
|
|
1170
|
+
},
|
|
1171
|
+
addCommands() {
|
|
1172
|
+
return {
|
|
1173
|
+
toggleOrderedList: () => ({ commands, chain }) => {
|
|
1174
|
+
if (this.options.keepAttributes) {
|
|
1175
|
+
return chain().toggleList(this.name, this.options.itemTypeName, this.options.keepMarks).updateAttributes(ListItemName2, this.editor.getAttributes(TextStyleName2)).run();
|
|
1176
|
+
}
|
|
1177
|
+
return commands.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks);
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
},
|
|
1181
|
+
addKeyboardShortcuts() {
|
|
1182
|
+
return {
|
|
1183
|
+
"Mod-Shift-7": () => this.editor.commands.toggleOrderedList()
|
|
1184
|
+
};
|
|
1185
|
+
},
|
|
1186
|
+
addInputRules() {
|
|
1187
|
+
let inputRule = wrappingInputRule22({
|
|
1188
|
+
find: orderedListInputRegex,
|
|
1189
|
+
type: this.type,
|
|
1190
|
+
getAttributes: (match) => ({ start: +match[1] }),
|
|
1191
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1]
|
|
1192
|
+
});
|
|
1193
|
+
if (this.options.keepMarks || this.options.keepAttributes) {
|
|
1194
|
+
inputRule = wrappingInputRule22({
|
|
1195
|
+
find: orderedListInputRegex,
|
|
1196
|
+
type: this.type,
|
|
1197
|
+
keepMarks: this.options.keepMarks,
|
|
1198
|
+
keepAttributes: this.options.keepAttributes,
|
|
1199
|
+
getAttributes: (match) => ({ start: +match[1], ...this.editor.getAttributes(TextStyleName2) }),
|
|
1200
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
|
|
1201
|
+
editor: this.editor
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
return [inputRule];
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
var inputRegex2 = /^\s*(\[([( |x])?\])\s$/;
|
|
1208
|
+
var TaskItem = Node4.create({
|
|
1209
|
+
name: "taskItem",
|
|
1210
|
+
addOptions() {
|
|
1211
|
+
return {
|
|
1212
|
+
nested: false,
|
|
1213
|
+
HTMLAttributes: {},
|
|
1214
|
+
taskListTypeName: "taskList",
|
|
1215
|
+
a11y: void 0
|
|
1216
|
+
};
|
|
1217
|
+
},
|
|
1218
|
+
content() {
|
|
1219
|
+
return this.options.nested ? "paragraph block*" : "paragraph+";
|
|
1220
|
+
},
|
|
1221
|
+
defining: true,
|
|
1222
|
+
addAttributes() {
|
|
1223
|
+
return {
|
|
1224
|
+
checked: {
|
|
1225
|
+
default: false,
|
|
1226
|
+
keepOnSplit: false,
|
|
1227
|
+
parseHTML: (element) => {
|
|
1228
|
+
const dataChecked = element.getAttribute("data-checked");
|
|
1229
|
+
return dataChecked === "" || dataChecked === "true";
|
|
1230
|
+
},
|
|
1231
|
+
renderHTML: (attributes) => ({
|
|
1232
|
+
"data-checked": attributes.checked
|
|
1233
|
+
})
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
},
|
|
1237
|
+
parseHTML() {
|
|
1238
|
+
return [
|
|
1239
|
+
{
|
|
1240
|
+
tag: `li[data-type="${this.name}"]`,
|
|
1241
|
+
priority: 51
|
|
1242
|
+
}
|
|
1243
|
+
];
|
|
1244
|
+
},
|
|
1245
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
1246
|
+
return [
|
|
1247
|
+
"li",
|
|
1248
|
+
mergeAttributes4(this.options.HTMLAttributes, HTMLAttributes, {
|
|
1249
|
+
"data-type": this.name
|
|
1250
|
+
}),
|
|
1251
|
+
[
|
|
1252
|
+
"label",
|
|
1253
|
+
[
|
|
1254
|
+
"input",
|
|
1255
|
+
{
|
|
1256
|
+
type: "checkbox",
|
|
1257
|
+
checked: node.attrs.checked ? "checked" : null
|
|
1258
|
+
}
|
|
1259
|
+
],
|
|
1260
|
+
["span"]
|
|
1261
|
+
],
|
|
1262
|
+
["div", 0]
|
|
1263
|
+
];
|
|
1264
|
+
},
|
|
1265
|
+
parseMarkdown: (token, h) => {
|
|
1266
|
+
const content = [];
|
|
1267
|
+
if (token.tokens && token.tokens.length > 0) {
|
|
1268
|
+
content.push(h.createNode("paragraph", {}, h.parseInline(token.tokens)));
|
|
1269
|
+
} else if (token.text) {
|
|
1270
|
+
content.push(h.createNode("paragraph", {}, [h.createNode("text", { text: token.text })]));
|
|
1271
|
+
} else {
|
|
1272
|
+
content.push(h.createNode("paragraph", {}, []));
|
|
1273
|
+
}
|
|
1274
|
+
if (token.nestedTokens && token.nestedTokens.length > 0) {
|
|
1275
|
+
const nestedContent = h.parseChildren(token.nestedTokens);
|
|
1276
|
+
content.push(...nestedContent);
|
|
1277
|
+
}
|
|
1278
|
+
return h.createNode("taskItem", { checked: token.checked || false }, content);
|
|
1279
|
+
},
|
|
1280
|
+
renderMarkdown: (node, h) => {
|
|
1281
|
+
var _a;
|
|
1282
|
+
const checkedChar = ((_a = node.attrs) == null ? void 0 : _a.checked) ? "x" : " ";
|
|
1283
|
+
const prefix = `- [${checkedChar}] `;
|
|
1284
|
+
return renderNestedMarkdownContent2(node, h, prefix);
|
|
1285
|
+
},
|
|
1286
|
+
addKeyboardShortcuts() {
|
|
1287
|
+
const shortcuts = {
|
|
1288
|
+
Enter: () => this.editor.commands.splitListItem(this.name),
|
|
1289
|
+
"Shift-Tab": () => this.editor.commands.liftListItem(this.name)
|
|
1290
|
+
};
|
|
1291
|
+
if (!this.options.nested) {
|
|
1292
|
+
return shortcuts;
|
|
1293
|
+
}
|
|
1294
|
+
return {
|
|
1295
|
+
...shortcuts,
|
|
1296
|
+
Tab: () => this.editor.commands.sinkListItem(this.name)
|
|
1297
|
+
};
|
|
1298
|
+
},
|
|
1299
|
+
addNodeView() {
|
|
1300
|
+
return ({ node, HTMLAttributes, getPos, editor }) => {
|
|
1301
|
+
const listItem = document.createElement("li");
|
|
1302
|
+
const checkboxWrapper = document.createElement("label");
|
|
1303
|
+
const checkboxStyler = document.createElement("span");
|
|
1304
|
+
const checkbox = document.createElement("input");
|
|
1305
|
+
const content = document.createElement("div");
|
|
1306
|
+
const updateA11Y = (currentNode) => {
|
|
1307
|
+
var _a, _b;
|
|
1308
|
+
checkbox.ariaLabel = ((_b = (_a = this.options.a11y) == null ? void 0 : _a.checkboxLabel) == null ? void 0 : _b.call(_a, currentNode, checkbox.checked)) || `Task item checkbox for ${currentNode.textContent || "empty task item"}`;
|
|
1309
|
+
};
|
|
1310
|
+
updateA11Y(node);
|
|
1311
|
+
checkboxWrapper.contentEditable = "false";
|
|
1312
|
+
checkbox.type = "checkbox";
|
|
1313
|
+
checkbox.addEventListener("mousedown", (event) => event.preventDefault());
|
|
1314
|
+
checkbox.addEventListener("change", (event) => {
|
|
1315
|
+
if (!editor.isEditable && !this.options.onReadOnlyChecked) {
|
|
1316
|
+
checkbox.checked = !checkbox.checked;
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const { checked } = event.target;
|
|
1320
|
+
if (editor.isEditable && typeof getPos === "function") {
|
|
1321
|
+
editor.chain().focus(void 0, { scrollIntoView: false }).command(({ tr }) => {
|
|
1322
|
+
const position = getPos();
|
|
1323
|
+
if (typeof position !== "number") {
|
|
1324
|
+
return false;
|
|
1325
|
+
}
|
|
1326
|
+
const currentNode = tr.doc.nodeAt(position);
|
|
1327
|
+
tr.setNodeMarkup(position, void 0, {
|
|
1328
|
+
...currentNode == null ? void 0 : currentNode.attrs,
|
|
1329
|
+
checked
|
|
1330
|
+
});
|
|
1331
|
+
return true;
|
|
1332
|
+
}).run();
|
|
1333
|
+
}
|
|
1334
|
+
if (!editor.isEditable && this.options.onReadOnlyChecked) {
|
|
1335
|
+
if (!this.options.onReadOnlyChecked(node, checked)) {
|
|
1336
|
+
checkbox.checked = !checkbox.checked;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {
|
|
1341
|
+
listItem.setAttribute(key, value);
|
|
1342
|
+
});
|
|
1343
|
+
listItem.dataset.checked = node.attrs.checked;
|
|
1344
|
+
checkbox.checked = node.attrs.checked;
|
|
1345
|
+
checkboxWrapper.append(checkbox, checkboxStyler);
|
|
1346
|
+
listItem.append(checkboxWrapper, content);
|
|
1347
|
+
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
|
1348
|
+
listItem.setAttribute(key, value);
|
|
1349
|
+
});
|
|
1350
|
+
let prevRenderedAttributeKeys = new Set(Object.keys(HTMLAttributes));
|
|
1351
|
+
return {
|
|
1352
|
+
dom: listItem,
|
|
1353
|
+
contentDOM: content,
|
|
1354
|
+
update: (updatedNode) => {
|
|
1355
|
+
if (updatedNode.type !== this.type) {
|
|
1356
|
+
return false;
|
|
1357
|
+
}
|
|
1358
|
+
listItem.dataset.checked = updatedNode.attrs.checked;
|
|
1359
|
+
checkbox.checked = updatedNode.attrs.checked;
|
|
1360
|
+
updateA11Y(updatedNode);
|
|
1361
|
+
const extensionAttributes = editor.extensionManager.attributes;
|
|
1362
|
+
const newHTMLAttributes = getRenderedAttributes(updatedNode, extensionAttributes);
|
|
1363
|
+
const newKeys = new Set(Object.keys(newHTMLAttributes));
|
|
1364
|
+
const staticAttrs = this.options.HTMLAttributes;
|
|
1365
|
+
prevRenderedAttributeKeys.forEach((key) => {
|
|
1366
|
+
if (!newKeys.has(key)) {
|
|
1367
|
+
if (key in staticAttrs) {
|
|
1368
|
+
listItem.setAttribute(key, staticAttrs[key]);
|
|
1369
|
+
} else {
|
|
1370
|
+
listItem.removeAttribute(key);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
Object.entries(newHTMLAttributes).forEach(([key, value]) => {
|
|
1375
|
+
if (value === null || value === void 0) {
|
|
1376
|
+
if (key in staticAttrs) {
|
|
1377
|
+
listItem.setAttribute(key, staticAttrs[key]);
|
|
1378
|
+
} else {
|
|
1379
|
+
listItem.removeAttribute(key);
|
|
1380
|
+
}
|
|
1381
|
+
} else {
|
|
1382
|
+
listItem.setAttribute(key, value);
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
prevRenderedAttributeKeys = newKeys;
|
|
1386
|
+
return true;
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
};
|
|
1390
|
+
},
|
|
1391
|
+
addInputRules() {
|
|
1392
|
+
return [
|
|
1393
|
+
wrappingInputRule3({
|
|
1394
|
+
find: inputRegex2,
|
|
1395
|
+
type: this.type,
|
|
1396
|
+
getAttributes: (match) => ({
|
|
1397
|
+
checked: match[match.length - 1] === "x"
|
|
1398
|
+
})
|
|
1399
|
+
})
|
|
1400
|
+
];
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
var TaskList = Node5.create({
|
|
1404
|
+
name: "taskList",
|
|
1405
|
+
addOptions() {
|
|
1406
|
+
return {
|
|
1407
|
+
itemTypeName: "taskItem",
|
|
1408
|
+
HTMLAttributes: {}
|
|
1409
|
+
};
|
|
1410
|
+
},
|
|
1411
|
+
group: "block list",
|
|
1412
|
+
content() {
|
|
1413
|
+
return `${this.options.itemTypeName}+`;
|
|
1414
|
+
},
|
|
1415
|
+
parseHTML() {
|
|
1416
|
+
return [
|
|
1417
|
+
{
|
|
1418
|
+
tag: `ul[data-type="${this.name}"]`,
|
|
1419
|
+
priority: 51
|
|
1420
|
+
}
|
|
1421
|
+
];
|
|
1422
|
+
},
|
|
1423
|
+
renderHTML({ HTMLAttributes }) {
|
|
1424
|
+
return ["ul", mergeAttributes5(this.options.HTMLAttributes, HTMLAttributes, { "data-type": this.name }), 0];
|
|
1425
|
+
},
|
|
1426
|
+
parseMarkdown: (token, h) => {
|
|
1427
|
+
return h.createNode("taskList", {}, h.parseChildren(token.items || []));
|
|
1428
|
+
},
|
|
1429
|
+
renderMarkdown: (node, h) => {
|
|
1430
|
+
if (!node.content) {
|
|
1431
|
+
return "";
|
|
1432
|
+
}
|
|
1433
|
+
return h.renderChildren(node.content, "\n");
|
|
1434
|
+
},
|
|
1435
|
+
markdownTokenizer: {
|
|
1436
|
+
name: "taskList",
|
|
1437
|
+
level: "block",
|
|
1438
|
+
start(src) {
|
|
1439
|
+
var _a;
|
|
1440
|
+
const index = (_a = src.match(/^\s*[-+*]\s+\[([ xX])\]\s+/)) == null ? void 0 : _a.index;
|
|
1441
|
+
return index !== void 0 ? index : -1;
|
|
1442
|
+
},
|
|
1443
|
+
tokenize(src, tokens, lexer) {
|
|
1444
|
+
const parseTaskListContent = (content) => {
|
|
1445
|
+
const nestedResult = parseIndentedBlocks(
|
|
1446
|
+
content,
|
|
1447
|
+
{
|
|
1448
|
+
itemPattern: /^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/,
|
|
1449
|
+
extractItemData: (match) => ({
|
|
1450
|
+
indentLevel: match[1].length,
|
|
1451
|
+
mainContent: match[4],
|
|
1452
|
+
checked: match[3].toLowerCase() === "x"
|
|
1453
|
+
}),
|
|
1454
|
+
createToken: (data, nestedTokens) => ({
|
|
1455
|
+
type: "taskItem",
|
|
1456
|
+
raw: "",
|
|
1457
|
+
mainContent: data.mainContent,
|
|
1458
|
+
indentLevel: data.indentLevel,
|
|
1459
|
+
checked: data.checked,
|
|
1460
|
+
text: data.mainContent,
|
|
1461
|
+
tokens: lexer.inlineTokens(data.mainContent),
|
|
1462
|
+
nestedTokens
|
|
1463
|
+
}),
|
|
1464
|
+
// Allow recursive nesting
|
|
1465
|
+
customNestedParser: parseTaskListContent
|
|
1466
|
+
},
|
|
1467
|
+
lexer
|
|
1468
|
+
);
|
|
1469
|
+
if (nestedResult) {
|
|
1470
|
+
return [
|
|
1471
|
+
{
|
|
1472
|
+
type: "taskList",
|
|
1473
|
+
raw: nestedResult.raw,
|
|
1474
|
+
items: nestedResult.items
|
|
1475
|
+
}
|
|
1476
|
+
];
|
|
1477
|
+
}
|
|
1478
|
+
return lexer.blockTokens(content);
|
|
1479
|
+
};
|
|
1480
|
+
const result = parseIndentedBlocks(
|
|
1481
|
+
src,
|
|
1482
|
+
{
|
|
1483
|
+
itemPattern: /^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/,
|
|
1484
|
+
extractItemData: (match) => ({
|
|
1485
|
+
indentLevel: match[1].length,
|
|
1486
|
+
mainContent: match[4],
|
|
1487
|
+
checked: match[3].toLowerCase() === "x"
|
|
1488
|
+
}),
|
|
1489
|
+
createToken: (data, nestedTokens) => ({
|
|
1490
|
+
type: "taskItem",
|
|
1491
|
+
raw: "",
|
|
1492
|
+
mainContent: data.mainContent,
|
|
1493
|
+
indentLevel: data.indentLevel,
|
|
1494
|
+
checked: data.checked,
|
|
1495
|
+
text: data.mainContent,
|
|
1496
|
+
tokens: lexer.inlineTokens(data.mainContent),
|
|
1497
|
+
nestedTokens
|
|
1498
|
+
}),
|
|
1499
|
+
// Use the recursive parser for nested content
|
|
1500
|
+
customNestedParser: parseTaskListContent
|
|
1501
|
+
},
|
|
1502
|
+
lexer
|
|
1503
|
+
);
|
|
1504
|
+
if (!result) {
|
|
1505
|
+
return void 0;
|
|
1506
|
+
}
|
|
1507
|
+
return {
|
|
1508
|
+
type: "taskList",
|
|
1509
|
+
raw: result.raw,
|
|
1510
|
+
items: result.items
|
|
1511
|
+
};
|
|
367
1512
|
}
|
|
1513
|
+
},
|
|
1514
|
+
markdownOptions: {
|
|
1515
|
+
indentsContent: true
|
|
1516
|
+
},
|
|
1517
|
+
addCommands() {
|
|
1518
|
+
return {
|
|
1519
|
+
toggleTaskList: () => ({ commands }) => {
|
|
1520
|
+
return commands.toggleList(this.name, this.options.itemTypeName);
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
},
|
|
1524
|
+
addKeyboardShortcuts() {
|
|
1525
|
+
return {
|
|
1526
|
+
"Mod-Shift-9": () => this.editor.commands.toggleTaskList()
|
|
1527
|
+
};
|
|
368
1528
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
1529
|
+
});
|
|
1530
|
+
var ListKit = Extension2.create({
|
|
1531
|
+
name: "listKit",
|
|
1532
|
+
addExtensions() {
|
|
1533
|
+
const extensions = [];
|
|
1534
|
+
if (this.options.bulletList !== false) {
|
|
1535
|
+
extensions.push(BulletList.configure(this.options.bulletList));
|
|
1536
|
+
}
|
|
1537
|
+
if (this.options.listItem !== false) {
|
|
1538
|
+
extensions.push(ListItem.configure(this.options.listItem));
|
|
1539
|
+
}
|
|
1540
|
+
if (this.options.listKeymap !== false) {
|
|
1541
|
+
extensions.push(ListKeymap.configure(this.options.listKeymap));
|
|
1542
|
+
}
|
|
1543
|
+
if (this.options.orderedList !== false) {
|
|
1544
|
+
extensions.push(OrderedList.configure(this.options.orderedList));
|
|
1545
|
+
}
|
|
1546
|
+
if (this.options.taskItem !== false) {
|
|
1547
|
+
extensions.push(TaskItem.configure(this.options.taskItem));
|
|
1548
|
+
}
|
|
1549
|
+
if (this.options.taskList !== false) {
|
|
1550
|
+
extensions.push(TaskList.configure(this.options.taskList));
|
|
1551
|
+
}
|
|
1552
|
+
return extensions;
|
|
372
1553
|
}
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
// src/roomState.ts
|
|
376
|
-
import WebSocket from "ws";
|
|
1554
|
+
});
|
|
377
1555
|
|
|
378
1556
|
// ../shared/src/editor-extensions.ts
|
|
379
|
-
import
|
|
380
|
-
import
|
|
381
|
-
import CodeBlock from "@tiptap/extension-code-block";
|
|
382
|
-
import TaskList from "@tiptap/extension-task-list";
|
|
383
|
-
import TaskItem from "@tiptap/extension-task-item";
|
|
1557
|
+
import TaskList2 from "@tiptap/extension-task-list";
|
|
1558
|
+
import TaskItem2 from "@tiptap/extension-task-item";
|
|
384
1559
|
import Highlight from "@tiptap/extension-highlight";
|
|
385
1560
|
import Subscript from "@tiptap/extension-subscript";
|
|
386
1561
|
import Superscript from "@tiptap/extension-superscript";
|
|
@@ -479,7 +1654,7 @@ var ImageBlockExtension = Image.extend({
|
|
|
479
1654
|
var imageBlockExtensions_default = ImageBlockExtension;
|
|
480
1655
|
|
|
481
1656
|
// ../shared/src/videoBlockNode.ts
|
|
482
|
-
import { Node, mergeAttributes } from "@tiptap/core";
|
|
1657
|
+
import { Node as Node6, mergeAttributes as mergeAttributes6 } from "@tiptap/core";
|
|
483
1658
|
|
|
484
1659
|
// ../shared/src/embedParser.ts
|
|
485
1660
|
var YOUTUBE_HOSTS = /* @__PURE__ */ new Set([
|
|
@@ -581,7 +1756,7 @@ function detectVideoProvider(url) {
|
|
|
581
1756
|
}
|
|
582
1757
|
}
|
|
583
1758
|
var IFRAME_SANDBOX = "allow-scripts allow-same-origin allow-presentation allow-popups";
|
|
584
|
-
var VideoBlockNode =
|
|
1759
|
+
var VideoBlockNode = Node6.create({
|
|
585
1760
|
name: "videoBlock",
|
|
586
1761
|
group: "block",
|
|
587
1762
|
addOptions() {
|
|
@@ -696,7 +1871,7 @@ var VideoBlockNode = Node.create({
|
|
|
696
1871
|
renderHTML({ node, HTMLAttributes }) {
|
|
697
1872
|
const attrs = node.attrs;
|
|
698
1873
|
if (attrs.src) {
|
|
699
|
-
const merged =
|
|
1874
|
+
const merged = mergeAttributes6(HTMLAttributes, {
|
|
700
1875
|
src: attrs.src,
|
|
701
1876
|
controls: ""
|
|
702
1877
|
});
|
|
@@ -707,7 +1882,7 @@ var VideoBlockNode = Node.create({
|
|
|
707
1882
|
return ["video", merged];
|
|
708
1883
|
}
|
|
709
1884
|
if (attrs.embedUrl) {
|
|
710
|
-
const merged =
|
|
1885
|
+
const merged = mergeAttributes6(HTMLAttributes, {
|
|
711
1886
|
src: attrs.embedUrl,
|
|
712
1887
|
sandbox: IFRAME_SANDBOX,
|
|
713
1888
|
frameborder: "0",
|
|
@@ -720,7 +1895,7 @@ var VideoBlockNode = Node.create({
|
|
|
720
1895
|
}
|
|
721
1896
|
return ["iframe", merged];
|
|
722
1897
|
}
|
|
723
|
-
return ["div",
|
|
1898
|
+
return ["div", mergeAttributes6(HTMLAttributes, { "data-video-error": "missing-src" })];
|
|
724
1899
|
},
|
|
725
1900
|
/**
|
|
726
1901
|
* Link-first markdown serialization (Phase 2 Unit 3):
|
|
@@ -771,7 +1946,7 @@ function filenameFromUrl(url) {
|
|
|
771
1946
|
var videoBlockNode_default = VideoBlockNode;
|
|
772
1947
|
|
|
773
1948
|
// ../shared/src/mediaKeyboardNav.ts
|
|
774
|
-
import { Extension } from "@tiptap/core";
|
|
1949
|
+
import { Extension as Extension3 } from "@tiptap/core";
|
|
775
1950
|
|
|
776
1951
|
// ../node_modules/prosemirror-model/dist/index.js
|
|
777
1952
|
function findDiffStart(a, b, pos) {
|
|
@@ -1805,7 +2980,7 @@ var NodeRange = class {
|
|
|
1805
2980
|
}
|
|
1806
2981
|
};
|
|
1807
2982
|
var emptyAttrs = /* @__PURE__ */ Object.create(null);
|
|
1808
|
-
var
|
|
2983
|
+
var Node7 = class _Node {
|
|
1809
2984
|
/**
|
|
1810
2985
|
@internal
|
|
1811
2986
|
*/
|
|
@@ -2206,7 +3381,7 @@ var Node2 = class _Node {
|
|
|
2206
3381
|
return node;
|
|
2207
3382
|
}
|
|
2208
3383
|
};
|
|
2209
|
-
|
|
3384
|
+
Node7.prototype.text = void 0;
|
|
2210
3385
|
function wrapMarks(marks, str) {
|
|
2211
3386
|
for (let i = marks.length - 1; i >= 0; i--)
|
|
2212
3387
|
str = marks[i].type.name + "(" + str + ")";
|
|
@@ -3846,7 +5021,7 @@ var MEDIA_NODE_TYPES = /* @__PURE__ */ new Set(["image", "videoBlock"]);
|
|
|
3846
5021
|
function isMediaNodeSelection(sel) {
|
|
3847
5022
|
return sel instanceof NodeSelection && MEDIA_NODE_TYPES.has(sel.node.type.name);
|
|
3848
5023
|
}
|
|
3849
|
-
var MediaKeyboardNav =
|
|
5024
|
+
var MediaKeyboardNav = Extension3.create({
|
|
3850
5025
|
name: "mediaKeyboardNav",
|
|
3851
5026
|
addProseMirrorPlugins() {
|
|
3852
5027
|
return [
|
|
@@ -3890,8 +5065,251 @@ var MediaKeyboardNav = Extension.create({
|
|
|
3890
5065
|
});
|
|
3891
5066
|
var mediaKeyboardNav_default = MediaKeyboardNav;
|
|
3892
5067
|
|
|
5068
|
+
// ../shared/src/inlineIconNode.ts
|
|
5069
|
+
import { Node as Node8, mergeAttributes as mergeAttributes7 } from "@tiptap/core";
|
|
5070
|
+
var INLINE_ICON_ALLOWLIST = [
|
|
5071
|
+
"message-square-plus",
|
|
5072
|
+
"pen-line"
|
|
5073
|
+
];
|
|
5074
|
+
function isInlineIconName(value) {
|
|
5075
|
+
return INLINE_ICON_ALLOWLIST.includes(value);
|
|
5076
|
+
}
|
|
5077
|
+
var INLINE_ICON_NODE_NAME = "inlineIcon";
|
|
5078
|
+
var InlineIconNode = Node8.create({
|
|
5079
|
+
name: INLINE_ICON_NODE_NAME,
|
|
5080
|
+
group: "inline",
|
|
5081
|
+
inline: true,
|
|
5082
|
+
atom: true,
|
|
5083
|
+
selectable: false,
|
|
5084
|
+
addAttributes() {
|
|
5085
|
+
return {
|
|
5086
|
+
name: {
|
|
5087
|
+
default: null,
|
|
5088
|
+
parseHTML: (el) => {
|
|
5089
|
+
const raw = el.getAttribute("data-icon");
|
|
5090
|
+
return raw && isInlineIconName(raw) ? raw : null;
|
|
5091
|
+
},
|
|
5092
|
+
renderHTML: (attrs) => attrs.name ? { "data-icon": attrs.name } : {}
|
|
5093
|
+
}
|
|
5094
|
+
};
|
|
5095
|
+
},
|
|
5096
|
+
parseHTML() {
|
|
5097
|
+
return [{ tag: "span[data-inline-icon]" }];
|
|
5098
|
+
},
|
|
5099
|
+
renderHTML({ HTMLAttributes }) {
|
|
5100
|
+
return [
|
|
5101
|
+
"span",
|
|
5102
|
+
mergeAttributes7(HTMLAttributes, {
|
|
5103
|
+
"data-inline-icon": "true",
|
|
5104
|
+
class: "inline-icon"
|
|
5105
|
+
})
|
|
5106
|
+
];
|
|
5107
|
+
},
|
|
5108
|
+
renderMarkdown(node) {
|
|
5109
|
+
const name = node.attrs?.name;
|
|
5110
|
+
return name && isInlineIconName(name) ? `:icon[${name}]` : "";
|
|
5111
|
+
}
|
|
5112
|
+
});
|
|
5113
|
+
|
|
5114
|
+
// ../shared/src/markdown.ts
|
|
5115
|
+
import { getSchema } from "@tiptap/core";
|
|
5116
|
+
import { MarkdownManager } from "@tiptap/markdown";
|
|
5117
|
+
import {
|
|
5118
|
+
prosemirrorJSONToYXmlFragment,
|
|
5119
|
+
yXmlFragmentToProsemirrorJSON
|
|
5120
|
+
} from "@tiptap/y-tiptap";
|
|
5121
|
+
|
|
5122
|
+
// ../shared/src/frontmatter.ts
|
|
5123
|
+
function extractFrontmatter(text) {
|
|
5124
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
5125
|
+
if (!match) return null;
|
|
5126
|
+
return { yaml: match[1].trim(), body: match[2] };
|
|
5127
|
+
}
|
|
5128
|
+
|
|
5129
|
+
// ../shared/src/markdown.ts
|
|
5130
|
+
var EMPTY_LIST_ITEM_SENTINEL = "<!-- composer-empty-list-item -->";
|
|
5131
|
+
function markdownToDocJSON(markdown, extensions = editorExtensions) {
|
|
5132
|
+
const manager = new MarkdownManager({ extensions });
|
|
5133
|
+
const parsed = manager.parse(markdown);
|
|
5134
|
+
const doc = parsed && parsed.type === "doc" ? parsed : { type: "doc", content: parsed?.content ?? [] };
|
|
5135
|
+
repairListItemSchema(doc);
|
|
5136
|
+
return doc;
|
|
5137
|
+
}
|
|
5138
|
+
function repairListItemSchema(node) {
|
|
5139
|
+
if (node.type === "listItem" || node.type === "taskItem") {
|
|
5140
|
+
if (!node.content || node.content.length === 0) {
|
|
5141
|
+
node.content = [{ type: "paragraph" }];
|
|
5142
|
+
return;
|
|
5143
|
+
}
|
|
5144
|
+
if (node.content[0]?.type !== "paragraph") {
|
|
5145
|
+
node.content = [{ type: "paragraph" }, ...node.content];
|
|
5146
|
+
}
|
|
5147
|
+
const first = node.content[0];
|
|
5148
|
+
if (first?.type === "paragraph" && paragraphIsEmptyListItemSentinel(first)) {
|
|
5149
|
+
node.content = [{ type: "paragraph" }, ...node.content.slice(1)];
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
if (node.content) {
|
|
5153
|
+
for (const child of node.content) repairListItemSchema(child);
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
function paragraphIsEmptyListItemSentinel(paragraph) {
|
|
5157
|
+
if (!paragraph.content || paragraph.content.length === 0) return false;
|
|
5158
|
+
let sawSentinel = false;
|
|
5159
|
+
for (const child of paragraph.content) {
|
|
5160
|
+
if (child.type === "paragraph" && (!child.content || child.content.length === 0)) {
|
|
5161
|
+
sawSentinel = true;
|
|
5162
|
+
continue;
|
|
5163
|
+
}
|
|
5164
|
+
if (child.type !== "text") return false;
|
|
5165
|
+
if (child.text === EMPTY_LIST_ITEM_SENTINEL) {
|
|
5166
|
+
sawSentinel = true;
|
|
5167
|
+
continue;
|
|
5168
|
+
}
|
|
5169
|
+
if ((child.text ?? "").trim() !== "") return false;
|
|
5170
|
+
}
|
|
5171
|
+
return sawSentinel;
|
|
5172
|
+
}
|
|
5173
|
+
function writeMarkdownToYFragment(fragment, markdown, extensions = editorExtensions) {
|
|
5174
|
+
const doc = fragment.doc;
|
|
5175
|
+
if (!doc) throw new Error("fragment must be attached to a Y.Doc");
|
|
5176
|
+
const fm = extractFrontmatter(markdown);
|
|
5177
|
+
const body = fm ? fm.body : markdown;
|
|
5178
|
+
const schema = getSchema(extensions);
|
|
5179
|
+
const bodyDoc = markdownToDocJSON(body, extensions);
|
|
5180
|
+
const children = [];
|
|
5181
|
+
if (fm) {
|
|
5182
|
+
children.push({
|
|
5183
|
+
type: "frontmatter",
|
|
5184
|
+
attrs: { language: "yaml" },
|
|
5185
|
+
content: fm.yaml ? [{ type: "text", text: fm.yaml }] : void 0
|
|
5186
|
+
});
|
|
5187
|
+
}
|
|
5188
|
+
if (bodyDoc.content) children.push(...bodyDoc.content);
|
|
5189
|
+
const fullDoc = { type: "doc", content: children };
|
|
5190
|
+
doc.transact(() => {
|
|
5191
|
+
prosemirrorJSONToYXmlFragment(schema, fullDoc, fragment);
|
|
5192
|
+
});
|
|
5193
|
+
}
|
|
5194
|
+
function markdownFromYFragment(fragment, extensions = editorExtensions) {
|
|
5195
|
+
const json = yXmlFragmentToProsemirrorJSON(fragment);
|
|
5196
|
+
if (!json || !json.content || json.content.length === 0) return "";
|
|
5197
|
+
const manager = new MarkdownManager({ extensions });
|
|
5198
|
+
return manager.serialize(json);
|
|
5199
|
+
}
|
|
5200
|
+
|
|
3893
5201
|
// ../shared/src/editor-extensions.ts
|
|
3894
5202
|
var CodeWithCombinableMarks = Code.extend({ excludes: "" });
|
|
5203
|
+
var markText = (node) => node.content?.[0]?.text ?? "";
|
|
5204
|
+
var SubscriptWithMarkdown = Subscript.extend({
|
|
5205
|
+
renderMarkdown: (node) => `<sub>${markText(node)}</sub>`
|
|
5206
|
+
});
|
|
5207
|
+
var SuperscriptWithMarkdown = Superscript.extend({
|
|
5208
|
+
renderMarkdown: (node) => `<sup>${markText(node)}</sup>`
|
|
5209
|
+
});
|
|
5210
|
+
var BlockquoteWithStableEmptyMarkdown = Blockquote.extend({
|
|
5211
|
+
renderMarkdown: (node, h) => {
|
|
5212
|
+
if (!node.content) return "";
|
|
5213
|
+
const rendered = node.content.map((child, index) => {
|
|
5214
|
+
const childContent = h.renderChild?.(child, index) ?? h.renderChildren([child]);
|
|
5215
|
+
return childContent.split("\n").map((line) => line.trim() === "" ? ">" : `> ${line}`).join("\n");
|
|
5216
|
+
});
|
|
5217
|
+
const nonEmpty = rendered.filter((part) => part.replace(/^>\s*/gm, "").trim() !== "");
|
|
5218
|
+
if (nonEmpty.length === 0) return "";
|
|
5219
|
+
return nonEmpty.join("\n>\n");
|
|
5220
|
+
}
|
|
5221
|
+
});
|
|
5222
|
+
function renderStableListItemMarkdown(node, h, ctx = {}) {
|
|
5223
|
+
if (!node.content) return "";
|
|
5224
|
+
const prefix = ctx.itemPrefix ?? (ctx.parentType === "orderedList" ? `${(ctx.meta?.parentAttrs?.start ?? 1) + (ctx.index ?? 0)}. ` : "- ");
|
|
5225
|
+
const [content, ...children] = node.content;
|
|
5226
|
+
const renderedMain = content ? h.renderChildren([content]) : "";
|
|
5227
|
+
const mainContent = renderedMain.trim() === "" ? EMPTY_LIST_ITEM_SENTINEL : renderedMain;
|
|
5228
|
+
let output = `${prefix}${mainContent}`;
|
|
5229
|
+
for (const [index, child] of children.entries()) {
|
|
5230
|
+
const childContent = h.renderChild?.(child, index + 1) ?? h.renderChildren([child]);
|
|
5231
|
+
const indentedChild = childContent.split("\n").map((line) => h.indent(line || "")).join("\n");
|
|
5232
|
+
output += child.type === "paragraph" ? `
|
|
5233
|
+
|
|
5234
|
+
${indentedChild}` : `
|
|
5235
|
+
${indentedChild}`;
|
|
5236
|
+
}
|
|
5237
|
+
return output;
|
|
5238
|
+
}
|
|
5239
|
+
var ListItemWithStableEmptyMarkdown = ListItem.extend({
|
|
5240
|
+
renderMarkdown: renderStableListItemMarkdown
|
|
5241
|
+
});
|
|
5242
|
+
var TaskItemWithStableEmptyMarkdown = TaskItem2.extend({
|
|
5243
|
+
renderMarkdown: (node, h) => renderStableListItemMarkdown(node, h, {
|
|
5244
|
+
itemPrefix: `- [${node.attrs?.checked ? "x" : " "}] `
|
|
5245
|
+
})
|
|
5246
|
+
});
|
|
5247
|
+
function normalizeAlign(value) {
|
|
5248
|
+
return value === "left" || value === "right" || value === "center" ? value : null;
|
|
5249
|
+
}
|
|
5250
|
+
function sanitizeCellText(raw) {
|
|
5251
|
+
return raw.replace(/\r?\n/g, "<br>").replace(/\s+/g, " ").trim().replace(/\|/g, "\\|");
|
|
5252
|
+
}
|
|
5253
|
+
function renderTableCellSafeMarkdown(node, h) {
|
|
5254
|
+
if (!node?.content || node.content.length === 0) return "";
|
|
5255
|
+
const rows = [];
|
|
5256
|
+
for (const rowNode of node.content) {
|
|
5257
|
+
const cells = [];
|
|
5258
|
+
for (const cellNode of rowNode.content ?? []) {
|
|
5259
|
+
const children = cellNode.content;
|
|
5260
|
+
let raw = "";
|
|
5261
|
+
if (Array.isArray(children) && children.length > 1) {
|
|
5262
|
+
raw = children.map((c) => sanitizeCellText(h.renderChildren(c))).join("<br>");
|
|
5263
|
+
} else {
|
|
5264
|
+
raw = sanitizeCellText(children ? h.renderChildren(children) : "");
|
|
5265
|
+
}
|
|
5266
|
+
cells.push({
|
|
5267
|
+
text: raw,
|
|
5268
|
+
isHeader: cellNode.type === "tableHeader",
|
|
5269
|
+
align: normalizeAlign(cellNode.attrs?.align)
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
rows.push(cells);
|
|
5273
|
+
}
|
|
5274
|
+
const columnCount = rows.reduce((max, r) => Math.max(max, r.length), 0);
|
|
5275
|
+
if (columnCount === 0) return "";
|
|
5276
|
+
const colWidths = new Array(columnCount).fill(0);
|
|
5277
|
+
for (const r of rows) {
|
|
5278
|
+
for (let i = 0; i < columnCount; i += 1) {
|
|
5279
|
+
const len = (r[i]?.text || "").length;
|
|
5280
|
+
if (len > colWidths[i]) colWidths[i] = len;
|
|
5281
|
+
if (colWidths[i] < 3) colWidths[i] = 3;
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
const pad = (s, width) => s + " ".repeat(Math.max(0, width - s.length));
|
|
5285
|
+
const headerRow = rows[0];
|
|
5286
|
+
const hasHeader = headerRow.some((c) => c.isHeader);
|
|
5287
|
+
const colAlignments = new Array(columnCount).fill(null);
|
|
5288
|
+
for (const r of rows) {
|
|
5289
|
+
for (let i = 0; i < columnCount; i += 1) {
|
|
5290
|
+
if (!colAlignments[i] && r[i]?.align) colAlignments[i] = r[i].align;
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
let out = "\n";
|
|
5294
|
+
const headerTexts = new Array(columnCount).fill(0).map((_, i) => hasHeader ? headerRow[i]?.text || "" : "");
|
|
5295
|
+
out += `| ${headerTexts.map((t, i) => pad(t, colWidths[i])).join(" | ")} |
|
|
5296
|
+
`;
|
|
5297
|
+
out += `| ${colWidths.map((w, index) => {
|
|
5298
|
+
const dashCount = Math.max(3, w);
|
|
5299
|
+
const a = colAlignments[index];
|
|
5300
|
+
if (a === "left") return `:${"-".repeat(dashCount)}`;
|
|
5301
|
+
if (a === "right") return `${"-".repeat(dashCount)}:`;
|
|
5302
|
+
if (a === "center") return `:${"-".repeat(dashCount)}:`;
|
|
5303
|
+
return "-".repeat(dashCount);
|
|
5304
|
+
}).join(" | ")} |
|
|
5305
|
+
`;
|
|
5306
|
+
const body = hasHeader ? rows.slice(1) : rows;
|
|
5307
|
+
for (const r of body) {
|
|
5308
|
+
out += `| ${new Array(columnCount).fill(0).map((_, i) => pad(r[i]?.text || "", colWidths[i])).join(" | ")} |
|
|
5309
|
+
`;
|
|
5310
|
+
}
|
|
5311
|
+
return out;
|
|
5312
|
+
}
|
|
3895
5313
|
var TableWithId = Table.extend({
|
|
3896
5314
|
addAttributes() {
|
|
3897
5315
|
return {
|
|
@@ -3902,7 +5320,8 @@ var TableWithId = Table.extend({
|
|
|
3902
5320
|
renderHTML: (attrs) => attrs.tableId ? { "data-table-id": attrs.tableId } : {}
|
|
3903
5321
|
}
|
|
3904
5322
|
};
|
|
3905
|
-
}
|
|
5323
|
+
},
|
|
5324
|
+
renderMarkdown: renderTableCellSafeMarkdown
|
|
3906
5325
|
});
|
|
3907
5326
|
var FrontmatterSchema = CodeBlock.extend({
|
|
3908
5327
|
name: "frontmatter",
|
|
@@ -3920,10 +5339,13 @@ ${text}
|
|
|
3920
5339
|
function buildEditorExtensions(opts = {}) {
|
|
3921
5340
|
const table = opts.table ?? TableWithId;
|
|
3922
5341
|
const frontmatter = opts.frontmatter ?? FrontmatterSchema;
|
|
5342
|
+
const inlineIcon = opts.inlineIcon ?? InlineIconNode;
|
|
3923
5343
|
return [
|
|
3924
5344
|
StarterKit.configure({
|
|
3925
5345
|
undoRedo: false,
|
|
5346
|
+
blockquote: false,
|
|
3926
5347
|
code: false,
|
|
5348
|
+
listItem: false,
|
|
3927
5349
|
link: {
|
|
3928
5350
|
openOnClick: true,
|
|
3929
5351
|
HTMLAttributes: { target: "_blank", rel: "noopener noreferrer" },
|
|
@@ -3931,14 +5353,16 @@ function buildEditorExtensions(opts = {}) {
|
|
|
3931
5353
|
}
|
|
3932
5354
|
}),
|
|
3933
5355
|
CodeWithCombinableMarks,
|
|
5356
|
+
BlockquoteWithStableEmptyMarkdown,
|
|
5357
|
+
ListItemWithStableEmptyMarkdown,
|
|
3934
5358
|
imageBlockExtensions_default.configure({ roomId: opts.roomId, awareness: opts.awareness }),
|
|
3935
5359
|
videoBlockNode_default.configure({ roomId: opts.roomId, awareness: opts.awareness }),
|
|
3936
5360
|
mediaKeyboardNav_default,
|
|
3937
|
-
|
|
3938
|
-
|
|
5361
|
+
TaskList2,
|
|
5362
|
+
TaskItemWithStableEmptyMarkdown.configure({ nested: true }),
|
|
3939
5363
|
Highlight,
|
|
3940
|
-
|
|
3941
|
-
|
|
5364
|
+
SubscriptWithMarkdown,
|
|
5365
|
+
SuperscriptWithMarkdown,
|
|
3942
5366
|
table.configure({
|
|
3943
5367
|
resizable: true,
|
|
3944
5368
|
handleWidth: 5,
|
|
@@ -3949,59 +5373,12 @@ function buildEditorExtensions(opts = {}) {
|
|
|
3949
5373
|
TableRow,
|
|
3950
5374
|
TableCell,
|
|
3951
5375
|
TableHeader,
|
|
3952
|
-
frontmatter
|
|
5376
|
+
frontmatter,
|
|
5377
|
+
inlineIcon
|
|
3953
5378
|
];
|
|
3954
5379
|
}
|
|
3955
5380
|
var editorExtensions = buildEditorExtensions();
|
|
3956
5381
|
|
|
3957
|
-
// ../shared/src/frontmatter.ts
|
|
3958
|
-
function extractFrontmatter(text) {
|
|
3959
|
-
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
3960
|
-
if (!match) return null;
|
|
3961
|
-
return { yaml: match[1].trim(), body: match[2] };
|
|
3962
|
-
}
|
|
3963
|
-
|
|
3964
|
-
// ../shared/src/markdown.ts
|
|
3965
|
-
import { getSchema } from "@tiptap/core";
|
|
3966
|
-
import { MarkdownManager } from "@tiptap/markdown";
|
|
3967
|
-
import {
|
|
3968
|
-
prosemirrorJSONToYXmlFragment,
|
|
3969
|
-
yXmlFragmentToProsemirrorJSON
|
|
3970
|
-
} from "@tiptap/y-tiptap";
|
|
3971
|
-
function markdownToDocJSON(markdown, extensions = editorExtensions) {
|
|
3972
|
-
const manager = new MarkdownManager({ extensions });
|
|
3973
|
-
const parsed = manager.parse(markdown);
|
|
3974
|
-
if (parsed && parsed.type === "doc") return parsed;
|
|
3975
|
-
return { type: "doc", content: parsed?.content ?? [] };
|
|
3976
|
-
}
|
|
3977
|
-
function writeMarkdownToYFragment(fragment, markdown, extensions = editorExtensions) {
|
|
3978
|
-
const doc = fragment.doc;
|
|
3979
|
-
if (!doc) throw new Error("fragment must be attached to a Y.Doc");
|
|
3980
|
-
const fm = extractFrontmatter(markdown);
|
|
3981
|
-
const body = fm ? fm.body : markdown;
|
|
3982
|
-
const schema = getSchema(extensions);
|
|
3983
|
-
const bodyDoc = markdownToDocJSON(body, extensions);
|
|
3984
|
-
const children = [];
|
|
3985
|
-
if (fm) {
|
|
3986
|
-
children.push({
|
|
3987
|
-
type: "frontmatter",
|
|
3988
|
-
attrs: { language: "yaml" },
|
|
3989
|
-
content: fm.yaml ? [{ type: "text", text: fm.yaml }] : void 0
|
|
3990
|
-
});
|
|
3991
|
-
}
|
|
3992
|
-
if (bodyDoc.content) children.push(...bodyDoc.content);
|
|
3993
|
-
const fullDoc = { type: "doc", content: children };
|
|
3994
|
-
doc.transact(() => {
|
|
3995
|
-
prosemirrorJSONToYXmlFragment(schema, fullDoc, fragment);
|
|
3996
|
-
});
|
|
3997
|
-
}
|
|
3998
|
-
function markdownFromYFragment(fragment, extensions = editorExtensions) {
|
|
3999
|
-
const json = yXmlFragmentToProsemirrorJSON(fragment);
|
|
4000
|
-
if (!json || !json.content || json.content.length === 0) return "";
|
|
4001
|
-
const manager = new MarkdownManager({ extensions });
|
|
4002
|
-
return manager.serialize(json);
|
|
4003
|
-
}
|
|
4004
|
-
|
|
4005
5382
|
// ../shared/src/insertMedia.ts
|
|
4006
5383
|
import { nanoid as nanoid2 } from "nanoid";
|
|
4007
5384
|
|
|
@@ -4371,22 +5748,22 @@ function buildFlatMap(fragment) {
|
|
|
4371
5748
|
walk(node);
|
|
4372
5749
|
}
|
|
4373
5750
|
if (idx < topLevel.length - 1) {
|
|
4374
|
-
flat += "\n";
|
|
5751
|
+
flat += "\n\n";
|
|
4375
5752
|
}
|
|
4376
5753
|
});
|
|
4377
5754
|
return { flat, map, blockFlatStarts };
|
|
4378
5755
|
}
|
|
4379
|
-
function
|
|
4380
|
-
if (needle.length === 0) return
|
|
4381
|
-
|
|
4382
|
-
let
|
|
4383
|
-
|
|
4384
|
-
const idx =
|
|
4385
|
-
if (idx < 0)
|
|
4386
|
-
|
|
4387
|
-
|
|
5756
|
+
function findAllOccurrences(haystack, needle) {
|
|
5757
|
+
if (needle.length === 0) return [];
|
|
5758
|
+
const out = [];
|
|
5759
|
+
let from2 = 0;
|
|
5760
|
+
while (true) {
|
|
5761
|
+
const idx = haystack.indexOf(needle, from2);
|
|
5762
|
+
if (idx < 0) break;
|
|
5763
|
+
out.push(idx);
|
|
5764
|
+
from2 = idx + 1;
|
|
4388
5765
|
}
|
|
4389
|
-
return
|
|
5766
|
+
return out;
|
|
4390
5767
|
}
|
|
4391
5768
|
function lookupFlatIndex(map, flatIndex) {
|
|
4392
5769
|
for (const entry of map) {
|
|
@@ -4484,13 +5861,11 @@ function resolveServerAnchor(doc, spec) {
|
|
|
4484
5861
|
return {
|
|
4485
5862
|
ok: false,
|
|
4486
5863
|
error: "section_not_found",
|
|
4487
|
-
currentSectionText: ""
|
|
5864
|
+
currentSectionText: "",
|
|
5865
|
+
currentSectionFlat: ""
|
|
4488
5866
|
};
|
|
4489
5867
|
}
|
|
4490
5868
|
const currentSectionText = getSection(doc, spec.headingId);
|
|
4491
|
-
if (!currentSectionText.includes(spec.textToFind)) {
|
|
4492
|
-
return { ok: false, error: "text_not_found", currentSectionText };
|
|
4493
|
-
}
|
|
4494
5869
|
const fragment = doc.getXmlFragment("default");
|
|
4495
5870
|
const { flat, map, blockFlatStarts } = buildFlatMap(fragment);
|
|
4496
5871
|
const range = getSectionBlockRange(doc, spec.headingId);
|
|
@@ -4498,25 +5873,68 @@ function resolveServerAnchor(doc, spec) {
|
|
|
4498
5873
|
return {
|
|
4499
5874
|
ok: false,
|
|
4500
5875
|
error: "section_not_found",
|
|
4501
|
-
currentSectionText
|
|
5876
|
+
currentSectionText,
|
|
5877
|
+
currentSectionFlat: ""
|
|
4502
5878
|
};
|
|
4503
5879
|
}
|
|
4504
5880
|
const sectionFlatStart = blockFlatStarts[range.start] ?? 0;
|
|
4505
5881
|
const sectionFlatEnd = range.end < blockFlatStarts.length ? blockFlatStarts[range.end] : flat.length;
|
|
4506
5882
|
const sectionFlat = flat.slice(sectionFlatStart, sectionFlatEnd);
|
|
4507
|
-
const
|
|
4508
|
-
if (
|
|
4509
|
-
return {
|
|
5883
|
+
const allMatches = findAllOccurrences(sectionFlat, spec.textToFind);
|
|
5884
|
+
if (allMatches.length === 0) {
|
|
5885
|
+
return {
|
|
5886
|
+
ok: false,
|
|
5887
|
+
error: "text_not_found",
|
|
5888
|
+
currentSectionText,
|
|
5889
|
+
currentSectionFlat: sectionFlat
|
|
5890
|
+
};
|
|
5891
|
+
}
|
|
5892
|
+
const topLevel = fragment.toArray();
|
|
5893
|
+
const isHeadingBlockAt = (absFlatIndex) => {
|
|
5894
|
+
let blockIdx = 0;
|
|
5895
|
+
for (let i = 0; i < blockFlatStarts.length; i++) {
|
|
5896
|
+
if (blockFlatStarts[i] <= absFlatIndex) blockIdx = i;
|
|
5897
|
+
else break;
|
|
5898
|
+
}
|
|
5899
|
+
const block = topLevel[blockIdx];
|
|
5900
|
+
return block instanceof Y4.XmlElement && block.nodeName === "heading";
|
|
5901
|
+
};
|
|
5902
|
+
const ranked = allMatches.map((sectionRelStart) => ({
|
|
5903
|
+
sectionRelStart,
|
|
5904
|
+
isHeading: isHeadingBlockAt(sectionFlatStart + sectionRelStart)
|
|
5905
|
+
}));
|
|
5906
|
+
ranked.sort((a, b) => {
|
|
5907
|
+
if (a.isHeading !== b.isHeading) return a.isHeading ? 1 : -1;
|
|
5908
|
+
return a.sectionRelStart - b.sectionRelStart;
|
|
5909
|
+
});
|
|
5910
|
+
const pick = ranked[occurrence - 1];
|
|
5911
|
+
if (!pick) {
|
|
5912
|
+
return {
|
|
5913
|
+
ok: false,
|
|
5914
|
+
error: "text_not_found",
|
|
5915
|
+
currentSectionText,
|
|
5916
|
+
currentSectionFlat: sectionFlat
|
|
5917
|
+
};
|
|
4510
5918
|
}
|
|
4511
|
-
const flatStart = sectionFlatStart + sectionRelStart;
|
|
5919
|
+
const flatStart = sectionFlatStart + pick.sectionRelStart;
|
|
4512
5920
|
const flatEnd = flatStart + spec.textToFind.length;
|
|
4513
5921
|
const startEntry = lookupFlatIndex(map, flatStart);
|
|
4514
5922
|
if (!startEntry) {
|
|
4515
|
-
return {
|
|
5923
|
+
return {
|
|
5924
|
+
ok: false,
|
|
5925
|
+
error: "text_not_found",
|
|
5926
|
+
currentSectionText,
|
|
5927
|
+
currentSectionFlat: sectionFlat
|
|
5928
|
+
};
|
|
4516
5929
|
}
|
|
4517
5930
|
const lastCharEntry = lookupFlatIndexEnd(map, flatEnd - 1);
|
|
4518
5931
|
if (!lastCharEntry) {
|
|
4519
|
-
return {
|
|
5932
|
+
return {
|
|
5933
|
+
ok: false,
|
|
5934
|
+
error: "text_not_found",
|
|
5935
|
+
currentSectionText,
|
|
5936
|
+
currentSectionFlat: sectionFlat
|
|
5937
|
+
};
|
|
4520
5938
|
}
|
|
4521
5939
|
const fromRelPos = Y4.createRelativePositionFromTypeIndex(
|
|
4522
5940
|
startEntry.xmlText,
|
|
@@ -5574,9 +6992,30 @@ var TOOL_DEFS = [
|
|
|
5574
6992
|
required: ["roomId", "threadId"]
|
|
5575
6993
|
}
|
|
5576
6994
|
},
|
|
6995
|
+
{
|
|
6996
|
+
name: "composer_list_threads",
|
|
6997
|
+
description: 'List comment and suggestion threads in the room, with full reply history. Same per-thread shape as `composer_get_thread` minus `sectionMarkdown` (omitted so the payload stays tractable across many threads \u2014 call `composer_get_full_doc` or `composer_get_thread` for surrounding context). Sorted by `createdAt` ascending so the agent reads them in the order they were posted. Use when you need to harvest discussion across the whole doc \u2014 e.g. "the user asked questions in the doc and other people answered in comment threads, summarize the answers". Optional `kind` filter narrows to one type when suggestion churn or comment chatter would be noise. Optional `status` filter drives the "which threads are still open" audit AND is the primary lever for shrinking the payload: `"open"` excludes resolved comments and accepted/rejected suggestions, which accumulate for the room\'s whole lifetime (they are never deleted), so on a long-lived doc the default `"all"` can be large.',
|
|
6998
|
+
inputSchema: {
|
|
6999
|
+
type: "object",
|
|
7000
|
+
properties: {
|
|
7001
|
+
roomId: { type: "string" },
|
|
7002
|
+
kind: {
|
|
7003
|
+
type: "string",
|
|
7004
|
+
enum: ["comment", "suggestion", "all"],
|
|
7005
|
+
description: 'Which thread kind to return. `"comment"` = Q&A / discussion threads only; `"suggestion"` = text-replacement proposals only; `"all"` (default) = both, with the `kind` field on each entry as the discriminator.'
|
|
7006
|
+
},
|
|
7007
|
+
status: {
|
|
7008
|
+
type: "string",
|
|
7009
|
+
enum: ["open", "resolved", "all"],
|
|
7010
|
+
description: 'Lifecycle filter. `"open"` = unresolved comments + pending suggestions (a thread with no terminal marker counts as open). `"resolved"` = resolved comments + accepted/rejected suggestions. `"all"` (default) = no filter. Use `"open"` for the unresolved-threads audit and to keep the response small on busy docs.'
|
|
7011
|
+
}
|
|
7012
|
+
},
|
|
7013
|
+
required: ["roomId"]
|
|
7014
|
+
}
|
|
7015
|
+
},
|
|
5577
7016
|
{
|
|
5578
7017
|
name: "composer_add_comment",
|
|
5579
|
-
description: "Post a new top-level comment anchored to a text span anywhere in the doc. Anchor is { headingId, textToFind, occurrence? }. Use this to flag something the user didn't ask about \u2014 cross-referencing related sections, raising a concern elsewhere in the doc, or seeding a thread on a new span. Use `composer_reply_comment` instead when continuing an existing thread. Accepts optional `state` (ack-first flow: post with `state: \"thinking\"` to start the live indicator immediately) and `mentions` (array of target userIds \u2014 the invoker's userId from the mention event payload, for the `@invoker` backlink). Returns { id } on success or an isError result if the anchor cannot be resolved.",
|
|
7018
|
+
description: "Post a new top-level comment anchored to a text span anywhere in the doc. Anchor is { headingId, textToFind, occurrence? }. Use this to flag something the user didn't ask about \u2014 cross-referencing related sections, raising a concern elsewhere in the doc, or seeding a thread on a new span. Use `composer_reply_comment` instead when continuing an existing thread. Accepts optional `state` (ack-first flow: post with `state: \"thinking\"` to start the live indicator immediately) and `mentions` (array of target userIds \u2014 the invoker's userId from the mention event payload, for the `@invoker` backlink).\n**`textToFind` matches the doc's stored text, not the markdown source.** Inline marks (`**bold**`, `*italic*`, `` `code` ``, `_emphasis_`, `~~strike~~`) are stored as Y.Marks \u2014 write plain text, no markers. Top-level blocks join with `\\n\\n`; list items have NO separator (three bullets `- a / - b / - c` are stored as `abc`). Copy from `Section as the matcher sees it` in any `text_not_found` error if unsure. Returns { id } on success or an isError result if the anchor cannot be resolved.",
|
|
5580
7019
|
inputSchema: {
|
|
5581
7020
|
type: "object",
|
|
5582
7021
|
properties: {
|
|
@@ -5630,7 +7069,7 @@ var TOOL_DEFS = [
|
|
|
5630
7069
|
},
|
|
5631
7070
|
{
|
|
5632
7071
|
name: "composer_add_suggestion",
|
|
5633
|
-
description: "Post a text replacement suggestion. A suggestion can target ANY span anywhere in the doc \u2014 not just the span of the thread that triggered you. Pick exactly one of:\n - `fromThreadId` \u2014 inherit the source thread's exact stored anchor. Right when the user's request is scoped to what they selected (the common case: 'rewrite this', 'make this shorter').\n - `anchor` \u2014 specify a span yourself via `{ headingId, textToFind, occurrence? }`. Use this when the user's request targets different text ('also update the intro', 'the bullet list in Section 3 is stale') OR for proactive suggestions with no source thread.\n**Anchor = what gets deleted.** Your `textToFind` is literally cut when the user accepts and `replacementText` is inserted in its place. Anchor the whole unit you're changing (full sentence including terminal punctuation; full list item text; full paragraph), match your replacementText's shape (inline for mid-paragraph edits, full markdown block for block replacements), end replacement at the same boundary as the anchor, and include any formatting you want preserved in the replacement itself \u2014 the anchor's bold / link / heading level is gone on accept. A too-narrow or mid-token anchor leaves broken spacing or smashed-together words. See SKILL.md 'Pick the right span' for the full rubric.\n**Ripple coverage is your responsibility.** If the change requires edits nearby or elsewhere (enumeration counts, cross-references, subject/verb agreement, restated facts), call this tool MULTIPLE times in the same turn \u2014 one suggestion per span \u2014 so accepting leaves the doc correct. If you're unsure whether ripples exist elsewhere in the doc, call `composer_get_full_doc` first. Returns { id } on success or an isError result if the anchor cannot be resolved.",
|
|
7072
|
+
description: "Post a text replacement suggestion. A suggestion can target ANY span anywhere in the doc \u2014 not just the span of the thread that triggered you. Pick exactly one of:\n - `fromThreadId` \u2014 inherit the source thread's exact stored anchor. Right when the user's request is scoped to what they selected (the common case: 'rewrite this', 'make this shorter').\n - `anchor` \u2014 specify a span yourself via `{ headingId, textToFind, occurrence? }`. Use this when the user's request targets different text ('also update the intro', 'the bullet list in Section 3 is stale') OR for proactive suggestions with no source thread.\n**Anchor = what gets deleted.** Your `textToFind` is literally cut when the user accepts and `replacementText` is inserted in its place. Anchor the whole unit you're changing (full sentence including terminal punctuation; full list item text; full paragraph), match your replacementText's shape (inline for mid-paragraph edits, full markdown block for block replacements), end replacement at the same boundary as the anchor, and include any formatting you want preserved in the replacement itself \u2014 the anchor's bold / link / heading level is gone on accept. A too-narrow or mid-token anchor leaves broken spacing or smashed-together words. See SKILL.md 'Pick the right span' for the full rubric.\n**`textToFind` matches the doc's stored text, not the markdown source.** Three rules govern the shape:\n 1. **Inline marks are stripped.** `**bold**`, `*italic*`, `` `code` ``, `_emphasis_`, `~~strike~~` are stored as Y.Marks, not as literal characters. Write the plain text \u2014 `the file back` not `` `the file back` ``.\n 2. **Top-level blocks separate with `\\n\\n`.** Heading + body, or two sibling paragraphs, are joined by a blank line. Soft line breaks WITHIN a single paragraph are preserved as single `\\n`.\n 3. **List items have NO separator.** Three bullets `- a / - b / - c` are stored as `abc` (smashed). Same for ordered list items.\n When in doubt, copy from the `Section as the matcher sees it` block in any `text_not_found` error.\n**Ripple coverage is your responsibility.** If the change requires edits nearby or elsewhere (enumeration counts, cross-references, subject/verb agreement, restated facts), call this tool MULTIPLE times in the same turn \u2014 one suggestion per span \u2014 so accepting leaves the doc correct. If you're unsure whether ripples exist elsewhere in the doc, call `composer_get_full_doc` first. Returns { id } on success or an isError result if the anchor cannot be resolved.",
|
|
5634
7073
|
inputSchema: {
|
|
5635
7074
|
type: "object",
|
|
5636
7075
|
properties: {
|
|
@@ -5787,6 +7226,12 @@ function asString(value, field) {
|
|
|
5787
7226
|
}
|
|
5788
7227
|
return value;
|
|
5789
7228
|
}
|
|
7229
|
+
function asStringAllowEmpty(value, field) {
|
|
7230
|
+
if (typeof value !== "string") {
|
|
7231
|
+
throw new Error(`${field} must be a string`);
|
|
7232
|
+
}
|
|
7233
|
+
return value;
|
|
7234
|
+
}
|
|
5790
7235
|
function asOptionalString(value, field) {
|
|
5791
7236
|
if (value === void 0) return void 0;
|
|
5792
7237
|
if (typeof value !== "string") {
|
|
@@ -6083,23 +7528,8 @@ function handleGetFullDoc(args) {
|
|
|
6083
7528
|
const state = getOrError(roomId);
|
|
6084
7529
|
return okResult({ markdown: serializeDocAsMarkdown(state.doc) });
|
|
6085
7530
|
}
|
|
6086
|
-
function
|
|
6087
|
-
const
|
|
6088
|
-
const roomId = asString(a.roomId, "roomId");
|
|
6089
|
-
const threadId = asString(a.threadId, "threadId");
|
|
6090
|
-
const state = getOrError(roomId);
|
|
6091
|
-
const commentRaw = state.doc.getMap("comments").get(threadId);
|
|
6092
|
-
const suggestionRaw = state.doc.getMap("suggestions").get(threadId);
|
|
6093
|
-
const raw = commentRaw ?? suggestionRaw;
|
|
6094
|
-
if (!raw) {
|
|
6095
|
-
return errorResult(`thread not found: ${threadId}`);
|
|
6096
|
-
}
|
|
6097
|
-
const kind = commentRaw ? "comment" : "suggestion";
|
|
6098
|
-
const anchoredContext = resolveAnchoredContext(
|
|
6099
|
-
state.doc,
|
|
6100
|
-
raw.anchorFrom,
|
|
6101
|
-
raw.anchorTo
|
|
6102
|
-
);
|
|
7531
|
+
function shapeThread(doc, threadId, raw, kind, opts) {
|
|
7532
|
+
const anchoredContext = resolveAnchoredContext(doc, raw.anchorFrom, raw.anchorTo);
|
|
6103
7533
|
const replies = Array.isArray(raw.replies) ? raw.replies : [];
|
|
6104
7534
|
const shapedReplies = replies.filter(
|
|
6105
7535
|
(r) => !!r && typeof r === "object" && typeof r.id === "string" && typeof r.text === "string"
|
|
@@ -6112,7 +7542,7 @@ function handleGetThread(args) {
|
|
|
6112
7542
|
mentions: Array.isArray(r.mentions) ? r.mentions.filter((m) => typeof m === "string") : void 0,
|
|
6113
7543
|
createdAt: typeof r.createdAt === "number" ? r.createdAt : void 0
|
|
6114
7544
|
}));
|
|
6115
|
-
return
|
|
7545
|
+
return {
|
|
6116
7546
|
threadId,
|
|
6117
7547
|
kind,
|
|
6118
7548
|
body: typeof raw.text === "string" ? raw.text : void 0,
|
|
@@ -6128,9 +7558,72 @@ function handleGetThread(args) {
|
|
|
6128
7558
|
anchoredText: anchoredContext.anchoredText,
|
|
6129
7559
|
headingId: anchoredContext.headingId,
|
|
6130
7560
|
headingText: anchoredContext.headingText,
|
|
6131
|
-
sectionMarkdown: anchoredContext.sectionMarkdown,
|
|
7561
|
+
sectionMarkdown: opts.includeSectionMarkdown ? anchoredContext.sectionMarkdown : void 0,
|
|
6132
7562
|
replies: shapedReplies
|
|
7563
|
+
};
|
|
7564
|
+
}
|
|
7565
|
+
function handleGetThread(args) {
|
|
7566
|
+
const a = asObject(args);
|
|
7567
|
+
const roomId = asString(a.roomId, "roomId");
|
|
7568
|
+
const threadId = asString(a.threadId, "threadId");
|
|
7569
|
+
const state = getOrError(roomId);
|
|
7570
|
+
const commentRaw = state.doc.getMap("comments").get(threadId);
|
|
7571
|
+
const suggestionRaw = state.doc.getMap("suggestions").get(threadId);
|
|
7572
|
+
const raw = commentRaw ?? suggestionRaw;
|
|
7573
|
+
if (!raw) {
|
|
7574
|
+
return errorResult(`thread not found: ${threadId}`);
|
|
7575
|
+
}
|
|
7576
|
+
const kind = commentRaw ? "comment" : "suggestion";
|
|
7577
|
+
return okResult(
|
|
7578
|
+
shapeThread(state.doc, threadId, raw, kind, { includeSectionMarkdown: true })
|
|
7579
|
+
);
|
|
7580
|
+
}
|
|
7581
|
+
function handleListThreads(args) {
|
|
7582
|
+
const a = asObject(args);
|
|
7583
|
+
const roomId = asString(a.roomId, "roomId");
|
|
7584
|
+
const state = getOrError(roomId);
|
|
7585
|
+
const kindArg = asOptionalString(a.kind, "kind") ?? "all";
|
|
7586
|
+
if (kindArg !== "all" && kindArg !== "comment" && kindArg !== "suggestion") {
|
|
7587
|
+
return errorResult(`kind must be one of "all", "comment", "suggestion" (got "${kindArg}")`);
|
|
7588
|
+
}
|
|
7589
|
+
const statusArg = asOptionalString(a.status, "status") ?? "all";
|
|
7590
|
+
if (statusArg !== "all" && statusArg !== "open" && statusArg !== "resolved") {
|
|
7591
|
+
return errorResult(`status must be one of "all", "open", "resolved" (got "${statusArg}")`);
|
|
7592
|
+
}
|
|
7593
|
+
const out = [];
|
|
7594
|
+
if (kindArg === "all" || kindArg === "comment") {
|
|
7595
|
+
const comments = state.doc.getMap("comments");
|
|
7596
|
+
comments.forEach((raw, id) => {
|
|
7597
|
+
if (raw && typeof raw === "object") {
|
|
7598
|
+
out.push(shapeThread(state.doc, id, raw, "comment", {
|
|
7599
|
+
includeSectionMarkdown: false
|
|
7600
|
+
}));
|
|
7601
|
+
}
|
|
7602
|
+
});
|
|
7603
|
+
}
|
|
7604
|
+
if (kindArg === "all" || kindArg === "suggestion") {
|
|
7605
|
+
const suggestions = state.doc.getMap("suggestions");
|
|
7606
|
+
suggestions.forEach((raw, id) => {
|
|
7607
|
+
if (raw && typeof raw === "object") {
|
|
7608
|
+
out.push(shapeThread(state.doc, id, raw, "suggestion", {
|
|
7609
|
+
includeSectionMarkdown: false
|
|
7610
|
+
}));
|
|
7611
|
+
}
|
|
7612
|
+
});
|
|
7613
|
+
}
|
|
7614
|
+
if (statusArg !== "all") {
|
|
7615
|
+
const isOpen = (t) => t.kind === "comment" ? t.resolved !== true : t.status === void 0 || t.status === "pending";
|
|
7616
|
+
const wantOpen = statusArg === "open";
|
|
7617
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
7618
|
+
if (isOpen(out[i]) !== wantOpen) out.splice(i, 1);
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
out.sort((a2, b) => {
|
|
7622
|
+
const aT = a2.createdAt ?? Number.POSITIVE_INFINITY;
|
|
7623
|
+
const bT = b.createdAt ?? Number.POSITIVE_INFINITY;
|
|
7624
|
+
return aT - bT;
|
|
6133
7625
|
});
|
|
7626
|
+
return okResult({ threads: out });
|
|
6134
7627
|
}
|
|
6135
7628
|
function performAddComment(state, a) {
|
|
6136
7629
|
const anchor = asAnchor(a.anchor);
|
|
@@ -6139,8 +7632,11 @@ function performAddComment(state, a) {
|
|
|
6139
7632
|
const resolved = resolveServerAnchor(state.doc, anchor);
|
|
6140
7633
|
if (!resolved.ok) {
|
|
6141
7634
|
return errorResult(
|
|
6142
|
-
`anchor ${resolved.error}.
|
|
6143
|
-
${resolved.currentSectionText}
|
|
7635
|
+
`anchor ${resolved.error}. Section as rendered (markdown form, with markers):
|
|
7636
|
+
${resolved.currentSectionText}
|
|
7637
|
+
|
|
7638
|
+
Section as the matcher sees it (use this exact form for textToFind \u2014 inline marks like \`code\`, **bold**, *italic* are stored as marks not characters, so they're absent here; soft line breaks are preserved; list items have no separators):
|
|
7639
|
+
${resolved.currentSectionFlat}`
|
|
6144
7640
|
);
|
|
6145
7641
|
}
|
|
6146
7642
|
const id = nanoid4();
|
|
@@ -6243,7 +7739,7 @@ function handleReplyComment(args) {
|
|
|
6243
7739
|
return performReplyComment(state, a);
|
|
6244
7740
|
}
|
|
6245
7741
|
function performAddSuggestion(state, a) {
|
|
6246
|
-
const replacementText =
|
|
7742
|
+
const replacementText = asStringAllowEmpty(a.replacementText, "replacementText");
|
|
6247
7743
|
const fromThreadId = asOptionalString(a.fromThreadId, "fromThreadId");
|
|
6248
7744
|
const agentState = asOptionalAgentState(a.state, "state");
|
|
6249
7745
|
let anchorFrom;
|
|
@@ -6402,6 +7898,7 @@ function performResolveThread(state, a) {
|
|
|
6402
7898
|
participantUserIds: getParticipantUserIds(existing),
|
|
6403
7899
|
createdAt
|
|
6404
7900
|
});
|
|
7901
|
+
markActivityReadForThread(state.doc, threadId, state.identity.userId);
|
|
6405
7902
|
});
|
|
6406
7903
|
return okResult({ threadId, resolved: true });
|
|
6407
7904
|
}
|
|
@@ -6574,6 +8071,8 @@ async function dispatchTool(name, args, signal) {
|
|
|
6574
8071
|
return handleGetFullDoc(args);
|
|
6575
8072
|
case "composer_get_thread":
|
|
6576
8073
|
return handleGetThread(args);
|
|
8074
|
+
case "composer_list_threads":
|
|
8075
|
+
return handleListThreads(args);
|
|
6577
8076
|
case "composer_add_comment":
|
|
6578
8077
|
return handleAddComment(args);
|
|
6579
8078
|
case "composer_reply_comment":
|