fix: only a band-wrapped text box gets the alignment buttons
A box in the line is a character its paragraph places, as a picture is, so the three frame-toolbar buttons were the paragraph's own alignment under a frame icon — a second place to look for the same thing. They now show for the band wrap alone, where nothing else can express the position. The ordinary command needed one correction to carry that: a selected box is one node whose range covers its own paragraphs, so aligning it moved the frame and re-set its text in the same keystroke.
This commit is contained in:
1 parent
060dcd7380
commit
f0cf382fb0
6 files changed
+63
-21
No files matched your search
@@ -674,9 +674,9 @@ import { EMPTY_PAGE_DECOR, type PageDecor } from '../storage/pageDecor';
|
||||
// just above it. Hidden while the image toolbar shows (an image inside a box).
|
||||
let textBoxUi = $state<{
|
||||
visible: boolean; top: number; left: number;
|
||||
wrap: WrapMode; wrapAlign: string | null; paraAlign: string | null; shapeKind: ShapeKind; fillColor: string | null;
|
||||
wrap: WrapMode; wrapAlign: string | null; shapeKind: ShapeKind; fillColor: string | null;
|
||||
strokeColor: string | null; strokeWidthPt: number; textVertical: boolean;
|
||||
}>({ visible: false, top: 0, left: 0, wrap: 'inline', wrapAlign: null, paraAlign: null, shapeKind: 'textbox', fillColor: '#FFFFFF', strokeColor: '#000000', strokeWidthPt: 1, textVertical: false });
|
||||
}>({ visible: false, top: 0, left: 0, wrap: 'inline', wrapAlign: null, shapeKind: 'textbox', fillColor: '#FFFFFF', strokeColor: '#000000', strokeWidthPt: 1, textVertical: false });
|
||||
|
||||
function recomputeTextBoxUi() {
|
||||
const ed = editor;
|
||||
@@ -702,8 +702,6 @@ import { EMPTY_PAGE_DECOR, type PageDecor } from '../storage/pageDecor';
|
||||
left: r.left - cRect.left + container!.scrollLeft,
|
||||
wrap: (a.wrap as WrapMode) || 'inline',
|
||||
wrapAlign: (a.wrapAlign as string | null) ?? null,
|
||||
// The box's own paragraph places an in-line box, so the toolbar reads it there.
|
||||
paraAlign: (view!.state.doc.resolve(found.pos).parent.attrs?.textAlign as string | null) ?? null,
|
||||
shapeKind: (a.shapeKind as ShapeKind) || 'textbox',
|
||||
fillColor: (a.fillColor as string | null) ?? null,
|
||||
strokeColor: (a.strokeColor as string | null) ?? null,
|
||||
@@ -1565,7 +1563,6 @@ import { EMPTY_PAGE_DECOR, type PageDecor } from '../storage/pageDecor';
|
||||
left={textBoxUi.left}
|
||||
wrap={textBoxUi.wrap}
|
||||
wrapAlign={textBoxUi.wrapAlign}
|
||||
paraAlign={textBoxUi.paraAlign}
|
||||
shapeKind={textBoxUi.shapeKind}
|
||||
fillColor={textBoxUi.fillColor}
|
||||
strokeColor={textBoxUi.strokeColor}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
left,
|
||||
wrap,
|
||||
wrapAlign,
|
||||
paraAlign,
|
||||
shapeKind,
|
||||
fillColor,
|
||||
strokeColor,
|
||||
@@ -24,7 +23,6 @@
|
||||
left: number;
|
||||
wrap: WrapMode;
|
||||
wrapAlign: string | null;
|
||||
paraAlign: string | null;
|
||||
shapeKind: ShapeKind;
|
||||
fillColor: string | null;
|
||||
strokeColor: string | null;
|
||||
@@ -47,13 +45,13 @@
|
||||
: t().textBox.wrapTopBottom;
|
||||
}
|
||||
|
||||
// Where the box sits across the column. A side wrap has no such choice — the wrap
|
||||
// names the side — so the buttons show for the two modes that leave it in flow.
|
||||
// Where the box sits across the band it spans. Only a band-wrapped box has such a
|
||||
// choice of its own: a side wrap names the side, and a box in the line is a character
|
||||
// its paragraph places — there the ordinary paragraph alignment does it, as on a
|
||||
// picture, so a second control here would only be the same thing under a frame icon.
|
||||
const alignModes = ['left', 'center', 'right'] as const;
|
||||
const alignable = $derived(wrap === 'inline' || wrap === 'topBottom');
|
||||
// A box in the line is a character: its paragraph places it, and that is what both
|
||||
// formats write. A band-wrapped box has a place of its own across the column.
|
||||
const current = $derived(wrap === 'inline' ? paraAlign ?? 'left' : wrapAlign ?? 'left');
|
||||
const alignable = $derived(wrap === 'topBottom');
|
||||
const current = $derived(wrapAlign ?? 'left');
|
||||
function alignTitle(a: (typeof alignModes)[number]): string {
|
||||
return a === 'left' ? t().textBox.alignLeft
|
||||
: a === 'center' ? t().textBox.alignCenter
|
||||
@@ -62,8 +60,7 @@
|
||||
// An imported box placed by coordinate keeps that x over any alignment, so picking
|
||||
// one drops it.
|
||||
function setAlign(a: (typeof alignModes)[number]) {
|
||||
if (wrap === 'inline') editor?.chain().focus().setTextBoxAlign(a === 'left' ? null : a).run();
|
||||
else set({ wrapAlign: a === 'left' ? null : a, wrapOffset: null });
|
||||
set({ wrapAlign: a === 'left' ? null : a, wrapOffset: null });
|
||||
}
|
||||
|
||||
const strokeWidths = [0.5, 1, 2.25];
|
||||
|
||||
@@ -24,7 +24,7 @@ import { Table, TableHeader, TableCell } from '@tiptap/extension-table';
|
||||
import { ResizableTableRow } from './extensions/tableRow';
|
||||
import History from '@tiptap/extension-history';
|
||||
import Placeholder from '@tiptap/extension-placeholder';
|
||||
import TextAlign from '@tiptap/extension-text-align';
|
||||
|
||||
import { PageBreaks } from './extensions/pageBreaks';
|
||||
import { LineHeight } from './extensions/lineHeight';
|
||||
import { ParagraphSpacing } from './extensions/paragraphSpacing';
|
||||
@@ -61,7 +61,7 @@ import { BibliographyEntry } from './extensions/bibliographyEntry';
|
||||
import { CrossReference } from './extensions/crossReference';
|
||||
import { Ruby } from './extensions/ruby';
|
||||
import { Formula } from './extensions/formula';
|
||||
import { TextBox } from './extensions/textBox';
|
||||
import { TextBox, TextAlignInFrames } from './extensions/textBox';
|
||||
import { Columns } from './extensions/columns';
|
||||
import { ColumnsFlow } from './extensions/columnsFlow';
|
||||
import { TableOfContents } from './extensions/tableOfContents';
|
||||
@@ -227,7 +227,8 @@ export const extensions = [
|
||||
// No defaultAlignment: it would put textAlign:'left' on every block, and that inline
|
||||
// style beats the paragraph style's alignment. Unset = follow the style; an explicit
|
||||
// 'left' stays direct formatting that overrides it, as in Word/LibreOffice.
|
||||
TextAlign.configure({
|
||||
// Taught that a selected box means the block it sits in (textBox.ts).
|
||||
TextAlignInFrames.configure({
|
||||
types: ['paragraph', 'heading'],
|
||||
alignments: ['left', 'center', 'right', 'justify'],
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core';
|
||||
import type { CommandProps } from '@tiptap/core';
|
||||
import TextAlign from '@tiptap/extension-text-align';
|
||||
import type { Editor } from '@tiptap/core';
|
||||
import type { Node as PMNode, Slice } from '@tiptap/pm/model';
|
||||
import { NodeSelection, TextSelection, Plugin } from '@tiptap/pm/state';
|
||||
@@ -881,3 +883,24 @@ export function getTextBoxDebug(view: EditorView): TextBoxDebugEntry[] {
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
// A selected text box is one node whose range covers its own paragraphs, so the command
|
||||
// would align the box's contents along with the block holding it — moving the frame and
|
||||
// re-setting its text in one keystroke. With the frame selected only the block it sits
|
||||
// in is meant; the caret inside the box still aligns that text.
|
||||
export const TextAlignInFrames = TextAlign.extend({
|
||||
addCommands() {
|
||||
const base = this.parent?.() ?? {};
|
||||
const onBox = (align: string | null) => (props: CommandProps) =>
|
||||
props.state.selection instanceof NodeSelection
|
||||
&& props.state.selection.node.type.name === 'textBox'
|
||||
&& props.chain().setTextBoxAlign(align).run();
|
||||
return {
|
||||
...base,
|
||||
setTextAlign: (align: string) => (props: CommandProps) =>
|
||||
onBox(align)(props) || base.setTextAlign!(align)(props),
|
||||
unsetTextAlign: () => (props: CommandProps) =>
|
||||
onBox(null)(props) || base.unsetTextAlign!()(props),
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user