UNPKG

@atlaskit/editor-common

Version:

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

287 lines (278 loc) • 11.3 kB
import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; export function addParagraphAtEnd(tr) { const { doc: { type: { schema: { nodes: { paragraph } } } }, doc } = tr; if (doc.lastChild && !(doc.lastChild.type === paragraph && doc.lastChild.content.size === 0)) { if (paragraph) { tr.insert(doc.content.size, paragraph.createAndFill()); } } tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size - 1)); tr.scrollIntoView(); } export function createParagraphAtEnd() { return function (state, dispatch) { const { tr } = state; addParagraphAtEnd(tr); if (dispatch) { dispatch(tr); } return true; }; } // Remove this when cleaning up platform_editor_toolbar_aifc // eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required /** * * @deprecated use changeImageAlignmentTr instead */ export const changeImageAlignment = align => (state, dispatch) => { const { from, to } = state.selection; const tr = state.tr; state.doc.nodesBetween(from, to, (node, pos) => { if (node.type === state.schema.nodes.mediaSingle) { tr.setNodeMarkup(pos, undefined, { ...node.attrs, layout: align === 'center' ? 'center' : `align-${align}` }); } }); if (tr.docChanged && dispatch) { dispatch(tr.scrollIntoView()); return true; } return false; }; export const changeImageAlignmentNext = align => tr => { const { from, to } = tr.selection; const initialDoc = tr.doc; tr.doc.nodesBetween(from, to, (node, pos) => { if (node.type === tr.doc.type.schema.nodes.mediaSingle) { tr.setNodeMarkup(pos, undefined, { ...node.attrs, layout: align === 'center' ? 'center' : `align-${align}` }); } }); // compare tr.doc with initialDoc instead of tr.docChanged // because tr passed in might have been modified prior this function // e.g. see changeAlignmentTr platform/packages/editor/editor-plugin-alignment/src/editor-commands/index.ts:L197 if (!tr.doc.eq(initialDoc)) { tr.scrollIntoView(); return true; } return false; }; // Remove this when cleaning up platform_editor_toolbar_aifc // eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required /** * * @deprecated use createToggleBlockMarkOnRangeNext instead, which does not require passing editorState */ export const createToggleBlockMarkOnRange = (markType, getAttrs, allowedBlocks) => (from, to, tr, state) => { let markApplied = false; state.doc.nodesBetween(from, to, (node, pos, parent) => { var _state$schema, _state$schema$nodes, _state$schema2, _state$schema2$nodes, _state$schema3, _state$schema3$marks; if (!node.type.isBlock) { return false; } const parentAllowsMark = state !== null && state !== void 0 && (_state$schema = state.schema) !== null && _state$schema !== void 0 && (_state$schema$nodes = _state$schema.nodes) !== null && _state$schema$nodes !== void 0 && _state$schema$nodes.blockTaskItem && (parent === null || parent === void 0 ? void 0 : parent.type) === (state === null || state === void 0 ? void 0 : (_state$schema2 = state.schema) === null || _state$schema2 === void 0 ? void 0 : (_state$schema2$nodes = _state$schema2.nodes) === null || _state$schema2$nodes === void 0 ? void 0 : _state$schema2$nodes.blockTaskItem) && markType === (state === null || state === void 0 ? void 0 : (_state$schema3 = state.schema) === null || _state$schema3 === void 0 ? void 0 : (_state$schema3$marks = _state$schema3.marks) === null || _state$schema3$marks === void 0 ? void 0 : _state$schema3$marks.indentation) ? false : parent === null || parent === void 0 ? void 0 : parent.type.allowsMarkType(markType); if ((!allowedBlocks || (Array.isArray(allowedBlocks) ? allowedBlocks.indexOf(node.type) > -1 : allowedBlocks(state.schema, node, parent))) && parentAllowsMark) { const oldMarks = node.marks.filter(mark => mark.type === markType); const prevAttrs = oldMarks.length ? oldMarks[0].attrs : undefined; const newAttrs = getAttrs(prevAttrs, node); if (newAttrs !== undefined) { tr.setNodeMarkup(pos, node.type, node.attrs, node.marks.filter(mark => !markType.excludes(mark.type)).concat(newAttrs === false ? [] : markType.create(newAttrs))); markApplied = true; } } return; }); return markApplied; }; export const createToggleBlockMarkOnRangeNext = (markType, getAttrs, allowedBlocks) => (from, to, tr) => { let markApplied = false; tr.doc.nodesBetween(from, to, (node, pos, parent) => { var _schema$nodes, _schema$nodes2, _schema$marks; if (!node.type.isBlock) { return false; } const schema = tr.doc.type.schema; const parentAllowsMark = schema !== null && schema !== void 0 && (_schema$nodes = schema.nodes) !== null && _schema$nodes !== void 0 && _schema$nodes.blockTaskItem && (parent === null || parent === void 0 ? void 0 : parent.type) === (schema === null || schema === void 0 ? void 0 : (_schema$nodes2 = schema.nodes) === null || _schema$nodes2 === void 0 ? void 0 : _schema$nodes2.blockTaskItem) && markType === (schema === null || schema === void 0 ? void 0 : (_schema$marks = schema.marks) === null || _schema$marks === void 0 ? void 0 : _schema$marks.indentation) ? false : parent === null || parent === void 0 ? void 0 : parent.type.allowsMarkType(markType); if ((!allowedBlocks || (Array.isArray(allowedBlocks) ? allowedBlocks.indexOf(node.type) > -1 : allowedBlocks(tr.doc.type.schema, node, parent))) && parentAllowsMark) { const oldMarks = node.marks.filter(mark => mark.type === markType); const prevAttrs = oldMarks.length ? oldMarks[0].attrs : undefined; const newAttrs = getAttrs(prevAttrs, node); if (newAttrs !== undefined) { tr.setNodeMarkup(pos, node.type, node.attrs, node.marks.filter(mark => !markType.excludes(mark.type)).concat(newAttrs === false ? [] : markType.create(newAttrs))); markApplied = true; } } return; }); return markApplied; }; export const createToggleInlineMarkOnRange = (markType, getAttrs) => (from, to, tr, state) => { let markApplied = false; state.doc.nodesBetween(from, to, (node, pos, parent) => { if (parent !== null && parent !== void 0 && parent.type.allowsMarkType(markType)) { const oldMarks = node.marks.filter(mark => mark.type === markType); const prevAttrs = oldMarks.length ? oldMarks[0].attrs : undefined; const newAttrs = getAttrs(prevAttrs, node); if (newAttrs !== undefined) { tr.setNodeMarkup(pos, node.type, node.attrs, node.marks.filter(mark => !markType.excludes(mark.type)).concat(newAttrs === false ? [] : markType.create(newAttrs))); tr.setSelection(NodeSelection.create(tr.doc, state.selection.from)); markApplied = true; } } return; }); return markApplied; }; // Remove this when cleaning up platform_editor_toolbar_aifc // eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required /** * @deprecated use toggleBlockMarkTr instead * Toggles block mark based on the return type of `getAttrs`. * This is similar to ProseMirror's `getAttrs` from `AttributeSpec` * return `false` to remove the mark. * return `undefined for no-op. * return an `object` to update the mark. */ export const toggleBlockMark = (markType, getAttrs, allowedBlocks) => (state, dispatch) => { let markApplied = false; const tr = state.tr; const toggleBlockMarkOnRange = createToggleBlockMarkOnRange(markType, getAttrs, allowedBlocks); if (state.selection instanceof CellSelection) { state.selection.forEachCell((cell, pos) => { markApplied = toggleBlockMarkOnRange(pos, pos + cell.nodeSize, tr, state); }); } else { const { from, to } = state.selection; markApplied = toggleBlockMarkOnRange(from, to, tr, state); } if (markApplied && tr.docChanged) { if (dispatch) { dispatch(tr.scrollIntoView()); } return true; } return false; }; /** * Toggles block mark based on the return type of `getAttrs`. * @returns true if the mark is applied, false otherwise. */ export const toggleBlockMarkNext = (markType, getAttrs, allowedBlocks) => tr => { let markApplied = false; const toggleBlockMarkOnRange = createToggleBlockMarkOnRangeNext(markType, getAttrs, allowedBlocks); const initialDoc = tr.doc; const { selection } = tr; if (selection instanceof CellSelection) { selection.forEachCell((cell, pos) => { markApplied = toggleBlockMarkOnRange(pos, pos + cell.nodeSize, tr); }); } else { const { from, to } = selection; markApplied = toggleBlockMarkOnRange(from, to, tr); } // compare tr.doc with initialDoc instead of tr.docChanged // because tr passed in might have been modified prior this function // e.g. see changeAlignmentTr platform/packages/editor/editor-plugin-alignment/src/editor-commands/index.ts:L197 if (markApplied && !initialDoc.eq(tr.doc)) { tr.scrollIntoView(); return true; } return false; }; export const clearEditorContent = (state, dispatch) => { const tr = state.tr; tr.replace(0, state.doc.nodeSize - 2); tr.setSelection(Selection.atStart(tr.doc)); if (dispatch) { dispatch(tr); return true; } return false; }; // https://github.com/ProseMirror/prosemirror-commands/blob/master/src/commands.js#L90 // Keep going left up the tree, without going across isolating boundaries, until we // can go along the tree at that same level // // You can think of this as, if you could construct each document like we do in the tests, // return the position of the first ) backwards from the current selection. export function findCutBefore($pos) { // parent is non-isolating, so we can look across this boundary if (!$pos.parent.type.spec.isolating) { // search up the tree from the pos's *parent* for (let i = $pos.depth - 1; i >= 0; i--) { // starting from the inner most node's parent, find out // if we're not its first child if ($pos.index(i) > 0) { return $pos.doc.resolve($pos.before(i + 1)); } if ($pos.node(i).type.spec.isolating) { break; } } } return null; } // eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required -- Ignored via go/ED-25883 /** * @deprecated * * This method is no longer needed and can be accessed via the * `editor-plugin-block-type` command of `setTextLevel` */ export function setHeading(level) { return function (state, dispatch) { const { selection, schema, tr } = state; const ranges = selection instanceof CellSelection ? selection.ranges : [selection]; ranges.forEach(({ $from, $to }) => { tr.setBlockType($from.pos, $to.pos, schema.nodes.heading, { level }); }); if (dispatch) { dispatch(tr); } return true; }; } // eslint-disable-next-line @atlaskit/editor/no-re-export export { insertBlock } from './insert-block';