UNPKG

@atlaskit/editor-plugin-code-bidi-warning

Version:

Code bidi warning plugin for @atlaskit/editor-core.

118 lines (115 loc) 4.2 kB
import React from 'react'; // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead import uuid from 'uuid/v4'; import CodeBidiWarning from '@atlaskit/code/bidi-warning'; import codeBidiWarningDecorator from '@atlaskit/code/bidi-warning-decorator'; import { pluginFactory, stepHasSlice } from '@atlaskit/editor-common/utils'; import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view'; import { codeBidiWarningPluginKey } from './plugin-key'; import reducer from './reducer'; export const pluginFactoryCreator = nodeViewPortalProviderAPI => pluginFactory(codeBidiWarningPluginKey, reducer, { onDocChanged: (tr, pluginState) => { if (!tr.steps.find(stepHasSlice)) { return pluginState; } const newBidiWarningsDecorationSet = createBidiWarningsDecorationSetFromDoc({ doc: tr.doc, codeBidiWarningLabel: pluginState.codeBidiWarningLabel, tooltipEnabled: pluginState.tooltipEnabled, nodeViewPortalProviderAPI }); return { ...pluginState, decorationSet: newBidiWarningsDecorationSet }; } }); export function createBidiWarningsDecorationSetFromDoc({ doc, codeBidiWarningLabel, tooltipEnabled, nodeViewPortalProviderAPI }) { const bidiCharactersAndTheirPositions = []; doc.descendants((node, pos) => { const isTextWithCodeMark = node.type.name === 'text' && node.marks && node.marks.some(mark => mark.type.name === 'code'); if (isTextWithCodeMark) { codeBidiWarningDecorator(node.textContent, ({ bidiCharacter, index }) => { bidiCharactersAndTheirPositions.push({ // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion position: pos + index, bidiCharacter }); }); return false; } const isCodeBlock = node.type.name === 'codeBlock'; if (isCodeBlock) { codeBidiWarningDecorator(node.textContent, ({ bidiCharacter, index }) => { bidiCharactersAndTheirPositions.push({ // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion position: pos + index + 1, bidiCharacter }); }); } }); // Bidi characters are not expected to commonly appear in code snippets, so recreating the decoration set // for documents rather than reusing existing decorations seems a reasonable performance/complexity tradeoff. if (bidiCharactersAndTheirPositions.length === 0) { return DecorationSet.empty; } const newBidiWarningsDecorationSet = DecorationSet.create(doc, bidiCharactersAndTheirPositions.map(({ position, bidiCharacter }) => { // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead const renderKey = uuid(); return Decoration.widget(position, _el => renderDOM({ bidiCharacter, codeBidiWarningLabel, tooltipEnabled, nodeViewPortalProviderAPI, renderKey }), { destroy: _el => { // removing portalprovider clean up due to a rendering bug // with this plugin under React 18. This matches the previous // React 16 behaviour which never cleaned up rendering. // // This will mean CodeBidi instances are not cleaned up, but // this is expected to be minimal due to the low frequency of // bidi characters in code blocks. // // We will fix this in a follow up ticket to rewrite the plugin // to use pure toDOM -> ED-26540 // nodeViewPortalProviderAPI.remove(renderKey); } }); })); return newBidiWarningsDecorationSet; } function renderDOM({ bidiCharacter, codeBidiWarningLabel, tooltipEnabled, nodeViewPortalProviderAPI, renderKey }) { const element = document.createElement('span'); nodeViewPortalProviderAPI.render(() => /*#__PURE__*/React.createElement(CodeBidiWarning, { bidiCharacter: bidiCharacter, skipChildren: true, label: codeBidiWarningLabel, tooltipEnabled: tooltipEnabled }), element, renderKey); return element; }