You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
9.0 KiB

3 years ago
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('prosemirror-state')) :
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', 'prosemirror-state'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-code-block"] = {}, global.core, global.prosemirrorState));
})(this, (function (exports, core, prosemirrorState) { 'use strict';
const backtickInputRegex = /^```([a-z]+)?[\s\n]$/;
const tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/;
const CodeBlock = core.Node.create({
name: 'codeBlock',
addOptions() {
return {
languageClassPrefix: 'language-',
exitOnTripleEnter: true,
exitOnArrowDown: true,
HTMLAttributes: {},
};
},
content: 'text*',
marks: '',
group: 'block',
code: true,
defining: true,
addAttributes() {
return {
language: {
default: null,
parseHTML: element => {
var _a;
const { languageClassPrefix } = this.options;
const classNames = [...((_a = element.firstElementChild) === null || _a === void 0 ? void 0 : _a.classList) || []];
const languages = classNames
.filter(className => className.startsWith(languageClassPrefix))
.map(className => className.replace(languageClassPrefix, ''));
const language = languages[0];
if (!language) {
return null;
}
return language;
},
rendered: false,
},
};
},
parseHTML() {
return [
{
tag: 'pre',
preserveWhitespace: 'full',
},
];
},
renderHTML({ node, HTMLAttributes }) {
return [
'pre',
core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
[
'code',
{
class: node.attrs.language
? this.options.languageClassPrefix + node.attrs.language
: null,
},
0,
],
];
},
addCommands() {
return {
setCodeBlock: attributes => ({ commands }) => {
return commands.setNode(this.name, attributes);
},
toggleCodeBlock: attributes => ({ commands }) => {
return commands.toggleNode(this.name, 'paragraph', attributes);
},
};
},
addKeyboardShortcuts() {
return {
'Mod-Alt-c': () => this.editor.commands.toggleCodeBlock(),
// remove code block when at start of document or code block is empty
Backspace: () => {
const { empty, $anchor } = this.editor.state.selection;
const isAtStart = $anchor.pos === 1;
if (!empty || $anchor.parent.type.name !== this.name) {
return false;
}
if (isAtStart || !$anchor.parent.textContent.length) {
return this.editor.commands.clearNodes();
}
return false;
},
// exit node on triple enter
Enter: ({ editor }) => {
if (!this.options.exitOnTripleEnter) {
return false;
}
const { state } = editor;
const { selection } = state;
const { $from, empty } = selection;
if (!empty || $from.parent.type !== this.type) {
return false;
}
const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
const endsWithDoubleNewline = $from.parent.textContent.endsWith('\n\n');
if (!isAtEnd || !endsWithDoubleNewline) {
return false;
}
return editor
.chain()
.command(({ tr }) => {
tr.delete($from.pos - 2, $from.pos);
return true;
})
.exitCode()
.run();
},
// exit node on arrow down
ArrowDown: ({ editor }) => {
if (!this.options.exitOnArrowDown) {
return false;
}
const { state } = editor;
const { selection, doc } = state;
const { $from, empty } = selection;
if (!empty || $from.parent.type !== this.type) {
return false;
}
const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
if (!isAtEnd) {
return false;
}
const after = $from.after();
if (after === undefined) {
return false;
}
const nodeAfter = doc.nodeAt(after);
if (nodeAfter) {
return false;
}
return editor.commands.exitCode();
},
};
},
addInputRules() {
return [
core.textblockTypeInputRule({
find: backtickInputRegex,
type: this.type,
getAttributes: match => ({
language: match[1],
}),
}),
core.textblockTypeInputRule({
find: tildeInputRegex,
type: this.type,
getAttributes: match => ({
language: match[1],
}),
}),
];
},
addProseMirrorPlugins() {
return [
// this plugin creates a code block for pasted content from VS Code
// we can also detect the copied code language
new prosemirrorState.Plugin({
key: new prosemirrorState.PluginKey('codeBlockVSCodeHandler'),
props: {
handlePaste: (view, event) => {
if (!event.clipboardData) {
return false;
}
// dont create a new code block within code blocks
if (this.editor.isActive(this.type.name)) {
return false;
}
const text = event.clipboardData.getData('text/plain');
const vscode = event.clipboardData.getData('vscode-editor-data');
const vscodeData = vscode
? JSON.parse(vscode)
: undefined;
const language = vscodeData === null || vscodeData === void 0 ? void 0 : vscodeData.mode;
if (!text || !language) {
return false;
}
const { tr } = view.state;
// create an empty code block
tr.replaceSelectionWith(this.type.create({ language }));
// put cursor inside the newly created code block
tr.setSelection(prosemirrorState.TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 2))));
// add text to code block
// strip carriage return chars from text pasted as code
// see: https://github.com/ProseMirror/prosemirror-view/commit/a50a6bcceb4ce52ac8fcc6162488d8875613aacd
tr.insertText(text.replace(/\r\n?/g, '\n'));
// store meta information
// this is useful for other plugins that depends on the paste event
// like the paste rule plugin
tr.setMeta('paste', true);
view.dispatch(tr);
return true;
},
},
}),
];
},
});
exports.CodeBlock = CodeBlock;
exports.backtickInputRegex = backtickInputRegex;
exports["default"] = CodeBlock;
exports.tildeInputRegex = tildeInputRegex;
Object.defineProperty(exports, '__esModule', { value: true });
}));
//# sourceMappingURL=tiptap-extension-code-block.umd.js.map