@atlaskit/editor-plugin-code-bidi-warning
Version:
Code bidi warning plugin for @atlaskit/editor-core.
118 lines (115 loc) • 4.2 kB
JavaScript
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;
}