@modern-admin/ui 0.1.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/components/accordion.d.ts +7 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.jsx +19 -0
- package/dist/components/accordion.jsx.map +1 -0
- package/dist/components/alert-dialog.d.ts +22 -0
- package/dist/components/alert-dialog.d.ts.map +1 -0
- package/dist/components/alert-dialog.jsx +27 -0
- package/dist/components/alert-dialog.jsx.map +1 -0
- package/dist/components/audit-timeline.d.ts +24 -0
- package/dist/components/audit-timeline.d.ts.map +1 -0
- package/dist/components/audit-timeline.jsx +60 -0
- package/dist/components/audit-timeline.jsx.map +1 -0
- package/dist/components/avatar.d.ts +6 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.jsx +10 -0
- package/dist/components/avatar.jsx.map +1 -0
- package/dist/components/badge.d.ts +10 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.jsx +19 -0
- package/dist/components/badge.jsx.map +1 -0
- package/dist/components/breadcrumb.d.ts +17 -0
- package/dist/components/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb.jsx +27 -0
- package/dist/components/breadcrumb.jsx.map +1 -0
- package/dist/components/button.d.ts +12 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.jsx +37 -0
- package/dist/components/button.jsx.map +1 -0
- package/dist/components/calendar.d.ts +9 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.jsx +102 -0
- package/dist/components/calendar.jsx.map +1 -0
- package/dist/components/card.d.ts +8 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.jsx +18 -0
- package/dist/components/card.jsx.map +1 -0
- package/dist/components/chart.d.ts +97 -0
- package/dist/components/chart.d.ts.map +1 -0
- package/dist/components/chart.jsx +233 -0
- package/dist/components/chart.jsx.map +1 -0
- package/dist/components/checkbox.d.ts +4 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.jsx +11 -0
- package/dist/components/checkbox.jsx.map +1 -0
- package/dist/components/combobox.d.ts +46 -0
- package/dist/components/combobox.d.ts.map +1 -0
- package/dist/components/combobox.jsx +145 -0
- package/dist/components/combobox.jsx.map +1 -0
- package/dist/components/command.d.ts +80 -0
- package/dist/components/command.d.ts.map +1 -0
- package/dist/components/command.jsx +32 -0
- package/dist/components/command.jsx.map +1 -0
- package/dist/components/date-picker.d.ts +24 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.jsx +149 -0
- package/dist/components/date-picker.jsx.map +1 -0
- package/dist/components/date-range-input.d.ts +22 -0
- package/dist/components/date-range-input.d.ts.map +1 -0
- package/dist/components/date-range-input.jsx +202 -0
- package/dist/components/date-range-input.jsx.map +1 -0
- package/dist/components/dialog.d.ts +19 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.jsx +30 -0
- package/dist/components/dialog.jsx.map +1 -0
- package/dist/components/diff-view.d.ts +24 -0
- package/dist/components/diff-view.d.ts.map +1 -0
- package/dist/components/diff-view.jsx +69 -0
- package/dist/components/diff-view.jsx.map +1 -0
- package/dist/components/dropdown-menu.d.ts +27 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.jsx +48 -0
- package/dist/components/dropdown-menu.jsx.map +1 -0
- package/dist/components/empty.d.ts +15 -0
- package/dist/components/empty.d.ts.map +1 -0
- package/dist/components/empty.jsx +27 -0
- package/dist/components/empty.jsx.map +1 -0
- package/dist/components/field.d.ts +23 -0
- package/dist/components/field.d.ts.map +1 -0
- package/dist/components/field.jsx +60 -0
- package/dist/components/field.jsx.map +1 -0
- package/dist/components/file-input.d.ts +50 -0
- package/dist/components/file-input.d.ts.map +1 -0
- package/dist/components/file-input.jsx +104 -0
- package/dist/components/file-input.jsx.map +1 -0
- package/dist/components/form.d.ts +20 -0
- package/dist/components/form.d.ts.map +1 -0
- package/dist/components/form.jsx +66 -0
- package/dist/components/form.jsx.map +1 -0
- package/dist/components/info-tooltip.d.ts +11 -0
- package/dist/components/info-tooltip.d.ts.map +1 -0
- package/dist/components/info-tooltip.jsx +17 -0
- package/dist/components/info-tooltip.jsx.map +1 -0
- package/dist/components/input.d.ts +13 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.jsx +19 -0
- package/dist/components/input.jsx.map +1 -0
- package/dist/components/json-editor.d.ts +23 -0
- package/dist/components/json-editor.d.ts.map +1 -0
- package/dist/components/json-editor.jsx +143 -0
- package/dist/components/json-editor.jsx.map +1 -0
- package/dist/components/kbd.d.ts +15 -0
- package/dist/components/kbd.d.ts.map +1 -0
- package/dist/components/kbd.jsx +23 -0
- package/dist/components/kbd.jsx.map +1 -0
- package/dist/components/key-value-editor.d.ts +92 -0
- package/dist/components/key-value-editor.d.ts.map +1 -0
- package/dist/components/key-value-editor.jsx +187 -0
- package/dist/components/key-value-editor.jsx.map +1 -0
- package/dist/components/keyboard-shortcuts-help.d.ts +17 -0
- package/dist/components/keyboard-shortcuts-help.d.ts.map +1 -0
- package/dist/components/keyboard-shortcuts-help.jsx +97 -0
- package/dist/components/keyboard-shortcuts-help.jsx.map +1 -0
- package/dist/components/label.d.ts +5 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.jsx +8 -0
- package/dist/components/label.jsx.map +1 -0
- package/dist/components/media-preview.d.ts +30 -0
- package/dist/components/media-preview.d.ts.map +1 -0
- package/dist/components/media-preview.jsx +189 -0
- package/dist/components/media-preview.jsx.map +1 -0
- package/dist/components/multi-file-input.d.ts +76 -0
- package/dist/components/multi-file-input.d.ts.map +1 -0
- package/dist/components/multi-file-input.jsx +131 -0
- package/dist/components/multi-file-input.jsx.map +1 -0
- package/dist/components/password-input.d.ts +10 -0
- package/dist/components/password-input.d.ts.map +1 -0
- package/dist/components/password-input.jsx +18 -0
- package/dist/components/password-input.jsx.map +1 -0
- package/dist/components/popover.d.ts +7 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.jsx +11 -0
- package/dist/components/popover.jsx.map +1 -0
- package/dist/components/revision-timeline.d.ts +30 -0
- package/dist/components/revision-timeline.d.ts.map +1 -0
- package/dist/components/revision-timeline.jsx +42 -0
- package/dist/components/revision-timeline.jsx.map +1 -0
- package/dist/components/richtext-editor.d.ts +43 -0
- package/dist/components/richtext-editor.d.ts.map +1 -0
- package/dist/components/richtext-editor.jsx +319 -0
- package/dist/components/richtext-editor.jsx.map +1 -0
- package/dist/components/richtext-mode.d.ts +23 -0
- package/dist/components/richtext-mode.d.ts.map +1 -0
- package/dist/components/richtext-mode.js +36 -0
- package/dist/components/richtext-mode.js.map +1 -0
- package/dist/components/richtext-render.d.ts +8 -0
- package/dist/components/richtext-render.d.ts.map +1 -0
- package/dist/components/richtext-render.jsx +33 -0
- package/dist/components/richtext-render.jsx.map +1 -0
- package/dist/components/richtext-sync.d.ts +37 -0
- package/dist/components/richtext-sync.d.ts.map +1 -0
- package/dist/components/richtext-sync.js +46 -0
- package/dist/components/richtext-sync.js.map +1 -0
- package/dist/components/scroll-area.d.ts +5 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/scroll-area.jsx +16 -0
- package/dist/components/scroll-area.jsx.map +1 -0
- package/dist/components/select.d.ts +36 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.jsx +87 -0
- package/dist/components/select.jsx.map +1 -0
- package/dist/components/separator.d.ts +4 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.jsx +6 -0
- package/dist/components/separator.jsx.map +1 -0
- package/dist/components/sheet.d.ts +29 -0
- package/dist/components/sheet.d.ts.map +1 -0
- package/dist/components/sheet.jsx +44 -0
- package/dist/components/sheet.jsx.map +1 -0
- package/dist/components/sidebar.d.ts +70 -0
- package/dist/components/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar.jsx +245 -0
- package/dist/components/sidebar.jsx.map +1 -0
- package/dist/components/skeleton.d.ts +3 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.jsx +6 -0
- package/dist/components/skeleton.jsx.map +1 -0
- package/dist/components/sonner.d.ts +6 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.jsx +29 -0
- package/dist/components/sonner.jsx.map +1 -0
- package/dist/components/switch.d.ts +4 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.jsx +8 -0
- package/dist/components/switch.jsx.map +1 -0
- package/dist/components/table.d.ts +10 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/table.jsx +21 -0
- package/dist/components/table.jsx.map +1 -0
- package/dist/components/tabs.d.ts +7 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.jsx +14 -0
- package/dist/components/tabs.jsx.map +1 -0
- package/dist/components/textarea.d.ts +4 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.jsx +5 -0
- package/dist/components/textarea.jsx.map +1 -0
- package/dist/components/tooltip.d.ts +7 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.jsx +11 -0
- package/dist/components/tooltip.jsx.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/theme.d.ts +11 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +44 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles.css +242 -0
- package/package.json +85 -0
- package/src/components/accordion.tsx +48 -0
- package/src/components/alert-dialog.tsx +113 -0
- package/src/components/audit-timeline.tsx +102 -0
- package/src/components/avatar.tsx +42 -0
- package/src/components/badge.tsx +34 -0
- package/src/components/breadcrumb.tsx +99 -0
- package/src/components/button.tsx +58 -0
- package/src/components/calendar.tsx +176 -0
- package/src/components/card.tsx +60 -0
- package/src/components/chart.tsx +558 -0
- package/src/components/checkbox.tsx +23 -0
- package/src/components/combobox.tsx +264 -0
- package/src/components/command.tsx +120 -0
- package/src/components/date-picker.tsx +221 -0
- package/src/components/date-range-input.tsx +295 -0
- package/src/components/dialog.tsx +94 -0
- package/src/components/diff-view.tsx +182 -0
- package/src/components/dropdown-menu.tsx +165 -0
- package/src/components/empty.tsx +100 -0
- package/src/components/field.tsx +168 -0
- package/src/components/file-input.tsx +233 -0
- package/src/components/form.tsx +152 -0
- package/src/components/info-tooltip.tsx +40 -0
- package/src/components/input.tsx +55 -0
- package/src/components/json-editor.tsx +210 -0
- package/src/components/kbd.tsx +35 -0
- package/src/components/key-value-editor.tsx +423 -0
- package/src/components/keyboard-shortcuts-help.tsx +136 -0
- package/src/components/label.tsx +16 -0
- package/src/components/media-preview.tsx +278 -0
- package/src/components/multi-file-input.tsx +315 -0
- package/src/components/password-input.tsx +50 -0
- package/src/components/popover.tsx +26 -0
- package/src/components/revision-timeline.tsx +93 -0
- package/src/components/richtext-editor.tsx +624 -0
- package/src/components/richtext-mode.ts +39 -0
- package/src/components/richtext-render.tsx +51 -0
- package/src/components/richtext-sync.ts +57 -0
- package/src/components/scroll-area.tsx +41 -0
- package/src/components/select.tsx +200 -0
- package/src/components/separator.tsx +21 -0
- package/src/components/sheet.tsx +109 -0
- package/src/components/sidebar.tsx +660 -0
- package/src/components/skeleton.tsx +9 -0
- package/src/components/sonner.tsx +45 -0
- package/src/components/switch.tsx +24 -0
- package/src/components/table.tsx +93 -0
- package/src/components/tabs.tsx +57 -0
- package/src/components/textarea.tsx +18 -0
- package/src/components/tooltip.tsx +25 -0
- package/src/index.ts +342 -0
- package/src/lib/theme.ts +45 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles.css +242 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// Tiptap-3-based WYSIWYG editor with a Source/Plain toggle.
|
|
2
|
+
//
|
|
3
|
+
// <RichtextEditor value={value} onChange={onChange} format="html" />
|
|
4
|
+
//
|
|
5
|
+
// `format` controls both the I/O contract (HTML vs Markdown) and the
|
|
6
|
+
// content of the Source view: when format='markdown' the editor uses the
|
|
7
|
+
// `tiptap-markdown` extension and `value` is the raw Markdown string;
|
|
8
|
+
// when format='html' the value is the editor's serialised HTML.
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
import { EditorContent, useEditor } from '@tiptap/react';
|
|
11
|
+
import { StarterKit } from '@tiptap/starter-kit';
|
|
12
|
+
import { Markdown } from 'tiptap-markdown';
|
|
13
|
+
import { Bold, Code, Code2, Columns2, Eye, Heading1, Heading2, Heading3, Italic, Link as LinkIcon, List, ListOrdered, Maximize2, Minimize2, Minus, Quote, Redo2, Strikethrough, Undo2, } from 'lucide-react';
|
|
14
|
+
import { cn } from '../lib/utils.js';
|
|
15
|
+
import { Button } from './button.js';
|
|
16
|
+
import { isSplitAvailable, resolveMode, shouldSyncEditor, } from './richtext-mode.js';
|
|
17
|
+
import { readEditorContent, shouldSyncToEditor } from './richtext-sync.js';
|
|
18
|
+
import { Separator } from './separator.js';
|
|
19
|
+
import { Textarea } from './textarea.js';
|
|
20
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip.js';
|
|
21
|
+
const HEADING_ICON = {
|
|
22
|
+
1: Heading1,
|
|
23
|
+
2: Heading2,
|
|
24
|
+
3: Heading3,
|
|
25
|
+
};
|
|
26
|
+
const proseContentClass = 'prose prose-sm max-w-none min-h-[160px] p-3 text-foreground focus:outline-none ' +
|
|
27
|
+
'prose-headings:scroll-mt-20 prose-headings:text-foreground prose-p:text-foreground ' +
|
|
28
|
+
'prose-strong:text-foreground prose-li:text-foreground prose-code:text-foreground ' +
|
|
29
|
+
'prose-pre:text-foreground prose-pre:bg-muted prose-blockquote:text-muted-foreground ' +
|
|
30
|
+
'prose-blockquote:border-border prose-a:text-foreground dark:prose-invert';
|
|
31
|
+
function ToolbarButton({ active, disabled, onClick, title, children, }) {
|
|
32
|
+
return (<Tooltip>
|
|
33
|
+
<TooltipTrigger asChild>
|
|
34
|
+
<Button type="button" variant={active ? 'secondary' : 'ghost'} size="icon" className="size-8" aria-pressed={active} aria-label={title} disabled={disabled} onMouseDown={(e) => {
|
|
35
|
+
// Prevent blur of the editor selection
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
}} onClick={onClick}>
|
|
38
|
+
{children}
|
|
39
|
+
</Button>
|
|
40
|
+
</TooltipTrigger>
|
|
41
|
+
<TooltipContent>{title}</TooltipContent>
|
|
42
|
+
</Tooltip>);
|
|
43
|
+
}
|
|
44
|
+
export function RichtextEditor({ value, onChange, format = 'html', placeholder, disabled, defaultMode = 'wysiwyg', className, onBlur, ariaLabelledBy, labels, }) {
|
|
45
|
+
const l = {
|
|
46
|
+
bold: labels?.bold ?? 'Bold',
|
|
47
|
+
italic: labels?.italic ?? 'Italic',
|
|
48
|
+
strikethrough: labels?.strikethrough ?? 'Strikethrough',
|
|
49
|
+
inlineCode: labels?.inlineCode ?? 'Inline code',
|
|
50
|
+
heading: labels?.heading ?? 'Heading {level}',
|
|
51
|
+
bulletList: labels?.bulletList ?? 'Bullet list',
|
|
52
|
+
numberedList: labels?.numberedList ?? 'Numbered list',
|
|
53
|
+
blockquote: labels?.blockquote ?? 'Blockquote',
|
|
54
|
+
horizontalRule: labels?.horizontalRule ?? 'Horizontal rule',
|
|
55
|
+
insertLink: labels?.insertLink ?? 'Insert link',
|
|
56
|
+
undo: labels?.undo ?? 'Undo',
|
|
57
|
+
redo: labels?.redo ?? 'Redo',
|
|
58
|
+
source: labels?.source ?? 'Source ({format})',
|
|
59
|
+
splitView: labels?.splitView ?? 'Split view',
|
|
60
|
+
visualEditor: labels?.visualEditor ?? 'Visual editor',
|
|
61
|
+
fullscreen: labels?.fullscreen ?? 'Fullscreen',
|
|
62
|
+
exitFullscreen: labels?.exitFullscreen ?? 'Exit fullscreen',
|
|
63
|
+
urlPrompt: labels?.urlPrompt ?? 'URL',
|
|
64
|
+
};
|
|
65
|
+
const [mode, setMode] = React.useState(defaultMode);
|
|
66
|
+
const [fullscreen, setFullscreen] = React.useState(false);
|
|
67
|
+
// Effective mode: collapses 'split' to 'wysiwyg' when not fullscreen so the
|
|
68
|
+
// layout stays usable. The user's chosen mode is preserved in `mode` so that
|
|
69
|
+
// re-entering fullscreen restores split.
|
|
70
|
+
const effectiveMode = resolveMode(mode, fullscreen);
|
|
71
|
+
// Currently active output format. Initial value comes from `format` prop;
|
|
72
|
+
// user can override via the toolbar HTML/MD switch. The prop acts as the
|
|
73
|
+
// controlled default — when it changes externally, sync local state.
|
|
74
|
+
const [activeFormat, setActiveFormat] = React.useState(format);
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
setActiveFormat(format);
|
|
77
|
+
}, [format]);
|
|
78
|
+
// Stable ref so onUpdate (registered once with the editor) always reads the
|
|
79
|
+
// latest active format without recreating the editor instance.
|
|
80
|
+
const activeFormatRef = React.useRef(activeFormat);
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
activeFormatRef.current = activeFormat;
|
|
83
|
+
}, [activeFormat]);
|
|
84
|
+
// Set to true inside handleFormatChange and cleared after the first sync
|
|
85
|
+
// effect run. Guards against the intermediate render where activeFormat has
|
|
86
|
+
// already changed to the new format but the external `value` prop still
|
|
87
|
+
// holds the previous format's string (parent hasn't re-rendered yet).
|
|
88
|
+
// Without this guard, the stale value would be written into the editor in
|
|
89
|
+
// the wrong format, corrupting the content.
|
|
90
|
+
const pendingFormatChangeRef = React.useRef(false);
|
|
91
|
+
// Tracks the most recent string we emitted via onChange. The sync effect
|
|
92
|
+
// uses it to detect "echoes" — frames where parent's value caught up to a
|
|
93
|
+
// previous onChange while the user has *already* typed more characters. In
|
|
94
|
+
// that frame editor.getHTML() is ahead of value; without echo-suppression
|
|
95
|
+
// we'd call setContent(value), reverting the editor to the stale string,
|
|
96
|
+
// dropping just-typed characters and jumping the cursor. See the
|
|
97
|
+
// "echo-suppression" tests in richtext-sync.test.ts for the full timeline.
|
|
98
|
+
const lastEmittedRef = React.useRef(value);
|
|
99
|
+
// Markdown extension is always loaded so the editor can produce both HTML
|
|
100
|
+
// and Markdown on demand without a costly remount when the user toggles.
|
|
101
|
+
const extensions = React.useMemo(() => [
|
|
102
|
+
StarterKit.configure({ heading: { levels: [1, 2, 3] } }),
|
|
103
|
+
// html: true (the library default) is required so that MarkdownIt
|
|
104
|
+
// recognises HTML blocks when setContent() is called with an HTML string
|
|
105
|
+
// (format='html'). With html:false, tags like <p>/<h2>/<strong> are
|
|
106
|
+
// treated as literal characters and displayed as raw text instead of
|
|
107
|
+
// being rendered. The markdown *output* (getMarkdown) is still clean
|
|
108
|
+
// markdown for all standard nodes — html:true only affects input parsing.
|
|
109
|
+
Markdown.configure({ html: true, transformPastedText: true }),
|
|
110
|
+
], []);
|
|
111
|
+
const editor = useEditor({
|
|
112
|
+
extensions,
|
|
113
|
+
content: value,
|
|
114
|
+
editable: !disabled,
|
|
115
|
+
editorProps: {
|
|
116
|
+
attributes: {
|
|
117
|
+
class: cn(proseContentClass),
|
|
118
|
+
'aria-labelledby': ariaLabelledBy ?? '',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
onUpdate: ({ editor: ed }) => {
|
|
122
|
+
const next = readEditorContent(ed, activeFormatRef.current);
|
|
123
|
+
lastEmittedRef.current = next;
|
|
124
|
+
onChange(next);
|
|
125
|
+
},
|
|
126
|
+
onBlur: () => onBlur?.(),
|
|
127
|
+
// Tiptap 3 ships with default content sync; no need for autofocus etc.
|
|
128
|
+
immediatelyRender: false,
|
|
129
|
+
}, [disabled, ariaLabelledBy]);
|
|
130
|
+
// Keep editor in sync when external `value` changes (e.g. form reset).
|
|
131
|
+
// shouldSyncToEditor guards against the intermediate render that follows a
|
|
132
|
+
// format switch: activeFormat has changed but value hasn't caught up yet.
|
|
133
|
+
// In that transient frame, calling setContent with the stale value would
|
|
134
|
+
// write the old format's string into the editor in the new format mode,
|
|
135
|
+
// corrupting the content. pendingFormatChangeRef is set in handleFormatChange
|
|
136
|
+
// and cleared here after the first effect invocation.
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
if (!editor || !shouldSyncEditor(effectiveMode)) {
|
|
139
|
+
pendingFormatChangeRef.current = false;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (shouldSyncToEditor(readEditorContent(editor, activeFormat), value, pendingFormatChangeRef.current, lastEmittedRef.current)) {
|
|
143
|
+
editor.commands.setContent(value, { emitUpdate: false });
|
|
144
|
+
lastEmittedRef.current = value;
|
|
145
|
+
}
|
|
146
|
+
pendingFormatChangeRef.current = false;
|
|
147
|
+
}, [editor, value, activeFormat, effectiveMode]);
|
|
148
|
+
const handleFormatChange = React.useCallback((next) => {
|
|
149
|
+
if (next === activeFormat)
|
|
150
|
+
return;
|
|
151
|
+
// Set the guard BEFORE queueing the state update so the sync effect
|
|
152
|
+
// skips the intermediate render where activeFormat='next' but value
|
|
153
|
+
// still holds the previous format's string.
|
|
154
|
+
pendingFormatChangeRef.current = true;
|
|
155
|
+
setActiveFormat(next);
|
|
156
|
+
if (editor) {
|
|
157
|
+
const emitted = readEditorContent(editor, next);
|
|
158
|
+
lastEmittedRef.current = emitted;
|
|
159
|
+
onChange(emitted);
|
|
160
|
+
}
|
|
161
|
+
}, [activeFormat, editor, onChange]);
|
|
162
|
+
const switchToMode = React.useCallback((next) => {
|
|
163
|
+
// When entering a mode that shows the editor, refresh its content from
|
|
164
|
+
// value so any edits made in the source textarea (where the editor sync
|
|
165
|
+
// was suppressed) become visible. Also update lastEmittedRef so the
|
|
166
|
+
// upcoming sync effect treats the editor as authoritative.
|
|
167
|
+
if (next !== 'source' && editor) {
|
|
168
|
+
editor.commands.setContent(value, { emitUpdate: false });
|
|
169
|
+
lastEmittedRef.current = value;
|
|
170
|
+
}
|
|
171
|
+
setMode(next);
|
|
172
|
+
}, [editor, value]);
|
|
173
|
+
const toggleFullscreen = React.useCallback(() => {
|
|
174
|
+
setFullscreen((v) => !v);
|
|
175
|
+
}, []);
|
|
176
|
+
// Exit fullscreen on Escape, and lock body scroll while fullscreen.
|
|
177
|
+
React.useEffect(() => {
|
|
178
|
+
if (!fullscreen || typeof window === 'undefined')
|
|
179
|
+
return;
|
|
180
|
+
const onKey = (e) => {
|
|
181
|
+
if (e.key === 'Escape') {
|
|
182
|
+
e.stopPropagation();
|
|
183
|
+
setFullscreen(false);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const prevOverflow = document.body.style.overflow;
|
|
187
|
+
document.body.style.overflow = 'hidden';
|
|
188
|
+
window.addEventListener('keydown', onKey);
|
|
189
|
+
return () => {
|
|
190
|
+
window.removeEventListener('keydown', onKey);
|
|
191
|
+
document.body.style.overflow = prevOverflow;
|
|
192
|
+
};
|
|
193
|
+
}, [fullscreen]);
|
|
194
|
+
const promptLink = React.useCallback(() => {
|
|
195
|
+
if (!editor)
|
|
196
|
+
return;
|
|
197
|
+
const prev = editor.getAttributes('link')?.href ?? '';
|
|
198
|
+
const url = typeof window !== 'undefined' ? window.prompt(l.urlPrompt, prev) : null;
|
|
199
|
+
if (url == null)
|
|
200
|
+
return;
|
|
201
|
+
if (url === '') {
|
|
202
|
+
editor.chain().focus().extendMarkRange('link').unsetMark('link').run();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Tiptap 3's StarterKit doesn't ship Link by default; it's only available
|
|
206
|
+
// when the Link extension is included. Fall back to no-op if not present.
|
|
207
|
+
const chain = editor.chain().focus().extendMarkRange('link');
|
|
208
|
+
if (typeof chain.setLink === 'function') {
|
|
209
|
+
chain.setLink({ href: url }).run();
|
|
210
|
+
}
|
|
211
|
+
}, [editor, l.urlPrompt]);
|
|
212
|
+
if (!editor) {
|
|
213
|
+
return (<div className={cn('rounded-md border border-input bg-background text-sm text-muted-foreground', className)}>
|
|
214
|
+
<div className="p-3">{placeholder ?? ''}</div>
|
|
215
|
+
</div>);
|
|
216
|
+
}
|
|
217
|
+
return (<div className={cn('rounded-md border border-input bg-background shadow-sm', disabled && 'opacity-50', fullscreen && 'fixed inset-0 z-50 flex flex-col rounded-none border-0 shadow-none', className)}>
|
|
218
|
+
{/* Toolbar */}
|
|
219
|
+
<div className={cn('flex flex-wrap items-center gap-0.5 border-b border-border p-1', fullscreen && 'shrink-0 bg-background')}>
|
|
220
|
+
<ToolbarButton title={l.bold} active={editor.isActive('bold')} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().toggleBold().run()}>
|
|
221
|
+
<Bold className="size-4"/>
|
|
222
|
+
</ToolbarButton>
|
|
223
|
+
<ToolbarButton title={l.italic} active={editor.isActive('italic')} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().toggleItalic().run()}>
|
|
224
|
+
<Italic className="size-4"/>
|
|
225
|
+
</ToolbarButton>
|
|
226
|
+
<ToolbarButton title={l.strikethrough} active={editor.isActive('strike')} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().toggleStrike().run()}>
|
|
227
|
+
<Strikethrough className="size-4"/>
|
|
228
|
+
</ToolbarButton>
|
|
229
|
+
<ToolbarButton title={l.inlineCode} active={editor.isActive('code')} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().toggleCode().run()}>
|
|
230
|
+
<Code className="size-4"/>
|
|
231
|
+
</ToolbarButton>
|
|
232
|
+
|
|
233
|
+
<Separator orientation="vertical" className="mx-0.5 h-6"/>
|
|
234
|
+
|
|
235
|
+
{[1, 2, 3].map((level) => {
|
|
236
|
+
const Icon = HEADING_ICON[level];
|
|
237
|
+
return (<ToolbarButton key={level} title={l.heading.replace('{level}', String(level))} active={editor.isActive('heading', { level })} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().toggleHeading({ level }).run()}>
|
|
238
|
+
<Icon className="size-4"/>
|
|
239
|
+
</ToolbarButton>);
|
|
240
|
+
})}
|
|
241
|
+
|
|
242
|
+
<Separator orientation="vertical" className="mx-0.5 h-6"/>
|
|
243
|
+
|
|
244
|
+
<ToolbarButton title={l.bulletList} active={editor.isActive('bulletList')} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().toggleBulletList().run()}>
|
|
245
|
+
<List className="size-4"/>
|
|
246
|
+
</ToolbarButton>
|
|
247
|
+
<ToolbarButton title={l.numberedList} active={editor.isActive('orderedList')} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().toggleOrderedList().run()}>
|
|
248
|
+
<ListOrdered className="size-4"/>
|
|
249
|
+
</ToolbarButton>
|
|
250
|
+
<ToolbarButton title={l.blockquote} active={editor.isActive('blockquote')} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().toggleBlockquote().run()}>
|
|
251
|
+
<Quote className="size-4"/>
|
|
252
|
+
</ToolbarButton>
|
|
253
|
+
<ToolbarButton title={l.horizontalRule} disabled={effectiveMode === 'source' || disabled} onClick={() => editor.chain().focus().setHorizontalRule().run()}>
|
|
254
|
+
<Minus className="size-4"/>
|
|
255
|
+
</ToolbarButton>
|
|
256
|
+
<ToolbarButton title={l.insertLink} active={editor.isActive('link')} disabled={effectiveMode === 'source' || disabled} onClick={promptLink}>
|
|
257
|
+
<LinkIcon className="size-4"/>
|
|
258
|
+
</ToolbarButton>
|
|
259
|
+
|
|
260
|
+
<Separator orientation="vertical" className="mx-0.5 h-6"/>
|
|
261
|
+
|
|
262
|
+
<ToolbarButton title={l.undo} disabled={effectiveMode === 'source' || !editor.can().undo() || disabled} onClick={() => editor.chain().focus().undo().run()}>
|
|
263
|
+
<Undo2 className="size-4"/>
|
|
264
|
+
</ToolbarButton>
|
|
265
|
+
<ToolbarButton title={l.redo} disabled={effectiveMode === 'source' || !editor.can().redo() || disabled} onClick={() => editor.chain().focus().redo().run()}>
|
|
266
|
+
<Redo2 className="size-4"/>
|
|
267
|
+
</ToolbarButton>
|
|
268
|
+
|
|
269
|
+
<div className="ml-auto"/>
|
|
270
|
+
|
|
271
|
+
{/* Format selector — picks the on-disk markup of the value. Switching
|
|
272
|
+
re-emits the current editor content in the new format, so it can
|
|
273
|
+
be used mid-editing without losing state. */}
|
|
274
|
+
<div className="flex h-8 items-center overflow-hidden rounded-md border border-input">
|
|
275
|
+
{['html', 'markdown'].map((f) => (<button key={f} type="button" aria-pressed={activeFormat === f} disabled={disabled} onMouseDown={(e) => e.preventDefault()} onClick={() => handleFormatChange(f)} className={cn('h-full cursor-pointer px-2 text-xs font-medium uppercase transition-colors', activeFormat === f
|
|
276
|
+
? 'bg-secondary text-secondary-foreground'
|
|
277
|
+
: 'bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground', disabled && 'pointer-events-none opacity-50')}>
|
|
278
|
+
{f === 'html' ? 'HTML' : 'MD'}
|
|
279
|
+
</button>))}
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<Separator orientation="vertical" className="mx-0.5 h-6"/>
|
|
283
|
+
|
|
284
|
+
{/* View-mode selector — three modes: source / split / wysiwyg.
|
|
285
|
+
Split is only enabled in fullscreen since the side-by-side layout
|
|
286
|
+
requires the full viewport width to be usable. */}
|
|
287
|
+
<ToolbarButton title={l.source.replace('{format}', activeFormat)} active={effectiveMode === 'source'} disabled={disabled} onClick={() => switchToMode('source')}>
|
|
288
|
+
<Code2 className="size-4"/>
|
|
289
|
+
</ToolbarButton>
|
|
290
|
+
{/* Split button is hidden outside fullscreen — the side-by-side layout
|
|
291
|
+
requires full viewport width to be usable, so a disabled button
|
|
292
|
+
would just waste toolbar space. */}
|
|
293
|
+
{isSplitAvailable(fullscreen) && (<ToolbarButton title={l.splitView} active={effectiveMode === 'split'} disabled={disabled} onClick={() => switchToMode('split')}>
|
|
294
|
+
<Columns2 className="size-4"/>
|
|
295
|
+
</ToolbarButton>)}
|
|
296
|
+
<ToolbarButton title={l.visualEditor} active={effectiveMode === 'wysiwyg'} disabled={disabled} onClick={() => switchToMode('wysiwyg')}>
|
|
297
|
+
<Eye className="size-4"/>
|
|
298
|
+
</ToolbarButton>
|
|
299
|
+
|
|
300
|
+
<ToolbarButton title={fullscreen ? l.exitFullscreen : l.fullscreen} active={fullscreen} onClick={toggleFullscreen}>
|
|
301
|
+
{fullscreen ? <Minimize2 className="size-4"/> : <Maximize2 className="size-4"/>}
|
|
302
|
+
</ToolbarButton>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
{/* Body — three layouts driven by effectiveMode.
|
|
306
|
+
- source: textarea only
|
|
307
|
+
- wysiwyg: editor only
|
|
308
|
+
- split: textarea (left) + editor (right), both bound to the same
|
|
309
|
+
`value` so each pane stays in sync via onChange + the sync effect. */}
|
|
310
|
+
{effectiveMode === 'source' ? (<Textarea value={value} onChange={(e) => onChange(e.target.value)} onBlur={() => onBlur?.()} placeholder={placeholder} disabled={disabled} spellCheck={false} className={cn('w-full rounded-none border-0 font-mono text-xs shadow-none focus-visible:ring-0', fullscreen ? 'min-h-0 flex-1 resize-none' : 'min-h-[160px] resize-y')}/>) : effectiveMode === 'split' ? (<div className={cn('flex w-full', fullscreen ? 'min-h-0 flex-1' : 'min-h-[160px]')}>
|
|
311
|
+
<Textarea value={value} onChange={(e) => onChange(e.target.value)} onBlur={() => onBlur?.()} placeholder={placeholder} disabled={disabled} spellCheck={false} className={cn('w-1/2 rounded-none border-0 font-mono text-xs shadow-none focus-visible:ring-0', fullscreen ? 'min-h-0 resize-none' : 'min-h-[160px] resize-none')}/>
|
|
312
|
+
{/* Central divider — explicit fixed-width vertical bar so it's
|
|
313
|
+
clearly visible against both panes' backgrounds. */}
|
|
314
|
+
<div aria-hidden className="w-px shrink-0 self-stretch bg-border"/>
|
|
315
|
+
<EditorContent editor={editor} className={cn('w-1/2 overflow-auto', disabled && 'pointer-events-none', fullscreen && 'min-h-0 [&_.ProseMirror]:min-h-full')}/>
|
|
316
|
+
</div>) : (<EditorContent editor={editor} className={cn(disabled && 'pointer-events-none', fullscreen && 'min-h-0 flex-1 overflow-auto [&_.ProseMirror]:min-h-full')}/>)}
|
|
317
|
+
</div>);
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=richtext-editor.jsx.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-editor.jsx","sourceRoot":"","sources":["../../src/components/richtext-editor.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type RichtextMode = 'wysiwyg' | 'source' | 'split';
|
|
2
|
+
/**
|
|
3
|
+
* Whether the external `value`→editor sync effect should run for the given mode.
|
|
4
|
+
*
|
|
5
|
+
* In 'source' the textarea owns the content; the editor is hidden and we skip
|
|
6
|
+
* setContent to avoid wasted work. In 'wysiwyg' and 'split' the editor is
|
|
7
|
+
* visible and must reflect any external change to `value`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function shouldSyncEditor(mode: RichtextMode): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the user can pick the split mode in the current fullscreen state.
|
|
12
|
+
* Split is only useful at full viewport width — outside fullscreen the panes
|
|
13
|
+
* would be too narrow to be usable.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isSplitAvailable(fullscreen: boolean): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the *effective* mode to render. When the user has selected
|
|
18
|
+
* split but exits fullscreen, we transparently fall back to wysiwyg so the
|
|
19
|
+
* layout stays usable. The selected mode is preserved by the caller so that
|
|
20
|
+
* re-entering fullscreen restores the split view.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveMode(mode: RichtextMode, fullscreen: boolean): RichtextMode;
|
|
23
|
+
//# sourceMappingURL=richtext-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-mode.d.ts","sourceRoot":"","sources":["../../src/components/richtext-mode.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEzD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,GAAG,YAAY,CAGjF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Pure helpers for the RichtextEditor's view-mode logic.
|
|
2
|
+
//
|
|
3
|
+
// Three modes are supported:
|
|
4
|
+
// - 'wysiwyg' — only the Tiptap editor is visible
|
|
5
|
+
// - 'source' — only a Textarea (raw HTML or Markdown) is visible
|
|
6
|
+
// - 'split' — both side-by-side (only meaningful in fullscreen)
|
|
7
|
+
/**
|
|
8
|
+
* Whether the external `value`→editor sync effect should run for the given mode.
|
|
9
|
+
*
|
|
10
|
+
* In 'source' the textarea owns the content; the editor is hidden and we skip
|
|
11
|
+
* setContent to avoid wasted work. In 'wysiwyg' and 'split' the editor is
|
|
12
|
+
* visible and must reflect any external change to `value`.
|
|
13
|
+
*/
|
|
14
|
+
export function shouldSyncEditor(mode) {
|
|
15
|
+
return mode !== 'source';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether the user can pick the split mode in the current fullscreen state.
|
|
19
|
+
* Split is only useful at full viewport width — outside fullscreen the panes
|
|
20
|
+
* would be too narrow to be usable.
|
|
21
|
+
*/
|
|
22
|
+
export function isSplitAvailable(fullscreen) {
|
|
23
|
+
return fullscreen;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the *effective* mode to render. When the user has selected
|
|
27
|
+
* split but exits fullscreen, we transparently fall back to wysiwyg so the
|
|
28
|
+
* layout stays usable. The selected mode is preserved by the caller so that
|
|
29
|
+
* re-entering fullscreen restores the split view.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveMode(mode, fullscreen) {
|
|
32
|
+
if (mode === 'split' && !fullscreen)
|
|
33
|
+
return 'wysiwyg';
|
|
34
|
+
return mode;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=richtext-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-mode.js","sourceRoot":"","sources":["../../src/components/richtext-mode.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,6BAA6B;AAC7B,oDAAoD;AACpD,oEAAoE;AACpE,oEAAoE;AAIpE;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAkB;IACjD,OAAO,IAAI,KAAK,QAAQ,CAAA;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmB;IAClD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAkB,EAAE,UAAmB;IACjE,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAA;IACrD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface RichtextRenderProps {
|
|
3
|
+
value: string;
|
|
4
|
+
format?: 'html' | 'markdown';
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function RichtextRender({ value, format, className, }: RichtextRenderProps): React.ReactElement;
|
|
8
|
+
//# sourceMappingURL=richtext-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-render.d.ts","sourceRoot":"","sources":["../../src/components/richtext-render.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAQD,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,MAAe,EACf,SAAS,GACV,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAiB1C"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Read-only renderer for HTML/Markdown produced by <RichtextEditor>.
|
|
2
|
+
// Sanitizes HTML through DOMPurify before injection. Markdown is parsed
|
|
3
|
+
// with `marked`, then sanitized. Output is wrapped in Tailwind `prose`
|
|
4
|
+
// utilities so headings/lists/code blocks/etc. get long-form typography.
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import DOMPurify from 'dompurify';
|
|
7
|
+
import { marked } from 'marked';
|
|
8
|
+
import { cn } from '../lib/utils.js';
|
|
9
|
+
const proseRenderClass = 'prose prose-sm max-w-none text-foreground prose-headings:text-foreground ' +
|
|
10
|
+
'prose-p:text-foreground prose-strong:text-foreground prose-li:text-foreground ' +
|
|
11
|
+
'prose-code:text-foreground prose-pre:text-foreground prose-pre:bg-muted ' +
|
|
12
|
+
'prose-blockquote:text-muted-foreground prose-blockquote:border-border ' +
|
|
13
|
+
'prose-a:text-foreground dark:prose-invert';
|
|
14
|
+
function renderHtml(html) {
|
|
15
|
+
// DOMPurify is browser-only; on SSR fall back to escaping.
|
|
16
|
+
if (typeof window === 'undefined')
|
|
17
|
+
return html;
|
|
18
|
+
return DOMPurify.sanitize(html, { USE_PROFILES: { html: true } });
|
|
19
|
+
}
|
|
20
|
+
export function RichtextRender({ value, format = 'html', className, }) {
|
|
21
|
+
const html = React.useMemo(() => {
|
|
22
|
+
if (!value)
|
|
23
|
+
return '';
|
|
24
|
+
if (format === 'markdown') {
|
|
25
|
+
// marked.parse is sync when no async extensions are configured.
|
|
26
|
+
const out = marked.parse(value, { async: false });
|
|
27
|
+
return renderHtml(out);
|
|
28
|
+
}
|
|
29
|
+
return renderHtml(value);
|
|
30
|
+
}, [value, format]);
|
|
31
|
+
return (<div className={cn(proseRenderClass, className)} dangerouslySetInnerHTML={{ __html: html }}/>);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=richtext-render.jsx.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-render.jsx","sourceRoot":"","sources":["../../src/components/richtext-render.tsx"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,wEAAwE;AACxE,uEAAuE;AACvE,yEAAyE;AAEzE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,SAAS,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAEpC,MAAM,gBAAgB,GACpB,2EAA2E;IAC3E,gFAAgF;IAChF,0EAA0E;IAC1E,wEAAwE;IACxE,2CAA2C,CAAA;AAQ7C,SAAS,UAAU,CAAC,IAAY;IAC9B,2DAA2D;IAC3D,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,CAAA;IAC9C,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;AACnE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,MAAM,GAAG,MAAM,EACf,SAAS,GACW;IACpB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9B,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAA;QACrB,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;YAC1B,gEAAgE;YAChE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAW,CAAA;YAC3D,OAAO,UAAU,CAAC,GAAG,CAAC,CAAA;QACxB,CAAC;QACD,OAAO,UAAU,CAAC,KAAK,CAAC,CAAA;IAC1B,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAEnB,OAAO,CACL,CAAC,GAAG,CACF,SAAS,CAAC,CAAC,EAAE,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC,CAC3C,uBAAuB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAC1C,CACH,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RichtextFormat } from './richtext-editor.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads the current content from a Tiptap editor instance.
|
|
4
|
+
* Returns the HTML or Markdown string depending on `format`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readEditorContent(ed: {
|
|
7
|
+
getHTML(): string;
|
|
8
|
+
storage: unknown;
|
|
9
|
+
}, format: RichtextFormat): string;
|
|
10
|
+
/**
|
|
11
|
+
* Returns `true` if the editor content should be replaced with the external
|
|
12
|
+
* `value` (i.e. `editor.commands.setContent(value)` should be called).
|
|
13
|
+
*
|
|
14
|
+
* Returns `false` when:
|
|
15
|
+
*
|
|
16
|
+
* 1. `pendingFormatChange === true` — a format switch was just initiated.
|
|
17
|
+
* In this intermediate React render, `activeFormat` has already been
|
|
18
|
+
* updated to the new format while the external `value` still holds the
|
|
19
|
+
* previous format's string (the parent hasn't re-rendered yet).
|
|
20
|
+
* Calling `setContent` here would write the stale, wrong-format string
|
|
21
|
+
* into the editor and corrupt the content. The flag is cleared after the
|
|
22
|
+
* first effect run, so once `value` catches up the normal diff logic
|
|
23
|
+
* takes over again.
|
|
24
|
+
*
|
|
25
|
+
* 2. `value === lastEmitted` — the parent is just echoing back a value we
|
|
26
|
+
* ourselves emitted via onChange. This is the common race during fast
|
|
27
|
+
* typing: the user keeps typing while React is still committing the
|
|
28
|
+
* previous onChange, so by the time the effect runs the editor is
|
|
29
|
+
* *already* ahead of value. Calling setContent in that frame would
|
|
30
|
+
* rewind the editor to a stale state, dropping the most recent
|
|
31
|
+
* keystrokes and resetting the cursor. When value matches lastEmitted
|
|
32
|
+
* we trust the editor and skip the sync.
|
|
33
|
+
*
|
|
34
|
+
* 3. `editorContent === value` — the editor already has the correct content.
|
|
35
|
+
*/
|
|
36
|
+
export declare function shouldSyncToEditor(editorContent: string, value: string, pendingFormatChange: boolean, lastEmitted?: string): boolean;
|
|
37
|
+
//# sourceMappingURL=richtext-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-sync.d.ts","sourceRoot":"","sources":["../../src/components/richtext-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE;IAAE,OAAO,IAAI,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,EAC3C,MAAM,EAAE,cAAc,GACrB,MAAM,CAOR;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,mBAAmB,EAAE,OAAO,EAC5B,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAIT"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Pure helpers for RichtextEditor content sync logic.
|
|
2
|
+
// Extracted so they can be unit-tested without a DOM/React environment.
|
|
3
|
+
/**
|
|
4
|
+
* Reads the current content from a Tiptap editor instance.
|
|
5
|
+
* Returns the HTML or Markdown string depending on `format`.
|
|
6
|
+
*/
|
|
7
|
+
export function readEditorContent(ed, format) {
|
|
8
|
+
if (format === 'markdown') {
|
|
9
|
+
return (ed.storage.markdown?.getMarkdown?.() ?? '');
|
|
10
|
+
}
|
|
11
|
+
return ed.getHTML();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns `true` if the editor content should be replaced with the external
|
|
15
|
+
* `value` (i.e. `editor.commands.setContent(value)` should be called).
|
|
16
|
+
*
|
|
17
|
+
* Returns `false` when:
|
|
18
|
+
*
|
|
19
|
+
* 1. `pendingFormatChange === true` — a format switch was just initiated.
|
|
20
|
+
* In this intermediate React render, `activeFormat` has already been
|
|
21
|
+
* updated to the new format while the external `value` still holds the
|
|
22
|
+
* previous format's string (the parent hasn't re-rendered yet).
|
|
23
|
+
* Calling `setContent` here would write the stale, wrong-format string
|
|
24
|
+
* into the editor and corrupt the content. The flag is cleared after the
|
|
25
|
+
* first effect run, so once `value` catches up the normal diff logic
|
|
26
|
+
* takes over again.
|
|
27
|
+
*
|
|
28
|
+
* 2. `value === lastEmitted` — the parent is just echoing back a value we
|
|
29
|
+
* ourselves emitted via onChange. This is the common race during fast
|
|
30
|
+
* typing: the user keeps typing while React is still committing the
|
|
31
|
+
* previous onChange, so by the time the effect runs the editor is
|
|
32
|
+
* *already* ahead of value. Calling setContent in that frame would
|
|
33
|
+
* rewind the editor to a stale state, dropping the most recent
|
|
34
|
+
* keystrokes and resetting the cursor. When value matches lastEmitted
|
|
35
|
+
* we trust the editor and skip the sync.
|
|
36
|
+
*
|
|
37
|
+
* 3. `editorContent === value` — the editor already has the correct content.
|
|
38
|
+
*/
|
|
39
|
+
export function shouldSyncToEditor(editorContent, value, pendingFormatChange, lastEmitted) {
|
|
40
|
+
if (pendingFormatChange)
|
|
41
|
+
return false;
|
|
42
|
+
if (lastEmitted !== undefined && value === lastEmitted)
|
|
43
|
+
return false;
|
|
44
|
+
return editorContent !== value;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=richtext-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-sync.js","sourceRoot":"","sources":["../../src/components/richtext-sync.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,wEAAwE;AAIxE;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,EAA2C,EAC3C,MAAsB;IAEtB,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1B,OAAO,CACJ,EAAE,CAAC,OAAqD,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,CAC1F,CAAA;IACH,CAAC;IACD,OAAO,EAAE,CAAC,OAAO,EAAE,CAAA;AACrB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAqB,EACrB,KAAa,EACb,mBAA4B,EAC5B,WAAoB;IAEpB,IAAI,mBAAmB;QAAE,OAAO,KAAK,CAAA;IACrC,IAAI,WAAW,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW;QAAE,OAAO,KAAK,CAAA;IACpE,OAAO,aAAa,KAAK,KAAK,CAAA;AAChC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
|
3
|
+
export declare const ScrollArea: React.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
4
|
+
export declare const ScrollBar: React.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaScrollbarProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
5
|
+
//# sourceMappingURL=scroll-area.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-area.d.ts","sourceRoot":"","sources":["../../src/components/scroll-area.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,mBAAmB,MAAM,6BAA6B,CAAA;AAGlE,eAAO,MAAM,UAAU,+JAerB,CAAA;AAGF,eAAO,MAAM,SAAS,wKAiBpB,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
|
3
|
+
import { cn } from '../lib/utils.js';
|
|
4
|
+
export const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => (<ScrollAreaPrimitive.Root ref={ref} className={cn('relative overflow-hidden', className)} {...props}>
|
|
5
|
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
|
6
|
+
{children}
|
|
7
|
+
</ScrollAreaPrimitive.Viewport>
|
|
8
|
+
<ScrollBar />
|
|
9
|
+
<ScrollAreaPrimitive.Corner />
|
|
10
|
+
</ScrollAreaPrimitive.Root>));
|
|
11
|
+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
|
12
|
+
export const ScrollBar = React.forwardRef(({ className, orientation = 'vertical', ...props }, ref) => (<ScrollAreaPrimitive.ScrollAreaScrollbar ref={ref} orientation={orientation} className={cn('flex touch-none select-none transition-colors', orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]', orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]', className)} {...props}>
|
|
13
|
+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border"/>
|
|
14
|
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>));
|
|
15
|
+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
|
16
|
+
//# sourceMappingURL=scroll-area.jsx.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-area.jsx","sourceRoot":"","sources":["../../src/components/scroll-area.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,mBAAmB,MAAM,6BAA6B,CAAA;AAClE,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAEpC,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAGxC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAC5C,CAAC,mBAAmB,CAAC,IAAI,CACvB,GAAG,CAAC,CAAC,GAAG,CAAC,CACT,SAAS,CAAC,CAAC,EAAE,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAC,CACrD,IAAI,KAAK,CAAC,CAEV;IAAA,CAAC,mBAAmB,CAAC,QAAQ,CAAC,SAAS,CAAC,iCAAiC,CACvE;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,mBAAmB,CAAC,QAAQ,CAC9B;IAAA,CAAC,SAAS,CAAC,AAAD,EACV;IAAA,CAAC,mBAAmB,CAAC,MAAM,CAAC,AAAD,EAC7B;EAAA,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAC5B,CAAC,CAAA;AACF,UAAU,CAAC,WAAW,GAAG,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAA;AAE7D,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,CAGvC,CAAC,EAAE,SAAS,EAAE,WAAW,GAAG,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAC5D,CAAC,mBAAmB,CAAC,mBAAmB,CACtC,GAAG,CAAC,CAAC,GAAG,CAAC,CACT,WAAW,CAAC,CAAC,WAAW,CAAC,CACzB,SAAS,CAAC,CAAC,EAAE,CACX,+CAA+C,EAC/C,WAAW,KAAK,UAAU,IAAI,oDAAoD,EAClF,WAAW,KAAK,YAAY,IAAI,sDAAsD,EACtF,SAAS,CACV,CAAC,CACF,IAAI,KAAK,CAAC,CAEV;IAAA,CAAC,mBAAmB,CAAC,eAAe,CAAC,SAAS,CAAC,wCAAwC,EACzF;EAAA,EAAE,mBAAmB,CAAC,mBAAmB,CAAC,CAC3C,CAAC,CAAA;AACF,SAAS,CAAC,WAAW,GAAG,mBAAmB,CAAC,mBAAmB,CAAC,WAAW,CAAA"}
|