@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
159 lines (155 loc) • 6.2 kB
JavaScript
import { createElement } from 'react';
import { bind } from 'bind-event-listener';
import ReactDOM from 'react-dom';
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
import uuid from 'uuid';
import { Decoration } from '@atlaskit/editor-prosemirror/view';
import { fg } from '@atlaskit/platform-feature-flags';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { DragHandle, DragHandleWithVisibility } from '../ui/drag-handle';
import { TYPE_HANDLE_DEC, TYPE_NODE_DEC, unmountDecorations } from './decorations-common';
import { getActiveBlockMarks, getMatchingBlockMarks } from './utils/marks';
export const emptyParagraphNodeDecorations = () => {
const anchorName = `--node-anchor-paragraph-0`;
const style = `anchor-name: ${anchorName}; margin-top: 0px;`;
return Decoration.node(0, 2, {
style,
['data-drag-handler-anchor-name']: anchorName
}, {
type: TYPE_NODE_DEC
});
};
export const findHandleDec = (decorations, from, to) => {
return decorations.find(from, to, spec => spec.type === TYPE_HANDLE_DEC);
};
export const dragHandleDecoration = ({
api,
formatMessage,
pos,
anchorName,
nodeType,
nodeViewPortalProviderAPI,
handleOptions,
anchorRectCache,
editorState
}) => {
unmountDecorations(nodeViewPortalProviderAPI, 'data-blocks-drag-handle-container', 'data-blocks-drag-handle-key');
let unbind;
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
const key = uuid();
const widgetSpec = editorExperiment('platform_editor_breakout_resizing', true) ? {
side: -1,
type: TYPE_HANDLE_DEC,
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
/**
* sigh - `marks` influences the position that the widget is drawn (as described on the `side` property).
* Exclude 'breakout' on purpose, so the widgets render at the top of the document to avoid z-index issues
* Other block marks must be added, otherwise PM will split the DOM elements causing mutations and re-draws
*/
marks: expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? getMatchingBlockMarks(editorState, pos, [editorState.schema.marks.alignment, editorState.schema.marks.fontSize]) : expValEquals('platform_editor_clean_up_widget_mark_logic', 'isEnabled', true) ? [] : getActiveBlockMarks(editorState, pos),
destroy: node => {
unbind && unbind();
}
} : {
side: -1,
type: TYPE_HANDLE_DEC,
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
marks: expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? getMatchingBlockMarks(editorState, pos, [editorState.schema.marks.alignment, editorState.schema.marks.fontSize]) : expValEquals('platform_editor_clean_up_widget_mark_logic', 'isEnabled', true) ? [] : expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? getActiveBlockMarks(editorState, pos) : undefined,
destroy: node => {
unbind && unbind();
}
};
return Decoration.widget(pos, (view, getPosUnsafe) => {
const element = document.createElement('span');
// inline decoration causes focus issues when refocusing Editor into first line
element.style.display = 'block';
if (fg('confluence_remix_button_right_side_block_fg')) {
element.setAttribute('data-blocks-decorator-widget', 'true');
}
element.setAttribute('data-testid', 'block-ctrl-decorator-widget');
element.setAttribute('data-blocks-drag-handle-container', 'true');
element.setAttribute('data-blocks-drag-handle-key', key);
let isTopLevelNode = true;
const getPos = () => {
try {
return getPosUnsafe();
} catch {
// Ignore errors from getPosUnsafe
return undefined;
}
};
const newPos = getPos();
if (typeof newPos === 'number') {
const $pos = view.state.doc.resolve(newPos);
isTopLevelNode = ($pos === null || $pos === void 0 ? void 0 : $pos.parent.type.name) === 'doc';
}
/*
* We disable mouseover event to fix flickering issue on hover
* However, the tooltip for nested drag handle is no long working.
*/
if (newPos === undefined || !isTopLevelNode) {
if (fg('platform_editor_fix_widget_destroy')) {
element.onmouseover = e => {
e.stopPropagation();
};
} else {
unbind = bind(element, {
type: 'mouseover',
listener: e => {
e.stopPropagation();
}
});
}
}
// There are times when global clear: "both" styles are applied to this decoration causing jumpiness
// due to margins applied to other nodes eg. Headings
element.style.clear = 'unset';
// temporarily re-instating ReactDOM.render to fix drag handle focus issue, fix to
// follow via ED-26546
// previous under platform_editor_react18_plugin_portalprovider
// nodeViewPortalProviderAPI.render(
// () =>
// createElement(DragHandle, {
// view,
// api,
// formatMessage,
// getPos,
// anchorName,
// nodeType,
// handleOptions,
// isTopLevelNode,
// }),
// element,
// key,
// );
if (editorExperiment('platform_editor_controls', 'variant1')) {
ReactDOM.render( /*#__PURE__*/createElement(DragHandleWithVisibility, {
view,
api,
formatMessage,
getPos,
anchorName,
nodeType,
handleOptions,
isTopLevelNode,
anchorRectCache
}), element);
} else {
ReactDOM.render( /*#__PURE__*/createElement(DragHandle, {
view,
api,
formatMessage,
getPos,
anchorName,
nodeType,
handleOptions,
isTopLevelNode,
anchorRectCache
}), element);
}
return element;
}, widgetSpec);
};