feat: insert caption is in the picture and table tabs too
References keeps it, as in both products, but that is a tab away from the object being captioned. The contextual tabs mount their own CaptionDialog — it reads the caret for category and position, so the copies take no props.
This commit is contained in:
1 parent
53317bf88b
commit
6c731b9233
5 files changed
+46
No files matched your search
@@ -84,6 +84,10 @@ at every height. A flex row cannot reserve width in part of its height, so it mo
|
||||
- **A reused picker anchors its panel itself**, and no prop says when it opened. `pinPanels`,
|
||||
one action on the band, watches for a mounted absolutely-positioned node and pins that; the
|
||||
ColorPicker places its own and only needs the band-aware top.
|
||||
- **Insert caption sits in three tabs**: References, where both products keep it, plus the
|
||||
picture/shape and Table Layout contextual tabs, which are open anyway when you caption
|
||||
something. Each mounts its own `CaptionDialog`; it reads the caret for its category, so the
|
||||
copies need no props of their own.
|
||||
- **Each wrapper names the menu it owns.** One shared open-menu id means every `clickOutside`
|
||||
sees every mousedown; without the id each wrapper would close a sibling's open menu, and the
|
||||
click on one of its rows would never land.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import RibbonButton from '../RibbonButton.svelte';
|
||||
import ColorPicker from '../../ColorPicker.svelte';
|
||||
import ShapePicker from '../../ShapePicker.svelte';
|
||||
import CaptionDialog from '../../CaptionDialog.svelte';
|
||||
import type { WrapMode } from '../../../editor/extensions/image';
|
||||
import type { ShapeKind } from '../../../editor/extensions/textBox';
|
||||
import { t } from '../../../i18n/i18n.svelte';
|
||||
@@ -29,6 +30,8 @@
|
||||
{ key: 'topBottom', icon: 'wrapTopBottom', label: () => t().image.wrapTopBottom },
|
||||
];
|
||||
|
||||
let captionOpen = $state(false);
|
||||
|
||||
function setWrap(w: WrapMode) {
|
||||
if (which === 'picture') editor?.chain().focus().setImageWrap(w).run();
|
||||
else editor?.chain().focus().setTextBoxAttrs({ wrap: w }).run();
|
||||
@@ -41,6 +44,21 @@
|
||||
{/each}
|
||||
</RibbonGroup>
|
||||
|
||||
<div class="ribbon-sep"></div>
|
||||
|
||||
<!-- Also in the References tab, where both products keep it — but that is a tab away
|
||||
from the object it captions, and this one only shows while that object is selected. -->
|
||||
<RibbonGroup label={t().ribbon.groups.captions}>
|
||||
<RibbonButton
|
||||
variant="big"
|
||||
icon="caption"
|
||||
label={t().ribbon.insertCaption}
|
||||
title={t().caption.title}
|
||||
disabled={!editor}
|
||||
onclick={() => (captionOpen = true)}
|
||||
/>
|
||||
</RibbonGroup>
|
||||
|
||||
{#if which === 'picture'}
|
||||
<div class="ribbon-sep"></div>
|
||||
|
||||
@@ -113,6 +131,8 @@
|
||||
</RibbonGroup>
|
||||
{/if}
|
||||
|
||||
<CaptionDialog bind:open={captionOpen} {editor} />
|
||||
|
||||
{#snippet fillIcon()}<span class="swatch" style="background: {fillColor ?? 'transparent'}"></span>{/snippet}
|
||||
{#snippet strokeIcon()}<span class="swatch outline" style="border-color: {strokeColor ?? 'currentColor'}"></span>{/snippet}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import TableSplitDialog from '../../TableSplitDialog.svelte';
|
||||
import TableSortDialog from '../../TableSortDialog.svelte';
|
||||
import TableFormulaDialog from '../../TableFormulaDialog.svelte';
|
||||
import CaptionDialog from '../../CaptionDialog.svelte';
|
||||
import { captionClicks, anchored, clickOutside, isMenuOpen, toggleMenu, closeMenu } from '../menu.svelte';
|
||||
import { isHeaderStyled } from '../../../editor/extensions/tableHeaderRow';
|
||||
import { DEFAULT_CELL_PADDING, parseCellPadding, type CellPadding } from '../../../editor/extensions/tableCellPadding';
|
||||
@@ -28,6 +29,8 @@
|
||||
which: 'design' | 'layout';
|
||||
} = $props();
|
||||
|
||||
let captionOpen = $state(false);
|
||||
|
||||
function run(cmd: (chain: ChainedCommands) => ChainedCommands) {
|
||||
if (!editor) return;
|
||||
cmd(editor.chain().focus()).run();
|
||||
@@ -302,8 +305,25 @@
|
||||
onclick={() => setNumberRecognition(!numberRecognition())}
|
||||
/>
|
||||
</RibbonGroup>
|
||||
|
||||
<div class="ribbon-sep"></div>
|
||||
|
||||
<!-- Also in the References tab, where both products keep it — but that is a tab away
|
||||
from the table it captions, and this one only shows while the caret is in one. -->
|
||||
<RibbonGroup label={t().ribbon.groups.captions}>
|
||||
<RibbonButton
|
||||
variant="big"
|
||||
icon="caption"
|
||||
label={t().ribbon.insertCaption}
|
||||
title={t().caption.title}
|
||||
disabled={!inTable}
|
||||
onclick={() => (captionOpen = true)}
|
||||
/>
|
||||
</RibbonGroup>
|
||||
{/if}
|
||||
|
||||
<CaptionDialog bind:open={captionOpen} {editor} />
|
||||
|
||||
{#snippet shadeIcon()}<span class="shade-glyph"></span>{/snippet}
|
||||
|
||||
<style>
|
||||
|
||||
@@ -773,6 +773,7 @@ const de: Messages = {
|
||||
arrange: 'Anordnen',
|
||||
shapeStyles: 'Formenarten',
|
||||
accessibility: 'Barrierefreiheit',
|
||||
captions: 'Beschriftungen',
|
||||
editing: 'Bearbeiten',
|
||||
},
|
||||
case: {
|
||||
|
||||
@@ -779,6 +779,7 @@ const en = {
|
||||
arrange: 'Arrange',
|
||||
shapeStyles: 'Shape Styles',
|
||||
accessibility: 'Accessibility',
|
||||
captions: 'Captions',
|
||||
editing: 'Editing',
|
||||
},
|
||||
case: {
|
||||
|
||||
Reference in new issue
Block a user