UNPKG

@atlaskit/editor-common

Version:

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

201 lines (196 loc) 7.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.applyMarkOnRange = void 0; exports.filterChildrenBetween = filterChildrenBetween; exports.transformSmartCharsMentionsAndEmojis = exports.toggleMark = void 0; var _state = require("@atlaskit/editor-prosemirror/state"); var _cellSelection = require("@atlaskit/editor-tables/cell-selection"); var _platformFeatureFlags = require("@atlaskit/platform-feature-flags"); // eslint-disable-next-line no-duplicate-imports var SMART_TO_ASCII = { '…': '...', '→': '->', '←': '<-', '–': '--', '“': '"', '”': '"', '‘': "'", '’': "'" }; var FIND_SMART_CHAR = new RegExp("[".concat(Object.keys(SMART_TO_ASCII).join(''), "]"), 'g'); var isNodeTextBlock = function isNodeTextBlock(schema) { var _schema$nodes = schema.nodes, mention = _schema$nodes.mention, text = _schema$nodes.text, emoji = _schema$nodes.emoji; return function (node, _, parent) { if (node.type === mention || node.type === emoji || node.type === text) { return parent === null || parent === void 0 ? void 0 : parent.isTextblock; } return; }; }; var replaceSmartCharsToAscii = function replaceSmartCharsToAscii(position, textContent, tr) { var schema = tr.doc.type.schema; var match; while (match = FIND_SMART_CHAR.exec(textContent)) { var _match = match, smartChar = _match[0], offset = _match.index; var replacePos = tr.mapping.map(position + offset); var replacementText = schema.text(SMART_TO_ASCII[smartChar]); tr.replaceWith(replacePos, replacePos + smartChar.length, replacementText); } }; var replaceMentionOrEmojiForTextContent = function replaceMentionOrEmojiForTextContent(position, nodeSize, textContent, tr) { var currentPos = tr.mapping.map(position); var schema = tr.doc.type.schema; tr.replaceWith(currentPos, currentPos + nodeSize, schema.text(textContent)); }; 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; } var transformSmartCharsMentionsAndEmojis = exports.transformSmartCharsMentionsAndEmojis = function transformSmartCharsMentionsAndEmojis(from, to, tr) { var schema = tr.doc.type.schema; var _schema$nodes2 = schema.nodes, mention = _schema$nodes2.mention, text = _schema$nodes2.text, emoji = _schema$nodes2.emoji; // Traverse through all the nodes within the range and replace them with their plaintext counterpart var children = filterChildrenBetween(tr.doc, from, to, isNodeTextBlock(schema)); children.forEach(function (_ref) { var node = _ref.node, pos = _ref.pos; if (node.type === mention || node.type === emoji) { // Convert gracefully when no text found, ProseMirror will blow up if you try to create a node with an empty string or undefined var replacementText = node.attrs.text; if (typeof replacementText === 'undefined' || replacementText === '') { replacementText = "".concat(node.type.name, " text missing"); } replaceMentionOrEmojiForTextContent(pos, node.nodeSize, replacementText, tr); } else if (node.type === text && node.text) { var replacePosition = pos > from ? pos : from; var textToReplace = pos > from ? node.text : node.text.substr(from - pos); replaceSmartCharsToAscii(replacePosition, textToReplace, tr); } }); }; var applyMarkOnRange = exports.applyMarkOnRange = function applyMarkOnRange(from, to, removeMark, mark, tr) { var schema = tr.doc.type.schema; var code = schema.marks.code; var inlineCard = schema.nodes.inlineCard; if (mark.type === code) { transformSmartCharsMentionsAndEmojis(from, to, tr); } tr.doc.nodesBetween(tr.mapping.map(from), tr.mapping.map(to), function (node, pos) { if ((0, _platformFeatureFlags.getBooleanFF)('platform.editor.allow-inline-comments-for-inline-nodes')) { if (!node.isText && node.type !== inlineCard) { 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; }; var entireSelectionContainsMark = function entireSelectionContainsMark(mark, doc, fromPos, toPos) { 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.isInSet(node.marks)); } }); return onlyContainsMark; }; var toggleMarkInRange = function toggleMarkInRange(mark) { return function (_ref2) { var tr = _ref2.tr; if (tr.selection instanceof _cellSelection.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 _removeMark = entireSelectionContainsMark(mark, tr.doc, $from.pos, $to.pos); applyMarkOnRange($from.pos, $to.pos, _removeMark, 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 */ var toggleMark = exports.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 _state.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 }); }; };