@ai-matrx/rich-editor 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/core/markdown-format.js +3 -3
- package/dist/core/text-insertion.d.ts +14 -0
- package/dist/core/text-insertion.js +55 -0
- package/dist/editor/RichEditor.d.ts +4 -0
- package/dist/editor/RichEditor.js +14 -0
- package/dist/editor/RichEditorContext.d.ts +16 -0
- package/dist/editor/RichEditorContext.js +17 -0
- package/dist/editor/RichEditorImpl.d.ts +137 -0
- package/dist/editor/RichEditorImpl.js +714 -0
- package/dist/editor/caretInsert.d.ts +11 -0
- package/dist/editor/caretInsert.js +9 -0
- package/dist/editor/rich-editor.css +181 -0
- package/dist/format/format-actions.d.ts +10 -0
- package/dist/format/format-actions.js +78 -0
- package/dist/format/useTextareaFormatting.d.ts +4 -0
- package/dist/format/useTextareaFormatting.js +37 -0
- package/dist/in-place/EditInPlace.d.ts +38 -0
- package/dist/in-place/EditInPlace.js +72 -0
- package/dist/in-place/EditInPlaceText.d.ts +18 -0
- package/dist/in-place/EditInPlaceText.js +159 -0
- package/dist/in-place/InPlaceEditor.d.ts +41 -0
- package/dist/in-place/InPlaceEditor.js +169 -0
- package/dist/islands/IslandCodeEditor.d.ts +12 -0
- package/dist/islands/IslandCodeEditor.js +82 -0
- package/dist/islands/IslandPreview.d.ts +6 -0
- package/dist/islands/IslandPreview.js +31 -0
- package/dist/islands/island-meta.d.ts +28 -0
- package/dist/islands/island-meta.js +176 -0
- package/dist/panels/FindReplacePanel.d.ts +10 -0
- package/dist/panels/FindReplacePanel.js +118 -0
- package/dist/panels/KindPicker.d.ts +4 -0
- package/dist/panels/KindPicker.js +95 -0
- package/dist/panels/OutlinePanel.d.ts +6 -0
- package/dist/panels/OutlinePanel.js +60 -0
- package/dist/panels/ShortcutsDialog.d.ts +4 -0
- package/dist/panels/ShortcutsDialog.js +24 -0
- package/dist/source/SourceEditor.d.ts +28 -0
- package/dist/source/SourceEditor.js +429 -0
- package/dist/source/cm-theme.d.ts +4 -0
- package/dist/source/cm-theme.js +71 -0
- package/dist/source/live-preview.d.ts +30 -0
- package/dist/source/live-preview.js +304 -0
- package/dist/source/markdown-language.d.ts +2 -0
- package/dist/source/markdown-language.js +7 -0
- package/dist/visual/BlockHandle.d.ts +6 -0
- package/dist/visual/BlockHandle.js +173 -0
- package/dist/visual/VisualEditor.d.ts +56 -0
- package/dist/visual/VisualEditor.js +312 -0
- package/dist/visual/auto-edit.d.ts +3 -0
- package/dist/visual/auto-edit.js +17 -0
- package/dist/visual/context-menu-caret.d.ts +10 -0
- package/dist/visual/context-menu-caret.js +42 -0
- package/dist/visual/decorations.d.ts +9 -0
- package/dist/visual/decorations.js +131 -0
- package/dist/visual/format-actions.d.ts +20 -0
- package/dist/visual/format-actions.js +128 -0
- package/dist/visual/menus/SuggestionMenu.d.ts +37 -0
- package/dist/visual/menus/SuggestionMenu.js +121 -0
- package/dist/visual/nodes/CalloutView.d.ts +2 -0
- package/dist/visual/nodes/CalloutView.js +45 -0
- package/dist/visual/nodes/InlineIslandView.d.ts +2 -0
- package/dist/visual/nodes/InlineIslandView.js +174 -0
- package/dist/visual/nodes/IslandBlockView.d.ts +2 -0
- package/dist/visual/nodes/IslandBlockView.js +206 -0
- package/dist/visual/nodes/SourceLockedView.d.ts +2 -0
- package/dist/visual/nodes/SourceLockedView.js +62 -0
- package/dist/visual/shortcut-handlers.d.ts +27 -0
- package/dist/visual/shortcut-handlers.js +43 -0
- package/dist/visual/slash-items.d.ts +18 -0
- package/dist/visual/slash-items.js +152 -0
- package/dist/visual/visual-extensions.d.ts +10 -0
- package/dist/visual/visual-extensions.js +246 -0
- package/dist/visual/visual-format-target.d.ts +9 -0
- package/dist/visual/visual-format-target.js +43 -0
- package/package.json +42 -4
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ComponentType } from "react";
|
|
2
|
+
import type { SuggestionKeyDownProps, SuggestionProps } from "@tiptap/suggestion";
|
|
3
|
+
export interface MenuItem {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
group?: string;
|
|
8
|
+
icon?: ComponentType<{
|
|
9
|
+
className?: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** Right-aligned hint (a shortcut, a type). */
|
|
12
|
+
hint?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SuggestionListHandle {
|
|
15
|
+
onKeyDown: (props: SuggestionKeyDownProps) => boolean;
|
|
16
|
+
}
|
|
17
|
+
interface SuggestionListProps {
|
|
18
|
+
items: MenuItem[];
|
|
19
|
+
command: (item: MenuItem) => void;
|
|
20
|
+
emptyText: string;
|
|
21
|
+
label: string;
|
|
22
|
+
/** With nothing to pick, Enter still belongs to the menu (a half-typed `{{name`). */
|
|
23
|
+
holdEnterWhenEmpty: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare const SuggestionList: import("react").ForwardRefExoticComponent<SuggestionListProps & import("react").RefAttributes<SuggestionListHandle>>;
|
|
26
|
+
/** The suggestion plugin's render() — one popup per active menu. */
|
|
27
|
+
export declare function suggestionRenderer(options: {
|
|
28
|
+
emptyText: string;
|
|
29
|
+
label: string;
|
|
30
|
+
holdEnterWhenEmpty?: boolean;
|
|
31
|
+
}): () => {
|
|
32
|
+
onStart: (props: SuggestionProps<MenuItem, MenuItem>) => void;
|
|
33
|
+
onUpdate: (props: SuggestionProps<MenuItem, MenuItem>) => void;
|
|
34
|
+
onKeyDown: (props: SuggestionKeyDownProps) => boolean;
|
|
35
|
+
onExit: () => void;
|
|
36
|
+
};
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
4
|
+
import { ReactRenderer } from "@tiptap/react";
|
|
5
|
+
import { cn } from "@ai-matrx/design-system";
|
|
6
|
+
const SuggestionList = forwardRef(
|
|
7
|
+
function SuggestionList2({ items, command, emptyText, label, holdEnterWhenEmpty }, ref) {
|
|
8
|
+
const [active, setActive] = useState(0);
|
|
9
|
+
const list = useRef(null);
|
|
10
|
+
useEffect(() => setActive(0), [items]);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
list.current?.querySelector(`[data-index="${active}"]`)?.scrollIntoView({ block: "nearest" });
|
|
13
|
+
}, [active]);
|
|
14
|
+
useImperativeHandle(ref, () => ({
|
|
15
|
+
onKeyDown: ({ event }) => {
|
|
16
|
+
if (!items.length) return holdEnterWhenEmpty && event.key === "Enter";
|
|
17
|
+
if (event.key === "ArrowDown") {
|
|
18
|
+
setActive((index) => (index + 1) % items.length);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (event.key === "ArrowUp") {
|
|
22
|
+
setActive((index) => (index + items.length - 1) % items.length);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (event.key === "Enter" || event.key === "Tab") {
|
|
26
|
+
const item = items[active];
|
|
27
|
+
if (item) command(item);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}));
|
|
33
|
+
let lastGroup;
|
|
34
|
+
return /* @__PURE__ */ jsxs(
|
|
35
|
+
"div",
|
|
36
|
+
{
|
|
37
|
+
ref: list,
|
|
38
|
+
role: "listbox",
|
|
39
|
+
"aria-label": label,
|
|
40
|
+
className: "matrx-touch-targets z-50 max-h-[min(22rem,60dvh)] w-72 overflow-y-auto rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg",
|
|
41
|
+
children: [
|
|
42
|
+
items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyText }),
|
|
43
|
+
items.map((item, index) => {
|
|
44
|
+
const Icon = item.icon;
|
|
45
|
+
const header = item.group && item.group !== lastGroup ? item.group : null;
|
|
46
|
+
lastGroup = item.group;
|
|
47
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
48
|
+
header && /* @__PURE__ */ jsx("div", { className: "px-2 pb-0.5 pt-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: header }),
|
|
49
|
+
/* @__PURE__ */ jsxs(
|
|
50
|
+
"button",
|
|
51
|
+
{
|
|
52
|
+
type: "button",
|
|
53
|
+
role: "option",
|
|
54
|
+
"aria-selected": index === active,
|
|
55
|
+
"data-index": index,
|
|
56
|
+
className: cn(
|
|
57
|
+
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm",
|
|
58
|
+
index === active ? "bg-accent text-accent-foreground" : "hover:bg-muted"
|
|
59
|
+
),
|
|
60
|
+
onMouseEnter: () => setActive(index),
|
|
61
|
+
onMouseDown: (event) => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
command(item);
|
|
64
|
+
},
|
|
65
|
+
children: [
|
|
66
|
+
Icon && /* @__PURE__ */ jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded border border-border bg-background", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
|
|
67
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
68
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate font-medium", children: item.title }),
|
|
69
|
+
item.description && /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", children: item.description })
|
|
70
|
+
] }),
|
|
71
|
+
item.hint && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: item.hint })
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
] }, item.id);
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
function suggestionRenderer(options) {
|
|
83
|
+
return () => {
|
|
84
|
+
let renderer = null;
|
|
85
|
+
let unmount = null;
|
|
86
|
+
const toProps = (props) => ({
|
|
87
|
+
items: props.items,
|
|
88
|
+
command: props.command,
|
|
89
|
+
emptyText: options.emptyText,
|
|
90
|
+
label: options.label,
|
|
91
|
+
holdEnterWhenEmpty: options.holdEnterWhenEmpty ?? true
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
onStart: (props) => {
|
|
95
|
+
renderer = new ReactRenderer(SuggestionList, { props: toProps(props), editor: props.editor });
|
|
96
|
+
unmount = props.mount(renderer.element);
|
|
97
|
+
},
|
|
98
|
+
onUpdate: (props) => {
|
|
99
|
+
renderer?.updateProps(toProps(props));
|
|
100
|
+
},
|
|
101
|
+
onKeyDown: (props) => {
|
|
102
|
+
if (props.event.key === "Escape") {
|
|
103
|
+
unmount?.();
|
|
104
|
+
unmount = null;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return renderer?.ref?.onKeyDown(props) ?? false;
|
|
108
|
+
},
|
|
109
|
+
onExit: () => {
|
|
110
|
+
unmount?.();
|
|
111
|
+
unmount = null;
|
|
112
|
+
renderer?.destroy();
|
|
113
|
+
renderer = null;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
SuggestionList,
|
|
120
|
+
suggestionRenderer
|
|
121
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { NodeViewContent, NodeViewWrapper } from "@tiptap/react";
|
|
4
|
+
import { AlertOctagon, AlertTriangle, Info, Lightbulb, MessageSquareWarning } from "lucide-react";
|
|
5
|
+
import { cn } from "@ai-matrx/design-system";
|
|
6
|
+
import { CALLOUT_TYPES } from "../../core/commands.js";
|
|
7
|
+
const CALLOUT_STYLE = {
|
|
8
|
+
NOTE: { icon: Info, label: "Note", className: "border-sky-500/60 bg-sky-500/5 [&_.rich-callout-head]:text-sky-700 dark:[&_.rich-callout-head]:text-sky-300" },
|
|
9
|
+
TIP: { icon: Lightbulb, label: "Tip", className: "border-emerald-500/60 bg-emerald-500/5 [&_.rich-callout-head]:text-emerald-700 dark:[&_.rich-callout-head]:text-emerald-300" },
|
|
10
|
+
IMPORTANT: { icon: MessageSquareWarning, label: "Important", className: "border-violet-500/60 bg-violet-500/5 [&_.rich-callout-head]:text-violet-700 dark:[&_.rich-callout-head]:text-violet-300" },
|
|
11
|
+
WARNING: { icon: AlertTriangle, label: "Warning", className: "border-amber-500/60 bg-amber-500/5 [&_.rich-callout-head]:text-amber-700 dark:[&_.rich-callout-head]:text-amber-300" },
|
|
12
|
+
CAUTION: { icon: AlertOctagon, label: "Caution", className: "border-red-500/60 bg-red-500/5 [&_.rich-callout-head]:text-red-700 dark:[&_.rich-callout-head]:text-red-300" }
|
|
13
|
+
};
|
|
14
|
+
function CalloutView({ node, updateAttributes, editor }) {
|
|
15
|
+
const alert = node.attrs.mdAlert;
|
|
16
|
+
const type = alert ? alert.slice(2, -1).toUpperCase() : null;
|
|
17
|
+
const style = type ? CALLOUT_STYLE[type] : null;
|
|
18
|
+
if (!type || !style || !alert) {
|
|
19
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "blockquote", children: /* @__PURE__ */ jsx(NodeViewContent, {}) });
|
|
20
|
+
}
|
|
21
|
+
const Icon = style.icon;
|
|
22
|
+
const lower = alert === alert.toLowerCase();
|
|
23
|
+
return /* @__PURE__ */ jsxs(NodeViewWrapper, { as: "div", className: cn("rich-editor-callout my-3 rounded-md border-l-4 px-3 py-2", style.className), children: [
|
|
24
|
+
/* @__PURE__ */ jsxs("div", { className: "rich-callout-head mb-1 flex items-center gap-1.5 text-sm font-semibold", contentEditable: false, children: [
|
|
25
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
|
|
26
|
+
editor.isEditable ? /* @__PURE__ */ jsx(
|
|
27
|
+
"select",
|
|
28
|
+
{
|
|
29
|
+
"aria-label": "Callout type",
|
|
30
|
+
className: "rounded bg-transparent text-sm font-semibold outline-none hover:bg-muted/60",
|
|
31
|
+
value: type,
|
|
32
|
+
onChange: (event) => {
|
|
33
|
+
const next = event.target.value;
|
|
34
|
+
updateAttributes({ mdAlert: `[!${lower ? next.toLowerCase() : next}]` });
|
|
35
|
+
},
|
|
36
|
+
children: CALLOUT_TYPES.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: CALLOUT_STYLE[option].label }, option))
|
|
37
|
+
}
|
|
38
|
+
) : style.label
|
|
39
|
+
] }),
|
|
40
|
+
/* @__PURE__ */ jsx(NodeViewContent, { className: "rich-callout-body" })
|
|
41
|
+
] });
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
CalloutView
|
|
45
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { humanizeIdentifier } from "@ai-matrx/kit/text-case";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { NodeViewWrapper } from "@tiptap/react";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@ai-matrx/design-system";
|
|
7
|
+
import { Braces, Sigma } from "lucide-react";
|
|
8
|
+
import { cn } from "@ai-matrx/design-system";
|
|
9
|
+
import { useRichEditorContext } from "../../editor/RichEditorContext.js";
|
|
10
|
+
import { IslandCodeEditor } from "../../islands/IslandCodeEditor.js";
|
|
11
|
+
import { IslandPreview } from "../../islands/IslandPreview.js";
|
|
12
|
+
import { inlineIslandLabel, texOf } from "../../islands/island-meta.js";
|
|
13
|
+
import { isEscapedBracketMath } from "@ai-matrx/rich-content/markdown-core/math-normalizer";
|
|
14
|
+
import {
|
|
15
|
+
VARIABLE_STATE_CLASS,
|
|
16
|
+
classifyVariable,
|
|
17
|
+
toVariableName
|
|
18
|
+
} from "../../core/variables.js";
|
|
19
|
+
function mathDelimiters(raw) {
|
|
20
|
+
if (raw.startsWith("\\(")) return ["\\(", "\\)"];
|
|
21
|
+
if (raw.startsWith("\\[")) return ["\\[", "\\]"];
|
|
22
|
+
if (raw.startsWith("$$")) return ["$$", "$$"];
|
|
23
|
+
return ["$", "$"];
|
|
24
|
+
}
|
|
25
|
+
function imageParts(raw) {
|
|
26
|
+
const match = /^!\[([^\]]*)\]\(([^)\s]+)/.exec(raw);
|
|
27
|
+
return match ? { alt: match[1] ?? "", src: match[2] ?? "" } : null;
|
|
28
|
+
}
|
|
29
|
+
function InlineIslandView({ node, updateAttributes, selected, editor }) {
|
|
30
|
+
const context = useRichEditorContext();
|
|
31
|
+
const raw = String(node.attrs.raw ?? "");
|
|
32
|
+
const islandType = String(node.attrs.islandType ?? "variable");
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
const readOnly = context.readOnly || !editor.isEditable;
|
|
35
|
+
const write = (next) => {
|
|
36
|
+
if (next === raw || !next) return;
|
|
37
|
+
context.approveIsland(raw);
|
|
38
|
+
updateAttributes({ raw: next });
|
|
39
|
+
};
|
|
40
|
+
if (islandType === "variable") {
|
|
41
|
+
const info = classifyVariable(raw, context.variables);
|
|
42
|
+
const [renaming, setRenaming] = [open, setOpen];
|
|
43
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "rich-editor-inline-island", contentEditable: false, children: /* @__PURE__ */ jsxs(Popover, { open: renaming && !readOnly, onOpenChange: setRenaming, children: [
|
|
44
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
45
|
+
"span",
|
|
46
|
+
{
|
|
47
|
+
role: "button",
|
|
48
|
+
tabIndex: -1,
|
|
49
|
+
title: info.title,
|
|
50
|
+
className: cn(
|
|
51
|
+
"mx-px inline-flex cursor-pointer items-center gap-0.5 rounded-md border px-1 py-px align-baseline font-medium text-[0.92em]",
|
|
52
|
+
VARIABLE_STATE_CLASS[info.state],
|
|
53
|
+
selected && "ring-2 ring-primary/50"
|
|
54
|
+
),
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ jsx(Braces, { className: "h-3 w-3 opacity-70" }),
|
|
57
|
+
info.name,
|
|
58
|
+
info.declared?.type && /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-[0.8em] opacity-70", children: info.declared.type })
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
) }),
|
|
62
|
+
/* @__PURE__ */ jsxs(PopoverContent, { sizing: "content", className: "p-2", align: "start", children: [
|
|
63
|
+
/* @__PURE__ */ jsx("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Variable" }),
|
|
64
|
+
/* @__PURE__ */ jsx(
|
|
65
|
+
"input",
|
|
66
|
+
{
|
|
67
|
+
className: "mb-2 h-8 w-full rounded-md border border-border bg-background px-2 text-base sm:text-sm",
|
|
68
|
+
defaultValue: info.name,
|
|
69
|
+
"aria-label": "Variable name",
|
|
70
|
+
onKeyDown: (event) => {
|
|
71
|
+
if (event.key === "Enter") {
|
|
72
|
+
const name = toVariableName(event.currentTarget.value);
|
|
73
|
+
if (name) write(`{{${name}}}`);
|
|
74
|
+
setRenaming(false);
|
|
75
|
+
editor.commands.focus();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
),
|
|
80
|
+
context.variables && context.variables.length > 0 && /* @__PURE__ */ jsx("div", { className: "max-h-48 overflow-y-auto", children: context.variables.map((variable) => /* @__PURE__ */ jsxs(
|
|
81
|
+
"button",
|
|
82
|
+
{
|
|
83
|
+
type: "button",
|
|
84
|
+
className: "flex w-full items-center justify-between rounded px-2 py-1 text-left text-sm hover:bg-muted",
|
|
85
|
+
onClick: () => {
|
|
86
|
+
write(`{{${variable.name}}}`);
|
|
87
|
+
setRenaming(false);
|
|
88
|
+
},
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsx("span", { children: humanizeIdentifier(variable.name) }),
|
|
91
|
+
variable.type && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: variable.type })
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
variable.name
|
|
95
|
+
)) }),
|
|
96
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-[11px] text-muted-foreground", children: info.title })
|
|
97
|
+
] })
|
|
98
|
+
] }) });
|
|
99
|
+
}
|
|
100
|
+
if (islandType === "math_inline") {
|
|
101
|
+
const tex = texOf(raw);
|
|
102
|
+
const [open$, close$] = mathDelimiters(raw);
|
|
103
|
+
if (open$ === "\\[" && !isEscapedBracketMath(tex, void 0)) {
|
|
104
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "rich-editor-inline-island", contentEditable: false, children: /* @__PURE__ */ jsxs("span", { title: "Escaped brackets \u2014 kept exactly as written", className: cn(selected && "rounded ring-2 ring-primary/50"), children: [
|
|
105
|
+
"[",
|
|
106
|
+
raw.slice(2, -2),
|
|
107
|
+
"]"
|
|
108
|
+
] }) });
|
|
109
|
+
}
|
|
110
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "rich-editor-inline-island", contentEditable: false, children: /* @__PURE__ */ jsxs(Popover, { open: open && !readOnly, onOpenChange: setOpen, children: [
|
|
111
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
112
|
+
"span",
|
|
113
|
+
{
|
|
114
|
+
role: "button",
|
|
115
|
+
tabIndex: -1,
|
|
116
|
+
title: "Equation \u2014 click to edit",
|
|
117
|
+
className: cn(
|
|
118
|
+
"mx-px inline-flex cursor-pointer items-center gap-0.5 rounded border border-violet-300/60 bg-violet-50 px-1 font-mono text-[0.85em] text-violet-700 dark:border-violet-700/60 dark:bg-violet-950/40 dark:text-violet-300",
|
|
119
|
+
selected && "ring-2 ring-primary/50"
|
|
120
|
+
),
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsx(Sigma, { className: "h-3 w-3 opacity-70" }),
|
|
123
|
+
tex || "equation"
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
) }),
|
|
127
|
+
/* @__PURE__ */ jsxs(PopoverContent, { sizing: "content", className: "space-y-2 p-2", align: "start", children: [
|
|
128
|
+
/* @__PURE__ */ jsx(
|
|
129
|
+
IslandCodeEditor,
|
|
130
|
+
{
|
|
131
|
+
value: tex,
|
|
132
|
+
onChange: (next) => write(`${open$}${next}${close$}`),
|
|
133
|
+
language: "math",
|
|
134
|
+
autoFocus: true,
|
|
135
|
+
ariaLabel: "Equation (TeX)",
|
|
136
|
+
onExit: () => {
|
|
137
|
+
setOpen(false);
|
|
138
|
+
editor.commands.focus();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
),
|
|
142
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border px-2 py-1", children: /* @__PURE__ */ jsx(IslandPreview, { raw: `$$
|
|
143
|
+
${tex}
|
|
144
|
+
$$`, islandType: "math_block" }) })
|
|
145
|
+
] })
|
|
146
|
+
] }) });
|
|
147
|
+
}
|
|
148
|
+
const image = islandType === "md_image" ? imageParts(raw) : null;
|
|
149
|
+
if (image) {
|
|
150
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "rich-editor-inline-island", contentEditable: false, children: /* @__PURE__ */ jsx(
|
|
151
|
+
"img",
|
|
152
|
+
{
|
|
153
|
+
src: image.src,
|
|
154
|
+
alt: image.alt,
|
|
155
|
+
title: raw,
|
|
156
|
+
className: cn("inline-block max-h-64 max-w-full rounded-md align-middle", selected && "ring-2 ring-primary")
|
|
157
|
+
}
|
|
158
|
+
) });
|
|
159
|
+
}
|
|
160
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "rich-editor-inline-island", contentEditable: false, children: /* @__PURE__ */ jsx(
|
|
161
|
+
"span",
|
|
162
|
+
{
|
|
163
|
+
title: `${inlineIslandLabel(islandType)} \u2014 kept exactly as written`,
|
|
164
|
+
className: cn(
|
|
165
|
+
"mx-px inline rounded border border-border bg-muted/60 px-1 font-mono text-[0.82em] text-muted-foreground",
|
|
166
|
+
selected && "ring-2 ring-primary/50"
|
|
167
|
+
),
|
|
168
|
+
children: raw
|
|
169
|
+
}
|
|
170
|
+
) });
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
InlineIslandView
|
|
174
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { NodeViewWrapper } from "@tiptap/react";
|
|
6
|
+
import { AlertTriangle, Check, Copy, GripVertical, Lock, Pencil, Trash2 } from "lucide-react";
|
|
7
|
+
import { cn } from "@ai-matrx/design-system";
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ai-matrx/design-system";
|
|
9
|
+
import { toast } from "@ai-matrx/rich-content/host/toast";
|
|
10
|
+
import { useRichEditorContext } from "../../editor/RichEditorContext.js";
|
|
11
|
+
import { IslandCodeEditor } from "../../islands/IslandCodeEditor.js";
|
|
12
|
+
import { IslandPreview } from "../../islands/IslandPreview.js";
|
|
13
|
+
import {
|
|
14
|
+
CODE_LANGUAGES,
|
|
15
|
+
islandMeta,
|
|
16
|
+
texOf,
|
|
17
|
+
fenceLanguage as fenceLanguageOf,
|
|
18
|
+
isMatrxFence,
|
|
19
|
+
withFenceLanguage
|
|
20
|
+
} from "../../islands/island-meta.js";
|
|
21
|
+
import { consumeAutoEdit } from "../auto-edit.js";
|
|
22
|
+
const PLAIN_TEXT = "__plain_text";
|
|
23
|
+
function IslandBlockView({ node, updateAttributes, deleteNode, selected, editor, getPos }) {
|
|
24
|
+
const { copyText } = useClipboard({
|
|
25
|
+
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
26
|
+
});
|
|
27
|
+
const context = useRichEditorContext();
|
|
28
|
+
const raw = String(node.attrs.raw ?? "");
|
|
29
|
+
const islandType = String(node.attrs.islandType ?? "fence");
|
|
30
|
+
const complete = node.attrs.complete !== false;
|
|
31
|
+
const meta = islandMeta(islandType, raw);
|
|
32
|
+
const Icon = meta.icon;
|
|
33
|
+
const [editing, setEditing] = useState(false);
|
|
34
|
+
const [copied, setCopied] = useState(false);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const pos = typeof getPos === "function" ? getPos() : void 0;
|
|
37
|
+
if (typeof pos === "number" && consumeAutoEdit(editor, pos)) setEditing(true);
|
|
38
|
+
}, [editor, getPos]);
|
|
39
|
+
const write = (next) => {
|
|
40
|
+
if (next === raw) return;
|
|
41
|
+
context.approveIsland(raw);
|
|
42
|
+
updateAttributes({ raw: next, complete: true });
|
|
43
|
+
};
|
|
44
|
+
const remove = () => {
|
|
45
|
+
context.approveIsland(raw);
|
|
46
|
+
deleteNode();
|
|
47
|
+
toast.info(`${meta.label} removed.`, {
|
|
48
|
+
action: { label: "Undo", onClick: () => editor.commands.undo() }
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const copy = async () => {
|
|
52
|
+
try {
|
|
53
|
+
if (!await copyText(raw)) return;
|
|
54
|
+
setCopied(true);
|
|
55
|
+
window.setTimeout(() => setCopied(false), 1200);
|
|
56
|
+
} catch {
|
|
57
|
+
toast.error("Copy failed \u2014 your browser blocked clipboard access.");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const isFence = islandType === "fence";
|
|
61
|
+
const isMath = meta.language === "math";
|
|
62
|
+
const readOnly = context.readOnly || !editor.isEditable;
|
|
63
|
+
if (isMatrxFence(islandType, raw)) {
|
|
64
|
+
return /* @__PURE__ */ jsxs(
|
|
65
|
+
NodeViewWrapper,
|
|
66
|
+
{
|
|
67
|
+
className: cn(
|
|
68
|
+
"rich-editor-island rich-editor-reference group/island relative my-1 flex min-w-0 items-start gap-1 rounded-md transition-colors",
|
|
69
|
+
selected && "ring-1 ring-primary/40"
|
|
70
|
+
),
|
|
71
|
+
"data-island-type": "matrx",
|
|
72
|
+
contentEditable: false,
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
"span",
|
|
76
|
+
{
|
|
77
|
+
"data-drag-handle": true,
|
|
78
|
+
className: "mt-1.5 shrink-0 cursor-grab touch-none text-muted-foreground/60 opacity-100 hover:text-foreground sm:opacity-0 sm:group-hover/island:opacity-100",
|
|
79
|
+
"aria-hidden": true,
|
|
80
|
+
children: /* @__PURE__ */ jsx(GripVertical, { className: "h-3.5 w-3.5" })
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(IslandPreview, { raw, islandType }) }),
|
|
84
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5 text-muted-foreground opacity-100 sm:opacity-0 sm:transition-opacity sm:group-hover/island:opacity-100 sm:group-focus-within/island:opacity-100", children: [
|
|
85
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "rounded p-1 hover:bg-muted hover:text-foreground pointer-coarse:flex pointer-coarse:h-11 pointer-coarse:w-11 pointer-coarse:items-center pointer-coarse:justify-center", onClick: copy, title: `Copy this ${meta.label.toLowerCase()}`, children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
|
|
86
|
+
!readOnly && /* @__PURE__ */ jsx(
|
|
87
|
+
"button",
|
|
88
|
+
{
|
|
89
|
+
type: "button",
|
|
90
|
+
className: "rounded p-1 hover:bg-destructive/10 hover:text-destructive-ink pointer-coarse:flex pointer-coarse:h-11 pointer-coarse:w-11 pointer-coarse:items-center pointer-coarse:justify-center",
|
|
91
|
+
onClick: remove,
|
|
92
|
+
title: `Remove this ${meta.label.toLowerCase()} (Undo brings it back)`,
|
|
93
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
] })
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return /* @__PURE__ */ jsxs(
|
|
102
|
+
NodeViewWrapper,
|
|
103
|
+
{
|
|
104
|
+
className: cn(
|
|
105
|
+
"rich-editor-island group/island relative my-3 rounded-lg border bg-card/60 transition-colors",
|
|
106
|
+
selected ? "border-primary ring-1 ring-primary/40" : "border-border"
|
|
107
|
+
),
|
|
108
|
+
"data-island-type": islandType,
|
|
109
|
+
contentEditable: false,
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-h-8 min-w-0 items-center gap-1.5 border-b border-border/60 px-2 py-1 text-xs text-muted-foreground", children: [
|
|
112
|
+
/* @__PURE__ */ jsx("span", { "data-drag-handle": true, className: "shrink-0 cursor-grab touch-none text-muted-foreground/60 hover:text-foreground", "aria-hidden": true, children: /* @__PURE__ */ jsx(GripVertical, { className: "h-3.5 w-3.5" }) }),
|
|
113
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
114
|
+
!(isFence && !readOnly) && /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-medium text-foreground/80", children: meta.label }),
|
|
115
|
+
!isFence && /* @__PURE__ */ jsx(Lock, { className: "h-3 w-3 shrink-0 opacity-60", "aria-label": "Protected \u2014 never rewritten by the editor" }),
|
|
116
|
+
!complete && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-amber-600 dark:text-amber-400", title: "This block's closing marker is missing, so it runs to the end of the text. It is kept exactly as written.", children: [
|
|
117
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-3 w-3" }),
|
|
118
|
+
" not closed"
|
|
119
|
+
] }),
|
|
120
|
+
isFence && !readOnly && // The app's Select (not a native one). Radix needs a non-empty
|
|
121
|
+
// value, so "Plain text" rides a sentinel.
|
|
122
|
+
/* @__PURE__ */ jsxs(
|
|
123
|
+
Select,
|
|
124
|
+
{
|
|
125
|
+
value: fenceLanguageOf(raw) || PLAIN_TEXT,
|
|
126
|
+
onValueChange: (value) => write(withFenceLanguage(raw, value === PLAIN_TEXT ? "" : value)),
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ jsx(
|
|
129
|
+
SelectTrigger,
|
|
130
|
+
{
|
|
131
|
+
"aria-label": "Code language",
|
|
132
|
+
className: "ml-1 min-w-[5.5rem] max-w-[9rem] shrink",
|
|
133
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
/* @__PURE__ */ jsx(SelectContent, { children: [.../* @__PURE__ */ new Set([fenceLanguageOf(raw), ...CODE_LANGUAGES])].map((language) => /* @__PURE__ */ jsx(SelectItem, { value: language || PLAIN_TEXT, className: "text-xs", children: language || "Plain text" }, language || PLAIN_TEXT)) })
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-0.5 opacity-100 sm:opacity-0 sm:transition-opacity sm:group-hover/island:opacity-100 sm:group-focus-within/island:opacity-100", children: [
|
|
141
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "rounded p-1 hover:bg-muted hover:text-foreground pointer-coarse:flex pointer-coarse:h-11 pointer-coarse:w-11 pointer-coarse:items-center pointer-coarse:justify-center", onClick: copy, title: "Copy its exact source", children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
|
|
142
|
+
!readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
"button",
|
|
145
|
+
{
|
|
146
|
+
type: "button",
|
|
147
|
+
className: cn("rounded p-1 hover:bg-muted hover:text-foreground pointer-coarse:flex pointer-coarse:h-11 pointer-coarse:w-11 pointer-coarse:items-center pointer-coarse:justify-center", editing && "bg-muted text-foreground"),
|
|
148
|
+
onClick: () => setEditing((value) => !value),
|
|
149
|
+
title: editing ? "Done editing" : "Edit its source",
|
|
150
|
+
"aria-pressed": editing,
|
|
151
|
+
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" })
|
|
152
|
+
}
|
|
153
|
+
),
|
|
154
|
+
/* @__PURE__ */ jsx(
|
|
155
|
+
"button",
|
|
156
|
+
{
|
|
157
|
+
type: "button",
|
|
158
|
+
className: "rounded p-1 hover:bg-destructive/10 hover:text-destructive-ink pointer-coarse:flex pointer-coarse:h-11 pointer-coarse:w-11 pointer-coarse:items-center pointer-coarse:justify-center",
|
|
159
|
+
onClick: remove,
|
|
160
|
+
title: "Remove this block from the document (Undo brings it back)",
|
|
161
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
] })
|
|
165
|
+
] })
|
|
166
|
+
] }),
|
|
167
|
+
editing && !readOnly ? /* @__PURE__ */ jsxs("div", { className: "space-y-2 p-2", children: [
|
|
168
|
+
/* @__PURE__ */ jsx(
|
|
169
|
+
IslandCodeEditor,
|
|
170
|
+
{
|
|
171
|
+
value: raw,
|
|
172
|
+
onChange: write,
|
|
173
|
+
language: meta.language,
|
|
174
|
+
autoFocus: true,
|
|
175
|
+
ariaLabel: `${meta.label} source`,
|
|
176
|
+
onExit: () => {
|
|
177
|
+
setEditing(false);
|
|
178
|
+
editor.commands.focus();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
isMath && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed border-border px-3 py-2", children: [
|
|
183
|
+
/* @__PURE__ */ jsx("div", { className: "mb-1 text-[11px] uppercase tracking-wide text-muted-foreground", children: "Preview" }),
|
|
184
|
+
/* @__PURE__ */ jsx(IslandPreview, { raw, islandType }),
|
|
185
|
+
!texOf(raw).trim() && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
186
|
+
"Type TeX above, e.g. \\frac",
|
|
187
|
+
"{a}",
|
|
188
|
+
"{b}",
|
|
189
|
+
"."
|
|
190
|
+
] })
|
|
191
|
+
] })
|
|
192
|
+
] }) : /* @__PURE__ */ jsx(
|
|
193
|
+
"div",
|
|
194
|
+
{
|
|
195
|
+
className: "max-h-[32rem] overflow-auto px-3 py-2",
|
|
196
|
+
onDoubleClick: () => !readOnly && setEditing(true),
|
|
197
|
+
children: /* @__PURE__ */ jsx(IslandPreview, { raw, islandType })
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
export {
|
|
205
|
+
IslandBlockView
|
|
206
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { NodeViewWrapper } from "@tiptap/react";
|
|
5
|
+
import { FileLock2, GripVertical, Pencil } from "lucide-react";
|
|
6
|
+
import { cn } from "@ai-matrx/design-system";
|
|
7
|
+
import { useRichEditorContext } from "../../editor/RichEditorContext.js";
|
|
8
|
+
import { IslandCodeEditor } from "../../islands/IslandCodeEditor.js";
|
|
9
|
+
import { IslandPreview } from "../../islands/IslandPreview.js";
|
|
10
|
+
function SourceLockedView({ node, updateAttributes, selected, editor }) {
|
|
11
|
+
const context = useRichEditorContext();
|
|
12
|
+
const raw = String(node.attrs.raw ?? "");
|
|
13
|
+
const reason = node.attrs.reason ? String(node.attrs.reason) : "source";
|
|
14
|
+
const [editing, setEditing] = useState(false);
|
|
15
|
+
const readOnly = context.readOnly || !editor.isEditable;
|
|
16
|
+
const isPageBreak = reason === "page break";
|
|
17
|
+
return /* @__PURE__ */ jsxs(
|
|
18
|
+
NodeViewWrapper,
|
|
19
|
+
{
|
|
20
|
+
className: cn(
|
|
21
|
+
"rich-editor-locked group/locked relative my-2 rounded-md border border-dashed transition-colors",
|
|
22
|
+
selected ? "border-primary" : "border-border/70"
|
|
23
|
+
),
|
|
24
|
+
contentEditable: false,
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-2 pt-1 text-[11px] text-muted-foreground opacity-100 sm:opacity-0 sm:transition-opacity sm:group-hover/locked:opacity-100 sm:group-focus-within/locked:opacity-100", children: [
|
|
27
|
+
/* @__PURE__ */ jsx("span", { "data-drag-handle": true, className: "cursor-grab text-muted-foreground/60", "aria-hidden": true, children: /* @__PURE__ */ jsx(GripVertical, { className: "h-3 w-3" }) }),
|
|
28
|
+
/* @__PURE__ */ jsx(FileLock2, { className: "h-3 w-3" }),
|
|
29
|
+
/* @__PURE__ */ jsx("span", { children: isPageBreak ? "Page break" : `Kept exactly as written \u2014 ${reason}` }),
|
|
30
|
+
!readOnly && /* @__PURE__ */ jsx(
|
|
31
|
+
"button",
|
|
32
|
+
{
|
|
33
|
+
type: "button",
|
|
34
|
+
className: cn("ml-auto rounded p-1 hover:bg-muted hover:text-foreground", editing && "bg-muted text-foreground"),
|
|
35
|
+
onClick: () => setEditing((value) => !value),
|
|
36
|
+
title: editing ? "Done editing" : "Edit as source",
|
|
37
|
+
"aria-pressed": editing,
|
|
38
|
+
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3" })
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
] }),
|
|
42
|
+
editing && !readOnly ? /* @__PURE__ */ jsx("div", { className: "p-2", children: /* @__PURE__ */ jsx(
|
|
43
|
+
IslandCodeEditor,
|
|
44
|
+
{
|
|
45
|
+
value: raw,
|
|
46
|
+
onChange: (next) => updateAttributes({ raw: next }),
|
|
47
|
+
language: "markdown",
|
|
48
|
+
autoFocus: true,
|
|
49
|
+
ariaLabel: "Source",
|
|
50
|
+
onExit: () => {
|
|
51
|
+
setEditing(false);
|
|
52
|
+
editor.commands.focus();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
) }) : /* @__PURE__ */ jsx("div", { className: "px-3 pb-2", onDoubleClick: () => !readOnly && setEditing(true), children: /* @__PURE__ */ jsx(IslandPreview, { raw, islandType: isPageBreak ? "html_comment" : "prose" }) })
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
SourceLockedView
|
|
62
|
+
};
|