@pilotiq/tiptap 3.2.0 → 3.3.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/PlainTextEditor.d.ts +91 -0
- package/dist/PlainTextEditor.d.ts.map +1 -0
- package/dist/PlainTextEditor.js +157 -0
- package/dist/PlainTextEditor.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/react/CollabTextRenderer.d.ts +26 -0
- package/dist/react/CollabTextRenderer.d.ts.map +1 -0
- package/dist/react/CollabTextRenderer.js +149 -0
- package/dist/react/CollabTextRenderer.js.map +1 -0
- package/dist/react/MarkdownEditor.d.ts +30 -0
- package/dist/react/MarkdownEditor.d.ts.map +1 -0
- package/dist/react/MarkdownEditor.js +354 -0
- package/dist/react/MarkdownEditor.js.map +1 -0
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +16 -1
- package/dist/register.js.map +1 -1
- package/package.json +4 -2
- package/src/PlainTextEditor.test.ts +158 -0
- package/src/PlainTextEditor.ts +229 -0
- package/src/index.ts +6 -0
- package/src/react/CollabTextRenderer.tsx +165 -0
- package/src/react/MarkdownEditor.tsx +506 -0
- package/src/register.ts +16 -1
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useEditor, EditorContent } from '@tiptap/react';
|
|
4
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
5
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
6
|
+
import Image from '@tiptap/extension-image';
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
import { Markdown } from 'tiptap-markdown';
|
|
9
|
+
import { useCollabRoom, getCollabExtensions, } from '@pilotiq/pilotiq/react';
|
|
10
|
+
// Inline lucide.dev SVGs — same posture as `toolbarButtons.tsx` so this
|
|
11
|
+
// package doesn't pull `lucide-react` as a peer dep. Keep stroke / size
|
|
12
|
+
// consistent with the rich-text toolbar.
|
|
13
|
+
const ICON_PROPS = {
|
|
14
|
+
width: 14, height: 14, viewBox: '0 0 24 24',
|
|
15
|
+
fill: 'none', stroke: 'currentColor',
|
|
16
|
+
strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
17
|
+
'aria-hidden': 'true',
|
|
18
|
+
};
|
|
19
|
+
const Spinner = (_jsx("svg", { ...ICON_PROPS, className: "animate-spin", children: _jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
|
|
20
|
+
const SvgIcons = {
|
|
21
|
+
bold: (_jsxs("svg", { ...ICON_PROPS, strokeWidth: 2.25, children: [_jsx("path", { d: "M6 12h9a4 4 0 0 1 0 8H6Z" }), _jsx("path", { d: "M6 4h7a4 4 0 0 1 0 8H6Z" })] })),
|
|
22
|
+
italic: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("line", { x1: "19", y1: "4", x2: "10", y2: "4" }), _jsx("line", { x1: "14", y1: "20", x2: "5", y2: "20" }), _jsx("line", { x1: "15", y1: "4", x2: "9", y2: "20" })] })),
|
|
23
|
+
strike: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("path", { d: "M16 4H9a3 3 0 0 0-2.83 4" }), _jsx("path", { d: "M14 12a4 4 0 0 1 0 8H6" }), _jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" })] })),
|
|
24
|
+
link: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }), _jsx("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.72-1.71" })] })),
|
|
25
|
+
heading: _jsx("span", { className: "text-xs font-semibold leading-none", children: "H2" }),
|
|
26
|
+
bulletList: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("line", { x1: "8", y1: "6", x2: "21", y2: "6" }), _jsx("line", { x1: "8", y1: "12", x2: "21", y2: "12" }), _jsx("line", { x1: "8", y1: "18", x2: "21", y2: "18" }), _jsx("circle", { cx: "4", cy: "6", r: "1" }), _jsx("circle", { cx: "4", cy: "12", r: "1" }), _jsx("circle", { cx: "4", cy: "18", r: "1" })] })),
|
|
27
|
+
orderedList: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("line", { x1: "10", y1: "6", x2: "21", y2: "6" }), _jsx("line", { x1: "10", y1: "12", x2: "21", y2: "12" }), _jsx("line", { x1: "10", y1: "18", x2: "21", y2: "18" }), _jsx("path", { d: "M4 6h1v4" }), _jsx("path", { d: "M4 10h2" }), _jsx("path", { d: "M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" })] })),
|
|
28
|
+
blockquote: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("path", { d: "M3 21c3 0 7-1 7-8V5a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h3" }), _jsx("path", { d: "M15 21c3 0 7-1 7-8V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h3" })] })),
|
|
29
|
+
codeBlock: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("polyline", { points: "16 18 22 12 16 6" }), _jsx("polyline", { points: "8 6 2 12 8 18" })] })),
|
|
30
|
+
attachFiles: (_jsx("svg", { ...ICON_PROPS, children: _jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) })),
|
|
31
|
+
pencil: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("path", { d: "M12 20h9" }), _jsx("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z" })] })),
|
|
32
|
+
source: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), _jsx("polyline", { points: "14 2 14 8 20 8" }), _jsx("line", { x1: "9", y1: "13", x2: "15", y2: "13" }), _jsx("line", { x1: "9", y1: "17", x2: "15", y2: "17" })] })),
|
|
33
|
+
eye: (_jsxs("svg", { ...ICON_PROPS, children: [_jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8Z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] })),
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Plug-in WYSIWYG markdown editor registered with pilotiq core's
|
|
37
|
+
* `registerMarkdownEditor()`. Replaces the legacy textarea + manual-toolbar
|
|
38
|
+
* UI with a real rich editor; serializes to markdown on every change via
|
|
39
|
+
* `tiptap-markdown` so the wire format stays a plain markdown string under
|
|
40
|
+
* the field name.
|
|
41
|
+
*
|
|
42
|
+
* Collab-aware: when a `<RecordCollabRoom>` is mounted up-tree AND
|
|
43
|
+
* `registerCollabExtensions()` ran (both shipped by `@pilotiq-pro/collab`),
|
|
44
|
+
* the editor binds to the room's shared `Y.XmlFragment` via Tiptap's
|
|
45
|
+
* `Collaboration` extension. Every peer mounts the same editor against the
|
|
46
|
+
* same fragment; markdown serialization runs locally per peer so only the
|
|
47
|
+
* ProseMirror tree crosses the wire.
|
|
48
|
+
*
|
|
49
|
+
* Tabs (top-right):
|
|
50
|
+
* - **Editor** (default) — WYSIWYG.
|
|
51
|
+
* - **Source** — raw markdown textarea; on switch back to Editor the editor
|
|
52
|
+
* parses the textarea contents (round-trips through tiptap-markdown).
|
|
53
|
+
* - **Preview** — read-only render of the current markdown via the editor's
|
|
54
|
+
* own HTML output. Same view a user would see on the public site if the
|
|
55
|
+
* resource ships a read-side renderer.
|
|
56
|
+
*
|
|
57
|
+
* Single-source-of-truth posture: the editor's `onUpdate` is the canonical
|
|
58
|
+
* write path. Source-tab edits flow back through the editor on tab-switch
|
|
59
|
+
* (no dual state, no drift between source and editor doc).
|
|
60
|
+
*/
|
|
61
|
+
export function MarkdownEditor({ name, defaultValue, placeholder, disabled = false, onChange, onBlur, toolbarButtons, minHeight, maxHeight, fileAttachmentsDirectory, fileAttachmentsVisibility, uploadUrl, }) {
|
|
62
|
+
const room = useCollabRoom();
|
|
63
|
+
const factory = getCollabExtensions();
|
|
64
|
+
const collabActive = !!(room && factory);
|
|
65
|
+
const [tab, setTab] = useState('editor');
|
|
66
|
+
const [sourceDraft, setSourceDraft] = useState(defaultValue);
|
|
67
|
+
const [uploading, setUploading] = useState(false);
|
|
68
|
+
const fileInputRef = useRef(null);
|
|
69
|
+
// Collab extension factory output. Built once per editor mount (the
|
|
70
|
+
// factory closes over the room's ydoc + provider + field name); keyed
|
|
71
|
+
// remount below ensures we never swap it underneath the running editor.
|
|
72
|
+
const collabExtensions = useMemo(() => {
|
|
73
|
+
if (!collabActive || !room || !factory)
|
|
74
|
+
return [];
|
|
75
|
+
return factory({
|
|
76
|
+
ydoc: room.ydoc,
|
|
77
|
+
provider: room.provider,
|
|
78
|
+
fieldName: name,
|
|
79
|
+
...(room.user ? { user: room.user } : {}),
|
|
80
|
+
});
|
|
81
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
82
|
+
}, [collabActive]);
|
|
83
|
+
const editor = useEditor({
|
|
84
|
+
editable: !disabled,
|
|
85
|
+
extensions: [
|
|
86
|
+
StarterKit.configure({
|
|
87
|
+
link: { openOnClick: false, autolink: true },
|
|
88
|
+
// Collaboration brings its own Yjs-backed history — disable
|
|
89
|
+
// StarterKit's local undoRedo when collab is active (else Tiptap
|
|
90
|
+
// logs a "not compatible with @tiptap/extension-undo-redo" warning).
|
|
91
|
+
...(collabActive ? { undoRedo: false } : {}),
|
|
92
|
+
}),
|
|
93
|
+
// Markdown round-trip — parses `content` (when non-collab) and
|
|
94
|
+
// exposes `editor.storage.markdown.getMarkdown()`. We pass `html:
|
|
95
|
+
// false` because the wire format is markdown only.
|
|
96
|
+
Markdown.configure({
|
|
97
|
+
html: false,
|
|
98
|
+
tightLists: true,
|
|
99
|
+
breaks: false,
|
|
100
|
+
linkify: true,
|
|
101
|
+
transformPastedText: true,
|
|
102
|
+
transformCopiedText: true,
|
|
103
|
+
}),
|
|
104
|
+
Image.configure({ inline: false, allowBase64: false }),
|
|
105
|
+
Placeholder.configure({ placeholder: placeholder ?? 'Write in markdown…' }),
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
...collabExtensions,
|
|
108
|
+
],
|
|
109
|
+
// Collab takes ownership of the document — passing `content` would
|
|
110
|
+
// race the Y.XmlFragment sync. Seed after first connect (effect below).
|
|
111
|
+
content: collabActive ? '' : defaultValue,
|
|
112
|
+
onUpdate({ editor }) {
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
114
|
+
const storage = editor.storage.markdown;
|
|
115
|
+
const md = typeof storage?.getMarkdown === 'function' ? storage.getMarkdown() : '';
|
|
116
|
+
onChange(md);
|
|
117
|
+
},
|
|
118
|
+
onBlur() { onBlur?.(); },
|
|
119
|
+
},
|
|
120
|
+
// Re-mount when collab toggles. Other props (name, placeholder) are
|
|
121
|
+
// stable per mount — the field renderer doesn't swap them at runtime.
|
|
122
|
+
[collabActive]);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!editor)
|
|
125
|
+
return;
|
|
126
|
+
editor.setEditable(!disabled && tab === 'editor');
|
|
127
|
+
}, [editor, disabled, tab]);
|
|
128
|
+
// First-load seed for collab. Collaboration starts the editor empty
|
|
129
|
+
// regardless of `content`; once the provider syncs from the server we
|
|
130
|
+
// check whether the field's `Y.XmlFragment` was ever written. Empty +
|
|
131
|
+
// we have an initial value = first session for this record. Mirrors
|
|
132
|
+
// the rich-text TiptapEditor seed path and the CollabTextRenderer seed.
|
|
133
|
+
const [hasSeeded, setHasSeeded] = useState(false);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (!editor || !collabActive || !room || hasSeeded)
|
|
136
|
+
return;
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
138
|
+
const ydoc = room.ydoc;
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
+
const provider = room.provider;
|
|
141
|
+
if (!ydoc || !provider)
|
|
142
|
+
return;
|
|
143
|
+
const trySeed = () => {
|
|
144
|
+
try {
|
|
145
|
+
const fragment = ydoc.getXmlFragment(name);
|
|
146
|
+
if (fragment && fragment.length === 0 && defaultValue) {
|
|
147
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
|
+
const cmd = editor.commands.setContent;
|
|
149
|
+
if (cmd)
|
|
150
|
+
cmd(defaultValue);
|
|
151
|
+
}
|
|
152
|
+
setHasSeeded(true);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
setHasSeeded(true);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
if (provider.synced) {
|
|
159
|
+
trySeed();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
provider.once('synced', trySeed);
|
|
163
|
+
return () => {
|
|
164
|
+
try {
|
|
165
|
+
provider.off?.('synced', trySeed);
|
|
166
|
+
}
|
|
167
|
+
catch { /* ignore */ }
|
|
168
|
+
};
|
|
169
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
170
|
+
}, [editor, collabActive, room]);
|
|
171
|
+
// Source-tab → Editor: parse the textarea back into the editor (this also
|
|
172
|
+
// emits onChange via the editor's onUpdate). One-way during the same flip.
|
|
173
|
+
const enterEditorTab = () => {
|
|
174
|
+
if (tab === 'source' && editor) {
|
|
175
|
+
try {
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
|
+
;
|
|
178
|
+
editor.commands.setContent(sourceDraft);
|
|
179
|
+
}
|
|
180
|
+
catch { /* ignore parse errors */ }
|
|
181
|
+
}
|
|
182
|
+
setTab('editor');
|
|
183
|
+
};
|
|
184
|
+
const enterSourceTab = () => {
|
|
185
|
+
if (editor) {
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
|
+
const storage = editor.storage.markdown;
|
|
188
|
+
const md = typeof storage?.getMarkdown === 'function' ? storage.getMarkdown() : '';
|
|
189
|
+
setSourceDraft(md);
|
|
190
|
+
}
|
|
191
|
+
setTab('source');
|
|
192
|
+
};
|
|
193
|
+
const enterPreviewTab = () => {
|
|
194
|
+
setTab('preview');
|
|
195
|
+
};
|
|
196
|
+
// Live preview HTML is the editor's own HTML output — same renderer that
|
|
197
|
+
// produced what the user sees in the Editor tab. Read-only.
|
|
198
|
+
const previewHtml = useMemo(() => {
|
|
199
|
+
if (tab !== 'preview' || !editor)
|
|
200
|
+
return '';
|
|
201
|
+
try {
|
|
202
|
+
return editor.getHTML();
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return '';
|
|
206
|
+
}
|
|
207
|
+
}, [tab, editor]);
|
|
208
|
+
const uploadAndInsert = async (file) => {
|
|
209
|
+
if (!uploadUrl || !editor)
|
|
210
|
+
return;
|
|
211
|
+
setUploading(true);
|
|
212
|
+
try {
|
|
213
|
+
const fd = new FormData();
|
|
214
|
+
fd.append('file', file);
|
|
215
|
+
if (fileAttachmentsDirectory)
|
|
216
|
+
fd.append('directory', fileAttachmentsDirectory);
|
|
217
|
+
if (fileAttachmentsVisibility)
|
|
218
|
+
fd.append('visibility', fileAttachmentsVisibility);
|
|
219
|
+
fd.append('fieldName', name);
|
|
220
|
+
const res = await fetch(uploadUrl, { method: 'POST', body: fd, headers: { Accept: 'application/json' } });
|
|
221
|
+
const data = await res.json().catch(() => ({}));
|
|
222
|
+
if (!res.ok || !data.ok || !data.url)
|
|
223
|
+
return;
|
|
224
|
+
const isImage = file.type.startsWith('image/');
|
|
225
|
+
if (isImage) {
|
|
226
|
+
editor.chain().focus().setImage({ src: data.url, alt: file.name }).run();
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
editor.chain().focus().insertContent(`[${file.name}](${data.url})`).run();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
setUploading(false);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
const onAttachClick = () => {
|
|
237
|
+
const el = fileInputRef.current;
|
|
238
|
+
if (el)
|
|
239
|
+
el.click();
|
|
240
|
+
};
|
|
241
|
+
const onFilePicked = (e) => {
|
|
242
|
+
const file = e.target.files?.[0];
|
|
243
|
+
if (file)
|
|
244
|
+
void uploadAndInsert(file);
|
|
245
|
+
e.target.value = '';
|
|
246
|
+
};
|
|
247
|
+
// Toolbar item resolution. The pilotiq-side ids map onto Tiptap commands.
|
|
248
|
+
// attachFiles is gated on a configured uploadUrl (server strips it server-
|
|
249
|
+
// side when no adapter is registered, but defensive double-gate here too).
|
|
250
|
+
const allow = useMemo(() => new Set(toolbarButtons), [toolbarButtons]);
|
|
251
|
+
const canAttach = allow.has('attachFiles') && !!uploadUrl;
|
|
252
|
+
const exec = (id) => {
|
|
253
|
+
if (!editor)
|
|
254
|
+
return;
|
|
255
|
+
const c = editor.chain().focus();
|
|
256
|
+
switch (id) {
|
|
257
|
+
case 'bold':
|
|
258
|
+
c.toggleBold().run();
|
|
259
|
+
break;
|
|
260
|
+
case 'italic':
|
|
261
|
+
c.toggleItalic().run();
|
|
262
|
+
break;
|
|
263
|
+
case 'strike':
|
|
264
|
+
c.toggleStrike().run();
|
|
265
|
+
break;
|
|
266
|
+
case 'link': {
|
|
267
|
+
const prev = editor.getAttributes('link').href;
|
|
268
|
+
const url = window.prompt('URL', prev ?? '') ?? '';
|
|
269
|
+
if (url === '')
|
|
270
|
+
c.unsetLink().run();
|
|
271
|
+
else
|
|
272
|
+
c.extendMarkRange('link').setLink({ href: url }).run();
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case 'heading':
|
|
276
|
+
c.toggleHeading({ level: 2 }).run();
|
|
277
|
+
break;
|
|
278
|
+
case 'bulletList':
|
|
279
|
+
c.toggleBulletList().run();
|
|
280
|
+
break;
|
|
281
|
+
case 'orderedList':
|
|
282
|
+
c.toggleOrderedList().run();
|
|
283
|
+
break;
|
|
284
|
+
case 'blockquote':
|
|
285
|
+
c.toggleBlockquote().run();
|
|
286
|
+
break;
|
|
287
|
+
case 'codeBlock':
|
|
288
|
+
c.toggleCodeBlock().run();
|
|
289
|
+
break;
|
|
290
|
+
case 'attachFiles':
|
|
291
|
+
onAttachClick();
|
|
292
|
+
break;
|
|
293
|
+
default: /* unknown id — skip */ break;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const isActive = (id) => {
|
|
297
|
+
if (!editor)
|
|
298
|
+
return false;
|
|
299
|
+
switch (id) {
|
|
300
|
+
case 'bold': return editor.isActive('bold');
|
|
301
|
+
case 'italic': return editor.isActive('italic');
|
|
302
|
+
case 'strike': return editor.isActive('strike');
|
|
303
|
+
case 'link': return editor.isActive('link');
|
|
304
|
+
case 'heading': return editor.isActive('heading', { level: 2 });
|
|
305
|
+
case 'bulletList': return editor.isActive('bulletList');
|
|
306
|
+
case 'orderedList': return editor.isActive('orderedList');
|
|
307
|
+
case 'blockquote': return editor.isActive('blockquote');
|
|
308
|
+
case 'codeBlock': return editor.isActive('codeBlock');
|
|
309
|
+
default: return false;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const labels = {
|
|
313
|
+
bold: 'Bold (⌘B)',
|
|
314
|
+
italic: 'Italic (⌘I)',
|
|
315
|
+
strike: 'Strikethrough',
|
|
316
|
+
link: 'Link (⌘K)',
|
|
317
|
+
heading: 'Heading',
|
|
318
|
+
bulletList: 'Bulleted list',
|
|
319
|
+
orderedList: 'Numbered list',
|
|
320
|
+
blockquote: 'Quote',
|
|
321
|
+
codeBlock: 'Code block',
|
|
322
|
+
attachFiles: 'Attach file',
|
|
323
|
+
};
|
|
324
|
+
const wrapperStyle = {};
|
|
325
|
+
if (minHeight)
|
|
326
|
+
wrapperStyle.minHeight = minHeight;
|
|
327
|
+
if (maxHeight)
|
|
328
|
+
wrapperStyle.maxHeight = maxHeight;
|
|
329
|
+
return (_jsxs("div", { className: "flex flex-col rounded-md border bg-background", children: [canAttach && (_jsx("input", { ref: fileInputRef, type: "file", className: "hidden", onChange: onFilePicked })), _jsxs("div", { className: "flex items-center justify-between border-b px-2 py-1 gap-2", children: [_jsxs("div", { className: "flex items-center gap-0.5", children: [_jsxs(TabButton, { active: tab === 'editor', onClick: enterEditorTab, children: [SvgIcons['pencil'], " Editor"] }), _jsxs(TabButton, { active: tab === 'source', onClick: enterSourceTab, children: [SvgIcons['source'], " Source"] }), _jsxs(TabButton, { active: tab === 'preview', onClick: enterPreviewTab, children: [SvgIcons['eye'], " Preview"] })] }), tab === 'editor' && toolbarButtons.length > 0 && (_jsx("div", { className: "flex items-center gap-0.5", children: toolbarButtons.map((b) => {
|
|
330
|
+
if (b === 'attachFiles' && !canAttach)
|
|
331
|
+
return null;
|
|
332
|
+
const icon = SvgIcons[b];
|
|
333
|
+
if (!icon)
|
|
334
|
+
return null;
|
|
335
|
+
const isAttach = b === 'attachFiles';
|
|
336
|
+
const active = isActive(b);
|
|
337
|
+
return (_jsx("button", { type: "button", className: [
|
|
338
|
+
'inline-flex size-7 items-center justify-center rounded text-foreground transition-colors',
|
|
339
|
+
active
|
|
340
|
+
? 'bg-accent text-accent-foreground'
|
|
341
|
+
: 'hover:bg-accent hover:text-accent-foreground',
|
|
342
|
+
'disabled:opacity-50',
|
|
343
|
+
].join(' '), onClick: () => exec(b), disabled: disabled || (isAttach && uploading), title: labels[b] ?? b, "aria-label": labels[b] ?? b, "aria-pressed": active, children: isAttach && uploading ? Spinner : icon }, b));
|
|
344
|
+
}) }))] }), tab === 'editor' && (_jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none px-3 py-2 [&_.ProseMirror]:outline-none [&_.ProseMirror]:min-h-[6rem]", style: wrapperStyle, children: _jsx(EditorContent, { editor: editor }) })), tab === 'source' && (_jsx("textarea", { className: "w-full resize-y bg-transparent px-3 py-2 text-sm font-mono leading-relaxed outline-none disabled:opacity-50", style: wrapperStyle, value: sourceDraft, onChange: (e) => setSourceDraft(e.target.value), ...(placeholder !== undefined ? { placeholder } : {}), disabled: disabled, "aria-label": `${name} (markdown source)` })), tab === 'preview' && (_jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none px-3 py-2", style: wrapperStyle, dangerouslySetInnerHTML: { __html: previewHtml || '<p class="text-muted-foreground italic">Nothing to preview</p>' } }))] }));
|
|
345
|
+
}
|
|
346
|
+
function TabButton({ active, onClick, children }) {
|
|
347
|
+
return (_jsx("button", { type: "button", className: [
|
|
348
|
+
'inline-flex items-center gap-1 rounded px-2 py-1 text-xs font-medium transition-colors',
|
|
349
|
+
active
|
|
350
|
+
? 'bg-accent text-accent-foreground'
|
|
351
|
+
: 'text-muted-foreground hover:text-foreground',
|
|
352
|
+
].join(' '), onClick: onClick, children: children }));
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=MarkdownEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownEditor.js","sourceRoot":"","sources":["../../src/react/MarkdownEditor.tsx"],"names":[],"mappings":"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"}
|
package/dist/register.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAgBrC"}
|
package/dist/register.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { registerFieldRenderer } from '@pilotiq/pilotiq/react';
|
|
1
|
+
import { registerFieldRenderer, registerCollabTextRenderer, registerMarkdownEditor } from '@pilotiq/pilotiq/react';
|
|
2
2
|
import { registerRichTextRenderer } from '@pilotiq/pilotiq/richtext';
|
|
3
3
|
import { TiptapEditor } from './react/TiptapEditor.js';
|
|
4
|
+
import { CollabTextRenderer } from './react/CollabTextRenderer.js';
|
|
5
|
+
import { MarkdownEditor } from './react/MarkdownEditor.js';
|
|
4
6
|
import { renderRichTextToHtml, isRichTextValue } from './render.js';
|
|
5
7
|
/**
|
|
6
8
|
* Register the Tiptap editor as the pilotiq renderer for `fieldType: 'richtext'`.
|
|
@@ -23,5 +25,18 @@ import { renderRichTextToHtml, isRichTextValue } from './render.js';
|
|
|
23
25
|
export function registerTiptap() {
|
|
24
26
|
registerFieldRenderer('richtext', TiptapEditor);
|
|
25
27
|
registerRichTextRenderer(renderRichTextToHtml, isRichTextValue);
|
|
28
|
+
// Phase B — opt every plain-text field in the panel into y-prosemirror
|
|
29
|
+
// backing when collab is on. `TextLikeInput` checks this registry; if it's
|
|
30
|
+
// populated AND a `<RecordCollabRoom>` is up-tree AND the field hasn't opted
|
|
31
|
+
// out via `.collab(false)`, the renderer mounts `CollabTextRenderer`
|
|
32
|
+
// instead of the legacy `Y.Text` + `computeDelta` + `preserveCursor` path.
|
|
33
|
+
registerCollabTextRenderer(CollabTextRenderer);
|
|
34
|
+
// WYSIWYG markdown editor — replaces `MarkdownField`'s legacy textarea +
|
|
35
|
+
// manual-toolbar path with a real rich editor that serializes to markdown
|
|
36
|
+
// via `tiptap-markdown` on every change. Collab-aware on the same
|
|
37
|
+
// `useCollabRoom()` + `getCollabExtensions()` plumbing as the rich-text
|
|
38
|
+
// editor. Without `@pilotiq/tiptap` installed, `MarkdownInput` falls back
|
|
39
|
+
// to the textarea path so panels that skip the adapter still work.
|
|
40
|
+
registerMarkdownEditor(MarkdownEditor);
|
|
26
41
|
}
|
|
27
42
|
//# sourceMappingURL=register.js.map
|
package/dist/register.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAClH,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEnE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,cAAc;IAC5B,qBAAqB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IAC/C,wBAAwB,CAAC,oBAAoB,EAAE,eAAe,CAAC,CAAA;IAC/D,uEAAuE;IACvE,2EAA2E;IAC3E,6EAA6E;IAC7E,qEAAqE;IACrE,2EAA2E;IAC3E,0BAA0B,CAAC,kBAAkB,CAAC,CAAA;IAC9C,yEAAyE;IACzE,0EAA0E;IAC1E,kEAAkE;IAClE,wEAAwE;IACxE,0EAA0E;IAC1E,mEAAmE;IACnE,sBAAsB,CAAC,cAAc,CAAC,CAAA;AACxC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilotiq/tiptap",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Tiptap rich-text editor adapter for @pilotiq/pilotiq — slash menu, draggable blocks, custom-block API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@tiptap/extension-table": "3.22.4",
|
|
52
52
|
"@tiptap/extension-details": "3.22.4",
|
|
53
53
|
"@tiptap/suggestion": "^3",
|
|
54
|
+
"tiptap-markdown": "^0.9",
|
|
54
55
|
"react": "^18 || ^19",
|
|
55
56
|
"react-dom": "^18 || ^19"
|
|
56
57
|
},
|
|
@@ -73,13 +74,14 @@
|
|
|
73
74
|
"@tiptap/extension-table": "3.22.4",
|
|
74
75
|
"@tiptap/extension-details": "3.22.4",
|
|
75
76
|
"@tiptap/suggestion": "^3",
|
|
77
|
+
"tiptap-markdown": "^0.9",
|
|
76
78
|
"@types/node": "^20",
|
|
77
79
|
"@types/react": "^19",
|
|
78
80
|
"@types/react-dom": "^19",
|
|
79
81
|
"react": "^19",
|
|
80
82
|
"react-dom": "^19",
|
|
81
83
|
"typescript": "^5",
|
|
82
|
-
"@pilotiq/pilotiq": "^0.
|
|
84
|
+
"@pilotiq/pilotiq": "^0.15.0"
|
|
83
85
|
},
|
|
84
86
|
"author": "Suleiman Shahbari",
|
|
85
87
|
"scripts": {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createPlainTextEditor,
|
|
6
|
+
plainTextToDoc,
|
|
7
|
+
type PlainTextEditorOptions,
|
|
8
|
+
} from './PlainTextEditor.js'
|
|
9
|
+
|
|
10
|
+
describe('plainTextToDoc — single-line', () => {
|
|
11
|
+
it('empty string yields one empty paragraph', () => {
|
|
12
|
+
assert.deepEqual(plainTextToDoc('', false), {
|
|
13
|
+
type: 'doc',
|
|
14
|
+
content: [{ type: 'paragraph' }],
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('wraps a single run of text in one paragraph', () => {
|
|
19
|
+
assert.deepEqual(plainTextToDoc('hello', false), {
|
|
20
|
+
type: 'doc',
|
|
21
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'hello' }] }],
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('strips embedded newlines (LF and CRLF) — single-line schema permits one paragraph only', () => {
|
|
26
|
+
assert.deepEqual(plainTextToDoc('a\nb', false), {
|
|
27
|
+
type: 'doc',
|
|
28
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'ab' }] }],
|
|
29
|
+
})
|
|
30
|
+
assert.deepEqual(plainTextToDoc('a\r\nb', false), {
|
|
31
|
+
type: 'doc',
|
|
32
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'ab' }] }],
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('plainTextToDoc — multi-line', () => {
|
|
38
|
+
it('empty string yields one empty paragraph', () => {
|
|
39
|
+
assert.deepEqual(plainTextToDoc('', true), {
|
|
40
|
+
type: 'doc',
|
|
41
|
+
content: [{ type: 'paragraph' }],
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('splits LF-separated lines into separate paragraphs', () => {
|
|
46
|
+
assert.deepEqual(plainTextToDoc('a\nb', true), {
|
|
47
|
+
type: 'doc',
|
|
48
|
+
content: [
|
|
49
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'a' }] },
|
|
50
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'b' }] },
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('preserves empty lines as empty paragraphs', () => {
|
|
56
|
+
assert.deepEqual(plainTextToDoc('a\n\nb', true), {
|
|
57
|
+
type: 'doc',
|
|
58
|
+
content: [
|
|
59
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'a' }] },
|
|
60
|
+
{ type: 'paragraph' },
|
|
61
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'b' }] },
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('normalises CRLF to single paragraph splits', () => {
|
|
67
|
+
assert.deepEqual(plainTextToDoc('a\r\nb', true), {
|
|
68
|
+
type: 'doc',
|
|
69
|
+
content: [
|
|
70
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'a' }] },
|
|
71
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'b' }] },
|
|
72
|
+
],
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('createPlainTextEditor — config shape', () => {
|
|
78
|
+
function names(extensions: ReadonlyArray<{ name: string }>): string[] {
|
|
79
|
+
return extensions.map((e) => e.name)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
it('default config: single-line, editable, schema + single-line keymap only', () => {
|
|
83
|
+
const cfg = createPlainTextEditor()
|
|
84
|
+
assert.equal(cfg.editable, true)
|
|
85
|
+
assert.equal(cfg.content, '')
|
|
86
|
+
const exts = (cfg.extensions ?? []) as Array<{ name: string }>
|
|
87
|
+
assert.deepEqual(names(exts), ['doc', 'paragraph', 'text', 'plainTextSingleLineKeymap'])
|
|
88
|
+
assert.equal(cfg.editorProps, undefined)
|
|
89
|
+
assert.equal(cfg.onUpdate, undefined)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('multiline mode drops the single-line keymap', () => {
|
|
93
|
+
const cfg = createPlainTextEditor({ multiline: true })
|
|
94
|
+
const exts = (cfg.extensions ?? []) as Array<{ name: string }>
|
|
95
|
+
assert.deepEqual(names(exts), ['doc', 'paragraph', 'text'])
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('placeholder appends the Placeholder extension', () => {
|
|
99
|
+
const cfg = createPlainTextEditor({ placeholder: 'Type here…' })
|
|
100
|
+
const exts = (cfg.extensions ?? []) as Array<{ name: string }>
|
|
101
|
+
assert.ok(exts.some((e) => e.name === 'placeholder'),
|
|
102
|
+
`expected placeholder extension, got ${names(exts).join(',')}`)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('caller-supplied extensions land after schema + behavior', () => {
|
|
106
|
+
const fakeExt = { name: 'fake-collab' } as unknown as Parameters<typeof createPlainTextEditor>[0] extends infer T
|
|
107
|
+
? T extends { extensions?: Array<infer E> } ? E : never : never
|
|
108
|
+
const cfg = createPlainTextEditor({ extensions: [fakeExt] })
|
|
109
|
+
const exts = (cfg.extensions ?? []) as Array<{ name: string }>
|
|
110
|
+
assert.equal(exts[exts.length - 1]?.name, 'fake-collab')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('editable can be turned off', () => {
|
|
114
|
+
const cfg = createPlainTextEditor({ editable: false })
|
|
115
|
+
assert.equal(cfg.editable, false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('seeds content as a doc JSON when text provided (single-line)', () => {
|
|
119
|
+
const cfg = createPlainTextEditor({ content: 'hello' })
|
|
120
|
+
assert.deepEqual(cfg.content, {
|
|
121
|
+
type: 'doc',
|
|
122
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'hello' }] }],
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('seeds content as multi-paragraph doc JSON when multiline + text provided', () => {
|
|
127
|
+
const cfg = createPlainTextEditor({ multiline: true, content: 'a\nb' })
|
|
128
|
+
assert.deepEqual(cfg.content, {
|
|
129
|
+
type: 'doc',
|
|
130
|
+
content: [
|
|
131
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'a' }] },
|
|
132
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'b' }] },
|
|
133
|
+
],
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('empty content stays an empty string sentinel — Collaboration-friendly', () => {
|
|
138
|
+
// When collab is on, callers pass content omitted/'' so Collaboration's
|
|
139
|
+
// y-prosemirror binding takes ownership of the doc without a seed race.
|
|
140
|
+
const cfg = createPlainTextEditor({ content: '' })
|
|
141
|
+
assert.equal(cfg.content, '')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('editorAttributes plumb into editorProps.attributes verbatim', () => {
|
|
145
|
+
const attrs = { class: 'foo bar', 'aria-label': 'Name' }
|
|
146
|
+
const cfg = createPlainTextEditor({ editorAttributes: attrs })
|
|
147
|
+
assert.deepEqual(cfg.editorProps, { attributes: attrs })
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('onUpdate is wired only when caller provides it', () => {
|
|
151
|
+
const withCb: PlainTextEditorOptions = { onUpdate: () => {} }
|
|
152
|
+
const cfgA = createPlainTextEditor(withCb)
|
|
153
|
+
assert.equal(typeof cfgA.onUpdate, 'function')
|
|
154
|
+
|
|
155
|
+
const cfgB = createPlainTextEditor()
|
|
156
|
+
assert.equal(cfgB.onUpdate, undefined)
|
|
157
|
+
})
|
|
158
|
+
})
|