@teriusu/rich-editor 0.11.2 → 0.11.3
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.
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
ListChecks,
|
|
25
25
|
Quote,
|
|
26
26
|
ChevronDown,
|
|
27
|
+
Check,
|
|
27
28
|
Sigma,
|
|
28
29
|
} from "lucide-svelte";
|
|
29
30
|
import { cn } from "../utils/cn";
|
|
@@ -102,16 +103,6 @@
|
|
|
102
103
|
return t("paragraph");
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
function isParagraphActive(): boolean {
|
|
106
|
-
return (
|
|
107
|
-
!isActive("heading") &&
|
|
108
|
-
!isActive("bulletList") &&
|
|
109
|
-
!isActive("orderedList") &&
|
|
110
|
-
!isActive("taskList") &&
|
|
111
|
-
!isActive("blockquote")
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
106
|
async function addLink() {
|
|
116
107
|
const selection = editor.state.selection.getBookmark();
|
|
117
108
|
const documentBeforePrompt = editor.state.doc;
|
|
@@ -175,7 +166,9 @@
|
|
|
175
166
|
const themeTokens = [
|
|
176
167
|
'--border', '--background', '--foreground', '--popover', '--muted',
|
|
177
168
|
'--muted-foreground', '--primary', '--primary-foreground', '--accent',
|
|
178
|
-
'--accent-foreground', '--ring', '--hce-menu-z', '--hce-menu-surface'
|
|
169
|
+
'--accent-foreground', '--ring', '--hce-menu-z', '--hce-menu-surface',
|
|
170
|
+
'--wh-100', '--wh-500', '--gray-500', '--gray-700', '--gray-900',
|
|
171
|
+
'--primary-500', '--color-line', '--radius-8', '--radius-2xl', '--shadow-popup'
|
|
179
172
|
];
|
|
180
173
|
const syncTheme = () => {
|
|
181
174
|
const hostStyle = getComputedStyle(editor.view.dom);
|
|
@@ -192,7 +185,7 @@
|
|
|
192
185
|
const left = viewport?.offsetLeft ?? 0;
|
|
193
186
|
const top = viewport?.offsetTop ?? 0;
|
|
194
187
|
anchor.style.left = `${left + width / 2}px`;
|
|
195
|
-
anchor.style.top =
|
|
188
|
+
anchor.style.top = `calc(${top + height}px - env(safe-area-inset-bottom, 0px))`;
|
|
196
189
|
element.style.maxWidth = mobile ? `${Math.max(0, width - 16)}px` : '';
|
|
197
190
|
element.style.setProperty('--bubble-popup-height', `${Math.max(44, height - element.getBoundingClientRect().height - 24)}px`);
|
|
198
191
|
};
|
|
@@ -329,125 +322,55 @@
|
|
|
329
322
|
);
|
|
330
323
|
</script>
|
|
331
324
|
|
|
325
|
+
{#snippet blockItem(labelKey: EditorMessageKey, Icon: typeof Type, select: () => void)}
|
|
326
|
+
{@const selected = getCurrentBlockLabel() === t(labelKey)}
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
class="block-menu-item"
|
|
330
|
+
role="menuitemradio"
|
|
331
|
+
aria-checked={selected}
|
|
332
|
+
onclick={() => { select(); showBlockMenu = false; }}
|
|
333
|
+
>
|
|
334
|
+
<span class="block-menu-icon"><Icon size={16} /></span>
|
|
335
|
+
<span class="block-menu-label">{t(labelKey)}</span>
|
|
336
|
+
{#if selected}<span class="block-menu-check"><Check size={16} /></span>{/if}
|
|
337
|
+
</button>
|
|
338
|
+
{/snippet}
|
|
339
|
+
|
|
332
340
|
{#snippet blockPopover()}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
onclick={() => {
|
|
351
|
-
editor.chain().focus().setParagraph().run();
|
|
352
|
-
showBlockMenu = false;
|
|
353
|
-
}}
|
|
354
|
-
>
|
|
355
|
-
<Type size={12} /> {t('paragraph')}
|
|
356
|
-
</button>
|
|
357
|
-
{#each [1, 2, 3] as level (level)}
|
|
358
|
-
{#if has(level === 1 ? 'h1' : level === 2 ? 'h2' : 'h3')}
|
|
359
|
-
{@const Icon = level === 1 ? Heading1 : level === 2 ? Heading2 : Heading3}
|
|
360
|
-
<button
|
|
361
|
-
type="button"
|
|
362
|
-
class={cn(
|
|
363
|
-
"w-full flex items-center gap-2 px-3 py-1.5 text-xs transition-colors",
|
|
364
|
-
isActive("heading", { level })
|
|
365
|
-
? "hce-active"
|
|
366
|
-
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
367
|
-
)}
|
|
368
|
-
onclick={() => {
|
|
369
|
-
editor
|
|
370
|
-
.chain()
|
|
371
|
-
.focus()
|
|
372
|
-
.toggleHeading({ level: level as 1 | 2 | 3 })
|
|
373
|
-
.run();
|
|
374
|
-
showBlockMenu = false;
|
|
375
|
-
}}
|
|
376
|
-
>
|
|
377
|
-
<Icon size={12} /> {t(level === 1 ? 'heading1' : level === 2 ? 'heading2' : 'heading3')}
|
|
378
|
-
</button>
|
|
379
|
-
{/if}
|
|
380
|
-
{/each}
|
|
381
|
-
{#if has('bullet-list')}
|
|
382
|
-
<button
|
|
383
|
-
type="button"
|
|
384
|
-
class={cn(
|
|
385
|
-
"w-full flex items-center gap-2 px-3 py-1.5 text-xs transition-colors",
|
|
386
|
-
isActive("bulletList")
|
|
387
|
-
? "hce-active"
|
|
388
|
-
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
389
|
-
)}
|
|
390
|
-
onclick={() => {
|
|
391
|
-
editor.chain().focus().toggleBulletList().run();
|
|
392
|
-
showBlockMenu = false;
|
|
393
|
-
}}
|
|
394
|
-
>
|
|
395
|
-
<List size={12} /> {t('bulletList')}
|
|
396
|
-
</button>
|
|
397
|
-
{/if}
|
|
398
|
-
{#if has('ordered-list')}
|
|
399
|
-
<button
|
|
400
|
-
type="button"
|
|
401
|
-
class={cn(
|
|
402
|
-
"w-full flex items-center gap-2 px-3 py-1.5 text-xs transition-colors",
|
|
403
|
-
isActive("orderedList")
|
|
404
|
-
? "hce-active"
|
|
405
|
-
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
406
|
-
)}
|
|
407
|
-
onclick={() => {
|
|
408
|
-
editor.chain().focus().toggleOrderedList().run();
|
|
409
|
-
showBlockMenu = false;
|
|
410
|
-
}}
|
|
411
|
-
>
|
|
412
|
-
<ListOrdered size={12} /> {t('orderedList')}
|
|
413
|
-
</button>
|
|
414
|
-
{/if}
|
|
415
|
-
{#if has('checklist')}
|
|
416
|
-
<button
|
|
417
|
-
type="button"
|
|
418
|
-
class={cn(
|
|
419
|
-
"w-full flex items-center gap-2 px-3 py-1.5 text-xs transition-colors",
|
|
420
|
-
isActive("taskList")
|
|
421
|
-
? "hce-active"
|
|
422
|
-
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
423
|
-
)}
|
|
424
|
-
onclick={() => {
|
|
425
|
-
editor.chain().focus().toggleTaskList().run();
|
|
426
|
-
showBlockMenu = false;
|
|
427
|
-
}}
|
|
428
|
-
>
|
|
429
|
-
<ListChecks size={12} /> {t('taskList')}
|
|
430
|
-
</button>
|
|
431
|
-
{/if}
|
|
432
|
-
{#if has('blockquote')}
|
|
433
|
-
<button
|
|
434
|
-
type="button"
|
|
435
|
-
class={cn(
|
|
436
|
-
"w-full flex items-center gap-2 px-3 py-1.5 text-xs transition-colors",
|
|
437
|
-
isActive("blockquote")
|
|
438
|
-
? "hce-active"
|
|
439
|
-
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
440
|
-
)}
|
|
441
|
-
onclick={() => {
|
|
442
|
-
editor.chain().focus().toggleBlockquote().run();
|
|
443
|
-
showBlockMenu = false;
|
|
444
|
-
}}
|
|
445
|
-
>
|
|
446
|
-
<Quote size={12} /> {t('blockquote')}
|
|
447
|
-
</button>
|
|
448
|
-
{/if}
|
|
449
|
-
</div>
|
|
341
|
+
{#if showBlockMenu}
|
|
342
|
+
<div
|
|
343
|
+
bind:this={blockPopupEl}
|
|
344
|
+
class="bubble-popover bubble-block-menu absolute bottom-full left-0 mb-1"
|
|
345
|
+
onmousedown={(e) => e.preventDefault()}
|
|
346
|
+
role="menu"
|
|
347
|
+
aria-orientation="vertical"
|
|
348
|
+
tabindex="-1"
|
|
349
|
+
>
|
|
350
|
+
{@render blockItem('paragraph', Type, () => { editor.chain().focus().setParagraph().run(); })}
|
|
351
|
+
{#each [1, 2, 3] as level (level)}
|
|
352
|
+
{#if has(level === 1 ? 'h1' : level === 2 ? 'h2' : 'h3')}
|
|
353
|
+
{@const Icon = level === 1 ? Heading1 : level === 2 ? Heading2 : Heading3}
|
|
354
|
+
{@render blockItem(
|
|
355
|
+
level === 1 ? 'heading1' : level === 2 ? 'heading2' : 'heading3', Icon,
|
|
356
|
+
() => { editor.chain().focus().toggleHeading({ level: level as 1 | 2 | 3 }).run(); }
|
|
357
|
+
)}
|
|
450
358
|
{/if}
|
|
359
|
+
{/each}
|
|
360
|
+
{#if has('bullet-list')}
|
|
361
|
+
{@render blockItem('bulletList', List, () => { editor.chain().focus().toggleBulletList().run(); })}
|
|
362
|
+
{/if}
|
|
363
|
+
{#if has('ordered-list')}
|
|
364
|
+
{@render blockItem('orderedList', ListOrdered, () => { editor.chain().focus().toggleOrderedList().run(); })}
|
|
365
|
+
{/if}
|
|
366
|
+
{#if has('checklist')}
|
|
367
|
+
{@render blockItem('taskList', ListChecks, () => { editor.chain().focus().toggleTaskList().run(); })}
|
|
368
|
+
{/if}
|
|
369
|
+
{#if has('blockquote')}
|
|
370
|
+
{@render blockItem('blockquote', Quote, () => { editor.chain().focus().toggleBlockquote().run(); })}
|
|
371
|
+
{/if}
|
|
372
|
+
</div>
|
|
373
|
+
{/if}
|
|
451
374
|
{/snippet}
|
|
452
375
|
|
|
453
376
|
{#snippet colorPopover()}
|
|
@@ -509,12 +432,13 @@
|
|
|
509
432
|
type="button"
|
|
510
433
|
onclick={() => (showBlockMenu = !showBlockMenu)}
|
|
511
434
|
aria-haspopup="menu"
|
|
435
|
+
aria-label={getCurrentBlockLabel()}
|
|
512
436
|
aria-expanded={showBlockMenu}
|
|
513
|
-
class="flex items-center gap-1 px-2.5 py-1.5 rounded-full text-xs font-medium text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
437
|
+
class="bubble-block-trigger flex items-center gap-1 px-2.5 py-1.5 rounded-full text-xs font-medium text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
514
438
|
>
|
|
515
|
-
<Type size={12} />
|
|
516
|
-
{getCurrentBlockLabel()}
|
|
517
|
-
<ChevronDown size={12} />
|
|
439
|
+
<Type size={mobile ? 14 : 12} />
|
|
440
|
+
{#if !mobile}{getCurrentBlockLabel()}{/if}
|
|
441
|
+
<ChevronDown size={mobile ? 8 : 12} />
|
|
518
442
|
</button>
|
|
519
443
|
{#if !mobile}{@render blockPopover()}{/if}
|
|
520
444
|
</div>
|
|
@@ -688,28 +612,30 @@
|
|
|
688
612
|
|
|
689
613
|
<style>
|
|
690
614
|
.mobile .hce-floating-panel {
|
|
691
|
-
border-radius:
|
|
692
|
-
padding:
|
|
693
|
-
padding-bottom: max(4px, env(safe-area-inset-bottom));
|
|
615
|
+
border-radius: 8px;
|
|
616
|
+
padding: 1px 3px;
|
|
694
617
|
}
|
|
695
618
|
.mobile .bubble-toolbar-rail {
|
|
696
619
|
display: flex;
|
|
697
620
|
flex-wrap: nowrap;
|
|
698
621
|
max-width: 100%;
|
|
699
|
-
|
|
622
|
+
gap: 1px;
|
|
700
623
|
padding: 0;
|
|
701
|
-
scrollbar-width: none;
|
|
702
|
-
touch-action: pan-x;
|
|
703
624
|
}
|
|
704
|
-
.mobile .bubble-toolbar-rail::-webkit-scrollbar { display: none; }
|
|
705
625
|
.mobile .bubble-toolbar-rail > * { flex-shrink: 0; }
|
|
706
|
-
.mobile button {
|
|
707
|
-
|
|
708
|
-
|
|
626
|
+
.mobile .bubble-toolbar-rail button {
|
|
627
|
+
width: 28px;
|
|
628
|
+
height: 28px;
|
|
629
|
+
padding: 0;
|
|
709
630
|
display: inline-flex;
|
|
710
631
|
align-items: center;
|
|
711
632
|
justify-content: center;
|
|
712
633
|
}
|
|
634
|
+
.mobile .bubble-toolbar-rail .bubble-block-trigger {
|
|
635
|
+
width: 32px;
|
|
636
|
+
gap: 1px;
|
|
637
|
+
}
|
|
638
|
+
.mobile .bubble-popover:not(.bubble-block-menu) button { min-width: 44px; min-height: 44px; }
|
|
713
639
|
.mobile input[type="color"] { min-width: 44px; min-height: 44px; }
|
|
714
640
|
.mobile .bubble-divider { display: none; }
|
|
715
641
|
.mobile .bubble-popover-anchor { position: static; }
|
|
@@ -719,4 +645,39 @@
|
|
|
719
645
|
max-height: min(var(--bubble-popup-height, 50dvh), 300px);
|
|
720
646
|
overflow-y: auto;
|
|
721
647
|
}
|
|
648
|
+
.bubble-block-menu {
|
|
649
|
+
width: 200px;
|
|
650
|
+
box-sizing: border-box;
|
|
651
|
+
padding: 4px;
|
|
652
|
+
background: var(--wh-100, var(--popover, var(--background, #fff)));
|
|
653
|
+
border: 1px solid var(--color-line, var(--border));
|
|
654
|
+
border-radius: var(--radius-2xl, 12px);
|
|
655
|
+
box-shadow: var(--shadow-popup, 0 8px 24px rgb(0 0 0 / 12%));
|
|
656
|
+
}
|
|
657
|
+
.mobile .bubble-block-menu { right: auto; max-width: 100%; }
|
|
658
|
+
.block-menu-item {
|
|
659
|
+
display: flex;
|
|
660
|
+
align-items: center;
|
|
661
|
+
width: 100%;
|
|
662
|
+
min-height: 32px;
|
|
663
|
+
gap: 8px;
|
|
664
|
+
padding: 6px 8px;
|
|
665
|
+
border-radius: var(--radius-8, 8px);
|
|
666
|
+
color: var(--gray-700, var(--foreground));
|
|
667
|
+
font: inherit;
|
|
668
|
+
font-size: 13px;
|
|
669
|
+
line-height: 20px;
|
|
670
|
+
text-align: left;
|
|
671
|
+
white-space: nowrap;
|
|
672
|
+
}
|
|
673
|
+
.block-menu-item:hover, .block-menu-item:focus-visible {
|
|
674
|
+
background: var(--wh-500, var(--muted));
|
|
675
|
+
}
|
|
676
|
+
.block-menu-item[aria-checked='true'] {
|
|
677
|
+
color: var(--gray-900, var(--foreground));
|
|
678
|
+
font-weight: 600;
|
|
679
|
+
}
|
|
680
|
+
.block-menu-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
681
|
+
.block-menu-icon { display: inline-flex; flex: none; color: var(--gray-500, var(--muted-foreground)); }
|
|
682
|
+
.block-menu-check { display: inline-flex; flex: none; color: var(--primary-500, var(--primary)); }
|
|
722
683
|
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BubbleToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/BubbleToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"BubbleToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/BubbleToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA2B3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EAA4C,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEzF,KAAK,gBAAgB,GAAI;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,8BAA8B;IAC9B,CAAC,CAAC,EAAE,gBAAgB,CAAC;CACtB,CAAC;AAmfJ,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|