UNPKG

@atlaskit/editor-plugin-text-formatting

Version:

Text-formatting plugin for @atlaskit/editor-core

111 lines (108 loc) 3.88 kB
import { entireSelectionContainsMark } from '@atlaskit/editor-common/mark'; import { TextSelection } from '@atlaskit/editor-prosemirror/state'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { fg } from '@atlaskit/platform-feature-flags'; /** * 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 const nextToggleMark = (markType, api, attrs) => ({ tr }) => { const 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 nextToggleMarkInRange(mark, api)({ tr }); }; const nextToggleMarkInRange = (mark, api) => ({ tr }) => { if (tr.selection instanceof CellSelection) { let removeMark = true; const cells = []; tr.selection.forEachCell((cell, cellPos) => { cells.push({ node: cell, pos: cellPos }); const from = cellPos; const to = cellPos + cell.nodeSize; removeMark && (removeMark = entireSelectionContainsMark(mark, tr.doc, from, to)); }); for (let i = cells.length - 1; i >= 0; i--) { const cell = cells[i]; const from = cell.pos; const to = from + cell.node.nodeSize; nextApplyMarkOnRange(from, to, removeMark, mark, tr, api); } } else { const { $from, $to } = tr.selection; // 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 const removeMark = entireSelectionContainsMark(mark, tr.doc, $from.pos, $to.pos); nextApplyMarkOnRange($from.pos, $to.pos, removeMark, mark, tr, api); } if (tr.docChanged) { return tr; } return null; }; export const nextApplyMarkOnRange = (from, to, removeMark, mark, tr, api) => { const { schema } = tr.doc.type; const { code } = schema.marks; if (mark.type === code) { var _api$base, _api$base$actions; api === null || api === void 0 ? void 0 : (_api$base = api.base) === null || _api$base === void 0 ? void 0 : (_api$base$actions = _api$base.actions) === null || _api$base$actions === void 0 ? void 0 : _api$base$actions.resolveMarks(from, to, tr); } /** * We should refactor this so text formatting doesn't reference plugins it doesn't know about. */ tr.doc.nodesBetween(tr.mapping.map(from), tr.mapping.map(to), (node, pos) => { if (fg('editor_inline_comments_on_inline_nodes')) { if (!node.isText) { const 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. const nodeBetweenFrom = Math.max(pos, tr.mapping.map(from)); const 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; };