fix: resolve TypeScript errors in frontend build
This commit is contained in:
21
node_modules/@tiptap/extension-code-block/LICENSE.md
generated
vendored
Normal file
21
node_modules/@tiptap/extension-code-block/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025, Tiptap GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
18
node_modules/@tiptap/extension-code-block/README.md
generated
vendored
Normal file
18
node_modules/@tiptap/extension-code-block/README.md
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# @tiptap/extension-code-block
|
||||
|
||||
[](https://www.npmjs.com/package/@tiptap/extension-code-block)
|
||||
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||
[](https://www.npmjs.com/package/@tiptap/extension-code-block)
|
||||
[](https://github.com/sponsors/ueberdosis)
|
||||
|
||||
## Introduction
|
||||
|
||||
Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.
|
||||
|
||||
## Official Documentation
|
||||
|
||||
Documentation can be found on the [Tiptap website](https://tiptap.dev).
|
||||
|
||||
## License
|
||||
|
||||
Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
|
||||
351
node_modules/@tiptap/extension-code-block/dist/index.cjs
generated
vendored
Normal file
351
node_modules/@tiptap/extension-code-block/dist/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.ts
|
||||
var index_exports = {};
|
||||
__export(index_exports, {
|
||||
CodeBlock: () => CodeBlock,
|
||||
backtickInputRegex: () => backtickInputRegex,
|
||||
default: () => index_default,
|
||||
tildeInputRegex: () => tildeInputRegex
|
||||
});
|
||||
module.exports = __toCommonJS(index_exports);
|
||||
|
||||
// src/code-block.ts
|
||||
var import_core = require("@tiptap/core");
|
||||
var import_state = require("@tiptap/pm/state");
|
||||
var DEFAULT_TAB_SIZE = 4;
|
||||
var backtickInputRegex = /^```([a-z]+)?[\s\n]$/;
|
||||
var tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/;
|
||||
var CodeBlock = import_core.Node.create({
|
||||
name: "codeBlock",
|
||||
addOptions() {
|
||||
return {
|
||||
languageClassPrefix: "language-",
|
||||
exitOnTripleEnter: true,
|
||||
exitOnArrowDown: true,
|
||||
defaultLanguage: null,
|
||||
enableTabIndentation: false,
|
||||
tabSize: DEFAULT_TAB_SIZE,
|
||||
HTMLAttributes: {}
|
||||
};
|
||||
},
|
||||
content: "text*",
|
||||
marks: "",
|
||||
group: "block",
|
||||
code: true,
|
||||
defining: true,
|
||||
addAttributes() {
|
||||
return {
|
||||
language: {
|
||||
default: this.options.defaultLanguage,
|
||||
parseHTML: (element) => {
|
||||
var _a;
|
||||
const { languageClassPrefix } = this.options;
|
||||
if (!languageClassPrefix) {
|
||||
return null;
|
||||
}
|
||||
const classNames = [...((_a = element.firstElementChild) == null ? 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",
|
||||
(0, import_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
|
||||
[
|
||||
"code",
|
||||
{
|
||||
class: node.attrs.language ? this.options.languageClassPrefix + node.attrs.language : null
|
||||
},
|
||||
0
|
||||
]
|
||||
];
|
||||
},
|
||||
markdownTokenName: "code",
|
||||
parseMarkdown: (token, helpers) => {
|
||||
var _a, _b;
|
||||
if (((_a = token.raw) == null ? void 0 : _a.startsWith("```")) === false && ((_b = token.raw) == null ? void 0 : _b.startsWith("~~~")) === false && token.codeBlockStyle !== "indented") {
|
||||
return [];
|
||||
}
|
||||
return helpers.createNode(
|
||||
"codeBlock",
|
||||
{ language: token.lang || null },
|
||||
token.text ? [helpers.createTextNode(token.text)] : []
|
||||
);
|
||||
},
|
||||
renderMarkdown: (node, h) => {
|
||||
var _a;
|
||||
let output = "";
|
||||
const language = ((_a = node.attrs) == null ? void 0 : _a.language) || "";
|
||||
if (!node.content) {
|
||||
output = `\`\`\`${language}
|
||||
|
||||
\`\`\``;
|
||||
} else {
|
||||
const lines = [`\`\`\`${language}`, h.renderChildren(node.content), "```"];
|
||||
output = lines.join("\n");
|
||||
}
|
||||
return output;
|
||||
},
|
||||
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;
|
||||
},
|
||||
// handle tab indentation
|
||||
Tab: ({ editor }) => {
|
||||
var _a;
|
||||
if (!this.options.enableTabIndentation) {
|
||||
return false;
|
||||
}
|
||||
const tabSize = (_a = this.options.tabSize) != null ? _a : DEFAULT_TAB_SIZE;
|
||||
const { state } = editor;
|
||||
const { selection } = state;
|
||||
const { $from, empty } = selection;
|
||||
if ($from.parent.type !== this.type) {
|
||||
return false;
|
||||
}
|
||||
const indent = " ".repeat(tabSize);
|
||||
if (empty) {
|
||||
return editor.commands.insertContent(indent);
|
||||
}
|
||||
return editor.commands.command(({ tr }) => {
|
||||
const { from, to } = selection;
|
||||
const text = state.doc.textBetween(from, to, "\n", "\n");
|
||||
const lines = text.split("\n");
|
||||
const indentedText = lines.map((line) => indent + line).join("\n");
|
||||
tr.replaceWith(from, to, state.schema.text(indentedText));
|
||||
return true;
|
||||
});
|
||||
},
|
||||
// handle shift+tab reverse indentation
|
||||
"Shift-Tab": ({ editor }) => {
|
||||
var _a;
|
||||
if (!this.options.enableTabIndentation) {
|
||||
return false;
|
||||
}
|
||||
const tabSize = (_a = this.options.tabSize) != null ? _a : DEFAULT_TAB_SIZE;
|
||||
const { state } = editor;
|
||||
const { selection } = state;
|
||||
const { $from, empty } = selection;
|
||||
if ($from.parent.type !== this.type) {
|
||||
return false;
|
||||
}
|
||||
if (empty) {
|
||||
return editor.commands.command(({ tr }) => {
|
||||
var _a2;
|
||||
const { pos } = $from;
|
||||
const codeBlockStart = $from.start();
|
||||
const codeBlockEnd = $from.end();
|
||||
const allText = state.doc.textBetween(codeBlockStart, codeBlockEnd, "\n", "\n");
|
||||
const lines = allText.split("\n");
|
||||
let currentLineIndex = 0;
|
||||
let charCount = 0;
|
||||
const relativeCursorPos = pos - codeBlockStart;
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
if (charCount + lines[i].length >= relativeCursorPos) {
|
||||
currentLineIndex = i;
|
||||
break;
|
||||
}
|
||||
charCount += lines[i].length + 1;
|
||||
}
|
||||
const currentLine = lines[currentLineIndex];
|
||||
const leadingSpaces = ((_a2 = currentLine.match(/^ */)) == null ? void 0 : _a2[0]) || "";
|
||||
const spacesToRemove = Math.min(leadingSpaces.length, tabSize);
|
||||
if (spacesToRemove === 0) {
|
||||
return true;
|
||||
}
|
||||
let lineStartPos = codeBlockStart;
|
||||
for (let i = 0; i < currentLineIndex; i += 1) {
|
||||
lineStartPos += lines[i].length + 1;
|
||||
}
|
||||
tr.delete(lineStartPos, lineStartPos + spacesToRemove);
|
||||
const cursorPosInLine = pos - lineStartPos;
|
||||
if (cursorPosInLine <= spacesToRemove) {
|
||||
tr.setSelection(import_state.TextSelection.create(tr.doc, lineStartPos));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return editor.commands.command(({ tr }) => {
|
||||
const { from, to } = selection;
|
||||
const text = state.doc.textBetween(from, to, "\n", "\n");
|
||||
const lines = text.split("\n");
|
||||
const reverseIndentText = lines.map((line) => {
|
||||
var _a2;
|
||||
const leadingSpaces = ((_a2 = line.match(/^ */)) == null ? void 0 : _a2[0]) || "";
|
||||
const spacesToRemove = Math.min(leadingSpaces.length, tabSize);
|
||||
return line.slice(spacesToRemove);
|
||||
}).join("\n");
|
||||
tr.replaceWith(from, to, state.schema.text(reverseIndentText));
|
||||
return true;
|
||||
});
|
||||
},
|
||||
// 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 === void 0) {
|
||||
return false;
|
||||
}
|
||||
const nodeAfter = doc.nodeAt(after);
|
||||
if (nodeAfter) {
|
||||
return editor.commands.command(({ tr }) => {
|
||||
tr.setSelection(import_state.Selection.near(doc.resolve(after)));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return editor.commands.exitCode();
|
||||
}
|
||||
};
|
||||
},
|
||||
addInputRules() {
|
||||
return [
|
||||
(0, import_core.textblockTypeInputRule)({
|
||||
find: backtickInputRegex,
|
||||
type: this.type,
|
||||
getAttributes: (match) => ({
|
||||
language: match[1]
|
||||
})
|
||||
}),
|
||||
(0, import_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 import_state.Plugin({
|
||||
key: new import_state.PluginKey("codeBlockVSCodeHandler"),
|
||||
props: {
|
||||
handlePaste: (view, event) => {
|
||||
if (!event.clipboardData) {
|
||||
return false;
|
||||
}
|
||||
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) : void 0;
|
||||
const language = vscodeData == null ? void 0 : vscodeData.mode;
|
||||
if (!text || !language) {
|
||||
return false;
|
||||
}
|
||||
const { tr, schema } = view.state;
|
||||
const textNode = schema.text(text.replace(/\r\n?/g, "\n"));
|
||||
tr.replaceSelectionWith(this.type.create({ language }, textNode));
|
||||
if (tr.selection.$from.parent.type !== this.type) {
|
||||
tr.setSelection(import_state.TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 2))));
|
||||
}
|
||||
tr.setMeta("paste", true);
|
||||
view.dispatch(tr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
// src/index.ts
|
||||
var index_default = CodeBlock;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
CodeBlock,
|
||||
backtickInputRegex,
|
||||
tildeInputRegex
|
||||
});
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
node_modules/@tiptap/extension-code-block/dist/index.cjs.map
generated
vendored
Normal file
1
node_modules/@tiptap/extension-code-block/dist/index.cjs.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
78
node_modules/@tiptap/extension-code-block/dist/index.d.cts
generated
vendored
Normal file
78
node_modules/@tiptap/extension-code-block/dist/index.d.cts
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Node } from '@tiptap/core';
|
||||
|
||||
interface CodeBlockOptions {
|
||||
/**
|
||||
* Adds a prefix to language classes that are applied to code tags.
|
||||
* @default 'language-'
|
||||
*/
|
||||
languageClassPrefix: string | null | undefined;
|
||||
/**
|
||||
* Define whether the node should be exited on triple enter.
|
||||
* @default true
|
||||
*/
|
||||
exitOnTripleEnter: boolean | null | undefined;
|
||||
/**
|
||||
* Define whether the node should be exited on arrow down if there is no node after it.
|
||||
* @default true
|
||||
*/
|
||||
exitOnArrowDown: boolean | null | undefined;
|
||||
/**
|
||||
* The default language.
|
||||
* @default null
|
||||
* @example 'js'
|
||||
*/
|
||||
defaultLanguage: string | null | undefined;
|
||||
/**
|
||||
* Enable tab key for indentation in code blocks.
|
||||
* @default false
|
||||
*/
|
||||
enableTabIndentation: boolean | null | undefined;
|
||||
/**
|
||||
* The number of spaces to use for tab indentation.
|
||||
* @default 4
|
||||
*/
|
||||
tabSize: number | null | undefined;
|
||||
/**
|
||||
* Custom HTML attributes that should be added to the rendered HTML tag.
|
||||
* @default {}
|
||||
* @example { class: 'foo' }
|
||||
*/
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
codeBlock: {
|
||||
/**
|
||||
* Set a code block
|
||||
* @param attributes Code block attributes
|
||||
* @example editor.commands.setCodeBlock({ language: 'javascript' })
|
||||
*/
|
||||
setCodeBlock: (attributes?: {
|
||||
language: string;
|
||||
}) => ReturnType;
|
||||
/**
|
||||
* Toggle a code block
|
||||
* @param attributes Code block attributes
|
||||
* @example editor.commands.toggleCodeBlock({ language: 'javascript' })
|
||||
*/
|
||||
toggleCodeBlock: (attributes?: {
|
||||
language: string;
|
||||
}) => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Matches a code block with backticks.
|
||||
*/
|
||||
declare const backtickInputRegex: RegExp;
|
||||
/**
|
||||
* Matches a code block with tildes.
|
||||
*/
|
||||
declare const tildeInputRegex: RegExp;
|
||||
/**
|
||||
* This extension allows you to create code blocks.
|
||||
* @see https://tiptap.dev/api/nodes/code-block
|
||||
*/
|
||||
declare const CodeBlock: Node<CodeBlockOptions, any>;
|
||||
|
||||
export { CodeBlock, type CodeBlockOptions, backtickInputRegex, CodeBlock as default, tildeInputRegex };
|
||||
78
node_modules/@tiptap/extension-code-block/dist/index.d.ts
generated
vendored
Normal file
78
node_modules/@tiptap/extension-code-block/dist/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Node } from '@tiptap/core';
|
||||
|
||||
interface CodeBlockOptions {
|
||||
/**
|
||||
* Adds a prefix to language classes that are applied to code tags.
|
||||
* @default 'language-'
|
||||
*/
|
||||
languageClassPrefix: string | null | undefined;
|
||||
/**
|
||||
* Define whether the node should be exited on triple enter.
|
||||
* @default true
|
||||
*/
|
||||
exitOnTripleEnter: boolean | null | undefined;
|
||||
/**
|
||||
* Define whether the node should be exited on arrow down if there is no node after it.
|
||||
* @default true
|
||||
*/
|
||||
exitOnArrowDown: boolean | null | undefined;
|
||||
/**
|
||||
* The default language.
|
||||
* @default null
|
||||
* @example 'js'
|
||||
*/
|
||||
defaultLanguage: string | null | undefined;
|
||||
/**
|
||||
* Enable tab key for indentation in code blocks.
|
||||
* @default false
|
||||
*/
|
||||
enableTabIndentation: boolean | null | undefined;
|
||||
/**
|
||||
* The number of spaces to use for tab indentation.
|
||||
* @default 4
|
||||
*/
|
||||
tabSize: number | null | undefined;
|
||||
/**
|
||||
* Custom HTML attributes that should be added to the rendered HTML tag.
|
||||
* @default {}
|
||||
* @example { class: 'foo' }
|
||||
*/
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
codeBlock: {
|
||||
/**
|
||||
* Set a code block
|
||||
* @param attributes Code block attributes
|
||||
* @example editor.commands.setCodeBlock({ language: 'javascript' })
|
||||
*/
|
||||
setCodeBlock: (attributes?: {
|
||||
language: string;
|
||||
}) => ReturnType;
|
||||
/**
|
||||
* Toggle a code block
|
||||
* @param attributes Code block attributes
|
||||
* @example editor.commands.toggleCodeBlock({ language: 'javascript' })
|
||||
*/
|
||||
toggleCodeBlock: (attributes?: {
|
||||
language: string;
|
||||
}) => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Matches a code block with backticks.
|
||||
*/
|
||||
declare const backtickInputRegex: RegExp;
|
||||
/**
|
||||
* Matches a code block with tildes.
|
||||
*/
|
||||
declare const tildeInputRegex: RegExp;
|
||||
/**
|
||||
* This extension allows you to create code blocks.
|
||||
* @see https://tiptap.dev/api/nodes/code-block
|
||||
*/
|
||||
declare const CodeBlock: Node<CodeBlockOptions, any>;
|
||||
|
||||
export { CodeBlock, type CodeBlockOptions, backtickInputRegex, CodeBlock as default, tildeInputRegex };
|
||||
322
node_modules/@tiptap/extension-code-block/dist/index.js
generated
vendored
Normal file
322
node_modules/@tiptap/extension-code-block/dist/index.js
generated
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
// src/code-block.ts
|
||||
import { mergeAttributes, Node, textblockTypeInputRule } from "@tiptap/core";
|
||||
import { Plugin, PluginKey, Selection, TextSelection } from "@tiptap/pm/state";
|
||||
var DEFAULT_TAB_SIZE = 4;
|
||||
var backtickInputRegex = /^```([a-z]+)?[\s\n]$/;
|
||||
var tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/;
|
||||
var CodeBlock = Node.create({
|
||||
name: "codeBlock",
|
||||
addOptions() {
|
||||
return {
|
||||
languageClassPrefix: "language-",
|
||||
exitOnTripleEnter: true,
|
||||
exitOnArrowDown: true,
|
||||
defaultLanguage: null,
|
||||
enableTabIndentation: false,
|
||||
tabSize: DEFAULT_TAB_SIZE,
|
||||
HTMLAttributes: {}
|
||||
};
|
||||
},
|
||||
content: "text*",
|
||||
marks: "",
|
||||
group: "block",
|
||||
code: true,
|
||||
defining: true,
|
||||
addAttributes() {
|
||||
return {
|
||||
language: {
|
||||
default: this.options.defaultLanguage,
|
||||
parseHTML: (element) => {
|
||||
var _a;
|
||||
const { languageClassPrefix } = this.options;
|
||||
if (!languageClassPrefix) {
|
||||
return null;
|
||||
}
|
||||
const classNames = [...((_a = element.firstElementChild) == null ? 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",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
[
|
||||
"code",
|
||||
{
|
||||
class: node.attrs.language ? this.options.languageClassPrefix + node.attrs.language : null
|
||||
},
|
||||
0
|
||||
]
|
||||
];
|
||||
},
|
||||
markdownTokenName: "code",
|
||||
parseMarkdown: (token, helpers) => {
|
||||
var _a, _b;
|
||||
if (((_a = token.raw) == null ? void 0 : _a.startsWith("```")) === false && ((_b = token.raw) == null ? void 0 : _b.startsWith("~~~")) === false && token.codeBlockStyle !== "indented") {
|
||||
return [];
|
||||
}
|
||||
return helpers.createNode(
|
||||
"codeBlock",
|
||||
{ language: token.lang || null },
|
||||
token.text ? [helpers.createTextNode(token.text)] : []
|
||||
);
|
||||
},
|
||||
renderMarkdown: (node, h) => {
|
||||
var _a;
|
||||
let output = "";
|
||||
const language = ((_a = node.attrs) == null ? void 0 : _a.language) || "";
|
||||
if (!node.content) {
|
||||
output = `\`\`\`${language}
|
||||
|
||||
\`\`\``;
|
||||
} else {
|
||||
const lines = [`\`\`\`${language}`, h.renderChildren(node.content), "```"];
|
||||
output = lines.join("\n");
|
||||
}
|
||||
return output;
|
||||
},
|
||||
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;
|
||||
},
|
||||
// handle tab indentation
|
||||
Tab: ({ editor }) => {
|
||||
var _a;
|
||||
if (!this.options.enableTabIndentation) {
|
||||
return false;
|
||||
}
|
||||
const tabSize = (_a = this.options.tabSize) != null ? _a : DEFAULT_TAB_SIZE;
|
||||
const { state } = editor;
|
||||
const { selection } = state;
|
||||
const { $from, empty } = selection;
|
||||
if ($from.parent.type !== this.type) {
|
||||
return false;
|
||||
}
|
||||
const indent = " ".repeat(tabSize);
|
||||
if (empty) {
|
||||
return editor.commands.insertContent(indent);
|
||||
}
|
||||
return editor.commands.command(({ tr }) => {
|
||||
const { from, to } = selection;
|
||||
const text = state.doc.textBetween(from, to, "\n", "\n");
|
||||
const lines = text.split("\n");
|
||||
const indentedText = lines.map((line) => indent + line).join("\n");
|
||||
tr.replaceWith(from, to, state.schema.text(indentedText));
|
||||
return true;
|
||||
});
|
||||
},
|
||||
// handle shift+tab reverse indentation
|
||||
"Shift-Tab": ({ editor }) => {
|
||||
var _a;
|
||||
if (!this.options.enableTabIndentation) {
|
||||
return false;
|
||||
}
|
||||
const tabSize = (_a = this.options.tabSize) != null ? _a : DEFAULT_TAB_SIZE;
|
||||
const { state } = editor;
|
||||
const { selection } = state;
|
||||
const { $from, empty } = selection;
|
||||
if ($from.parent.type !== this.type) {
|
||||
return false;
|
||||
}
|
||||
if (empty) {
|
||||
return editor.commands.command(({ tr }) => {
|
||||
var _a2;
|
||||
const { pos } = $from;
|
||||
const codeBlockStart = $from.start();
|
||||
const codeBlockEnd = $from.end();
|
||||
const allText = state.doc.textBetween(codeBlockStart, codeBlockEnd, "\n", "\n");
|
||||
const lines = allText.split("\n");
|
||||
let currentLineIndex = 0;
|
||||
let charCount = 0;
|
||||
const relativeCursorPos = pos - codeBlockStart;
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
if (charCount + lines[i].length >= relativeCursorPos) {
|
||||
currentLineIndex = i;
|
||||
break;
|
||||
}
|
||||
charCount += lines[i].length + 1;
|
||||
}
|
||||
const currentLine = lines[currentLineIndex];
|
||||
const leadingSpaces = ((_a2 = currentLine.match(/^ */)) == null ? void 0 : _a2[0]) || "";
|
||||
const spacesToRemove = Math.min(leadingSpaces.length, tabSize);
|
||||
if (spacesToRemove === 0) {
|
||||
return true;
|
||||
}
|
||||
let lineStartPos = codeBlockStart;
|
||||
for (let i = 0; i < currentLineIndex; i += 1) {
|
||||
lineStartPos += lines[i].length + 1;
|
||||
}
|
||||
tr.delete(lineStartPos, lineStartPos + spacesToRemove);
|
||||
const cursorPosInLine = pos - lineStartPos;
|
||||
if (cursorPosInLine <= spacesToRemove) {
|
||||
tr.setSelection(TextSelection.create(tr.doc, lineStartPos));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return editor.commands.command(({ tr }) => {
|
||||
const { from, to } = selection;
|
||||
const text = state.doc.textBetween(from, to, "\n", "\n");
|
||||
const lines = text.split("\n");
|
||||
const reverseIndentText = lines.map((line) => {
|
||||
var _a2;
|
||||
const leadingSpaces = ((_a2 = line.match(/^ */)) == null ? void 0 : _a2[0]) || "";
|
||||
const spacesToRemove = Math.min(leadingSpaces.length, tabSize);
|
||||
return line.slice(spacesToRemove);
|
||||
}).join("\n");
|
||||
tr.replaceWith(from, to, state.schema.text(reverseIndentText));
|
||||
return true;
|
||||
});
|
||||
},
|
||||
// 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 === void 0) {
|
||||
return false;
|
||||
}
|
||||
const nodeAfter = doc.nodeAt(after);
|
||||
if (nodeAfter) {
|
||||
return editor.commands.command(({ tr }) => {
|
||||
tr.setSelection(Selection.near(doc.resolve(after)));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return editor.commands.exitCode();
|
||||
}
|
||||
};
|
||||
},
|
||||
addInputRules() {
|
||||
return [
|
||||
textblockTypeInputRule({
|
||||
find: backtickInputRegex,
|
||||
type: this.type,
|
||||
getAttributes: (match) => ({
|
||||
language: match[1]
|
||||
})
|
||||
}),
|
||||
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 Plugin({
|
||||
key: new PluginKey("codeBlockVSCodeHandler"),
|
||||
props: {
|
||||
handlePaste: (view, event) => {
|
||||
if (!event.clipboardData) {
|
||||
return false;
|
||||
}
|
||||
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) : void 0;
|
||||
const language = vscodeData == null ? void 0 : vscodeData.mode;
|
||||
if (!text || !language) {
|
||||
return false;
|
||||
}
|
||||
const { tr, schema } = view.state;
|
||||
const textNode = schema.text(text.replace(/\r\n?/g, "\n"));
|
||||
tr.replaceSelectionWith(this.type.create({ language }, textNode));
|
||||
if (tr.selection.$from.parent.type !== this.type) {
|
||||
tr.setSelection(TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 2))));
|
||||
}
|
||||
tr.setMeta("paste", true);
|
||||
view.dispatch(tr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
// src/index.ts
|
||||
var index_default = CodeBlock;
|
||||
export {
|
||||
CodeBlock,
|
||||
backtickInputRegex,
|
||||
index_default as default,
|
||||
tildeInputRegex
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/@tiptap/extension-code-block/dist/index.js.map
generated
vendored
Normal file
1
node_modules/@tiptap/extension-code-block/dist/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
50
node_modules/@tiptap/extension-code-block/package.json
generated
vendored
Normal file
50
node_modules/@tiptap/extension-code-block/package.json
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@tiptap/extension-code-block",
|
||||
"description": "code block extension for tiptap",
|
||||
"version": "3.21.0",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
"tiptap extension"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": {
|
||||
"import": "./dist/index.d.ts",
|
||||
"require": "./dist/index.d.cts"
|
||||
},
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@tiptap/core": "^3.21.0",
|
||||
"@tiptap/pm": "^3.21.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^3.21.0",
|
||||
"@tiptap/pm": "^3.21.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ueberdosis/tiptap",
|
||||
"directory": "packages/extension-code-block"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"lint": "prettier ./src/ --check && eslint --cache --quiet --no-error-on-unmatched-pattern ./src/"
|
||||
}
|
||||
}
|
||||
477
node_modules/@tiptap/extension-code-block/src/code-block.ts
generated
vendored
Normal file
477
node_modules/@tiptap/extension-code-block/src/code-block.ts
generated
vendored
Normal file
@@ -0,0 +1,477 @@
|
||||
import { mergeAttributes, Node, textblockTypeInputRule } from '@tiptap/core'
|
||||
import { Plugin, PluginKey, Selection, TextSelection } from '@tiptap/pm/state'
|
||||
|
||||
const DEFAULT_TAB_SIZE = 4
|
||||
|
||||
export interface CodeBlockOptions {
|
||||
/**
|
||||
* Adds a prefix to language classes that are applied to code tags.
|
||||
* @default 'language-'
|
||||
*/
|
||||
languageClassPrefix: string | null | undefined
|
||||
/**
|
||||
* Define whether the node should be exited on triple enter.
|
||||
* @default true
|
||||
*/
|
||||
exitOnTripleEnter: boolean | null | undefined
|
||||
/**
|
||||
* Define whether the node should be exited on arrow down if there is no node after it.
|
||||
* @default true
|
||||
*/
|
||||
exitOnArrowDown: boolean | null | undefined
|
||||
/**
|
||||
* The default language.
|
||||
* @default null
|
||||
* @example 'js'
|
||||
*/
|
||||
defaultLanguage: string | null | undefined
|
||||
/**
|
||||
* Enable tab key for indentation in code blocks.
|
||||
* @default false
|
||||
*/
|
||||
enableTabIndentation: boolean | null | undefined
|
||||
/**
|
||||
* The number of spaces to use for tab indentation.
|
||||
* @default 4
|
||||
*/
|
||||
tabSize: number | null | undefined
|
||||
/**
|
||||
* Custom HTML attributes that should be added to the rendered HTML tag.
|
||||
* @default {}
|
||||
* @example { class: 'foo' }
|
||||
*/
|
||||
HTMLAttributes: Record<string, any>
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
codeBlock: {
|
||||
/**
|
||||
* Set a code block
|
||||
* @param attributes Code block attributes
|
||||
* @example editor.commands.setCodeBlock({ language: 'javascript' })
|
||||
*/
|
||||
setCodeBlock: (attributes?: { language: string }) => ReturnType
|
||||
/**
|
||||
* Toggle a code block
|
||||
* @param attributes Code block attributes
|
||||
* @example editor.commands.toggleCodeBlock({ language: 'javascript' })
|
||||
*/
|
||||
toggleCodeBlock: (attributes?: { language: string }) => ReturnType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a code block with backticks.
|
||||
*/
|
||||
export const backtickInputRegex = /^```([a-z]+)?[\s\n]$/
|
||||
|
||||
/**
|
||||
* Matches a code block with tildes.
|
||||
*/
|
||||
export const tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/
|
||||
|
||||
/**
|
||||
* This extension allows you to create code blocks.
|
||||
* @see https://tiptap.dev/api/nodes/code-block
|
||||
*/
|
||||
export const CodeBlock = Node.create<CodeBlockOptions>({
|
||||
name: 'codeBlock',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
languageClassPrefix: 'language-',
|
||||
exitOnTripleEnter: true,
|
||||
exitOnArrowDown: true,
|
||||
defaultLanguage: null,
|
||||
enableTabIndentation: false,
|
||||
tabSize: DEFAULT_TAB_SIZE,
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: 'text*',
|
||||
|
||||
marks: '',
|
||||
|
||||
group: 'block',
|
||||
|
||||
code: true,
|
||||
|
||||
defining: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
language: {
|
||||
default: this.options.defaultLanguage,
|
||||
parseHTML: element => {
|
||||
const { languageClassPrefix } = this.options
|
||||
|
||||
if (!languageClassPrefix) {
|
||||
return null
|
||||
}
|
||||
|
||||
const classNames = [...(element.firstElementChild?.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',
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
[
|
||||
'code',
|
||||
{
|
||||
class: node.attrs.language ? this.options.languageClassPrefix + node.attrs.language : null,
|
||||
},
|
||||
0,
|
||||
],
|
||||
]
|
||||
},
|
||||
|
||||
markdownTokenName: 'code',
|
||||
|
||||
parseMarkdown: (token, helpers) => {
|
||||
if (
|
||||
token.raw?.startsWith('```') === false &&
|
||||
token.raw?.startsWith('~~~') === false &&
|
||||
token.codeBlockStyle !== 'indented'
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
return helpers.createNode(
|
||||
'codeBlock',
|
||||
{ language: token.lang || null },
|
||||
token.text ? [helpers.createTextNode(token.text)] : [],
|
||||
)
|
||||
},
|
||||
|
||||
renderMarkdown: (node, h) => {
|
||||
let output = ''
|
||||
const language = node.attrs?.language || ''
|
||||
|
||||
if (!node.content) {
|
||||
output = `\`\`\`${language}\n\n\`\`\``
|
||||
} else {
|
||||
const lines = [`\`\`\`${language}`, h.renderChildren(node.content), '```']
|
||||
output = lines.join('\n')
|
||||
}
|
||||
|
||||
return output
|
||||
},
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
// handle tab indentation
|
||||
Tab: ({ editor }) => {
|
||||
if (!this.options.enableTabIndentation) {
|
||||
return false
|
||||
}
|
||||
|
||||
const tabSize = this.options.tabSize ?? DEFAULT_TAB_SIZE
|
||||
const { state } = editor
|
||||
const { selection } = state
|
||||
const { $from, empty } = selection
|
||||
|
||||
if ($from.parent.type !== this.type) {
|
||||
return false
|
||||
}
|
||||
|
||||
const indent = ' '.repeat(tabSize)
|
||||
|
||||
if (empty) {
|
||||
return editor.commands.insertContent(indent)
|
||||
}
|
||||
|
||||
return editor.commands.command(({ tr }) => {
|
||||
const { from, to } = selection
|
||||
const text = state.doc.textBetween(from, to, '\n', '\n')
|
||||
const lines = text.split('\n')
|
||||
const indentedText = lines.map(line => indent + line).join('\n')
|
||||
|
||||
tr.replaceWith(from, to, state.schema.text(indentedText))
|
||||
return true
|
||||
})
|
||||
},
|
||||
|
||||
// handle shift+tab reverse indentation
|
||||
'Shift-Tab': ({ editor }) => {
|
||||
if (!this.options.enableTabIndentation) {
|
||||
return false
|
||||
}
|
||||
|
||||
const tabSize = this.options.tabSize ?? DEFAULT_TAB_SIZE
|
||||
const { state } = editor
|
||||
const { selection } = state
|
||||
const { $from, empty } = selection
|
||||
|
||||
if ($from.parent.type !== this.type) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
return editor.commands.command(({ tr }) => {
|
||||
const { pos } = $from
|
||||
const codeBlockStart = $from.start()
|
||||
const codeBlockEnd = $from.end()
|
||||
|
||||
const allText = state.doc.textBetween(codeBlockStart, codeBlockEnd, '\n', '\n')
|
||||
const lines = allText.split('\n')
|
||||
|
||||
let currentLineIndex = 0
|
||||
let charCount = 0
|
||||
const relativeCursorPos = pos - codeBlockStart
|
||||
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
if (charCount + lines[i].length >= relativeCursorPos) {
|
||||
currentLineIndex = i
|
||||
break
|
||||
}
|
||||
charCount += lines[i].length + 1
|
||||
}
|
||||
|
||||
const currentLine = lines[currentLineIndex]
|
||||
const leadingSpaces = currentLine.match(/^ */)?.[0] || ''
|
||||
const spacesToRemove = Math.min(leadingSpaces.length, tabSize)
|
||||
|
||||
if (spacesToRemove === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
let lineStartPos = codeBlockStart
|
||||
for (let i = 0; i < currentLineIndex; i += 1) {
|
||||
lineStartPos += lines[i].length + 1
|
||||
}
|
||||
|
||||
tr.delete(lineStartPos, lineStartPos + spacesToRemove)
|
||||
|
||||
const cursorPosInLine = pos - lineStartPos
|
||||
if (cursorPosInLine <= spacesToRemove) {
|
||||
tr.setSelection(TextSelection.create(tr.doc, lineStartPos))
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return editor.commands.command(({ tr }) => {
|
||||
const { from, to } = selection
|
||||
const text = state.doc.textBetween(from, to, '\n', '\n')
|
||||
const lines = text.split('\n')
|
||||
const reverseIndentText = lines
|
||||
.map(line => {
|
||||
const leadingSpaces = line.match(/^ */)?.[0] || ''
|
||||
const spacesToRemove = Math.min(leadingSpaces.length, tabSize)
|
||||
return line.slice(spacesToRemove)
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
tr.replaceWith(from, to, state.schema.text(reverseIndentText))
|
||||
return true
|
||||
})
|
||||
},
|
||||
|
||||
// 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 editor.commands.command(({ tr }) => {
|
||||
tr.setSelection(Selection.near(doc.resolve(after)))
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return editor.commands.exitCode()
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addInputRules() {
|
||||
return [
|
||||
textblockTypeInputRule({
|
||||
find: backtickInputRegex,
|
||||
type: this.type,
|
||||
getAttributes: match => ({
|
||||
language: match[1],
|
||||
}),
|
||||
}),
|
||||
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 Plugin({
|
||||
key: new PluginKey('codeBlockVSCodeHandler'),
|
||||
props: {
|
||||
handlePaste: (view, event) => {
|
||||
if (!event.clipboardData) {
|
||||
return false
|
||||
}
|
||||
|
||||
// don’t 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?.mode
|
||||
|
||||
if (!text || !language) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { tr, schema } = view.state
|
||||
|
||||
// prepare a text node
|
||||
// strip carriage return chars from text pasted as code
|
||||
// see: https://github.com/ProseMirror/prosemirror-view/commit/a50a6bcceb4ce52ac8fcc6162488d8875613aacd
|
||||
const textNode = schema.text(text.replace(/\r\n?/g, '\n'))
|
||||
|
||||
// create a code block with the text node
|
||||
// replace selection with the code block
|
||||
tr.replaceSelectionWith(this.type.create({ language }, textNode))
|
||||
|
||||
if (tr.selection.$from.parent.type !== this.type) {
|
||||
// put cursor inside the newly created code block
|
||||
tr.setSelection(TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 2))))
|
||||
}
|
||||
|
||||
// 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
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
||||
5
node_modules/@tiptap/extension-code-block/src/index.ts
generated
vendored
Normal file
5
node_modules/@tiptap/extension-code-block/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { CodeBlock } from './code-block.js'
|
||||
|
||||
export * from './code-block.js'
|
||||
|
||||
export default CodeBlock
|
||||
Reference in New Issue
Block a user