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[] = [ { type: 'primary', items: [ { type: 'change-lang', generate: ({ blockComponent, setActive }) => { const state = { active: false }; return { action: noop, render: () => html` { state.active = active; if (!active) { await sleep(1000); if (state.active) return; } setActive(active); }} > `, }; }, }, { type: 'preview', generate: ({ blockComponent }) => { return { action: noop, render: () => html` `, }; }, }, { type: 'copy-code', label: 'Copy code', icon: CopyIcon, generate: ({ blockComponent }) => { return { action: () => { blockComponent.copyCode(); }, render: item => html` { e.stopPropagation(); item.action(); }} > ${item.icon} `, }; }, }, { 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` { e.stopPropagation(); item.action(); }} > ${icon} `; }, }; }, }, { type: 'caption', label: 'Caption', icon: CaptionIcon, when: ({ doc }) => !doc.readonly, generate: ({ blockComponent }) => { return { action: () => { blockComponent.captionEditor?.show(); }, render: item => html` { e.stopPropagation(); item.action(); }} > ${item.icon} `, }; }, }, { 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` { e.stopPropagation(); item.action(); }} > ${item.icon} `, }; }, }, ], }, ]; export const toggleGroup: MenuItemGroup = { 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` { const currentWrap = blockComponent.model.props.wrap; blockComponent.setWrap(!currentWrap); }} aria-label=${label} > ${icon} ${label} `; }, }; }, }, { type: 'line-number', when: ({ std }) => std.getOptional(CodeBlockConfigExtension.identifier)?.showLineNumbers ?? true, generate: ({ blockComponent }) => { return { action: () => {}, render: () => { const lineNumber = blockComponent.model.props.lineNumber ?? true; const label = lineNumber ? 'Cancel line number' : 'Line number'; return html` { const currentLineNumber = blockComponent.model.props.lineNumber ?? true; blockComponent.store.updateBlock(blockComponent.model, { lineNumber: !currentLineNumber, }); }} aria-label=${label} > ${NumberedListIcon()} ${label} `; }, }; }, }, ], }; // Clipboard Group export const clipboardGroup: MenuItemGroup = { 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 = { 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[] = [ toggleGroup, clipboardGroup, deleteGroup, ];