@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
201 lines (196 loc) • 7.5 kB
JavaScript
;
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
});
};
};