UNPKG

@atlaskit/editor-plugin-code-block-advanced

Version:

CodeBlockAdvanced plugin for @atlaskit/editor-core

180 lines (162 loc) 5.36 kB
import { codeBlock, codeBlockWithExtendedAttributes, codeBlockWithLocalId, } from '@atlaskit/adf-schema'; import { areCodeBlockLineNumbersHidden } from '@atlaskit/editor-common/code-block'; import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view'; import { CodeBlockSharedCssClassName } from '@atlaskit/editor-common/styles'; import type { NodeSpec, DOMOutputSpec, Node } from '@atlaskit/editor-prosemirror/model'; import { fg } from '@atlaskit/platform-feature-flags'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { token } from '@atlaskit/tokens'; interface Config { allowCodeFolding: boolean; } const codeBlockClassNames = { container: CodeBlockSharedCssClassName.CODEBLOCK_CONTAINER, start: CodeBlockSharedCssClassName.CODEBLOCK_START, end: CodeBlockSharedCssClassName.CODEBLOCK_END, contentWrapper: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT_WRAPPER, contentWrapped: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT_WRAPPED, content: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT, }; const MATCH_NEWLINES = new RegExp('\n', 'gu'); const getFontSize = () => expValEquals('confluence_compact_text_format', 'isEnabled', true) || (expValEquals('cc_editor_ai_content_mode', 'variant', 'test') && fg('platform_editor_content_mode_button_mvp')) ? '0.875em' : '0.875rem'; const getGutterBaseStyle = () => ({ backgroundColor: token('color.background.neutral'), position: 'relative', flexShrink: 0, // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography fontSize: getFontSize(), boxSizing: 'content-box', }); const getGutterPadding = (allowCodeFolding: boolean) => allowCodeFolding ? `${token('space.100')} ${token('space.250')} ${token('space.100')} ${token('space.075')}` : token('space.100'); const getGuttersWithLineNumbers = (content: string, config: Config): DOMOutputSpec => [ 'div', { // Based on packages/editor/editor-common/src/styles/shared/code-block.ts // But we can't reuse that class as it adds a ::before that intefers with this approach style: convertToInlineCss({ ...getGutterBaseStyle(), width: 'var(--lineNumberGutterWidth, 2rem)', /* top and bottom | left and right */ padding: getGutterPadding(config.allowCodeFolding), }), contenteditable: 'false', }, [ 'div', { class: 'code-block-gutter-pseudo-element', style: convertToInlineCss({ textAlign: 'right', color: token('color.text.subtlest'), fontFamily: token('font.family.code'), whiteSpace: 'pre-wrap', }), 'data-label': content, }, ], ]; const getFoldOnlyGutter = (): DOMOutputSpec => [ 'div', { style: convertToInlineCss({ ...getGutterBaseStyle(), padding: `${token('space.100')} ${token('space.150')} ${token('space.100')} ${token('space.100')}`, }), contenteditable: 'false', }, ]; const getGutters = (content: string, config: Config, hideLineNumbers: boolean): DOMOutputSpec[] => { if (!hideLineNumbers) { return [getGuttersWithLineNumbers(content, config)]; } if (config.allowCodeFolding) { return [getFoldOnlyGutter()]; } return []; }; // Based on: `packages/editor/editor-plugin-code-block/src/nodeviews/code-block.ts` const toDOM = (node: Node, formattedAriaLabel: string, config: Config): DOMOutputSpec => { let totalLineCount = 1; node.forEach((node) => { const text = node.text; if (text) { totalLineCount += (node.text.match(MATCH_NEWLINES) || []).length; } }); const hideLineNumbers = areCodeBlockLineNumbersHidden(node); const maxDigits = totalLineCount.toString().length; const content = node.textContent .split('\n') .map((_, i) => i + 1) .join('\n'); const gutters = getGutters(content, config, hideLineNumbers); const isCodeBlockWrapped = expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && node.attrs.wrap === true; const className = [ codeBlockClassNames.container, isCodeBlockWrapped ? codeBlockClassNames.contentWrapped : undefined, ] .filter(Boolean) .join(' '); return [ 'pre', { class: className, style: `--lineNumberGutterWidth:${maxDigits}ch;`, 'data-language': node.attrs.language || '', ...(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && { 'data-wrap': node.attrs.wrap ? 'true' : 'false', ...(hideLineNumbers && { 'data-hide-line-numbers': 'true' }), }), }, ['div', { class: codeBlockClassNames.start, contenteditable: 'false' }], [ 'div', { class: codeBlockClassNames.contentWrapper, }, ...gutters, [ 'div', { class: codeBlockClassNames.content, }, [ 'code', { 'data-language': node.attrs.language || '', spellcheck: 'false', 'data-testid': 'code-block--code', 'aria-label': formattedAriaLabel, ...(fg('platform_editor_adf_with_localid') && { 'data-local-id': node.attrs.localId }), }, 0, ], ], ], ['div', { class: codeBlockClassNames.end, contenteditable: 'false' }], ]; }; export const codeBlockNodeWithFixedToDOM = (config: Config): NodeSpec => { return { ...(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? codeBlockWithExtendedAttributes : fg('platform_editor_adf_with_localid') ? codeBlockWithLocalId : codeBlock), toDOM: (node) => toDOM(node, '', config), }; };