@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.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 Extension3, wrappingInputRule as wrappingInputRule2, markInputRule } from "@tiptap/core";
683
- var TesseraInputRules = Extension3.create({
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 Extension4 } from "@tiptap/core";
784
+ import { Extension as Extension3 } from "@tiptap/core";
711
785
  import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
712
- var TesseraShortcuts = Extension4.create({
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 Extension5 } from "@tiptap/core";
789
- import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
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 = Extension5.create({
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 Plugin2({
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 Extension6 } from "@tiptap/core";
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 / placeholder
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 / placeholder
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 = tesseraMessages[locale] ?? tesseraMessages["zh-CN"];
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 = Extension6.create({
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 Extension7 } from "@tiptap/core";
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 = Extension7.create({
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 Extension10 } from "@tiptap/core";
1687
+ import { Extension as Extension7 } from "@tiptap/core";
1750
1688
  import { Plugin as Plugin4 } from "@tiptap/pm/state";
1751
- var BlockContextMenu = Extension10.create({
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 Extension11 } from "@tiptap/core";
1789
- import { Plugin as Plugin5, PluginKey as PluginKey6 } from "@tiptap/pm/state";
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 PluginKey6("tesseraGallery");
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 = Extension11.create({
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 Extension12 } from "@tiptap/core";
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 = Extension12.create({
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 Extension13 } from "@tiptap/core";
2090
- import { Plugin as Plugin6, PluginKey as PluginKey7 } from "@tiptap/pm/state";
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 = Extension13.create({
2057
+ var TesseraWordPaste = Extension10.create({
2118
2058
  name: "tesseraWordPaste",
2119
2059
  addProseMirrorPlugins() {
2120
2060
  return [
2121
2061
  new Plugin6({
2122
- key: new PluginKey7("tesseraWordPaste"),
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(blockId2) {
2157
- return `${location.origin}${location.pathname}#block-${blockId2}`;
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
- TaskItem.configure({ nested: true }),
2205
- Hint,
2206
- Collapsible,
2207
- CollapsibleSummary,
2208
- CollapsibleContent,
2209
- ImageBlock,
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
- // src/diff.ts
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
- PlaceholderCommands,
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