UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

219 lines (211 loc) 7.7 kB
import { TextSelection } from '@atlaskit/editor-prosemirror/state'; // eslint-disable-next-line no-duplicate-imports import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { fg } from '@atlaskit/platform-feature-flags'; var SMART_TO_ASCII = { '…': '...', '→': '->', '←': '<-', '–': '--', '“': '"', '”': '"', '‘': "'", '’': "'" }; // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp var FIND_SMART_CHAR = new RegExp("[".concat(Object.keys(SMART_TO_ASCII).join(''), "]"), 'g'); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/max-params export function filterChildrenBetween(doc, from, to, predicate) { var results = []; doc.nodesBetween(from, to, function (node, pos, parent) { if (predicate(node, pos, parent)) { results.push({ node: node, pos: pos }); } }); return results; } export function transformNonTextNodesToText(from, to, tr) { var doc = tr.doc; var schema = doc.type.schema; var _schema$nodes = schema.nodes, mentionNodeType = _schema$nodes.mention, textNodeType = _schema$nodes.text, emojiNodeType = _schema$nodes.emoji, inlineCardNodeType = _schema$nodes.inlineCard; var nodesToChange = []; doc.nodesBetween(from, to, function (node, pos, parent) { if ([mentionNodeType, textNodeType, emojiNodeType, inlineCardNodeType].includes(node.type)) { nodesToChange.push({ node: node, pos: pos }); } }); nodesToChange.forEach(function (_ref) { var node = _ref.node, pos = _ref.pos; if (node.type !== textNodeType) { var newText = node.attrs.url || // url for inlineCard node.attrs.text || "".concat(node.type.name, " text missing"); // fallback for missing text var currentPos = tr.mapping.map(pos); tr.replaceWith(currentPos, currentPos + node.nodeSize, schema.text(newText, node.marks)); } else if (node.text) { // Find a valid start and end position because the text may be partially selected. var startPositionInSelection = Math.max(pos, from); var endPositionInSelection = Math.min(pos + node.nodeSize, to); var textForReplacing = doc.textBetween(startPositionInSelection, endPositionInSelection); var _newText = textForReplacing.replace(FIND_SMART_CHAR, function (match) { var _SMART_TO_ASCII$match; return (_SMART_TO_ASCII$match = SMART_TO_ASCII[match]) !== null && _SMART_TO_ASCII$match !== void 0 ? _SMART_TO_ASCII$match : match; }); var currentStartPos = tr.mapping.map(startPositionInSelection); var currentEndPos = tr.mapping.map(endPositionInSelection); tr.replaceWith(currentStartPos, currentEndPos, schema.text(_newText, node.marks)); } }); } export var applyMarkOnRange = function applyMarkOnRange(from, to, removeMark, mark, tr // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/max-params ) { var schema = tr.doc.type.schema; var code = schema.marks.code; if (mark.type === code) { transformNonTextNodesToText(from, to, tr); } tr.doc.nodesBetween(tr.mapping.map(from), tr.mapping.map(to), function (node, pos) { if (fg('editor_inline_comments_on_inline_nodes')) { if (!node.isText) { var isAllowedInlineNode = ['emoji', 'status', 'date', 'mention', 'inlineCard'].includes(node.type.name); if (!isAllowedInlineNode) { return true; } } } else { if (!node.isText) { return true; } } // This is an issue when the user selects some text. // We need to check if the current node position is less than the range selection from. // If it’s true, that means we should apply the mark using the range selection, // not the current node position. var nodeBetweenFrom = Math.max(pos, tr.mapping.map(from)); var nodeBetweenTo = Math.min(pos + node.nodeSize, tr.mapping.map(to)); if (removeMark) { tr.removeMark(nodeBetweenFrom, nodeBetweenTo, mark); } else { tr.addMark(nodeBetweenFrom, nodeBetweenTo, mark); } return true; }); return tr; }; export var entireSelectionContainsMark = function entireSelectionContainsMark(mark, doc, fromPos, toPos // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/max-params ) { var onlyContainsMark = true; doc.nodesBetween(fromPos, toPos, function (node) { // Skip recursion once we've found text which doesn't include the mark if (!onlyContainsMark) { return false; } if (node.isText) { onlyContainsMark && (onlyContainsMark = !!(mark !== null && mark !== void 0 && mark.isInSet(node.marks))); } }); return onlyContainsMark; }; var toggleMarkInRange = function toggleMarkInRange(mark) { return function (_ref2) { var tr = _ref2.tr; if (tr.selection instanceof CellSelection) { var _removeMark = true; var cells = []; tr.selection.forEachCell(function (cell, cellPos) { cells.push({ node: cell, pos: cellPos }); var from = cellPos; var to = cellPos + cell.nodeSize; _removeMark && (_removeMark = entireSelectionContainsMark(mark, tr.doc, from, to)); }); for (var i = cells.length - 1; i >= 0; i--) { var cell = cells[i]; var from = cell.pos; var to = from + cell.node.nodeSize; applyMarkOnRange(from, to, _removeMark, mark, tr); } } else { var _tr$selection = tr.selection, $from = _tr$selection.$from, $to = _tr$selection.$to; // We decide to remove the mark only if the entire selection contains the mark // Examples with *bold* text // Scenario 1: Selection contains both bold and non-bold text -> bold entire selection // Scenario 2: Selection contains only bold text -> un-bold entire selection // Scenario 3: Selection contains no bold text -> bold entire selection var _removeMark2 = entireSelectionContainsMark(mark, tr.doc, $from.pos, $to.pos); applyMarkOnRange($from.pos, $to.pos, _removeMark2, mark, tr); } if (tr.docChanged) { return tr; } return null; }; }; /** * A custom version of the ProseMirror toggleMark, where we only toggle marks * on text nodes in the selection rather than all inline nodes. * @param markType * @param attrs */ export var toggleMark = function toggleMark(markType, attrs) { return function (_ref3) { var tr = _ref3.tr; var mark = markType.create(attrs); // For cursor selections we can use the default behaviour. if (tr.selection instanceof TextSelection && tr.selection.$cursor) { if (mark.isInSet(tr.storedMarks || tr.selection.$cursor.marks())) { tr.removeStoredMark(mark); } else { tr.addStoredMark(mark); } return tr; } return toggleMarkInRange(mark)({ tr: tr }); }; }; /** * A wrapper around ProseMirror removeMark and removeStoredMark, which handles mark removal in text, CellSelections and cursor stored marks. */ export var removeMark = function removeMark(mark) { return function (_ref4) { var tr = _ref4.tr; var selection = tr.selection; if (selection instanceof CellSelection) { selection.forEachCell(function (cell, cellPos) { var from = cellPos; var to = cellPos + cell.nodeSize; tr.removeMark(from, to, mark); }); } else if (selection instanceof TextSelection && selection.$cursor) { tr.removeStoredMark(mark); } else { var from = selection.from, to = selection.to; tr.removeMark(from, to, mark); } return tr; }; };