@atlaskit/editor-plugin-code-block
Version:
Code block plugin for @atlaskit/editor-core
96 lines (89 loc) • 4.51 kB
JavaScript
import { GapCursorSelection } from '@atlaskit/editor-common/selection';
import { mapSlice, timestampToString } from '@atlaskit/editor-common/utils';
import { Fragment } from '@atlaskit/editor-prosemirror/model';
import { TextSelection } from '@atlaskit/editor-prosemirror/state';
import { safeInsert } from '@atlaskit/editor-prosemirror/utils';
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/max-params
export function transformToCodeBlockAction(state, start,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
attrs, isNestingInQuoteSupported) {
const startOfCodeBlockText = state.selection.$from;
const endPosition = state.selection.empty && !(state.selection instanceof GapCursorSelection) ? startOfCodeBlockText.end() : state.selection.$to.pos;
const startLinePosition = startOfCodeBlockText.start();
//when cmd+A is used to select the content. start position should be 0.
const parentStartPosition = startOfCodeBlockText.depth === 0 ? 0 : startOfCodeBlockText.before();
const contentSlice = state.doc.slice(startOfCodeBlockText.pos, endPosition);
const codeBlockSlice = mapSlice(contentSlice, (node, parent, index) => {
if (node.type === state.schema.nodes.hardBreak) {
return state.schema.text('\n');
}
if (node.isText) {
return node.mark([]);
}
if (node.isInline) {
// Convert dates
if (node.attrs.timestamp) {
return state.schema.text(timestampToString(node.attrs.timestamp, null));
}
// Convert links
if (node.attrs.url) {
return state.schema.text(node.attrs.url);
}
return node.attrs.text ? state.schema.text(node.attrs.text) : null;
}
// if the current node is the last child of the Slice exit early to prevent
// adding additional line breaks
if (contentSlice.content.childCount - 1 === index) {
return node.content;
}
//useful to decide whether to append line breaks when the content has list items.
const isParentLastChild = parent && contentSlice.content.childCount - 1 === index;
// add line breaks at the end of each paragraph to mimic layout of selected content
// do not add line breaks when the 'paragraph' parent is last child.
if (node.content.childCount && node.type === state.schema.nodes.paragraph && !isParentLastChild) {
return node.content.append(Fragment.from(state.schema.text('\n\n')));
}
return node.content.childCount ? node.content : null;
});
const tr = state.tr;
// Replace current block node
const startMapped = startLinePosition === start ? parentStartPosition : start;
const codeBlock = state.schema.nodes.codeBlock;
const codeBlockNode = codeBlock.createChecked(attrs, codeBlockSlice.content);
/** we only allow the insertion of a codeblock inside a blockquote if nesting in quotes is supported */
const grandParentNode = state.selection.$from.node(-1);
const grandParentNodeType = grandParentNode === null || grandParentNode === void 0 ? void 0 : grandParentNode.type.name;
if (grandParentNodeType === 'blockquote' && !isNestingInQuoteSupported) {
const grandparentEndPos = startOfCodeBlockText.start(-1) + grandParentNode.nodeSize - 1;
safeInsert(codeBlock.createChecked(attrs, codeBlockSlice.content), grandparentEndPos)(tr).scrollIntoView();
tr.delete(startMapped, Math.min(endPosition, tr.doc.content.size));
return tr;
}
tr.replaceWith(startMapped, Math.min(endPosition, tr.doc.content.size), codeBlockNode);
// Reposition cursor when inserting into layouts or table headers
const mapped = tr.doc.resolve(tr.mapping.map(startMapped) + 1);
const selection = TextSelection.findFrom(mapped, state.selection instanceof GapCursorSelection ? -1 : 1, true);
if (selection) {
return tr.setSelection(selection);
}
return tr.setSelection(TextSelection.create(tr.doc, Math.min(start + startOfCodeBlockText.node().nodeSize - 1, tr.doc.content.size)));
}
export function isConvertableToCodeBlock(state) {
// Before a document is loaded, there is no selection.
if (!state.selection) {
return false;
}
const {
$from
} = state.selection;
const node = $from.parent;
if (!node.isTextblock || node.type === state.schema.nodes.codeBlock) {
return false;
}
const parentDepth = $from.depth - 1;
const parentNode = $from.node(parentDepth);
const index = $from.index(parentDepth);
return parentNode.canReplaceWith(index, index + 1, state.schema.nodes.codeBlock);
}