@atlaskit/editor-plugin-paste-options-toolbar
Version:
Paste options toolbar for @atlaskit/editor-core
178 lines (172 loc) • 7.38 kB
JavaScript
import { logException } from '@atlaskit/editor-common/monitoring';
import { md } from '@atlaskit/editor-common/paste';
import { MarkdownTransformer } from '@atlaskit/editor-markdown-transformer';
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
import { Selection } from '@atlaskit/editor-prosemirror/state';
import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
import { escapeLinks } from './index';
export const formatMarkdown = (tr, pluginState) => {
let pasteStartPos = pluginState.pasteStartPos;
const pasteEndPos = pluginState.pasteEndPos;
const plaintext = pluginState.plaintext;
if (pasteStartPos < 0) {
return tr;
}
const resolvedPasteStartPos = tr.doc.resolve(pasteStartPos);
const parentOffset = resolvedPasteStartPos.parentOffset;
if (parentOffset === 0 && resolvedPasteStartPos.depth > 0) {
pasteStartPos = resolvedPasteStartPos.before();
}
const markdownSlice = getMarkdownSlice(plaintext, tr.doc.type.schema, tr.selection);
if (!markdownSlice) {
return tr;
}
pasteSliceIntoTransactionWithSelectionAdjust({
tr,
pasteStartPos,
pasteEndPos,
slice: markdownSlice
});
return tr;
};
export const formatRichText = (tr, pluginState) => {
let pasteStartPos = pluginState.pasteStartPos;
const pasteEndPos = pluginState.pasteEndPos;
const richTextSlice = pluginState.richTextSlice;
if (pasteStartPos < 0) {
return tr;
}
if (richTextSlice.content.size === 0) {
return tr;
}
const resolvedPasteStartPos = tr.doc.resolve(pasteStartPos);
const parentOffset = resolvedPasteStartPos.parentOffset;
if (parentOffset === 0 && resolvedPasteStartPos.depth > 0) {
pasteStartPos = resolvedPasteStartPos.before();
}
richTextSliceTransactionWithSelectionAdjust({
tr,
pasteStartPos,
pasteEndPos,
slice: richTextSlice
});
return tr;
};
export const formatPlainText = (tr, pluginState) => {
let pasteStartPos = pluginState.pasteStartPos;
const pasteEndPos = pluginState.pasteEndPos;
const plaintext = pluginState.plaintext;
//not possible to create plain text slice with empty string
if (pasteStartPos < 0 || plaintext === '') {
return tr;
}
const resolvedPasteStartPos = tr.doc.resolve(pasteStartPos);
const parentOffset = resolvedPasteStartPos.parentOffset;
if (parentOffset === 0 && resolvedPasteStartPos.depth > 0) {
pasteStartPos = resolvedPasteStartPos.before();
}
const schema = tr.doc.type.schema;
const plainTextNode = schema.text(plaintext);
const plainTextFragment = Fragment.from(schema.nodes.paragraph.createAndFill(null, plainTextNode));
const plainTextSlice = new Slice(plainTextFragment, resolvedPasteStartPos.depth, resolvedPasteStartPos.depth);
pasteSliceIntoTransactionWithSelectionAdjust({
tr,
pasteStartPos,
pasteEndPos,
slice: plainTextSlice
});
return tr;
};
function pasteSliceIntoTransactionWithSelectionAdjust({
tr,
pasteStartPos,
pasteEndPos,
slice
}) {
tr.replaceRange(pasteStartPos, pasteEndPos, slice);
// ProseMirror doesn't give a proper way to tell us where something was inserted.
// However, we can know "how" it inserted something.
//
// So, instead of weird depth calculations, we can use the step produced by the transform.
// For instance:
// The `replaceStep.to and replaceStep.from`, tell us the real position
// where the content will be insert.
// Then, we can use the `tr.mapping.map` to the updated position after the replace operation
const replaceStep = tr.steps[0];
if (!(replaceStep instanceof ReplaceStep)) {
return tr;
}
const lastInsertNode = replaceStep.slice.content.lastChild;
const emptyNodeReference = lastInsertNode === null || lastInsertNode === void 0 ? void 0 : lastInsertNode.type.createAndFill();
const isLastNodeEmpty = (emptyNodeReference === null || emptyNodeReference === void 0 ? void 0 : emptyNodeReference.nodeSize) === (lastInsertNode === null || lastInsertNode === void 0 ? void 0 : lastInsertNode.nodeSize);
const isStepSplitingTarget = !(lastInsertNode !== null && lastInsertNode !== void 0 && lastInsertNode.isLeaf) && isLastNodeEmpty;
const $nextHead = tr.doc.resolve(tr.mapping.map(replaceStep.to));
const $nextPosition = isStepSplitingTarget && $nextHead.depth > 0 ? tr.doc.resolve($nextHead.before()) : $nextHead;
// The findFrom will make search for both: TextSelection and NodeSelections.
const nextSelection = Selection.findFrom($nextPosition, -1);
if (nextSelection) {
tr.setSelection(nextSelection);
}
}
function richTextSliceTransactionWithSelectionAdjust({
tr,
pasteStartPos,
pasteEndPos,
slice
}) {
tr.replaceRange(pasteStartPos, pasteEndPos, slice);
// ProseMirror doesn't give a proper way to tell us where something was inserted.
// However, we can know "how" it inserted something.
//
// So, instead of weird depth calculations, we can use the step produced by the transform.
// For instance:
// The `replaceStep.to and replaceStep.from`, tell us the real position
// where the content will be insert.
// Then, we can use the `tr.mapping.map` to the updated position after the replace operation
const replaceStep = tr.steps[0];
if (!(replaceStep instanceof ReplaceStep)) {
return tr;
}
const nextPosition = tr.mapping.map(replaceStep.to);
// The findFrom will make search for both: TextSelection and NodeSelections.
const nextSelection = Selection.findFrom(tr.doc.resolve(Math.min(nextPosition, tr.doc.content.size)), -1);
if (nextSelection) {
tr.setSelection(nextSelection);
}
}
export function getMarkdownSlice(text, schema, selection) {
const targetOpenStartNode = selection.$from.parent;
const targetOpenEndNode = selection.$to.parent;
try {
var _doc$content$firstChi, _doc$content$lastChil;
let textInput = text;
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
const textSplitByCodeBlock = textInput.split(/```/);
for (let i = 0; i < textSplitByCodeBlock.length; i++) {
if (i % 2 === 0) {
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
textSplitByCodeBlock[i] = textSplitByCodeBlock[i].replace(/\\/g, '\\\\');
}
}
textInput = textSplitByCodeBlock.join('```');
const atlassianMarkDownParser = new MarkdownTransformer(schema, md);
const doc = atlassianMarkDownParser.parse(escapeLinks(textInput));
if (!doc || !doc.content) {
return;
}
const canMergeOpenStart = targetOpenStartNode.type === ((_doc$content$firstChi = doc.content.firstChild) === null || _doc$content$firstChi === void 0 ? void 0 : _doc$content$firstChi.type);
const canMergeOpenEnd = targetOpenEndNode.type === ((_doc$content$lastChil = doc.content.lastChild) === null || _doc$content$lastChil === void 0 ? void 0 : _doc$content$lastChil.type);
const $start = Selection.atStart(doc).$from;
const $end = Selection.atEnd(doc).$from;
const openStart = canMergeOpenStart ? $start.depth : 0;
const openEnd = canMergeOpenEnd ? $end.depth : 0;
return new Slice(doc.content, openStart, openEnd);
} catch (error) {
logException(error, {
location: 'editor-plugin-paste-options-toolbar/util'
});
return;
}
}