Some checks failed
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 4s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Analyze (javascript, affine) (push) Failing after 3s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
320 lines
9.3 KiB
TypeScript
320 lines
9.3 KiB
TypeScript
import {
|
|
CancelWrapIcon,
|
|
CaptionIcon,
|
|
CollapseCodeIcon,
|
|
CopyIcon,
|
|
DeleteIcon,
|
|
DuplicateIcon,
|
|
ExpandCodeIcon,
|
|
WrapIcon,
|
|
} from '@blocksuite/affine-components/icons';
|
|
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
|
|
import { CommentProviderIdentifier } from '@blocksuite/affine-shared/services';
|
|
import { isInsidePageEditor } from '@blocksuite/affine-shared/utils';
|
|
import { noop, sleep } from '@blocksuite/global/utils';
|
|
import { CommentIcon, NumberedListIcon } from '@blocksuite/icons/lit';
|
|
import { BlockSelection } from '@blocksuite/std';
|
|
import { html } from 'lit';
|
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
|
|
import { CodeBlockConfigExtension } from '../code-block-config.js';
|
|
import type { CodeBlockToolbarContext } from './context.js';
|
|
import { duplicateCodeBlock } from './utils.js';
|
|
|
|
export const PRIMARY_GROUPS: MenuItemGroup<CodeBlockToolbarContext>[] = [
|
|
{
|
|
type: 'primary',
|
|
items: [
|
|
{
|
|
type: 'change-lang',
|
|
generate: ({ blockComponent, setActive }) => {
|
|
const state = { active: false };
|
|
return {
|
|
action: noop,
|
|
render: () =>
|
|
html`<language-list-button
|
|
.blockComponent=${blockComponent}
|
|
.onActiveStatusChange=${async (active: boolean) => {
|
|
state.active = active;
|
|
if (!active) {
|
|
await sleep(1000);
|
|
if (state.active) return;
|
|
}
|
|
setActive(active);
|
|
}}
|
|
>
|
|
</language-list-button>`,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
type: 'preview',
|
|
generate: ({ blockComponent }) => {
|
|
return {
|
|
action: noop,
|
|
render: () => html`
|
|
<preview-button .blockComponent=${blockComponent}>
|
|
</preview-button>
|
|
`,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
type: 'copy-code',
|
|
label: 'Copy code',
|
|
icon: CopyIcon,
|
|
generate: ({ blockComponent }) => {
|
|
return {
|
|
action: () => {
|
|
blockComponent.copyCode();
|
|
},
|
|
render: item => html`
|
|
<editor-icon-button
|
|
class="code-toolbar-button copy-code"
|
|
aria-label=${ifDefined(item.label)}
|
|
.tooltip=${item.label}
|
|
.tooltipOffset=${4}
|
|
.iconSize=${'16px'}
|
|
.iconContainerPadding=${4}
|
|
@click=${(e: MouseEvent) => {
|
|
e.stopPropagation();
|
|
item.action();
|
|
}}
|
|
>
|
|
${item.icon}
|
|
</editor-icon-button>
|
|
`,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
type: 'collapse',
|
|
when: ({ doc }) => !doc.readonly,
|
|
generate: ({ blockComponent }) => {
|
|
return {
|
|
action: () => {
|
|
blockComponent.setCollapsed(!blockComponent.collapsed$.value);
|
|
},
|
|
render: item => {
|
|
const collapsed = blockComponent.collapsed$.value;
|
|
const icon = collapsed ? ExpandCodeIcon : CollapseCodeIcon;
|
|
const label = collapsed ? 'Expand code' : 'Collapse code';
|
|
return html`
|
|
<editor-icon-button
|
|
class="code-toolbar-button collapse"
|
|
aria-label=${label}
|
|
.tooltip=${label}
|
|
.tooltipOffset=${4}
|
|
.iconSize=${'16px'}
|
|
.iconContainerPadding=${4}
|
|
@click=${(e: MouseEvent) => {
|
|
e.stopPropagation();
|
|
item.action();
|
|
}}
|
|
>
|
|
${icon}
|
|
</editor-icon-button>
|
|
`;
|
|
},
|
|
};
|
|
},
|
|
},
|
|
{
|
|
type: 'caption',
|
|
label: 'Caption',
|
|
icon: CaptionIcon,
|
|
when: ({ doc }) => !doc.readonly,
|
|
generate: ({ blockComponent }) => {
|
|
return {
|
|
action: () => {
|
|
blockComponent.captionEditor?.show();
|
|
},
|
|
render: item => html`
|
|
<editor-icon-button
|
|
class="code-toolbar-button caption"
|
|
aria-label=${ifDefined(item.label)}
|
|
.tooltip=${item.label}
|
|
.tooltipOffset=${4}
|
|
.iconSize=${'16px'}
|
|
.iconContainerPadding=${4}
|
|
@click=${(e: MouseEvent) => {
|
|
e.stopPropagation();
|
|
item.action();
|
|
}}
|
|
>
|
|
${item.icon}
|
|
</editor-icon-button>
|
|
`,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
type: 'comment',
|
|
label: 'Comment',
|
|
tooltip: 'Comment',
|
|
icon: CommentIcon({
|
|
width: '20',
|
|
height: '20',
|
|
}),
|
|
when: ({ std }) => !!std.getOptional(CommentProviderIdentifier),
|
|
generate: ({ blockComponent }) => {
|
|
return {
|
|
action: () => {
|
|
const commentProvider = blockComponent.std.getOptional(
|
|
CommentProviderIdentifier
|
|
);
|
|
if (!commentProvider) return;
|
|
|
|
commentProvider.addComment([
|
|
new BlockSelection({
|
|
blockId: blockComponent.model.id,
|
|
}),
|
|
]);
|
|
},
|
|
render: item =>
|
|
html`<editor-icon-button
|
|
class="code-toolbar-button comment"
|
|
aria-label=${ifDefined(item.label)}
|
|
.tooltip=${item.label}
|
|
.tooltipOffset=${4}
|
|
.iconSize=${'16px'}
|
|
.iconContainerPadding=${4}
|
|
@click=${(e: MouseEvent) => {
|
|
e.stopPropagation();
|
|
item.action();
|
|
}}
|
|
>
|
|
${item.icon}
|
|
</editor-icon-button>`,
|
|
};
|
|
},
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
|
type: 'toggle',
|
|
items: [
|
|
{
|
|
type: 'wrap',
|
|
generate: ({ blockComponent }) => {
|
|
return {
|
|
action: () => {},
|
|
render: () => {
|
|
const wrapped = blockComponent.model.props.wrap;
|
|
const label = wrapped ? 'Cancel wrap' : 'Wrap';
|
|
const icon = wrapped ? CancelWrapIcon : WrapIcon;
|
|
return html`
|
|
<editor-menu-action
|
|
@click=${() => {
|
|
const currentWrap = blockComponent.model.props.wrap;
|
|
blockComponent.setWrap(!currentWrap);
|
|
}}
|
|
aria-label=${label}
|
|
>
|
|
${icon}
|
|
<span class="label">${label}</span>
|
|
<toggle-switch
|
|
style="margin-left: auto;"
|
|
.on="${wrapped}"
|
|
></toggle-switch>
|
|
</editor-menu-action>
|
|
`;
|
|
},
|
|
};
|
|
},
|
|
},
|
|
{
|
|
type: 'line-number',
|
|
when: ({ std }) =>
|
|
std.getOptional(CodeBlockConfigExtension.identifier)?.showLineNumbers ??
|
|
true,
|
|
generate: ({ blockComponent }) => {
|
|
return {
|
|
action: () => {},
|
|
render: () => {
|
|
const lineNumber = blockComponent.showLineNumbers;
|
|
const label = lineNumber ? 'Cancel line number' : 'Line number';
|
|
return html`
|
|
<editor-menu-action
|
|
@click=${() => {
|
|
blockComponent.store.updateBlock(blockComponent.model, {
|
|
lineNumber: !blockComponent.showLineNumbers,
|
|
});
|
|
}}
|
|
aria-label=${label}
|
|
>
|
|
${NumberedListIcon()}
|
|
<span class="label">${label}</span>
|
|
<toggle-switch
|
|
style="margin-left: auto;"
|
|
.on="${lineNumber}"
|
|
></toggle-switch>
|
|
</editor-menu-action>
|
|
`;
|
|
},
|
|
};
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
// Clipboard Group
|
|
export const clipboardGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
|
type: 'clipboard',
|
|
items: [
|
|
{
|
|
type: 'duplicate',
|
|
label: 'Duplicate',
|
|
icon: DuplicateIcon,
|
|
when: ({ doc }) => !doc.readonly,
|
|
action: ({ host, blockComponent, close }) => {
|
|
const codeId = duplicateCodeBlock(blockComponent.model);
|
|
|
|
host.updateComplete
|
|
.then(() => {
|
|
host.selection.setGroup('note', [
|
|
host.selection.create(BlockSelection, {
|
|
blockId: codeId,
|
|
}),
|
|
]);
|
|
|
|
if (isInsidePageEditor(host)) {
|
|
const duplicateElement = host.view.getBlock(codeId);
|
|
if (duplicateElement) {
|
|
duplicateElement.scrollIntoView({ block: 'nearest' });
|
|
}
|
|
}
|
|
})
|
|
.catch(console.error);
|
|
|
|
close();
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
// Delete Group
|
|
export const deleteGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
|
type: 'delete',
|
|
items: [
|
|
{
|
|
type: 'delete',
|
|
label: 'Delete',
|
|
icon: DeleteIcon,
|
|
when: ({ doc }) => !doc.readonly,
|
|
action: ({ doc, blockComponent, close }) => {
|
|
doc.deleteBlock(blockComponent.model);
|
|
close();
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
export const MORE_GROUPS: MenuItemGroup<CodeBlockToolbarContext>[] = [
|
|
toggleGroup,
|
|
clipboardGroup,
|
|
deleteGroup,
|
|
];
|