@atlaskit/editor-plugin-selection-marker
Version:
Selection marker plugin for @atlaskit/editor-core.
127 lines (123 loc) • 6.47 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
/**
* @jsxRuntime classic
* @jsx jsx
*/
import { TextSelection } from '@atlaskit/editor-prosemirror/state';
import { Decoration } from '@atlaskit/editor-prosemirror/view';
var selectionMarkerHighlightStyles = {
content: "''",
position: 'absolute',
backgroundImage: "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMyIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDMgMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMSAxSDBMMSAxLjg1NzE0VjE4LjE0MzNMMCAxOS4wMDA0SDNMMiAxOC4xNDMzVjEuODU3MTRMMyAxSDJIMVoiIGZpbGw9IiM1NzlERkYiLz4KPHJlY3QgeT0iMTkiIHdpZHRoPSIzIiBoZWlnaHQ9IjEiIGZpbGw9IiM1NzlERkYiLz4KPHJlY3Qgd2lkdGg9IjMiIGhlaWdodD0iMSIgZmlsbD0iIzU3OURGRiIvPgo8L3N2Zz4K')",
top: "var(--ds-space-0, 0px)",
bottom: "var(--ds-space-negative-025, -2px)",
backgroundRepeat: 'no-repeat',
backgroundPositionX: 'center',
backgroundPositionY: 'center',
backgroundSize: 'contain',
aspectRatio: '3/20',
left: '0px',
marginLeft: "var(--ds-space-negative-025, -2px)",
right: '0px',
marginRight: "var(--ds-space-negative-025, -2px)",
pointerEvents: 'none'
};
var selectionMarkerBlockCursorStyles = {
content: "''",
position: 'absolute',
background: "var(--ds-text, #292A2E)",
width: '1px',
display: 'inline-block',
top: "var(--ds-space-0, 0px)",
bottom: "var(--ds-space-negative-025, -2px)",
left: '1px',
marginLeft: "var(--ds-space-negative-025, -2px)",
right: '0px',
marginRight: "var(--ds-space-negative-025, -2px)",
pointerEvents: 'none'
};
// Same as above but defined as an inline element to avoid breaking long words
var selectionMarkerInlineCursorStyles = {
content: "''",
position: 'relative',
pointerEvents: 'none',
borderLeft: "var(--ds-border-width, 1px)".concat(" solid ", "var(--ds-text, #292A2E)"),
marginLeft: '-1px',
left: '0.5px'
};
/**
* Converts a camelCased CSS property name to a hyphenated CSS property name.
*
* @param property - CamelCased CSS property name.
* @returns Hyphenated CSS property name.
*/
function hyphenate(property) {
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
return property.replace(/[A-Z]/g, function (match) {
return "-".concat(match.toLowerCase());
}).replace(/^ms/, '-ms');
}
var Widget = function Widget(_ref) {
var type = _ref.type,
isHighlight = _ref.isHighlight,
isInWord = _ref.isInWord;
var span = document.createElement('span');
var selectionMarkerCursorStyles = isInWord ? selectionMarkerInlineCursorStyles : selectionMarkerBlockCursorStyles;
var styles = isHighlight ? selectionMarkerHighlightStyles : selectionMarkerCursorStyles;
for (var _i = 0, _Object$entries = Object.entries(styles); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
rule = _Object$entries$_i[0],
value = _Object$entries$_i[1];
span.style.setProperty(hyphenate(rule), value);
}
span.setAttribute('contentEditable', 'false');
span.dataset.testid = "selection-marker-".concat(type, "-cursor");
return span;
};
var toDOM = function toDOM(type, isHighlight, isInWord) {
var element = document.createElement('span');
element.contentEditable = 'false';
element.setAttribute('style', "position: relative;");
element.appendChild(Widget({
type: type,
isHighlight: isHighlight,
isInWord: isInWord
}));
return element;
};
var containsText = function containsText(resolvedPos) {
var nodeBefore = resolvedPos.nodeBefore,
nodeAfter = resolvedPos.nodeAfter;
return (nodeBefore === null || nodeBefore === void 0 ? void 0 : nodeBefore.isInline) || (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.isInline);
};
export var createWidgetDecoration = function createWidgetDecoration(resolvedPos, type, selection, isHighlight) {
var _nodeBefore$textConte, _nodeAfter$textConten;
// We don't want the cursor to show if it's not text selection
// ie. if it's on media selection
if (!(selection instanceof TextSelection) || containsText(resolvedPos) === false || !selection.empty) {
return [];
}
// We're inside a word if the parent, before, and after nodes are all text nodes
// and the before/after nodes are appended/prepended with non-whitespace characters
// Also if we're making a selection and not just a cursor, this isn't relevant
var nodeBefore = resolvedPos.nodeBefore,
nodeAfter = resolvedPos.nodeAfter,
parent = resolvedPos.parent;
// Check if the parent is a text node and the before/after nodes are also text nodes
var areTextNodes = parent.isTextblock && (nodeBefore === null || nodeBefore === void 0 ? void 0 : nodeBefore.isText) && (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.isText);
var lastCharacterOfBeforeNode = nodeBefore === null || nodeBefore === void 0 || (_nodeBefore$textConte = nodeBefore.textContent) === null || _nodeBefore$textConte === void 0 ? void 0 : _nodeBefore$textConte.slice(-1);
var firstCharacterOfAfterNode = nodeAfter === null || nodeAfter === void 0 || (_nodeAfter$textConten = nodeAfter.textContent) === null || _nodeAfter$textConten === void 0 ? void 0 : _nodeAfter$textConten.slice(0, 1);
var areAdjacentCharactersNonWhitespace =
// @ts-ignore - TS1501 Older versions of TypeScript don't play nice with the u flag. With the current AFM TypeScript version, this *should* be fine, but the pipeline type check fails, hence why a ts-ignore is needed (over a ts-expect-error)
/(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/.test(lastCharacterOfBeforeNode || '') && /(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/.test(firstCharacterOfAfterNode || '');
var isInWord = Boolean(areTextNodes && areAdjacentCharactersNonWhitespace);
return [Decoration.widget(resolvedPos.pos, toDOM(type, isHighlight, isInWord), {
side: -1,
key: "".concat(type, "WidgetDecoration"),
stopEvent: function stopEvent() {
return true;
},
ignoreSelection: true
})];
};