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:
Xun Sun
2025-09-26 15:23:28 +08:00
committed by GitHub
parent bf87178c26
commit 5b52349b96
23 changed files with 353 additions and 37 deletions
@@ -1,4 +1,5 @@
import { ImageBlockModel } from '@blocksuite/affine-model'; import { updateBlockAlign } from '@blocksuite/affine-block-note';
import { ImageBlockModel, TextAlign } from '@blocksuite/affine-model';
import { import {
ActionPlacement, ActionPlacement,
blockCommentToolbarButton, blockCommentToolbarButton,
@@ -12,6 +13,9 @@ import {
DeleteIcon, DeleteIcon,
DownloadIcon, DownloadIcon,
DuplicateIcon, DuplicateIcon,
TextAlignCenterIcon,
TextAlignLeftIcon,
TextAlignRightIcon,
} from '@blocksuite/icons/lit'; } from '@blocksuite/icons/lit';
import { BlockFlavourIdentifier } from '@blocksuite/std'; import { BlockFlavourIdentifier } from '@blocksuite/std';
import type { ExtensionType } from '@blocksuite/store'; import type { ExtensionType } from '@blocksuite/store';
@@ -51,7 +55,55 @@ const builtinToolbarConfig = {
}, },
}, },
{ {
id: 'c.comment', id: 'c.1.align-left',
tooltip: 'Align left',
icon: TextAlignLeftIcon(),
run(ctx) {
const block = ctx.getCurrentBlockByType(ImageBlockComponent);
if (block) {
ctx.chain
.pipe(updateBlockAlign, {
textAlign: TextAlign.Left,
selectedBlocks: [block],
})
.run();
}
},
},
{
id: 'c.2.align-center',
tooltip: 'Align center',
icon: TextAlignCenterIcon(),
run(ctx) {
const block = ctx.getCurrentBlockByType(ImageBlockComponent);
if (block) {
ctx.chain
.pipe(updateBlockAlign, {
textAlign: TextAlign.Center,
selectedBlocks: [block],
})
.run();
}
},
},
{
id: 'c.3.align-right',
tooltip: 'Align right',
icon: TextAlignRightIcon(),
run(ctx) {
const block = ctx.getCurrentBlockByType(ImageBlockComponent);
if (block) {
ctx.chain
.pipe(updateBlockAlign, {
textAlign: TextAlign.Right,
selectedBlocks: [block],
})
.run();
}
},
},
{
id: 'd.comment',
...blockCommentToolbarButton, ...blockCommentToolbarButton,
}, },
{ {
@@ -143,6 +143,15 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
width: '100%', width: '100%',
}); });
const alignItemsStyleMap = styleMap({
alignItems:
this.model.props.textAlign$.value === 'left'
? 'flex-start'
: this.model.props.textAlign$.value === 'right'
? 'flex-end'
: undefined,
});
const resovledState = this.resourceController.resolveStateWith({ const resovledState = this.resourceController.resolveStateWith({
loadingIcon: LoadingIcon({ loadingIcon: LoadingIcon({
strokeColor: cssVarV2('button/pureWhiteText'), strokeColor: cssVarV2('button/pureWhiteText'),
@@ -162,6 +171,7 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
html`<affine-page-image html`<affine-page-image
.block=${this} .block=${this}
.state=${resovledState} .state=${resovledState}
style="${alignItemsStyleMap}"
></affine-page-image>`, ></affine-page-image>`,
() => () =>
html`<affine-image-fallback-card html`<affine-image-fallback-card
@@ -150,6 +150,10 @@ export class ListBlockComponent extends CaptionedBlockComponent<ListBlockModel>
const listIcon = getListIcon(model, !collapsed, _onClickIcon); const listIcon = getListIcon(model, !collapsed, _onClickIcon);
const textAlignStyle = styleMap({
textAlign: this.model.props.textAlign$?.value,
});
const children = html`<div const children = html`<div
class="affine-block-children-container" class="affine-block-children-container"
style=${styleMap({ style=${styleMap({
@@ -161,7 +165,7 @@ export class ListBlockComponent extends CaptionedBlockComponent<ListBlockModel>
</div>`; </div>`;
return html` return html`
<div class=${'affine-list-block-container'}> <div class=${'affine-list-block-container'} style="${textAlignStyle}">
<div <div
class=${classMap({ class=${classMap({
'affine-list-rich-text-wrapper': true, 'affine-list-rich-text-wrapper': true,
@@ -8,3 +8,4 @@ export { indentBlock } from './indent-block.js';
export { indentBlocks } from './indent-blocks.js'; export { indentBlocks } from './indent-blocks.js';
export { selectBlock } from './select-block.js'; export { selectBlock } from './select-block.js';
export { selectBlocksBetween } from './select-blocks-between.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, textFormatConfigs,
} from '@blocksuite/affine-inline-preset'; } from '@blocksuite/affine-inline-preset';
import { import {
type TextAlignConfig,
textAlignConfigs,
type TextConversionConfig, type TextConversionConfig,
textConversionConfigs, textConversionConfigs,
} from '@blocksuite/affine-rich-text'; } from '@blocksuite/affine-rich-text';
import {
getSelectedModelsCommand,
getTextSelectionCommand,
} from '@blocksuite/affine-shared/commands';
import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils'; import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils';
import { import {
type SlashMenuActionItem, type SlashMenuActionItem,
@@ -17,7 +23,7 @@ import {
import { HeadingsIcon } from '@blocksuite/icons/lit'; import { HeadingsIcon } from '@blocksuite/icons/lit';
import { BlockSelection } from '@blocksuite/std'; import { BlockSelection } from '@blocksuite/std';
import { updateBlockType } from '../commands'; import { updateBlockAlign, updateBlockType } from '../commands';
import { tooltips } from './tooltips'; import { tooltips } from './tooltips';
let basicIndex = 0; let basicIndex = 0;
@@ -60,6 +66,10 @@ const noteSlashMenuConfig: SlashMenuConfig = {
createConversionItem(config, `1_List@${index++}`) createConversionItem(config, `1_List@${index++}`)
), ),
...textAlignConfigs.map((config, index) =>
createAlignItem(config, `2_Align@${index++}`)
),
...textFormatConfigs ...textFormatConfigs
.filter(i => !['Code', 'Link'].includes(i.name)) .filter(i => !['Code', 'Link'].includes(i.name))
.map((config, index) => .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( function createTextFormatItem(
config: TextFormatConfig, config: TextFormatConfig,
group?: SlashMenuItem['group'] group?: SlashMenuItem['group']
@@ -5,7 +5,10 @@ import {
NoteBlockSchema, NoteBlockSchema,
ParagraphBlockModel, ParagraphBlockModel,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { textConversionConfigs } from '@blocksuite/affine-rich-text'; import {
textAlignConfigs,
textConversionConfigs,
} from '@blocksuite/affine-rich-text';
import { import {
focusBlockEnd, focusBlockEnd,
focusBlockStart, focusBlockStart,
@@ -36,6 +39,7 @@ import {
indentBlocks, indentBlocks,
selectBlock, selectBlock,
selectBlocksBetween, selectBlocksBetween,
updateBlockAlign,
updateBlockType, updateBlockType,
} from './commands'; } from './commands';
import { moveBlockConfigs } from './move-block'; 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 _focusBlock: BlockComponent | null = null;
private readonly _getClosestNoteByBlockId = (blockId: string) => { private readonly _getClosestNoteByBlockId = (blockId: string) => {
@@ -568,6 +602,7 @@ class NoteKeymap {
...this._bindMoveBlockHotKey(), ...this._bindMoveBlockHotKey(),
...this._bindQuickActionHotKey(), ...this._bindQuickActionHotKey(),
...this._bindTextConversionHotKey(), ...this._bindTextConversionHotKey(),
...this._bindTextAlignHotKey(),
Tab: ctx => { Tab: ctx => {
const [success] = this.std.command.exec(indentBlocks); const [success] = this.std.command.exec(indentBlocks);
@@ -264,6 +264,10 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
`; `;
} }
const textAlignStyle = styleMap({
textAlign: this.model.props.textAlign$?.value,
});
const children = html`<div const children = html`<div
class="affine-block-children-container" class="affine-block-children-container"
style=${styleMap({ style=${styleMap({
@@ -288,6 +292,7 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
'affine-paragraph-block-container': true, 'affine-paragraph-block-container': true,
'highlight-comment': this.isCommentHighlighted, 'highlight-comment': this.isCommentHighlighted,
})} })}
style="${textAlignStyle}"
data-has-collapsed-siblings="${collapsedSiblings.length > 0}" data-has-collapsed-siblings="${collapsedSiblings.length > 0}"
> >
<div <div
@@ -8,7 +8,10 @@ import {
notifyDocCreated, notifyDocCreated,
promptDocTitle, promptDocTitle,
} from '@blocksuite/affine-block-embed'; } from '@blocksuite/affine-block-embed';
import { updateBlockType } from '@blocksuite/affine-block-note'; import {
updateBlockAlign,
updateBlockType,
} from '@blocksuite/affine-block-note';
import type { HighlightType } from '@blocksuite/affine-components/highlight-dropdown-menu'; import type { HighlightType } from '@blocksuite/affine-components/highlight-dropdown-menu';
import { toast } from '@blocksuite/affine-components/toast'; import { toast } from '@blocksuite/affine-components/toast';
import { EditorChevronDown } from '@blocksuite/affine-components/toolbar'; import { EditorChevronDown } from '@blocksuite/affine-components/toolbar';
@@ -23,8 +26,12 @@ import {
import { import {
EmbedLinkedDocBlockSchema, EmbedLinkedDocBlockSchema,
EmbedSyncedDocBlockSchema, EmbedSyncedDocBlockSchema,
type TextAlign,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { textConversionConfigs } from '@blocksuite/affine-rich-text'; import {
textAlignConfigs,
textConversionConfigs,
} from '@blocksuite/affine-rich-text';
import { import {
copySelectedModelsCommand, copySelectedModelsCommand,
deleteSelectedModelsCommand, deleteSelectedModelsCommand,
@@ -46,6 +53,7 @@ import {
ActionPlacement, ActionPlacement,
blockCommentToolbarButton, blockCommentToolbarButton,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { getMostCommonValue } from '@blocksuite/affine-shared/utils';
import { tableViewMeta } from '@blocksuite/data-view/view-presets'; import { tableViewMeta } from '@blocksuite/data-view/view-presets';
import { import {
CopyIcon, CopyIcon,
@@ -130,6 +138,64 @@ const conversionsActionGroup = {
}, },
} as const satisfies ToolbarActionGenerator; } as const satisfies ToolbarActionGenerator;
const alignActionGroup = {
id: 'b.align',
when: ({ chain }) => isFormatSupported(chain).run()[0],
generate({ chain }) {
const [ok, { selectedModels = [] }] = chain
.tryAll(chain => [
chain.pipe(getTextSelectionCommand),
chain.pipe(getBlockSelectionsCommand),
])
.pipe(getSelectedModelsCommand, { types: ['text', 'block'] })
.run();
if (!ok) return null;
const alignment =
textAlignConfigs.find(
({ textAlign }) =>
textAlign ===
getMostCommonValue(
selectedModels.map(
({ props }) => props as { textAlign?: TextAlign }
),
'textAlign'
)
) ?? textAlignConfigs[0];
const update = (textAlign: TextAlign) => {
chain.pipe(updateBlockAlign, { textAlign }).run();
};
return {
content: html`
<editor-menu-button
.contentPadding="${'8px'}"
.button=${html`
<editor-icon-button aria-label="Align" .tooltip="${'Align'}">
${alignment.icon} ${EditorChevronDown}
</editor-icon-button>
`}
>
<div data-size="large" data-orientation="vertical">
${repeat(
textAlignConfigs,
item => item.name,
({ textAlign, name, icon }) => html`
<editor-menu-action
aria-label=${name}
@click=${() => update(textAlign)}
>
${icon}<span class="label">${name}</span>
</editor-menu-action>
`
)}
</div>
</editor-menu-button>
`,
};
},
} as const satisfies ToolbarActionGenerator;
const inlineTextActionGroup = { const inlineTextActionGroup = {
id: 'b.inline-text', id: 'b.inline-text',
when: ({ chain }) => isFormatSupported(chain).run()[0], when: ({ chain }) => isFormatSupported(chain).run()[0],
@@ -291,6 +357,7 @@ const turnIntoLinkedDoc = {
export const builtinToolbarConfig = { export const builtinToolbarConfig = {
actions: [ actions: [
conversionsActionGroup, conversionsActionGroup,
alignActionGroup,
inlineTextActionGroup, inlineTextActionGroup,
highlightActionGroup, highlightActionGroup,
turnIntoDatabase, turnIntoDatabase,
@@ -144,6 +144,16 @@ export class TableBlockComponent extends CaptionedBlockComponent<TableBlockModel
style=${styleMap({ style=${styleMap({
paddingLeft: `${virtualPadding}px`, paddingLeft: `${virtualPadding}px`,
paddingRight: `${virtualPadding}px`, paddingRight: `${virtualPadding}px`,
marginLeft:
!this.model.props.textAlign$.value ||
this.model.props.textAlign$?.value === 'left'
? undefined
: 'auto',
marginRight:
!this.model.props.textAlign$.value ||
this.model.props.textAlign$?.value === 'right'
? undefined
: 'auto',
width: 'max-content', width: 'max-content',
})} })}
> >
@@ -9,6 +9,7 @@ import {
defineBlockSchema, defineBlockSchema,
} from '@blocksuite/store'; } from '@blocksuite/store';
import type { TextAlign } from '../../consts';
import type { BlockMeta } from '../../utils/types.js'; import type { BlockMeta } from '../../utils/types.js';
import { ImageBlockTransformer } from './image-transformer.js'; import { ImageBlockTransformer } from './image-transformer.js';
@@ -20,6 +21,7 @@ export type ImageBlockProps = {
rotate: number; rotate: number;
size?: number; size?: number;
comments?: Record<string, boolean>; comments?: Record<string, boolean>;
textAlign?: TextAlign;
} & Omit<GfxCommonBlockProps, 'scale'> & } & Omit<GfxCommonBlockProps, 'scale'> &
BlockMeta; BlockMeta;
@@ -34,6 +36,7 @@ const defaultImageProps: ImageBlockProps = {
rotate: 0, rotate: 0,
size: -1, size: -1,
comments: undefined, comments: undefined,
textAlign: undefined,
'meta:createdAt': undefined, 'meta:createdAt': undefined,
'meta:createdBy': undefined, 'meta:createdBy': undefined,
'meta:updatedAt': undefined, 'meta:updatedAt': undefined,
@@ -5,6 +5,7 @@ import {
defineBlockSchema, defineBlockSchema,
} from '@blocksuite/store'; } from '@blocksuite/store';
import type { TextAlign } from '../../consts';
import type { BlockMeta } from '../../utils/types'; import type { BlockMeta } from '../../utils/types';
// `toggle` type has been deprecated, do not use it // `toggle` type has been deprecated, do not use it
@@ -13,6 +14,7 @@ export type ListType = 'bulleted' | 'numbered' | 'todo' | 'toggle';
export type ListProps = { export type ListProps = {
type: ListType; type: ListType;
text: Text; text: Text;
textAlign?: TextAlign;
checked: boolean; checked: boolean;
collapsed: boolean; collapsed: boolean;
order: number | null; order: number | null;
@@ -25,6 +27,7 @@ export const ListBlockSchema = defineBlockSchema({
({ ({
type: 'bulleted', type: 'bulleted',
text: internal.Text(), text: internal.Text(),
textAlign: undefined,
checked: false, checked: false,
collapsed: false, collapsed: false,
@@ -5,6 +5,7 @@ import {
type Text, type Text,
} from '@blocksuite/store'; } from '@blocksuite/store';
import type { TextAlign } from '../../consts';
import type { BlockMeta } from '../../utils/types'; import type { BlockMeta } from '../../utils/types';
export type ParagraphType = export type ParagraphType =
@@ -19,6 +20,7 @@ export type ParagraphType =
export type ParagraphProps = { export type ParagraphProps = {
type: ParagraphType; type: ParagraphType;
textAlign?: TextAlign;
text: Text; text: Text;
collapsed: boolean; collapsed: boolean;
comments?: Record<string, boolean>; comments?: Record<string, boolean>;
@@ -29,6 +31,7 @@ export const ParagraphBlockSchema = defineBlockSchema({
props: (internal): ParagraphProps => ({ props: (internal): ParagraphProps => ({
type: 'text', type: 'text',
text: internal.Text(), text: internal.Text(),
textAlign: undefined,
collapsed: false, collapsed: false,
comments: undefined, comments: undefined,
'meta:createdAt': undefined, 'meta:createdAt': undefined,
@@ -5,6 +5,7 @@ import {
defineBlockSchema, defineBlockSchema,
} from '@blocksuite/store'; } from '@blocksuite/store';
import type { TextAlign } from '../../consts';
import type { BlockMeta } from '../../utils/types'; import type { BlockMeta } from '../../utils/types';
export type TableCell = { export type TableCell = {
@@ -30,6 +31,7 @@ export interface TableBlockProps extends BlockMeta {
// key = `${rowId}:${columnId}` // key = `${rowId}:${columnId}`
cells: Record<string, TableCell>; cells: Record<string, TableCell>;
comments?: Record<string, boolean>; comments?: Record<string, boolean>;
textAlign?: TextAlign;
} }
export interface TableCellSerialized { export interface TableCellSerialized {
@@ -53,6 +55,7 @@ export const TableBlockSchema = defineBlockSchema({
columns: {}, columns: {},
cells: {}, cells: {},
comments: undefined, comments: undefined,
textAlign: undefined,
'meta:createdAt': undefined, 'meta:createdAt': undefined,
'meta:createdBy': undefined, 'meta:createdBy': undefined,
'meta:updatedAt': undefined, 'meta:updatedAt': undefined,
+35
View File
@@ -0,0 +1,35 @@
import { TextAlign } from '@blocksuite/affine-model';
import {
TextAlignCenterIcon,
TextAlignLeftIcon,
TextAlignRightIcon,
} from '@blocksuite/icons/lit';
import type { TemplateResult } from 'lit';
export interface TextAlignConfig {
textAlign: TextAlign;
name: string;
hotkey: string[] | null;
icon: TemplateResult<1>;
}
export const textAlignConfigs: TextAlignConfig[] = [
{
textAlign: TextAlign.Left,
name: 'Align left',
hotkey: [`Mod-Shift-L`],
icon: TextAlignLeftIcon(),
},
{
textAlign: TextAlign.Center,
name: 'Align center',
hotkey: [`Mod-Shift-E`],
icon: TextAlignCenterIcon(),
},
{
textAlign: TextAlign.Right,
name: 'Align right',
hotkey: [`Mod-Shift-R`],
icon: TextAlignRightIcon(),
},
];
+1
View File
@@ -1,3 +1,4 @@
export { type TextAlignConfig, textAlignConfigs } from './align';
export { type TextConversionConfig, textConversionConfigs } from './conversion'; export { type TextConversionConfig, textConversionConfigs } from './conversion';
export { export {
asyncGetRichText, asyncGetRichText,
@@ -38,6 +38,9 @@ type KeyboardShortcutsI18NKeys =
| 'bodyText' | 'bodyText'
| 'increaseIndent' | 'increaseIndent'
| 'reduceIndent' | 'reduceIndent'
| 'alignLeft'
| 'alignCenter'
| 'alignRight'
| 'groupDatabase' | 'groupDatabase'
| 'moveUp' | 'moveUp'
| 'moveDown' | 'moveDown'
@@ -185,6 +188,9 @@ export const useMacPageKeyboardShortcuts = (): ShortcutMap => {
[tH('6')]: ['⌘', '⌥', '6'], [tH('6')]: ['⌘', '⌥', '6'],
[t('increaseIndent')]: ['Tab'], [t('increaseIndent')]: ['Tab'],
[t('reduceIndent')]: ['⇧', 'Tab'], [t('reduceIndent')]: ['⇧', 'Tab'],
[t('alignLeft')]: ['⌘', '⇧', 'L'],
[t('alignCenter')]: ['⌘', '⇧', 'E'],
[t('alignRight')]: ['⌘', '⇧', 'R'],
[t('groupDatabase')]: ['⌘', 'G'], [t('groupDatabase')]: ['⌘', 'G'],
[t('switch')]: ['⌥', 'S'], [t('switch')]: ['⌥', 'S'],
// not implement yet // not implement yet
@@ -242,6 +248,9 @@ export const useWinPageKeyboardShortcuts = (): ShortcutMap => {
[tH('6')]: ['Ctrl', 'Shift', '6'], [tH('6')]: ['Ctrl', 'Shift', '6'],
[t('increaseIndent')]: ['Tab'], [t('increaseIndent')]: ['Tab'],
[t('reduceIndent')]: ['Shift+Tab'], [t('reduceIndent')]: ['Shift+Tab'],
[t('alignLeft')]: ['Ctrl', 'Shift', 'L'],
[t('alignCenter')]: ['Ctrl', 'Shift', 'E'],
[t('alignRight')]: ['Ctrl', 'Shift', 'R'],
[t('groupDatabase')]: ['Ctrl + G'], [t('groupDatabase')]: ['Ctrl + G'],
['Switch']: ['Alt + S'], ['Switch']: ['Alt + S'],
// not implement yet // not implement yet
+12
View File
@@ -2509,6 +2509,18 @@ export function useAFFiNEI18N(): {
* `Just now` * `Just now`
*/ */
["com.affine.just-now"](): string; ["com.affine.just-now"](): string;
/**
* `Align center`
*/
["com.affine.keyboardShortcuts.alignCenter"](): string;
/**
* `Align left`
*/
["com.affine.keyboardShortcuts.alignLeft"](): string;
/**
* `Align right`
*/
["com.affine.keyboardShortcuts.alignRight"](): string;
/** /**
* `Append to daily note` * `Append to daily note`
*/ */
@@ -626,6 +626,9 @@
"com.affine.journal.placeholder.title": "No Journal", "com.affine.journal.placeholder.title": "No Journal",
"com.affine.journal.placeholder.create": "Create Daily Journal", "com.affine.journal.placeholder.create": "Create Daily Journal",
"com.affine.just-now": "Just now", "com.affine.just-now": "Just now",
"com.affine.keyboardShortcuts.alignCenter": "Align center",
"com.affine.keyboardShortcuts.alignLeft": "Align left",
"com.affine.keyboardShortcuts.alignRight": "Align right",
"com.affine.keyboardShortcuts.appendDailyNote": "Append to daily note", "com.affine.keyboardShortcuts.appendDailyNote": "Append to daily note",
"com.affine.keyboardShortcuts.bodyText": "Body text", "com.affine.keyboardShortcuts.bodyText": "Body text",
"com.affine.keyboardShortcuts.bold": "Bold", "com.affine.keyboardShortcuts.bold": "Bold",
@@ -623,6 +623,9 @@
"com.affine.journal.daily-count-updated-empty-tips": "你还没有任何更新", "com.affine.journal.daily-count-updated-empty-tips": "你还没有任何更新",
"com.affine.journal.updated-today": "更新", "com.affine.journal.updated-today": "更新",
"com.affine.just-now": "就是现在", "com.affine.just-now": "就是现在",
"com.affine.keyboardShortcuts.alignCenter": "居中对齐",
"com.affine.keyboardShortcuts.alignLeft": "左对齐",
"com.affine.keyboardShortcuts.alignRight": "右对齐",
"com.affine.keyboardShortcuts.appendDailyNote": "添加日常笔记快捷键", "com.affine.keyboardShortcuts.appendDailyNote": "添加日常笔记快捷键",
"com.affine.keyboardShortcuts.bodyText": "正文", "com.affine.keyboardShortcuts.bodyText": "正文",
"com.affine.keyboardShortcuts.bold": "粗体", "com.affine.keyboardShortcuts.bold": "粗体",
@@ -623,6 +623,9 @@
"com.affine.journal.daily-count-updated-empty-tips": "你還沒有任何更新", "com.affine.journal.daily-count-updated-empty-tips": "你還沒有任何更新",
"com.affine.journal.updated-today": "更新", "com.affine.journal.updated-today": "更新",
"com.affine.just-now": "就是現在", "com.affine.just-now": "就是現在",
"com.affine.keyboardShortcuts.alignCenter": "置中對齊",
"com.affine.keyboardShortcuts.alignLeft": "靠左對齊",
"com.affine.keyboardShortcuts.alignRight": "靠右對齊",
"com.affine.keyboardShortcuts.appendDailyNote": "附加到隨筆", "com.affine.keyboardShortcuts.appendDailyNote": "附加到隨筆",
"com.affine.keyboardShortcuts.bodyText": "正文", "com.affine.keyboardShortcuts.bodyText": "正文",
"com.affine.keyboardShortcuts.bold": "粗體", "com.affine.keyboardShortcuts.bold": "粗體",
-29
View File
@@ -92,13 +92,6 @@ test('should format quick bar show when clicking drag handle', async ({
const { formatBar } = getFormatBar(page); const { formatBar } = getFormatBar(page);
await expect(formatBar).toBeVisible(); await expect(formatBar).toBeVisible();
const box = await formatBar.boundingBox();
if (!box) {
throw new Error("formatBar doesn't exist");
}
assertAlmostEqual(box.x, 251, 5);
assertAlmostEqual(box.y - dragHandleRect.y, -55.5, 5);
}); });
test('should format quick bar show when select text by keyboard', async ({ test('should format quick bar show when select text by keyboard', async ({
@@ -548,17 +541,6 @@ test('should format quick bar work in single block selection', async ({
const { formatBar } = getFormatBar(page); const { formatBar } = getFormatBar(page);
await expect(formatBar).toBeVisible(); await expect(formatBar).toBeVisible();
const formatRect = await formatBar.boundingBox();
const selectionRect = await blockSelections.boundingBox();
if (!formatRect) {
throw new Error('formatRect is not found');
}
if (!selectionRect) {
throw new Error('selectionRect is not found');
}
assertAlmostEqual(formatRect.x - selectionRect.x, 147.5, 10);
assertAlmostEqual(formatRect.y - selectionRect.y, -48, 10);
const boldBtn = formatBar.getByTestId('bold'); const boldBtn = formatBar.getByTestId('bold');
await boldBtn.click(); await boldBtn.click();
const italicBtn = formatBar.getByTestId('italic'); const italicBtn = formatBar.getByTestId('italic');
@@ -603,17 +585,6 @@ test('should format quick bar work in multiple block selection', async ({
const formatBarController = getFormatBar(page); const formatBarController = getFormatBar(page);
await expect(formatBarController.formatBar).toBeVisible(); await expect(formatBarController.formatBar).toBeVisible();
const box = await formatBarController.formatBar.boundingBox();
if (!box) {
throw new Error("formatBar doesn't exist");
}
const rect = await blockSelections.first().boundingBox();
if (!rect) {
throw new Error('rect is not found');
}
assertAlmostEqual(box.x - rect.x, 147.5, 10);
assertAlmostEqual(box.y - rect.y, -48, 10);
await formatBarController.boldBtn.click(); await formatBarController.boldBtn.click();
await formatBarController.italicBtn.click(); await formatBarController.italicBtn.click();
await formatBarController.underlineBtn.click(); await formatBarController.underlineBtn.click();
+1 -1
View File
@@ -606,7 +606,7 @@ test.describe('slash search', () => {
await expect(slashMenu).toBeVisible(); await expect(slashMenu).toBeVisible();
await type(page, 'c'); await type(page, 'c');
await expect(slashItems).toHaveCount(8); await expect(slashItems).toHaveCount(9);
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']);
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Italic']); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Italic']);
await expect(slashItems.nth(2).locator('.text')).toHaveText(['New Doc']); await expect(slashItems.nth(2).locator('.text')).toHaveText(['New Doc']);