@intinyagroup/rich-text 0.1.1-alpha.16 → 0.1.1-alpha.17
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.
|
@@ -1,36 +1,62 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount, onDestroy } from
|
|
3
|
-
import { Editor, Node, mergeAttributes } from
|
|
4
|
-
import StarterKit from
|
|
5
|
-
import Placeholder from
|
|
6
|
-
import Underline from
|
|
7
|
-
import TextAlign from
|
|
8
|
-
import Link from
|
|
9
|
-
import Image from
|
|
10
|
-
import Highlight from
|
|
11
|
-
import Table from
|
|
12
|
-
import TableRow from
|
|
13
|
-
import TableCell from
|
|
14
|
-
import TableHeader from
|
|
15
|
-
import Typography from
|
|
16
|
-
import BubbleMenu from
|
|
17
|
-
import TaskList from
|
|
18
|
-
import TaskItem from
|
|
2
|
+
import { onMount, onDestroy } from "svelte";
|
|
3
|
+
import { Editor, Node, mergeAttributes } from "@tiptap/core";
|
|
4
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
5
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
6
|
+
import Underline from "@tiptap/extension-underline";
|
|
7
|
+
import TextAlign from "@tiptap/extension-text-align";
|
|
8
|
+
import Link from "@tiptap/extension-link";
|
|
9
|
+
import Image from "@tiptap/extension-image";
|
|
10
|
+
import Highlight from "@tiptap/extension-highlight";
|
|
11
|
+
import Table from "@tiptap/extension-table";
|
|
12
|
+
import TableRow from "@tiptap/extension-table-row";
|
|
13
|
+
import TableCell from "@tiptap/extension-table-cell";
|
|
14
|
+
import TableHeader from "@tiptap/extension-table-header";
|
|
15
|
+
import Typography from "@tiptap/extension-typography";
|
|
16
|
+
import BubbleMenu from "@tiptap/extension-bubble-menu";
|
|
17
|
+
import TaskList from "@tiptap/extension-task-list";
|
|
18
|
+
import TaskItem from "@tiptap/extension-task-item";
|
|
19
19
|
import {
|
|
20
|
-
Bold,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
Bold,
|
|
21
|
+
Italic,
|
|
22
|
+
Underline as UnderlineIcon,
|
|
23
|
+
Strikethrough,
|
|
24
|
+
Highlighter,
|
|
25
|
+
AlignLeft,
|
|
26
|
+
AlignCenter,
|
|
27
|
+
AlignRight,
|
|
28
|
+
AlignJustify,
|
|
29
|
+
List,
|
|
30
|
+
ListOrdered,
|
|
31
|
+
Quote,
|
|
32
|
+
Code,
|
|
33
|
+
Minus,
|
|
34
|
+
Link as LinkIcon,
|
|
35
|
+
Image as ImageIcon,
|
|
36
|
+
Undo,
|
|
37
|
+
Redo,
|
|
38
|
+
Heading1,
|
|
39
|
+
Heading2,
|
|
40
|
+
Heading3,
|
|
41
|
+
TableIcon,
|
|
42
|
+
Plus,
|
|
43
|
+
Trash2,
|
|
44
|
+
Video,
|
|
45
|
+
CheckSquare,
|
|
46
|
+
Info,
|
|
47
|
+
AlertTriangle,
|
|
48
|
+
Sparkles,
|
|
49
|
+
HelpCircle,
|
|
50
|
+
FileText,
|
|
51
|
+
} from "lucide-svelte";
|
|
52
|
+
import { Button, Separator } from "@intinyagroup/ui";
|
|
53
|
+
import { cn } from "@intinyagroup/grid-core/utils";
|
|
28
54
|
|
|
29
55
|
let {
|
|
30
|
-
content =
|
|
31
|
-
placeholder =
|
|
56
|
+
content = "",
|
|
57
|
+
placeholder = "Start writing...",
|
|
32
58
|
editable = true,
|
|
33
|
-
mode =
|
|
59
|
+
mode = "classic",
|
|
34
60
|
height = 400,
|
|
35
61
|
class: className,
|
|
36
62
|
onUpdate,
|
|
@@ -41,7 +67,7 @@
|
|
|
41
67
|
placeholder?: string;
|
|
42
68
|
editable?: boolean;
|
|
43
69
|
/** Toolbar mode: 'classic' (fixed top toolbar), 'bubble' (Notion-style floating toolbar only), or 'none' */
|
|
44
|
-
mode?:
|
|
70
|
+
mode?: "classic" | "bubble" | "none";
|
|
45
71
|
height?: number;
|
|
46
72
|
class?: string;
|
|
47
73
|
onUpdate?: (html: string) => void;
|
|
@@ -58,32 +84,103 @@
|
|
|
58
84
|
|
|
59
85
|
// Slash commands state
|
|
60
86
|
let showSlashMenu = $state(false);
|
|
61
|
-
let slashSearch = $state(
|
|
87
|
+
let slashSearch = $state("");
|
|
62
88
|
let slashIndex = $state(0);
|
|
63
89
|
let slashMenuPos = $state({ top: 0, left: 0 });
|
|
64
90
|
|
|
65
91
|
const slashCommands = [
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
|
|
92
|
+
{
|
|
93
|
+
title: "Sub-page",
|
|
94
|
+
desc: "Embed a sub-page inside this page",
|
|
95
|
+
icon: FileText,
|
|
96
|
+
action: () => insertSubPage(),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: "Heading 1",
|
|
100
|
+
desc: "Big section heading",
|
|
101
|
+
icon: Heading1,
|
|
102
|
+
action: () => setHeading(1),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: "Heading 2",
|
|
106
|
+
desc: "Medium section heading",
|
|
107
|
+
icon: Heading2,
|
|
108
|
+
action: () => setHeading(2),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
title: "Heading 3",
|
|
112
|
+
desc: "Small subsection heading",
|
|
113
|
+
icon: Heading3,
|
|
114
|
+
action: () => setHeading(3),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: "To-do list",
|
|
118
|
+
desc: "Track tasks with a checklist",
|
|
119
|
+
icon: CheckSquare,
|
|
120
|
+
action: () => editor?.chain().focus().toggleTaskList().run(),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
title: "Bullet list",
|
|
124
|
+
desc: "Create a bulleted list",
|
|
125
|
+
icon: List,
|
|
126
|
+
action: () => toggleBulletList(),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
title: "Numbered list",
|
|
130
|
+
desc: "Create a numbered list",
|
|
131
|
+
icon: ListOrdered,
|
|
132
|
+
action: () => toggleOrderedList(),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
title: "Quote",
|
|
136
|
+
desc: "Capture a quote or blockquote",
|
|
137
|
+
icon: Quote,
|
|
138
|
+
action: () => toggleBlockquote(),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: "Code block",
|
|
142
|
+
desc: "Code snippet with syntax highlight",
|
|
143
|
+
icon: Code,
|
|
144
|
+
action: () => toggleCodeBlock(),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
title: "Callout Info",
|
|
148
|
+
desc: "Informational highlight block",
|
|
149
|
+
icon: Info,
|
|
150
|
+
action: () => insertCallout("info"),
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
title: "Callout Warning",
|
|
154
|
+
desc: "Warning or caution block",
|
|
155
|
+
icon: AlertTriangle,
|
|
156
|
+
action: () => insertCallout("warning"),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
title: "Callout Tip",
|
|
160
|
+
desc: "Tip or recommendation block",
|
|
161
|
+
icon: Sparkles,
|
|
162
|
+
action: () => insertCallout("tip"),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
title: "Table",
|
|
166
|
+
desc: "Insert 3x3 table grid",
|
|
167
|
+
icon: TableIcon,
|
|
168
|
+
action: () => insertTable(),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: "Divider",
|
|
172
|
+
desc: "Visually divide sections",
|
|
173
|
+
icon: Minus,
|
|
174
|
+
action: () => insertHorizontalRule(),
|
|
175
|
+
},
|
|
80
176
|
];
|
|
81
177
|
|
|
82
178
|
const slashFilteredCommands = $derived(
|
|
83
|
-
slashCommands.filter(
|
|
84
|
-
c
|
|
85
|
-
|
|
86
|
-
|
|
179
|
+
slashCommands.filter(
|
|
180
|
+
(c) =>
|
|
181
|
+
c.title.toLowerCase().includes(slashSearch.toLowerCase()) ||
|
|
182
|
+
c.desc.toLowerCase().includes(slashSearch.toLowerCase()),
|
|
183
|
+
),
|
|
87
184
|
);
|
|
88
185
|
// ---------------------------------------------------------------------------
|
|
89
186
|
// Custom Extensions
|
|
@@ -95,16 +192,19 @@
|
|
|
95
192
|
return {
|
|
96
193
|
...this.parent?.(),
|
|
97
194
|
href: { default: null },
|
|
98
|
-
alt: { default:
|
|
99
|
-
title: { default:
|
|
100
|
-
target: { default:
|
|
195
|
+
alt: { default: "" },
|
|
196
|
+
title: { default: "" },
|
|
197
|
+
target: { default: "_blank" },
|
|
101
198
|
};
|
|
102
199
|
},
|
|
103
200
|
renderHTML({ HTMLAttributes }) {
|
|
104
201
|
const { href, target, alt, title, ...rest } = HTMLAttributes;
|
|
105
|
-
const img = [
|
|
202
|
+
const img = [
|
|
203
|
+
"img",
|
|
204
|
+
mergeAttributes(this.options.HTMLAttributes, rest, { alt, title }),
|
|
205
|
+
];
|
|
106
206
|
if (href) {
|
|
107
|
-
return [
|
|
207
|
+
return ["a", { href, target, class: "image-link" }, img];
|
|
108
208
|
}
|
|
109
209
|
return img;
|
|
110
210
|
},
|
|
@@ -112,8 +212,8 @@
|
|
|
112
212
|
|
|
113
213
|
/** YouTube iframe embed node */
|
|
114
214
|
const CustomYoutube = Node.create({
|
|
115
|
-
name:
|
|
116
|
-
group:
|
|
215
|
+
name: "youtube",
|
|
216
|
+
group: "block",
|
|
117
217
|
atom: true,
|
|
118
218
|
addAttributes() {
|
|
119
219
|
return { src: { default: null } };
|
|
@@ -126,14 +226,14 @@
|
|
|
126
226
|
},
|
|
127
227
|
renderHTML({ HTMLAttributes }) {
|
|
128
228
|
return [
|
|
129
|
-
|
|
130
|
-
{ class:
|
|
229
|
+
"div",
|
|
230
|
+
{ class: "video-wrapper" },
|
|
131
231
|
[
|
|
132
|
-
|
|
232
|
+
"iframe",
|
|
133
233
|
mergeAttributes(HTMLAttributes, {
|
|
134
|
-
width:
|
|
135
|
-
height:
|
|
136
|
-
allowfullscreen:
|
|
234
|
+
width: "100%",
|
|
235
|
+
height: "315",
|
|
236
|
+
allowfullscreen: "true",
|
|
137
237
|
}),
|
|
138
238
|
],
|
|
139
239
|
];
|
|
@@ -152,8 +252,8 @@
|
|
|
152
252
|
reader.onload = (e) => {
|
|
153
253
|
const img = new Image();
|
|
154
254
|
img.onload = () => {
|
|
155
|
-
const canvas = document.createElement(
|
|
156
|
-
const ctx = canvas.getContext(
|
|
255
|
+
const canvas = document.createElement("canvas");
|
|
256
|
+
const ctx = canvas.getContext("2d");
|
|
157
257
|
if (!ctx) {
|
|
158
258
|
resolve(file);
|
|
159
259
|
return;
|
|
@@ -179,13 +279,13 @@
|
|
|
179
279
|
canvas.toBlob(
|
|
180
280
|
(blob) => {
|
|
181
281
|
if (blob) {
|
|
182
|
-
resolve(new File([blob], file.name, { type:
|
|
282
|
+
resolve(new File([blob], file.name, { type: "image/jpeg" }));
|
|
183
283
|
} else {
|
|
184
284
|
resolve(file);
|
|
185
285
|
}
|
|
186
286
|
},
|
|
187
|
-
|
|
188
|
-
0.85
|
|
287
|
+
"image/jpeg",
|
|
288
|
+
0.85,
|
|
189
289
|
);
|
|
190
290
|
};
|
|
191
291
|
img.src = e.target?.result as string;
|
|
@@ -218,15 +318,15 @@
|
|
|
218
318
|
|
|
219
319
|
function handleFiles(files: File[]) {
|
|
220
320
|
for (const file of files) {
|
|
221
|
-
if (file.type.startsWith(
|
|
321
|
+
if (file.type.startsWith("image/")) {
|
|
222
322
|
uploadAndInsertImage(file);
|
|
223
323
|
}
|
|
224
324
|
}
|
|
225
325
|
}
|
|
226
326
|
|
|
227
327
|
const CustomYoutube = Node.create({
|
|
228
|
-
name:
|
|
229
|
-
group:
|
|
328
|
+
name: "youtube",
|
|
329
|
+
group: "block",
|
|
230
330
|
atom: true,
|
|
231
331
|
addAttributes() {
|
|
232
332
|
return {
|
|
@@ -238,15 +338,17 @@
|
|
|
238
338
|
},
|
|
239
339
|
renderHTML({ HTMLAttributes }) {
|
|
240
340
|
return [
|
|
241
|
-
|
|
242
|
-
{
|
|
341
|
+
"div",
|
|
342
|
+
{
|
|
343
|
+
class: "video-container my-4 aspect-video rounded-xl overflow-hidden",
|
|
344
|
+
},
|
|
243
345
|
[
|
|
244
|
-
|
|
346
|
+
"iframe",
|
|
245
347
|
mergeAttributes(HTMLAttributes, {
|
|
246
|
-
class:
|
|
348
|
+
class: "w-full h-full border-0",
|
|
247
349
|
allow:
|
|
248
|
-
|
|
249
|
-
allowfullscreen:
|
|
350
|
+
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
351
|
+
allowfullscreen: "true",
|
|
250
352
|
}),
|
|
251
353
|
],
|
|
252
354
|
];
|
|
@@ -254,14 +356,14 @@
|
|
|
254
356
|
});
|
|
255
357
|
/** SubPage block extension (Page-in-page card) */
|
|
256
358
|
const SubPage = Node.create({
|
|
257
|
-
name:
|
|
258
|
-
group:
|
|
359
|
+
name: "subpage",
|
|
360
|
+
group: "block",
|
|
259
361
|
atom: true,
|
|
260
362
|
addAttributes() {
|
|
261
363
|
return {
|
|
262
364
|
id: { default: () => `page-${Date.now()}` },
|
|
263
|
-
title: { default:
|
|
264
|
-
icon: { default:
|
|
365
|
+
title: { default: "Untitled Sub-page" },
|
|
366
|
+
icon: { default: "📄" },
|
|
265
367
|
};
|
|
266
368
|
},
|
|
267
369
|
parseHTML() {
|
|
@@ -269,28 +371,40 @@
|
|
|
269
371
|
},
|
|
270
372
|
renderHTML({ HTMLAttributes }) {
|
|
271
373
|
return [
|
|
272
|
-
|
|
374
|
+
"div",
|
|
273
375
|
mergeAttributes(HTMLAttributes, {
|
|
274
|
-
|
|
275
|
-
class:
|
|
276
|
-
|
|
277
|
-
|
|
376
|
+
"data-type": "subpage",
|
|
377
|
+
class:
|
|
378
|
+
"subpage-block my-2 flex items-center gap-2.5 px-3 py-2 rounded-lg border border-[var(--ui-border)] bg-[var(--ui-card)] hover:bg-[var(--ui-secondary)]/50 transition-colors cursor-pointer shadow-xs group",
|
|
379
|
+
"data-page-id": HTMLAttributes.id,
|
|
380
|
+
"data-page-title": HTMLAttributes.title,
|
|
278
381
|
}),
|
|
279
|
-
[
|
|
280
|
-
|
|
382
|
+
[
|
|
383
|
+
"span",
|
|
384
|
+
{ class: "text-base shrink-0 select-none" },
|
|
385
|
+
HTMLAttributes.icon || "📄",
|
|
386
|
+
],
|
|
387
|
+
[
|
|
388
|
+
"span",
|
|
389
|
+
{
|
|
390
|
+
class:
|
|
391
|
+
"text-sm font-semibold text-[var(--ui-foreground)] underline-offset-4 group-hover:underline truncate",
|
|
392
|
+
},
|
|
393
|
+
HTMLAttributes.title || "Untitled Sub-page",
|
|
394
|
+
],
|
|
281
395
|
];
|
|
282
396
|
},
|
|
283
397
|
});
|
|
284
398
|
|
|
285
399
|
/** Callout block extension with type icon & colored border */
|
|
286
400
|
const Callout = Node.create({
|
|
287
|
-
name:
|
|
288
|
-
group:
|
|
289
|
-
content:
|
|
401
|
+
name: "callout",
|
|
402
|
+
group: "block",
|
|
403
|
+
content: "block+",
|
|
290
404
|
defining: true,
|
|
291
405
|
addAttributes() {
|
|
292
406
|
return {
|
|
293
|
-
type: { default:
|
|
407
|
+
type: { default: "info" }, // info | warning | tip
|
|
294
408
|
};
|
|
295
409
|
},
|
|
296
410
|
parseHTML() {
|
|
@@ -298,10 +412,10 @@
|
|
|
298
412
|
},
|
|
299
413
|
renderHTML({ HTMLAttributes }) {
|
|
300
414
|
return [
|
|
301
|
-
|
|
415
|
+
"div",
|
|
302
416
|
mergeAttributes(HTMLAttributes, {
|
|
303
|
-
|
|
304
|
-
class: `callout-box callout-${HTMLAttributes.type ||
|
|
417
|
+
"data-type": "callout",
|
|
418
|
+
class: `callout-box callout-${HTMLAttributes.type || "info"} my-3 p-3.5 rounded-xl border flex gap-3`,
|
|
305
419
|
}),
|
|
306
420
|
0,
|
|
307
421
|
];
|
|
@@ -309,10 +423,10 @@
|
|
|
309
423
|
});
|
|
310
424
|
|
|
311
425
|
function addYoutube() {
|
|
312
|
-
const url = window.prompt(
|
|
426
|
+
const url = window.prompt("Enter YouTube URL:");
|
|
313
427
|
if (!url || !editor) return;
|
|
314
428
|
const match = url.match(
|
|
315
|
-
/^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=|shorts\/)([^#&?]*)
|
|
429
|
+
/^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=|shorts\/)([^#&?]*).*/,
|
|
316
430
|
);
|
|
317
431
|
const id = match && match[2].length === 11 ? match[2] : null;
|
|
318
432
|
if (id) {
|
|
@@ -320,7 +434,7 @@
|
|
|
320
434
|
.chain()
|
|
321
435
|
.focus()
|
|
322
436
|
.insertContent({
|
|
323
|
-
type:
|
|
437
|
+
type: "youtube",
|
|
324
438
|
attrs: { src: `https://www.youtube.com/embed/${id}` },
|
|
325
439
|
})
|
|
326
440
|
.run();
|
|
@@ -333,82 +447,138 @@
|
|
|
333
447
|
|
|
334
448
|
function getActiveStates(e: Editor): Record<string, boolean> {
|
|
335
449
|
return {
|
|
336
|
-
bold: e.isActive(
|
|
337
|
-
italic: e.isActive(
|
|
338
|
-
underline: e.isActive(
|
|
339
|
-
strike: e.isActive(
|
|
340
|
-
highlight: e.isActive(
|
|
341
|
-
h1: e.isActive(
|
|
342
|
-
h2: e.isActive(
|
|
343
|
-
h3: e.isActive(
|
|
344
|
-
bulletList: e.isActive(
|
|
345
|
-
orderedList: e.isActive(
|
|
346
|
-
blockquote: e.isActive(
|
|
347
|
-
codeBlock: e.isActive(
|
|
348
|
-
alignLeft: e.isActive({ textAlign:
|
|
349
|
-
alignCenter: e.isActive({ textAlign:
|
|
350
|
-
alignRight: e.isActive({ textAlign:
|
|
351
|
-
alignJustify: e.isActive({ textAlign:
|
|
352
|
-
link: e.isActive(
|
|
450
|
+
bold: e.isActive("bold"),
|
|
451
|
+
italic: e.isActive("italic"),
|
|
452
|
+
underline: e.isActive("underline"),
|
|
453
|
+
strike: e.isActive("strike"),
|
|
454
|
+
highlight: e.isActive("highlight"),
|
|
455
|
+
h1: e.isActive("heading", { level: 1 }),
|
|
456
|
+
h2: e.isActive("heading", { level: 2 }),
|
|
457
|
+
h3: e.isActive("heading", { level: 3 }),
|
|
458
|
+
bulletList: e.isActive("bulletList"),
|
|
459
|
+
orderedList: e.isActive("orderedList"),
|
|
460
|
+
blockquote: e.isActive("blockquote"),
|
|
461
|
+
codeBlock: e.isActive("codeBlock"),
|
|
462
|
+
alignLeft: e.isActive({ textAlign: "left" }),
|
|
463
|
+
alignCenter: e.isActive({ textAlign: "center" }),
|
|
464
|
+
alignRight: e.isActive({ textAlign: "right" }),
|
|
465
|
+
alignJustify: e.isActive({ textAlign: "justify" }),
|
|
466
|
+
link: e.isActive("link"),
|
|
353
467
|
};
|
|
354
468
|
}
|
|
355
469
|
|
|
356
|
-
function toggleBold() {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
function
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
function
|
|
470
|
+
function toggleBold() {
|
|
471
|
+
editor?.chain().focus().toggleBold().run();
|
|
472
|
+
}
|
|
473
|
+
function toggleItalic() {
|
|
474
|
+
editor?.chain().focus().toggleItalic().run();
|
|
475
|
+
}
|
|
476
|
+
function toggleUnderline() {
|
|
477
|
+
editor?.chain().focus().toggleUnderline().run();
|
|
478
|
+
}
|
|
479
|
+
function toggleStrike() {
|
|
480
|
+
editor?.chain().focus().toggleStrike().run();
|
|
481
|
+
}
|
|
482
|
+
function setHeading(level: 1 | 2 | 3) {
|
|
483
|
+
editor?.chain().focus().toggleHeading({ level }).run();
|
|
484
|
+
}
|
|
485
|
+
function toggleBulletList() {
|
|
486
|
+
editor?.chain().focus().toggleBulletList().run();
|
|
487
|
+
}
|
|
488
|
+
function toggleOrderedList() {
|
|
489
|
+
editor?.chain().focus().toggleOrderedList().run();
|
|
490
|
+
}
|
|
491
|
+
function toggleBlockquote() {
|
|
492
|
+
editor?.chain().focus().toggleBlockquote().run();
|
|
493
|
+
}
|
|
494
|
+
function toggleCodeBlock() {
|
|
495
|
+
editor?.chain().focus().toggleCodeBlock().run();
|
|
496
|
+
}
|
|
497
|
+
function setAlign(align: "left" | "center" | "right" | "justify") {
|
|
366
498
|
editor?.chain().focus().setTextAlign(align).run();
|
|
367
499
|
}
|
|
368
500
|
function setLink() {
|
|
369
|
-
const url = window.prompt(
|
|
501
|
+
const url = window.prompt("Enter URL:");
|
|
370
502
|
if (url) editor?.chain().focus().setLink({ href: url }).run();
|
|
371
503
|
}
|
|
372
504
|
function setImage() {
|
|
373
|
-
const url = window.prompt(
|
|
505
|
+
const url = window.prompt("Enter image URL:");
|
|
374
506
|
if (url) editor?.chain().focus().setImage({ src: url }).run();
|
|
375
507
|
}
|
|
376
|
-
function insertHorizontalRule() {
|
|
508
|
+
function insertHorizontalRule() {
|
|
509
|
+
editor?.chain().focus().setHorizontalRule().run();
|
|
510
|
+
}
|
|
377
511
|
function insertTable() {
|
|
378
|
-
editor
|
|
512
|
+
editor
|
|
513
|
+
?.chain()
|
|
514
|
+
.focus()
|
|
515
|
+
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
|
|
516
|
+
.run();
|
|
379
517
|
}
|
|
380
|
-
function insertCallout(type:
|
|
381
|
-
editor
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
518
|
+
function insertCallout(type: "info" | "warning" | "tip") {
|
|
519
|
+
editor
|
|
520
|
+
?.chain()
|
|
521
|
+
.focus()
|
|
522
|
+
.insertContent({
|
|
523
|
+
type: "callout",
|
|
524
|
+
attrs: { type },
|
|
525
|
+
content: [
|
|
526
|
+
{ type: "paragraph", text: "Tulis catatan penting di sini..." },
|
|
527
|
+
],
|
|
528
|
+
})
|
|
529
|
+
.run();
|
|
386
530
|
}
|
|
387
531
|
function insertSubPage() {
|
|
388
|
-
const title = window.prompt(
|
|
389
|
-
editor
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
532
|
+
const title = window.prompt("Enter Sub-page title:") || "Untitled Sub-page";
|
|
533
|
+
editor
|
|
534
|
+
?.chain()
|
|
535
|
+
.focus()
|
|
536
|
+
.insertContent({
|
|
537
|
+
type: "subpage",
|
|
538
|
+
attrs: { id: `page-${Date.now()}`, title, icon: "📄" },
|
|
539
|
+
})
|
|
540
|
+
.run();
|
|
393
541
|
}
|
|
394
542
|
|
|
395
|
-
function executeSlashCommand(cmd: typeof slashCommands[0]) {
|
|
543
|
+
function executeSlashCommand(cmd: (typeof slashCommands)[0]) {
|
|
396
544
|
if (!editor) return;
|
|
397
545
|
const { from } = editor.state.selection;
|
|
398
546
|
const deleteFrom = from - (slashSearch.length + 1);
|
|
399
|
-
editor
|
|
547
|
+
editor
|
|
548
|
+
.chain()
|
|
549
|
+
.focus()
|
|
550
|
+
.deleteRange({ from: Math.max(0, deleteFrom), to: from })
|
|
551
|
+
.run();
|
|
400
552
|
cmd.action();
|
|
401
553
|
showSlashMenu = false;
|
|
402
554
|
}
|
|
403
|
-
function addColumnBefore() {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
function
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
|
|
555
|
+
function addColumnBefore() {
|
|
556
|
+
editor?.chain().focus().addColumnBefore().run();
|
|
557
|
+
}
|
|
558
|
+
function addColumnAfter() {
|
|
559
|
+
editor?.chain().focus().addColumnAfter().run();
|
|
560
|
+
}
|
|
561
|
+
function deleteColumn() {
|
|
562
|
+
editor?.chain().focus().deleteColumn().run();
|
|
563
|
+
}
|
|
564
|
+
function addRowBefore() {
|
|
565
|
+
editor?.chain().focus().addRowBefore().run();
|
|
566
|
+
}
|
|
567
|
+
function addRowAfter() {
|
|
568
|
+
editor?.chain().focus().addRowAfter().run();
|
|
569
|
+
}
|
|
570
|
+
function deleteRow() {
|
|
571
|
+
editor?.chain().focus().deleteRow().run();
|
|
572
|
+
}
|
|
573
|
+
function deleteTable() {
|
|
574
|
+
editor?.chain().focus().deleteTable().run();
|
|
575
|
+
}
|
|
576
|
+
function undo() {
|
|
577
|
+
editor?.chain().focus().undo().run();
|
|
578
|
+
}
|
|
579
|
+
function redo() {
|
|
580
|
+
editor?.chain().focus().redo().run();
|
|
581
|
+
}
|
|
412
582
|
|
|
413
583
|
// ---------------------------------------------------------------------------
|
|
414
584
|
// Lifecycle
|
|
@@ -423,12 +593,12 @@
|
|
|
423
593
|
StarterKit,
|
|
424
594
|
Placeholder.configure({ placeholder }),
|
|
425
595
|
Underline,
|
|
426
|
-
TextAlign.configure({ types: [
|
|
596
|
+
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
427
597
|
Link.configure({ openOnClick: false }),
|
|
428
598
|
CustomImage.configure({
|
|
429
599
|
inline: false,
|
|
430
600
|
HTMLAttributes: {
|
|
431
|
-
class:
|
|
601
|
+
class: "rounded-lg mx-auto block max-w-full h-auto cursor-pointer",
|
|
432
602
|
},
|
|
433
603
|
}),
|
|
434
604
|
Highlight.configure({ multicolor: true }),
|
|
@@ -439,10 +609,10 @@
|
|
|
439
609
|
TableHeader,
|
|
440
610
|
BubbleMenu.configure({
|
|
441
611
|
element: bubbleMenuEl!,
|
|
442
|
-
pluginKey:
|
|
612
|
+
pluginKey: "bubbleMenu",
|
|
443
613
|
shouldShow: ({ editor: e }) =>
|
|
444
614
|
e.isEditable &&
|
|
445
|
-
!e.isActive(
|
|
615
|
+
!e.isActive("table") &&
|
|
446
616
|
e.view.state.selection.content().size > 0,
|
|
447
617
|
}),
|
|
448
618
|
CustomYoutube,
|
|
@@ -461,7 +631,12 @@
|
|
|
461
631
|
// Detect slash command trigger
|
|
462
632
|
const { state } = e;
|
|
463
633
|
const { from } = state.selection;
|
|
464
|
-
const textBefore = state.doc.textBetween(
|
|
634
|
+
const textBefore = state.doc.textBetween(
|
|
635
|
+
Math.max(0, from - 20),
|
|
636
|
+
from,
|
|
637
|
+
"\n",
|
|
638
|
+
"\0",
|
|
639
|
+
);
|
|
465
640
|
const slashMatch = textBefore.match(/\/([a-zA-Z0-9]*)$/);
|
|
466
641
|
|
|
467
642
|
if (slashMatch) {
|
|
@@ -473,7 +648,13 @@
|
|
|
473
648
|
if (parentRect) {
|
|
474
649
|
slashMenuPos = {
|
|
475
650
|
top: coords.bottom - parentRect.top + 8,
|
|
476
|
-
left: Math.max(
|
|
651
|
+
left: Math.max(
|
|
652
|
+
16,
|
|
653
|
+
Math.min(
|
|
654
|
+
coords.left - parentRect.left,
|
|
655
|
+
parentRect.width - 260,
|
|
656
|
+
),
|
|
657
|
+
),
|
|
477
658
|
};
|
|
478
659
|
}
|
|
479
660
|
} catch {
|
|
@@ -489,7 +670,8 @@
|
|
|
489
670
|
},
|
|
490
671
|
editorProps: {
|
|
491
672
|
attributes: {
|
|
492
|
-
class:
|
|
673
|
+
class:
|
|
674
|
+
"rich-text-content prose prose-sm max-w-none focus:outline-none",
|
|
493
675
|
},
|
|
494
676
|
handleDOMEvents: {
|
|
495
677
|
paste: (_view, event) => {
|
|
@@ -497,7 +679,7 @@
|
|
|
497
679
|
if (!items) return false;
|
|
498
680
|
const imageFiles: File[] = [];
|
|
499
681
|
for (let i = 0; i < items.length; i++) {
|
|
500
|
-
if (items[i].type.startsWith(
|
|
682
|
+
if (items[i].type.startsWith("image/")) {
|
|
501
683
|
const file = items[i].getAsFile();
|
|
502
684
|
if (file) imageFiles.push(file);
|
|
503
685
|
}
|
|
@@ -513,7 +695,7 @@
|
|
|
513
695
|
const files = event.dataTransfer?.files;
|
|
514
696
|
if (!files?.length) return false;
|
|
515
697
|
const imageFiles = Array.from(files).filter((f) =>
|
|
516
|
-
f.type.startsWith(
|
|
698
|
+
f.type.startsWith("image/"),
|
|
517
699
|
);
|
|
518
700
|
if (imageFiles.length) {
|
|
519
701
|
event.preventDefault();
|
|
@@ -523,10 +705,12 @@
|
|
|
523
705
|
return false;
|
|
524
706
|
},
|
|
525
707
|
click: (_view, event) => {
|
|
526
|
-
const target = (event.target as HTMLElement)?.closest(
|
|
708
|
+
const target = (event.target as HTMLElement)?.closest(
|
|
709
|
+
".subpage-block",
|
|
710
|
+
) as HTMLElement | null;
|
|
527
711
|
if (target) {
|
|
528
|
-
const id = target.getAttribute(
|
|
529
|
-
const title = target.getAttribute(
|
|
712
|
+
const id = target.getAttribute("data-page-id") || "";
|
|
713
|
+
const title = target.getAttribute("data-page-title") || "";
|
|
530
714
|
onOpenSubPage?.({ id, title });
|
|
531
715
|
return true;
|
|
532
716
|
}
|
|
@@ -534,30 +718,32 @@
|
|
|
534
718
|
},
|
|
535
719
|
keydown: (_view, event) => {
|
|
536
720
|
if (showSlashMenu) {
|
|
537
|
-
if (event.key ===
|
|
721
|
+
if (event.key === "ArrowDown") {
|
|
538
722
|
event.preventDefault();
|
|
539
723
|
slashIndex = (slashIndex + 1) % slashFilteredCommands.length;
|
|
540
724
|
return true;
|
|
541
725
|
}
|
|
542
|
-
if (event.key ===
|
|
726
|
+
if (event.key === "ArrowUp") {
|
|
543
727
|
event.preventDefault();
|
|
544
|
-
slashIndex =
|
|
728
|
+
slashIndex =
|
|
729
|
+
(slashIndex - 1 + slashFilteredCommands.length) %
|
|
730
|
+
slashFilteredCommands.length;
|
|
545
731
|
return true;
|
|
546
732
|
}
|
|
547
|
-
if (event.key ===
|
|
733
|
+
if (event.key === "Enter") {
|
|
548
734
|
event.preventDefault();
|
|
549
735
|
const cmd = slashFilteredCommands[slashIndex];
|
|
550
736
|
if (cmd) executeSlashCommand(cmd);
|
|
551
737
|
return true;
|
|
552
738
|
}
|
|
553
|
-
if (event.key ===
|
|
739
|
+
if (event.key === "Escape") {
|
|
554
740
|
event.preventDefault();
|
|
555
741
|
showSlashMenu = false;
|
|
556
742
|
return true;
|
|
557
743
|
}
|
|
558
744
|
}
|
|
559
745
|
return false;
|
|
560
|
-
}
|
|
746
|
+
},
|
|
561
747
|
},
|
|
562
748
|
},
|
|
563
749
|
});
|
|
@@ -570,14 +756,16 @@
|
|
|
570
756
|
|
|
571
757
|
<div
|
|
572
758
|
class={cn(
|
|
573
|
-
|
|
574
|
-
className
|
|
759
|
+
"rich-text-editor group rounded-xl border border-[var(--ui-border)] bg-[var(--ui-card)] shadow-sm overflow-hidden transition-[border-color,box-shadow] focus-within:border-[var(--ui-ring)] focus-within:ring-2 focus-within:ring-[var(--ui-ring)]/15",
|
|
760
|
+
className,
|
|
575
761
|
)}
|
|
576
762
|
>
|
|
577
763
|
<!-- Fixed Top Toolbar (only shown in 'classic' mode) -->
|
|
578
|
-
{#if editable && mode ===
|
|
764
|
+
{#if editable && mode === "classic"}
|
|
579
765
|
<div
|
|
580
|
-
class="flex
|
|
766
|
+
class="rich-text-toolbar sticky top-0 z-20 flex min-h-12 items-center gap-1 overflow-x-auto border-b border-[var(--ui-border)] bg-[var(--ui-card)]/95 px-2.5 py-2 backdrop-blur-md sm:px-3"
|
|
767
|
+
role="toolbar"
|
|
768
|
+
aria-label="Text formatting"
|
|
581
769
|
>
|
|
582
770
|
<div class="flex items-center gap-0.5">
|
|
583
771
|
<Button
|
|
@@ -607,10 +795,10 @@
|
|
|
607
795
|
variant="ghost"
|
|
608
796
|
size="sm"
|
|
609
797
|
class={cn(
|
|
610
|
-
|
|
798
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
611
799
|
isActive.h1
|
|
612
|
-
?
|
|
613
|
-
:
|
|
800
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
801
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
614
802
|
)}
|
|
615
803
|
onclick={() => setHeading(1)}
|
|
616
804
|
title="Heading 1"
|
|
@@ -621,10 +809,10 @@
|
|
|
621
809
|
variant="ghost"
|
|
622
810
|
size="sm"
|
|
623
811
|
class={cn(
|
|
624
|
-
|
|
812
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
625
813
|
isActive.h2
|
|
626
|
-
?
|
|
627
|
-
:
|
|
814
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
815
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
628
816
|
)}
|
|
629
817
|
onclick={() => setHeading(2)}
|
|
630
818
|
title="Heading 2"
|
|
@@ -635,10 +823,10 @@
|
|
|
635
823
|
variant="ghost"
|
|
636
824
|
size="sm"
|
|
637
825
|
class={cn(
|
|
638
|
-
|
|
826
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
639
827
|
isActive.h3
|
|
640
|
-
?
|
|
641
|
-
:
|
|
828
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
829
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
642
830
|
)}
|
|
643
831
|
onclick={() => setHeading(3)}
|
|
644
832
|
title="Heading 3"
|
|
@@ -654,10 +842,10 @@
|
|
|
654
842
|
variant="ghost"
|
|
655
843
|
size="sm"
|
|
656
844
|
class={cn(
|
|
657
|
-
|
|
845
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
658
846
|
isActive.bold
|
|
659
|
-
?
|
|
660
|
-
:
|
|
847
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
848
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
661
849
|
)}
|
|
662
850
|
onclick={toggleBold}
|
|
663
851
|
title="Bold (Ctrl+B)"
|
|
@@ -668,10 +856,10 @@
|
|
|
668
856
|
variant="ghost"
|
|
669
857
|
size="sm"
|
|
670
858
|
class={cn(
|
|
671
|
-
|
|
859
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
672
860
|
isActive.italic
|
|
673
|
-
?
|
|
674
|
-
:
|
|
861
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
862
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
675
863
|
)}
|
|
676
864
|
onclick={toggleItalic}
|
|
677
865
|
title="Italic (Ctrl+I)"
|
|
@@ -682,10 +870,10 @@
|
|
|
682
870
|
variant="ghost"
|
|
683
871
|
size="sm"
|
|
684
872
|
class={cn(
|
|
685
|
-
|
|
873
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
686
874
|
isActive.underline
|
|
687
|
-
?
|
|
688
|
-
:
|
|
875
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
876
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
689
877
|
)}
|
|
690
878
|
onclick={toggleUnderline}
|
|
691
879
|
title="Underline (Ctrl+U)"
|
|
@@ -696,10 +884,10 @@
|
|
|
696
884
|
variant="ghost"
|
|
697
885
|
size="sm"
|
|
698
886
|
class={cn(
|
|
699
|
-
|
|
887
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
700
888
|
isActive.strike
|
|
701
|
-
?
|
|
702
|
-
:
|
|
889
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
890
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
703
891
|
)}
|
|
704
892
|
onclick={toggleStrike}
|
|
705
893
|
title="Strikethrough"
|
|
@@ -710,12 +898,12 @@
|
|
|
710
898
|
variant="ghost"
|
|
711
899
|
size="sm"
|
|
712
900
|
class={cn(
|
|
713
|
-
|
|
901
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
714
902
|
isActive.highlight
|
|
715
|
-
?
|
|
716
|
-
:
|
|
903
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
904
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
717
905
|
)}
|
|
718
|
-
onclick={() => toggleHighlight(
|
|
906
|
+
onclick={() => toggleHighlight("#fef08a")}
|
|
719
907
|
title="Highlight text"
|
|
720
908
|
>
|
|
721
909
|
<Highlighter class="size-3.5" />
|
|
@@ -728,10 +916,10 @@
|
|
|
728
916
|
variant="ghost"
|
|
729
917
|
size="sm"
|
|
730
918
|
class={cn(
|
|
731
|
-
|
|
919
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
732
920
|
isActive.bulletList
|
|
733
|
-
?
|
|
734
|
-
:
|
|
921
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
922
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
735
923
|
)}
|
|
736
924
|
onclick={toggleBulletList}
|
|
737
925
|
title="Bullet list"
|
|
@@ -742,10 +930,10 @@
|
|
|
742
930
|
variant="ghost"
|
|
743
931
|
size="sm"
|
|
744
932
|
class={cn(
|
|
745
|
-
|
|
933
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
746
934
|
isActive.orderedList
|
|
747
|
-
?
|
|
748
|
-
:
|
|
935
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
936
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
749
937
|
)}
|
|
750
938
|
onclick={toggleOrderedList}
|
|
751
939
|
title="Numbered list"
|
|
@@ -756,10 +944,10 @@
|
|
|
756
944
|
variant="ghost"
|
|
757
945
|
size="sm"
|
|
758
946
|
class={cn(
|
|
759
|
-
|
|
947
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
760
948
|
isActive.blockquote
|
|
761
|
-
?
|
|
762
|
-
:
|
|
949
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
950
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
763
951
|
)}
|
|
764
952
|
onclick={toggleBlockquote}
|
|
765
953
|
title="Quote"
|
|
@@ -770,10 +958,10 @@
|
|
|
770
958
|
variant="ghost"
|
|
771
959
|
size="sm"
|
|
772
960
|
class={cn(
|
|
773
|
-
|
|
961
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
774
962
|
isActive.codeBlock
|
|
775
|
-
?
|
|
776
|
-
:
|
|
963
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
964
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
777
965
|
)}
|
|
778
966
|
onclick={toggleCodeBlock}
|
|
779
967
|
title="Code block"
|
|
@@ -789,12 +977,12 @@
|
|
|
789
977
|
variant="ghost"
|
|
790
978
|
size="sm"
|
|
791
979
|
class={cn(
|
|
792
|
-
|
|
980
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
793
981
|
isActive.alignLeft
|
|
794
|
-
?
|
|
795
|
-
:
|
|
982
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
983
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
796
984
|
)}
|
|
797
|
-
onclick={() => setAlign(
|
|
985
|
+
onclick={() => setAlign("left")}
|
|
798
986
|
title="Align left"
|
|
799
987
|
>
|
|
800
988
|
<AlignLeft class="size-3.5" />
|
|
@@ -803,12 +991,12 @@
|
|
|
803
991
|
variant="ghost"
|
|
804
992
|
size="sm"
|
|
805
993
|
class={cn(
|
|
806
|
-
|
|
994
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
807
995
|
isActive.alignCenter
|
|
808
|
-
?
|
|
809
|
-
:
|
|
996
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
997
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
810
998
|
)}
|
|
811
|
-
onclick={() => setAlign(
|
|
999
|
+
onclick={() => setAlign("center")}
|
|
812
1000
|
title="Align center"
|
|
813
1001
|
>
|
|
814
1002
|
<AlignCenter class="size-3.5" />
|
|
@@ -817,12 +1005,12 @@
|
|
|
817
1005
|
variant="ghost"
|
|
818
1006
|
size="sm"
|
|
819
1007
|
class={cn(
|
|
820
|
-
|
|
1008
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
821
1009
|
isActive.alignRight
|
|
822
|
-
?
|
|
823
|
-
:
|
|
1010
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1011
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
824
1012
|
)}
|
|
825
|
-
onclick={() => setAlign(
|
|
1013
|
+
onclick={() => setAlign("right")}
|
|
826
1014
|
title="Align right"
|
|
827
1015
|
>
|
|
828
1016
|
<AlignRight class="size-3.5" />
|
|
@@ -831,12 +1019,12 @@
|
|
|
831
1019
|
variant="ghost"
|
|
832
1020
|
size="sm"
|
|
833
1021
|
class={cn(
|
|
834
|
-
|
|
1022
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
835
1023
|
isActive.alignJustify
|
|
836
|
-
?
|
|
837
|
-
:
|
|
1024
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1025
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
838
1026
|
)}
|
|
839
|
-
onclick={() => setAlign(
|
|
1027
|
+
onclick={() => setAlign("justify")}
|
|
840
1028
|
title="Align justify"
|
|
841
1029
|
>
|
|
842
1030
|
<AlignJustify class="size-3.5" />
|
|
@@ -850,10 +1038,10 @@
|
|
|
850
1038
|
variant="ghost"
|
|
851
1039
|
size="sm"
|
|
852
1040
|
class={cn(
|
|
853
|
-
|
|
1041
|
+
"size-7.5 p-0 rounded-md transition-colors",
|
|
854
1042
|
isActive.link
|
|
855
|
-
?
|
|
856
|
-
:
|
|
1043
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1044
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
857
1045
|
)}
|
|
858
1046
|
onclick={setLink}
|
|
859
1047
|
title="Insert link"
|
|
@@ -898,17 +1086,17 @@
|
|
|
898
1086
|
</Button>
|
|
899
1087
|
</div>
|
|
900
1088
|
{#if uploadingCount > 0}
|
|
901
|
-
<span
|
|
902
|
-
|
|
1089
|
+
<span
|
|
1090
|
+
class="ml-auto text-xs text-[var(--ui-muted-foreground)] font-medium"
|
|
1091
|
+
>
|
|
1092
|
+
Uploading {uploadingCount} image{uploadingCount > 1 ? "s" : ""}...
|
|
903
1093
|
</span>
|
|
904
1094
|
{/if}
|
|
905
1095
|
</div>
|
|
906
1096
|
{/if}
|
|
907
|
-
|
|
908
|
-
<!-- Editor -->
|
|
909
1097
|
<div
|
|
910
1098
|
bind:this={editorEl}
|
|
911
|
-
class="prose prose-sm max-w-none p-
|
|
1099
|
+
class="rich-text-content prose prose-sm max-w-none p-5 sm:p-7 focus:outline-none"
|
|
912
1100
|
style="min-height: {height}px;"
|
|
913
1101
|
></div>
|
|
914
1102
|
|
|
@@ -920,7 +1108,9 @@
|
|
|
920
1108
|
class="absolute z-50 w-64 max-h-72 overflow-y-auto rounded-xl border border-[var(--ui-border)] bg-[var(--ui-popover)] p-1.5 shadow-2xl text-[var(--ui-popover-foreground)] animate-in fade-in-50 zoom-in-95"
|
|
921
1109
|
style="top: {slashMenuPos.top}px; left: {slashMenuPos.left}px;"
|
|
922
1110
|
>
|
|
923
|
-
<div
|
|
1111
|
+
<div
|
|
1112
|
+
class="px-2 py-1 text-[10px] font-bold uppercase tracking-wider text-[var(--ui-muted-foreground)]"
|
|
1113
|
+
>
|
|
924
1114
|
Basic blocks
|
|
925
1115
|
</div>
|
|
926
1116
|
{#each slashFilteredCommands as cmd, i (cmd.title)}
|
|
@@ -928,18 +1118,26 @@
|
|
|
928
1118
|
<button
|
|
929
1119
|
type="button"
|
|
930
1120
|
class={cn(
|
|
931
|
-
|
|
932
|
-
i === slashIndex
|
|
1121
|
+
"flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left transition-colors cursor-pointer",
|
|
1122
|
+
i === slashIndex
|
|
1123
|
+
? "bg-[var(--ui-accent)] text-[var(--ui-accent-foreground)]"
|
|
1124
|
+
: "hover:bg-[var(--ui-secondary)]",
|
|
933
1125
|
)}
|
|
934
1126
|
onmouseenter={() => (slashIndex = i)}
|
|
935
1127
|
onclick={() => executeSlashCommand(cmd)}
|
|
936
1128
|
>
|
|
937
|
-
<div
|
|
1129
|
+
<div
|
|
1130
|
+
class="flex size-7 items-center justify-center rounded-md border border-[var(--ui-border)] bg-[var(--ui-card)] shrink-0"
|
|
1131
|
+
>
|
|
938
1132
|
<Icon class="size-4 text-[var(--ui-foreground)]" />
|
|
939
1133
|
</div>
|
|
940
1134
|
<div class="flex flex-col min-w-0">
|
|
941
|
-
<span class="text-xs font-semibold text-[var(--ui-foreground)]"
|
|
942
|
-
|
|
1135
|
+
<span class="text-xs font-semibold text-[var(--ui-foreground)]"
|
|
1136
|
+
>{cmd.title}</span
|
|
1137
|
+
>
|
|
1138
|
+
<span class="text-[10px] text-[var(--ui-muted-foreground)] truncate"
|
|
1139
|
+
>{cmd.desc}</span
|
|
1140
|
+
>
|
|
943
1141
|
</div>
|
|
944
1142
|
</button>
|
|
945
1143
|
{/each}
|
|
@@ -956,10 +1154,10 @@
|
|
|
956
1154
|
variant="ghost"
|
|
957
1155
|
size="sm"
|
|
958
1156
|
class={cn(
|
|
959
|
-
|
|
960
|
-
editor.isActive(
|
|
961
|
-
?
|
|
962
|
-
:
|
|
1157
|
+
"size-7 p-0 rounded-md transition-colors",
|
|
1158
|
+
editor.isActive("bold")
|
|
1159
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1160
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
963
1161
|
)}
|
|
964
1162
|
onclick={toggleBold}
|
|
965
1163
|
>
|
|
@@ -969,10 +1167,10 @@
|
|
|
969
1167
|
variant="ghost"
|
|
970
1168
|
size="sm"
|
|
971
1169
|
class={cn(
|
|
972
|
-
|
|
973
|
-
editor.isActive(
|
|
974
|
-
?
|
|
975
|
-
:
|
|
1170
|
+
"size-7 p-0 rounded-md transition-colors",
|
|
1171
|
+
editor.isActive("italic")
|
|
1172
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1173
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
976
1174
|
)}
|
|
977
1175
|
onclick={toggleItalic}
|
|
978
1176
|
>
|
|
@@ -982,10 +1180,10 @@
|
|
|
982
1180
|
variant="ghost"
|
|
983
1181
|
size="sm"
|
|
984
1182
|
class={cn(
|
|
985
|
-
|
|
986
|
-
editor.isActive(
|
|
987
|
-
?
|
|
988
|
-
:
|
|
1183
|
+
"size-7 p-0 rounded-md transition-colors",
|
|
1184
|
+
editor.isActive("underline")
|
|
1185
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1186
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
989
1187
|
)}
|
|
990
1188
|
onclick={toggleUnderline}
|
|
991
1189
|
>
|
|
@@ -995,10 +1193,10 @@
|
|
|
995
1193
|
variant="ghost"
|
|
996
1194
|
size="sm"
|
|
997
1195
|
class={cn(
|
|
998
|
-
|
|
999
|
-
editor.isActive(
|
|
1000
|
-
?
|
|
1001
|
-
:
|
|
1196
|
+
"size-7 p-0 rounded-md transition-colors",
|
|
1197
|
+
editor.isActive("strike")
|
|
1198
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1199
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
1002
1200
|
)}
|
|
1003
1201
|
onclick={toggleStrike}
|
|
1004
1202
|
>
|
|
@@ -1008,12 +1206,12 @@
|
|
|
1008
1206
|
variant="ghost"
|
|
1009
1207
|
size="sm"
|
|
1010
1208
|
class={cn(
|
|
1011
|
-
|
|
1012
|
-
editor.isActive(
|
|
1013
|
-
?
|
|
1014
|
-
:
|
|
1209
|
+
"size-7 p-0 rounded-md transition-colors",
|
|
1210
|
+
editor.isActive("highlight")
|
|
1211
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1212
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
1015
1213
|
)}
|
|
1016
|
-
onclick={() => toggleHighlight(
|
|
1214
|
+
onclick={() => toggleHighlight("#fef08a")}
|
|
1017
1215
|
>
|
|
1018
1216
|
<Highlighter class="size-3.5" />
|
|
1019
1217
|
</Button>
|
|
@@ -1021,10 +1219,10 @@
|
|
|
1021
1219
|
variant="ghost"
|
|
1022
1220
|
size="sm"
|
|
1023
1221
|
class={cn(
|
|
1024
|
-
|
|
1025
|
-
editor.isActive(
|
|
1026
|
-
?
|
|
1027
|
-
:
|
|
1222
|
+
"size-7 p-0 rounded-md transition-colors",
|
|
1223
|
+
editor.isActive("link")
|
|
1224
|
+
? "bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-bold shadow-xs"
|
|
1225
|
+
: "text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]",
|
|
1028
1226
|
)}
|
|
1029
1227
|
onclick={setLink}
|
|
1030
1228
|
>
|
|
@@ -1179,7 +1377,7 @@
|
|
|
1179
1377
|
background: color-mix(in srgb, var(--ui-primary) 10%, transparent);
|
|
1180
1378
|
}
|
|
1181
1379
|
:global(.tiptap .selectedCell::after) {
|
|
1182
|
-
content:
|
|
1380
|
+
content: "";
|
|
1183
1381
|
position: absolute;
|
|
1184
1382
|
inset: 0;
|
|
1185
1383
|
background: rgba(0, 0, 0, 0.05);
|
|
@@ -1220,4 +1418,180 @@
|
|
|
1220
1418
|
padding: 0.1em 0.2em;
|
|
1221
1419
|
border-radius: 2px;
|
|
1222
1420
|
}
|
|
1421
|
+
|
|
1422
|
+
/* Rich-text surface contract: survives host Tailwind scanning boundaries. */
|
|
1423
|
+
:global(.rich-text-editor) {
|
|
1424
|
+
--rich-text-content-width: 72ch;
|
|
1425
|
+
color: var(--ui-foreground);
|
|
1426
|
+
}
|
|
1427
|
+
:global(.rich-text-toolbar) {
|
|
1428
|
+
scrollbar-width: thin;
|
|
1429
|
+
scrollbar-color: var(--ui-border) transparent;
|
|
1430
|
+
}
|
|
1431
|
+
:global(.rich-text-toolbar > div) {
|
|
1432
|
+
flex-shrink: 0;
|
|
1433
|
+
}
|
|
1434
|
+
:global(.rich-text-content) {
|
|
1435
|
+
overflow-y: auto;
|
|
1436
|
+
overscroll-behavior: contain;
|
|
1437
|
+
background: var(--ui-card);
|
|
1438
|
+
}
|
|
1439
|
+
:global(.rich-text-content .tiptap) {
|
|
1440
|
+
max-width: var(--rich-text-content-width);
|
|
1441
|
+
margin: 0 auto;
|
|
1442
|
+
}
|
|
1443
|
+
:global(.rich-text-content .tiptap ::selection) {
|
|
1444
|
+
background: color-mix(in oklch, var(--ui-primary) 24%, transparent);
|
|
1445
|
+
}
|
|
1446
|
+
:global(.rich-text-content .tiptap p.is-editor-empty:first-child::before) {
|
|
1447
|
+
color: color-mix(in oklch, var(--ui-muted-foreground) 80%, transparent);
|
|
1448
|
+
}
|
|
1449
|
+
:global(.rich-text-content .tiptap:focus) {
|
|
1450
|
+
outline: none;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/* Polished document surface */
|
|
1454
|
+
:global(.rich-text-content .tiptap) {
|
|
1455
|
+
color: var(--ui-foreground);
|
|
1456
|
+
caret-color: var(--ui-primary);
|
|
1457
|
+
line-height: 1.75;
|
|
1458
|
+
font-size: 0.9375rem;
|
|
1459
|
+
}
|
|
1460
|
+
:global(.rich-text-content .tiptap > :first-child) {
|
|
1461
|
+
margin-top: 0;
|
|
1462
|
+
}
|
|
1463
|
+
:global(.rich-text-content .tiptap > :last-child) {
|
|
1464
|
+
margin-bottom: 0;
|
|
1465
|
+
}
|
|
1466
|
+
:global(.tiptap p) {
|
|
1467
|
+
margin: 0.65rem 0;
|
|
1468
|
+
}
|
|
1469
|
+
:global(.tiptap h1),
|
|
1470
|
+
:global(.tiptap h2),
|
|
1471
|
+
:global(.tiptap h3) {
|
|
1472
|
+
color: var(--ui-foreground);
|
|
1473
|
+
letter-spacing: -0.025em;
|
|
1474
|
+
line-height: 1.25;
|
|
1475
|
+
}
|
|
1476
|
+
:global(.tiptap h1) {
|
|
1477
|
+
font-size: clamp(1.5rem, 3vw, 2rem);
|
|
1478
|
+
margin: 1.75rem 0 0.75rem;
|
|
1479
|
+
}
|
|
1480
|
+
:global(.tiptap h2) {
|
|
1481
|
+
font-size: clamp(1.25rem, 2.5vw, 1.5rem);
|
|
1482
|
+
margin: 1.5rem 0 0.65rem;
|
|
1483
|
+
}
|
|
1484
|
+
:global(.tiptap h3) {
|
|
1485
|
+
font-size: 1.125rem;
|
|
1486
|
+
margin: 1.25rem 0 0.5rem;
|
|
1487
|
+
}
|
|
1488
|
+
:global(.tiptap strong) {
|
|
1489
|
+
font-weight: 700;
|
|
1490
|
+
color: var(--ui-foreground);
|
|
1491
|
+
}
|
|
1492
|
+
:global(.tiptap a:not(.image-link)) {
|
|
1493
|
+
color: var(--ui-primary);
|
|
1494
|
+
text-decoration: underline;
|
|
1495
|
+
text-decoration-color: color-mix(
|
|
1496
|
+
in oklch,
|
|
1497
|
+
var(--ui-primary) 35%,
|
|
1498
|
+
transparent
|
|
1499
|
+
);
|
|
1500
|
+
text-underline-offset: 3px;
|
|
1501
|
+
}
|
|
1502
|
+
:global(.tiptap a:not(.image-link):hover) {
|
|
1503
|
+
text-decoration-color: var(--ui-primary);
|
|
1504
|
+
}
|
|
1505
|
+
:global(.tiptap ul),
|
|
1506
|
+
:global(.tiptap ol) {
|
|
1507
|
+
margin: 0.75rem 0;
|
|
1508
|
+
}
|
|
1509
|
+
:global(.tiptap li + li) {
|
|
1510
|
+
margin-top: 0.25rem;
|
|
1511
|
+
}
|
|
1512
|
+
:global(.tiptap blockquote) {
|
|
1513
|
+
background: color-mix(in oklch, var(--ui-primary) 5%, transparent);
|
|
1514
|
+
border-left-width: 3px;
|
|
1515
|
+
border-radius: 0 0.5rem 0.5rem 0;
|
|
1516
|
+
padding: 0.75rem 1rem;
|
|
1517
|
+
margin: 1rem 0;
|
|
1518
|
+
}
|
|
1519
|
+
:global(.tiptap pre) {
|
|
1520
|
+
border: 1px solid var(--ui-border);
|
|
1521
|
+
background: var(--ui-secondary);
|
|
1522
|
+
color: var(--ui-foreground);
|
|
1523
|
+
box-shadow: inset 0 1px 0 color-mix(in oklch, white 5%, transparent);
|
|
1524
|
+
overflow-x: auto;
|
|
1525
|
+
}
|
|
1526
|
+
:global(.tiptap code:not(pre code)) {
|
|
1527
|
+
color: var(--ui-primary);
|
|
1528
|
+
border: 1px solid color-mix(in oklch, var(--ui-primary) 15%, transparent);
|
|
1529
|
+
}
|
|
1530
|
+
:global(.tiptap table) {
|
|
1531
|
+
display: block;
|
|
1532
|
+
overflow-x: auto;
|
|
1533
|
+
border: 1px solid var(--ui-border);
|
|
1534
|
+
border-radius: 0.625rem;
|
|
1535
|
+
background: var(--ui-card);
|
|
1536
|
+
}
|
|
1537
|
+
:global(.tiptap th),
|
|
1538
|
+
:global(.tiptap td) {
|
|
1539
|
+
min-width: 7rem;
|
|
1540
|
+
border-color: var(--ui-border);
|
|
1541
|
+
padding: 0.625rem 0.75rem;
|
|
1542
|
+
}
|
|
1543
|
+
:global(.tiptap th) {
|
|
1544
|
+
color: var(--ui-foreground);
|
|
1545
|
+
background: var(--ui-secondary);
|
|
1546
|
+
font-size: 0.8125rem;
|
|
1547
|
+
}
|
|
1548
|
+
:global(.tiptap td) {
|
|
1549
|
+
color: var(--ui-foreground);
|
|
1550
|
+
}
|
|
1551
|
+
:global(.tiptap .selectedCell) {
|
|
1552
|
+
background: color-mix(in oklch, var(--ui-primary) 12%, transparent);
|
|
1553
|
+
}
|
|
1554
|
+
:global(.tiptap .selectedCell::after) {
|
|
1555
|
+
background: color-mix(in oklch, var(--ui-primary) 10%, transparent);
|
|
1556
|
+
}
|
|
1557
|
+
:global(.tiptap img) {
|
|
1558
|
+
border: 1px solid var(--ui-border);
|
|
1559
|
+
box-shadow: 0 8px 24px
|
|
1560
|
+
color-mix(in oklch, var(--ui-foreground) 10%, transparent);
|
|
1561
|
+
transition: box-shadow 150ms ease-out;
|
|
1562
|
+
}
|
|
1563
|
+
:global(.tiptap img:hover) {
|
|
1564
|
+
box-shadow: 0 12px 30px
|
|
1565
|
+
color-mix(in oklch, var(--ui-foreground) 16%, transparent);
|
|
1566
|
+
}
|
|
1567
|
+
:global(.tiptap .callout-box) {
|
|
1568
|
+
box-shadow: 0 1px 2px
|
|
1569
|
+
color-mix(in oklch, var(--ui-foreground) 6%, transparent);
|
|
1570
|
+
}
|
|
1571
|
+
:global(.tiptap hr) {
|
|
1572
|
+
border-top-color: var(--ui-border);
|
|
1573
|
+
margin: 1.5rem 0;
|
|
1574
|
+
}
|
|
1575
|
+
:global(.tiptap:focus-visible) {
|
|
1576
|
+
outline: none;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
@media (max-width: 640px) {
|
|
1580
|
+
:global(.rich-text-toolbar) {
|
|
1581
|
+
scrollbar-width: none;
|
|
1582
|
+
}
|
|
1583
|
+
:global(.rich-text-toolbar::-webkit-scrollbar) {
|
|
1584
|
+
display: none;
|
|
1585
|
+
}
|
|
1586
|
+
:global(.rich-text-content .tiptap) {
|
|
1587
|
+
font-size: 0.875rem;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1592
|
+
:global(.rich-text-editor),
|
|
1593
|
+
:global(.tiptap img) {
|
|
1594
|
+
transition: none;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1223
1597
|
</style>
|