@estiva-app/ui 0.69.2 → 0.71.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/BlockNodes.d.ts +2 -0
- package/dist/BlockNodes.d.ts.map +1 -1
- package/dist/ComposerTriggers.d.ts +8 -0
- package/dist/ComposerTriggers.d.ts.map +1 -1
- package/dist/EditorBlockHandle.d.ts +53 -4
- package/dist/EditorBlockHandle.d.ts.map +1 -1
- package/dist/LinkChips.d.ts +2 -0
- package/dist/LinkChips.d.ts.map +1 -1
- package/dist/PastedMarkdown.d.ts +24 -0
- package/dist/PastedMarkdown.d.ts.map +1 -0
- package/dist/editor.d.ts +2 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +554 -403
- package/dist/editor.js.map +4 -4
- package/package.json +1 -1
- package/registry.json +81 -5
- package/src/BlockHandle.test.tsx +1 -1
- package/src/BlockNodes.tsx +2 -0
- package/src/ComposerTriggers.tsx +3 -1
- package/src/EditorBlockHandle.mdx +14 -9
- package/src/EditorBlockHandle.stories.tsx +5 -11
- package/src/EditorBlockHandle.test.tsx +200 -0
- package/src/EditorBlockHandle.tsx +181 -10
- package/src/LinkChips.ts +2 -0
- package/src/PastedMarkdown.mdx +59 -0
- package/src/PastedMarkdown.stories.tsx +62 -0
- package/src/PastedMarkdown.test.ts +134 -0
- package/src/PastedMarkdown.ts +68 -0
- package/src/editor.ts +16 -1
- package/src/pages.test.ts +1 -0
- package/src/registry/registry.test.ts +6 -6
- package/stories/Choosing.mdx +1 -0
package/dist/editor.js
CHANGED
|
@@ -339,6 +339,47 @@ var PastedImages = Extension2.create({
|
|
|
339
339
|
}
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
+
// src/PastedMarkdown.ts
|
|
343
|
+
import { Extension as Extension3 } from "@tiptap/core";
|
|
344
|
+
import { Slice } from "@tiptap/pm/model";
|
|
345
|
+
import { Plugin as Plugin3, PluginKey as PluginKey3 } from "@tiptap/pm/state";
|
|
346
|
+
function inTableCell($context) {
|
|
347
|
+
for (let depth = $context.depth; depth > 0; depth--) {
|
|
348
|
+
const role = $context.node(depth).type.spec.tableRole;
|
|
349
|
+
if (role === "cell" || role === "header_cell") return true;
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
var hasTable = (blocks) => blocks.some((block) => block.type === "table" || hasTable(block.content ?? []));
|
|
354
|
+
var PastedMarkdown = Extension3.create({
|
|
355
|
+
name: "pastedMarkdown",
|
|
356
|
+
addOptions() {
|
|
357
|
+
return { read: () => void 0 };
|
|
358
|
+
},
|
|
359
|
+
addProseMirrorPlugins() {
|
|
360
|
+
const options = this.options;
|
|
361
|
+
return [
|
|
362
|
+
new Plugin3({
|
|
363
|
+
key: new PluginKey3("pastedMarkdown"),
|
|
364
|
+
props: {
|
|
365
|
+
// Reached only for a text paste outside code; `plain` is the Shift paste.
|
|
366
|
+
clipboardTextParser: (text, $context, plain, view) => {
|
|
367
|
+
if (plain) return void 0;
|
|
368
|
+
try {
|
|
369
|
+
const blocks = options.read(text.replace(/\r\n?/g, "\n"))?.content ?? [];
|
|
370
|
+
if (blocks.every((block) => block.type === "paragraph")) return void 0;
|
|
371
|
+
if (inTableCell($context) && hasTable(blocks)) return void 0;
|
|
372
|
+
return Slice.maxOpen(view.state.schema.nodeFromJSON({ type: "doc", content: blocks }).content);
|
|
373
|
+
} catch {
|
|
374
|
+
return void 0;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
];
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
342
383
|
// src/CodeBlockColours.ts
|
|
343
384
|
import { CodeBlockLowlight } from "@tiptap/extension-code-block-lowlight";
|
|
344
385
|
|
|
@@ -475,271 +516,19 @@ function suggestionPopup(options) {
|
|
|
475
516
|
import { useMemo } from "react";
|
|
476
517
|
import { IconCopy, IconTrash } from "@tabler/icons-react";
|
|
477
518
|
import { BlockHandle, blockAtY } from "@estiva-app/ui";
|
|
519
|
+
import { Selection } from "@tiptap/pm/state";
|
|
478
520
|
import { MenuItem, MenuSub } from "@estiva-app/ui";
|
|
479
|
-
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
480
|
-
function editorBlocks(view) {
|
|
481
|
-
const out = [];
|
|
482
|
-
view.state.doc.forEach((node, pos) => {
|
|
483
|
-
const element = view.nodeDOM(pos);
|
|
484
|
-
if (element instanceof HTMLElement) out.push({ id: node.attrs.blockId || void 0, element, pos });
|
|
485
|
-
});
|
|
486
|
-
return out;
|
|
487
|
-
}
|
|
488
|
-
function duplicateBlock(editor, pos) {
|
|
489
|
-
const node = editor.state.doc.nodeAt(pos);
|
|
490
|
-
if (!node) return false;
|
|
491
|
-
editor.view.dispatch(editor.state.tr.insert(pos + node.nodeSize, node).scrollIntoView());
|
|
492
|
-
editor.commands.focus(pos + node.nodeSize + 1);
|
|
493
|
-
return true;
|
|
494
|
-
}
|
|
495
|
-
function deleteBlock(editor, pos) {
|
|
496
|
-
const { state } = editor;
|
|
497
|
-
const node = state.doc.nodeAt(pos);
|
|
498
|
-
if (!node) return false;
|
|
499
|
-
const tr = state.doc.childCount === 1 ? state.tr.replaceWith(pos, pos + node.nodeSize, state.schema.nodes.paragraph.create()) : state.tr.delete(pos, pos + node.nodeSize);
|
|
500
|
-
editor.view.dispatch(tr);
|
|
501
|
-
editor.commands.focus(Math.min(pos + 1, editor.state.doc.content.size));
|
|
502
|
-
return true;
|
|
503
|
-
}
|
|
504
|
-
function moveBlock(editor, from, to) {
|
|
505
|
-
const node = editor.state.doc.nodeAt(from);
|
|
506
|
-
if (!node || to >= from && to <= from + node.nodeSize) return false;
|
|
507
|
-
const tr = editor.state.tr.delete(from, from + node.nodeSize);
|
|
508
|
-
const at = tr.mapping.map(to);
|
|
509
|
-
editor.view.dispatch(tr.insert(at, node).scrollIntoView());
|
|
510
|
-
editor.commands.focus(at + 1);
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
function startBlockDrag(editor, pos, event, image) {
|
|
514
|
-
editor.commands.setNodeSelection(pos);
|
|
515
|
-
const { view } = editor;
|
|
516
|
-
const slice = view.state.selection.content();
|
|
517
|
-
const { dom, text } = view.serializeForClipboard(slice);
|
|
518
|
-
const transfer = event.dataTransfer;
|
|
519
|
-
if (transfer) {
|
|
520
|
-
transfer.clearData();
|
|
521
|
-
transfer.setData("text/html", dom.innerHTML);
|
|
522
|
-
transfer.setData("text/plain", text);
|
|
523
|
-
transfer.effectAllowed = "copyMove";
|
|
524
|
-
if (image) transfer.setDragImage(image, 0, 0);
|
|
525
|
-
}
|
|
526
|
-
view.dragging = { slice, move: true };
|
|
527
|
-
}
|
|
528
|
-
var blockDropCursor = { color: false, class: "bg-accent-primary" };
|
|
529
|
-
var NOTHING_YET = { find: () => null, rows: () => null, startDrag: () => {
|
|
530
|
-
}, drop: () => {
|
|
531
|
-
}, endDrag: () => {
|
|
532
|
-
}, onChange: () => () => {
|
|
533
|
-
} };
|
|
534
|
-
function EditorBlockHandle({ editor, turnInto = [], actions, children, className }) {
|
|
535
|
-
const editing = useMemo(() => {
|
|
536
|
-
if (!editor) return NOTHING_YET;
|
|
537
|
-
let dragged;
|
|
538
|
-
return {
|
|
539
|
-
find: (y) => blockAtY(editorBlocks(editor.view), y),
|
|
540
|
-
rows: (block) => {
|
|
541
|
-
const pos = block.pos;
|
|
542
|
-
if (!editor.isEditable) return null;
|
|
543
|
-
const node = pos === void 0 ? null : editor.state.doc.nodeAt(pos);
|
|
544
|
-
if (pos === void 0 || !node) return null;
|
|
545
|
-
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
546
|
-
node.isTextblock && turnInto.length > 0 && /* @__PURE__ */ jsx3(MenuSub, { label: "Turn into", children: turnInto.map((row) => /* @__PURE__ */ jsx3(
|
|
547
|
-
MenuItem,
|
|
548
|
-
{
|
|
549
|
-
label: row.label,
|
|
550
|
-
selected: row.active?.(node) ?? false,
|
|
551
|
-
onClick: () => {
|
|
552
|
-
editor.chain().focus().setTextSelection(pos + 1).run();
|
|
553
|
-
row.run(editor);
|
|
554
|
-
}
|
|
555
|
-
},
|
|
556
|
-
row.label
|
|
557
|
-
)) }),
|
|
558
|
-
/* @__PURE__ */ jsx3(MenuItem, { label: "Duplicate", leading: /* @__PURE__ */ jsx3(IconCopy, { size: 16, stroke: 1.5, className: "text-text-secondary" }), onClick: () => duplicateBlock(editor, pos) }),
|
|
559
|
-
/* @__PURE__ */ jsx3(MenuItem, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx3(IconTrash, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: () => deleteBlock(editor, pos) })
|
|
560
|
-
] });
|
|
561
|
-
},
|
|
562
|
-
startDrag: (block, event) => {
|
|
563
|
-
dragged = block.pos;
|
|
564
|
-
if (block.pos !== void 0) startBlockDrag(editor, block.pos, event, block.element);
|
|
565
|
-
},
|
|
566
|
-
drop: (target, after) => {
|
|
567
|
-
const node = target.pos === void 0 ? null : editor.state.doc.nodeAt(target.pos);
|
|
568
|
-
if (dragged === void 0 || target.pos === void 0 || !node) return;
|
|
569
|
-
moveBlock(editor, dragged, after ? target.pos + node.nodeSize : target.pos);
|
|
570
|
-
},
|
|
571
|
-
// ProseMirror clears `dragging` on its own dragend, which a drag started outside its element never reaches.
|
|
572
|
-
endDrag: () => setTimeout(() => editor.view.dragging = null, 50),
|
|
573
|
-
onChange: (changed) => {
|
|
574
|
-
editor.on("update", changed);
|
|
575
|
-
return () => {
|
|
576
|
-
editor.off("update", changed);
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
};
|
|
580
|
-
}, [editor, turnInto]);
|
|
581
|
-
return /* @__PURE__ */ jsx3(BlockHandle, { editing, actions, className, children });
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// src/EditorTableControls.tsx
|
|
585
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
586
|
-
import { TextSelection } from "@tiptap/pm/state";
|
|
587
|
-
import { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, deleteColumn, deleteRow, deleteTable, toggleHeader } from "@tiptap/pm/tables";
|
|
588
|
-
import { IconColumnInsertLeft, IconColumnInsertRight, IconGripHorizontal, IconGripVertical, IconPlus, IconRowInsertBottom, IconRowInsertTop, IconTrash as IconTrash2 } from "@tabler/icons-react";
|
|
589
|
-
import { cn } from "@estiva-app/ui";
|
|
590
|
-
import { IconButton } from "@estiva-app/ui";
|
|
591
|
-
|
|
592
|
-
// src/looks.ts
|
|
593
|
-
var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
|
|
594
|
-
|
|
595
|
-
// src/EditorTableControls.tsx
|
|
596
|
-
import { Menu, MenuItem as MenuItem2, MenuSeparator } from "@estiva-app/ui";
|
|
597
|
-
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
598
|
-
function inCell(editor, cell, command) {
|
|
599
|
-
const { view } = editor;
|
|
600
|
-
if (!view.dom.contains(cell)) return false;
|
|
601
|
-
const at = view.posAtDOM(cell, 0);
|
|
602
|
-
view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(at))));
|
|
603
|
-
const done = command(view.state, view.dispatch, view);
|
|
604
|
-
editor.commands.focus();
|
|
605
|
-
return done;
|
|
606
|
-
}
|
|
607
|
-
function isHeaderRow(row) {
|
|
608
|
-
return row.cells.length > 0 && Array.from(row.cells).every((c) => c.tagName === "TH");
|
|
609
|
-
}
|
|
610
|
-
function deleteTableRow(editor, cell) {
|
|
611
|
-
const row = cell.closest("tr");
|
|
612
|
-
const table = cell.closest("table");
|
|
613
|
-
if (!row || !table || table.rows.length <= 1) return inCell(editor, cell, deleteTable);
|
|
614
|
-
const header = isHeaderRow(row);
|
|
615
|
-
return inCell(editor, cell, header ? (state, dispatch, view) => deleteRow(state, dispatch) && (!view || toggleHeader("row")(view.state, view.dispatch)) : deleteRow);
|
|
616
|
-
}
|
|
617
|
-
function deleteTableColumn(editor, cell) {
|
|
618
|
-
const row = cell.closest("tr");
|
|
619
|
-
return inCell(editor, cell, row && row.cells.length > 1 ? deleteColumn : deleteTable);
|
|
620
|
-
}
|
|
621
|
-
function appendTableRow(editor, table) {
|
|
622
|
-
const last = table.rows[table.rows.length - 1]?.cells[0];
|
|
623
|
-
return !!last && inCell(editor, last, addRowAfter);
|
|
624
|
-
}
|
|
625
|
-
function appendTableColumn(editor, table) {
|
|
626
|
-
const first = table.rows[0];
|
|
627
|
-
const last = first?.cells[first.cells.length - 1];
|
|
628
|
-
return !!last && inCell(editor, last, addColumnAfter);
|
|
629
|
-
}
|
|
630
|
-
function place(frame, { table, row, cell }) {
|
|
631
|
-
const at = frame.getBoundingClientRect();
|
|
632
|
-
const t = table.getBoundingClientRect();
|
|
633
|
-
const r = row.getBoundingClientRect();
|
|
634
|
-
const c = cell.getBoundingClientRect();
|
|
635
|
-
return {
|
|
636
|
-
tableBox: { top: t.top - at.top, left: t.left - at.left, width: t.width, height: t.height },
|
|
637
|
-
rowBox: { top: r.top - at.top, height: r.height },
|
|
638
|
-
columnBox: { left: c.left - at.left, width: c.width }
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
var ICON = { size: 16, stroke: 1.5, className: "text-text-secondary" };
|
|
642
|
-
var keepFocus = (e) => e.preventDefault();
|
|
643
|
-
var HANDLE_BOX = cn(FLOATING_SURFACE_CLASSES, "shadow-none");
|
|
644
|
-
function EditorTableControls({ editor, children, className }) {
|
|
645
|
-
const frame = useRef3(null);
|
|
646
|
-
const [hovered, setHovered] = useState2(null);
|
|
647
|
-
const [menu, setMenu] = useState2(null);
|
|
648
|
-
const editable = !!editor && editor.isEditable;
|
|
649
|
-
const follow = useCallback2(
|
|
650
|
-
(target) => {
|
|
651
|
-
if (!editor || !frame.current || !(target instanceof Element)) return;
|
|
652
|
-
if (target.closest("[data-table-control]")) return;
|
|
653
|
-
const cell = target.closest("td, th");
|
|
654
|
-
const row = cell?.closest("tr");
|
|
655
|
-
const table = row?.closest("table");
|
|
656
|
-
if (!cell || !row || !table || !editor.view.dom.contains(table)) {
|
|
657
|
-
setHovered(null);
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
const next = { table, row, cell, ...place(frame.current, { table, row, cell }) };
|
|
661
|
-
setHovered((was) => was && was.cell === cell && JSON.stringify([was.tableBox, was.rowBox, was.columnBox]) === JSON.stringify([next.tableBox, next.rowBox, next.columnBox]) ? was : next);
|
|
662
|
-
},
|
|
663
|
-
[editor]
|
|
664
|
-
);
|
|
665
|
-
useEffect2(() => {
|
|
666
|
-
if (!editor) return;
|
|
667
|
-
const changed = ({ transaction }) => {
|
|
668
|
-
if (transaction.docChanged && !menu) setHovered(null);
|
|
669
|
-
};
|
|
670
|
-
editor.on("transaction", changed);
|
|
671
|
-
return () => {
|
|
672
|
-
editor.off("transaction", changed);
|
|
673
|
-
};
|
|
674
|
-
}, [editor, menu]);
|
|
675
|
-
const shown = editable ? hovered : null;
|
|
676
|
-
const run = (act) => () => {
|
|
677
|
-
if (editor) act();
|
|
678
|
-
setMenu(null);
|
|
679
|
-
setHovered(null);
|
|
680
|
-
};
|
|
681
|
-
return /* @__PURE__ */ jsxs3(
|
|
682
|
-
"div",
|
|
683
|
-
{
|
|
684
|
-
ref: frame,
|
|
685
|
-
className: cn("relative", className),
|
|
686
|
-
onMouseMove: (e) => {
|
|
687
|
-
if (!menu) follow(e.target);
|
|
688
|
-
},
|
|
689
|
-
onMouseLeave: () => {
|
|
690
|
-
if (!menu) setHovered(null);
|
|
691
|
-
},
|
|
692
|
-
children: [
|
|
693
|
-
children,
|
|
694
|
-
shown && editor && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
695
|
-
/* @__PURE__ */ jsx4("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute -translate-x-1/2 -translate-y-1/2"), style: { left: shown.tableBox.left, top: shown.rowBox.top + shown.rowBox.height / 2 }, children: /* @__PURE__ */ jsxs3(
|
|
696
|
-
Menu,
|
|
697
|
-
{
|
|
698
|
-
open: menu === "row",
|
|
699
|
-
onOpenChange: (open) => setMenu(open ? "row" : null),
|
|
700
|
-
trigger: /* @__PURE__ */ jsx4(IconButton, { "aria-label": "Row menu", tooltip: "Row", className: "h-6 w-4 p-0", children: /* @__PURE__ */ jsx4(IconGripVertical, { size: 16, stroke: 1.5 }) }),
|
|
701
|
-
children: [
|
|
702
|
-
!isHeaderRow(shown.row) && /* @__PURE__ */ jsx4(MenuItem2, { label: "Insert above", leading: /* @__PURE__ */ jsx4(IconRowInsertTop, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowBefore)) }),
|
|
703
|
-
/* @__PURE__ */ jsx4(MenuItem2, { label: "Insert below", leading: /* @__PURE__ */ jsx4(IconRowInsertBottom, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowAfter)) }),
|
|
704
|
-
/* @__PURE__ */ jsx4(MenuSeparator, {}),
|
|
705
|
-
/* @__PURE__ */ jsx4(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableRow(editor, shown.cell)) }),
|
|
706
|
-
/* @__PURE__ */ jsx4(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
|
|
707
|
-
]
|
|
708
|
-
}
|
|
709
|
-
) }),
|
|
710
|
-
/* @__PURE__ */ jsx4("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute -translate-x-1/2 -translate-y-1/2"), style: { left: shown.columnBox.left + shown.columnBox.width / 2, top: shown.tableBox.top }, children: /* @__PURE__ */ jsxs3(
|
|
711
|
-
Menu,
|
|
712
|
-
{
|
|
713
|
-
open: menu === "column",
|
|
714
|
-
onOpenChange: (open) => setMenu(open ? "column" : null),
|
|
715
|
-
trigger: /* @__PURE__ */ jsx4(IconButton, { "aria-label": "Column menu", tooltip: "Column", className: "h-4 w-6 p-0", children: /* @__PURE__ */ jsx4(IconGripHorizontal, { size: 16, stroke: 1.5 }) }),
|
|
716
|
-
children: [
|
|
717
|
-
/* @__PURE__ */ jsx4(MenuItem2, { label: "Insert left", leading: /* @__PURE__ */ jsx4(IconColumnInsertLeft, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnBefore)) }),
|
|
718
|
-
/* @__PURE__ */ jsx4(MenuItem2, { label: "Insert right", leading: /* @__PURE__ */ jsx4(IconColumnInsertRight, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnAfter)) }),
|
|
719
|
-
/* @__PURE__ */ jsx4(MenuSeparator, {}),
|
|
720
|
-
/* @__PURE__ */ jsx4(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableColumn(editor, shown.cell)) }),
|
|
721
|
-
/* @__PURE__ */ jsx4(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
|
|
722
|
-
]
|
|
723
|
-
}
|
|
724
|
-
) }),
|
|
725
|
-
shown.row === shown.table.rows[shown.table.rows.length - 1] && /* @__PURE__ */ jsx4("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute flex h-4"), style: { left: shown.tableBox.left, top: shown.tableBox.top + shown.tableBox.height, width: shown.tableBox.width }, children: /* @__PURE__ */ jsx4(IconButton, { "aria-label": "Add row", tooltip: "Add row", className: "h-full w-full p-0", onMouseDown: keepFocus, onClick: run(() => appendTableRow(editor, shown.table)), children: /* @__PURE__ */ jsx4(IconPlus, { size: 12, stroke: 1.5 }) }) }),
|
|
726
|
-
shown.cell === shown.row.cells[shown.row.cells.length - 1] && /* @__PURE__ */ jsx4("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute flex w-4"), style: { left: shown.tableBox.left + shown.tableBox.width, top: shown.tableBox.top, height: shown.tableBox.height }, children: /* @__PURE__ */ jsx4(IconButton, { "aria-label": "Add column", tooltip: "Add column", className: "h-full w-full p-0", onMouseDown: keepFocus, onClick: run(() => appendTableColumn(editor, shown.table)), children: /* @__PURE__ */ jsx4(IconPlus, { size: 12, stroke: 1.5 }) }) })
|
|
727
|
-
] })
|
|
728
|
-
]
|
|
729
|
-
}
|
|
730
|
-
);
|
|
731
|
-
}
|
|
732
521
|
|
|
733
522
|
// src/ComposerTriggers.tsx
|
|
734
|
-
import { Extension as
|
|
735
|
-
import { PluginKey as
|
|
523
|
+
import { Extension as Extension4 } from "@tiptap/core";
|
|
524
|
+
import { PluginKey as PluginKey4 } from "@tiptap/pm/state";
|
|
736
525
|
import Mention from "@tiptap/extension-mention";
|
|
737
526
|
import Suggestion from "@tiptap/suggestion";
|
|
738
527
|
import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
|
|
739
528
|
import { Avatar } from "@estiva-app/ui";
|
|
740
529
|
import { EnterHint } from "@estiva-app/ui";
|
|
741
530
|
import { inlineChipClassName } from "@estiva-app/ui";
|
|
742
|
-
import { jsx as
|
|
531
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
743
532
|
var PEOPLE_LIMIT = 6;
|
|
744
533
|
function filterPeople(people, query) {
|
|
745
534
|
const q = query.toLowerCase();
|
|
@@ -757,8 +546,8 @@ function peopleMenu(urgent) {
|
|
|
757
546
|
row: (person) => ({
|
|
758
547
|
label: person.name,
|
|
759
548
|
description: person.description,
|
|
760
|
-
leading: person.avatar ?? /* @__PURE__ */
|
|
761
|
-
hint: /* @__PURE__ */
|
|
549
|
+
leading: person.avatar ?? /* @__PURE__ */ jsx3(Avatar, { size: 32, alt: person.name }),
|
|
550
|
+
hint: /* @__PURE__ */ jsx3(EnterHint, {}),
|
|
762
551
|
size: "tall"
|
|
763
552
|
})
|
|
764
553
|
};
|
|
@@ -885,9 +674,9 @@ var CaptionedReference = Mention.extend({
|
|
|
885
674
|
addNodeView() {
|
|
886
675
|
const { icon, iconFor } = this.options;
|
|
887
676
|
return ReactNodeViewRenderer(
|
|
888
|
-
({ node }) => /* @__PURE__ */
|
|
889
|
-
/* @__PURE__ */
|
|
890
|
-
/* @__PURE__ */
|
|
677
|
+
({ node }) => /* @__PURE__ */ jsxs2(NodeViewWrapper, { as: "span", className: inlineChipClassName("neutral", "max-w-[24ch] cursor-default"), children: [
|
|
678
|
+
/* @__PURE__ */ jsx3("span", { className: "flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary", children: iconFor?.(node.attrs.uri ?? null) ?? icon }),
|
|
679
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate", children: captionOf(node.attrs.label, node.attrs.snippet) })
|
|
891
680
|
] }),
|
|
892
681
|
{ as: "span" }
|
|
893
682
|
);
|
|
@@ -909,11 +698,11 @@ function referenceMenu({ ariaLabel, sectionLabel, icon, iconFor }) {
|
|
|
909
698
|
return groups;
|
|
910
699
|
},
|
|
911
700
|
itemKey: (item) => `reference-${item.id}`,
|
|
912
|
-
row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */
|
|
701
|
+
row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */ jsx3(EnterHint, {}) })
|
|
913
702
|
};
|
|
914
703
|
}
|
|
915
704
|
var listed = (options, query) => options.sections ? options.sections(query).flatMap((section) => section.items.map((item) => ({ ...item, section: section.label }))) : filterReferences(options.items(), query);
|
|
916
|
-
var ReferenceTrigger =
|
|
705
|
+
var ReferenceTrigger = Extension4.create({
|
|
917
706
|
name: "referenceTrigger",
|
|
918
707
|
addOptions() {
|
|
919
708
|
return { items: () => [], nodeName: "captionedReference", ariaLabel: "References", sectionLabel: "References", icon: null };
|
|
@@ -924,7 +713,7 @@ var ReferenceTrigger = Extension3.create({
|
|
|
924
713
|
Suggestion({
|
|
925
714
|
editor: this.editor,
|
|
926
715
|
char: "[",
|
|
927
|
-
pluginKey: new
|
|
716
|
+
pluginKey: new PluginKey4("referenceTrigger"),
|
|
928
717
|
...listening("]"),
|
|
929
718
|
// As a mention's: only where the chip may go.
|
|
930
719
|
allow: ({ state, range }) => {
|
|
@@ -942,130 +731,488 @@ var ReferenceTrigger = Extension3.create({
|
|
|
942
731
|
})
|
|
943
732
|
];
|
|
944
733
|
}
|
|
945
|
-
});
|
|
946
|
-
var offers = (editor, command) => !command.needs || command.needs.every((name) => editor.extensionManager.extensions.some((extension) => extension.name === name));
|
|
947
|
-
var FORMATS = [
|
|
948
|
-
{ id: "paragraph", label: "Text" },
|
|
949
|
-
{ id: "heading", label: "Heading", trigger: "#" },
|
|
950
|
-
{ id: "subheading", label: "Subheading", trigger: "##" },
|
|
951
|
-
{ id: "quote", label: "Quote", trigger: ">" },
|
|
952
|
-
{ id: "code", label: "Code", trigger: "`", blockTrigger: "```" },
|
|
953
|
-
{ id: "bulletList", label: "Bulleted list", trigger: "-" },
|
|
954
|
-
{ id: "orderedList", label: "Numbered list", trigger: "1." }
|
|
955
|
-
];
|
|
956
|
-
var DOCUMENT_FORMATS = [
|
|
957
|
-
{ id: "checklist", label: "Checklist", trigger: "[]" },
|
|
958
|
-
{ id: "note", label: "Note" },
|
|
959
|
-
{ id: "warning", label: "Warning" }
|
|
960
|
-
];
|
|
961
|
-
function applyFormat(editor, id, code = "inline") {
|
|
962
|
-
const chain = editor.chain().focus();
|
|
963
|
-
const run = (command, ...args) => {
|
|
964
|
-
const named2 = chain[command];
|
|
965
|
-
return named2 ? named2(...args).run() : false;
|
|
966
|
-
};
|
|
967
|
-
switch (id) {
|
|
968
|
-
case "paragraph":
|
|
969
|
-
return run("setParagraph");
|
|
970
|
-
case "heading":
|
|
971
|
-
return run("setHeading", { level: 1 });
|
|
972
|
-
case "subheading":
|
|
973
|
-
return run("setHeading", { level: 2 });
|
|
974
|
-
case "quote":
|
|
975
|
-
return run("toggleBlockquote");
|
|
976
|
-
case "bulletList":
|
|
977
|
-
return run("toggleBulletList");
|
|
978
|
-
case "orderedList":
|
|
979
|
-
return run("toggleOrderedList");
|
|
980
|
-
case "code":
|
|
981
|
-
return run(code === "block" ? "toggleCodeBlock" : "toggleCode");
|
|
982
|
-
case "checklist": {
|
|
983
|
-
if (!editor.isActive("listItem") && !run("toggleBulletList")) return false;
|
|
984
|
-
if (typeof editor.getAttributes("listItem").checked === "boolean") return true;
|
|
985
|
-
return editor.chain().updateAttributes("listItem", { checked: false }).run();
|
|
986
|
-
}
|
|
987
|
-
case "note":
|
|
988
|
-
case "warning": {
|
|
989
|
-
if (!editor.isActive("blockquote") && !run("toggleBlockquote")) return false;
|
|
990
|
-
return editor.chain().updateAttributes("blockquote", { tone: id }).run();
|
|
734
|
+
});
|
|
735
|
+
var offers = (editor, command) => !command.needs || command.needs.every((name) => editor.extensionManager.extensions.some((extension) => extension.name === name));
|
|
736
|
+
var FORMATS = [
|
|
737
|
+
{ id: "paragraph", label: "Text" },
|
|
738
|
+
{ id: "heading", label: "Heading", trigger: "#" },
|
|
739
|
+
{ id: "subheading", label: "Subheading", trigger: "##" },
|
|
740
|
+
{ id: "quote", label: "Quote", trigger: ">" },
|
|
741
|
+
{ id: "code", label: "Code", trigger: "`", blockTrigger: "```" },
|
|
742
|
+
{ id: "bulletList", label: "Bulleted list", trigger: "-" },
|
|
743
|
+
{ id: "orderedList", label: "Numbered list", trigger: "1." }
|
|
744
|
+
];
|
|
745
|
+
var DOCUMENT_FORMATS = [
|
|
746
|
+
{ id: "checklist", label: "Checklist", trigger: "[]" },
|
|
747
|
+
{ id: "note", label: "Note" },
|
|
748
|
+
{ id: "warning", label: "Warning" }
|
|
749
|
+
];
|
|
750
|
+
function applyFormat(editor, id, code = "inline") {
|
|
751
|
+
const chain = editor.chain().focus();
|
|
752
|
+
const run = (command, ...args) => {
|
|
753
|
+
const named2 = chain[command];
|
|
754
|
+
return named2 ? named2(...args).run() : false;
|
|
755
|
+
};
|
|
756
|
+
switch (id) {
|
|
757
|
+
case "paragraph":
|
|
758
|
+
return run("setParagraph");
|
|
759
|
+
case "heading":
|
|
760
|
+
return run("setHeading", { level: 1 });
|
|
761
|
+
case "subheading":
|
|
762
|
+
return run("setHeading", { level: 2 });
|
|
763
|
+
case "quote":
|
|
764
|
+
return run("toggleBlockquote");
|
|
765
|
+
case "bulletList":
|
|
766
|
+
return run("toggleBulletList");
|
|
767
|
+
case "orderedList":
|
|
768
|
+
return run("toggleOrderedList");
|
|
769
|
+
case "code":
|
|
770
|
+
return run(code === "block" ? "toggleCodeBlock" : "toggleCode");
|
|
771
|
+
case "checklist": {
|
|
772
|
+
if (!editor.isActive("listItem") && !run("toggleBulletList")) return false;
|
|
773
|
+
if (typeof editor.getAttributes("listItem").checked === "boolean") return true;
|
|
774
|
+
return editor.chain().updateAttributes("listItem", { checked: false }).run();
|
|
775
|
+
}
|
|
776
|
+
case "note":
|
|
777
|
+
case "warning": {
|
|
778
|
+
if (!editor.isActive("blockquote") && !run("toggleBlockquote")) return false;
|
|
779
|
+
return editor.chain().updateAttributes("blockquote", { tone: id }).run();
|
|
780
|
+
}
|
|
781
|
+
default:
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
var KEYWORDS = {
|
|
786
|
+
checklist: ["todo", "task"],
|
|
787
|
+
note: ["callout"],
|
|
788
|
+
warning: ["callout"]
|
|
789
|
+
};
|
|
790
|
+
var NEEDS = {
|
|
791
|
+
checklist: ["checklist"],
|
|
792
|
+
note: ["callout"],
|
|
793
|
+
warning: ["callout"]
|
|
794
|
+
};
|
|
795
|
+
function formatSection(code = "inline") {
|
|
796
|
+
return {
|
|
797
|
+
label: "Format",
|
|
798
|
+
commands: (code === "block" ? [...FORMATS, ...DOCUMENT_FORMATS] : FORMATS).map((format) => ({
|
|
799
|
+
key: `format-${format.id}`,
|
|
800
|
+
row: { label: format.label, shortcut: code === "block" && format.blockTrigger || format.trigger },
|
|
801
|
+
keywords: [format.id, ...KEYWORDS[format.id] ?? []],
|
|
802
|
+
...NEEDS[format.id] ? { needs: NEEDS[format.id] } : {},
|
|
803
|
+
run: (editor) => {
|
|
804
|
+
applyFormat(editor, format.id, code);
|
|
805
|
+
}
|
|
806
|
+
}))
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function typeTrigger(input) {
|
|
810
|
+
return (editor) => {
|
|
811
|
+
requestAnimationFrame(() => editor.commands.insertContent(input));
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function matches(command, q) {
|
|
815
|
+
return [command.row.label, ...command.keywords ?? []].some((text) => text.toLowerCase().includes(q));
|
|
816
|
+
}
|
|
817
|
+
function filterSlashSections(sections, query) {
|
|
818
|
+
const q = query.toLowerCase();
|
|
819
|
+
return sections.map((section) => ({ ...section, commands: q ? section.commands.filter((command) => matches(command, q)) : section.commands }));
|
|
820
|
+
}
|
|
821
|
+
var SlashCommands = Extension4.create({
|
|
822
|
+
name: "slashCommands",
|
|
823
|
+
addOptions() {
|
|
824
|
+
return { sections: [formatSection()] };
|
|
825
|
+
},
|
|
826
|
+
addProseMirrorPlugins() {
|
|
827
|
+
const sections = this.options.sections;
|
|
828
|
+
return [
|
|
829
|
+
Suggestion({
|
|
830
|
+
editor: this.editor,
|
|
831
|
+
char: "/",
|
|
832
|
+
startOfLine: false,
|
|
833
|
+
pluginKey: new PluginKey4("slashCommands"),
|
|
834
|
+
items: ({ query, editor }) => filterSlashSections(sections, query).flatMap(
|
|
835
|
+
(section, at) => section.commands.filter((command) => offers(editor, command)).map((command) => ({ ...command, section: at }))
|
|
836
|
+
),
|
|
837
|
+
command: ({ editor, range, props }) => {
|
|
838
|
+
editor.chain().focus().deleteRange(range).run();
|
|
839
|
+
props.run(editor);
|
|
840
|
+
},
|
|
841
|
+
render: suggestionPopup({
|
|
842
|
+
ariaLabel: "Commands",
|
|
843
|
+
width: "w-[300px]",
|
|
844
|
+
maxHeight: "max-h-[400px]",
|
|
845
|
+
sections: (rows) => sections.map((section, at) => ({ label: section.label, items: rows.filter((row) => row.section === at) })),
|
|
846
|
+
itemKey: (row) => row.key,
|
|
847
|
+
row: (row) => row.row
|
|
848
|
+
})
|
|
849
|
+
})
|
|
850
|
+
];
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// src/EditorBlockHandle.tsx
|
|
855
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
856
|
+
function editorBlocks(view) {
|
|
857
|
+
const out = [];
|
|
858
|
+
view.state.doc.forEach((node, pos) => {
|
|
859
|
+
const element = view.nodeDOM(pos);
|
|
860
|
+
if (element instanceof HTMLElement) out.push({ id: node.attrs.blockId || void 0, element, pos });
|
|
861
|
+
});
|
|
862
|
+
return out;
|
|
863
|
+
}
|
|
864
|
+
function duplicateBlock(editor, pos) {
|
|
865
|
+
const node = editor.state.doc.nodeAt(pos);
|
|
866
|
+
if (!node) return false;
|
|
867
|
+
editor.view.dispatch(editor.state.tr.insert(pos + node.nodeSize, node).scrollIntoView());
|
|
868
|
+
editor.commands.focus(pos + node.nodeSize + 1);
|
|
869
|
+
return true;
|
|
870
|
+
}
|
|
871
|
+
function deleteBlock(editor, pos) {
|
|
872
|
+
const { state } = editor;
|
|
873
|
+
const node = state.doc.nodeAt(pos);
|
|
874
|
+
if (!node) return false;
|
|
875
|
+
const tr = state.doc.childCount === 1 ? state.tr.replaceWith(pos, pos + node.nodeSize, state.schema.nodes.paragraph.create()) : state.tr.delete(pos, pos + node.nodeSize);
|
|
876
|
+
editor.view.dispatch(tr);
|
|
877
|
+
editor.commands.focus(Math.min(pos + 1, editor.state.doc.content.size));
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
function moveBlock(editor, from, to) {
|
|
881
|
+
const node = editor.state.doc.nodeAt(from);
|
|
882
|
+
if (!node || to >= from && to <= from + node.nodeSize) return false;
|
|
883
|
+
const tr = editor.state.tr.delete(from, from + node.nodeSize);
|
|
884
|
+
const at = tr.mapping.map(to);
|
|
885
|
+
editor.view.dispatch(tr.insert(at, node).scrollIntoView());
|
|
886
|
+
editor.commands.focus(at + 1);
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
function startBlockDrag(editor, pos, event, image) {
|
|
890
|
+
editor.commands.setNodeSelection(pos);
|
|
891
|
+
const { view } = editor;
|
|
892
|
+
const slice = view.state.selection.content();
|
|
893
|
+
const { dom, text } = view.serializeForClipboard(slice);
|
|
894
|
+
const transfer = event.dataTransfer;
|
|
895
|
+
if (transfer) {
|
|
896
|
+
transfer.clearData();
|
|
897
|
+
transfer.setData("text/html", dom.innerHTML);
|
|
898
|
+
transfer.setData("text/plain", text);
|
|
899
|
+
transfer.effectAllowed = "copyMove";
|
|
900
|
+
if (image) transfer.setDragImage(image, 0, 0);
|
|
901
|
+
}
|
|
902
|
+
view.dragging = { slice, move: true };
|
|
903
|
+
}
|
|
904
|
+
var blockDropCursor = { color: false, class: "bg-accent-primary" };
|
|
905
|
+
var NOTHING_YET = { find: () => null, rows: () => null, startDrag: () => {
|
|
906
|
+
}, drop: () => {
|
|
907
|
+
}, endDrag: () => {
|
|
908
|
+
}, onChange: () => () => {
|
|
909
|
+
} };
|
|
910
|
+
function EditorBlockHandle({ editor, turnInto = turnIntoRows, actions, children, className }) {
|
|
911
|
+
const editing = useMemo(() => {
|
|
912
|
+
if (!editor) return NOTHING_YET;
|
|
913
|
+
let dragged;
|
|
914
|
+
return {
|
|
915
|
+
find: (y) => blockAtY(editorBlocks(editor.view), y),
|
|
916
|
+
rows: (block) => {
|
|
917
|
+
const pos = block.pos;
|
|
918
|
+
if (!editor.isEditable) return null;
|
|
919
|
+
const node = pos === void 0 ? null : editor.state.doc.nodeAt(pos);
|
|
920
|
+
if (pos === void 0 || !node) return null;
|
|
921
|
+
const turnRows = typeof turnInto === "function" ? turnInto(node, editor) : node.isTextblock ? turnInto : [];
|
|
922
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
923
|
+
turnRows.length > 0 && /* @__PURE__ */ jsx4(MenuSub, { label: "Turn into", children: turnRows.map((row) => /* @__PURE__ */ jsx4(
|
|
924
|
+
MenuItem,
|
|
925
|
+
{
|
|
926
|
+
label: row.label,
|
|
927
|
+
selected: row.active?.(node) ?? false,
|
|
928
|
+
onClick: () => {
|
|
929
|
+
if (node.isTextblock) editor.chain().focus().setTextSelection(pos + 1).run();
|
|
930
|
+
row.run(editor, pos);
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
row.label
|
|
934
|
+
)) }),
|
|
935
|
+
/* @__PURE__ */ jsx4(MenuItem, { label: "Duplicate", leading: /* @__PURE__ */ jsx4(IconCopy, { size: 16, stroke: 1.5, className: "text-text-secondary" }), onClick: () => duplicateBlock(editor, pos) }),
|
|
936
|
+
/* @__PURE__ */ jsx4(MenuItem, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: () => deleteBlock(editor, pos) })
|
|
937
|
+
] });
|
|
938
|
+
},
|
|
939
|
+
startDrag: (block, event) => {
|
|
940
|
+
dragged = block.pos;
|
|
941
|
+
if (block.pos !== void 0) startBlockDrag(editor, block.pos, event, block.element);
|
|
942
|
+
},
|
|
943
|
+
drop: (target, after) => {
|
|
944
|
+
const node = target.pos === void 0 ? null : editor.state.doc.nodeAt(target.pos);
|
|
945
|
+
if (dragged === void 0 || target.pos === void 0 || !node) return;
|
|
946
|
+
moveBlock(editor, dragged, after ? target.pos + node.nodeSize : target.pos);
|
|
947
|
+
},
|
|
948
|
+
// ProseMirror clears `dragging` on its own dragend, which a drag started outside its element never reaches.
|
|
949
|
+
endDrag: () => setTimeout(() => editor.view.dragging = null, 50),
|
|
950
|
+
onChange: (changed) => {
|
|
951
|
+
editor.on("update", changed);
|
|
952
|
+
return () => {
|
|
953
|
+
editor.off("update", changed);
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
}, [editor, turnInto]);
|
|
958
|
+
return /* @__PURE__ */ jsx4(BlockHandle, { editing, actions, className, children });
|
|
959
|
+
}
|
|
960
|
+
var LISTS = ["bulletList", "orderedList"];
|
|
961
|
+
var isList = (node) => LISTS.includes(node.type.name);
|
|
962
|
+
function listItems(list, at) {
|
|
963
|
+
const out = [];
|
|
964
|
+
list.descendants((node, pos) => {
|
|
965
|
+
if (node.type.name === "listItem") out.push({ node, pos: at + 1 + pos });
|
|
966
|
+
});
|
|
967
|
+
return out;
|
|
968
|
+
}
|
|
969
|
+
var isChecklistItem = ({ node }) => typeof node.attrs.checked === "boolean";
|
|
970
|
+
var isChecklist = (list) => listItems(list, 0).every(isChecklistItem);
|
|
971
|
+
var isPlainList = (list, type) => list.type.name === type && !listItems(list, 0).some(isChecklistItem);
|
|
972
|
+
var hasExtension = (editor, name) => editor.extensionManager.extensions.some((e) => e.name === name);
|
|
973
|
+
function caretInto(editor, pos) {
|
|
974
|
+
const { state } = editor;
|
|
975
|
+
editor.view.dispatch(state.tr.setSelection(Selection.near(state.doc.resolve(Math.min(pos + 1, state.doc.content.size)))));
|
|
976
|
+
editor.commands.focus();
|
|
977
|
+
}
|
|
978
|
+
function turnListInto(editor, pos, kind) {
|
|
979
|
+
const { state } = editor;
|
|
980
|
+
const list = state.doc.nodeAt(pos);
|
|
981
|
+
if (!list || !isList(list)) return false;
|
|
982
|
+
const tr = state.tr;
|
|
983
|
+
if (kind === "text") {
|
|
984
|
+
const blocks = [];
|
|
985
|
+
for (const { node: item } of listItems(list, pos)) {
|
|
986
|
+
item.forEach((child, _offset, index) => {
|
|
987
|
+
if (isList(child)) return;
|
|
988
|
+
blocks.push(child.type.create({ ...child.attrs, blockId: index === 0 ? item.attrs.blockId : null }, child.content, child.marks));
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
tr.replaceWith(pos, pos + list.nodeSize, blocks);
|
|
992
|
+
} else {
|
|
993
|
+
const type = kind === "checklist" ? list.type : state.schema.nodes[kind];
|
|
994
|
+
if (list.type !== type) tr.setNodeMarkup(pos, type, list.attrs);
|
|
995
|
+
list.descendants((node, at) => {
|
|
996
|
+
if (isList(node) && node.type !== type) tr.setNodeMarkup(pos + 1 + at, type, node.attrs);
|
|
997
|
+
});
|
|
998
|
+
for (const { node, pos: at } of listItems(list, pos)) {
|
|
999
|
+
const checked = kind === "checklist" ? typeof node.attrs.checked === "boolean" ? node.attrs.checked : false : null;
|
|
1000
|
+
if (node.attrs.checked !== checked) tr.setNodeAttribute(at, "checked", checked);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (tr.docChanged) editor.view.dispatch(tr.scrollIntoView());
|
|
1004
|
+
caretInto(editor, pos);
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
1007
|
+
function turnQuoteInto(editor, pos, kind) {
|
|
1008
|
+
const { state } = editor;
|
|
1009
|
+
const quote = state.doc.nodeAt(pos);
|
|
1010
|
+
if (!quote || quote.type.name !== "blockquote") return false;
|
|
1011
|
+
const tr = state.tr;
|
|
1012
|
+
if (kind === "text") {
|
|
1013
|
+
const blocks = [];
|
|
1014
|
+
quote.forEach((child) => blocks.push(blocks.length === 0 ? child.type.create({ ...child.attrs, blockId: quote.attrs.blockId }, child.content, child.marks) : child));
|
|
1015
|
+
tr.replaceWith(pos, pos + quote.nodeSize, blocks);
|
|
1016
|
+
} else {
|
|
1017
|
+
const tone = kind === "quote" ? null : kind;
|
|
1018
|
+
if ((quote.attrs.tone ?? null) !== tone) tr.setNodeAttribute(pos, "tone", tone);
|
|
1019
|
+
}
|
|
1020
|
+
if (tr.docChanged) editor.view.dispatch(tr.scrollIntoView());
|
|
1021
|
+
caretInto(editor, pos);
|
|
1022
|
+
return true;
|
|
1023
|
+
}
|
|
1024
|
+
function isFormat(node, id) {
|
|
1025
|
+
if (id === "paragraph") return node.type.name === "paragraph";
|
|
1026
|
+
if (id === "heading") return node.type.name === "heading" && node.attrs.level === 1;
|
|
1027
|
+
if (id === "subheading") return node.type.name === "heading" && node.attrs.level === 2;
|
|
1028
|
+
if (id === "code") return node.type.name === "codeBlock";
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
function turnIntoRows(node, editor) {
|
|
1032
|
+
const checklist = hasExtension(editor, "checklist");
|
|
1033
|
+
const callout = hasExtension(editor, "callout");
|
|
1034
|
+
if (node.isTextblock) {
|
|
1035
|
+
const document2 = DOCUMENT_FORMATS.filter((f) => f.id === "checklist" ? checklist : callout);
|
|
1036
|
+
return [...FORMATS, ...document2].map((format) => ({
|
|
1037
|
+
label: format.label,
|
|
1038
|
+
run: (e) => void applyFormat(e, format.id, "block"),
|
|
1039
|
+
active: (n) => isFormat(n, format.id)
|
|
1040
|
+
}));
|
|
1041
|
+
}
|
|
1042
|
+
if (isList(node)) {
|
|
1043
|
+
const rows = [
|
|
1044
|
+
{ label: "Bulleted list", run: (e, pos) => void turnListInto(e, pos, "bulletList"), active: (n) => isPlainList(n, "bulletList") },
|
|
1045
|
+
{ label: "Numbered list", run: (e, pos) => void turnListInto(e, pos, "orderedList"), active: (n) => isPlainList(n, "orderedList") }
|
|
1046
|
+
];
|
|
1047
|
+
if (checklist) rows.push({ label: "Checklist", run: (e, pos) => void turnListInto(e, pos, "checklist"), active: isChecklist });
|
|
1048
|
+
rows.push({ label: "Text", run: (e, pos) => void turnListInto(e, pos, "text") });
|
|
1049
|
+
return rows;
|
|
1050
|
+
}
|
|
1051
|
+
if (node.type.name === "blockquote") {
|
|
1052
|
+
const rows = [{ label: "Quote", run: (e, pos) => void turnQuoteInto(e, pos, "quote"), active: (n) => !n.attrs.tone }];
|
|
1053
|
+
if (callout) {
|
|
1054
|
+
rows.push({ label: "Note", run: (e, pos) => void turnQuoteInto(e, pos, "note"), active: (n) => n.attrs.tone === "note" });
|
|
1055
|
+
rows.push({ label: "Warning", run: (e, pos) => void turnQuoteInto(e, pos, "warning"), active: (n) => n.attrs.tone === "warning" });
|
|
991
1056
|
}
|
|
992
|
-
|
|
993
|
-
|
|
1057
|
+
rows.push({ label: "Text", run: (e, pos) => void turnQuoteInto(e, pos, "text") });
|
|
1058
|
+
return rows;
|
|
994
1059
|
}
|
|
1060
|
+
return [];
|
|
995
1061
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
};
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1062
|
+
|
|
1063
|
+
// src/EditorTableControls.tsx
|
|
1064
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
1065
|
+
import { TextSelection } from "@tiptap/pm/state";
|
|
1066
|
+
import { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, deleteColumn, deleteRow, deleteTable, toggleHeader } from "@tiptap/pm/tables";
|
|
1067
|
+
import { IconColumnInsertLeft, IconColumnInsertRight, IconGripHorizontal, IconGripVertical, IconPlus, IconRowInsertBottom, IconRowInsertTop, IconTrash as IconTrash2 } from "@tabler/icons-react";
|
|
1068
|
+
import { cn } from "@estiva-app/ui";
|
|
1069
|
+
import { IconButton } from "@estiva-app/ui";
|
|
1070
|
+
|
|
1071
|
+
// src/looks.ts
|
|
1072
|
+
var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
|
|
1073
|
+
|
|
1074
|
+
// src/EditorTableControls.tsx
|
|
1075
|
+
import { Menu, MenuItem as MenuItem2, MenuSeparator } from "@estiva-app/ui";
|
|
1076
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1077
|
+
function inCell(editor, cell, command) {
|
|
1078
|
+
const { view } = editor;
|
|
1079
|
+
if (!view.dom.contains(cell)) return false;
|
|
1080
|
+
const at = view.posAtDOM(cell, 0);
|
|
1081
|
+
view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(at))));
|
|
1082
|
+
const done = command(view.state, view.dispatch, view);
|
|
1083
|
+
editor.commands.focus();
|
|
1084
|
+
return done;
|
|
1085
|
+
}
|
|
1086
|
+
function isHeaderRow(row) {
|
|
1087
|
+
return row.cells.length > 0 && Array.from(row.cells).every((c) => c.tagName === "TH");
|
|
1088
|
+
}
|
|
1089
|
+
function deleteTableRow(editor, cell) {
|
|
1090
|
+
const row = cell.closest("tr");
|
|
1091
|
+
const table = cell.closest("table");
|
|
1092
|
+
if (!row || !table || table.rows.length <= 1) return inCell(editor, cell, deleteTable);
|
|
1093
|
+
const header = isHeaderRow(row);
|
|
1094
|
+
return inCell(editor, cell, header ? (state, dispatch, view) => deleteRow(state, dispatch) && (!view || toggleHeader("row")(view.state, view.dispatch)) : deleteRow);
|
|
1095
|
+
}
|
|
1096
|
+
function deleteTableColumn(editor, cell) {
|
|
1097
|
+
const row = cell.closest("tr");
|
|
1098
|
+
return inCell(editor, cell, row && row.cells.length > 1 ? deleteColumn : deleteTable);
|
|
1099
|
+
}
|
|
1100
|
+
function appendTableRow(editor, table) {
|
|
1101
|
+
const last = table.rows[table.rows.length - 1]?.cells[0];
|
|
1102
|
+
return !!last && inCell(editor, last, addRowAfter);
|
|
1103
|
+
}
|
|
1104
|
+
function appendTableColumn(editor, table) {
|
|
1105
|
+
const first = table.rows[0];
|
|
1106
|
+
const last = first?.cells[first.cells.length - 1];
|
|
1107
|
+
return !!last && inCell(editor, last, addColumnAfter);
|
|
1108
|
+
}
|
|
1109
|
+
function place(frame, { table, row, cell }) {
|
|
1110
|
+
const at = frame.getBoundingClientRect();
|
|
1111
|
+
const t = table.getBoundingClientRect();
|
|
1112
|
+
const r = row.getBoundingClientRect();
|
|
1113
|
+
const c = cell.getBoundingClientRect();
|
|
1007
1114
|
return {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
row: { label: format.label, shortcut: code === "block" && format.blockTrigger || format.trigger },
|
|
1012
|
-
keywords: [format.id, ...KEYWORDS[format.id] ?? []],
|
|
1013
|
-
...NEEDS[format.id] ? { needs: NEEDS[format.id] } : {},
|
|
1014
|
-
run: (editor) => {
|
|
1015
|
-
applyFormat(editor, format.id, code);
|
|
1016
|
-
}
|
|
1017
|
-
}))
|
|
1115
|
+
tableBox: { top: t.top - at.top, left: t.left - at.left, width: t.width, height: t.height },
|
|
1116
|
+
rowBox: { top: r.top - at.top, height: r.height },
|
|
1117
|
+
columnBox: { left: c.left - at.left, width: c.width }
|
|
1018
1118
|
};
|
|
1019
1119
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1120
|
+
var ICON = { size: 16, stroke: 1.5, className: "text-text-secondary" };
|
|
1121
|
+
var keepFocus = (e) => e.preventDefault();
|
|
1122
|
+
var HANDLE_BOX = cn(FLOATING_SURFACE_CLASSES, "shadow-none");
|
|
1123
|
+
function EditorTableControls({ editor, children, className }) {
|
|
1124
|
+
const frame = useRef3(null);
|
|
1125
|
+
const [hovered, setHovered] = useState2(null);
|
|
1126
|
+
const [menu, setMenu] = useState2(null);
|
|
1127
|
+
const editable = !!editor && editor.isEditable;
|
|
1128
|
+
const follow = useCallback2(
|
|
1129
|
+
(target) => {
|
|
1130
|
+
if (!editor || !frame.current || !(target instanceof Element)) return;
|
|
1131
|
+
if (target.closest("[data-table-control]")) return;
|
|
1132
|
+
const cell = target.closest("td, th");
|
|
1133
|
+
const row = cell?.closest("tr");
|
|
1134
|
+
const table = row?.closest("table");
|
|
1135
|
+
if (!cell || !row || !table || !editor.view.dom.contains(table)) {
|
|
1136
|
+
setHovered(null);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const next = { table, row, cell, ...place(frame.current, { table, row, cell }) };
|
|
1140
|
+
setHovered((was) => was && was.cell === cell && JSON.stringify([was.tableBox, was.rowBox, was.columnBox]) === JSON.stringify([next.tableBox, next.rowBox, next.columnBox]) ? was : next);
|
|
1141
|
+
},
|
|
1142
|
+
[editor]
|
|
1143
|
+
);
|
|
1144
|
+
useEffect2(() => {
|
|
1145
|
+
if (!editor) return;
|
|
1146
|
+
const changed = ({ transaction }) => {
|
|
1147
|
+
if (transaction.docChanged && !menu) setHovered(null);
|
|
1148
|
+
};
|
|
1149
|
+
editor.on("transaction", changed);
|
|
1150
|
+
return () => {
|
|
1151
|
+
editor.off("transaction", changed);
|
|
1152
|
+
};
|
|
1153
|
+
}, [editor, menu]);
|
|
1154
|
+
const shown = editable ? hovered : null;
|
|
1155
|
+
const run = (act) => () => {
|
|
1156
|
+
if (editor) act();
|
|
1157
|
+
setMenu(null);
|
|
1158
|
+
setHovered(null);
|
|
1023
1159
|
};
|
|
1160
|
+
return /* @__PURE__ */ jsxs4(
|
|
1161
|
+
"div",
|
|
1162
|
+
{
|
|
1163
|
+
ref: frame,
|
|
1164
|
+
className: cn("relative", className),
|
|
1165
|
+
onMouseMove: (e) => {
|
|
1166
|
+
if (!menu) follow(e.target);
|
|
1167
|
+
},
|
|
1168
|
+
onMouseLeave: () => {
|
|
1169
|
+
if (!menu) setHovered(null);
|
|
1170
|
+
},
|
|
1171
|
+
children: [
|
|
1172
|
+
children,
|
|
1173
|
+
shown && editor && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1174
|
+
/* @__PURE__ */ jsx5("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute -translate-x-1/2 -translate-y-1/2"), style: { left: shown.tableBox.left, top: shown.rowBox.top + shown.rowBox.height / 2 }, children: /* @__PURE__ */ jsxs4(
|
|
1175
|
+
Menu,
|
|
1176
|
+
{
|
|
1177
|
+
open: menu === "row",
|
|
1178
|
+
onOpenChange: (open) => setMenu(open ? "row" : null),
|
|
1179
|
+
trigger: /* @__PURE__ */ jsx5(IconButton, { "aria-label": "Row menu", tooltip: "Row", className: "h-6 w-4 p-0", children: /* @__PURE__ */ jsx5(IconGripVertical, { size: 16, stroke: 1.5 }) }),
|
|
1180
|
+
children: [
|
|
1181
|
+
!isHeaderRow(shown.row) && /* @__PURE__ */ jsx5(MenuItem2, { label: "Insert above", leading: /* @__PURE__ */ jsx5(IconRowInsertTop, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowBefore)) }),
|
|
1182
|
+
/* @__PURE__ */ jsx5(MenuItem2, { label: "Insert below", leading: /* @__PURE__ */ jsx5(IconRowInsertBottom, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowAfter)) }),
|
|
1183
|
+
/* @__PURE__ */ jsx5(MenuSeparator, {}),
|
|
1184
|
+
/* @__PURE__ */ jsx5(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx5(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableRow(editor, shown.cell)) }),
|
|
1185
|
+
/* @__PURE__ */ jsx5(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx5(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
|
|
1186
|
+
]
|
|
1187
|
+
}
|
|
1188
|
+
) }),
|
|
1189
|
+
/* @__PURE__ */ jsx5("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute -translate-x-1/2 -translate-y-1/2"), style: { left: shown.columnBox.left + shown.columnBox.width / 2, top: shown.tableBox.top }, children: /* @__PURE__ */ jsxs4(
|
|
1190
|
+
Menu,
|
|
1191
|
+
{
|
|
1192
|
+
open: menu === "column",
|
|
1193
|
+
onOpenChange: (open) => setMenu(open ? "column" : null),
|
|
1194
|
+
trigger: /* @__PURE__ */ jsx5(IconButton, { "aria-label": "Column menu", tooltip: "Column", className: "h-4 w-6 p-0", children: /* @__PURE__ */ jsx5(IconGripHorizontal, { size: 16, stroke: 1.5 }) }),
|
|
1195
|
+
children: [
|
|
1196
|
+
/* @__PURE__ */ jsx5(MenuItem2, { label: "Insert left", leading: /* @__PURE__ */ jsx5(IconColumnInsertLeft, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnBefore)) }),
|
|
1197
|
+
/* @__PURE__ */ jsx5(MenuItem2, { label: "Insert right", leading: /* @__PURE__ */ jsx5(IconColumnInsertRight, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnAfter)) }),
|
|
1198
|
+
/* @__PURE__ */ jsx5(MenuSeparator, {}),
|
|
1199
|
+
/* @__PURE__ */ jsx5(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx5(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableColumn(editor, shown.cell)) }),
|
|
1200
|
+
/* @__PURE__ */ jsx5(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx5(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
|
|
1201
|
+
]
|
|
1202
|
+
}
|
|
1203
|
+
) }),
|
|
1204
|
+
shown.row === shown.table.rows[shown.table.rows.length - 1] && /* @__PURE__ */ jsx5("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute flex h-4"), style: { left: shown.tableBox.left, top: shown.tableBox.top + shown.tableBox.height, width: shown.tableBox.width }, children: /* @__PURE__ */ jsx5(IconButton, { "aria-label": "Add row", tooltip: "Add row", className: "h-full w-full p-0", onMouseDown: keepFocus, onClick: run(() => appendTableRow(editor, shown.table)), children: /* @__PURE__ */ jsx5(IconPlus, { size: 12, stroke: 1.5 }) }) }),
|
|
1205
|
+
shown.cell === shown.row.cells[shown.row.cells.length - 1] && /* @__PURE__ */ jsx5("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute flex w-4"), style: { left: shown.tableBox.left + shown.tableBox.width, top: shown.tableBox.top, height: shown.tableBox.height }, children: /* @__PURE__ */ jsx5(IconButton, { "aria-label": "Add column", tooltip: "Add column", className: "h-full w-full p-0", onMouseDown: keepFocus, onClick: run(() => appendTableColumn(editor, shown.table)), children: /* @__PURE__ */ jsx5(IconPlus, { size: 12, stroke: 1.5 }) }) })
|
|
1206
|
+
] })
|
|
1207
|
+
]
|
|
1208
|
+
}
|
|
1209
|
+
);
|
|
1024
1210
|
}
|
|
1025
|
-
function matches(command, q) {
|
|
1026
|
-
return [command.row.label, ...command.keywords ?? []].some((text) => text.toLowerCase().includes(q));
|
|
1027
|
-
}
|
|
1028
|
-
function filterSlashSections(sections, query) {
|
|
1029
|
-
const q = query.toLowerCase();
|
|
1030
|
-
return sections.map((section) => ({ ...section, commands: q ? section.commands.filter((command) => matches(command, q)) : section.commands }));
|
|
1031
|
-
}
|
|
1032
|
-
var SlashCommands = Extension3.create({
|
|
1033
|
-
name: "slashCommands",
|
|
1034
|
-
addOptions() {
|
|
1035
|
-
return { sections: [formatSection()] };
|
|
1036
|
-
},
|
|
1037
|
-
addProseMirrorPlugins() {
|
|
1038
|
-
const sections = this.options.sections;
|
|
1039
|
-
return [
|
|
1040
|
-
Suggestion({
|
|
1041
|
-
editor: this.editor,
|
|
1042
|
-
char: "/",
|
|
1043
|
-
startOfLine: false,
|
|
1044
|
-
pluginKey: new PluginKey3("slashCommands"),
|
|
1045
|
-
items: ({ query, editor }) => filterSlashSections(sections, query).flatMap(
|
|
1046
|
-
(section, at) => section.commands.filter((command) => offers(editor, command)).map((command) => ({ ...command, section: at }))
|
|
1047
|
-
),
|
|
1048
|
-
command: ({ editor, range, props }) => {
|
|
1049
|
-
editor.chain().focus().deleteRange(range).run();
|
|
1050
|
-
props.run(editor);
|
|
1051
|
-
},
|
|
1052
|
-
render: suggestionPopup({
|
|
1053
|
-
ariaLabel: "Commands",
|
|
1054
|
-
width: "w-[300px]",
|
|
1055
|
-
maxHeight: "max-h-[400px]",
|
|
1056
|
-
sections: (rows) => sections.map((section, at) => ({ label: section.label, items: rows.filter((row) => row.section === at) })),
|
|
1057
|
-
itemKey: (row) => row.key,
|
|
1058
|
-
row: (row) => row.row
|
|
1059
|
-
})
|
|
1060
|
-
})
|
|
1061
|
-
];
|
|
1062
|
-
}
|
|
1063
|
-
});
|
|
1064
1211
|
|
|
1065
1212
|
// src/BlockNodes.tsx
|
|
1066
|
-
import { Extension as
|
|
1213
|
+
import { Extension as Extension8, InputRule, Node as Node2 } from "@tiptap/core";
|
|
1067
1214
|
import { DOMSerializer } from "@tiptap/pm/model";
|
|
1068
|
-
import { Plugin as
|
|
1215
|
+
import { Plugin as Plugin7, PluginKey as PluginKey8 } from "@tiptap/pm/state";
|
|
1069
1216
|
import { Mapping, ReplaceAroundStep } from "@tiptap/pm/transform";
|
|
1070
1217
|
import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2, ReactRenderer as ReactRenderer2 } from "@tiptap/react";
|
|
1071
1218
|
import { Checkbox } from "@estiva-app/ui";
|
|
@@ -1073,17 +1220,17 @@ import { calloutLook } from "@estiva-app/ui";
|
|
|
1073
1220
|
|
|
1074
1221
|
// src/ReferenceMenu.tsx
|
|
1075
1222
|
import { useCallback as useCallback3 } from "react";
|
|
1076
|
-
import { Extension as
|
|
1223
|
+
import { Extension as Extension7 } from "@tiptap/core";
|
|
1077
1224
|
import { useEditorState } from "@tiptap/react";
|
|
1078
|
-
import { NodeSelection as NodeSelection2, Plugin as
|
|
1225
|
+
import { NodeSelection as NodeSelection2, Plugin as Plugin6, PluginKey as PluginKey7, TextSelection as TextSelection2 } from "@tiptap/pm/state";
|
|
1079
1226
|
import { ReplaceStep } from "@tiptap/pm/transform";
|
|
1080
1227
|
import { IconCheck } from "@tabler/icons-react";
|
|
1081
1228
|
|
|
1082
1229
|
// src/LinkChips.ts
|
|
1083
|
-
import { Extension as
|
|
1230
|
+
import { Extension as Extension5 } from "@tiptap/core";
|
|
1084
1231
|
import { closeHistory } from "@tiptap/pm/history";
|
|
1085
|
-
import { Plugin as
|
|
1086
|
-
var key = new
|
|
1232
|
+
import { Plugin as Plugin4, PluginKey as PluginKey5 } from "@tiptap/pm/state";
|
|
1233
|
+
var key = new PluginKey5("linkChips");
|
|
1087
1234
|
var CHIPS_MADE = "linkChipsMade";
|
|
1088
1235
|
var PASTES_KEPT = 8;
|
|
1089
1236
|
var LINK_OR_URL = /(\[[^[\]\n]+\]\((?:https?:\/\/|mailto:)[^\s)]+\))|(https?:\/\/[^\s<>"'`]+)/g;
|
|
@@ -1158,7 +1305,7 @@ function refreshLinkChips(editor) {
|
|
|
1158
1305
|
const tr = apply(editor.state, findLinkChips(editor.state.doc, editor.state.selection, chips.options.referenceOf, chips.pasted));
|
|
1159
1306
|
if (tr) editor.view.dispatch(tr);
|
|
1160
1307
|
}
|
|
1161
|
-
var LinkChips =
|
|
1308
|
+
var LinkChips = Extension5.create({
|
|
1162
1309
|
name: "linkChips",
|
|
1163
1310
|
addOptions() {
|
|
1164
1311
|
return { referenceOf: null, ask: null, limit: 24 };
|
|
@@ -1167,7 +1314,7 @@ var LinkChips = Extension4.create({
|
|
|
1167
1314
|
const options = this.options;
|
|
1168
1315
|
const editor = this.editor;
|
|
1169
1316
|
return [
|
|
1170
|
-
new
|
|
1317
|
+
new Plugin4({
|
|
1171
1318
|
key,
|
|
1172
1319
|
// The options, where `refreshLinkChips` can read them from the state alone, and where the last pastes landed.
|
|
1173
1320
|
state: {
|
|
@@ -1231,8 +1378,8 @@ import { MenuItem as MenuItem3, MenuSection, MenuSeparator as MenuSeparator2 } f
|
|
|
1231
1378
|
import { Popover as Popover2 } from "@estiva-app/ui";
|
|
1232
1379
|
|
|
1233
1380
|
// src/ReferenceCards.ts
|
|
1234
|
-
import { Extension as
|
|
1235
|
-
import { NodeSelection, Plugin as
|
|
1381
|
+
import { Extension as Extension6 } from "@tiptap/core";
|
|
1382
|
+
import { NodeSelection, Plugin as Plugin5, PluginKey as PluginKey6 } from "@tiptap/pm/state";
|
|
1236
1383
|
import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
|
|
1237
1384
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
1238
1385
|
function layoutOf(raw) {
|
|
@@ -1261,7 +1408,7 @@ function referenceCardPlacements(doc) {
|
|
|
1261
1408
|
});
|
|
1262
1409
|
return out;
|
|
1263
1410
|
}
|
|
1264
|
-
var key2 = new
|
|
1411
|
+
var key2 = new PluginKey6("referenceCards");
|
|
1265
1412
|
function elementFor(storage, slotKey) {
|
|
1266
1413
|
let element = storage.elements.get(slotKey);
|
|
1267
1414
|
if (!element) {
|
|
@@ -1275,7 +1422,7 @@ function elementFor(storage, slotKey) {
|
|
|
1275
1422
|
function storageOf(editor) {
|
|
1276
1423
|
return editor.storage.referenceCards;
|
|
1277
1424
|
}
|
|
1278
|
-
var ReferenceCards =
|
|
1425
|
+
var ReferenceCards = Extension6.create({
|
|
1279
1426
|
name: "referenceCards",
|
|
1280
1427
|
addStorage() {
|
|
1281
1428
|
return { elements: /* @__PURE__ */ new Map() };
|
|
@@ -1295,7 +1442,7 @@ var ReferenceCards = Extension5.create({
|
|
|
1295
1442
|
)
|
|
1296
1443
|
);
|
|
1297
1444
|
return [
|
|
1298
|
-
new
|
|
1445
|
+
new Plugin5({
|
|
1299
1446
|
key: key2,
|
|
1300
1447
|
state: {
|
|
1301
1448
|
init: (_, state) => decorate(state.doc),
|
|
@@ -1374,7 +1521,7 @@ function setReferenceCardAt(editor, pos, layout) {
|
|
|
1374
1521
|
|
|
1375
1522
|
// src/ReferenceMenu.tsx
|
|
1376
1523
|
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1377
|
-
var key3 = new
|
|
1524
|
+
var key3 = new PluginKey7("referenceMenu");
|
|
1378
1525
|
var LAYOUTS = [
|
|
1379
1526
|
{ layout: "none", label: "None" },
|
|
1380
1527
|
{ layout: "compact", label: "Compact" },
|
|
@@ -1453,14 +1600,14 @@ function caretAfter(state, pos) {
|
|
|
1453
1600
|
const node = state.doc.nodeAt(pos);
|
|
1454
1601
|
return state.tr.setSelection(TextSelection2.create(state.doc, pos + (node?.nodeSize ?? 1)));
|
|
1455
1602
|
}
|
|
1456
|
-
var ReferenceMenu =
|
|
1603
|
+
var ReferenceMenu = Extension7.create({
|
|
1457
1604
|
name: "referenceMenu",
|
|
1458
1605
|
// Before the editor's own keys, so Enter on a highlighted row is the row's, not a new line.
|
|
1459
1606
|
priority: 1e3,
|
|
1460
1607
|
addProseMirrorPlugins() {
|
|
1461
1608
|
const editor = this.editor;
|
|
1462
1609
|
return [
|
|
1463
|
-
new
|
|
1610
|
+
new Plugin6({
|
|
1464
1611
|
key: key3,
|
|
1465
1612
|
state: {
|
|
1466
1613
|
init: () => null,
|
|
@@ -1642,7 +1789,7 @@ function claimsIn(doc) {
|
|
|
1642
1789
|
doc.descendants((node, pos, parent) => {
|
|
1643
1790
|
const fixed = node.type.name === "unknownBlock";
|
|
1644
1791
|
if (!fixed && !BLOCK_TYPE_SET.has(node.type.name)) return;
|
|
1645
|
-
if (parent?.type.name === "listItem" &&
|
|
1792
|
+
if (parent?.type.name === "listItem" && LISTS2.has(node.type.name)) return;
|
|
1646
1793
|
const id = node.attrs.blockId;
|
|
1647
1794
|
if (typeof id !== "string" || id === "") return;
|
|
1648
1795
|
const copies = claims.get(id);
|
|
@@ -1689,11 +1836,11 @@ function repeatedBlockIds(doc, before, mapping) {
|
|
|
1689
1836
|
}
|
|
1690
1837
|
var BLOCK_CONTAINERS = /* @__PURE__ */ new Set(["bulletList", "orderedList", "table", "tableRow", "tableHeader", "tableCell"]);
|
|
1691
1838
|
var FLATTENED = /* @__PURE__ */ new Set(["listItem", "blockquote"]);
|
|
1692
|
-
var
|
|
1839
|
+
var LISTS2 = /* @__PURE__ */ new Set(["bulletList", "orderedList"]);
|
|
1693
1840
|
function innerLists(item, pos) {
|
|
1694
1841
|
const out = [];
|
|
1695
1842
|
item.forEach((child, offset) => {
|
|
1696
|
-
if (
|
|
1843
|
+
if (LISTS2.has(child.type.name)) out.push({ pos: pos + 1 + offset, node: child });
|
|
1697
1844
|
});
|
|
1698
1845
|
return out;
|
|
1699
1846
|
}
|
|
@@ -1722,7 +1869,7 @@ function hiddenIds(doc) {
|
|
|
1722
1869
|
doc.descendants((node, pos) => {
|
|
1723
1870
|
if (!FLATTENED.has(node.type.name)) return;
|
|
1724
1871
|
node.forEach((child, offset) => {
|
|
1725
|
-
const inner = child.isTextblock || node.type.name === "listItem" &&
|
|
1872
|
+
const inner = child.isTextblock || node.type.name === "listItem" && LISTS2.has(child.type.name);
|
|
1726
1873
|
if (inner && idOf(child) !== void 0) out.push(pos + 1 + offset);
|
|
1727
1874
|
});
|
|
1728
1875
|
});
|
|
@@ -1762,7 +1909,7 @@ function bringsInBlocks(transactions) {
|
|
|
1762
1909
|
})
|
|
1763
1910
|
);
|
|
1764
1911
|
}
|
|
1765
|
-
var BlockId =
|
|
1912
|
+
var BlockId = Extension8.create({
|
|
1766
1913
|
name: "blockId",
|
|
1767
1914
|
onCreate() {
|
|
1768
1915
|
const { state, view } = this.editor;
|
|
@@ -1774,8 +1921,8 @@ var BlockId = Extension7.create({
|
|
|
1774
1921
|
},
|
|
1775
1922
|
addProseMirrorPlugins() {
|
|
1776
1923
|
return [
|
|
1777
|
-
new
|
|
1778
|
-
key: new
|
|
1924
|
+
new Plugin7({
|
|
1925
|
+
key: new PluginKey8("blockIdSplit"),
|
|
1779
1926
|
appendTransaction: (transactions, old, state) => {
|
|
1780
1927
|
if (!bringsInBlocks(transactions)) return null;
|
|
1781
1928
|
const mapping = new Mapping();
|
|
@@ -2000,7 +2147,7 @@ function listItemView(editor, node, view, getPos) {
|
|
|
2000
2147
|
}
|
|
2001
2148
|
};
|
|
2002
2149
|
}
|
|
2003
|
-
var Checklist =
|
|
2150
|
+
var Checklist = Extension8.create({
|
|
2004
2151
|
name: "checklist",
|
|
2005
2152
|
// Before the list item's own Enter and Backspace.
|
|
2006
2153
|
priority: 101,
|
|
@@ -2065,14 +2212,14 @@ var Checklist = Extension7.create({
|
|
|
2065
2212
|
addProseMirrorPlugins() {
|
|
2066
2213
|
const editor = this.editor;
|
|
2067
2214
|
return [
|
|
2068
|
-
new
|
|
2069
|
-
key: new
|
|
2215
|
+
new Plugin7({
|
|
2216
|
+
key: new PluginKey8("checklist"),
|
|
2070
2217
|
props: { nodeViews: { listItem: (node, view, getPos) => listItemView(editor, node, view, getPos) } }
|
|
2071
2218
|
})
|
|
2072
2219
|
];
|
|
2073
2220
|
}
|
|
2074
2221
|
});
|
|
2075
|
-
var Callout =
|
|
2222
|
+
var Callout = Extension8.create({
|
|
2076
2223
|
name: "callout",
|
|
2077
2224
|
addGlobalAttributes() {
|
|
2078
2225
|
return [
|
|
@@ -2103,6 +2250,7 @@ export {
|
|
|
2103
2250
|
KeptSelection,
|
|
2104
2251
|
LinkChips,
|
|
2105
2252
|
PastedImages,
|
|
2253
|
+
PastedMarkdown,
|
|
2106
2254
|
PersonMention,
|
|
2107
2255
|
ReferenceCards,
|
|
2108
2256
|
ReferenceMenu,
|
|
@@ -2144,6 +2292,9 @@ export {
|
|
|
2144
2292
|
setReferenceCardAt,
|
|
2145
2293
|
startBlockDrag,
|
|
2146
2294
|
suggestionPopup,
|
|
2295
|
+
turnIntoRows,
|
|
2296
|
+
turnListInto,
|
|
2297
|
+
turnQuoteInto,
|
|
2147
2298
|
typeTrigger,
|
|
2148
2299
|
urgentPubkeys,
|
|
2149
2300
|
useReferenceCards
|