Files
edentext/src/lib/components/ImageToolbar.svelte
T

125 lines
4.3 KiB
Svelte

<script lang="ts">
import type { Editor } from '@tiptap/core';
import type { WrapMode } from '../editor/extensions/image';
import { t } from '../i18n/i18n.svelte';
let {
editor,
top,
left,
wrap,
}: {
editor: Editor | null;
top: number;
left: number;
wrap: WrapMode;
} = $props();
// preventDefault on mousedown keeps the image node-selected; .focus() re-anchors
// to it so setImageWrap targets the right node.
function set(mode: WrapMode) {
editor?.chain().focus().setImageWrap(mode).run();
}
const modes: WrapMode[] = ['inline', 'left', 'right', 'topBottom'];
function wrapTitle(m: WrapMode): string {
return m === 'inline' ? t().image.wrapInline
: m === 'left' ? t().image.wrapLeft
: m === 'right' ? t().image.wrapRight
: t().image.wrapTopBottom;
}
</script>
<div
class="image-toolbar"
style="top: {top}px; left: {left}px;"
role="toolbar"
tabindex="-1"
aria-label={t().image.wrapToolbar}
onmousedown={(e) => e.preventDefault()}
>
{#each modes as m}
<button
class="it-btn"
class:active={wrap === m}
title={wrapTitle(m)}
aria-label={wrapTitle(m)}
aria-pressed={wrap === m}
onclick={() => set(m)}
>
{#if m === 'inline'}
<svg width="22" height="22" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<line x1="2.5" y1="3" x2="15.5" y2="3" stroke="currentColor" stroke-width="1.2" />
<line x1="2.5" y1="12" x2="5.5" y2="12" stroke="currentColor" stroke-width="1.2" />
<rect x="6.5" y="6" width="5" height="6" rx="1" fill="currentColor" />
<line x1="12.5" y1="12" x2="15.5" y2="12" stroke="currentColor" stroke-width="1.2" />
<line x1="2.5" y1="15" x2="15.5" y2="15" stroke="currentColor" stroke-width="1.2" />
</svg>
{:else if m === 'left'}
<svg width="22" height="22" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<line x1="2.5" y1="3" x2="15.5" y2="3" stroke="currentColor" stroke-width="1.2" />
<rect x="2.5" y="6" width="6" height="6" rx="1" fill="currentColor" />
<line x1="10" y1="7" x2="15.5" y2="7" stroke="currentColor" stroke-width="1.2" />
<line x1="10" y1="10" x2="15.5" y2="10" stroke="currentColor" stroke-width="1.2" />
<line x1="2.5" y1="15" x2="15.5" y2="15" stroke="currentColor" stroke-width="1.2" />
</svg>
{:else if m === 'right'}
<svg width="22" height="22" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<line x1="2.5" y1="3" x2="15.5" y2="3" stroke="currentColor" stroke-width="1.2" />
<rect x="9.5" y="6" width="6" height="6" rx="1" fill="currentColor" />
<line x1="2.5" y1="7" x2="8" y2="7" stroke="currentColor" stroke-width="1.2" />
<line x1="2.5" y1="10" x2="8" y2="10" stroke="currentColor" stroke-width="1.2" />
<line x1="2.5" y1="15" x2="15.5" y2="15" stroke="currentColor" stroke-width="1.2" />
</svg>
{:else}
<svg width="22" height="22" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<line x1="2.5" y1="3" x2="15.5" y2="3" stroke="currentColor" stroke-width="1.2" />
<rect x="6" y="6" width="6" height="6" rx="1" fill="currentColor" />
<line x1="2.5" y1="15" x2="15.5" y2="15" stroke="currentColor" stroke-width="1.2" />
</svg>
{/if}
</button>
{/each}
</div>
<style>
.image-toolbar {
position: absolute;
transform: translateY(calc(-100% - 6px));
z-index: 150;
display: flex;
align-items: center;
gap: 1px;
padding: 3px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14);
}
.it-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.7rem;
height: 1.7rem;
min-width: unset;
padding: 0;
border: none;
border-radius: calc(var(--radius) - 2px);
background: transparent;
color: var(--color-text);
cursor: pointer;
transition: background 0.12s, color 0.12s;
}
.it-btn:hover {
background: var(--color-btn-hover);
}
.it-btn.active {
background: var(--color-btn-active, var(--color-btn-hover));
color: var(--color-accent, #3b82f6);
}
</style>