mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-24 18:02:47 +08:00
feat: implement textAlign property (#11790)
for paragraph blocks, image blocks, list blocks, and table blocks Should fix #8617 and #11254. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added text alignment options (left, center, right) for paragraph, list, image, note, and table blocks. - Introduced alignment controls in toolbars and slash menus for easier formatting. - Enabled keyboard shortcuts for quick text alignment changes (supports Mac and Windows). - **Localization** - Added English, Simplified Chinese, and Traditional Chinese translations for new alignment commands and shortcuts. - **Style** - Blocks now visually reflect selected text alignment in their layout. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: L-Sun <zover.v@gmail.com>
This commit is contained in:
@@ -8,3 +8,4 @@ export { indentBlock } from './indent-block.js';
|
||||
export { indentBlocks } from './indent-blocks.js';
|
||||
export { selectBlock } from './select-block.js';
|
||||
export { selectBlocksBetween } from './select-blocks-between.js';
|
||||
export { updateBlockAlign } from './update-block-align.js';
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { TextAlign } from '@blocksuite/affine-model';
|
||||
import {
|
||||
getBlockSelectionsCommand,
|
||||
getImageSelectionsCommand,
|
||||
getSelectedBlocksCommand,
|
||||
getTextSelectionCommand,
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import {
|
||||
type BlockComponent,
|
||||
type Command,
|
||||
TextSelection,
|
||||
} from '@blocksuite/std';
|
||||
|
||||
type UpdateBlockAlignConfig = {
|
||||
textAlign: TextAlign;
|
||||
selectedBlocks?: BlockComponent[];
|
||||
};
|
||||
|
||||
export const updateBlockAlign: Command<UpdateBlockAlignConfig> = (
|
||||
ctx,
|
||||
next
|
||||
) => {
|
||||
let { std, textAlign, selectedBlocks } = ctx;
|
||||
|
||||
if (selectedBlocks === null) {
|
||||
const [result, ctx] = std.command
|
||||
.chain()
|
||||
.tryAll(chain => [
|
||||
chain.pipe(getTextSelectionCommand),
|
||||
chain.pipe(getBlockSelectionsCommand),
|
||||
chain.pipe(getImageSelectionsCommand),
|
||||
])
|
||||
.pipe(getSelectedBlocksCommand, { types: ['text', 'block', 'image'] })
|
||||
.run();
|
||||
if (result) {
|
||||
selectedBlocks = ctx.selectedBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedBlocks || selectedBlocks.length === 0) return false;
|
||||
|
||||
selectedBlocks.forEach(block => {
|
||||
std.store.updateBlock(block.model, { textAlign });
|
||||
});
|
||||
|
||||
const selectionManager = std.host.selection;
|
||||
const textSelection = selectionManager.find(TextSelection);
|
||||
if (!textSelection) {
|
||||
return false;
|
||||
}
|
||||
selectionManager.setGroup('note', [textSelection]);
|
||||
return next();
|
||||
};
|
||||
@@ -4,9 +4,15 @@ import {
|
||||
textFormatConfigs,
|
||||
} from '@blocksuite/affine-inline-preset';
|
||||
import {
|
||||
type TextAlignConfig,
|
||||
textAlignConfigs,
|
||||
type TextConversionConfig,
|
||||
textConversionConfigs,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
getSelectedModelsCommand,
|
||||
getTextSelectionCommand,
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
type SlashMenuActionItem,
|
||||
@@ -17,7 +23,7 @@ import {
|
||||
import { HeadingsIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
|
||||
import { updateBlockType } from '../commands';
|
||||
import { updateBlockAlign, updateBlockType } from '../commands';
|
||||
import { tooltips } from './tooltips';
|
||||
|
||||
let basicIndex = 0;
|
||||
@@ -60,6 +66,10 @@ const noteSlashMenuConfig: SlashMenuConfig = {
|
||||
createConversionItem(config, `1_List@${index++}`)
|
||||
),
|
||||
|
||||
...textAlignConfigs.map((config, index) =>
|
||||
createAlignItem(config, `2_Align@${index++}`)
|
||||
),
|
||||
|
||||
...textFormatConfigs
|
||||
.filter(i => !['Code', 'Link'].includes(i.name))
|
||||
.map((config, index) =>
|
||||
@@ -89,6 +99,26 @@ function createConversionItem(
|
||||
};
|
||||
}
|
||||
|
||||
function createAlignItem(
|
||||
config: TextAlignConfig,
|
||||
group?: SlashMenuItem['group']
|
||||
): SlashMenuActionItem {
|
||||
const { textAlign, name, icon } = config;
|
||||
return {
|
||||
name,
|
||||
group,
|
||||
icon,
|
||||
action: ({ std }) => {
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(getTextSelectionCommand)
|
||||
.pipe(getSelectedModelsCommand, { types: ['text'] })
|
||||
.pipe(updateBlockAlign, { textAlign })
|
||||
.run();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createTextFormatItem(
|
||||
config: TextFormatConfig,
|
||||
group?: SlashMenuItem['group']
|
||||
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { textConversionConfigs } from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
textAlignConfigs,
|
||||
textConversionConfigs,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
focusBlockEnd,
|
||||
focusBlockStart,
|
||||
@@ -36,6 +39,7 @@ import {
|
||||
indentBlocks,
|
||||
selectBlock,
|
||||
selectBlocksBetween,
|
||||
updateBlockAlign,
|
||||
updateBlockType,
|
||||
} from './commands';
|
||||
import { moveBlockConfigs } from './move-block';
|
||||
@@ -157,6 +161,36 @@ class NoteKeymap {
|
||||
);
|
||||
};
|
||||
|
||||
private readonly _bindTextAlignHotKey = () => {
|
||||
return textAlignConfigs.reduce(
|
||||
(acc, item) => {
|
||||
const keymap = item.hotkey!.reduce(
|
||||
(acc, key) => {
|
||||
return {
|
||||
...acc,
|
||||
[key]: ctx => {
|
||||
ctx.get('defaultState').event.preventDefault();
|
||||
const [result] = this._std.command
|
||||
.chain()
|
||||
.pipe(updateBlockAlign, { textAlign: item.textAlign })
|
||||
.run();
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
},
|
||||
{} as Record<string, UIEventHandler>
|
||||
);
|
||||
|
||||
return {
|
||||
...acc,
|
||||
...keymap,
|
||||
};
|
||||
},
|
||||
{} as Record<string, UIEventHandler>
|
||||
);
|
||||
};
|
||||
|
||||
private _focusBlock: BlockComponent | null = null;
|
||||
|
||||
private readonly _getClosestNoteByBlockId = (blockId: string) => {
|
||||
@@ -568,6 +602,7 @@ class NoteKeymap {
|
||||
...this._bindMoveBlockHotKey(),
|
||||
...this._bindQuickActionHotKey(),
|
||||
...this._bindTextConversionHotKey(),
|
||||
...this._bindTextAlignHotKey(),
|
||||
Tab: ctx => {
|
||||
const [success] = this.std.command.exec(indentBlocks);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user