@estiva-app/ui 0.65.3 → 0.67.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 +33 -0
- package/dist/BlockNodes.d.ts.map +1 -1
- package/dist/ComposerTriggers.d.ts +11 -2
- package/dist/ComposerTriggers.d.ts.map +1 -1
- package/dist/EditorTableControls.d.ts +38 -0
- package/dist/EditorTableControls.d.ts.map +1 -0
- package/dist/NavItem.d.ts +11 -1
- package/dist/NavItem.d.ts.map +1 -1
- package/dist/RichText.d.ts +13 -1
- package/dist/RichText.d.ts.map +1 -1
- package/dist/editor.d.ts +2 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +363 -18
- package/dist/editor.js.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +71 -23
- package/dist/index.js.map +3 -3
- package/package.json +2 -1
- package/registry.json +209 -4
- package/src/BlockNodes.mdx +32 -1
- package/src/BlockNodes.stories.tsx +29 -1
- package/src/BlockNodes.test.tsx +141 -2
- package/src/BlockNodes.tsx +227 -4
- package/src/ComposerTriggers.test.tsx +23 -0
- package/src/ComposerTriggers.tsx +61 -5
- package/src/EditorTableControls.mdx +78 -0
- package/src/EditorTableControls.stories.tsx +63 -0
- package/src/EditorTableControls.test.tsx +288 -0
- package/src/EditorTableControls.tsx +255 -0
- package/src/NavItem.mdx +10 -0
- package/src/NavItem.stories.tsx +23 -1
- package/src/NavItem.test.tsx +36 -0
- package/src/NavItem.tsx +40 -16
- package/src/RichText.mdx +12 -0
- package/src/RichText.stories.tsx +50 -0
- package/src/RichText.test.tsx +56 -0
- package/src/RichText.tsx +83 -10
- package/src/editor.ts +4 -0
- package/src/index.ts +1 -1
- package/src/registry/registry.test.ts +7 -7
- package/src/registry/siblings.test.ts +2 -2
- package/stories/Choosing.mdx +1 -0
package/dist/editor.js
CHANGED
|
@@ -580,6 +580,155 @@ function EditorBlockHandle({ editor, turnInto = [], actions, children, className
|
|
|
580
580
|
return /* @__PURE__ */ jsx3(BlockHandle, { editing, actions, className, children });
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
+
// src/EditorTableControls.tsx
|
|
584
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
585
|
+
import { TextSelection } from "@tiptap/pm/state";
|
|
586
|
+
import { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, deleteColumn, deleteRow, deleteTable, toggleHeader } from "@tiptap/pm/tables";
|
|
587
|
+
import { IconColumnInsertLeft, IconColumnInsertRight, IconGripHorizontal, IconGripVertical, IconPlus, IconRowInsertBottom, IconRowInsertTop, IconTrash as IconTrash2 } from "@tabler/icons-react";
|
|
588
|
+
import { cn } from "@estiva-app/ui";
|
|
589
|
+
import { IconButton } from "@estiva-app/ui";
|
|
590
|
+
|
|
591
|
+
// src/looks.ts
|
|
592
|
+
var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
|
|
593
|
+
|
|
594
|
+
// src/EditorTableControls.tsx
|
|
595
|
+
import { Menu, MenuItem as MenuItem2, MenuSeparator } from "@estiva-app/ui";
|
|
596
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
597
|
+
function inCell(editor, cell, command) {
|
|
598
|
+
const { view } = editor;
|
|
599
|
+
if (!view.dom.contains(cell)) return false;
|
|
600
|
+
const at = view.posAtDOM(cell, 0);
|
|
601
|
+
view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(at))));
|
|
602
|
+
const done = command(view.state, view.dispatch, view);
|
|
603
|
+
editor.commands.focus();
|
|
604
|
+
return done;
|
|
605
|
+
}
|
|
606
|
+
function isHeaderRow(row) {
|
|
607
|
+
return row.cells.length > 0 && Array.from(row.cells).every((c) => c.tagName === "TH");
|
|
608
|
+
}
|
|
609
|
+
function deleteTableRow(editor, cell) {
|
|
610
|
+
const row = cell.closest("tr");
|
|
611
|
+
const table = cell.closest("table");
|
|
612
|
+
if (!row || !table || table.rows.length <= 1) return inCell(editor, cell, deleteTable);
|
|
613
|
+
const header = isHeaderRow(row);
|
|
614
|
+
return inCell(editor, cell, header ? (state, dispatch, view) => deleteRow(state, dispatch) && (!view || toggleHeader("row")(view.state, view.dispatch)) : deleteRow);
|
|
615
|
+
}
|
|
616
|
+
function deleteTableColumn(editor, cell) {
|
|
617
|
+
const row = cell.closest("tr");
|
|
618
|
+
return inCell(editor, cell, row && row.cells.length > 1 ? deleteColumn : deleteTable);
|
|
619
|
+
}
|
|
620
|
+
function appendTableRow(editor, table) {
|
|
621
|
+
const last = table.rows[table.rows.length - 1]?.cells[0];
|
|
622
|
+
return !!last && inCell(editor, last, addRowAfter);
|
|
623
|
+
}
|
|
624
|
+
function appendTableColumn(editor, table) {
|
|
625
|
+
const first = table.rows[0];
|
|
626
|
+
const last = first?.cells[first.cells.length - 1];
|
|
627
|
+
return !!last && inCell(editor, last, addColumnAfter);
|
|
628
|
+
}
|
|
629
|
+
function place(frame, { table, row, cell }) {
|
|
630
|
+
const at = frame.getBoundingClientRect();
|
|
631
|
+
const t = table.getBoundingClientRect();
|
|
632
|
+
const r = row.getBoundingClientRect();
|
|
633
|
+
const c = cell.getBoundingClientRect();
|
|
634
|
+
return {
|
|
635
|
+
tableBox: { top: t.top - at.top, left: t.left - at.left, width: t.width, height: t.height },
|
|
636
|
+
rowBox: { top: r.top - at.top, height: r.height },
|
|
637
|
+
columnBox: { left: c.left - at.left, width: c.width }
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
var ICON = { size: 16, stroke: 1.5, className: "text-text-secondary" };
|
|
641
|
+
var keepFocus = (e) => e.preventDefault();
|
|
642
|
+
var HANDLE_BOX = cn(FLOATING_SURFACE_CLASSES, "shadow-none");
|
|
643
|
+
function EditorTableControls({ editor, children, className }) {
|
|
644
|
+
const frame = useRef3(null);
|
|
645
|
+
const [hovered, setHovered] = useState2(null);
|
|
646
|
+
const [menu, setMenu] = useState2(null);
|
|
647
|
+
const editable = !!editor && editor.isEditable;
|
|
648
|
+
const follow = useCallback2(
|
|
649
|
+
(target) => {
|
|
650
|
+
if (!editor || !frame.current || !(target instanceof Element)) return;
|
|
651
|
+
if (target.closest("[data-table-control]")) return;
|
|
652
|
+
const cell = target.closest("td, th");
|
|
653
|
+
const row = cell?.closest("tr");
|
|
654
|
+
const table = row?.closest("table");
|
|
655
|
+
if (!cell || !row || !table || !editor.view.dom.contains(table)) {
|
|
656
|
+
setHovered(null);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const next = { table, row, cell, ...place(frame.current, { table, row, cell }) };
|
|
660
|
+
setHovered((was) => was && was.cell === cell && JSON.stringify([was.tableBox, was.rowBox, was.columnBox]) === JSON.stringify([next.tableBox, next.rowBox, next.columnBox]) ? was : next);
|
|
661
|
+
},
|
|
662
|
+
[editor]
|
|
663
|
+
);
|
|
664
|
+
useEffect2(() => {
|
|
665
|
+
if (!editor) return;
|
|
666
|
+
const changed = ({ transaction }) => {
|
|
667
|
+
if (transaction.docChanged && !menu) setHovered(null);
|
|
668
|
+
};
|
|
669
|
+
editor.on("transaction", changed);
|
|
670
|
+
return () => {
|
|
671
|
+
editor.off("transaction", changed);
|
|
672
|
+
};
|
|
673
|
+
}, [editor, menu]);
|
|
674
|
+
const shown = editable ? hovered : null;
|
|
675
|
+
const run = (act) => () => {
|
|
676
|
+
if (editor) act();
|
|
677
|
+
setMenu(null);
|
|
678
|
+
setHovered(null);
|
|
679
|
+
};
|
|
680
|
+
return /* @__PURE__ */ jsxs3(
|
|
681
|
+
"div",
|
|
682
|
+
{
|
|
683
|
+
ref: frame,
|
|
684
|
+
className: cn("relative", className),
|
|
685
|
+
onMouseMove: (e) => {
|
|
686
|
+
if (!menu) follow(e.target);
|
|
687
|
+
},
|
|
688
|
+
onMouseLeave: () => {
|
|
689
|
+
if (!menu) setHovered(null);
|
|
690
|
+
},
|
|
691
|
+
children: [
|
|
692
|
+
children,
|
|
693
|
+
shown && editor && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
694
|
+
/* @__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(
|
|
695
|
+
Menu,
|
|
696
|
+
{
|
|
697
|
+
open: menu === "row",
|
|
698
|
+
onOpenChange: (open) => setMenu(open ? "row" : null),
|
|
699
|
+
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 }) }),
|
|
700
|
+
children: [
|
|
701
|
+
!isHeaderRow(shown.row) && /* @__PURE__ */ jsx4(MenuItem2, { label: "Insert above", leading: /* @__PURE__ */ jsx4(IconRowInsertTop, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowBefore)) }),
|
|
702
|
+
/* @__PURE__ */ jsx4(MenuItem2, { label: "Insert below", leading: /* @__PURE__ */ jsx4(IconRowInsertBottom, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowAfter)) }),
|
|
703
|
+
/* @__PURE__ */ jsx4(MenuSeparator, {}),
|
|
704
|
+
/* @__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)) }),
|
|
705
|
+
/* @__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)) })
|
|
706
|
+
]
|
|
707
|
+
}
|
|
708
|
+
) }),
|
|
709
|
+
/* @__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(
|
|
710
|
+
Menu,
|
|
711
|
+
{
|
|
712
|
+
open: menu === "column",
|
|
713
|
+
onOpenChange: (open) => setMenu(open ? "column" : null),
|
|
714
|
+
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 }) }),
|
|
715
|
+
children: [
|
|
716
|
+
/* @__PURE__ */ jsx4(MenuItem2, { label: "Insert left", leading: /* @__PURE__ */ jsx4(IconColumnInsertLeft, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnBefore)) }),
|
|
717
|
+
/* @__PURE__ */ jsx4(MenuItem2, { label: "Insert right", leading: /* @__PURE__ */ jsx4(IconColumnInsertRight, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnAfter)) }),
|
|
718
|
+
/* @__PURE__ */ jsx4(MenuSeparator, {}),
|
|
719
|
+
/* @__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)) }),
|
|
720
|
+
/* @__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)) })
|
|
721
|
+
]
|
|
722
|
+
}
|
|
723
|
+
) }),
|
|
724
|
+
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 }) }) }),
|
|
725
|
+
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 }) }) })
|
|
726
|
+
] })
|
|
727
|
+
]
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
583
732
|
// src/ComposerTriggers.tsx
|
|
584
733
|
import { Extension as Extension3 } from "@tiptap/core";
|
|
585
734
|
import { PluginKey as PluginKey3 } from "@tiptap/pm/state";
|
|
@@ -589,7 +738,7 @@ import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
|
|
|
589
738
|
import { Avatar } from "@estiva-app/ui";
|
|
590
739
|
import { EnterHint } from "@estiva-app/ui";
|
|
591
740
|
import { inlineChipClassName } from "@estiva-app/ui";
|
|
592
|
-
import { jsx as
|
|
741
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
593
742
|
var PEOPLE_LIMIT = 6;
|
|
594
743
|
function filterPeople(people, query) {
|
|
595
744
|
const q = query.toLowerCase();
|
|
@@ -607,8 +756,8 @@ function peopleMenu(urgent) {
|
|
|
607
756
|
row: (person) => ({
|
|
608
757
|
label: person.name,
|
|
609
758
|
description: person.description,
|
|
610
|
-
leading: person.avatar ?? /* @__PURE__ */
|
|
611
|
-
hint: /* @__PURE__ */
|
|
759
|
+
leading: person.avatar ?? /* @__PURE__ */ jsx5(Avatar, { size: 32, alt: person.name }),
|
|
760
|
+
hint: /* @__PURE__ */ jsx5(EnterHint, {}),
|
|
612
761
|
size: "tall"
|
|
613
762
|
})
|
|
614
763
|
};
|
|
@@ -735,9 +884,9 @@ var CaptionedReference = Mention.extend({
|
|
|
735
884
|
addNodeView() {
|
|
736
885
|
const { icon, iconFor } = this.options;
|
|
737
886
|
return ReactNodeViewRenderer(
|
|
738
|
-
({ node }) => /* @__PURE__ */
|
|
739
|
-
/* @__PURE__ */
|
|
740
|
-
/* @__PURE__ */
|
|
887
|
+
({ node }) => /* @__PURE__ */ jsxs4(NodeViewWrapper, { as: "span", className: inlineChipClassName("neutral", "max-w-[24ch] cursor-default"), children: [
|
|
888
|
+
/* @__PURE__ */ jsx5("span", { className: "flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary", children: iconFor?.(node.attrs.uri ?? null) ?? icon }),
|
|
889
|
+
/* @__PURE__ */ jsx5("span", { className: "truncate", children: captionOf(node.attrs.label, node.attrs.snippet) })
|
|
741
890
|
] }),
|
|
742
891
|
{ as: "span" }
|
|
743
892
|
);
|
|
@@ -759,7 +908,7 @@ function referenceMenu({ ariaLabel, sectionLabel, icon, iconFor }) {
|
|
|
759
908
|
return groups;
|
|
760
909
|
},
|
|
761
910
|
itemKey: (item) => `reference-${item.id}`,
|
|
762
|
-
row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */
|
|
911
|
+
row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */ jsx5(EnterHint, {}) })
|
|
763
912
|
};
|
|
764
913
|
}
|
|
765
914
|
var listed = (options, query) => options.sections ? options.sections(query).flatMap((section) => section.items.map((item) => ({ ...item, section: section.label }))) : filterReferences(options.items(), query);
|
|
@@ -793,6 +942,7 @@ var ReferenceTrigger = Extension3.create({
|
|
|
793
942
|
];
|
|
794
943
|
}
|
|
795
944
|
});
|
|
945
|
+
var offers = (editor, command) => !command.needs || command.needs.every((name) => editor.extensionManager.extensions.some((extension) => extension.name === name));
|
|
796
946
|
var FORMATS = [
|
|
797
947
|
{ id: "paragraph", label: "Text" },
|
|
798
948
|
{ id: "heading", label: "Heading", trigger: "#" },
|
|
@@ -802,11 +952,16 @@ var FORMATS = [
|
|
|
802
952
|
{ id: "bulletList", label: "Bulleted list", trigger: "-" },
|
|
803
953
|
{ id: "orderedList", label: "Numbered list", trigger: "1." }
|
|
804
954
|
];
|
|
955
|
+
var DOCUMENT_FORMATS = [
|
|
956
|
+
{ id: "checklist", label: "Checklist", trigger: "[]" },
|
|
957
|
+
{ id: "note", label: "Note" },
|
|
958
|
+
{ id: "warning", label: "Warning" }
|
|
959
|
+
];
|
|
805
960
|
function applyFormat(editor, id, code = "inline") {
|
|
806
961
|
const chain = editor.chain().focus();
|
|
807
962
|
const run = (command, ...args) => {
|
|
808
|
-
const
|
|
809
|
-
return
|
|
963
|
+
const named2 = chain[command];
|
|
964
|
+
return named2 ? named2(...args).run() : false;
|
|
810
965
|
};
|
|
811
966
|
switch (id) {
|
|
812
967
|
case "paragraph":
|
|
@@ -823,17 +978,38 @@ function applyFormat(editor, id, code = "inline") {
|
|
|
823
978
|
return run("toggleOrderedList");
|
|
824
979
|
case "code":
|
|
825
980
|
return run(code === "block" ? "toggleCodeBlock" : "toggleCode");
|
|
981
|
+
case "checklist": {
|
|
982
|
+
if (!editor.isActive("listItem") && !run("toggleBulletList")) return false;
|
|
983
|
+
if (typeof editor.getAttributes("listItem").checked === "boolean") return true;
|
|
984
|
+
return editor.chain().updateAttributes("listItem", { checked: false }).run();
|
|
985
|
+
}
|
|
986
|
+
case "note":
|
|
987
|
+
case "warning": {
|
|
988
|
+
if (!editor.isActive("blockquote") && !run("toggleBlockquote")) return false;
|
|
989
|
+
return editor.chain().updateAttributes("blockquote", { tone: id }).run();
|
|
990
|
+
}
|
|
826
991
|
default:
|
|
827
992
|
return false;
|
|
828
993
|
}
|
|
829
994
|
}
|
|
995
|
+
var KEYWORDS = {
|
|
996
|
+
checklist: ["todo", "task"],
|
|
997
|
+
note: ["callout"],
|
|
998
|
+
warning: ["callout"]
|
|
999
|
+
};
|
|
1000
|
+
var NEEDS = {
|
|
1001
|
+
checklist: ["checklist"],
|
|
1002
|
+
note: ["callout"],
|
|
1003
|
+
warning: ["callout"]
|
|
1004
|
+
};
|
|
830
1005
|
function formatSection(code = "inline") {
|
|
831
1006
|
return {
|
|
832
1007
|
label: "Format",
|
|
833
|
-
commands: FORMATS.map((format) => ({
|
|
1008
|
+
commands: (code === "block" ? [...FORMATS, ...DOCUMENT_FORMATS] : FORMATS).map((format) => ({
|
|
834
1009
|
key: `format-${format.id}`,
|
|
835
1010
|
row: { label: format.label, shortcut: code === "block" && format.blockTrigger || format.trigger },
|
|
836
|
-
keywords: [format.id],
|
|
1011
|
+
keywords: [format.id, ...KEYWORDS[format.id] ?? []],
|
|
1012
|
+
...NEEDS[format.id] ? { needs: NEEDS[format.id] } : {},
|
|
837
1013
|
run: (editor) => {
|
|
838
1014
|
applyFormat(editor, format.id, code);
|
|
839
1015
|
}
|
|
@@ -865,7 +1041,9 @@ var SlashCommands = Extension3.create({
|
|
|
865
1041
|
char: "/",
|
|
866
1042
|
startOfLine: false,
|
|
867
1043
|
pluginKey: new PluginKey3("slashCommands"),
|
|
868
|
-
items: ({ query }) => filterSlashSections(sections, query).flatMap(
|
|
1044
|
+
items: ({ query, editor }) => filterSlashSections(sections, query).flatMap(
|
|
1045
|
+
(section, at) => section.commands.filter((command) => offers(editor, command)).map((command) => ({ ...command, section: at }))
|
|
1046
|
+
),
|
|
869
1047
|
command: ({ editor, range, props }) => {
|
|
870
1048
|
editor.chain().focus().deleteRange(range).run();
|
|
871
1049
|
props.run(editor);
|
|
@@ -884,11 +1062,14 @@ var SlashCommands = Extension3.create({
|
|
|
884
1062
|
});
|
|
885
1063
|
|
|
886
1064
|
// src/BlockNodes.tsx
|
|
887
|
-
import { Extension as Extension4, Node as Node2 } from "@tiptap/core";
|
|
1065
|
+
import { Extension as Extension4, InputRule, Node as Node2 } from "@tiptap/core";
|
|
1066
|
+
import { DOMSerializer } from "@tiptap/pm/model";
|
|
888
1067
|
import { Plugin as Plugin3, PluginKey as PluginKey4 } from "@tiptap/pm/state";
|
|
889
1068
|
import { Mapping, ReplaceAroundStep } from "@tiptap/pm/transform";
|
|
890
|
-
import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
|
|
891
|
-
import {
|
|
1069
|
+
import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2, ReactRenderer as ReactRenderer2 } from "@tiptap/react";
|
|
1070
|
+
import { Checkbox } from "@estiva-app/ui";
|
|
1071
|
+
import { calloutLook } from "@estiva-app/ui";
|
|
1072
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
892
1073
|
var BLOCK_TYPES = [
|
|
893
1074
|
"paragraph",
|
|
894
1075
|
"heading",
|
|
@@ -1118,7 +1299,7 @@ var ReferenceNode = Node2.create({
|
|
|
1118
1299
|
addNodeView() {
|
|
1119
1300
|
const View = this.options.view;
|
|
1120
1301
|
if (!View) return null;
|
|
1121
|
-
return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */
|
|
1302
|
+
return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx6(NodeViewWrapper2, { as: "span", className: "inline", children: /* @__PURE__ */ jsx6(View, { uri: String(node.attrs.uri ?? "") }) }));
|
|
1122
1303
|
}
|
|
1123
1304
|
});
|
|
1124
1305
|
var UnknownBlock = Node2.create({
|
|
@@ -1146,7 +1327,7 @@ var UnknownBlock = Node2.create({
|
|
|
1146
1327
|
function UnknownBlockView({ node }) {
|
|
1147
1328
|
const source = node.attrs.source;
|
|
1148
1329
|
const text = String(node.attrs.text ?? "");
|
|
1149
|
-
return /* @__PURE__ */
|
|
1330
|
+
return /* @__PURE__ */ jsx6(
|
|
1150
1331
|
NodeViewWrapper2,
|
|
1151
1332
|
{
|
|
1152
1333
|
"data-block-id": source?.id,
|
|
@@ -1186,15 +1367,174 @@ var AttachmentNode = Node2.create({
|
|
|
1186
1367
|
addNodeView() {
|
|
1187
1368
|
const View = this.options.view;
|
|
1188
1369
|
if (!View) return null;
|
|
1189
|
-
return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */
|
|
1370
|
+
return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx6(NodeViewWrapper2, { className: "my-2", children: /* @__PURE__ */ jsx6(View, { attrs: node.attrs }) }));
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
var TICK = "checklistTick";
|
|
1374
|
+
function isChecklistTick(transaction) {
|
|
1375
|
+
return transaction.getMeta(TICK) === true;
|
|
1376
|
+
}
|
|
1377
|
+
function named(chain, command, ...args) {
|
|
1378
|
+
const own = chain[command];
|
|
1379
|
+
return own ? own(...args) : chain;
|
|
1380
|
+
}
|
|
1381
|
+
function itemAtCaret(state) {
|
|
1382
|
+
const { $from } = state.selection;
|
|
1383
|
+
if ($from.depth < 2) return null;
|
|
1384
|
+
const item = $from.node(-1);
|
|
1385
|
+
if (item.type.name !== "listItem" || $from.index(-1) !== 0) return null;
|
|
1386
|
+
return { node: item, pos: $from.before(-1) };
|
|
1387
|
+
}
|
|
1388
|
+
var sameAttrs = (a, b) => Object.keys({ ...a.attrs, ...b.attrs }).every((key) => a.attrs[key] === b.attrs[key]);
|
|
1389
|
+
function listItemView(editor, node, view, getPos) {
|
|
1390
|
+
const toDOM = node.type.spec.toDOM;
|
|
1391
|
+
const { dom, contentDOM } = toDOM ? DOMSerializer.renderSpec(document, toDOM(node)) : { dom: document.createElement("li"), contentDOM: void 0 };
|
|
1392
|
+
const checked = node.attrs.checked;
|
|
1393
|
+
if (typeof checked !== "boolean") return { dom, contentDOM: contentDOM ?? dom, update: (next) => next.type === node.type && sameAttrs(next, node) };
|
|
1394
|
+
const li = dom;
|
|
1395
|
+
const box = document.createElement("span");
|
|
1396
|
+
box.contentEditable = "false";
|
|
1397
|
+
box.setAttribute("data-checklist-box", "");
|
|
1398
|
+
const body = document.createElement("div");
|
|
1399
|
+
li.replaceChildren(box, body);
|
|
1400
|
+
const tick = (next) => {
|
|
1401
|
+
const pos = getPos();
|
|
1402
|
+
if (pos === void 0 || !view.editable) return;
|
|
1403
|
+
view.dispatch(view.state.tr.setNodeAttribute(pos, "checked", next).setMeta(TICK, true));
|
|
1404
|
+
};
|
|
1405
|
+
let words = node.firstChild?.textContent.trim() || "Item";
|
|
1406
|
+
const props = () => ({ checked, onChange: view.editable ? tick : void 0, "aria-label": words });
|
|
1407
|
+
const renderer = new ReactRenderer2(Checkbox, { editor, props: props(), as: "span" });
|
|
1408
|
+
box.appendChild(renderer.element);
|
|
1409
|
+
let drawn = "";
|
|
1410
|
+
const sync = () => {
|
|
1411
|
+
const now = `${view.editable}:${words}`;
|
|
1412
|
+
if (now === drawn) return;
|
|
1413
|
+
drawn = now;
|
|
1414
|
+
renderer.updateProps(props());
|
|
1415
|
+
for (const [name, value] of Object.entries({ role: "checkbox", "aria-checked": String(checked), "aria-readonly": "true", "aria-label": words })) {
|
|
1416
|
+
if (view.editable) box.removeAttribute(name);
|
|
1417
|
+
else box.setAttribute(name, value);
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
sync();
|
|
1421
|
+
editor.on("update", sync);
|
|
1422
|
+
return {
|
|
1423
|
+
dom: li,
|
|
1424
|
+
contentDOM: body,
|
|
1425
|
+
update: (next) => {
|
|
1426
|
+
if (next.type !== node.type || !sameAttrs(next, node)) return false;
|
|
1427
|
+
words = next.firstChild?.textContent.trim() || "Item";
|
|
1428
|
+
sync();
|
|
1429
|
+
return true;
|
|
1430
|
+
},
|
|
1431
|
+
stopEvent: (event) => box.contains(event.target),
|
|
1432
|
+
ignoreMutation: (mutation) => box.contains(mutation.target),
|
|
1433
|
+
destroy: () => {
|
|
1434
|
+
editor.off("update", sync);
|
|
1435
|
+
renderer.destroy();
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
var Checklist = Extension4.create({
|
|
1440
|
+
name: "checklist",
|
|
1441
|
+
// Before the list item's own Enter and Backspace.
|
|
1442
|
+
priority: 101,
|
|
1443
|
+
addGlobalAttributes() {
|
|
1444
|
+
return [
|
|
1445
|
+
{
|
|
1446
|
+
types: ["listItem"],
|
|
1447
|
+
attributes: {
|
|
1448
|
+
checked: {
|
|
1449
|
+
default: null,
|
|
1450
|
+
keepOnSplit: false,
|
|
1451
|
+
parseHTML: (element) => {
|
|
1452
|
+
const value = element.getAttribute("data-checked");
|
|
1453
|
+
return value === "true" ? true : value === "false" ? false : null;
|
|
1454
|
+
},
|
|
1455
|
+
renderHTML: (attributes) => typeof attributes.checked === "boolean" ? { "data-checked": String(attributes.checked) } : {}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
];
|
|
1460
|
+
},
|
|
1461
|
+
addInputRules() {
|
|
1462
|
+
return [
|
|
1463
|
+
new InputRule({
|
|
1464
|
+
find: /^\[([ xX]?)\]\s$/,
|
|
1465
|
+
handler: ({ state, range, match, chain }) => {
|
|
1466
|
+
const checked = match[1].toLowerCase() === "x";
|
|
1467
|
+
const inList = itemAtCaret(state) !== null;
|
|
1468
|
+
if (!inList && !chain().toggleBulletList) return null;
|
|
1469
|
+
const deleted = chain().deleteRange(range);
|
|
1470
|
+
const listed2 = inList ? deleted : named(deleted, "toggleBulletList");
|
|
1471
|
+
listed2.updateAttributes("listItem", { checked }).run();
|
|
1472
|
+
}
|
|
1473
|
+
})
|
|
1474
|
+
];
|
|
1475
|
+
},
|
|
1476
|
+
addKeyboardShortcuts() {
|
|
1477
|
+
return {
|
|
1478
|
+
Enter: () => {
|
|
1479
|
+
const { state } = this.editor;
|
|
1480
|
+
const item = itemAtCaret(state);
|
|
1481
|
+
const { $from, empty } = state.selection;
|
|
1482
|
+
if (!item || typeof item.node.attrs.checked !== "boolean") return false;
|
|
1483
|
+
if (!empty) return this.editor.chain().deleteSelection().splitListItem("listItem", { checked: false }).run();
|
|
1484
|
+
if ($from.parent.content.size === 0) return false;
|
|
1485
|
+
if ($from.parentOffset > 0) return this.editor.commands.splitListItem("listItem", { checked: false });
|
|
1486
|
+
return this.editor.chain().splitListItem("listItem", { checked: item.node.attrs.checked }).command(({ tr }) => {
|
|
1487
|
+
tr.setNodeAttribute(tr.mapping.map(item.pos), "checked", false);
|
|
1488
|
+
return true;
|
|
1489
|
+
}).run();
|
|
1490
|
+
},
|
|
1491
|
+
Backspace: () => {
|
|
1492
|
+
const { state } = this.editor;
|
|
1493
|
+
const item = itemAtCaret(state);
|
|
1494
|
+
const { $from, empty } = state.selection;
|
|
1495
|
+
if (!item || !empty || $from.parentOffset > 0 || typeof item.node.attrs.checked !== "boolean") return false;
|
|
1496
|
+
this.editor.view.dispatch(state.tr.setNodeAttribute(item.pos, "checked", null));
|
|
1497
|
+
return true;
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
},
|
|
1501
|
+
addProseMirrorPlugins() {
|
|
1502
|
+
const editor = this.editor;
|
|
1503
|
+
return [
|
|
1504
|
+
new Plugin3({
|
|
1505
|
+
key: new PluginKey4("checklist"),
|
|
1506
|
+
props: { nodeViews: { listItem: (node, view, getPos) => listItemView(editor, node, view, getPos) } }
|
|
1507
|
+
})
|
|
1508
|
+
];
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
var Callout = Extension4.create({
|
|
1512
|
+
name: "callout",
|
|
1513
|
+
addGlobalAttributes() {
|
|
1514
|
+
return [
|
|
1515
|
+
{
|
|
1516
|
+
types: ["blockquote"],
|
|
1517
|
+
attributes: {
|
|
1518
|
+
tone: {
|
|
1519
|
+
default: null,
|
|
1520
|
+
parseHTML: (element) => element.getAttribute("data-tone"),
|
|
1521
|
+
// As `RichText` draws it: the tone as written, and the look it is drawn in.
|
|
1522
|
+
renderHTML: (attributes) => typeof attributes.tone === "string" ? { "data-tone": attributes.tone, "data-callout": calloutLook(attributes.tone) } : {}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
];
|
|
1190
1527
|
}
|
|
1191
1528
|
});
|
|
1192
1529
|
export {
|
|
1193
1530
|
AttachmentNode,
|
|
1194
1531
|
BlockId,
|
|
1532
|
+
Callout,
|
|
1195
1533
|
CaptionedReference,
|
|
1534
|
+
Checklist,
|
|
1196
1535
|
CodeBlockColours,
|
|
1197
1536
|
EditorBlockHandle,
|
|
1537
|
+
EditorTableControls,
|
|
1198
1538
|
FORMATS,
|
|
1199
1539
|
KeptSelection,
|
|
1200
1540
|
PastedImages,
|
|
@@ -1205,14 +1545,19 @@ export {
|
|
|
1205
1545
|
SlashCommands,
|
|
1206
1546
|
UnknownBlock,
|
|
1207
1547
|
UrgentPersonMention,
|
|
1548
|
+
appendTableColumn,
|
|
1549
|
+
appendTableRow,
|
|
1208
1550
|
applyFormat,
|
|
1209
1551
|
blockDropCursor,
|
|
1210
1552
|
deleteBlock,
|
|
1553
|
+
deleteTableColumn,
|
|
1554
|
+
deleteTableRow,
|
|
1211
1555
|
duplicateBlock,
|
|
1212
1556
|
filterPeople,
|
|
1213
1557
|
filterReferences,
|
|
1214
1558
|
filterSlashSections,
|
|
1215
1559
|
formatSection,
|
|
1560
|
+
isChecklistTick,
|
|
1216
1561
|
isSuggestionActive,
|
|
1217
1562
|
isSuggestionOpen,
|
|
1218
1563
|
moveBlock,
|