Files
notesnook/packages/editor/dist/extensions/codeblock/codeblock.js
2022-05-30 05:08:09 +05:00

561 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
import { Node, textblockTypeInputRule, mergeAttributes } from "@tiptap/core";
import { Plugin, PluginKey, TextSelection, } from "prosemirror-state";
import { findParentNodeClosestToPos, ReactNodeViewRenderer, } from "@tiptap/react";
import { CodeblockComponent } from "./component";
import { HighlighterPlugin } from "./highlighter";
import detectIndent from "detect-indent";
export var backtickInputRegex = /^```([a-z]+)?[\s\n]$/;
export var tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/;
var ZERO_WIDTH_SPACE = "\u200b";
var NEWLINE = "\n";
export var CodeBlock = Node.create({
name: "codeblock",
addOptions: function () {
return {
languageClassPrefix: "language-",
exitOnTripleEnter: true,
exitOnArrowDown: true,
exitOnArrowUp: true,
HTMLAttributes: {},
};
},
content: "text*",
marks: "",
group: "block",
code: true,
defining: true,
addAttributes: function () {
var _this = this;
return {
lines: {
default: [],
rendered: false,
},
indentType: {
default: "space",
parseHTML: function (element) {
var indentType = element.dataset.indentType;
if (indentType)
return indentType;
return detectIndent(element.innerText).type;
},
renderHTML: function (attributes) {
if (!attributes.indentType) {
return {};
}
return {
"data-indent-type": attributes.indentType,
};
},
},
indentLength: {
default: 2,
parseHTML: function (element) {
var indentLength = element.dataset.indentLength;
if (indentLength)
return indentLength;
return detectIndent(element.innerText).amount;
},
renderHTML: function (attributes) {
if (!attributes.indentLength) {
return {};
}
return {
"data-indent-length": attributes.indentLength,
};
},
},
language: {
default: null,
parseHTML: function (element) {
var _a;
var languageClassPrefix = _this.options.languageClassPrefix;
var classNames = __spreadArray(__spreadArray([], __read((element.classList || [])), false), __read((((_a = element === null || element === void 0 ? void 0 : element.firstElementChild) === null || _a === void 0 ? void 0 : _a.classList) || [])), false);
var languages = classNames
.filter(function (className) { return className.startsWith(languageClassPrefix); })
.map(function (className) { return className.replace(languageClassPrefix, ""); });
var language = languages[0];
if (!language) {
return null;
}
return language;
},
renderHTML: function (attributes) {
if (!attributes.language) {
return {};
}
return {
class: "language-".concat(attributes.language),
};
},
},
};
},
parseHTML: function () {
return [
{
tag: "pre",
preserveWhitespace: "full",
// contentElement: (node) => {
// if (node instanceof HTMLElement) {
// node.innerText = node.innerText.replaceAll("\n\u200b\n", "\n\n");
// }
// return node;
// },
},
];
},
renderHTML: function (_a) {
var HTMLAttributes = _a.HTMLAttributes;
return [
"pre",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
];
},
addCommands: function () {
var _this = this;
return {
setCodeBlock: function (attributes) {
return function (_a) {
var commands = _a.commands;
return commands.setNode(_this.name, attributes);
};
},
toggleCodeBlock: function (attributes) {
return function (_a) {
var commands = _a.commands;
return commands.toggleNode(_this.name, "paragraph", attributes);
};
},
changeCodeBlockIndentation: function (options) {
return function (_a) {
var e_1, _b;
var editor = _a.editor, tr = _a.tr, commands = _a.commands;
var state = editor.state;
var selection = state.selection;
var $from = selection.$from;
if ($from.parent.type !== _this.type) {
return false;
}
var lines = $from.parent.attrs.lines;
try {
for (var lines_1 = __values(lines), lines_1_1 = lines_1.next(); !lines_1_1.done; lines_1_1 = lines_1.next()) {
var line = lines_1_1.value;
var text = line.text();
var whitespaceLength = text.length - text.trimStart().length;
if (!whitespaceLength)
continue;
var indentLength = whitespaceLength;
var indentToken = indent(options.type, indentLength);
tr.insertText(indentToken, tr.mapping.map(line.from), tr.mapping.map(line.from + whitespaceLength));
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (lines_1_1 && !lines_1_1.done && (_b = lines_1.return)) _b.call(lines_1);
}
finally { if (e_1) throw e_1.error; }
}
commands.updateAttributes(_this.type, {
indentType: options.type,
indentLength: options.length,
});
return true;
};
},
};
},
addKeyboardShortcuts: function () {
var _this = this;
return {
"Mod-Alt-c": function () { return _this.editor.commands.toggleCodeBlock(); },
"Mod-a": function (_a) {
var editor = _a.editor;
var $anchor = _this.editor.state.selection.$anchor;
if ($anchor.parent.type.name !== _this.name) {
return false;
}
var codeblock = findParentNodeClosestToPos($anchor, function (node) { return node.type.name === _this.type.name; });
if (!codeblock)
return false;
return editor.commands.setTextSelection({
from: codeblock.pos,
to: codeblock.pos + codeblock.node.nodeSize,
});
},
// remove code block when at start of document or code block is empty
Backspace: function () {
var _a = _this.editor.state.selection, empty = _a.empty, $anchor = _a.$anchor;
var 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: function (_a) {
var editor = _a.editor;
var state = editor.state;
var selection = state.selection;
var $from = selection.$from, empty = selection.empty;
if (!empty || $from.parent.type !== _this.type) {
return false;
}
var indentation = parseIndentation($from.parent);
return ((_this.options.exitOnTripleEnter &&
exitOnTripleEnter(editor, $from)) ||
indentOnEnter(editor, $from, indentation));
},
// exit node on arrow up
ArrowUp: function (_a) {
var editor = _a.editor;
if (!_this.options.exitOnArrowUp) {
return false;
}
var state = editor.state;
var selection = state.selection;
var $anchor = selection.$anchor, empty = selection.empty;
if (!empty || $anchor.parent.type !== _this.type) {
return false;
}
var isAtStart = $anchor.pos === 1;
if (!isAtStart) {
return false;
}
return editor.commands.insertContentAt(0, "<p></p>");
},
// exit node on arrow down
ArrowDown: function (_a) {
var editor = _a.editor;
if (!_this.options.exitOnArrowDown) {
return false;
}
var state = editor.state;
var selection = state.selection, doc = state.doc;
var $from = selection.$from, empty = selection.empty;
if (!empty || $from.parent.type !== _this.type) {
return false;
}
var isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
if (!isAtEnd) {
return false;
}
var after = $from.after();
if (after === undefined) {
return false;
}
var nodeAfter = doc.nodeAt(after);
if (nodeAfter) {
editor.commands.setNodeSelection($from.before());
return false;
}
return editor.commands.exitCode();
},
"Shift-Tab": function (_a) {
var editor = _a.editor;
var state = editor.state;
var selection = state.selection;
var $from = selection.$from;
if ($from.parent.type !== _this.type) {
return false;
}
var indentation = parseIndentation($from.parent);
var indentToken = indent(indentation.type, indentation.length);
var lines = $from.parent.attrs.lines;
var selectedLines = getSelectedLines(lines, selection);
return editor
.chain()
.command(function (_a) {
var tr = _a.tr;
return withSelection(tr, function (tr) {
var e_2, _a;
try {
for (var selectedLines_1 = __values(selectedLines), selectedLines_1_1 = selectedLines_1.next(); !selectedLines_1_1.done; selectedLines_1_1 = selectedLines_1.next()) {
var line = selectedLines_1_1.value;
if (line.text(indentToken.length) !== indentToken)
continue;
tr.delete(tr.mapping.map(line.from), tr.mapping.map(line.from + indentation.length));
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (selectedLines_1_1 && !selectedLines_1_1.done && (_a = selectedLines_1.return)) _a.call(selectedLines_1);
}
finally { if (e_2) throw e_2.error; }
}
});
})
.run();
},
Tab: function (_a) {
var editor = _a.editor;
var state = editor.state;
var selection = state.selection;
var $from = selection.$from;
if ($from.parent.type !== _this.type) {
return false;
}
var lines = $from.parent.attrs.lines;
var selectedLines = getSelectedLines(lines, selection);
return editor
.chain()
.command(function (_a) {
var tr = _a.tr;
return withSelection(tr, function (tr) {
var e_3, _a;
var indentation = parseIndentation($from.parent);
var indentToken = indent(indentation.type, indentation.length);
if (selectedLines.length === 1)
return tr.insertText(indentToken, $from.pos);
try {
for (var selectedLines_2 = __values(selectedLines), selectedLines_2_1 = selectedLines_2.next(); !selectedLines_2_1.done; selectedLines_2_1 = selectedLines_2.next()) {
var line = selectedLines_2_1.value;
tr.insertText(indentToken, tr.mapping.map(line.from));
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (selectedLines_2_1 && !selectedLines_2_1.done && (_a = selectedLines_2.return)) _a.call(selectedLines_2);
}
finally { if (e_3) throw e_3.error; }
}
});
})
.run();
},
};
},
addInputRules: function () {
return [
textblockTypeInputRule({
find: backtickInputRegex,
type: this.type,
getAttributes: function (match) { return ({
language: match[1],
}); },
}),
textblockTypeInputRule({
find: tildeInputRegex,
type: this.type,
getAttributes: function (match) { return ({
language: match[1],
}); },
}),
];
},
addProseMirrorPlugins: function () {
var _this = this;
return [
// this plugin creates a code block for pasted content from VS Code
// we can also detect the copied code language
new Plugin({
key: new PluginKey("codeBlockVSCodeHandler"),
props: {
handlePaste: function (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;
}
var text = event.clipboardData.getData("text/plain");
var vscode = event.clipboardData.getData("vscode-editor-data");
var vscodeData = vscode ? JSON.parse(vscode) : undefined;
var language = vscodeData === null || vscodeData === void 0 ? void 0 : vscodeData.mode;
if (!text || !language) {
return false;
}
var tr = view.state.tr;
// create an empty code block
tr.replaceSelectionWith(_this.type.create({ language: language }));
// put cursor inside the newly created code block
tr.setSelection(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;
},
},
}),
HighlighterPlugin({ name: this.name, defaultLanguage: "txt" }),
];
},
addNodeView: function () {
return ReactNodeViewRenderer(CodeblockComponent);
},
});
export function toCaretPosition(lines, selection) {
var e_4, _a;
var $from = selection.$from, $to = selection.$to, $head = selection.$head;
if ($from.parent.type.name !== CodeBlock.name)
return;
try {
for (var lines_2 = __values(lines), lines_2_1 = lines_2.next(); !lines_2_1.done; lines_2_1 = lines_2.next()) {
var line = lines_2_1.value;
if ($head.pos >= line.from && $head.pos <= line.to) {
var lineLength = line.length + 1;
return {
line: line.index + 1,
column: lineLength - (line.to - $head.pos),
selected: $to.pos - $from.pos,
total: lines.length,
};
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (lines_2_1 && !lines_2_1.done && (_a = lines_2.return)) _a.call(lines_2);
}
finally { if (e_4) throw e_4.error; }
}
return;
}
export function getLines(node) {
var lines = node.attrs.lines;
return lines || [];
}
function exitOnTripleEnter(editor, $from) {
var isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
var endsWithDoubleNewline = $from.parent.textContent.endsWith("\n\n");
if (!isAtEnd || !endsWithDoubleNewline) {
return false;
}
return editor
.chain()
.command(function (_a) {
var tr = _a.tr;
tr.delete($from.pos - 2, $from.pos);
return true;
})
.exitCode()
.run();
}
function indentOnEnter(editor, $from, options) {
var lines = $from.parent.attrs.lines;
var currentLine = getLineAt(lines, $from.pos);
if (!currentLine)
return false;
var text = editor.state.doc.textBetween(currentLine.from, currentLine.to);
var indentLength = text.length - text.trimStart().length;
var newline = "".concat(NEWLINE).concat(indent(options.type, indentLength));
return editor.commands.insertContent(newline, {
parseOptions: { preserveWhitespace: "full" },
});
}
export function toCodeLines(code, pos) {
var positions = [];
var start = 0;
var from = pos + 1;
var index = 0;
var _loop_1 = function () {
var end = code.indexOf("\n", start);
if (end <= -1)
end = code.length;
var lineLength = end - start;
var to = from + lineLength;
var lineStart = start;
positions.push({
index: index,
length: lineLength,
from: from,
to: to,
text: function (length) {
return code.slice(lineStart, length ? lineStart + length : lineStart + lineLength);
},
});
from = to + 1;
start = end + 1;
++index;
};
while (start <= code.length) {
_loop_1();
}
return positions;
}
function getSelectedLines(lines, selection) {
var $from = selection.$from, $to = selection.$to;
return lines.filter(function (line) {
return inRange(line.from, $from.pos, $to.pos) ||
inRange(line.to, $from.pos, $to.pos) ||
inRange($from.pos, line.from, line.to);
});
}
function parseIndentation(node) {
var _a = node.attrs, indentType = _a.indentType, indentLength = _a.indentLength;
return {
type: indentType,
length: parseInt(indentLength),
};
}
function getLineAt(lines, pos) {
return lines.find(function (line) { return pos >= line.from && pos <= line.to; });
}
function inRange(x, a, b) {
return x >= a && x <= b;
}
function indent(type, length) {
var char = type === "space" ? " " : "\t";
return char.repeat(length);
}
/**
* Persist selection between transaction steps
*/
function withSelection(tr, callback) {
var _a = tr.selection, $anchor = _a.$anchor, $head = _a.$head;
callback(tr);
tr.setSelection(new TextSelection(tr.doc.resolve(tr.mapping.map($anchor.pos)), tr.doc.resolve(tr.mapping.map($head.pos))));
return true;
}