@kedataindo/docflow-plugins 0.0.4 → 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/index.js CHANGED
@@ -1,3 +1,18 @@
1
+ import {
2
+ BibliographyNode,
3
+ CSL_LOCALE_EN_US,
4
+ CSL_STYLES,
5
+ CitationEngineExtension,
6
+ CitationNode,
7
+ CiteEngine,
8
+ DEFAULT_CSL_STYLE,
9
+ buildCitationNodes,
10
+ citationPlugin,
11
+ getCitationEngine,
12
+ nextCitationId,
13
+ sanitizeCiteprocHtml
14
+ } from "./chunk-MZZTJIFJ.js";
15
+
1
16
  // src/formatting.ts
2
17
  import { definePlugin } from "@kedataindo/docflow-core";
3
18
  import Underline from "@tiptap/extension-underline";
@@ -94,7 +109,7 @@ import { definePlugin as definePlugin5 } from "@kedataindo/docflow-core";
94
109
  import Link from "@tiptap/extension-link";
95
110
  var linkPlugin = definePlugin5({
96
111
  id: "link",
97
- tiptapExtensions: [Link.configure({ openOnClick: false })],
112
+ tiptapExtensions: [Link.configure({ openOnClick: true })],
98
113
  toolbar: [
99
114
  { id: "set-link", label: "Link", action: "setLink", iconComponent: "Link" }
100
115
  ],
@@ -117,7 +132,26 @@ var linkPlugin = definePlugin5({
117
132
  // src/image.ts
118
133
  import { definePlugin as definePlugin6 } from "@kedataindo/docflow-core";
119
134
  import Image from "@tiptap/extension-image";
120
- var DEFAULT_IMAGE_SRC = "https://via.placeholder.com/300x200";
135
+ function pickImageFile() {
136
+ if (typeof document === "undefined") return Promise.resolve(null);
137
+ return new Promise((resolve) => {
138
+ const input = document.createElement("input");
139
+ input.type = "file";
140
+ input.accept = "image/*";
141
+ const cleanup = () => input.remove();
142
+ input.onchange = () => {
143
+ const file = input.files?.[0] ?? null;
144
+ cleanup();
145
+ resolve(file);
146
+ };
147
+ input.oncancel = () => {
148
+ cleanup();
149
+ resolve(null);
150
+ };
151
+ document.body.appendChild(input);
152
+ input.click();
153
+ });
154
+ }
121
155
  var imagePlugin = definePlugin6({
122
156
  id: "image",
123
157
  tiptapExtensions: [Image],
@@ -126,26 +160,172 @@ var imagePlugin = definePlugin6({
126
160
  commands: {
127
161
  insertImage: (editor, ...args) => {
128
162
  const options = args[0];
129
- const src = options?.src ?? (typeof window !== "undefined" && typeof window.prompt === "function" ? window.prompt("Enter image URL:", "https://") : null) ?? DEFAULT_IMAGE_SRC;
130
- if (!src) return false;
131
- return editor.commands.setImage({ src });
163
+ if (options?.src) {
164
+ return editor.commands.setImage({ src: options.src, alt: options.alt, title: options.title });
165
+ }
166
+ const context = editor.storage.editorContext;
167
+ const upload = context?.onImageUpload;
168
+ if (upload) {
169
+ void pickImageFile().then(async (file) => {
170
+ if (!file) return;
171
+ try {
172
+ const result = await upload(file);
173
+ if (result?.src) {
174
+ editor.commands.setImage({ src: result.src, alt: result.alt, title: result.title });
175
+ }
176
+ } catch (err) {
177
+ console.error("[image] upload failed:", err);
178
+ }
179
+ });
180
+ return true;
181
+ }
182
+ if (typeof window !== "undefined" && typeof window.prompt === "function") {
183
+ const src = window.prompt("Enter image URL:", "https://");
184
+ if (!src) return false;
185
+ return editor.commands.setImage({ src });
186
+ }
187
+ return false;
132
188
  }
133
189
  }
134
190
  });
135
191
 
136
192
  // src/table.ts
137
193
  import { definePlugin as definePlugin7 } from "@kedataindo/docflow-core";
194
+ import { mergeAttributes } from "@tiptap/core";
138
195
  import Table from "@tiptap/extension-table";
139
196
  import TableRow from "@tiptap/extension-table-row";
140
197
  import TableCell from "@tiptap/extension-table-cell";
141
198
  import TableHeader from "@tiptap/extension-table-header";
199
+ var CustomTable = Table.extend({
200
+ renderHTML({ node, HTMLAttributes }) {
201
+ let colCount = 0;
202
+ const firstRow = node.firstChild;
203
+ if (firstRow) {
204
+ firstRow.forEach((cell) => {
205
+ colCount += cell.attrs.colspan || 1;
206
+ });
207
+ }
208
+ const cols = [];
209
+ let totalWidth = 0;
210
+ let hasExplicitWidths = false;
211
+ if (colCount > 0) {
212
+ for (let i = 0; i < colCount; i++) {
213
+ let width = null;
214
+ if (firstRow) {
215
+ let currentColIdx = 0;
216
+ firstRow.forEach((cell) => {
217
+ const colspan = cell.attrs.colspan || 1;
218
+ if (i >= currentColIdx && i < currentColIdx + colspan) {
219
+ const colwidth = cell.attrs.colwidth;
220
+ if (colwidth && Array.isArray(colwidth)) {
221
+ const indexInCell = i - currentColIdx;
222
+ const w = colwidth[indexInCell];
223
+ if (typeof w === "number" && w > 0) {
224
+ width = w;
225
+ }
226
+ }
227
+ }
228
+ currentColIdx += colspan;
229
+ });
230
+ }
231
+ if (width) {
232
+ totalWidth += width;
233
+ hasExplicitWidths = true;
234
+ }
235
+ cols.push([
236
+ "col",
237
+ width ? { style: `width: ${width}px !important`, width: String(width) } : {}
238
+ ]);
239
+ }
240
+ }
241
+ const tableAttrs = mergeAttributes(HTMLAttributes);
242
+ if (hasExplicitWidths && totalWidth > 0) {
243
+ const existingStyle = tableAttrs.style || "";
244
+ tableAttrs.style = existingStyle ? `${existingStyle}; width: ${totalWidth}px !important` : `width: ${totalWidth}px !important`;
245
+ }
246
+ return [
247
+ "table",
248
+ tableAttrs,
249
+ ["colgroup", ...cols],
250
+ ["tbody", 0]
251
+ ];
252
+ }
253
+ });
254
+ var CustomTableCell = TableCell.extend({
255
+ addAttributes() {
256
+ return {
257
+ ...this.parent?.(),
258
+ colwidth: {
259
+ default: null,
260
+ parseHTML: (element) => {
261
+ const width = element.getAttribute("width") || element.style.width;
262
+ if (width) {
263
+ const parsed = parseInt(width, 10);
264
+ return isNaN(parsed) || parsed <= 0 ? null : [parsed];
265
+ }
266
+ const colwidth = element.getAttribute("colwidth");
267
+ return colwidth ? colwidth.split(",").map((w) => parseInt(w, 10)).filter((w) => !isNaN(w) && w > 0) : null;
268
+ },
269
+ renderHTML: (attributes) => {
270
+ if (!attributes.colwidth || !Array.isArray(attributes.colwidth) || attributes.colwidth.length === 0) {
271
+ return {};
272
+ }
273
+ const validWidths = attributes.colwidth.filter((w2) => typeof w2 === "number" && w2 > 0);
274
+ if (validWidths.length === 0) {
275
+ return {};
276
+ }
277
+ const w = validWidths[0];
278
+ return {
279
+ colwidth: validWidths.join(","),
280
+ style: `width: ${w}px !important`,
281
+ width: String(w)
282
+ };
283
+ }
284
+ }
285
+ };
286
+ }
287
+ });
288
+ var CustomTableHeader = TableHeader.extend({
289
+ addAttributes() {
290
+ return {
291
+ ...this.parent?.(),
292
+ colwidth: {
293
+ default: null,
294
+ parseHTML: (element) => {
295
+ const width = element.getAttribute("width") || element.style.width;
296
+ if (width) {
297
+ const parsed = parseInt(width, 10);
298
+ return isNaN(parsed) || parsed <= 0 ? null : [parsed];
299
+ }
300
+ const colwidth = element.getAttribute("colwidth");
301
+ return colwidth ? colwidth.split(",").map((w) => parseInt(w, 10)).filter((w) => !isNaN(w) && w > 0) : null;
302
+ },
303
+ renderHTML: (attributes) => {
304
+ if (!attributes.colwidth || !Array.isArray(attributes.colwidth) || attributes.colwidth.length === 0) {
305
+ return {};
306
+ }
307
+ const validWidths = attributes.colwidth.filter((w2) => typeof w2 === "number" && w2 > 0);
308
+ if (validWidths.length === 0) {
309
+ return {};
310
+ }
311
+ const w = validWidths[0];
312
+ return {
313
+ colwidth: validWidths.join(","),
314
+ style: `width: ${w}px !important`,
315
+ width: String(w)
316
+ };
317
+ }
318
+ }
319
+ };
320
+ }
321
+ });
142
322
  var tablePlugin = definePlugin7({
143
323
  id: "table",
144
324
  tiptapExtensions: [
145
- Table.configure({ resizable: true }),
325
+ CustomTable.configure({ resizable: false }),
146
326
  TableRow,
147
- TableCell,
148
- TableHeader
327
+ CustomTableCell,
328
+ CustomTableHeader
149
329
  ],
150
330
  toolbar: [
151
331
  { id: "insert-table", label: "Insert Table", action: "insertTable", args: [{ rows: 3, cols: 3, withHeaderRow: true }], iconComponent: "Table" }
@@ -185,9 +365,9 @@ function createPlaceholderPlugin(options = {}) {
185
365
  var placeholderPlugin = createPlaceholderPlugin();
186
366
 
187
367
  // src/pageBreak.ts
188
- import { Node, mergeAttributes } from "@tiptap/core";
368
+ import { Node as Node2, mergeAttributes as mergeAttributes2 } from "@tiptap/core";
189
369
  import { definePlugin as definePlugin11 } from "@kedataindo/docflow-core";
190
- var PageBreak = Node.create({
370
+ var PageBreak = Node2.create({
191
371
  name: "pageBreak",
192
372
  group: "block",
193
373
  selectable: false,
@@ -198,7 +378,7 @@ var PageBreak = Node.create({
198
378
  renderHTML({ HTMLAttributes }) {
199
379
  return [
200
380
  "div",
201
- mergeAttributes(HTMLAttributes, {
381
+ mergeAttributes2(HTMLAttributes, {
202
382
  "data-page-break": "true",
203
383
  "data-node-type": "pageBreak",
204
384
  class: "docs-editor-page-break"
@@ -243,9 +423,9 @@ var pageBreakPlugin = definePlugin11({
243
423
  });
244
424
 
245
425
  // src/footnote.ts
246
- import { Node as Node2, mergeAttributes as mergeAttributes2 } from "@tiptap/core";
426
+ import { Node as Node3, mergeAttributes as mergeAttributes3 } from "@tiptap/core";
247
427
  import { definePlugin as definePlugin12 } from "@kedataindo/docflow-core";
248
- var FootnoteNode = Node2.create({
428
+ var FootnoteNode = Node3.create({
249
429
  name: "footnote",
250
430
  group: "inline",
251
431
  inline: true,
@@ -258,6 +438,25 @@ var FootnoteNode = Node2.create({
258
438
  default: "",
259
439
  parseHTML: (el) => el.getAttribute("data-footnote-content") ?? "",
260
440
  renderHTML: (attrs) => ({ "data-footnote-content": attrs.content })
441
+ },
442
+ // Phase 6: when sourceId is set, the footnote body is citeproc-rendered
443
+ // from the source (never typed, never stored as text). The `content`
444
+ // attr stays authoritative for free-text footnotes — existing documents
445
+ // render byte-identically (backward compatible).
446
+ citationId: {
447
+ default: null,
448
+ parseHTML: (el) => el.getAttribute("data-citation-id"),
449
+ renderHTML: (attrs) => attrs.citationId ? { "data-citation-id": attrs.citationId } : {}
450
+ },
451
+ sourceId: {
452
+ default: null,
453
+ parseHTML: (el) => el.getAttribute("data-footnote-source-id"),
454
+ renderHTML: (attrs) => attrs.sourceId ? { "data-footnote-source-id": attrs.sourceId } : {}
455
+ },
456
+ locator: {
457
+ default: "",
458
+ parseHTML: (el) => el.getAttribute("data-footnote-locator") ?? "",
459
+ renderHTML: (attrs) => attrs.locator ? { "data-footnote-locator": attrs.locator } : {}
261
460
  }
262
461
  };
263
462
  },
@@ -265,7 +464,7 @@ var FootnoteNode = Node2.create({
265
464
  return [{ tag: 'span[data-node-type="footnote"]' }];
266
465
  },
267
466
  renderHTML({ HTMLAttributes }) {
268
- return ["span", mergeAttributes2(HTMLAttributes, {
467
+ return ["span", mergeAttributes3(HTMLAttributes, {
269
468
  "data-node-type": "footnote",
270
469
  class: "docs-footnote-ref"
271
470
  }), "1"];
@@ -281,6 +480,11 @@ var FootnoteNode = Node2.create({
281
480
  dom.className = "docs-footnote-ref";
282
481
  dom.setAttribute("data-node-type", "footnote");
283
482
  dom.setAttribute("data-footnote-content", String(attrs.content ?? ""));
483
+ if (attrs.sourceId) {
484
+ dom.setAttribute("data-footnote-source-id", String(attrs.sourceId));
485
+ dom.setAttribute("data-citation-id", String(attrs.citationId ?? ""));
486
+ if (attrs.locator) dom.setAttribute("data-footnote-locator", String(attrs.locator));
487
+ }
284
488
  dom.textContent = "1";
285
489
  return {
286
490
  dom,
@@ -289,6 +493,15 @@ var FootnoteNode = Node2.create({
289
493
  try {
290
494
  const updatedAttrs = updatedNode?.attrs ?? {};
291
495
  dom.setAttribute("data-footnote-content", String(updatedAttrs.content ?? ""));
496
+ if (updatedAttrs.sourceId) {
497
+ dom.setAttribute("data-footnote-source-id", String(updatedAttrs.sourceId));
498
+ dom.setAttribute("data-citation-id", String(updatedAttrs.citationId ?? ""));
499
+ if (updatedAttrs.locator) dom.setAttribute("data-footnote-locator", String(updatedAttrs.locator));
500
+ } else {
501
+ dom.removeAttribute("data-footnote-source-id");
502
+ dom.removeAttribute("data-citation-id");
503
+ dom.removeAttribute("data-footnote-locator");
504
+ }
292
505
  } catch {
293
506
  }
294
507
  return true;
@@ -312,9 +525,249 @@ var footnotePlugin = definePlugin12({
312
525
  }
313
526
  });
314
527
 
528
+ // src/toc.ts
529
+ import { Extension, Node as Node4, mergeAttributes as mergeAttributes4 } from "@tiptap/core";
530
+ import { definePlugin as definePlugin13 } from "@kedataindo/docflow-core";
531
+ function collectHeadingsFromDoc(doc) {
532
+ const headings = [];
533
+ doc.descendants((node, pos) => {
534
+ if (node.type.name === "heading") {
535
+ const level = node.attrs.level;
536
+ if (level === 1 || level === 2 || level === 3) {
537
+ headings.push({ level, text: node.textContent.trim(), pos });
538
+ }
539
+ }
540
+ return true;
541
+ });
542
+ return headings;
543
+ }
544
+ function collectHeadings(editor) {
545
+ return collectHeadingsFromDoc(editor.state.doc);
546
+ }
547
+ function createPageResolver(editor) {
548
+ let breakers = [];
549
+ try {
550
+ const pagination = editor.view.dom.querySelector("[data-rm-pagination]");
551
+ if (pagination) {
552
+ breakers = Array.from(pagination.querySelectorAll(".rm-page-break .breaker")).filter((el) => el instanceof HTMLElement);
553
+ }
554
+ } catch {
555
+ }
556
+ if (breakers.length === 0) return () => null;
557
+ const editorDom = editor.view.dom;
558
+ return (pos) => {
559
+ try {
560
+ const coords = editor.view.coordsAtPos(pos);
561
+ const top = coords.top - editorDom.getBoundingClientRect().top + editorDom.scrollTop;
562
+ let page = 1;
563
+ for (const breaker of breakers) {
564
+ if (top < breaker.offsetTop) return page;
565
+ page++;
566
+ }
567
+ return page;
568
+ } catch {
569
+ return null;
570
+ }
571
+ };
572
+ }
573
+ function buildTocEntryNodes(schema, headings, pageFor) {
574
+ const entryType = schema.nodes.tocEntry;
575
+ if (headings.length === 0) {
576
+ return [entryType.create({ level: 1, pos: 0 })];
577
+ }
578
+ return headings.map((h) => {
579
+ const content = [];
580
+ if (h.text) content.push(schema.text(h.text));
581
+ const page = pageFor(h.pos);
582
+ if (page !== null) content.push(schema.nodes.tocPageNum.create({ page }));
583
+ return entryType.create({ level: h.level, pos: h.pos }, content);
584
+ });
585
+ }
586
+ function buildTocContentJSON(editor) {
587
+ const headings = collectHeadings(editor);
588
+ if (headings.length === 0) {
589
+ return [{ type: "tocEntry", attrs: { level: 1, pos: 0 } }];
590
+ }
591
+ const pageFor = createPageResolver(editor);
592
+ return headings.map((h) => {
593
+ const content = [];
594
+ if (h.text) content.push({ type: "text", text: h.text });
595
+ const page = pageFor(h.pos);
596
+ if (page !== null) content.push({ type: "tocPageNum", attrs: { page } });
597
+ return {
598
+ type: "tocEntry",
599
+ attrs: { level: h.level, pos: h.pos },
600
+ ...content.length > 0 ? { content } : {}
601
+ };
602
+ });
603
+ }
604
+ function applyTocRegeneration(editor, tr) {
605
+ const tocType = editor.schema.nodes.toc;
606
+ if (!tocType) return false;
607
+ const targets = [];
608
+ tr.doc.descendants((node, pos) => {
609
+ if (node.type.name === "toc") {
610
+ targets.push({ pos, nodeSize: node.nodeSize });
611
+ return false;
612
+ }
613
+ return true;
614
+ });
615
+ if (targets.length === 0) return false;
616
+ const entries = buildTocEntryNodes(editor.schema, collectHeadingsFromDoc(tr.doc), createPageResolver(editor));
617
+ for (const target of targets.reverse()) {
618
+ tr.replaceWith(target.pos, target.pos + target.nodeSize, tocType.create(null, entries));
619
+ }
620
+ return true;
621
+ }
622
+ function regenerateToc(editor) {
623
+ const tr = editor.state.tr;
624
+ if (!applyTocRegeneration(editor, tr)) return false;
625
+ editor.view.dispatch(tr);
626
+ return true;
627
+ }
628
+ var TocCommandsExtension = Extension.create({
629
+ name: "tocCommands",
630
+ addCommands() {
631
+ return {
632
+ insertToc: () => ({ editor, chain }) => chain().focus().insertContent({ type: "toc", content: buildTocContentJSON(editor) }).run(),
633
+ refreshToc: () => ({ editor, tr }) => applyTocRegeneration(editor, tr)
634
+ };
635
+ }
636
+ });
637
+ var TocPageNumNode = Node4.create({
638
+ name: "tocPageNum",
639
+ inline: true,
640
+ group: "inline",
641
+ atom: true,
642
+ selectable: false,
643
+ addAttributes() {
644
+ return {
645
+ page: {
646
+ default: null,
647
+ parseHTML: (el) => Number(el.textContent) || null,
648
+ renderHTML: (attrs) => attrs.page ? { "data-page": attrs.page } : {}
649
+ }
650
+ };
651
+ },
652
+ parseHTML() {
653
+ return [{ tag: "span[data-toc-page]" }];
654
+ },
655
+ renderHTML({ node, HTMLAttributes }) {
656
+ return ["span", mergeAttributes4(HTMLAttributes, {
657
+ "data-toc-page": "",
658
+ class: "docs-toc__page"
659
+ }), String(node.attrs.page ?? "")];
660
+ }
661
+ });
662
+ var TocEntryNode = Node4.create({
663
+ name: "tocEntry",
664
+ content: "inline*",
665
+ defining: true,
666
+ addAttributes() {
667
+ return {
668
+ level: {
669
+ default: 1,
670
+ parseHTML: (el) => Number(el.getAttribute("data-level") ?? 1),
671
+ renderHTML: (attrs) => ({ "data-level": attrs.level })
672
+ },
673
+ pos: {
674
+ default: 0,
675
+ parseHTML: (el) => Number(el.getAttribute("data-pos") ?? 0),
676
+ renderHTML: (attrs) => ({ "data-pos": attrs.pos })
677
+ }
678
+ };
679
+ },
680
+ parseHTML() {
681
+ return [{ tag: "p[data-toc-entry]" }];
682
+ },
683
+ renderHTML({ HTMLAttributes }) {
684
+ return ["p", mergeAttributes4(HTMLAttributes, {
685
+ "data-toc-entry": "",
686
+ class: "docs-toc__entry"
687
+ }), 0];
688
+ }
689
+ });
690
+ var TocNode = Node4.create({
691
+ name: "toc",
692
+ group: "block",
693
+ content: "tocEntry+",
694
+ defining: true,
695
+ parseHTML() {
696
+ return [{ tag: "div[data-toc]" }];
697
+ },
698
+ renderHTML({ HTMLAttributes }) {
699
+ return ["div", mergeAttributes4(HTMLAttributes, {
700
+ "data-toc": "",
701
+ class: "docs-toc"
702
+ }), 0];
703
+ },
704
+ // Return type cast to `any` to bypass TipTap's strict NodeViewRenderer typing
705
+ // (same pattern as FootnoteNode / BibliographyNode).
706
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
707
+ addNodeView() {
708
+ return (props) => {
709
+ const editor = props.editor;
710
+ const dom = document.createElement("div");
711
+ dom.className = "docs-toc";
712
+ dom.setAttribute("data-toc", "");
713
+ const chrome = document.createElement("div");
714
+ chrome.className = "docs-toc__chrome";
715
+ chrome.contentEditable = "false";
716
+ const caption = document.createElement("span");
717
+ caption.className = "docs-toc__caption";
718
+ caption.textContent = "Table of contents";
719
+ const refresh = document.createElement("button");
720
+ refresh.type = "button";
721
+ refresh.className = "docs-toc__refresh";
722
+ refresh.title = "Refresh table of contents";
723
+ refresh.textContent = "\u27F3";
724
+ chrome.appendChild(caption);
725
+ chrome.appendChild(refresh);
726
+ const contentDOM = document.createElement("div");
727
+ contentDOM.className = "docs-toc__entries";
728
+ dom.appendChild(chrome);
729
+ dom.appendChild(contentDOM);
730
+ const onRefresh = (event) => {
731
+ event.preventDefault();
732
+ regenerateToc(editor);
733
+ };
734
+ const onEntryClick = (event) => {
735
+ const entry = event.target?.closest?.("[data-toc-entry]");
736
+ if (!entry) return;
737
+ const pos = Number(entry.getAttribute("data-pos") ?? 0);
738
+ const clamped = Math.min(Math.max(0, pos), editor.state.doc.content.size);
739
+ editor.chain().focus().setTextSelection(clamped).scrollIntoView().run();
740
+ };
741
+ refresh.addEventListener("click", onRefresh);
742
+ dom.addEventListener("click", onEntryClick);
743
+ return {
744
+ dom,
745
+ contentDOM,
746
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
747
+ update(updatedNode) {
748
+ return updatedNode?.type?.name === "toc";
749
+ },
750
+ destroy() {
751
+ refresh.removeEventListener("click", onRefresh);
752
+ dom.removeEventListener("click", onEntryClick);
753
+ }
754
+ };
755
+ };
756
+ }
757
+ });
758
+ var tocPlugin = definePlugin13({
759
+ id: "toc",
760
+ tiptapExtensions: [TocPageNumNode, TocEntryNode, TocNode, TocCommandsExtension],
761
+ slashCommands: [{ name: "Table of contents", description: "Daftar isi", command: "insertToc" }],
762
+ commands: {
763
+ insertToc: (editor) => editor.commands.insertToc(),
764
+ refreshToc: (editor) => regenerateToc(editor)
765
+ }
766
+ });
767
+
315
768
  // src/fontSize.ts
316
- import { definePlugin as definePlugin13, FontSizeExtension } from "@kedataindo/docflow-core";
317
- var fontSizePlugin = definePlugin13({
769
+ import { definePlugin as definePlugin14, FontSizeExtension } from "@kedataindo/docflow-core";
770
+ var fontSizePlugin = definePlugin14({
318
771
  id: "font-size",
319
772
  tiptapExtensions: [FontSizeExtension],
320
773
  commands: {
@@ -331,6 +784,625 @@ var fontSizePlugin = definePlugin13({
331
784
  }
332
785
  });
333
786
 
787
+ // src/textColor.ts
788
+ import { definePlugin as definePlugin15 } from "@kedataindo/docflow-core";
789
+ import Color from "@tiptap/extension-color";
790
+ var textColorPlugin = definePlugin15({
791
+ id: "text-color",
792
+ tiptapExtensions: [Color],
793
+ toolbar: [
794
+ { id: "text-color", label: "Text Color", action: "setTextColor", iconComponent: "Palette" }
795
+ ],
796
+ commands: {
797
+ setTextColor: (editor, ...args) => {
798
+ const color = args[0];
799
+ if (!color) return false;
800
+ if (color === "default") {
801
+ return editor.chain().focus().unsetColor().run();
802
+ }
803
+ return editor.chain().focus().setColor(color).run();
804
+ }
805
+ }
806
+ });
807
+
808
+ // src/highlight.ts
809
+ import { definePlugin as definePlugin16 } from "@kedataindo/docflow-core";
810
+ import Highlight from "@tiptap/extension-highlight";
811
+ import { mergeAttributes as mergeAttributes5 } from "@tiptap/core";
812
+ var GoogleDocsHighlight = Highlight.extend({
813
+ parseHTML() {
814
+ return [
815
+ // Standard <mark> tag (default TipTap behavior)
816
+ { tag: "mark" },
817
+ // Google Docs pastes background-color as inline span style
818
+ { tag: "span", style: "background-color" }
819
+ ];
820
+ },
821
+ renderHTML({ HTMLAttributes }) {
822
+ return ["mark", mergeAttributes5(this.options.HTMLAttributes, HTMLAttributes), 0];
823
+ }
824
+ });
825
+ var highlightPlugin = definePlugin16({
826
+ id: "highlight",
827
+ tiptapExtensions: [
828
+ GoogleDocsHighlight.configure({
829
+ multicolor: true
830
+ })
831
+ ],
832
+ toolbar: [
833
+ { id: "highlight", label: "Highlight", action: "setHighlight", iconComponent: "Highlighter" }
834
+ ],
835
+ commands: {
836
+ setHighlight: (editor, ...args) => {
837
+ const color = args[0];
838
+ if (!color) {
839
+ return editor.chain().focus().toggleHighlight().run();
840
+ }
841
+ if (color === "default") {
842
+ return editor.chain().focus().unsetHighlight().run();
843
+ }
844
+ return editor.chain().focus().toggleHighlight({ color }).run();
845
+ }
846
+ }
847
+ });
848
+
849
+ // src/ai.ts
850
+ import { Extension as Extension2 } from "@tiptap/core";
851
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
852
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
853
+ import { definePlugin as definePlugin17 } from "@kedataindo/docflow-core";
854
+ var aiPluginKey = new PluginKey("docflow-ai");
855
+ var CONTEXT_CHARS = 1500;
856
+ function getAIPreview(editor) {
857
+ return editor.storage.ai ? editor.storage.ai.preview ?? null : null;
858
+ }
859
+ function getAIStreamPort(editor) {
860
+ return editor.storage.editorContext?.aiStream;
861
+ }
862
+ function boundedContext(state, from, to) {
863
+ return {
864
+ before: state.doc.textBetween(Math.max(0, from - CONTEXT_CHARS), from, "\n", " "),
865
+ after: state.doc.textBetween(to, Math.min(state.doc.content.size, to + CONTEXT_CHARS), "\n", " ")
866
+ };
867
+ }
868
+ function runAIStream(editor, req, abort) {
869
+ const aiStream = getAIStreamPort(editor);
870
+ if (!aiStream) return;
871
+ void (async () => {
872
+ const push = (meta) => {
873
+ if (abort.signal.aborted || editor.isDestroyed) return;
874
+ editor.view.dispatch(editor.state.tr.setMeta(aiPluginKey, meta));
875
+ };
876
+ try {
877
+ for await (const chunk of aiStream(req, abort.signal)) {
878
+ if (abort.signal.aborted || editor.isDestroyed) return;
879
+ push({ type: "chunk", text: chunk });
880
+ }
881
+ push({ type: "done" });
882
+ } catch (err) {
883
+ push({
884
+ type: "error",
885
+ message: err instanceof Error ? err.message : "AI request failed"
886
+ });
887
+ }
888
+ })();
889
+ }
890
+ function stopStream(editor) {
891
+ const storage = editor.storage.ai;
892
+ storage.abort?.abort();
893
+ storage.abort = null;
894
+ }
895
+ var AIExtension = Extension2.create({
896
+ name: "ai",
897
+ addStorage() {
898
+ return {
899
+ preview: null,
900
+ abort: null
901
+ };
902
+ },
903
+ addCommands() {
904
+ return {
905
+ aiTransform: (options) => ({ editor, state, dispatch }) => {
906
+ if (aiPluginKey.getState(state)) return false;
907
+ if (!getAIStreamPort(editor)) {
908
+ console.warn("[ai] no aiStream port injected \u2014 AI actions are inert");
909
+ return false;
910
+ }
911
+ const { from, to, empty } = state.selection;
912
+ if (empty) return false;
913
+ const originalText = state.doc.textBetween(from, to, "\n", " ");
914
+ if (!originalText.trim()) return false;
915
+ const abort = new AbortController();
916
+ editor.storage.ai.abort = abort;
917
+ dispatch?.(
918
+ state.tr.setMeta(aiPluginKey, {
919
+ type: "start",
920
+ mode: "transform",
921
+ status: "streaming",
922
+ from,
923
+ to,
924
+ originalText,
925
+ action: options.action
926
+ })
927
+ );
928
+ const req = {
929
+ action: options.action,
930
+ selection: originalText,
931
+ context: boundedContext(state, from, to),
932
+ ...options.prompt ? { prompt: options.prompt } : {}
933
+ };
934
+ runAIStream(editor, req, abort);
935
+ return true;
936
+ },
937
+ aiGenerate: () => ({ editor, state, dispatch }) => {
938
+ if (aiPluginKey.getState(state)) return false;
939
+ if (!getAIStreamPort(editor)) {
940
+ console.warn("[ai] no aiStream port injected \u2014 AI actions are inert");
941
+ return false;
942
+ }
943
+ const pos = state.selection.to;
944
+ dispatch?.(
945
+ state.tr.setMeta(aiPluginKey, {
946
+ type: "start",
947
+ mode: "generate",
948
+ status: "prompt",
949
+ from: pos,
950
+ to: pos,
951
+ originalText: "",
952
+ action: "generate"
953
+ })
954
+ );
955
+ return true;
956
+ },
957
+ aiPromptSubmit: (options) => ({ editor, state, dispatch }) => {
958
+ const preview = aiPluginKey.getState(state);
959
+ if (!preview || preview.mode !== "generate" || preview.status !== "prompt") return false;
960
+ const prompt = options.prompt?.trim();
961
+ if (!prompt) return false;
962
+ const abort = new AbortController();
963
+ editor.storage.ai.abort = abort;
964
+ dispatch?.(
965
+ state.tr.setMeta(aiPluginKey, {
966
+ type: "start",
967
+ mode: "generate",
968
+ status: "streaming",
969
+ from: preview.from,
970
+ to: preview.to,
971
+ originalText: "",
972
+ action: "generate"
973
+ })
974
+ );
975
+ const req = {
976
+ action: "generate",
977
+ prompt,
978
+ context: boundedContext(state, preview.from, preview.to)
979
+ };
980
+ runAIStream(editor, req, abort);
981
+ editor.commands.focus();
982
+ return true;
983
+ },
984
+ aiAccept: () => ({ editor, state, dispatch }) => {
985
+ const preview = aiPluginKey.getState(state);
986
+ if (!preview || preview.status === "prompt") return false;
987
+ stopStream(editor);
988
+ if (!dispatch) return true;
989
+ const tr = state.tr;
990
+ if (preview.text.trim()) {
991
+ tr.replaceWith(preview.from, preview.to, state.schema.text(preview.text));
992
+ }
993
+ tr.setMeta(aiPluginKey, { type: "clear" });
994
+ dispatch(tr);
995
+ return true;
996
+ },
997
+ aiReject: () => ({ editor, state, dispatch }) => {
998
+ const preview = aiPluginKey.getState(state);
999
+ if (!preview) return false;
1000
+ stopStream(editor);
1001
+ dispatch?.(state.tr.setMeta(aiPluginKey, { type: "clear" }));
1002
+ return true;
1003
+ }
1004
+ };
1005
+ },
1006
+ addProseMirrorPlugins() {
1007
+ const editor = this.editor;
1008
+ let promptWidget = null;
1009
+ function getPromptWidget() {
1010
+ if (promptWidget) return promptWidget;
1011
+ const wrap = document.createElement("span");
1012
+ wrap.className = "docs-ai-prompt";
1013
+ const input = document.createElement("input");
1014
+ input.type = "text";
1015
+ input.className = "docs-ai-prompt__input";
1016
+ input.placeholder = "Ask AI to write\u2026 (Enter \u23CE \xB7 Esc cancel)";
1017
+ input.addEventListener("keydown", (event) => {
1018
+ if (event.key === "Enter") {
1019
+ event.preventDefault();
1020
+ editor.commands.aiPromptSubmit({ prompt: input.value });
1021
+ } else if (event.key === "Escape") {
1022
+ event.preventDefault();
1023
+ editor.commands.aiReject();
1024
+ }
1025
+ });
1026
+ input.addEventListener("blur", () => {
1027
+ const preview = aiPluginKey.getState(editor.state);
1028
+ if (preview?.status === "prompt") editor.commands.aiReject();
1029
+ });
1030
+ wrap.appendChild(input);
1031
+ setTimeout(() => input.focus(), 0);
1032
+ promptWidget = { dom: wrap, stopEvent: (e) => e.target instanceof Node && wrap.contains(e.target) };
1033
+ return promptWidget;
1034
+ }
1035
+ function buildGhostWidget(preview) {
1036
+ const ghost = document.createElement("span");
1037
+ let actions = null;
1038
+ if (preview.status === "error") {
1039
+ ghost.className = "docs-ai-ghost docs-ai-ghost--error";
1040
+ ghost.textContent = ` ${preview.error ?? "AI error"} \u2014 press Esc`;
1041
+ } else {
1042
+ ghost.className = "docs-ai-ghost";
1043
+ const textSpan = document.createElement("span");
1044
+ if (!preview.text && preview.status === "streaming") {
1045
+ textSpan.textContent = "AI writing\u2026";
1046
+ textSpan.className = "docs-ai-ghost-placeholder";
1047
+ } else {
1048
+ textSpan.textContent = preview.text + (preview.status === "streaming" ? "\u258C" : "");
1049
+ }
1050
+ ghost.appendChild(textSpan);
1051
+ if (preview.status === "done") {
1052
+ actions = document.createElement("span");
1053
+ actions.className = "docs-ai-ghost-actions";
1054
+ const acceptBtn = document.createElement("button");
1055
+ acceptBtn.type = "button";
1056
+ acceptBtn.className = "docs-ai-ghost-btn docs-ai-ghost-btn--accept";
1057
+ acceptBtn.textContent = "\u2713 Accept";
1058
+ acceptBtn.title = "Accept (Enter)";
1059
+ acceptBtn.addEventListener("mousedown", (e) => {
1060
+ e.preventDefault();
1061
+ editor.commands.aiAccept();
1062
+ });
1063
+ const rejectBtn = document.createElement("button");
1064
+ rejectBtn.type = "button";
1065
+ rejectBtn.className = "docs-ai-ghost-btn docs-ai-ghost-btn--reject";
1066
+ rejectBtn.textContent = "\u2717 Reject";
1067
+ rejectBtn.title = "Reject (Esc)";
1068
+ rejectBtn.addEventListener("mousedown", (e) => {
1069
+ e.preventDefault();
1070
+ editor.commands.aiReject();
1071
+ });
1072
+ actions.appendChild(acceptBtn);
1073
+ actions.appendChild(rejectBtn);
1074
+ ghost.appendChild(actions);
1075
+ }
1076
+ }
1077
+ return {
1078
+ dom: ghost,
1079
+ // Keep button clicks away from ProseMirror (it would move the
1080
+ // selection and swallow the mousedown before our handler).
1081
+ stopEvent: (e) => actions !== null && e.target instanceof Node && actions.contains(e.target)
1082
+ };
1083
+ }
1084
+ return [
1085
+ new Plugin({
1086
+ key: aiPluginKey,
1087
+ state: {
1088
+ init: () => null,
1089
+ apply(tr, prev) {
1090
+ const meta = tr.getMeta(aiPluginKey);
1091
+ if (meta) {
1092
+ switch (meta.type) {
1093
+ case "start":
1094
+ return {
1095
+ mode: meta.mode,
1096
+ status: meta.status,
1097
+ from: meta.from,
1098
+ to: meta.to,
1099
+ originalText: meta.originalText,
1100
+ text: "",
1101
+ action: meta.action
1102
+ };
1103
+ case "chunk":
1104
+ return prev ? { ...prev, text: prev.text + meta.text } : prev;
1105
+ case "done":
1106
+ return prev ? { ...prev, status: "done" } : prev;
1107
+ case "error":
1108
+ return prev ? { ...prev, status: "error", error: meta.message } : prev;
1109
+ case "clear":
1110
+ return null;
1111
+ }
1112
+ }
1113
+ if (!prev) return prev;
1114
+ if (prev.status === "prompt" && tr.selectionSet) return null;
1115
+ if (tr.docChanged) {
1116
+ if (prev.mode === "generate") {
1117
+ const pos = tr.mapping.map(prev.from, 1);
1118
+ return { ...prev, from: pos, to: pos };
1119
+ }
1120
+ const from = tr.mapping.map(prev.from, -1);
1121
+ const to = tr.mapping.map(prev.to, 1);
1122
+ if (to <= from || tr.doc.textBetween(from, to, "\n", " ") !== prev.originalText) {
1123
+ return null;
1124
+ }
1125
+ return { ...prev, from, to };
1126
+ }
1127
+ return prev;
1128
+ }
1129
+ },
1130
+ props: {
1131
+ decorations(state) {
1132
+ const preview = aiPluginKey.getState(state);
1133
+ if (!preview) {
1134
+ promptWidget = null;
1135
+ return DecorationSet.empty;
1136
+ }
1137
+ const decorations = [];
1138
+ if (preview.mode === "transform" && preview.to > preview.from) {
1139
+ decorations.push(
1140
+ Decoration.inline(preview.from, preview.to, { class: "docs-ai-selection" })
1141
+ );
1142
+ }
1143
+ if (preview.status === "prompt") {
1144
+ const widget = getPromptWidget();
1145
+ decorations.push(
1146
+ Decoration.widget(preview.to, widget.dom, {
1147
+ key: "docs-ai-prompt",
1148
+ side: 1,
1149
+ stopEvent: widget.stopEvent
1150
+ })
1151
+ );
1152
+ } else {
1153
+ promptWidget = null;
1154
+ const ghost = buildGhostWidget(preview);
1155
+ decorations.push(
1156
+ Decoration.widget(preview.to, ghost.dom, {
1157
+ // The key MUST track the rendered content: prosemirror-view's
1158
+ // WidgetType.eq treats same-key widgets as identical and skips
1159
+ // the redraw — a constant key freezes the ghost at its first
1160
+ // paint (the "AI writing…" bug).
1161
+ key: `docs-ai-ghost-${preview.status}-${preview.text.length}-${(preview.error ?? "").length}`,
1162
+ side: 1,
1163
+ stopEvent: ghost.stopEvent
1164
+ })
1165
+ );
1166
+ }
1167
+ return DecorationSet.create(state.doc, decorations);
1168
+ },
1169
+ handleKeyDown(_view, event) {
1170
+ const preview = aiPluginKey.getState(editor.state);
1171
+ if (!preview) return false;
1172
+ if (event.key === "Escape") {
1173
+ editor.commands.aiReject();
1174
+ return true;
1175
+ }
1176
+ if (preview.status === "prompt") return false;
1177
+ if ((event.key === "Enter" || event.key === "Tab") && preview.status !== "error") {
1178
+ event.preventDefault();
1179
+ editor.commands.aiAccept();
1180
+ return true;
1181
+ }
1182
+ return false;
1183
+ }
1184
+ }
1185
+ })
1186
+ ];
1187
+ },
1188
+ // Mirror the PM plugin state into extension storage so Vue components
1189
+ // (BubbleMenu) can read it without importing the plugin key.
1190
+ onTransaction() {
1191
+ const storage = this.editor.storage.ai;
1192
+ storage.preview = aiPluginKey.getState(this.editor.state) ?? null;
1193
+ },
1194
+ onDestroy() {
1195
+ const storage = this.editor.storage.ai;
1196
+ storage.abort?.abort();
1197
+ storage.abort = null;
1198
+ storage.preview = null;
1199
+ }
1200
+ });
1201
+ var aiPlugin = definePlugin17({
1202
+ id: "ai",
1203
+ tiptapExtensions: [AIExtension],
1204
+ slashCommands: [{ name: "AI", description: "Generate text with AI", command: "aiGenerate" }]
1205
+ });
1206
+
1207
+ // src/comment.ts
1208
+ import { Mark } from "@tiptap/core";
1209
+ var CommentMark = Mark.create({
1210
+ name: "comment",
1211
+ // Comments are inclusive (typing inside an existing comment extends
1212
+ // the mark range — that's the typical inline-comment UX). Excluding
1213
+ // would split the mark on every keystroke.
1214
+ inclusive: true,
1215
+ addAttributes() {
1216
+ return {
1217
+ threadId: {
1218
+ default: null,
1219
+ parseHTML: (el) => el.getAttribute("data-comment-thread"),
1220
+ renderHTML: (attrs) => {
1221
+ if (!attrs.threadId) return {};
1222
+ return { "data-comment-thread": attrs.threadId, class: "docs-comment" };
1223
+ }
1224
+ },
1225
+ pos: {
1226
+ default: null,
1227
+ // No HTML render — position is server-side state.
1228
+ renderHTML: () => ({})
1229
+ }
1230
+ };
1231
+ },
1232
+ parseHTML() {
1233
+ return [{ tag: "span[data-comment-thread]" }];
1234
+ },
1235
+ renderHTML({ HTMLAttributes }) {
1236
+ return ["span", HTMLAttributes, 0];
1237
+ }
1238
+ });
1239
+ var commentPlugin = {
1240
+ id: "comment",
1241
+ tiptapExtensions: [CommentMark],
1242
+ toolbar: [],
1243
+ slashCommands: [],
1244
+ commands: {}
1245
+ };
1246
+
1247
+ // src/slashMenu.ts
1248
+ import { Extension as Extension3 } from "@tiptap/core";
1249
+ import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
1250
+ import { definePlugin as definePlugin18 } from "@kedataindo/docflow-core";
1251
+ var slashState = {
1252
+ open: false,
1253
+ query: "",
1254
+ position: { top: 0, left: 0 },
1255
+ commands: [],
1256
+ selectedIndex: 0
1257
+ };
1258
+ var listeners = [];
1259
+ function onSlashStateChange(fn) {
1260
+ listeners.push(fn);
1261
+ return () => {
1262
+ listeners = listeners.filter((l) => l !== fn);
1263
+ };
1264
+ }
1265
+ function notifyListeners() {
1266
+ listeners.forEach((fn) => fn());
1267
+ }
1268
+ function closeSlashMenu() {
1269
+ slashState = { ...slashState, open: false, query: "" };
1270
+ notifyListeners();
1271
+ }
1272
+ function openSlashMenu(view, query) {
1273
+ const { selection } = view.state;
1274
+ const pos = selection.head;
1275
+ const coords = view.coordsAtPos(pos);
1276
+ const allCommands = getRegisteredCommands();
1277
+ const filtered = query ? allCommands.filter((c) => c.name.toLowerCase().includes(query.toLowerCase())) : allCommands;
1278
+ slashState = {
1279
+ open: true,
1280
+ query,
1281
+ position: { top: coords.bottom + 4, left: coords.left },
1282
+ commands: filtered.map((c) => ({ ...c, action: null })),
1283
+ selectedIndex: 0
1284
+ };
1285
+ notifyListeners();
1286
+ }
1287
+ var registeredCommands = /* @__PURE__ */ new Map();
1288
+ function registerSlashCommands(editorId, commands) {
1289
+ const existing = registeredCommands.get(editorId) || [];
1290
+ registeredCommands.set(editorId, [...existing, ...commands]);
1291
+ }
1292
+ function getRegisteredCommands() {
1293
+ const all = [];
1294
+ registeredCommands.forEach((cmds) => {
1295
+ all.push(...cmds);
1296
+ });
1297
+ const seen = /* @__PURE__ */ new Set();
1298
+ return all.filter((c) => {
1299
+ if (seen.has(c.name)) return false;
1300
+ seen.add(c.name);
1301
+ return true;
1302
+ });
1303
+ }
1304
+ var SlashMenuExtension = Extension3.create({
1305
+ name: "slashMenu",
1306
+ addProseMirrorPlugins() {
1307
+ return [
1308
+ new Plugin2({
1309
+ key: new PluginKey2("slashMenu"),
1310
+ props: {
1311
+ handleTextInput(view, from, _to, text) {
1312
+ if (text === "/") {
1313
+ const $pos = view.state.doc.resolve(from);
1314
+ const textBefore = $pos.parent.textContent.slice(0, $pos.parentOffset);
1315
+ if (textBefore === "" || textBefore.endsWith(" ")) {
1316
+ setTimeout(() => openSlashMenu(view, ""), 10);
1317
+ return false;
1318
+ }
1319
+ }
1320
+ if (slashState.open && text.length === 1 && /[a-zA-Z]/.test(text)) {
1321
+ const newQuery = slashState.query + text;
1322
+ openSlashMenu(view, newQuery);
1323
+ return false;
1324
+ }
1325
+ return false;
1326
+ },
1327
+ handleKeyDown(view, event) {
1328
+ if (!slashState.open) return false;
1329
+ if (event.key === "ArrowDown") {
1330
+ event.preventDefault();
1331
+ slashState.selectedIndex = Math.min(slashState.selectedIndex + 1, slashState.commands.length - 1);
1332
+ notifyListeners();
1333
+ return true;
1334
+ }
1335
+ if (event.key === "ArrowUp") {
1336
+ event.preventDefault();
1337
+ slashState.selectedIndex = Math.max(slashState.selectedIndex - 1, 0);
1338
+ notifyListeners();
1339
+ return true;
1340
+ }
1341
+ if (event.key === "Enter") {
1342
+ event.preventDefault();
1343
+ const cmd = slashState.commands[slashState.selectedIndex];
1344
+ if (cmd) {
1345
+ const $pos = view.state.doc.resolve(view.state.selection.head);
1346
+ const lineStart = $pos.start();
1347
+ const slashPos = lineStart + $pos.parent.textContent.lastIndexOf("/");
1348
+ if (slashPos >= lineStart) {
1349
+ const tr = view.state.tr.delete(slashPos, view.state.selection.head);
1350
+ view.dispatch(tr);
1351
+ }
1352
+ const editor = view._tiptapEditor;
1353
+ if (editor) {
1354
+ const actionMap = editor;
1355
+ if (typeof actionMap[cmd.command] === "function") {
1356
+ actionMap[cmd.command]();
1357
+ }
1358
+ }
1359
+ }
1360
+ closeSlashMenu();
1361
+ return true;
1362
+ }
1363
+ if (event.key === "Escape") {
1364
+ closeSlashMenu();
1365
+ return true;
1366
+ }
1367
+ if (event.key === "Backspace") {
1368
+ if (slashState.query.length > 0) {
1369
+ const newQuery = slashState.query.slice(0, -1);
1370
+ if (newQuery) {
1371
+ openSlashMenu(view, newQuery);
1372
+ } else {
1373
+ openSlashMenu(view, "");
1374
+ }
1375
+ return false;
1376
+ } else {
1377
+ const $pos = view.state.doc.resolve(view.state.selection.head);
1378
+ const lineStart = $pos.start();
1379
+ const slashPos = $pos.parent.textContent.lastIndexOf("/");
1380
+ if (slashPos >= 0) {
1381
+ const tr = view.state.tr.delete(lineStart + slashPos, view.state.selection.head);
1382
+ view.dispatch(tr);
1383
+ }
1384
+ closeSlashMenu();
1385
+ return true;
1386
+ }
1387
+ }
1388
+ return false;
1389
+ }
1390
+ }
1391
+ })
1392
+ ];
1393
+ }
1394
+ });
1395
+ var slashMenuPlugin = definePlugin18({
1396
+ id: "slash-menu",
1397
+ tiptapExtensions: [SlashMenuExtension],
1398
+ hooks: {
1399
+ onInit(_editor) {
1400
+ const commands = [];
1401
+ registerSlashCommands("default", commands);
1402
+ }
1403
+ }
1404
+ });
1405
+
334
1406
  // src/index.ts
335
1407
  var defaultPlugins = [
336
1408
  formattingPlugin,
@@ -345,25 +1417,62 @@ var defaultPlugins = [
345
1417
  placeholderPlugin,
346
1418
  pageBreakPlugin,
347
1419
  footnotePlugin,
348
- fontSizePlugin
1420
+ tocPlugin,
1421
+ fontSizePlugin,
1422
+ textColorPlugin,
1423
+ highlightPlugin,
1424
+ citationPlugin,
1425
+ aiPlugin,
1426
+ commentPlugin
349
1427
  ];
350
1428
  export {
1429
+ AIExtension,
1430
+ BibliographyNode,
1431
+ CSL_LOCALE_EN_US,
1432
+ CSL_STYLES,
1433
+ CitationEngineExtension,
1434
+ CitationNode,
1435
+ CiteEngine,
1436
+ CommentMark as CommentMarkExtension,
1437
+ DEFAULT_CSL_STYLE,
351
1438
  FontSizeExtension,
352
1439
  FootnoteNode,
353
1440
  PageBreak,
1441
+ SlashMenuExtension,
1442
+ TocEntryNode,
1443
+ TocNode,
1444
+ TocPageNumNode,
1445
+ aiPlugin,
1446
+ aiPluginKey,
354
1447
  alignmentPlugin,
355
1448
  blockquotePlugin,
1449
+ buildCitationNodes,
1450
+ citationPlugin,
356
1451
  codeBlockPlugin,
1452
+ collectHeadings,
1453
+ commentPlugin,
357
1454
  createPlaceholderPlugin,
358
1455
  defaultPlugins,
359
1456
  fontSizePlugin,
360
1457
  footnotePlugin,
361
1458
  formattingPlugin,
1459
+ getAIPreview,
1460
+ getCitationEngine,
362
1461
  headingsPlugin,
1462
+ highlightPlugin,
363
1463
  imagePlugin,
364
1464
  linkPlugin,
365
1465
  listsPlugin,
1466
+ nextCitationId,
1467
+ onSlashStateChange,
366
1468
  pageBreakPlugin,
367
1469
  placeholderPlugin,
368
- tablePlugin
1470
+ regenerateToc,
1471
+ registerSlashCommands,
1472
+ sanitizeCiteprocHtml,
1473
+ slashMenuPlugin,
1474
+ slashState,
1475
+ tablePlugin,
1476
+ textColorPlugin,
1477
+ tocPlugin
369
1478
  };