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