@tessera-editor/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +137 -173
- package/dist/index.js +307 -405
- package/dist/index.js.map +1 -1
- package/dist/tessera.css +132 -120
- package/package.json +1 -1
- package/src/__tests__/host-config.test.ts +115 -0
- package/src/__tests__/linkedit.test.ts +98 -0
- package/src/__tests__/table-guard.test.ts +155 -0
- package/src/__tests__/v11.test.ts +1 -50
- package/src/extensions/context-menu.ts +7 -2
- package/src/extensions/shortcuts.ts +8 -6
- package/src/extensions/slash.ts +43 -25
- package/src/i18n.ts +26 -33
- package/src/index.ts +10 -13
- package/src/linkedit.ts +66 -0
- package/src/markdown.ts +0 -1
- package/src/nodes/table.ts +130 -0
- package/src/preset.ts +65 -26
- package/src/services.ts +1 -22
- package/src/styles/tessera.css +132 -120
- package/src/diff.ts +0 -150
- package/src/extensions/history.ts +0 -133
- package/src/marks/placeholder.ts +0 -63
package/dist/index.js
CHANGED
|
@@ -225,6 +225,7 @@ var ImageBlock = Node3.create({
|
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
// src/nodes/table.ts
|
|
228
|
+
import { Plugin as Plugin2 } from "@tiptap/pm/state";
|
|
228
229
|
import { Table, TableRow, TableCell, TableHeader } from "@tiptap/extension-table";
|
|
229
230
|
var TABLE_COLUMN_KINDS = [
|
|
230
231
|
"text",
|
|
@@ -260,6 +261,59 @@ function normalizeTypes(types, cols) {
|
|
|
260
261
|
}
|
|
261
262
|
return list.slice(0, cols);
|
|
262
263
|
}
|
|
264
|
+
function cellKindAt(doc, pos) {
|
|
265
|
+
const $pos = doc.resolve(pos);
|
|
266
|
+
let tableDepth = -1;
|
|
267
|
+
for (let depth = $pos.depth; depth > 0; depth--) {
|
|
268
|
+
if ($pos.node(depth).type.name === "table") {
|
|
269
|
+
tableDepth = depth;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (tableDepth < 1 || $pos.depth < tableDepth + 2) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
const cell = $pos.node(tableDepth + 2);
|
|
277
|
+
if (cell.type.name !== "tableCell") {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
const table = $pos.node(tableDepth);
|
|
281
|
+
const col = $pos.index(tableDepth + 1);
|
|
282
|
+
return normalizeTypes(table.attrs.types, columnCount(table))[col] ?? "text";
|
|
283
|
+
}
|
|
284
|
+
function typedCellRanges(doc) {
|
|
285
|
+
const out = [];
|
|
286
|
+
doc.descendants((node, pos) => {
|
|
287
|
+
if (node.type.name !== "table") {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const types = normalizeTypes(node.attrs.types, columnCount(node));
|
|
291
|
+
node.forEach((row, rowOff) => {
|
|
292
|
+
let col = 0;
|
|
293
|
+
row.forEach((cell, cellOff) => {
|
|
294
|
+
const kind = types[col];
|
|
295
|
+
if (cell.type.name === "tableCell" && kind && kind !== "text") {
|
|
296
|
+
const base = pos + 1 + rowOff + 1 + cellOff;
|
|
297
|
+
out.push({ from: base + 1, to: base + cell.nodeSize - 1 });
|
|
298
|
+
}
|
|
299
|
+
col += 1;
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
function clearColumnCellText(tr, schema, table, index) {
|
|
306
|
+
table.node.forEach((row, rowOff) => {
|
|
307
|
+
let col = 0;
|
|
308
|
+
row.forEach((cell, cellOff) => {
|
|
309
|
+
if (col === index && cell.type.name === "tableCell" && cell.textContent.trim()) {
|
|
310
|
+
const base = table.pos + 1 + rowOff + 1 + cellOff;
|
|
311
|
+
tr.replaceWith(base + 1, base + cell.nodeSize - 1, schema.nodes.paragraph.create(null));
|
|
312
|
+
}
|
|
313
|
+
col += 1;
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
}
|
|
263
317
|
function cellSortValue(row, index, kind) {
|
|
264
318
|
const cell = row.maybeChild(index);
|
|
265
319
|
if (!cell) {
|
|
@@ -328,6 +382,30 @@ function tableNodeToCsv(table) {
|
|
|
328
382
|
}
|
|
329
383
|
var AiTable = Table.extend({
|
|
330
384
|
name: "table",
|
|
385
|
+
addProseMirrorPlugins() {
|
|
386
|
+
return [
|
|
387
|
+
new Plugin2({
|
|
388
|
+
props: {
|
|
389
|
+
// typed cells are owned by their widget: never let the text caret
|
|
390
|
+
// enter the hidden paragraph, type or paste into it, and make
|
|
391
|
+
// cursor movement skip the cell as an atomic unit
|
|
392
|
+
handleTextInput: (view, from) => {
|
|
393
|
+
const kind = cellKindAt(view.state.doc, from);
|
|
394
|
+
return kind !== null && kind !== "text";
|
|
395
|
+
},
|
|
396
|
+
handlePaste: (view) => {
|
|
397
|
+
const kind = cellKindAt(view.state.doc, view.state.selection.from);
|
|
398
|
+
return kind !== null && kind !== "text";
|
|
399
|
+
},
|
|
400
|
+
handleClick: (view, pos) => {
|
|
401
|
+
const kind = cellKindAt(view.state.doc, pos);
|
|
402
|
+
return kind !== null && kind !== "text";
|
|
403
|
+
},
|
|
404
|
+
atomicRanges: (state) => typedCellRanges(state.doc)
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
];
|
|
408
|
+
},
|
|
331
409
|
addAttributes() {
|
|
332
410
|
return {
|
|
333
411
|
...this.parent?.(),
|
|
@@ -370,10 +448,40 @@ var AiTable = Table.extend({
|
|
|
370
448
|
types[index] = kind;
|
|
371
449
|
if (dispatch) {
|
|
372
450
|
tr.setNodeMarkup(table.pos, void 0, { ...table.node.attrs, types });
|
|
451
|
+
if (kind !== "text") {
|
|
452
|
+
clearColumnCellText(tr, state.schema, table, index);
|
|
453
|
+
}
|
|
373
454
|
dispatch(tr);
|
|
374
455
|
}
|
|
375
456
|
return true;
|
|
376
457
|
},
|
|
458
|
+
normalizeTypedCells: () => ({ state, dispatch, tr }) => {
|
|
459
|
+
const edits = [];
|
|
460
|
+
state.doc.descendants((node, pos) => {
|
|
461
|
+
if (node.type.name !== "table") {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const types = normalizeTypes(node.attrs.types, columnCount(node));
|
|
465
|
+
node.forEach((row, rowOff) => {
|
|
466
|
+
let col = 0;
|
|
467
|
+
row.forEach((cell, cellOff) => {
|
|
468
|
+
const kind = types[col];
|
|
469
|
+
if (cell.type.name === "tableCell" && kind && kind !== "text" && cell.textContent.trim()) {
|
|
470
|
+
const base = pos + 1 + rowOff + 1 + cellOff;
|
|
471
|
+
edits.push({ from: base + 1, to: base + cell.nodeSize - 1 });
|
|
472
|
+
}
|
|
473
|
+
col += 1;
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
if (dispatch && edits.length) {
|
|
478
|
+
for (const e of [...edits].reverse()) {
|
|
479
|
+
tr.replaceWith(e.from, e.to, state.schema.nodes.paragraph.create(null));
|
|
480
|
+
}
|
|
481
|
+
dispatch(tr);
|
|
482
|
+
}
|
|
483
|
+
return edits.length > 0;
|
|
484
|
+
},
|
|
377
485
|
sortTableByColumn: (index, direction) => ({ state, dispatch, tr }) => {
|
|
378
486
|
const table = locateTable(state);
|
|
379
487
|
if (!table) {
|
|
@@ -644,43 +752,9 @@ var CommentCommands = Extension.create({
|
|
|
644
752
|
}
|
|
645
753
|
});
|
|
646
754
|
|
|
647
|
-
// src/marks/placeholder.ts
|
|
648
|
-
import { Mark as Mark3, mergeAttributes as mergeAttributes8, Extension as Extension2 } from "@tiptap/core";
|
|
649
|
-
var PlaceholderMark = Mark3.create({
|
|
650
|
-
name: "tesseraPlaceholder",
|
|
651
|
-
addAttributes() {
|
|
652
|
-
return {
|
|
653
|
-
kind: {
|
|
654
|
-
default: "text",
|
|
655
|
-
parseHTML: (element) => element.getAttribute("data-kind") ?? "text",
|
|
656
|
-
renderHTML: (attributes) => ({ "data-kind": String(attributes.kind) })
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
},
|
|
660
|
-
parseHTML() {
|
|
661
|
-
return [{ tag: 'span[data-type="tessera-placeholder"]' }];
|
|
662
|
-
},
|
|
663
|
-
renderHTML({ HTMLAttributes }) {
|
|
664
|
-
return ["span", mergeAttributes8({ "data-type": "tessera-placeholder" }, HTMLAttributes)];
|
|
665
|
-
}
|
|
666
|
-
});
|
|
667
|
-
var PlaceholderCommands = Extension2.create({
|
|
668
|
-
name: "tesseraPlaceholderCommands",
|
|
669
|
-
addCommands() {
|
|
670
|
-
return {
|
|
671
|
-
togglePlaceholderMark: (kind = "text") => ({ commands }) => commands.toggleMark("tesseraPlaceholder", { kind }),
|
|
672
|
-
insertPlaceholderToken: (kind, label) => ({ chain }) => chain().insertContent({
|
|
673
|
-
type: "text",
|
|
674
|
-
text: label,
|
|
675
|
-
marks: [{ type: "tesseraPlaceholder", attrs: { kind } }]
|
|
676
|
-
}).run()
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
});
|
|
680
|
-
|
|
681
755
|
// src/extensions/input-rules.ts
|
|
682
|
-
import { Extension as
|
|
683
|
-
var TesseraInputRules =
|
|
756
|
+
import { Extension as Extension2, wrappingInputRule as wrappingInputRule2, markInputRule } from "@tiptap/core";
|
|
757
|
+
var TesseraInputRules = Extension2.create({
|
|
684
758
|
name: "tesseraInputRules",
|
|
685
759
|
addInputRules() {
|
|
686
760
|
const taskList = this.editor.schema.nodes.taskList;
|
|
@@ -707,9 +781,9 @@ var TesseraInputRules = Extension3.create({
|
|
|
707
781
|
});
|
|
708
782
|
|
|
709
783
|
// src/extensions/shortcuts.ts
|
|
710
|
-
import { Extension as
|
|
784
|
+
import { Extension as Extension3 } from "@tiptap/core";
|
|
711
785
|
import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
|
|
712
|
-
var TesseraShortcuts =
|
|
786
|
+
var TesseraShortcuts = Extension3.create({
|
|
713
787
|
name: "tesseraShortcuts",
|
|
714
788
|
priority: 500,
|
|
715
789
|
addCommands() {
|
|
@@ -719,6 +793,7 @@ var TesseraShortcuts = Extension4.create({
|
|
|
719
793
|
};
|
|
720
794
|
},
|
|
721
795
|
addKeyboardShortcuts() {
|
|
796
|
+
const has = (name) => this.editor.extensionManager.extensions.some((ext) => ext.name === name);
|
|
722
797
|
return {
|
|
723
798
|
"Mod-Shift-1": () => this.editor.commands.toggleHeading({ level: 1 }),
|
|
724
799
|
"Mod-Shift-2": () => this.editor.commands.toggleHeading({ level: 2 }),
|
|
@@ -726,8 +801,8 @@ var TesseraShortcuts = Extension4.create({
|
|
|
726
801
|
"Mod-Shift-4": () => this.editor.commands.toggleHeading({ level: 4 }),
|
|
727
802
|
"Mod-Shift-7": () => this.editor.commands.toggleOrderedList(),
|
|
728
803
|
"Mod-Shift-8": () => this.editor.commands.toggleBulletList(),
|
|
729
|
-
"Mod-Shift-c": () => this.editor.commands.toggleTaskList(),
|
|
730
|
-
"Mod-Alt-h": () => this.editor.commands.toggleHint(),
|
|
804
|
+
"Mod-Shift-c": () => has("taskList") && this.editor.commands.toggleTaskList(),
|
|
805
|
+
"Mod-Alt-h": () => has("hint") && this.editor.commands.toggleHint(),
|
|
731
806
|
"Mod-j": () => this.editor.commands.toggleCode(),
|
|
732
807
|
"Mod-Shift-9": () => this.editor.commands.toggleCodeBlock(),
|
|
733
808
|
"Mod-Shift-.": () => this.editor.commands.toggleBlockquote(),
|
|
@@ -747,9 +822,8 @@ var TesseraShortcuts = Extension4.create({
|
|
|
747
822
|
this.editor.emit("tessera:askPanel", {});
|
|
748
823
|
return true;
|
|
749
824
|
},
|
|
750
|
-
"Mod-Alt-s": () => this.editor.commands.insertTableTyped({ withHeaderRow: true }),
|
|
751
|
-
"Mod-Alt-t": () => this.editor.commands.insertTableTyped({ withHeaderRow: false }),
|
|
752
|
-
"Mod-Alt-p": () => this.editor.commands.togglePlaceholderMark("text"),
|
|
825
|
+
"Mod-Alt-s": () => has("table") && this.editor.commands.insertTableTyped({ withHeaderRow: true }),
|
|
826
|
+
"Mod-Alt-t": () => has("table") && this.editor.commands.insertTableTyped({ withHeaderRow: false }),
|
|
753
827
|
"Mod-Alt-m": () => {
|
|
754
828
|
this.editor.emit("tessera:commentPanel", {});
|
|
755
829
|
return true;
|
|
@@ -785,8 +859,8 @@ function swapBlock(state, tr, dispatch, direction) {
|
|
|
785
859
|
}
|
|
786
860
|
|
|
787
861
|
// src/extensions/find-replace.ts
|
|
788
|
-
import { Extension as
|
|
789
|
-
import { Plugin as
|
|
862
|
+
import { Extension as Extension4 } from "@tiptap/core";
|
|
863
|
+
import { Plugin as Plugin3, PluginKey as PluginKey2 } from "@tiptap/pm/state";
|
|
790
864
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
791
865
|
var findReplaceKey = new PluginKey2("tesseraFindReplace");
|
|
792
866
|
function computeMatches(doc, query) {
|
|
@@ -808,7 +882,7 @@ function computeMatches(doc, query) {
|
|
|
808
882
|
});
|
|
809
883
|
return matches;
|
|
810
884
|
}
|
|
811
|
-
var TesseraFindReplace =
|
|
885
|
+
var TesseraFindReplace = Extension4.create({
|
|
812
886
|
name: "tesseraFindReplace",
|
|
813
887
|
addStorage() {
|
|
814
888
|
return {
|
|
@@ -901,7 +975,7 @@ var TesseraFindReplace = Extension5.create({
|
|
|
901
975
|
},
|
|
902
976
|
addProseMirrorPlugins() {
|
|
903
977
|
return [
|
|
904
|
-
new
|
|
978
|
+
new Plugin3({
|
|
905
979
|
key: findReplaceKey,
|
|
906
980
|
state: {
|
|
907
981
|
init: () => ({ query: "", matches: [], active: 0, visible: false }),
|
|
@@ -954,7 +1028,7 @@ var TesseraFindReplace = Extension5.create({
|
|
|
954
1028
|
});
|
|
955
1029
|
|
|
956
1030
|
// src/extensions/slash.ts
|
|
957
|
-
import { Extension as
|
|
1031
|
+
import { Extension as Extension5 } from "@tiptap/core";
|
|
958
1032
|
import Suggestion from "@tiptap/suggestion";
|
|
959
1033
|
import { PluginKey as PluginKey3 } from "@tiptap/pm/state";
|
|
960
1034
|
|
|
@@ -1020,6 +1094,8 @@ var tesseraMessages = {
|
|
|
1020
1094
|
// link panel
|
|
1021
1095
|
linkPlaceholder: "\u94FE\u63A5\u5730\u5740\u2026",
|
|
1022
1096
|
linkApply: "\u5E94\u7528",
|
|
1097
|
+
linkSave: "\u4FDD\u5B58",
|
|
1098
|
+
linkTextPlaceholder: "\u663E\u793A\u6587\u5B57\u2026",
|
|
1023
1099
|
linkRemove: "\u79FB\u9664\u94FE\u63A5",
|
|
1024
1100
|
linkOpen: "\u6253\u5F00",
|
|
1025
1101
|
// empty-line toolbar
|
|
@@ -1038,6 +1114,10 @@ var tesseraMessages = {
|
|
|
1038
1114
|
imageAlignLeft: "\u5DE6\u5BF9\u9F50",
|
|
1039
1115
|
imageAlignCenter: "\u5C45\u4E2D",
|
|
1040
1116
|
imageAlignFull: "\u5168\u5BBD",
|
|
1117
|
+
imageZoomIn: "\u653E\u5927",
|
|
1118
|
+
imageZoomOut: "\u7F29\u5C0F",
|
|
1119
|
+
imageZoomReset: "\u91CD\u7F6E",
|
|
1120
|
+
imageZoomHint: "Ctrl+\u6EDA\u8F6E\u7F29\u653E \xB7 \u53CC\u51FB\u56FE\u7247\u590D\u4F4D",
|
|
1041
1121
|
// AI panel
|
|
1042
1122
|
aiTitle: "AI",
|
|
1043
1123
|
aiAskPlaceholder: "\u9488\u5BF9\u672C\u6587\u6863\u63D0\u95EE\u2026",
|
|
@@ -1076,17 +1156,6 @@ var tesseraMessages = {
|
|
|
1076
1156
|
tableCopyCsv: "\u590D\u5236\u4E3A CSV",
|
|
1077
1157
|
tableToggleHeader: "\u5207\u6362\u8868\u5934",
|
|
1078
1158
|
cellEmpty: "\u7A7A",
|
|
1079
|
-
// v1.1: history
|
|
1080
|
-
itemHistory: "\u7248\u672C\u5386\u53F2",
|
|
1081
|
-
historyTitle: "\u7248\u672C\u5386\u53F2",
|
|
1082
|
-
historyEmpty: "\u6682\u65E0\u5FEB\u7167\uFF08\u7F16\u8F91\u540E\u7A7A\u95F2\u81EA\u52A8\u4FDD\u5B58\uFF0C\u6216\u624B\u52A8\u6355\u83B7\uFF09",
|
|
1083
|
-
historyCapture: "\u6355\u83B7\u5FEB\u7167",
|
|
1084
|
-
historyRestore: "\u6062\u590D\u6B64\u7248\u672C",
|
|
1085
|
-
historyCurrent: "\u5F53\u524D",
|
|
1086
|
-
historyDiffAdded: "\u65B0\u589E",
|
|
1087
|
-
historyDiffRemoved: "\u5220\u9664",
|
|
1088
|
-
historyDiffChanged: "\u4FEE\u6539",
|
|
1089
|
-
historyConfirmRestore: "\u6062\u590D\u5230\u8BE5\u7248\u672C\uFF1F\u5F53\u524D\u5185\u5BB9\u5C06\u88AB\u66FF\u6362\uFF08\u53EF\u64A4\u9500\uFF09",
|
|
1090
1159
|
// v1.1: comments
|
|
1091
1160
|
tooltipCommentV11: "\u8BC4\u8BBA",
|
|
1092
1161
|
commentTitle: "\u8BC4\u8BBA",
|
|
@@ -1098,7 +1167,7 @@ var tesseraMessages = {
|
|
|
1098
1167
|
commentDelete: "\u5220\u9664",
|
|
1099
1168
|
commentResolvedBadge: "\u5DF2\u89E3\u51B3",
|
|
1100
1169
|
commentCount: (n) => `${n} \u6761\u8BC4\u8BBA`,
|
|
1101
|
-
// v1.1: embed / toc
|
|
1170
|
+
// v1.1: embed / toc
|
|
1102
1171
|
itemEmbed: "\u5D4C\u5165",
|
|
1103
1172
|
itemEmbedDesc: "\u5D4C\u5165\u5916\u90E8\u7F51\u9875\uFF08iframe \u6C99\u7BB1\uFF09",
|
|
1104
1173
|
itemToc: "\u76EE\u5F55",
|
|
@@ -1108,9 +1177,6 @@ var tesseraMessages = {
|
|
|
1108
1177
|
embedInvalid: "\u65E0\u6548\u94FE\u63A5",
|
|
1109
1178
|
embedOpen: "\u6253\u5F00\u539F\u94FE\u63A5",
|
|
1110
1179
|
tocEmpty: "\u6682\u65E0\u6807\u9898\u2014\u2014\u6DFB\u52A0 H1\u2013H4 \u540E\u81EA\u52A8\u51FA\u73B0",
|
|
1111
|
-
placeholderText: "\u5F85\u8865\u5145",
|
|
1112
|
-
placeholderPerson: "\u5F85\u586B\u4EBA",
|
|
1113
|
-
placeholderDate: "\u5F85\u586B\u65E5\u671F",
|
|
1114
1180
|
// v1.1: block context menu
|
|
1115
1181
|
menuCopyAnchor: "\u590D\u5236\u951A\u94FE\u63A5",
|
|
1116
1182
|
menuCopyBlockId: "\u590D\u5236\u5757 ID",
|
|
@@ -1169,6 +1235,8 @@ var tesseraMessages = {
|
|
|
1169
1235
|
highlightNone: "No highlight",
|
|
1170
1236
|
linkPlaceholder: "Link URL\u2026",
|
|
1171
1237
|
linkApply: "Apply",
|
|
1238
|
+
linkSave: "Save",
|
|
1239
|
+
linkTextPlaceholder: "Link text\u2026",
|
|
1172
1240
|
linkRemove: "Remove link",
|
|
1173
1241
|
linkOpen: "Open",
|
|
1174
1242
|
emptyLineExpand: "Show all blocks",
|
|
@@ -1184,6 +1252,10 @@ var tesseraMessages = {
|
|
|
1184
1252
|
imageAlignLeft: "Align left",
|
|
1185
1253
|
imageAlignCenter: "Center",
|
|
1186
1254
|
imageAlignFull: "Full width",
|
|
1255
|
+
imageZoomIn: "Zoom in",
|
|
1256
|
+
imageZoomOut: "Zoom out",
|
|
1257
|
+
imageZoomReset: "Reset zoom",
|
|
1258
|
+
imageZoomHint: "Ctrl+scroll to zoom \xB7 double-click to reset",
|
|
1187
1259
|
aiTitle: "AI",
|
|
1188
1260
|
aiAskPlaceholder: "Ask about this doc\u2026",
|
|
1189
1261
|
aiSend: "Send",
|
|
@@ -1221,17 +1293,6 @@ var tesseraMessages = {
|
|
|
1221
1293
|
tableCopyCsv: "Copy as CSV",
|
|
1222
1294
|
tableToggleHeader: "Toggle header row",
|
|
1223
1295
|
cellEmpty: "Empty",
|
|
1224
|
-
// v1.1: history
|
|
1225
|
-
itemHistory: "Version history",
|
|
1226
|
-
historyTitle: "Version history",
|
|
1227
|
-
historyEmpty: "No snapshots yet (auto-captured when idle, or capture manually)",
|
|
1228
|
-
historyCapture: "Capture snapshot",
|
|
1229
|
-
historyRestore: "Restore this version",
|
|
1230
|
-
historyCurrent: "Current",
|
|
1231
|
-
historyDiffAdded: "Added",
|
|
1232
|
-
historyDiffRemoved: "Removed",
|
|
1233
|
-
historyDiffChanged: "Changed",
|
|
1234
|
-
historyConfirmRestore: "Restore this version? Current content will be replaced (undoable)",
|
|
1235
1296
|
// v1.1: comments
|
|
1236
1297
|
tooltipCommentV11: "Comment",
|
|
1237
1298
|
commentTitle: "Comments",
|
|
@@ -1243,7 +1304,7 @@ var tesseraMessages = {
|
|
|
1243
1304
|
commentDelete: "Delete",
|
|
1244
1305
|
commentResolvedBadge: "Resolved",
|
|
1245
1306
|
commentCount: (n) => `${n} comment${n === 1 ? "" : "s"}`,
|
|
1246
|
-
// v1.1: embed / toc
|
|
1307
|
+
// v1.1: embed / toc
|
|
1247
1308
|
itemEmbed: "Embed",
|
|
1248
1309
|
itemEmbedDesc: "Embed an external page (sandboxed iframe)",
|
|
1249
1310
|
itemToc: "Table of contents",
|
|
@@ -1253,17 +1314,17 @@ var tesseraMessages = {
|
|
|
1253
1314
|
embedInvalid: "Invalid URL",
|
|
1254
1315
|
embedOpen: "Open original",
|
|
1255
1316
|
tocEmpty: "No headings yet \u2014 add H1\u2013H4 and they appear here",
|
|
1256
|
-
placeholderText: "to fill in",
|
|
1257
|
-
placeholderPerson: "assignee",
|
|
1258
|
-
placeholderDate: "due date",
|
|
1259
1317
|
// v1.1: block context menu
|
|
1260
1318
|
menuCopyAnchor: "Copy anchor link",
|
|
1261
1319
|
menuCopyBlockId: "Copy block ID",
|
|
1262
1320
|
menuDeleteBlock: "Delete block"
|
|
1263
1321
|
}
|
|
1264
1322
|
};
|
|
1265
|
-
function createTesseraT(locale = "zh-CN") {
|
|
1266
|
-
const table =
|
|
1323
|
+
function createTesseraT(locale = "zh-CN", overrides) {
|
|
1324
|
+
const table = {
|
|
1325
|
+
...tesseraMessages[locale] ?? tesseraMessages["zh-CN"],
|
|
1326
|
+
...overrides
|
|
1327
|
+
};
|
|
1267
1328
|
return (key) => table[key] ?? key;
|
|
1268
1329
|
}
|
|
1269
1330
|
|
|
@@ -1271,6 +1332,37 @@ function createTesseraT(locale = "zh-CN") {
|
|
|
1271
1332
|
function chainDelete(editor, range) {
|
|
1272
1333
|
return editor.chain().focus().deleteRange(range);
|
|
1273
1334
|
}
|
|
1335
|
+
function slashItemNodeName(item) {
|
|
1336
|
+
switch (item.id) {
|
|
1337
|
+
case "divider":
|
|
1338
|
+
return "horizontalRule";
|
|
1339
|
+
case "embed":
|
|
1340
|
+
return "embedBlock";
|
|
1341
|
+
case "toc":
|
|
1342
|
+
return "tocBlock";
|
|
1343
|
+
case "image":
|
|
1344
|
+
return "imageBlock";
|
|
1345
|
+
case "table":
|
|
1346
|
+
case "table-simple":
|
|
1347
|
+
return "table";
|
|
1348
|
+
case "h1":
|
|
1349
|
+
case "h2":
|
|
1350
|
+
case "h3":
|
|
1351
|
+
case "h4":
|
|
1352
|
+
return "heading";
|
|
1353
|
+
case "text":
|
|
1354
|
+
return "paragraph";
|
|
1355
|
+
default:
|
|
1356
|
+
return item.id;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
function filterSlashItems(items, excludeBlocks) {
|
|
1360
|
+
if (!excludeBlocks || excludeBlocks.length === 0) {
|
|
1361
|
+
return items;
|
|
1362
|
+
}
|
|
1363
|
+
const excluded = new Set(excludeBlocks);
|
|
1364
|
+
return items.filter((item) => !excluded.has(slashItemNodeName(item)));
|
|
1365
|
+
}
|
|
1274
1366
|
function defaultSlashItems(t) {
|
|
1275
1367
|
return [
|
|
1276
1368
|
{
|
|
@@ -1427,41 +1519,18 @@ function defaultSlashItems(t) {
|
|
|
1427
1519
|
description: t("itemTocDesc"),
|
|
1428
1520
|
keywords: ["toc", "outline", "\u76EE\u5F55", "\u5927\u7EB2"],
|
|
1429
1521
|
command: ({ editor, range }) => chainDelete(editor, range).insertToc().run()
|
|
1430
|
-
},
|
|
1431
|
-
{
|
|
1432
|
-
id: "placeholder-person",
|
|
1433
|
-
group: "advanced",
|
|
1434
|
-
title: t("placeholderPerson"),
|
|
1435
|
-
keywords: ["somebody", "person", "owner", "\u5F85\u586B\u4EBA"],
|
|
1436
|
-
command: ({ editor, range }) => chainDelete(editor, range).insertPlaceholderToken("person", t("placeholderPerson")).run()
|
|
1437
|
-
},
|
|
1438
|
-
{
|
|
1439
|
-
id: "placeholder-date",
|
|
1440
|
-
group: "advanced",
|
|
1441
|
-
title: t("placeholderDate"),
|
|
1442
|
-
keywords: ["date", "due", "\u5F85\u586B\u65E5\u671F"],
|
|
1443
|
-
command: ({ editor, range }) => chainDelete(editor, range).insertPlaceholderToken("date", t("placeholderDate")).run()
|
|
1444
|
-
},
|
|
1445
|
-
{
|
|
1446
|
-
id: "history",
|
|
1447
|
-
group: "advanced",
|
|
1448
|
-
title: t("itemHistory"),
|
|
1449
|
-
keywords: ["history", "version", "snapshot", "\u5386\u53F2", "\u7248\u672C"],
|
|
1450
|
-
command: ({ editor, range }) => {
|
|
1451
|
-
chainDelete(editor, range).run();
|
|
1452
|
-
editor.emit("tessera:historyPanel", {});
|
|
1453
|
-
}
|
|
1454
1522
|
}
|
|
1455
1523
|
];
|
|
1456
1524
|
}
|
|
1457
|
-
var SlashMenu =
|
|
1525
|
+
var SlashMenu = Extension5.create({
|
|
1458
1526
|
name: "tesseraSlashMenu",
|
|
1459
1527
|
addOptions() {
|
|
1460
1528
|
return {
|
|
1461
1529
|
locale: "zh-CN",
|
|
1462
1530
|
extraItems: void 0,
|
|
1463
1531
|
includeAiItems: false,
|
|
1464
|
-
render: void 0
|
|
1532
|
+
render: void 0,
|
|
1533
|
+
excludeItems: void 0
|
|
1465
1534
|
};
|
|
1466
1535
|
},
|
|
1467
1536
|
addProseMirrorPlugins() {
|
|
@@ -1469,7 +1538,7 @@ var SlashMenu = Extension6.create({
|
|
|
1469
1538
|
const options = this.options;
|
|
1470
1539
|
const t = createTesseraT(options.locale);
|
|
1471
1540
|
const items = [
|
|
1472
|
-
...defaultSlashItems(t),
|
|
1541
|
+
...filterSlashItems(defaultSlashItems(t), options.excludeItems),
|
|
1473
1542
|
...options.extraItems?.({ editor, t }) ?? []
|
|
1474
1543
|
];
|
|
1475
1544
|
return [
|
|
@@ -1498,7 +1567,7 @@ var SlashMenu = Extension6.create({
|
|
|
1498
1567
|
});
|
|
1499
1568
|
|
|
1500
1569
|
// src/extensions/emoji.ts
|
|
1501
|
-
import { Extension as
|
|
1570
|
+
import { Extension as Extension6 } from "@tiptap/core";
|
|
1502
1571
|
import { PluginKey as PluginKey4 } from "@tiptap/pm/state";
|
|
1503
1572
|
import Suggestion2 from "@tiptap/suggestion";
|
|
1504
1573
|
var EMOJI_ITEMS = [
|
|
@@ -1587,7 +1656,7 @@ function filterEmojiItems(query, items = EMOJI_ITEMS) {
|
|
|
1587
1656
|
(item) => item.name.toLowerCase().includes(q) || item.keywords.some((k) => k.toLowerCase().includes(q))
|
|
1588
1657
|
);
|
|
1589
1658
|
}
|
|
1590
|
-
var EmojiMenu =
|
|
1659
|
+
var EmojiMenu = Extension6.create({
|
|
1591
1660
|
name: "tesseraEmojiMenu",
|
|
1592
1661
|
addOptions() {
|
|
1593
1662
|
return {
|
|
@@ -1614,141 +1683,10 @@ var EmojiMenu = Extension7.create({
|
|
|
1614
1683
|
}
|
|
1615
1684
|
});
|
|
1616
1685
|
|
|
1617
|
-
// src/extensions/history.ts
|
|
1618
|
-
import { Extension as Extension9 } from "@tiptap/core";
|
|
1619
|
-
import { Plugin as Plugin3, PluginKey as PluginKey5 } from "@tiptap/pm/state";
|
|
1620
|
-
|
|
1621
|
-
// src/services.ts
|
|
1622
|
-
import { Extension as Extension8 } from "@tiptap/core";
|
|
1623
|
-
var TesseraServices = Extension8.create({
|
|
1624
|
-
name: "tesseraServices",
|
|
1625
|
-
addStorage() {
|
|
1626
|
-
return {
|
|
1627
|
-
upload: void 0,
|
|
1628
|
-
storage: void 0,
|
|
1629
|
-
comments: void 0,
|
|
1630
|
-
identity: void 0
|
|
1631
|
-
};
|
|
1632
|
-
}
|
|
1633
|
-
});
|
|
1634
|
-
function servicesBag(editor) {
|
|
1635
|
-
return editor.storage.tesseraServices;
|
|
1636
|
-
}
|
|
1637
|
-
function getUploadService(editor) {
|
|
1638
|
-
return servicesBag(editor)?.upload;
|
|
1639
|
-
}
|
|
1640
|
-
function getStorageService(editor) {
|
|
1641
|
-
return servicesBag(editor)?.storage;
|
|
1642
|
-
}
|
|
1643
|
-
function getCommentStore(editor) {
|
|
1644
|
-
return servicesBag(editor)?.comments;
|
|
1645
|
-
}
|
|
1646
|
-
function getIdentityService(editor) {
|
|
1647
|
-
return servicesBag(editor)?.identity;
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
// src/extensions/history.ts
|
|
1651
|
-
var historyKey = new PluginKey5("tesseraHistory");
|
|
1652
|
-
var TesseraHistory = Extension9.create({
|
|
1653
|
-
name: "tesseraHistory",
|
|
1654
|
-
addOptions() {
|
|
1655
|
-
return {
|
|
1656
|
-
idleMs: 5 * 60 * 1e3,
|
|
1657
|
-
minIntervalMs: 60 * 1e3,
|
|
1658
|
-
label: void 0
|
|
1659
|
-
};
|
|
1660
|
-
},
|
|
1661
|
-
addCommands() {
|
|
1662
|
-
return {
|
|
1663
|
-
captureSnapshot: (label) => ({ editor, state }) => {
|
|
1664
|
-
const storage = getStorageService(editor);
|
|
1665
|
-
if (!storage) {
|
|
1666
|
-
return false;
|
|
1667
|
-
}
|
|
1668
|
-
const doc = editor.getJSON();
|
|
1669
|
-
const snapshot = {
|
|
1670
|
-
id: `snap-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1671
|
-
ts: Date.now(),
|
|
1672
|
-
doc,
|
|
1673
|
-
label: label ?? this.options.label?.()
|
|
1674
|
-
};
|
|
1675
|
-
void storage.saveSnapshot(snapshot).then(() => {
|
|
1676
|
-
editor.emit("tessera:snapshotSaved", { snapshot });
|
|
1677
|
-
});
|
|
1678
|
-
const tr = state.tr.setMeta(historyKey, { type: "captured", ts: snapshot.ts });
|
|
1679
|
-
editor.view.dispatch(tr);
|
|
1680
|
-
return true;
|
|
1681
|
-
}
|
|
1682
|
-
};
|
|
1683
|
-
},
|
|
1684
|
-
addProseMirrorPlugins() {
|
|
1685
|
-
const editor = this.editor;
|
|
1686
|
-
const options = this.options;
|
|
1687
|
-
return [
|
|
1688
|
-
new Plugin3({
|
|
1689
|
-
key: historyKey,
|
|
1690
|
-
state: {
|
|
1691
|
-
init: () => ({ lastChange: 0, lastCapture: 0, timer: null }),
|
|
1692
|
-
apply: (tr, prev) => {
|
|
1693
|
-
const meta = tr.getMeta(historyKey);
|
|
1694
|
-
if (meta?.type === "captured") {
|
|
1695
|
-
return { ...prev, lastCapture: meta.ts ?? Date.now() };
|
|
1696
|
-
}
|
|
1697
|
-
if (!tr.docChanged) {
|
|
1698
|
-
return prev;
|
|
1699
|
-
}
|
|
1700
|
-
return { ...prev, lastChange: Date.now() };
|
|
1701
|
-
}
|
|
1702
|
-
},
|
|
1703
|
-
view() {
|
|
1704
|
-
return {
|
|
1705
|
-
update: (_view, prevState) => {
|
|
1706
|
-
const before = historyKey.getState(prevState);
|
|
1707
|
-
const after = historyKey.getState(editor.state);
|
|
1708
|
-
if (!before || !after || after.lastChange === before.lastChange) {
|
|
1709
|
-
return;
|
|
1710
|
-
}
|
|
1711
|
-
const storage = getStorageService(editor);
|
|
1712
|
-
if (!storage) {
|
|
1713
|
-
return;
|
|
1714
|
-
}
|
|
1715
|
-
const s = historyKey.getState(editor.state);
|
|
1716
|
-
if (s?.timer) {
|
|
1717
|
-
clearTimeout(s.timer);
|
|
1718
|
-
}
|
|
1719
|
-
const timer = setTimeout(() => {
|
|
1720
|
-
const now = Date.now();
|
|
1721
|
-
const cur = historyKey.getState(editor.state);
|
|
1722
|
-
if (!cur || now - cur.lastChange < options.idleMs - 50) {
|
|
1723
|
-
return;
|
|
1724
|
-
}
|
|
1725
|
-
if (now - cur.lastCapture < options.minIntervalMs) {
|
|
1726
|
-
return;
|
|
1727
|
-
}
|
|
1728
|
-
editor.commands.captureSnapshot();
|
|
1729
|
-
}, options.idleMs);
|
|
1730
|
-
const st = historyKey.getState(editor.state);
|
|
1731
|
-
if (st) {
|
|
1732
|
-
st.timer = timer;
|
|
1733
|
-
}
|
|
1734
|
-
},
|
|
1735
|
-
destroy() {
|
|
1736
|
-
const s = historyKey.getState(editor.state);
|
|
1737
|
-
if (s?.timer) {
|
|
1738
|
-
clearTimeout(s.timer);
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
};
|
|
1742
|
-
}
|
|
1743
|
-
})
|
|
1744
|
-
];
|
|
1745
|
-
}
|
|
1746
|
-
});
|
|
1747
|
-
|
|
1748
1686
|
// src/extensions/context-menu.ts
|
|
1749
|
-
import { Extension as
|
|
1687
|
+
import { Extension as Extension7 } from "@tiptap/core";
|
|
1750
1688
|
import { Plugin as Plugin4 } from "@tiptap/pm/state";
|
|
1751
|
-
var BlockContextMenu =
|
|
1689
|
+
var BlockContextMenu = Extension7.create({
|
|
1752
1690
|
name: "tesseraBlockMenu",
|
|
1753
1691
|
addProseMirrorPlugins() {
|
|
1754
1692
|
const editor = this.editor;
|
|
@@ -1757,6 +1695,9 @@ var BlockContextMenu = Extension10.create({
|
|
|
1757
1695
|
props: {
|
|
1758
1696
|
handleDOMEvents: {
|
|
1759
1697
|
contextmenu: (view, event) => {
|
|
1698
|
+
if (!editor.isEditable) {
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1760
1701
|
const coords = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
|
1761
1702
|
if (!coords) {
|
|
1762
1703
|
return false;
|
|
@@ -1785,10 +1726,10 @@ var BlockContextMenu = Extension10.create({
|
|
|
1785
1726
|
});
|
|
1786
1727
|
|
|
1787
1728
|
// src/extensions/gallery.ts
|
|
1788
|
-
import { Extension as
|
|
1789
|
-
import { Plugin as Plugin5, PluginKey as
|
|
1729
|
+
import { Extension as Extension8 } from "@tiptap/core";
|
|
1730
|
+
import { Plugin as Plugin5, PluginKey as PluginKey5 } from "@tiptap/pm/state";
|
|
1790
1731
|
import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
|
|
1791
|
-
var galleryKey = new
|
|
1732
|
+
var galleryKey = new PluginKey5("tesseraGallery");
|
|
1792
1733
|
function findGalleryRuns(doc) {
|
|
1793
1734
|
const runs = [];
|
|
1794
1735
|
let start = -1;
|
|
@@ -1833,7 +1774,7 @@ function galleryDecorations(doc) {
|
|
|
1833
1774
|
}
|
|
1834
1775
|
return DecorationSet2.create(doc, decorations);
|
|
1835
1776
|
}
|
|
1836
|
-
var TesseraGallery =
|
|
1777
|
+
var TesseraGallery = Extension8.create({
|
|
1837
1778
|
name: "tesseraGallery",
|
|
1838
1779
|
addProseMirrorPlugins() {
|
|
1839
1780
|
return [
|
|
@@ -1854,7 +1795,7 @@ var TesseraGallery = Extension11.create({
|
|
|
1854
1795
|
});
|
|
1855
1796
|
|
|
1856
1797
|
// src/extensions/metrics.ts
|
|
1857
|
-
import { Extension as
|
|
1798
|
+
import { Extension as Extension9 } from "@tiptap/core";
|
|
1858
1799
|
|
|
1859
1800
|
// src/markdown.ts
|
|
1860
1801
|
import MarkdownIt from "markdown-it";
|
|
@@ -2008,7 +1949,6 @@ function createMarkdownSerializer(schema) {
|
|
|
2008
1949
|
textStyle: { open: "", close: "", mixable: true },
|
|
2009
1950
|
color: { open: "", close: "", mixable: true },
|
|
2010
1951
|
aiAttribution: { open: "", close: "", mixable: true },
|
|
2011
|
-
tesseraPlaceholder: { open: "", close: "", mixable: true },
|
|
2012
1952
|
comment: { open: "", close: "", mixable: true }
|
|
2013
1953
|
};
|
|
2014
1954
|
return new MarkdownSerializer(nodes, marks);
|
|
@@ -2078,7 +2018,7 @@ function measureTesseraMetrics(editor) {
|
|
|
2078
2018
|
serializeMs
|
|
2079
2019
|
};
|
|
2080
2020
|
}
|
|
2081
|
-
var TesseraMetrics =
|
|
2021
|
+
var TesseraMetrics = Extension9.create({
|
|
2082
2022
|
name: "tesseraMetrics"
|
|
2083
2023
|
});
|
|
2084
2024
|
function getTesseraMetrics(editor) {
|
|
@@ -2086,8 +2026,8 @@ function getTesseraMetrics(editor) {
|
|
|
2086
2026
|
}
|
|
2087
2027
|
|
|
2088
2028
|
// src/extensions/word-paste.ts
|
|
2089
|
-
import { Extension as
|
|
2090
|
-
import { Plugin as Plugin6, PluginKey as
|
|
2029
|
+
import { Extension as Extension10 } from "@tiptap/core";
|
|
2030
|
+
import { Plugin as Plugin6, PluginKey as PluginKey6 } from "@tiptap/pm/state";
|
|
2091
2031
|
|
|
2092
2032
|
// src/wordpaste.ts
|
|
2093
2033
|
function isWordHtml(html) {
|
|
@@ -2114,12 +2054,12 @@ function cleanWordHtml(html) {
|
|
|
2114
2054
|
}
|
|
2115
2055
|
|
|
2116
2056
|
// src/extensions/word-paste.ts
|
|
2117
|
-
var TesseraWordPaste =
|
|
2057
|
+
var TesseraWordPaste = Extension10.create({
|
|
2118
2058
|
name: "tesseraWordPaste",
|
|
2119
2059
|
addProseMirrorPlugins() {
|
|
2120
2060
|
return [
|
|
2121
2061
|
new Plugin6({
|
|
2122
|
-
key: new
|
|
2062
|
+
key: new PluginKey6("tesseraWordPaste"),
|
|
2123
2063
|
props: {
|
|
2124
2064
|
transformPastedHTML(html) {
|
|
2125
2065
|
return isWordHtml(html) ? cleanWordHtml(html) : html;
|
|
@@ -2153,11 +2093,54 @@ function deleteBlockById(editor, id) {
|
|
|
2153
2093
|
editor.view.dispatch(tr);
|
|
2154
2094
|
return true;
|
|
2155
2095
|
}
|
|
2156
|
-
function blockAnchorUrl(
|
|
2157
|
-
return `${location.origin}${location.pathname}#block-${
|
|
2096
|
+
function blockAnchorUrl(blockId) {
|
|
2097
|
+
return `${location.origin}${location.pathname}#block-${blockId}`;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// src/linkedit.ts
|
|
2101
|
+
import { getMarkRange } from "@tiptap/core";
|
|
2102
|
+
function findLinkRange(editor, pos) {
|
|
2103
|
+
const { doc, schema } = editor.state;
|
|
2104
|
+
const at = pos ?? editor.state.selection.from;
|
|
2105
|
+
const linkType = schema.marks.link;
|
|
2106
|
+
for (const p of [at, Math.max(0, at - 1)]) {
|
|
2107
|
+
const range = getMarkRange(doc.resolve(p), linkType);
|
|
2108
|
+
if (!range) continue;
|
|
2109
|
+
const node = doc.nodeAt(range.from);
|
|
2110
|
+
const href = node?.marks.find((m) => m.type === linkType)?.attrs.href ?? "";
|
|
2111
|
+
return {
|
|
2112
|
+
from: range.from,
|
|
2113
|
+
to: range.to,
|
|
2114
|
+
text: doc.textBetween(range.from, range.to, "\n"),
|
|
2115
|
+
href
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
return null;
|
|
2119
|
+
}
|
|
2120
|
+
function saveLinkRange(editor, range, next) {
|
|
2121
|
+
const { schema, tr, doc } = editor.state;
|
|
2122
|
+
const linkType = schema.marks.link;
|
|
2123
|
+
if (!next.href.trim()) {
|
|
2124
|
+
removeLinkRange(editor, range);
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
const linkMark = linkType.create({ href: next.href.trim() });
|
|
2128
|
+
if (next.text !== range.text) {
|
|
2129
|
+
const first = doc.nodeAt(range.from);
|
|
2130
|
+
const otherMarks = (first?.marks ?? []).filter((m) => m.type !== linkType);
|
|
2131
|
+
tr.replaceWith(range.from, range.to, schema.text(next.text, [...otherMarks, linkMark]));
|
|
2132
|
+
} else {
|
|
2133
|
+
tr.removeMark(range.from, range.to, linkType).addMark(range.from, range.to, linkMark);
|
|
2134
|
+
}
|
|
2135
|
+
editor.view.dispatch(tr);
|
|
2136
|
+
}
|
|
2137
|
+
function removeLinkRange(editor, range) {
|
|
2138
|
+
const tr = editor.state.tr.removeMark(range.from, range.to, editor.state.schema.marks.link);
|
|
2139
|
+
editor.view.dispatch(tr);
|
|
2158
2140
|
}
|
|
2159
2141
|
|
|
2160
2142
|
// src/preset.ts
|
|
2143
|
+
import { getSchema } from "@tiptap/core";
|
|
2161
2144
|
import StarterKit from "@tiptap/starter-kit";
|
|
2162
2145
|
import { TaskList, TaskItem } from "@tiptap/extension-list";
|
|
2163
2146
|
import { TextStyle } from "@tiptap/extension-text-style";
|
|
@@ -2165,6 +2148,33 @@ import { Color } from "@tiptap/extension-color";
|
|
|
2165
2148
|
import { Highlight } from "@tiptap/extension-highlight";
|
|
2166
2149
|
import { UniqueID } from "@tiptap/extension-unique-id";
|
|
2167
2150
|
import { Placeholder } from "@tiptap/extensions";
|
|
2151
|
+
|
|
2152
|
+
// src/services.ts
|
|
2153
|
+
import { Extension as Extension11 } from "@tiptap/core";
|
|
2154
|
+
var TesseraServices = Extension11.create({
|
|
2155
|
+
name: "tesseraServices",
|
|
2156
|
+
addStorage() {
|
|
2157
|
+
return {
|
|
2158
|
+
upload: void 0,
|
|
2159
|
+
comments: void 0,
|
|
2160
|
+
identity: void 0
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
function servicesBag(editor) {
|
|
2165
|
+
return editor.storage.tesseraServices;
|
|
2166
|
+
}
|
|
2167
|
+
function getUploadService(editor) {
|
|
2168
|
+
return servicesBag(editor)?.upload;
|
|
2169
|
+
}
|
|
2170
|
+
function getCommentStore(editor) {
|
|
2171
|
+
return servicesBag(editor)?.comments;
|
|
2172
|
+
}
|
|
2173
|
+
function getIdentityService(editor) {
|
|
2174
|
+
return servicesBag(editor)?.identity;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/preset.ts
|
|
2168
2178
|
var ID_BLOCK_TYPES = [
|
|
2169
2179
|
"paragraph",
|
|
2170
2180
|
"heading",
|
|
@@ -2184,8 +2194,26 @@ var ID_BLOCK_TYPES = [
|
|
|
2184
2194
|
"embedBlock",
|
|
2185
2195
|
"tocBlock"
|
|
2186
2196
|
];
|
|
2197
|
+
var SUPPORTED_BLOCK_TYPES = [
|
|
2198
|
+
"paragraph",
|
|
2199
|
+
"heading",
|
|
2200
|
+
"bulletList",
|
|
2201
|
+
"orderedList",
|
|
2202
|
+
"taskList",
|
|
2203
|
+
"blockquote",
|
|
2204
|
+
"codeBlock",
|
|
2205
|
+
"horizontalRule",
|
|
2206
|
+
"imageBlock",
|
|
2207
|
+
"table",
|
|
2208
|
+
"hint",
|
|
2209
|
+
"collapsible",
|
|
2210
|
+
"embedBlock",
|
|
2211
|
+
"tocBlock"
|
|
2212
|
+
];
|
|
2187
2213
|
function createTesseraExtensions(options = {}) {
|
|
2188
|
-
const t = createTesseraT(options.locale ?? "zh-CN");
|
|
2214
|
+
const t = createTesseraT(options.locale ?? "zh-CN", options.messages);
|
|
2215
|
+
const excluded = new Set(options.excludeBlocks ?? []);
|
|
2216
|
+
const keep = (name) => !excluded.has(name);
|
|
2189
2217
|
return [
|
|
2190
2218
|
StarterKit.configure({
|
|
2191
2219
|
heading: { levels: [1, 2, 3, 4] },
|
|
@@ -2193,171 +2221,46 @@ function createTesseraExtensions(options = {}) {
|
|
|
2193
2221
|
openOnClick: false,
|
|
2194
2222
|
autolink: true,
|
|
2195
2223
|
defaultProtocol: "https"
|
|
2196
|
-
}
|
|
2224
|
+
},
|
|
2197
2225
|
// undoRedo keeps defaults (newGroupDelay 500ms): streaming AI chunks
|
|
2198
2226
|
// arriving faster than that already merge into one undo step.
|
|
2227
|
+
horizontalRule: keep("horizontalRule") ? void 0 : false
|
|
2199
2228
|
}),
|
|
2200
2229
|
TextStyle,
|
|
2201
2230
|
Color,
|
|
2202
2231
|
Highlight.configure({ multicolor: true }),
|
|
2203
|
-
TaskList,
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
AiTable,
|
|
2211
|
-
TableRow,
|
|
2212
|
-
AiTableCell,
|
|
2213
|
-
AiTableHeader,
|
|
2214
|
-
EmbedBlock,
|
|
2215
|
-
TocBlock,
|
|
2232
|
+
...keep("taskList") ? [TaskList, TaskItem.configure({ nested: true })] : [],
|
|
2233
|
+
...keep("hint") ? [Hint] : [],
|
|
2234
|
+
...keep("collapsible") ? [Collapsible, CollapsibleSummary, CollapsibleContent] : [],
|
|
2235
|
+
...keep("imageBlock") ? [ImageBlock] : [],
|
|
2236
|
+
...keep("table") ? [AiTable, TableRow, AiTableCell, AiTableHeader] : [],
|
|
2237
|
+
...keep("embedBlock") ? [EmbedBlock] : [],
|
|
2238
|
+
...keep("tocBlock") ? [TocBlock] : [],
|
|
2216
2239
|
AiAttribution,
|
|
2217
2240
|
CommentMark,
|
|
2218
2241
|
CommentCommands,
|
|
2219
|
-
PlaceholderMark,
|
|
2220
|
-
PlaceholderCommands,
|
|
2221
2242
|
UniqueID.configure({
|
|
2222
|
-
types: ID_BLOCK_TYPES,
|
|
2243
|
+
types: ID_BLOCK_TYPES.filter(keep),
|
|
2223
2244
|
attributeName: "id"
|
|
2224
2245
|
}),
|
|
2225
2246
|
Placeholder.configure({
|
|
2226
|
-
placeholder: ({ node }) => node.type.name === "paragraph" ? t("placeholderEmpty") : "",
|
|
2247
|
+
placeholder: ({ node }) => node.type.name === "paragraph" ? options.placeholder ?? t("placeholderEmpty") : "",
|
|
2227
2248
|
showOnlyWhenEditable: true
|
|
2228
2249
|
}),
|
|
2229
2250
|
TesseraInputRules,
|
|
2230
2251
|
TesseraShortcuts,
|
|
2231
2252
|
TesseraFindReplace,
|
|
2232
|
-
TesseraHistory.configure({ idleMs: options.historyIdleMs }),
|
|
2233
2253
|
BlockContextMenu,
|
|
2234
|
-
SlashMenu.configure({ locale: options.locale ?? "zh-CN" }),
|
|
2254
|
+
SlashMenu.configure({ locale: options.locale ?? "zh-CN", excludeItems: options.excludeBlocks }),
|
|
2235
2255
|
EmojiMenu,
|
|
2236
|
-
TesseraGallery,
|
|
2256
|
+
...keep("imageBlock") ? [TesseraGallery] : [],
|
|
2237
2257
|
TesseraMetrics,
|
|
2238
2258
|
TesseraWordPaste,
|
|
2239
2259
|
TesseraServices
|
|
2240
2260
|
];
|
|
2241
2261
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
function topLevel(doc) {
|
|
2245
|
-
return doc.content ?? [];
|
|
2246
|
-
}
|
|
2247
|
-
function blockId(block) {
|
|
2248
|
-
const id = block.attrs?.id;
|
|
2249
|
-
return typeof id === "string" ? id : null;
|
|
2250
|
-
}
|
|
2251
|
-
function collectText(node) {
|
|
2252
|
-
let text = "";
|
|
2253
|
-
if (node.text) {
|
|
2254
|
-
text += node.text;
|
|
2255
|
-
}
|
|
2256
|
-
for (const child of node.content ?? []) {
|
|
2257
|
-
text += collectText(child);
|
|
2258
|
-
}
|
|
2259
|
-
return text;
|
|
2260
|
-
}
|
|
2261
|
-
function tokenize(text) {
|
|
2262
|
-
return text.match(/[\u4e00-\u9fa5]|[a-zA-Z0-9]+|\s+|[^\sa-zA-Z0-9\u4e00-\u9fa5]/g) ?? [];
|
|
2263
|
-
}
|
|
2264
|
-
function wordDiff(beforeText, afterText) {
|
|
2265
|
-
const a = tokenize(beforeText);
|
|
2266
|
-
const b = tokenize(afterText);
|
|
2267
|
-
if (a.length * b.length > 4e6) {
|
|
2268
|
-
return [
|
|
2269
|
-
{ text: beforeText, type: "del" },
|
|
2270
|
-
{ text: afterText, type: "add" }
|
|
2271
|
-
];
|
|
2272
|
-
}
|
|
2273
|
-
const dp = Array.from({ length: a.length + 1 }, () => new Uint32Array(b.length + 1));
|
|
2274
|
-
for (let i2 = a.length - 1; i2 >= 0; i2--) {
|
|
2275
|
-
for (let j2 = b.length - 1; j2 >= 0; j2--) {
|
|
2276
|
-
dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
const parts = [];
|
|
2280
|
-
const push = (text, type) => {
|
|
2281
|
-
const last = parts[parts.length - 1];
|
|
2282
|
-
if (last && last.type === type) {
|
|
2283
|
-
last.text += text;
|
|
2284
|
-
} else {
|
|
2285
|
-
parts.push({ text, type });
|
|
2286
|
-
}
|
|
2287
|
-
};
|
|
2288
|
-
let i = 0;
|
|
2289
|
-
let j = 0;
|
|
2290
|
-
while (i < a.length && j < b.length) {
|
|
2291
|
-
if (a[i] === b[j]) {
|
|
2292
|
-
push(a[i], "same");
|
|
2293
|
-
i++;
|
|
2294
|
-
j++;
|
|
2295
|
-
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
2296
|
-
push(a[i], "del");
|
|
2297
|
-
i++;
|
|
2298
|
-
} else {
|
|
2299
|
-
push(b[j], "add");
|
|
2300
|
-
j++;
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
while (i < a.length) {
|
|
2304
|
-
push(a[i++], "del");
|
|
2305
|
-
}
|
|
2306
|
-
while (j < b.length) {
|
|
2307
|
-
push(b[j++], "add");
|
|
2308
|
-
}
|
|
2309
|
-
return parts;
|
|
2310
|
-
}
|
|
2311
|
-
function sameBlock(a, b) {
|
|
2312
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
2313
|
-
}
|
|
2314
|
-
function diffDocs(before, after) {
|
|
2315
|
-
const beforeBlocks = topLevel(before);
|
|
2316
|
-
const afterBlocks = topLevel(after);
|
|
2317
|
-
const afterById = /* @__PURE__ */ new Map();
|
|
2318
|
-
for (const block of afterBlocks) {
|
|
2319
|
-
const id = blockId(block);
|
|
2320
|
-
if (id) {
|
|
2321
|
-
afterById.set(id, block);
|
|
2322
|
-
}
|
|
2323
|
-
}
|
|
2324
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2325
|
-
const entries = [];
|
|
2326
|
-
for (const block of beforeBlocks) {
|
|
2327
|
-
const id = blockId(block);
|
|
2328
|
-
if (id && afterById.has(id)) {
|
|
2329
|
-
seen.add(id);
|
|
2330
|
-
const next = afterById.get(id);
|
|
2331
|
-
if (sameBlock(block, next)) {
|
|
2332
|
-
entries.push({ kind: "unchanged", id, before: block, after: next });
|
|
2333
|
-
} else {
|
|
2334
|
-
entries.push({ kind: "changed", id, before: block, after: next, wordDiff: wordDiff(collectText(block), collectText(next)) });
|
|
2335
|
-
}
|
|
2336
|
-
} else {
|
|
2337
|
-
entries.push({ kind: "removed", id: id ?? void 0, before: block });
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2340
|
-
for (const block of afterBlocks) {
|
|
2341
|
-
const id = blockId(block);
|
|
2342
|
-
if (id && seen.has(id)) {
|
|
2343
|
-
continue;
|
|
2344
|
-
}
|
|
2345
|
-
if (!id || !beforeBlocks.some((b) => blockId(b) === id)) {
|
|
2346
|
-
entries.push({ kind: "added", id: id ?? void 0, after: block });
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
return entries;
|
|
2350
|
-
}
|
|
2351
|
-
function diffSummary(entries) {
|
|
2352
|
-
let added = 0;
|
|
2353
|
-
let removed = 0;
|
|
2354
|
-
let changed = 0;
|
|
2355
|
-
for (const entry of entries) {
|
|
2356
|
-
if (entry.kind === "added") added++;
|
|
2357
|
-
else if (entry.kind === "removed") removed++;
|
|
2358
|
-
else if (entry.kind === "changed") changed++;
|
|
2359
|
-
}
|
|
2360
|
-
return { added, removed, changed };
|
|
2262
|
+
function createTesseraSchema(options = {}) {
|
|
2263
|
+
return getSchema(createTesseraExtensions(options));
|
|
2361
2264
|
}
|
|
2362
2265
|
|
|
2363
2266
|
// src/writeback.ts
|
|
@@ -2485,13 +2388,11 @@ export {
|
|
|
2485
2388
|
Hint,
|
|
2486
2389
|
ID_BLOCK_TYPES,
|
|
2487
2390
|
ImageBlock,
|
|
2488
|
-
|
|
2489
|
-
PlaceholderMark,
|
|
2391
|
+
SUPPORTED_BLOCK_TYPES,
|
|
2490
2392
|
SlashMenu,
|
|
2491
2393
|
TABLE_COLUMN_KINDS,
|
|
2492
2394
|
TesseraFindReplace,
|
|
2493
2395
|
TesseraGallery,
|
|
2494
|
-
TesseraHistory,
|
|
2495
2396
|
TesseraInputRules,
|
|
2496
2397
|
TesseraMetrics,
|
|
2497
2398
|
TesseraServices,
|
|
@@ -2503,25 +2404,24 @@ export {
|
|
|
2503
2404
|
cleanWordHtml,
|
|
2504
2405
|
createMarkdownSerializer,
|
|
2505
2406
|
createTesseraExtensions,
|
|
2407
|
+
createTesseraSchema,
|
|
2506
2408
|
createTesseraT,
|
|
2507
2409
|
defaultSlashItems,
|
|
2508
2410
|
deleteBlockById,
|
|
2509
|
-
diffDocs,
|
|
2510
|
-
diffSummary,
|
|
2511
2411
|
docToMarkdown,
|
|
2512
2412
|
filterEmojiItems,
|
|
2413
|
+
filterSlashItems,
|
|
2513
2414
|
findBlockPosById,
|
|
2514
2415
|
findGalleryRuns,
|
|
2416
|
+
findLinkRange,
|
|
2515
2417
|
findReplaceKey,
|
|
2516
2418
|
galleryKey,
|
|
2517
2419
|
getBlockJson,
|
|
2518
2420
|
getCommentStore,
|
|
2519
2421
|
getIdentityService,
|
|
2520
|
-
getStorageService,
|
|
2521
2422
|
getTesseraMetrics,
|
|
2522
2423
|
getTopLevelBlocks,
|
|
2523
2424
|
getUploadService,
|
|
2524
|
-
historyKey,
|
|
2525
2425
|
isWordHtml,
|
|
2526
2426
|
listCommentRanges,
|
|
2527
2427
|
markdownToDoc,
|
|
@@ -2529,10 +2429,12 @@ export {
|
|
|
2529
2429
|
modifyRange,
|
|
2530
2430
|
normalizeTypes,
|
|
2531
2431
|
removeBlocks,
|
|
2432
|
+
removeLinkRange,
|
|
2433
|
+
saveLinkRange,
|
|
2434
|
+
slashItemNodeName,
|
|
2532
2435
|
stableJson,
|
|
2533
2436
|
tableNodeToCsv,
|
|
2534
2437
|
tableToCsvAt,
|
|
2535
|
-
tesseraMessages
|
|
2536
|
-
wordDiff
|
|
2438
|
+
tesseraMessages
|
|
2537
2439
|
};
|
|
2538
2440
|
//# sourceMappingURL=index.js.map
|