@tiptap/extensions
Advanced tools
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/character-count/character-count.ts"],"mappings":";;;UAIiB;;;;;;EAMf;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA,cAAc;;;;;;EAMd,cAAc;;UAGC;;;;;;;EAOf,aAAa;IAAY,OAAO;IAAiB;;;;;;;EAOjD,QAAQ;IAAY,OAAO;;;;YAIjB;IACR,gBAAgB;;;;;;;cAQP,gBAAc,UAAA,uBAAA"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/character-count/character-count.ts"],"mappings":";;;UAIiB;;;;;;EAMf;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA,cAAc;;;;;;EAMd,cAAc;;UAGC;;;;;;;EAOf,aAAa;IAAY,OAAO;IAAiB;;;;;;;EAOjD,QAAQ;IAAY,OAAO;;;;YAIjB;IACR,gBAAgB;;;;;;;cAQP,gBAAc,UAAA,uBAAA"} |
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/drop-cursor/drop-cursor.ts"],"mappings":";;UAGiB;;;;;;EAMf;;;;;;EAOA;;;;;;EAOA;;;;;;;;cASW,YAAU,UAAA"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/drop-cursor/drop-cursor.ts"],"mappings":";;UAGiB;;;;;;EAMf;;;;;;EAOA;;;;;;EAOA;;;;;;;;cASW,YAAU,UAAA"} |
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/focus/focus.ts"],"mappings":";;UAIiB;;;;;;EAMf;;;;;;;;;;;EAYA;;;;;;cAOW,OAAK,UAAA"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/focus/focus.ts"],"mappings":";;UAIiB;;;;;;EAMf;;;;;;;;;;;EAYA;;;;;;cAOW,OAAK,UAAA"} |
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/gap-cursor/gap-cursor.ts"],"mappings":";;;YAKY,WAAW,SAAS;;;;;IAK5B,oCAGM;MACA;MACA,SAAS;MACT,SAAS;MACT,QAAQ,aAAa,WAAW;;;;;;;;;;cAW7B,WAAS"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/gap-cursor/gap-cursor.ts"],"mappings":";;;YAKY,WAAW,SAAS;;;;;IAK5B,oCAGM;MACA;MACA,SAAS;MACT,SAAS;MACT,QAAQ,aAAa,WAAW;;;;;;;;;;cAW7B,WAAS"} |
| {"version":3,"file":"index.d.cts","names":[],"sources":["../src/character-count/character-count.ts","../src/drop-cursor/drop-cursor.ts","../src/focus/focus.ts","../src/gap-cursor/gap-cursor.ts","../src/placeholder/constants.ts","../src/placeholder/types.ts","../src/placeholder/placeholder.ts","../src/placeholder/utils/preparePlaceholderAttribute.ts","../src/selection/selection.ts","../src/trailing-node/trailing-node.ts","../src/undo-redo/undo-redo.ts"],"mappings":";;;;;UAIiB;;;;;;EAMf;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA,cAAc;;;;;;EAMd,cAAc;;UAGC;;;;;;;EAOf,aAAa;IAAY,OAAO;IAAiB;;;;;;;EAOjD,QAAQ;IAAY,OAAO;;;;YAIjB;IACR,gBAAgB;;;;;;;cAQP,gBAAc,UAAA,uBAAA;;;UC/DV;;;;;;EAMf;;;;;;EAOA;;;;;;EAOA;;;;;;;;cASW,YAAU,UAAA;;;UC5BN;;;;;;EAMf;;;;;;;;;;;EAYA;;;;;;cAOW,OAAK,UAAA;;;;YCxBN,WAAW,SAAS;;;;;IAK5B,oCAGM;MACA;MACA,SAAS;MACT,SAAS;MACT,QAAQ,aAAa,WAAW;;;;;;;;;;cAW7B,WAAS;;;;cCxBT;;cAGA,YAAU,UAAA;;;UCJN;;;;;EAKf;;;;;;;EAQA,kBACM;IACA,QAAQ;IACR,MAAM;IACN;IACA;;;;;;;EASN;;;;;;;EAQA,eACM;IACA,QAAQ;IACR,MAAM;IACN;IACA;;;;;;;;;EAWN;;;;;;;;EASA;;;;;;;;EASA;;;;;;;;;cC7DW,aAAW,UAAA;;;;;;;;iBCNR,4BAA4B;;;KCAhC;;;;;;EAMV;;;;;;;;cAqCW,WAAS,UAAA;;;cC5CT;;;;;;UAkBI;;;;;;;EAOf;;;;;EAKA;;;;;;cAOW,cAAY,UAAA;;;UCtCR;;;;;;EAMf;;;;;;EAOA;;;YAIU,SAAS;IACjB;;;;;MAKE,YAAY;;;;;MAKZ,YAAY;;;;;;;;;;;;;cAcL,UAAQ,UAAA"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../src/character-count/character-count.ts","../src/drop-cursor/drop-cursor.ts","../src/focus/focus.ts","../src/gap-cursor/gap-cursor.ts","../src/placeholder/constants.ts","../src/placeholder/types.ts","../src/placeholder/placeholder.ts","../src/placeholder/utils/preparePlaceholderAttribute.ts","../src/selection/selection.ts","../src/trailing-node/trailing-node.ts","../src/undo-redo/undo-redo.ts"],"mappings":";;;;;UAIiB;;;;;;EAMf;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA,cAAc;;;;;;EAMd,cAAc;;UAGC;;;;;;;EAOf,aAAa;IAAY,OAAO;IAAiB;;;;;;;EAOjD,QAAQ;IAAY,OAAO;;;;YAIjB;IACR,gBAAgB;;;;;;;cAQP,gBAAc,UAAA,uBAAA;;;UC/DV;;;;;;EAMf;;;;;;EAOA;;;;;;EAOA;;;;;;;;cASW,YAAU,UAAA;;;UC5BN;;;;;;EAMf;;;;;;;;;;;EAYA;;;;;;cAOW,OAAK,UAAA;;;;YCxBN,WAAW,SAAS;;;;;IAK5B,oCAGM;MACA;MACA,SAAS;MACT,SAAS;MACT,QAAQ,aAAa,WAAW;;;;;;;;;;cAW7B,WAAS;;;;cCxBT;;cAGA,YAAU,UAAA;;;UCJN;;;;;EAKf;;;;;;;EAQA,kBACM;IACA,QAAQ;IACR,MAAM;IACN;IACA;;;;;;;EASN;;;;;;;EAQA,eACM;IACA,QAAQ;IACR,MAAM;IACN;IACA;;;;;;;;;EAWN;;;;;;;;EASA;;;;;;;;EASA;;;;;;;;;cC7DW,aAAW,UAAA;;;;;;;;iBCNR,4BAA4B;;;KCAhC;;;;;;EAMV;;;;;;;;cAqCW,WAAS,UAAA;;;cC5CT;;;;;;UAkBI;;;;;;;EAOf;;;;;EAKA;;;;;;cAOW,cAAY,UAAA;;;UCtCR;;;;;;EAMf;;;;;;EAOA;;;YAIU,SAAS;IACjB;;;;;MAKE,YAAY;;;;;MAKZ,YAAY;;;;;;;;;;;;;cAcL,UAAQ,UAAA"} |
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/placeholder/constants.ts","../../src/placeholder/types.ts","../../src/placeholder/placeholder.ts","../../src/placeholder/utils/preparePlaceholderAttribute.ts"],"mappings":";;;;;;cAIa;;cAGA,YAAU,UAAA;;;UCJN;;;;;EAKf;;;;;;;EAQA,kBACM;IACA,QAAQ;IACR,MAAM;IACN;IACA;;;;;;;EASN;;;;;;;EAQA,eACM;IACA,QAAQ;IACR,MAAM;IACN;IACA;;;;;;;;;EAWN;;;;;;;;EASA;;;;;;;;EASA;;;;;;;;;cC7DW,aAAW,UAAA;;;;;;;;iBCNR,4BAA4B"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/placeholder/constants.ts","../../src/placeholder/types.ts","../../src/placeholder/placeholder.ts","../../src/placeholder/utils/preparePlaceholderAttribute.ts"],"mappings":";;;;;;cAIa;;cAGA,YAAU,UAAA;;;UCJN;;;;;EAKf;;;;;;;EAQA,kBACM;IACA,QAAQ;IACR,MAAM;IACN;IACA;;;;;;;EASN;;;;;;;EAQA,eACM;IACA,QAAQ;IACR,MAAM;IACN;IACA;;;;;;;;;EAWN;;;;;;;;EASA;;;;;;;;EASA;;;;;;;;;cC7DW,aAAW,UAAA;;;;;;;;iBCNR,4BAA4B"} |
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/selection/selection.ts"],"mappings":";;KAKY;;;;;;EAMV;;;;;;;;cAqCW,WAAS,UAAA"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/selection/selection.ts"],"mappings":";;KAKY;;;;;;EAMV;;;;;;;;cAqCW,WAAS,UAAA"} |
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/trailing-node/trailing-node.ts"],"mappings":";;cAIa;;;;;;UAkBI;;;;;;;EAOf;;;;;EAKA;;;;;;cAOW,cAAY,UAAA"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/trailing-node/trailing-node.ts"],"mappings":";;cAIa;;;;;;UAkBI;;;;;;;EAOf;;;;;EAKA;;;;;;cAOW,cAAY,UAAA"} |
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/undo-redo/undo-redo.ts"],"mappings":";;UAGiB;;;;;;EAMf;;;;;;EAOA;;;YAIU,SAAS;IACjB;;;;;MAKE,YAAY;;;;;MAKZ,YAAY;;;;;;;;;;;;;cAcL,UAAQ,UAAA"} |
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/undo-redo/undo-redo.ts"],"mappings":";;UAGiB;;;;;;EAMf;;;;;;EAOA;;;YAIU,SAAS;IACjB;;;;;MAKE,YAAY;;;;;MAKZ,YAAY;;;;;;;;;;;;;cAcL,UAAQ,UAAA"} |
@@ -1,131 +0,87 @@ | ||
| "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/character-count/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| CharacterCount: () => CharacterCount | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_state = require("@tiptap/pm/state"); | ||
| //#region src/character-count/character-count.ts | ||
| /** | ||
| * This extension allows you to count the characters and words of your document. | ||
| * @see https://tiptap.dev/api/extensions/character-count | ||
| */ | ||
| const CharacterCount = _tiptap_core.Extension.create({ | ||
| name: "characterCount", | ||
| addOptions() { | ||
| return { | ||
| limit: null, | ||
| autoTrim: true, | ||
| mode: "textSize", | ||
| textCounter: (text) => text.length, | ||
| wordCounter: (text) => text.split(" ").filter((word) => word !== "").length | ||
| }; | ||
| }, | ||
| addStorage() { | ||
| return { | ||
| characters: () => 0, | ||
| words: () => 0 | ||
| }; | ||
| }, | ||
| onBeforeCreate() { | ||
| this.storage.characters = (options) => { | ||
| const node = (options === null || options === void 0 ? void 0 : options.node) || this.editor.state.doc; | ||
| if (((options === null || options === void 0 ? void 0 : options.mode) || this.options.mode) === "textSize") { | ||
| const text = node.textBetween(0, node.content.size, void 0, " "); | ||
| return this.options.textCounter(text); | ||
| } | ||
| return node.nodeSize; | ||
| }; | ||
| this.storage.words = (options) => { | ||
| const node = (options === null || options === void 0 ? void 0 : options.node) || this.editor.state.doc; | ||
| const text = node.textBetween(0, node.content.size, " ", " "); | ||
| return this.options.wordCounter(text); | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| let initialEvaluationDone = false; | ||
| return [new _tiptap_pm_state.Plugin({ | ||
| key: new _tiptap_pm_state.PluginKey("characterCount"), | ||
| appendTransaction: (transactions, oldState, newState) => { | ||
| if (initialEvaluationDone) return; | ||
| const limit = this.options.limit; | ||
| const autoTrim = this.options.autoTrim; | ||
| if (limit === null || limit === void 0 || limit === 0 || autoTrim === false) { | ||
| initialEvaluationDone = true; | ||
| return; | ||
| } | ||
| const initialContentSize = this.storage.characters({ node: newState.doc }); | ||
| if (initialContentSize > limit) { | ||
| const over = initialContentSize - limit; | ||
| const from = 0; | ||
| const to = over; | ||
| console.warn(`[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`); | ||
| const tr = newState.tr.deleteRange(from, to); | ||
| initialEvaluationDone = true; | ||
| return tr; | ||
| } | ||
| initialEvaluationDone = true; | ||
| }, | ||
| filterTransaction: (transaction, state) => { | ||
| const limit = this.options.limit; | ||
| if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) return true; | ||
| const oldSize = this.storage.characters({ node: state.doc }); | ||
| const newSize = this.storage.characters({ node: transaction.doc }); | ||
| if (newSize <= limit) return true; | ||
| if (oldSize > limit && newSize > limit && newSize <= oldSize) return true; | ||
| if (oldSize > limit && newSize > limit && newSize > oldSize) return false; | ||
| if (!transaction.getMeta("paste")) return false; | ||
| const pos = transaction.selection.$head.pos; | ||
| const from = pos - (newSize - limit); | ||
| const to = pos; | ||
| transaction.deleteRange(from, to); | ||
| if (this.storage.characters({ node: transaction.doc }) > limit) return false; | ||
| return true; | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| //#endregion | ||
| exports.CharacterCount = CharacterCount; | ||
| // src/character-count/character-count.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_state = require("@tiptap/pm/state"); | ||
| var CharacterCount = import_core.Extension.create({ | ||
| name: "characterCount", | ||
| addOptions() { | ||
| return { | ||
| limit: null, | ||
| autoTrim: true, | ||
| mode: "textSize", | ||
| textCounter: (text) => text.length, | ||
| wordCounter: (text) => text.split(" ").filter((word) => word !== "").length | ||
| }; | ||
| }, | ||
| addStorage() { | ||
| return { | ||
| characters: () => 0, | ||
| words: () => 0 | ||
| }; | ||
| }, | ||
| onBeforeCreate() { | ||
| this.storage.characters = (options) => { | ||
| const node = (options == null ? void 0 : options.node) || this.editor.state.doc; | ||
| const mode = (options == null ? void 0 : options.mode) || this.options.mode; | ||
| if (mode === "textSize") { | ||
| const text = node.textBetween(0, node.content.size, void 0, " "); | ||
| return this.options.textCounter(text); | ||
| } | ||
| return node.nodeSize; | ||
| }; | ||
| this.storage.words = (options) => { | ||
| const node = (options == null ? void 0 : options.node) || this.editor.state.doc; | ||
| const text = node.textBetween(0, node.content.size, " ", " "); | ||
| return this.options.wordCounter(text); | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| let initialEvaluationDone = false; | ||
| return [ | ||
| new import_state.Plugin({ | ||
| key: new import_state.PluginKey("characterCount"), | ||
| appendTransaction: (transactions, oldState, newState) => { | ||
| if (initialEvaluationDone) { | ||
| return; | ||
| } | ||
| const limit = this.options.limit; | ||
| const autoTrim = this.options.autoTrim; | ||
| if (limit === null || limit === void 0 || limit === 0 || autoTrim === false) { | ||
| initialEvaluationDone = true; | ||
| return; | ||
| } | ||
| const initialContentSize = this.storage.characters({ node: newState.doc }); | ||
| if (initialContentSize > limit) { | ||
| const over = initialContentSize - limit; | ||
| const from = 0; | ||
| const to = over; | ||
| console.warn( | ||
| `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.` | ||
| ); | ||
| const tr = newState.tr.deleteRange(from, to); | ||
| initialEvaluationDone = true; | ||
| return tr; | ||
| } | ||
| initialEvaluationDone = true; | ||
| }, | ||
| filterTransaction: (transaction, state) => { | ||
| const limit = this.options.limit; | ||
| if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) { | ||
| return true; | ||
| } | ||
| const oldSize = this.storage.characters({ node: state.doc }); | ||
| const newSize = this.storage.characters({ node: transaction.doc }); | ||
| if (newSize <= limit) { | ||
| return true; | ||
| } | ||
| if (oldSize > limit && newSize > limit && newSize <= oldSize) { | ||
| return true; | ||
| } | ||
| if (oldSize > limit && newSize > limit && newSize > oldSize) { | ||
| return false; | ||
| } | ||
| const isPaste = transaction.getMeta("paste"); | ||
| if (!isPaste) { | ||
| return false; | ||
| } | ||
| const pos = transaction.selection.$head.pos; | ||
| const over = newSize - limit; | ||
| const from = pos - over; | ||
| const to = pos; | ||
| transaction.deleteRange(from, to); | ||
| const updatedSize = this.storage.characters({ node: transaction.doc }); | ||
| if (updatedSize > limit) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| CharacterCount | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/character-count/index.ts","../../src/character-count/character-count.ts"],"sourcesContent":["export * from './character-count.js'\n","import { Extension } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport interface CharacterCountOptions {\n /**\n * The maximum number of characters that should be allowed. Defaults to `0`.\n * @default null\n * @example 180\n */\n limit: number | null | undefined\n /**\n * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n * If set to `nodeSize`, the nodeSize of the document is used.\n * @default 'textSize'\n * @example 'textSize'\n */\n mode: 'textSize' | 'nodeSize'\n /**\n * Sets whether the content will be automatically trimmed when programatically setting content over the limit.\n * If set to false, the user will be able to trim the text manually.\n * @default true\n * @example false\n */\n autoTrim?: boolean\n /**\n * The text counter function to use. Defaults to a simple character count.\n * @default (text) => text.length\n * @example (text) => [...new Intl.Segmenter().segment(text)].length\n */\n textCounter: (text: string) => number\n /**\n * The word counter function to use. Defaults to a simple word count.\n * @default (text) => text.split(' ').filter(word => word !== '').length\n * @example (text) => text.split(/\\s+/).filter(word => word !== '').length\n */\n wordCounter: (text: string) => number\n}\n\nexport interface CharacterCountStorage {\n /**\n * Get the number of characters for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the characters from. Defaults to the current document.\n * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n */\n characters: (options?: { node?: ProseMirrorNode; mode?: 'textSize' | 'nodeSize' }) => number\n\n /**\n * Get the number of words for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the words from. Defaults to the current document.\n */\n words: (options?: { node?: ProseMirrorNode }) => number\n}\n\ndeclare module '@tiptap/core' {\n interface Storage {\n characterCount: CharacterCountStorage\n }\n}\n\n/**\n * This extension allows you to count the characters and words of your document.\n * @see https://tiptap.dev/api/extensions/character-count\n */\nexport const CharacterCount = Extension.create<CharacterCountOptions, CharacterCountStorage>({\n name: 'characterCount',\n\n addOptions() {\n return {\n limit: null,\n autoTrim: true,\n mode: 'textSize',\n textCounter: text => text.length,\n wordCounter: text => text.split(' ').filter(word => word !== '').length,\n }\n },\n\n addStorage() {\n return {\n characters: () => 0,\n words: () => 0,\n }\n },\n\n onBeforeCreate() {\n this.storage.characters = options => {\n const node = options?.node || this.editor.state.doc\n const mode = options?.mode || this.options.mode\n\n if (mode === 'textSize') {\n const text = node.textBetween(0, node.content.size, undefined, ' ')\n\n return this.options.textCounter(text)\n }\n\n return node.nodeSize\n }\n\n this.storage.words = options => {\n const node = options?.node || this.editor.state.doc\n const text = node.textBetween(0, node.content.size, ' ', ' ')\n\n return this.options.wordCounter(text)\n }\n },\n\n addProseMirrorPlugins() {\n let initialEvaluationDone = false\n\n return [\n new Plugin({\n key: new PluginKey('characterCount'),\n appendTransaction: (transactions, oldState, newState) => {\n if (initialEvaluationDone) {\n return\n }\n\n const limit = this.options.limit\n const autoTrim = this.options.autoTrim\n\n if (limit === null || limit === undefined || limit === 0 || autoTrim === false) {\n initialEvaluationDone = true\n return\n }\n\n const initialContentSize = this.storage.characters({ node: newState.doc })\n\n if (initialContentSize > limit) {\n const over = initialContentSize - limit\n const from = 0\n const to = over\n\n console.warn(\n `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`,\n )\n const tr = newState.tr.deleteRange(from, to)\n initialEvaluationDone = true\n return tr\n }\n\n initialEvaluationDone = true\n },\n filterTransaction: (transaction, state) => {\n const limit = this.options.limit\n\n // Nothing has changed or no limit is defined. Ignore it.\n if (!transaction.docChanged || limit === 0 || limit === null || limit === undefined) {\n return true\n }\n\n const oldSize = this.storage.characters({ node: state.doc })\n const newSize = this.storage.characters({ node: transaction.doc })\n\n // Everything is in the limit. Good.\n if (newSize <= limit) {\n return true\n }\n\n // The limit has already been exceeded but will be reduced.\n if (oldSize > limit && newSize > limit && newSize <= oldSize) {\n return true\n }\n\n // The limit has already been exceeded and will be increased further.\n if (oldSize > limit && newSize > limit && newSize > oldSize) {\n return false\n }\n\n const isPaste = transaction.getMeta('paste')\n\n // Block all exceeding transactions that were not pasted.\n if (!isPaste) {\n return false\n }\n\n // For pasted content, we try to remove the exceeding content.\n const pos = transaction.selection.$head.pos\n const over = newSize - limit\n const from = pos - over\n const to = pos\n\n // It’s probably a bad idea to mutate transactions within `filterTransaction`\n // but for now this is working fine.\n transaction.deleteRange(from, to)\n\n // In some situations, the limit will continue to be exceeded after trimming.\n // This happens e.g. when truncating within a complex node (e.g. table)\n // and ProseMirror has to close this node again.\n // If this is the case, we prevent the transaction completely.\n const updatedSize = this.storage.characters({ node: transaction.doc })\n\n if (updatedSize > limit) {\n return false\n }\n\n return true\n },\n }),\n ]\n },\n})\n"],"mappings":"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","names":[]} | ||
| {"version":3,"file":"index.cjs","names":["Extension","Plugin","PluginKey"],"sources":["../../src/character-count/character-count.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport interface CharacterCountOptions {\n /**\n * The maximum number of characters that should be allowed. Defaults to `0`.\n * @default null\n * @example 180\n */\n limit: number | null | undefined\n /**\n * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n * If set to `nodeSize`, the nodeSize of the document is used.\n * @default 'textSize'\n * @example 'textSize'\n */\n mode: 'textSize' | 'nodeSize'\n /**\n * Sets whether the content will be automatically trimmed when programatically setting content over the limit.\n * If set to false, the user will be able to trim the text manually.\n * @default true\n * @example false\n */\n autoTrim?: boolean\n /**\n * The text counter function to use. Defaults to a simple character count.\n * @default (text) => text.length\n * @example (text) => [...new Intl.Segmenter().segment(text)].length\n */\n textCounter: (text: string) => number\n /**\n * The word counter function to use. Defaults to a simple word count.\n * @default (text) => text.split(' ').filter(word => word !== '').length\n * @example (text) => text.split(/\\s+/).filter(word => word !== '').length\n */\n wordCounter: (text: string) => number\n}\n\nexport interface CharacterCountStorage {\n /**\n * Get the number of characters for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the characters from. Defaults to the current document.\n * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n */\n characters: (options?: { node?: ProseMirrorNode; mode?: 'textSize' | 'nodeSize' }) => number\n\n /**\n * Get the number of words for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the words from. Defaults to the current document.\n */\n words: (options?: { node?: ProseMirrorNode }) => number\n}\n\ndeclare module '@tiptap/core' {\n interface Storage {\n characterCount: CharacterCountStorage\n }\n}\n\n/**\n * This extension allows you to count the characters and words of your document.\n * @see https://tiptap.dev/api/extensions/character-count\n */\nexport const CharacterCount = Extension.create<CharacterCountOptions, CharacterCountStorage>({\n name: 'characterCount',\n\n addOptions() {\n return {\n limit: null,\n autoTrim: true,\n mode: 'textSize',\n textCounter: text => text.length,\n wordCounter: text => text.split(' ').filter(word => word !== '').length,\n }\n },\n\n addStorage() {\n return {\n characters: () => 0,\n words: () => 0,\n }\n },\n\n onBeforeCreate() {\n this.storage.characters = options => {\n const node = options?.node || this.editor.state.doc\n const mode = options?.mode || this.options.mode\n\n if (mode === 'textSize') {\n const text = node.textBetween(0, node.content.size, undefined, ' ')\n\n return this.options.textCounter(text)\n }\n\n return node.nodeSize\n }\n\n this.storage.words = options => {\n const node = options?.node || this.editor.state.doc\n const text = node.textBetween(0, node.content.size, ' ', ' ')\n\n return this.options.wordCounter(text)\n }\n },\n\n addProseMirrorPlugins() {\n let initialEvaluationDone = false\n\n return [\n new Plugin({\n key: new PluginKey('characterCount'),\n appendTransaction: (transactions, oldState, newState) => {\n if (initialEvaluationDone) {\n return\n }\n\n const limit = this.options.limit\n const autoTrim = this.options.autoTrim\n\n if (limit === null || limit === undefined || limit === 0 || autoTrim === false) {\n initialEvaluationDone = true\n return\n }\n\n const initialContentSize = this.storage.characters({ node: newState.doc })\n\n if (initialContentSize > limit) {\n const over = initialContentSize - limit\n const from = 0\n const to = over\n\n console.warn(\n `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`,\n )\n const tr = newState.tr.deleteRange(from, to)\n initialEvaluationDone = true\n return tr\n }\n\n initialEvaluationDone = true\n },\n filterTransaction: (transaction, state) => {\n const limit = this.options.limit\n\n // Nothing has changed or no limit is defined. Ignore it.\n if (!transaction.docChanged || limit === 0 || limit === null || limit === undefined) {\n return true\n }\n\n const oldSize = this.storage.characters({ node: state.doc })\n const newSize = this.storage.characters({ node: transaction.doc })\n\n // Everything is in the limit. Good.\n if (newSize <= limit) {\n return true\n }\n\n // The limit has already been exceeded but will be reduced.\n if (oldSize > limit && newSize > limit && newSize <= oldSize) {\n return true\n }\n\n // The limit has already been exceeded and will be increased further.\n if (oldSize > limit && newSize > limit && newSize > oldSize) {\n return false\n }\n\n const isPaste = transaction.getMeta('paste')\n\n // Block all exceeding transactions that were not pasted.\n if (!isPaste) {\n return false\n }\n\n // For pasted content, we try to remove the exceeding content.\n const pos = transaction.selection.$head.pos\n const over = newSize - limit\n const from = pos - over\n const to = pos\n\n // It’s probably a bad idea to mutate transactions within `filterTransaction`\n // but for now this is working fine.\n transaction.deleteRange(from, to)\n\n // In some situations, the limit will continue to be exceeded after trimming.\n // This happens e.g. when truncating within a complex node (e.g. table)\n // and ProseMirror has to close this node again.\n // If this is the case, we prevent the transaction completely.\n const updatedSize = this.storage.characters({ node: transaction.doc })\n\n if (updatedSize > limit) {\n return false\n }\n\n return true\n },\n }),\n ]\n },\n})\n"],"mappings":";;;;;;;;AAkEA,MAAa,iBAAiBA,aAAAA,UAAU,OAAqD;CAC3F,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,UAAU;GACV,MAAM;GACN,cAAa,SAAQ,KAAK;GAC1B,cAAa,SAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,QAAO,SAAQ,SAAS,EAAE,CAAC,CAAC;EACnE;CACF;CAEA,aAAa;EACX,OAAO;GACL,kBAAkB;GAClB,aAAa;EACf;CACF;CAEA,iBAAiB;EACf,KAAK,QAAQ,cAAa,YAAW;GACnC,MAAM,QAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAO,QAAS,SAAQ,KAAK,OAAO,MAAM;GAGhD,MAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAFa,QAAS,SAAQ,KAAK,QAAQ,UAE9B,YAAY;IACvB,MAAM,OAAO,KAAK,YAAY,GAAG,KAAK,QAAQ,MAAM,KAAA,GAAW,GAAG;IAElE,OAAO,KAAK,QAAQ,YAAY,IAAI;GACtC;GAEA,OAAO,KAAK;EACd;EAEA,KAAK,QAAQ,SAAQ,YAAW;GAC9B,MAAM,QAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAO,QAAS,SAAQ,KAAK,OAAO,MAAM;GAChD,MAAM,OAAO,KAAK,YAAY,GAAG,KAAK,QAAQ,MAAM,KAAK,GAAG;GAE5D,OAAO,KAAK,QAAQ,YAAY,IAAI;EACtC;CACF;CAEA,wBAAwB;EACtB,IAAI,wBAAwB;EAE5B,OAAO,CACL,IAAIC,iBAAAA,OAAO;GACT,KAAK,IAAIC,iBAAAA,UAAU,gBAAgB;GACnC,oBAAoB,cAAc,UAAU,aAAa;IACvD,IAAI,uBACF;IAGF,MAAM,QAAQ,KAAK,QAAQ;IAC3B,MAAM,WAAW,KAAK,QAAQ;IAE9B,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,KAAK,aAAa,OAAO;KAC9E,wBAAwB;KACxB;IACF;IAEA,MAAM,qBAAqB,KAAK,QAAQ,WAAW,EAAE,MAAM,SAAS,IAAI,CAAC;IAEzE,IAAI,qBAAqB,OAAO;KAC9B,MAAM,OAAO,qBAAqB;KAClC,MAAM,OAAO;KACb,MAAM,KAAK;KAEX,QAAQ,KACN,sDAAsD,MAAM,gDAC9D;KACA,MAAM,KAAK,SAAS,GAAG,YAAY,MAAM,EAAE;KAC3C,wBAAwB;KACxB,OAAO;IACT;IAEA,wBAAwB;GAC1B;GACA,oBAAoB,aAAa,UAAU;IACzC,MAAM,QAAQ,KAAK,QAAQ;IAG3B,IAAI,CAAC,YAAY,cAAc,UAAU,KAAK,UAAU,QAAQ,UAAU,KAAA,GACxE,OAAO;IAGT,MAAM,UAAU,KAAK,QAAQ,WAAW,EAAE,MAAM,MAAM,IAAI,CAAC;IAC3D,MAAM,UAAU,KAAK,QAAQ,WAAW,EAAE,MAAM,YAAY,IAAI,CAAC;IAGjE,IAAI,WAAW,OACb,OAAO;IAIT,IAAI,UAAU,SAAS,UAAU,SAAS,WAAW,SACnD,OAAO;IAIT,IAAI,UAAU,SAAS,UAAU,SAAS,UAAU,SAClD,OAAO;IAMT,IAAI,CAHY,YAAY,QAAQ,OAGzB,GACT,OAAO;IAIT,MAAM,MAAM,YAAY,UAAU,MAAM;IAExC,MAAM,OAAO,OADA,UAAU;IAEvB,MAAM,KAAK;IAIX,YAAY,YAAY,MAAM,EAAE;IAQhC,IAFoB,KAAK,QAAQ,WAAW,EAAE,MAAM,YAAY,IAAI,CAEtD,IAAI,OAChB,OAAO;IAGT,OAAO;GACT;EACF,CAAC,CACH;CACF;AACF,CAAC"} |
@@ -1,62 +0,62 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Node } from '@tiptap/pm/model'; | ||
| import { Extension } from "@tiptap/core"; | ||
| import { Node } from "@tiptap/pm/model"; | ||
| //#region src/character-count/character-count.d.ts | ||
| interface CharacterCountOptions { | ||
| /** | ||
| * The maximum number of characters that should be allowed. Defaults to `0`. | ||
| * @default null | ||
| * @example 180 | ||
| */ | ||
| limit: number | null | undefined; | ||
| /** | ||
| * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| * If set to `nodeSize`, the nodeSize of the document is used. | ||
| * @default 'textSize' | ||
| * @example 'textSize' | ||
| */ | ||
| mode: 'textSize' | 'nodeSize'; | ||
| /** | ||
| * Sets whether the content will be automatically trimmed when programatically setting content over the limit. | ||
| * If set to false, the user will be able to trim the text manually. | ||
| * @default true | ||
| * @example false | ||
| */ | ||
| autoTrim?: boolean; | ||
| /** | ||
| * The text counter function to use. Defaults to a simple character count. | ||
| * @default (text) => text.length | ||
| * @example (text) => [...new Intl.Segmenter().segment(text)].length | ||
| */ | ||
| textCounter: (text: string) => number; | ||
| /** | ||
| * The word counter function to use. Defaults to a simple word count. | ||
| * @default (text) => text.split(' ').filter(word => word !== '').length | ||
| * @example (text) => text.split(/\s+/).filter(word => word !== '').length | ||
| */ | ||
| wordCounter: (text: string) => number; | ||
| /** | ||
| * The maximum number of characters that should be allowed. Defaults to `0`. | ||
| * @default null | ||
| * @example 180 | ||
| */ | ||
| limit: number | null | undefined; | ||
| /** | ||
| * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| * If set to `nodeSize`, the nodeSize of the document is used. | ||
| * @default 'textSize' | ||
| * @example 'textSize' | ||
| */ | ||
| mode: 'textSize' | 'nodeSize'; | ||
| /** | ||
| * Sets whether the content will be automatically trimmed when programatically setting content over the limit. | ||
| * If set to false, the user will be able to trim the text manually. | ||
| * @default true | ||
| * @example false | ||
| */ | ||
| autoTrim?: boolean; | ||
| /** | ||
| * The text counter function to use. Defaults to a simple character count. | ||
| * @default (text) => text.length | ||
| * @example (text) => [...new Intl.Segmenter().segment(text)].length | ||
| */ | ||
| textCounter: (text: string) => number; | ||
| /** | ||
| * The word counter function to use. Defaults to a simple word count. | ||
| * @default (text) => text.split(' ').filter(word => word !== '').length | ||
| * @example (text) => text.split(/\s+/).filter(word => word !== '').length | ||
| */ | ||
| wordCounter: (text: string) => number; | ||
| } | ||
| interface CharacterCountStorage { | ||
| /** | ||
| * Get the number of characters for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the characters from. Defaults to the current document. | ||
| * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| */ | ||
| characters: (options?: { | ||
| node?: Node; | ||
| mode?: 'textSize' | 'nodeSize'; | ||
| }) => number; | ||
| /** | ||
| * Get the number of words for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the words from. Defaults to the current document. | ||
| */ | ||
| words: (options?: { | ||
| node?: Node; | ||
| }) => number; | ||
| /** | ||
| * Get the number of characters for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the characters from. Defaults to the current document. | ||
| * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| */ | ||
| characters: (options?: { | ||
| node?: Node; | ||
| mode?: 'textSize' | 'nodeSize'; | ||
| }) => number; | ||
| /** | ||
| * Get the number of words for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the words from. Defaults to the current document. | ||
| */ | ||
| words: (options?: { | ||
| node?: Node; | ||
| }) => number; | ||
| } | ||
| declare module '@tiptap/core' { | ||
| interface Storage { | ||
| characterCount: CharacterCountStorage; | ||
| } | ||
| interface Storage { | ||
| characterCount: CharacterCountStorage; | ||
| } | ||
| } | ||
@@ -68,3 +68,4 @@ /** | ||
| declare const CharacterCount: Extension<CharacterCountOptions, CharacterCountStorage>; | ||
| export { CharacterCount, type CharacterCountOptions, type CharacterCountStorage }; | ||
| //#endregion | ||
| export { CharacterCount, CharacterCountOptions, CharacterCountStorage }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,62 +0,62 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Node } from '@tiptap/pm/model'; | ||
| import { Extension } from "@tiptap/core"; | ||
| import { Node } from "@tiptap/pm/model"; | ||
| //#region src/character-count/character-count.d.ts | ||
| interface CharacterCountOptions { | ||
| /** | ||
| * The maximum number of characters that should be allowed. Defaults to `0`. | ||
| * @default null | ||
| * @example 180 | ||
| */ | ||
| limit: number | null | undefined; | ||
| /** | ||
| * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| * If set to `nodeSize`, the nodeSize of the document is used. | ||
| * @default 'textSize' | ||
| * @example 'textSize' | ||
| */ | ||
| mode: 'textSize' | 'nodeSize'; | ||
| /** | ||
| * Sets whether the content will be automatically trimmed when programatically setting content over the limit. | ||
| * If set to false, the user will be able to trim the text manually. | ||
| * @default true | ||
| * @example false | ||
| */ | ||
| autoTrim?: boolean; | ||
| /** | ||
| * The text counter function to use. Defaults to a simple character count. | ||
| * @default (text) => text.length | ||
| * @example (text) => [...new Intl.Segmenter().segment(text)].length | ||
| */ | ||
| textCounter: (text: string) => number; | ||
| /** | ||
| * The word counter function to use. Defaults to a simple word count. | ||
| * @default (text) => text.split(' ').filter(word => word !== '').length | ||
| * @example (text) => text.split(/\s+/).filter(word => word !== '').length | ||
| */ | ||
| wordCounter: (text: string) => number; | ||
| /** | ||
| * The maximum number of characters that should be allowed. Defaults to `0`. | ||
| * @default null | ||
| * @example 180 | ||
| */ | ||
| limit: number | null | undefined; | ||
| /** | ||
| * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| * If set to `nodeSize`, the nodeSize of the document is used. | ||
| * @default 'textSize' | ||
| * @example 'textSize' | ||
| */ | ||
| mode: 'textSize' | 'nodeSize'; | ||
| /** | ||
| * Sets whether the content will be automatically trimmed when programatically setting content over the limit. | ||
| * If set to false, the user will be able to trim the text manually. | ||
| * @default true | ||
| * @example false | ||
| */ | ||
| autoTrim?: boolean; | ||
| /** | ||
| * The text counter function to use. Defaults to a simple character count. | ||
| * @default (text) => text.length | ||
| * @example (text) => [...new Intl.Segmenter().segment(text)].length | ||
| */ | ||
| textCounter: (text: string) => number; | ||
| /** | ||
| * The word counter function to use. Defaults to a simple word count. | ||
| * @default (text) => text.split(' ').filter(word => word !== '').length | ||
| * @example (text) => text.split(/\s+/).filter(word => word !== '').length | ||
| */ | ||
| wordCounter: (text: string) => number; | ||
| } | ||
| interface CharacterCountStorage { | ||
| /** | ||
| * Get the number of characters for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the characters from. Defaults to the current document. | ||
| * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| */ | ||
| characters: (options?: { | ||
| node?: Node; | ||
| mode?: 'textSize' | 'nodeSize'; | ||
| }) => number; | ||
| /** | ||
| * Get the number of words for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the words from. Defaults to the current document. | ||
| */ | ||
| words: (options?: { | ||
| node?: Node; | ||
| }) => number; | ||
| /** | ||
| * Get the number of characters for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the characters from. Defaults to the current document. | ||
| * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| */ | ||
| characters: (options?: { | ||
| node?: Node; | ||
| mode?: 'textSize' | 'nodeSize'; | ||
| }) => number; | ||
| /** | ||
| * Get the number of words for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the words from. Defaults to the current document. | ||
| */ | ||
| words: (options?: { | ||
| node?: Node; | ||
| }) => number; | ||
| } | ||
| declare module '@tiptap/core' { | ||
| interface Storage { | ||
| characterCount: CharacterCountStorage; | ||
| } | ||
| interface Storage { | ||
| characterCount: CharacterCountStorage; | ||
| } | ||
| } | ||
@@ -68,3 +68,4 @@ /** | ||
| declare const CharacterCount: Extension<CharacterCountOptions, CharacterCountStorage>; | ||
| export { CharacterCount, type CharacterCountOptions, type CharacterCountStorage }; | ||
| //#endregion | ||
| export { CharacterCount, CharacterCountOptions, CharacterCountStorage }; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,104 +0,86 @@ | ||
| // src/character-count/character-count.ts | ||
| import { Extension } from "@tiptap/core"; | ||
| import { Plugin, PluginKey } from "@tiptap/pm/state"; | ||
| var CharacterCount = Extension.create({ | ||
| name: "characterCount", | ||
| addOptions() { | ||
| return { | ||
| limit: null, | ||
| autoTrim: true, | ||
| mode: "textSize", | ||
| textCounter: (text) => text.length, | ||
| wordCounter: (text) => text.split(" ").filter((word) => word !== "").length | ||
| }; | ||
| }, | ||
| addStorage() { | ||
| return { | ||
| characters: () => 0, | ||
| words: () => 0 | ||
| }; | ||
| }, | ||
| onBeforeCreate() { | ||
| this.storage.characters = (options) => { | ||
| const node = (options == null ? void 0 : options.node) || this.editor.state.doc; | ||
| const mode = (options == null ? void 0 : options.mode) || this.options.mode; | ||
| if (mode === "textSize") { | ||
| const text = node.textBetween(0, node.content.size, void 0, " "); | ||
| return this.options.textCounter(text); | ||
| } | ||
| return node.nodeSize; | ||
| }; | ||
| this.storage.words = (options) => { | ||
| const node = (options == null ? void 0 : options.node) || this.editor.state.doc; | ||
| const text = node.textBetween(0, node.content.size, " ", " "); | ||
| return this.options.wordCounter(text); | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| let initialEvaluationDone = false; | ||
| return [ | ||
| new Plugin({ | ||
| key: new PluginKey("characterCount"), | ||
| appendTransaction: (transactions, oldState, newState) => { | ||
| if (initialEvaluationDone) { | ||
| return; | ||
| } | ||
| const limit = this.options.limit; | ||
| const autoTrim = this.options.autoTrim; | ||
| if (limit === null || limit === void 0 || limit === 0 || autoTrim === false) { | ||
| initialEvaluationDone = true; | ||
| return; | ||
| } | ||
| const initialContentSize = this.storage.characters({ node: newState.doc }); | ||
| if (initialContentSize > limit) { | ||
| const over = initialContentSize - limit; | ||
| const from = 0; | ||
| const to = over; | ||
| console.warn( | ||
| `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.` | ||
| ); | ||
| const tr = newState.tr.deleteRange(from, to); | ||
| initialEvaluationDone = true; | ||
| return tr; | ||
| } | ||
| initialEvaluationDone = true; | ||
| }, | ||
| filterTransaction: (transaction, state) => { | ||
| const limit = this.options.limit; | ||
| if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) { | ||
| return true; | ||
| } | ||
| const oldSize = this.storage.characters({ node: state.doc }); | ||
| const newSize = this.storage.characters({ node: transaction.doc }); | ||
| if (newSize <= limit) { | ||
| return true; | ||
| } | ||
| if (oldSize > limit && newSize > limit && newSize <= oldSize) { | ||
| return true; | ||
| } | ||
| if (oldSize > limit && newSize > limit && newSize > oldSize) { | ||
| return false; | ||
| } | ||
| const isPaste = transaction.getMeta("paste"); | ||
| if (!isPaste) { | ||
| return false; | ||
| } | ||
| const pos = transaction.selection.$head.pos; | ||
| const over = newSize - limit; | ||
| const from = pos - over; | ||
| const to = pos; | ||
| transaction.deleteRange(from, to); | ||
| const updatedSize = this.storage.characters({ node: transaction.doc }); | ||
| if (updatedSize > limit) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| //#region src/character-count/character-count.ts | ||
| /** | ||
| * This extension allows you to count the characters and words of your document. | ||
| * @see https://tiptap.dev/api/extensions/character-count | ||
| */ | ||
| const CharacterCount = Extension.create({ | ||
| name: "characterCount", | ||
| addOptions() { | ||
| return { | ||
| limit: null, | ||
| autoTrim: true, | ||
| mode: "textSize", | ||
| textCounter: (text) => text.length, | ||
| wordCounter: (text) => text.split(" ").filter((word) => word !== "").length | ||
| }; | ||
| }, | ||
| addStorage() { | ||
| return { | ||
| characters: () => 0, | ||
| words: () => 0 | ||
| }; | ||
| }, | ||
| onBeforeCreate() { | ||
| this.storage.characters = (options) => { | ||
| const node = (options === null || options === void 0 ? void 0 : options.node) || this.editor.state.doc; | ||
| if (((options === null || options === void 0 ? void 0 : options.mode) || this.options.mode) === "textSize") { | ||
| const text = node.textBetween(0, node.content.size, void 0, " "); | ||
| return this.options.textCounter(text); | ||
| } | ||
| return node.nodeSize; | ||
| }; | ||
| this.storage.words = (options) => { | ||
| const node = (options === null || options === void 0 ? void 0 : options.node) || this.editor.state.doc; | ||
| const text = node.textBetween(0, node.content.size, " ", " "); | ||
| return this.options.wordCounter(text); | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| let initialEvaluationDone = false; | ||
| return [new Plugin({ | ||
| key: new PluginKey("characterCount"), | ||
| appendTransaction: (transactions, oldState, newState) => { | ||
| if (initialEvaluationDone) return; | ||
| const limit = this.options.limit; | ||
| const autoTrim = this.options.autoTrim; | ||
| if (limit === null || limit === void 0 || limit === 0 || autoTrim === false) { | ||
| initialEvaluationDone = true; | ||
| return; | ||
| } | ||
| const initialContentSize = this.storage.characters({ node: newState.doc }); | ||
| if (initialContentSize > limit) { | ||
| const over = initialContentSize - limit; | ||
| const from = 0; | ||
| const to = over; | ||
| console.warn(`[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`); | ||
| const tr = newState.tr.deleteRange(from, to); | ||
| initialEvaluationDone = true; | ||
| return tr; | ||
| } | ||
| initialEvaluationDone = true; | ||
| }, | ||
| filterTransaction: (transaction, state) => { | ||
| const limit = this.options.limit; | ||
| if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) return true; | ||
| const oldSize = this.storage.characters({ node: state.doc }); | ||
| const newSize = this.storage.characters({ node: transaction.doc }); | ||
| if (newSize <= limit) return true; | ||
| if (oldSize > limit && newSize > limit && newSize <= oldSize) return true; | ||
| if (oldSize > limit && newSize > limit && newSize > oldSize) return false; | ||
| if (!transaction.getMeta("paste")) return false; | ||
| const pos = transaction.selection.$head.pos; | ||
| const from = pos - (newSize - limit); | ||
| const to = pos; | ||
| transaction.deleteRange(from, to); | ||
| if (this.storage.characters({ node: transaction.doc }) > limit) return false; | ||
| return true; | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| export { | ||
| CharacterCount | ||
| }; | ||
| //#endregion | ||
| export { CharacterCount }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/character-count/character-count.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport interface CharacterCountOptions {\n /**\n * The maximum number of characters that should be allowed. Defaults to `0`.\n * @default null\n * @example 180\n */\n limit: number | null | undefined\n /**\n * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n * If set to `nodeSize`, the nodeSize of the document is used.\n * @default 'textSize'\n * @example 'textSize'\n */\n mode: 'textSize' | 'nodeSize'\n /**\n * Sets whether the content will be automatically trimmed when programatically setting content over the limit.\n * If set to false, the user will be able to trim the text manually.\n * @default true\n * @example false\n */\n autoTrim?: boolean\n /**\n * The text counter function to use. Defaults to a simple character count.\n * @default (text) => text.length\n * @example (text) => [...new Intl.Segmenter().segment(text)].length\n */\n textCounter: (text: string) => number\n /**\n * The word counter function to use. Defaults to a simple word count.\n * @default (text) => text.split(' ').filter(word => word !== '').length\n * @example (text) => text.split(/\\s+/).filter(word => word !== '').length\n */\n wordCounter: (text: string) => number\n}\n\nexport interface CharacterCountStorage {\n /**\n * Get the number of characters for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the characters from. Defaults to the current document.\n * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n */\n characters: (options?: { node?: ProseMirrorNode; mode?: 'textSize' | 'nodeSize' }) => number\n\n /**\n * Get the number of words for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the words from. Defaults to the current document.\n */\n words: (options?: { node?: ProseMirrorNode }) => number\n}\n\ndeclare module '@tiptap/core' {\n interface Storage {\n characterCount: CharacterCountStorage\n }\n}\n\n/**\n * This extension allows you to count the characters and words of your document.\n * @see https://tiptap.dev/api/extensions/character-count\n */\nexport const CharacterCount = Extension.create<CharacterCountOptions, CharacterCountStorage>({\n name: 'characterCount',\n\n addOptions() {\n return {\n limit: null,\n autoTrim: true,\n mode: 'textSize',\n textCounter: text => text.length,\n wordCounter: text => text.split(' ').filter(word => word !== '').length,\n }\n },\n\n addStorage() {\n return {\n characters: () => 0,\n words: () => 0,\n }\n },\n\n onBeforeCreate() {\n this.storage.characters = options => {\n const node = options?.node || this.editor.state.doc\n const mode = options?.mode || this.options.mode\n\n if (mode === 'textSize') {\n const text = node.textBetween(0, node.content.size, undefined, ' ')\n\n return this.options.textCounter(text)\n }\n\n return node.nodeSize\n }\n\n this.storage.words = options => {\n const node = options?.node || this.editor.state.doc\n const text = node.textBetween(0, node.content.size, ' ', ' ')\n\n return this.options.wordCounter(text)\n }\n },\n\n addProseMirrorPlugins() {\n let initialEvaluationDone = false\n\n return [\n new Plugin({\n key: new PluginKey('characterCount'),\n appendTransaction: (transactions, oldState, newState) => {\n if (initialEvaluationDone) {\n return\n }\n\n const limit = this.options.limit\n const autoTrim = this.options.autoTrim\n\n if (limit === null || limit === undefined || limit === 0 || autoTrim === false) {\n initialEvaluationDone = true\n return\n }\n\n const initialContentSize = this.storage.characters({ node: newState.doc })\n\n if (initialContentSize > limit) {\n const over = initialContentSize - limit\n const from = 0\n const to = over\n\n console.warn(\n `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`,\n )\n const tr = newState.tr.deleteRange(from, to)\n initialEvaluationDone = true\n return tr\n }\n\n initialEvaluationDone = true\n },\n filterTransaction: (transaction, state) => {\n const limit = this.options.limit\n\n // Nothing has changed or no limit is defined. Ignore it.\n if (!transaction.docChanged || limit === 0 || limit === null || limit === undefined) {\n return true\n }\n\n const oldSize = this.storage.characters({ node: state.doc })\n const newSize = this.storage.characters({ node: transaction.doc })\n\n // Everything is in the limit. Good.\n if (newSize <= limit) {\n return true\n }\n\n // The limit has already been exceeded but will be reduced.\n if (oldSize > limit && newSize > limit && newSize <= oldSize) {\n return true\n }\n\n // The limit has already been exceeded and will be increased further.\n if (oldSize > limit && newSize > limit && newSize > oldSize) {\n return false\n }\n\n const isPaste = transaction.getMeta('paste')\n\n // Block all exceeding transactions that were not pasted.\n if (!isPaste) {\n return false\n }\n\n // For pasted content, we try to remove the exceeding content.\n const pos = transaction.selection.$head.pos\n const over = newSize - limit\n const from = pos - over\n const to = pos\n\n // It’s probably a bad idea to mutate transactions within `filterTransaction`\n // but for now this is working fine.\n transaction.deleteRange(from, to)\n\n // In some situations, the limit will continue to be exceeded after trimming.\n // This happens e.g. when truncating within a complex node (e.g. table)\n // and ProseMirror has to close this node again.\n // If this is the case, we prevent the transaction completely.\n const updatedSize = this.storage.characters({ node: transaction.doc })\n\n if (updatedSize > limit) {\n return false\n }\n\n return true\n },\n }),\n ]\n },\n})\n"],"mappings":"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","names":[]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/character-count/character-count.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport interface CharacterCountOptions {\n /**\n * The maximum number of characters that should be allowed. Defaults to `0`.\n * @default null\n * @example 180\n */\n limit: number | null | undefined\n /**\n * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n * If set to `nodeSize`, the nodeSize of the document is used.\n * @default 'textSize'\n * @example 'textSize'\n */\n mode: 'textSize' | 'nodeSize'\n /**\n * Sets whether the content will be automatically trimmed when programatically setting content over the limit.\n * If set to false, the user will be able to trim the text manually.\n * @default true\n * @example false\n */\n autoTrim?: boolean\n /**\n * The text counter function to use. Defaults to a simple character count.\n * @default (text) => text.length\n * @example (text) => [...new Intl.Segmenter().segment(text)].length\n */\n textCounter: (text: string) => number\n /**\n * The word counter function to use. Defaults to a simple word count.\n * @default (text) => text.split(' ').filter(word => word !== '').length\n * @example (text) => text.split(/\\s+/).filter(word => word !== '').length\n */\n wordCounter: (text: string) => number\n}\n\nexport interface CharacterCountStorage {\n /**\n * Get the number of characters for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the characters from. Defaults to the current document.\n * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n */\n characters: (options?: { node?: ProseMirrorNode; mode?: 'textSize' | 'nodeSize' }) => number\n\n /**\n * Get the number of words for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the words from. Defaults to the current document.\n */\n words: (options?: { node?: ProseMirrorNode }) => number\n}\n\ndeclare module '@tiptap/core' {\n interface Storage {\n characterCount: CharacterCountStorage\n }\n}\n\n/**\n * This extension allows you to count the characters and words of your document.\n * @see https://tiptap.dev/api/extensions/character-count\n */\nexport const CharacterCount = Extension.create<CharacterCountOptions, CharacterCountStorage>({\n name: 'characterCount',\n\n addOptions() {\n return {\n limit: null,\n autoTrim: true,\n mode: 'textSize',\n textCounter: text => text.length,\n wordCounter: text => text.split(' ').filter(word => word !== '').length,\n }\n },\n\n addStorage() {\n return {\n characters: () => 0,\n words: () => 0,\n }\n },\n\n onBeforeCreate() {\n this.storage.characters = options => {\n const node = options?.node || this.editor.state.doc\n const mode = options?.mode || this.options.mode\n\n if (mode === 'textSize') {\n const text = node.textBetween(0, node.content.size, undefined, ' ')\n\n return this.options.textCounter(text)\n }\n\n return node.nodeSize\n }\n\n this.storage.words = options => {\n const node = options?.node || this.editor.state.doc\n const text = node.textBetween(0, node.content.size, ' ', ' ')\n\n return this.options.wordCounter(text)\n }\n },\n\n addProseMirrorPlugins() {\n let initialEvaluationDone = false\n\n return [\n new Plugin({\n key: new PluginKey('characterCount'),\n appendTransaction: (transactions, oldState, newState) => {\n if (initialEvaluationDone) {\n return\n }\n\n const limit = this.options.limit\n const autoTrim = this.options.autoTrim\n\n if (limit === null || limit === undefined || limit === 0 || autoTrim === false) {\n initialEvaluationDone = true\n return\n }\n\n const initialContentSize = this.storage.characters({ node: newState.doc })\n\n if (initialContentSize > limit) {\n const over = initialContentSize - limit\n const from = 0\n const to = over\n\n console.warn(\n `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`,\n )\n const tr = newState.tr.deleteRange(from, to)\n initialEvaluationDone = true\n return tr\n }\n\n initialEvaluationDone = true\n },\n filterTransaction: (transaction, state) => {\n const limit = this.options.limit\n\n // Nothing has changed or no limit is defined. Ignore it.\n if (!transaction.docChanged || limit === 0 || limit === null || limit === undefined) {\n return true\n }\n\n const oldSize = this.storage.characters({ node: state.doc })\n const newSize = this.storage.characters({ node: transaction.doc })\n\n // Everything is in the limit. Good.\n if (newSize <= limit) {\n return true\n }\n\n // The limit has already been exceeded but will be reduced.\n if (oldSize > limit && newSize > limit && newSize <= oldSize) {\n return true\n }\n\n // The limit has already been exceeded and will be increased further.\n if (oldSize > limit && newSize > limit && newSize > oldSize) {\n return false\n }\n\n const isPaste = transaction.getMeta('paste')\n\n // Block all exceeding transactions that were not pasted.\n if (!isPaste) {\n return false\n }\n\n // For pasted content, we try to remove the exceeding content.\n const pos = transaction.selection.$head.pos\n const over = newSize - limit\n const from = pos - over\n const to = pos\n\n // It’s probably a bad idea to mutate transactions within `filterTransaction`\n // but for now this is working fine.\n transaction.deleteRange(from, to)\n\n // In some situations, the limit will continue to be exceeded after trimming.\n // This happens e.g. when truncating within a complex node (e.g. table)\n // and ProseMirror has to close this node again.\n // If this is the case, we prevent the transaction completely.\n const updatedSize = this.storage.characters({ node: transaction.doc })\n\n if (updatedSize > limit) {\n return false\n }\n\n return true\n },\n }),\n ]\n },\n})\n"],"mappings":";;;;;;;AAkEA,MAAa,iBAAiB,UAAU,OAAqD;CAC3F,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,UAAU;GACV,MAAM;GACN,cAAa,SAAQ,KAAK;GAC1B,cAAa,SAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,QAAO,SAAQ,SAAS,EAAE,CAAC,CAAC;EACnE;CACF;CAEA,aAAa;EACX,OAAO;GACL,kBAAkB;GAClB,aAAa;EACf;CACF;CAEA,iBAAiB;EACf,KAAK,QAAQ,cAAa,YAAW;GACnC,MAAM,QAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAO,QAAS,SAAQ,KAAK,OAAO,MAAM;GAGhD,MAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAFa,QAAS,SAAQ,KAAK,QAAQ,UAE9B,YAAY;IACvB,MAAM,OAAO,KAAK,YAAY,GAAG,KAAK,QAAQ,MAAM,KAAA,GAAW,GAAG;IAElE,OAAO,KAAK,QAAQ,YAAY,IAAI;GACtC;GAEA,OAAO,KAAK;EACd;EAEA,KAAK,QAAQ,SAAQ,YAAW;GAC9B,MAAM,QAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAO,QAAS,SAAQ,KAAK,OAAO,MAAM;GAChD,MAAM,OAAO,KAAK,YAAY,GAAG,KAAK,QAAQ,MAAM,KAAK,GAAG;GAE5D,OAAO,KAAK,QAAQ,YAAY,IAAI;EACtC;CACF;CAEA,wBAAwB;EACtB,IAAI,wBAAwB;EAE5B,OAAO,CACL,IAAI,OAAO;GACT,KAAK,IAAI,UAAU,gBAAgB;GACnC,oBAAoB,cAAc,UAAU,aAAa;IACvD,IAAI,uBACF;IAGF,MAAM,QAAQ,KAAK,QAAQ;IAC3B,MAAM,WAAW,KAAK,QAAQ;IAE9B,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,KAAK,aAAa,OAAO;KAC9E,wBAAwB;KACxB;IACF;IAEA,MAAM,qBAAqB,KAAK,QAAQ,WAAW,EAAE,MAAM,SAAS,IAAI,CAAC;IAEzE,IAAI,qBAAqB,OAAO;KAC9B,MAAM,OAAO,qBAAqB;KAClC,MAAM,OAAO;KACb,MAAM,KAAK;KAEX,QAAQ,KACN,sDAAsD,MAAM,gDAC9D;KACA,MAAM,KAAK,SAAS,GAAG,YAAY,MAAM,EAAE;KAC3C,wBAAwB;KACxB,OAAO;IACT;IAEA,wBAAwB;GAC1B;GACA,oBAAoB,aAAa,UAAU;IACzC,MAAM,QAAQ,KAAK,QAAQ;IAG3B,IAAI,CAAC,YAAY,cAAc,UAAU,KAAK,UAAU,QAAQ,UAAU,KAAA,GACxE,OAAO;IAGT,MAAM,UAAU,KAAK,QAAQ,WAAW,EAAE,MAAM,MAAM,IAAI,CAAC;IAC3D,MAAM,UAAU,KAAK,QAAQ,WAAW,EAAE,MAAM,YAAY,IAAI,CAAC;IAGjE,IAAI,WAAW,OACb,OAAO;IAIT,IAAI,UAAU,SAAS,UAAU,SAAS,WAAW,SACnD,OAAO;IAIT,IAAI,UAAU,SAAS,UAAU,SAAS,UAAU,SAClD,OAAO;IAMT,IAAI,CAHY,YAAY,QAAQ,OAGzB,GACT,OAAO;IAIT,MAAM,MAAM,YAAY,UAAU,MAAM;IAExC,MAAM,OAAO,OADA,UAAU;IAEvB,MAAM,KAAK;IAIX,YAAY,YAAY,MAAM,EAAE;IAQhC,IAFoB,KAAK,QAAQ,WAAW,EAAE,MAAM,YAAY,IAAI,CAEtD,IAAI,OAChB,OAAO;IAGT,OAAO;GACT;EACF,CAAC,CACH;CACF;AACF,CAAC"} |
@@ -1,47 +0,27 @@ | ||
| "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/drop-cursor/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| Dropcursor: () => Dropcursor | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_dropcursor = require("@tiptap/pm/dropcursor"); | ||
| //#region src/drop-cursor/drop-cursor.ts | ||
| /** | ||
| * This extension allows you to add a drop cursor to your editor. | ||
| * A drop cursor is a line that appears when you drag and drop content | ||
| * in-between nodes. | ||
| * @see https://tiptap.dev/api/extensions/dropcursor | ||
| */ | ||
| const Dropcursor = _tiptap_core.Extension.create({ | ||
| name: "dropCursor", | ||
| addOptions() { | ||
| return { | ||
| color: "currentColor", | ||
| width: 1, | ||
| class: void 0 | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [(0, _tiptap_pm_dropcursor.dropCursor)(this.options)]; | ||
| } | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| //#endregion | ||
| exports.Dropcursor = Dropcursor; | ||
| // src/drop-cursor/drop-cursor.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_dropcursor = require("@tiptap/pm/dropcursor"); | ||
| var Dropcursor = import_core.Extension.create({ | ||
| name: "dropCursor", | ||
| addOptions() { | ||
| return { | ||
| color: "currentColor", | ||
| width: 1, | ||
| class: void 0 | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [(0, import_dropcursor.dropCursor)(this.options)]; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| Dropcursor | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/drop-cursor/index.ts","../../src/drop-cursor/drop-cursor.ts"],"sourcesContent":["export * from './drop-cursor.js'\n","import { Extension } from '@tiptap/core'\nimport { dropCursor } from '@tiptap/pm/dropcursor'\n\nexport interface DropcursorOptions {\n /**\n * The color of the drop cursor. Use `false` to apply no color and rely only on class.\n * @default 'currentColor'\n * @example 'red'\n */\n color?: string | false\n\n /**\n * The width of the drop cursor\n * @default 1\n * @example 2\n */\n width: number | undefined\n\n /**\n * The class of the drop cursor\n * @default undefined\n * @example 'drop-cursor'\n */\n class: string | undefined\n}\n\n/**\n * This extension allows you to add a drop cursor to your editor.\n * A drop cursor is a line that appears when you drag and drop content\n * in-between nodes.\n * @see https://tiptap.dev/api/extensions/dropcursor\n */\nexport const Dropcursor = Extension.create<DropcursorOptions>({\n name: 'dropCursor',\n\n addOptions() {\n return {\n color: 'currentColor',\n width: 1,\n class: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n return [dropCursor(this.options)]\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA0B;AAC1B,wBAA2B;AA+BpB,IAAM,aAAa,sBAAU,OAA0B;AAAA,EAC5D,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO,KAAC,8BAAW,KAAK,OAAO,CAAC;AAAA,EAClC;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.cjs","names":["Extension"],"sources":["../../src/drop-cursor/drop-cursor.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { dropCursor } from '@tiptap/pm/dropcursor'\n\nexport interface DropcursorOptions {\n /**\n * The color of the drop cursor. Use `false` to apply no color and rely only on class.\n * @default 'currentColor'\n * @example 'red'\n */\n color?: string | false\n\n /**\n * The width of the drop cursor\n * @default 1\n * @example 2\n */\n width: number | undefined\n\n /**\n * The class of the drop cursor\n * @default undefined\n * @example 'drop-cursor'\n */\n class: string | undefined\n}\n\n/**\n * This extension allows you to add a drop cursor to your editor.\n * A drop cursor is a line that appears when you drag and drop content\n * in-between nodes.\n * @see https://tiptap.dev/api/extensions/dropcursor\n */\nexport const Dropcursor = Extension.create<DropcursorOptions>({\n name: 'dropCursor',\n\n addOptions() {\n return {\n color: 'currentColor',\n width: 1,\n class: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n return [dropCursor(this.options)]\n },\n})\n"],"mappings":";;;;;;;;;;AAgCA,MAAa,aAAaA,aAAAA,UAAU,OAA0B;CAC5D,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,OAAO;GACP,OAAO,KAAA;EACT;CACF;CAEA,wBAAwB;EACtB,OAAO,EAAA,GAAA,sBAAA,WAAA,CAAY,KAAK,OAAO,CAAC;CAClC;AACF,CAAC"} |
@@ -1,22 +0,22 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/drop-cursor/drop-cursor.d.ts | ||
| interface DropcursorOptions { | ||
| /** | ||
| * The color of the drop cursor. Use `false` to apply no color and rely only on class. | ||
| * @default 'currentColor' | ||
| * @example 'red' | ||
| */ | ||
| color?: string | false; | ||
| /** | ||
| * The width of the drop cursor | ||
| * @default 1 | ||
| * @example 2 | ||
| */ | ||
| width: number | undefined; | ||
| /** | ||
| * The class of the drop cursor | ||
| * @default undefined | ||
| * @example 'drop-cursor' | ||
| */ | ||
| class: string | undefined; | ||
| /** | ||
| * The color of the drop cursor. Use `false` to apply no color and rely only on class. | ||
| * @default 'currentColor' | ||
| * @example 'red' | ||
| */ | ||
| color?: string | false; | ||
| /** | ||
| * The width of the drop cursor | ||
| * @default 1 | ||
| * @example 2 | ||
| */ | ||
| width: number | undefined; | ||
| /** | ||
| * The class of the drop cursor | ||
| * @default undefined | ||
| * @example 'drop-cursor' | ||
| */ | ||
| class: string | undefined; | ||
| } | ||
@@ -30,3 +30,4 @@ /** | ||
| declare const Dropcursor: Extension<DropcursorOptions, any>; | ||
| export { Dropcursor, type DropcursorOptions }; | ||
| //#endregion | ||
| export { Dropcursor, DropcursorOptions }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,22 +0,22 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/drop-cursor/drop-cursor.d.ts | ||
| interface DropcursorOptions { | ||
| /** | ||
| * The color of the drop cursor. Use `false` to apply no color and rely only on class. | ||
| * @default 'currentColor' | ||
| * @example 'red' | ||
| */ | ||
| color?: string | false; | ||
| /** | ||
| * The width of the drop cursor | ||
| * @default 1 | ||
| * @example 2 | ||
| */ | ||
| width: number | undefined; | ||
| /** | ||
| * The class of the drop cursor | ||
| * @default undefined | ||
| * @example 'drop-cursor' | ||
| */ | ||
| class: string | undefined; | ||
| /** | ||
| * The color of the drop cursor. Use `false` to apply no color and rely only on class. | ||
| * @default 'currentColor' | ||
| * @example 'red' | ||
| */ | ||
| color?: string | false; | ||
| /** | ||
| * The width of the drop cursor | ||
| * @default 1 | ||
| * @example 2 | ||
| */ | ||
| width: number | undefined; | ||
| /** | ||
| * The class of the drop cursor | ||
| * @default undefined | ||
| * @example 'drop-cursor' | ||
| */ | ||
| class: string | undefined; | ||
| } | ||
@@ -30,3 +30,4 @@ /** | ||
| declare const Dropcursor: Extension<DropcursorOptions, any>; | ||
| export { Dropcursor, type DropcursorOptions }; | ||
| //#endregion | ||
| export { Dropcursor, DropcursorOptions }; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,20 +0,26 @@ | ||
| // src/drop-cursor/drop-cursor.ts | ||
| import { Extension } from "@tiptap/core"; | ||
| import { dropCursor } from "@tiptap/pm/dropcursor"; | ||
| var Dropcursor = Extension.create({ | ||
| name: "dropCursor", | ||
| addOptions() { | ||
| return { | ||
| color: "currentColor", | ||
| width: 1, | ||
| class: void 0 | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [dropCursor(this.options)]; | ||
| } | ||
| //#region src/drop-cursor/drop-cursor.ts | ||
| /** | ||
| * This extension allows you to add a drop cursor to your editor. | ||
| * A drop cursor is a line that appears when you drag and drop content | ||
| * in-between nodes. | ||
| * @see https://tiptap.dev/api/extensions/dropcursor | ||
| */ | ||
| const Dropcursor = Extension.create({ | ||
| name: "dropCursor", | ||
| addOptions() { | ||
| return { | ||
| color: "currentColor", | ||
| width: 1, | ||
| class: void 0 | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [dropCursor(this.options)]; | ||
| } | ||
| }); | ||
| export { | ||
| Dropcursor | ||
| }; | ||
| //#endregion | ||
| export { Dropcursor }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/drop-cursor/drop-cursor.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { dropCursor } from '@tiptap/pm/dropcursor'\n\nexport interface DropcursorOptions {\n /**\n * The color of the drop cursor. Use `false` to apply no color and rely only on class.\n * @default 'currentColor'\n * @example 'red'\n */\n color?: string | false\n\n /**\n * The width of the drop cursor\n * @default 1\n * @example 2\n */\n width: number | undefined\n\n /**\n * The class of the drop cursor\n * @default undefined\n * @example 'drop-cursor'\n */\n class: string | undefined\n}\n\n/**\n * This extension allows you to add a drop cursor to your editor.\n * A drop cursor is a line that appears when you drag and drop content\n * in-between nodes.\n * @see https://tiptap.dev/api/extensions/dropcursor\n */\nexport const Dropcursor = Extension.create<DropcursorOptions>({\n name: 'dropCursor',\n\n addOptions() {\n return {\n color: 'currentColor',\n width: 1,\n class: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n return [dropCursor(this.options)]\n },\n})\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AA+BpB,IAAM,aAAa,UAAU,OAA0B;AAAA,EAC5D,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO,CAAC,WAAW,KAAK,OAAO,CAAC;AAAA,EAClC;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/drop-cursor/drop-cursor.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { dropCursor } from '@tiptap/pm/dropcursor'\n\nexport interface DropcursorOptions {\n /**\n * The color of the drop cursor. Use `false` to apply no color and rely only on class.\n * @default 'currentColor'\n * @example 'red'\n */\n color?: string | false\n\n /**\n * The width of the drop cursor\n * @default 1\n * @example 2\n */\n width: number | undefined\n\n /**\n * The class of the drop cursor\n * @default undefined\n * @example 'drop-cursor'\n */\n class: string | undefined\n}\n\n/**\n * This extension allows you to add a drop cursor to your editor.\n * A drop cursor is a line that appears when you drag and drop content\n * in-between nodes.\n * @see https://tiptap.dev/api/extensions/dropcursor\n */\nexport const Dropcursor = Extension.create<DropcursorOptions>({\n name: 'dropCursor',\n\n addOptions() {\n return {\n color: 'currentColor',\n width: 1,\n class: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n return [dropCursor(this.options)]\n },\n})\n"],"mappings":";;;;;;;;;AAgCA,MAAa,aAAa,UAAU,OAA0B;CAC5D,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,OAAO;GACP,OAAO,KAAA;EACT;CACF;CAEA,wBAAwB;EACtB,OAAO,CAAC,WAAW,KAAK,OAAO,CAAC;CAClC;AACF,CAAC"} |
+45
-92
@@ -1,95 +0,48 @@ | ||
| "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/focus/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| Focus: () => Focus | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_state = require("@tiptap/pm/state"); | ||
| let _tiptap_pm_view = require("@tiptap/pm/view"); | ||
| //#region src/focus/focus.ts | ||
| /** | ||
| * This extension allows you to add a class to the focused node. | ||
| * @see https://www.tiptap.dev/api/extensions/focus | ||
| */ | ||
| const Focus = _tiptap_core.Extension.create({ | ||
| name: "focus", | ||
| addOptions() { | ||
| return { | ||
| className: "has-focus", | ||
| mode: "all" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [new _tiptap_pm_state.Plugin({ | ||
| key: new _tiptap_pm_state.PluginKey("focus"), | ||
| props: { decorations: ({ doc, selection }) => { | ||
| const { isEditable, isFocused } = this.editor; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| if (!isEditable || !isFocused) return _tiptap_pm_view.DecorationSet.create(doc, []); | ||
| let maxLevels = 0; | ||
| if (this.options.mode === "deepest") doc.descendants((node, pos) => { | ||
| if (node.isText) return; | ||
| if (!(anchor >= pos && anchor <= pos + node.nodeSize - 1)) return false; | ||
| maxLevels += 1; | ||
| }); | ||
| let currentLevel = 0; | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) return false; | ||
| if (!(anchor >= pos && anchor <= pos + node.nodeSize - 1)) return false; | ||
| currentLevel += 1; | ||
| if (this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1) return this.options.mode === "deepest"; | ||
| decorations.push(_tiptap_pm_view.Decoration.node(pos, pos + node.nodeSize, { class: this.options.className })); | ||
| }); | ||
| return _tiptap_pm_view.DecorationSet.create(doc, decorations); | ||
| } } | ||
| })]; | ||
| } | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| //#endregion | ||
| exports.Focus = Focus; | ||
| // src/focus/focus.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_state = require("@tiptap/pm/state"); | ||
| var import_view = require("@tiptap/pm/view"); | ||
| var Focus = import_core.Extension.create({ | ||
| name: "focus", | ||
| addOptions() { | ||
| return { | ||
| className: "has-focus", | ||
| mode: "all" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [ | ||
| new import_state.Plugin({ | ||
| key: new import_state.PluginKey("focus"), | ||
| props: { | ||
| decorations: ({ doc, selection }) => { | ||
| const { isEditable, isFocused } = this.editor; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| if (!isEditable || !isFocused) { | ||
| return import_view.DecorationSet.create(doc, []); | ||
| } | ||
| let maxLevels = 0; | ||
| if (this.options.mode === "deepest") { | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) { | ||
| return; | ||
| } | ||
| const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; | ||
| if (!isCurrent) { | ||
| return false; | ||
| } | ||
| maxLevels += 1; | ||
| }); | ||
| } | ||
| let currentLevel = 0; | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) { | ||
| return false; | ||
| } | ||
| const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; | ||
| if (!isCurrent) { | ||
| return false; | ||
| } | ||
| currentLevel += 1; | ||
| const outOfScope = this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1; | ||
| if (outOfScope) { | ||
| return this.options.mode === "deepest"; | ||
| } | ||
| decorations.push( | ||
| import_view.Decoration.node(pos, pos + node.nodeSize, { | ||
| class: this.options.className | ||
| }) | ||
| ); | ||
| }); | ||
| return import_view.DecorationSet.create(doc, decorations); | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| Focus | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/focus/index.ts","../../src/focus/focus.ts"],"sourcesContent":["export * from './focus.js'\n","import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport interface FocusOptions {\n /**\n * The class name that should be added to the focused node.\n * @default 'has-focus'\n * @example 'is-focused'\n */\n className: string\n\n /**\n * The mode by which the focused node is determined.\n * - All: All nodes are marked as focused.\n * - Deepest: Only the deepest node is marked as focused.\n * - Shallowest: Only the shallowest node is marked as focused.\n *\n * @default 'all'\n * @example 'deepest'\n * @example 'shallowest'\n */\n mode: 'all' | 'deepest' | 'shallowest'\n}\n\n/**\n * This extension allows you to add a class to the focused node.\n * @see https://www.tiptap.dev/api/extensions/focus\n */\nexport const Focus = Extension.create<FocusOptions>({\n name: 'focus',\n\n addOptions() {\n return {\n className: 'has-focus',\n mode: 'all',\n }\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('focus'),\n props: {\n decorations: ({ doc, selection }) => {\n const { isEditable, isFocused } = this.editor\n const { anchor } = selection\n const decorations: Decoration[] = []\n\n if (!isEditable || !isFocused) {\n return DecorationSet.create(doc, [])\n }\n\n // Maximum Levels\n let maxLevels = 0\n\n if (this.options.mode === 'deepest') {\n doc.descendants((node, pos) => {\n if (node.isText) {\n return\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n maxLevels += 1\n })\n }\n\n // Loop through current\n let currentLevel = 0\n\n doc.descendants((node, pos) => {\n if (node.isText) {\n return false\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n currentLevel += 1\n\n const outOfScope =\n (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) ||\n (this.options.mode === 'shallowest' && currentLevel > 1)\n\n if (outOfScope) {\n return this.options.mode === 'deepest'\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: this.options.className,\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n },\n },\n }),\n ]\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA0B;AAC1B,mBAAkC;AAClC,kBAA0C;AA2BnC,IAAM,QAAQ,sBAAU,OAAqB;AAAA,EAClD,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,WAAW;AAAA,MACX,MAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO;AAAA,MACL,IAAI,oBAAO;AAAA,QACT,KAAK,IAAI,uBAAU,OAAO;AAAA,QAC1B,OAAO;AAAA,UACL,aAAa,CAAC,EAAE,KAAK,UAAU,MAAM;AACnC,kBAAM,EAAE,YAAY,UAAU,IAAI,KAAK;AACvC,kBAAM,EAAE,OAAO,IAAI;AACnB,kBAAM,cAA4B,CAAC;AAEnC,gBAAI,CAAC,cAAc,CAAC,WAAW;AAC7B,qBAAO,0BAAc,OAAO,KAAK,CAAC,CAAC;AAAA,YACrC;AAGA,gBAAI,YAAY;AAEhB,gBAAI,KAAK,QAAQ,SAAS,WAAW;AACnC,kBAAI,YAAY,CAAC,MAAM,QAAQ;AAC7B,oBAAI,KAAK,QAAQ;AACf;AAAA,gBACF;AAEA,sBAAM,YAAY,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW;AAEnE,oBAAI,CAAC,WAAW;AACd,yBAAO;AAAA,gBACT;AAEA,6BAAa;AAAA,cACf,CAAC;AAAA,YACH;AAGA,gBAAI,eAAe;AAEnB,gBAAI,YAAY,CAAC,MAAM,QAAQ;AAC7B,kBAAI,KAAK,QAAQ;AACf,uBAAO;AAAA,cACT;AAEA,oBAAM,YAAY,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW;AAEnE,kBAAI,CAAC,WAAW;AACd,uBAAO;AAAA,cACT;AAEA,8BAAgB;AAEhB,oBAAM,aACH,KAAK,QAAQ,SAAS,aAAa,YAAY,eAAe,KAC9D,KAAK,QAAQ,SAAS,gBAAgB,eAAe;AAExD,kBAAI,YAAY;AACd,uBAAO,KAAK,QAAQ,SAAS;AAAA,cAC/B;AAEA,0BAAY;AAAA,gBACV,uBAAW,KAAK,KAAK,MAAM,KAAK,UAAU;AAAA,kBACxC,OAAO,KAAK,QAAQ;AAAA,gBACtB,CAAC;AAAA,cACH;AAAA,YACF,CAAC;AAED,mBAAO,0BAAc,OAAO,KAAK,WAAW;AAAA,UAC9C;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.cjs","names":["Extension","Plugin","PluginKey","DecorationSet","Decoration"],"sources":["../../src/focus/focus.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport interface FocusOptions {\n /**\n * The class name that should be added to the focused node.\n * @default 'has-focus'\n * @example 'is-focused'\n */\n className: string\n\n /**\n * The mode by which the focused node is determined.\n * - All: All nodes are marked as focused.\n * - Deepest: Only the deepest node is marked as focused.\n * - Shallowest: Only the shallowest node is marked as focused.\n *\n * @default 'all'\n * @example 'deepest'\n * @example 'shallowest'\n */\n mode: 'all' | 'deepest' | 'shallowest'\n}\n\n/**\n * This extension allows you to add a class to the focused node.\n * @see https://www.tiptap.dev/api/extensions/focus\n */\nexport const Focus = Extension.create<FocusOptions>({\n name: 'focus',\n\n addOptions() {\n return {\n className: 'has-focus',\n mode: 'all',\n }\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('focus'),\n props: {\n decorations: ({ doc, selection }) => {\n const { isEditable, isFocused } = this.editor\n const { anchor } = selection\n const decorations: Decoration[] = []\n\n if (!isEditable || !isFocused) {\n return DecorationSet.create(doc, [])\n }\n\n // Maximum Levels\n let maxLevels = 0\n\n if (this.options.mode === 'deepest') {\n doc.descendants((node, pos) => {\n if (node.isText) {\n return\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n maxLevels += 1\n })\n }\n\n // Loop through current\n let currentLevel = 0\n\n doc.descendants((node, pos) => {\n if (node.isText) {\n return false\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n currentLevel += 1\n\n const outOfScope =\n (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) ||\n (this.options.mode === 'shallowest' && currentLevel > 1)\n\n if (outOfScope) {\n return this.options.mode === 'deepest'\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: this.options.className,\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n },\n },\n }),\n ]\n },\n})\n"],"mappings":";;;;;;;;;AA6BA,MAAa,QAAQA,aAAAA,UAAU,OAAqB;CAClD,MAAM;CAEN,aAAa;EACX,OAAO;GACL,WAAW;GACX,MAAM;EACR;CACF;CAEA,wBAAwB;EACtB,OAAO,CACL,IAAIC,iBAAAA,OAAO;GACT,KAAK,IAAIC,iBAAAA,UAAU,OAAO;GAC1B,OAAO,EACL,cAAc,EAAE,KAAK,gBAAgB;IACnC,MAAM,EAAE,YAAY,cAAc,KAAK;IACvC,MAAM,EAAE,WAAW;IACnB,MAAM,cAA4B,CAAC;IAEnC,IAAI,CAAC,cAAc,CAAC,WAClB,OAAOC,gBAAAA,cAAc,OAAO,KAAK,CAAC,CAAC;IAIrC,IAAI,YAAY;IAEhB,IAAI,KAAK,QAAQ,SAAS,WACxB,IAAI,aAAa,MAAM,QAAQ;KAC7B,IAAI,KAAK,QACP;KAKF,IAAI,EAFc,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW,IAGjE,OAAO;KAGT,aAAa;IACf,CAAC;IAIH,IAAI,eAAe;IAEnB,IAAI,aAAa,MAAM,QAAQ;KAC7B,IAAI,KAAK,QACP,OAAO;KAKT,IAAI,EAFc,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW,IAGjE,OAAO;KAGT,gBAAgB;KAMhB,IAHG,KAAK,QAAQ,SAAS,aAAa,YAAY,eAAe,KAC9D,KAAK,QAAQ,SAAS,gBAAgB,eAAe,GAGtD,OAAO,KAAK,QAAQ,SAAS;KAG/B,YAAY,KACVC,gBAAAA,WAAW,KAAK,KAAK,MAAM,KAAK,UAAU,EACxC,OAAO,KAAK,QAAQ,UACtB,CAAC,CACH;IACF,CAAC;IAED,OAAOD,gBAAAA,cAAc,OAAO,KAAK,WAAW;GAC9C,EACF;EACF,CAAC,CACH;CACF;AACF,CAAC"} |
+22
-21
@@ -1,21 +0,21 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/focus/focus.d.ts | ||
| interface FocusOptions { | ||
| /** | ||
| * The class name that should be added to the focused node. | ||
| * @default 'has-focus' | ||
| * @example 'is-focused' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The mode by which the focused node is determined. | ||
| * - All: All nodes are marked as focused. | ||
| * - Deepest: Only the deepest node is marked as focused. | ||
| * - Shallowest: Only the shallowest node is marked as focused. | ||
| * | ||
| * @default 'all' | ||
| * @example 'deepest' | ||
| * @example 'shallowest' | ||
| */ | ||
| mode: 'all' | 'deepest' | 'shallowest'; | ||
| /** | ||
| * The class name that should be added to the focused node. | ||
| * @default 'has-focus' | ||
| * @example 'is-focused' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The mode by which the focused node is determined. | ||
| * - All: All nodes are marked as focused. | ||
| * - Deepest: Only the deepest node is marked as focused. | ||
| * - Shallowest: Only the shallowest node is marked as focused. | ||
| * | ||
| * @default 'all' | ||
| * @example 'deepest' | ||
| * @example 'shallowest' | ||
| */ | ||
| mode: 'all' | 'deepest' | 'shallowest'; | ||
| } | ||
@@ -27,3 +27,4 @@ /** | ||
| declare const Focus: Extension<FocusOptions, any>; | ||
| export { Focus, type FocusOptions }; | ||
| //#endregion | ||
| export { Focus, FocusOptions }; | ||
| //# sourceMappingURL=index.d.cts.map |
+22
-21
@@ -1,21 +0,21 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/focus/focus.d.ts | ||
| interface FocusOptions { | ||
| /** | ||
| * The class name that should be added to the focused node. | ||
| * @default 'has-focus' | ||
| * @example 'is-focused' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The mode by which the focused node is determined. | ||
| * - All: All nodes are marked as focused. | ||
| * - Deepest: Only the deepest node is marked as focused. | ||
| * - Shallowest: Only the shallowest node is marked as focused. | ||
| * | ||
| * @default 'all' | ||
| * @example 'deepest' | ||
| * @example 'shallowest' | ||
| */ | ||
| mode: 'all' | 'deepest' | 'shallowest'; | ||
| /** | ||
| * The class name that should be added to the focused node. | ||
| * @default 'has-focus' | ||
| * @example 'is-focused' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The mode by which the focused node is determined. | ||
| * - All: All nodes are marked as focused. | ||
| * - Deepest: Only the deepest node is marked as focused. | ||
| * - Shallowest: Only the shallowest node is marked as focused. | ||
| * | ||
| * @default 'all' | ||
| * @example 'deepest' | ||
| * @example 'shallowest' | ||
| */ | ||
| mode: 'all' | 'deepest' | 'shallowest'; | ||
| } | ||
@@ -27,3 +27,4 @@ /** | ||
| declare const Focus: Extension<FocusOptions, any>; | ||
| export { Focus, type FocusOptions }; | ||
| //#endregion | ||
| export { Focus, FocusOptions }; | ||
| //# sourceMappingURL=index.d.ts.map |
+42
-63
@@ -1,68 +0,47 @@ | ||
| // src/focus/focus.ts | ||
| import { Extension } from "@tiptap/core"; | ||
| import { Plugin, PluginKey } from "@tiptap/pm/state"; | ||
| import { Decoration, DecorationSet } from "@tiptap/pm/view"; | ||
| var Focus = Extension.create({ | ||
| name: "focus", | ||
| addOptions() { | ||
| return { | ||
| className: "has-focus", | ||
| mode: "all" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [ | ||
| new Plugin({ | ||
| key: new PluginKey("focus"), | ||
| props: { | ||
| decorations: ({ doc, selection }) => { | ||
| const { isEditable, isFocused } = this.editor; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| if (!isEditable || !isFocused) { | ||
| return DecorationSet.create(doc, []); | ||
| } | ||
| let maxLevels = 0; | ||
| if (this.options.mode === "deepest") { | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) { | ||
| return; | ||
| } | ||
| const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; | ||
| if (!isCurrent) { | ||
| return false; | ||
| } | ||
| maxLevels += 1; | ||
| }); | ||
| } | ||
| let currentLevel = 0; | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) { | ||
| return false; | ||
| } | ||
| const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; | ||
| if (!isCurrent) { | ||
| return false; | ||
| } | ||
| currentLevel += 1; | ||
| const outOfScope = this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1; | ||
| if (outOfScope) { | ||
| return this.options.mode === "deepest"; | ||
| } | ||
| decorations.push( | ||
| Decoration.node(pos, pos + node.nodeSize, { | ||
| class: this.options.className | ||
| }) | ||
| ); | ||
| }); | ||
| return DecorationSet.create(doc, decorations); | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| //#region src/focus/focus.ts | ||
| /** | ||
| * This extension allows you to add a class to the focused node. | ||
| * @see https://www.tiptap.dev/api/extensions/focus | ||
| */ | ||
| const Focus = Extension.create({ | ||
| name: "focus", | ||
| addOptions() { | ||
| return { | ||
| className: "has-focus", | ||
| mode: "all" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [new Plugin({ | ||
| key: new PluginKey("focus"), | ||
| props: { decorations: ({ doc, selection }) => { | ||
| const { isEditable, isFocused } = this.editor; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| if (!isEditable || !isFocused) return DecorationSet.create(doc, []); | ||
| let maxLevels = 0; | ||
| if (this.options.mode === "deepest") doc.descendants((node, pos) => { | ||
| if (node.isText) return; | ||
| if (!(anchor >= pos && anchor <= pos + node.nodeSize - 1)) return false; | ||
| maxLevels += 1; | ||
| }); | ||
| let currentLevel = 0; | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) return false; | ||
| if (!(anchor >= pos && anchor <= pos + node.nodeSize - 1)) return false; | ||
| currentLevel += 1; | ||
| if (this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1) return this.options.mode === "deepest"; | ||
| decorations.push(Decoration.node(pos, pos + node.nodeSize, { class: this.options.className })); | ||
| }); | ||
| return DecorationSet.create(doc, decorations); | ||
| } } | ||
| })]; | ||
| } | ||
| }); | ||
| export { | ||
| Focus | ||
| }; | ||
| //#endregion | ||
| export { Focus }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/focus/focus.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport interface FocusOptions {\n /**\n * The class name that should be added to the focused node.\n * @default 'has-focus'\n * @example 'is-focused'\n */\n className: string\n\n /**\n * The mode by which the focused node is determined.\n * - All: All nodes are marked as focused.\n * - Deepest: Only the deepest node is marked as focused.\n * - Shallowest: Only the shallowest node is marked as focused.\n *\n * @default 'all'\n * @example 'deepest'\n * @example 'shallowest'\n */\n mode: 'all' | 'deepest' | 'shallowest'\n}\n\n/**\n * This extension allows you to add a class to the focused node.\n * @see https://www.tiptap.dev/api/extensions/focus\n */\nexport const Focus = Extension.create<FocusOptions>({\n name: 'focus',\n\n addOptions() {\n return {\n className: 'has-focus',\n mode: 'all',\n }\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('focus'),\n props: {\n decorations: ({ doc, selection }) => {\n const { isEditable, isFocused } = this.editor\n const { anchor } = selection\n const decorations: Decoration[] = []\n\n if (!isEditable || !isFocused) {\n return DecorationSet.create(doc, [])\n }\n\n // Maximum Levels\n let maxLevels = 0\n\n if (this.options.mode === 'deepest') {\n doc.descendants((node, pos) => {\n if (node.isText) {\n return\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n maxLevels += 1\n })\n }\n\n // Loop through current\n let currentLevel = 0\n\n doc.descendants((node, pos) => {\n if (node.isText) {\n return false\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n currentLevel += 1\n\n const outOfScope =\n (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) ||\n (this.options.mode === 'shallowest' && currentLevel > 1)\n\n if (outOfScope) {\n return this.options.mode === 'deepest'\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: this.options.className,\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n },\n },\n }),\n ]\n },\n})\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,QAAQ,iBAAiB;AAClC,SAAS,YAAY,qBAAqB;AA2BnC,IAAM,QAAQ,UAAU,OAAqB;AAAA,EAClD,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,WAAW;AAAA,MACX,MAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,QACT,KAAK,IAAI,UAAU,OAAO;AAAA,QAC1B,OAAO;AAAA,UACL,aAAa,CAAC,EAAE,KAAK,UAAU,MAAM;AACnC,kBAAM,EAAE,YAAY,UAAU,IAAI,KAAK;AACvC,kBAAM,EAAE,OAAO,IAAI;AACnB,kBAAM,cAA4B,CAAC;AAEnC,gBAAI,CAAC,cAAc,CAAC,WAAW;AAC7B,qBAAO,cAAc,OAAO,KAAK,CAAC,CAAC;AAAA,YACrC;AAGA,gBAAI,YAAY;AAEhB,gBAAI,KAAK,QAAQ,SAAS,WAAW;AACnC,kBAAI,YAAY,CAAC,MAAM,QAAQ;AAC7B,oBAAI,KAAK,QAAQ;AACf;AAAA,gBACF;AAEA,sBAAM,YAAY,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW;AAEnE,oBAAI,CAAC,WAAW;AACd,yBAAO;AAAA,gBACT;AAEA,6BAAa;AAAA,cACf,CAAC;AAAA,YACH;AAGA,gBAAI,eAAe;AAEnB,gBAAI,YAAY,CAAC,MAAM,QAAQ;AAC7B,kBAAI,KAAK,QAAQ;AACf,uBAAO;AAAA,cACT;AAEA,oBAAM,YAAY,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW;AAEnE,kBAAI,CAAC,WAAW;AACd,uBAAO;AAAA,cACT;AAEA,8BAAgB;AAEhB,oBAAM,aACH,KAAK,QAAQ,SAAS,aAAa,YAAY,eAAe,KAC9D,KAAK,QAAQ,SAAS,gBAAgB,eAAe;AAExD,kBAAI,YAAY;AACd,uBAAO,KAAK,QAAQ,SAAS;AAAA,cAC/B;AAEA,0BAAY;AAAA,gBACV,WAAW,KAAK,KAAK,MAAM,KAAK,UAAU;AAAA,kBACxC,OAAO,KAAK,QAAQ;AAAA,gBACtB,CAAC;AAAA,cACH;AAAA,YACF,CAAC;AAED,mBAAO,cAAc,OAAO,KAAK,WAAW;AAAA,UAC9C;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/focus/focus.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport interface FocusOptions {\n /**\n * The class name that should be added to the focused node.\n * @default 'has-focus'\n * @example 'is-focused'\n */\n className: string\n\n /**\n * The mode by which the focused node is determined.\n * - All: All nodes are marked as focused.\n * - Deepest: Only the deepest node is marked as focused.\n * - Shallowest: Only the shallowest node is marked as focused.\n *\n * @default 'all'\n * @example 'deepest'\n * @example 'shallowest'\n */\n mode: 'all' | 'deepest' | 'shallowest'\n}\n\n/**\n * This extension allows you to add a class to the focused node.\n * @see https://www.tiptap.dev/api/extensions/focus\n */\nexport const Focus = Extension.create<FocusOptions>({\n name: 'focus',\n\n addOptions() {\n return {\n className: 'has-focus',\n mode: 'all',\n }\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('focus'),\n props: {\n decorations: ({ doc, selection }) => {\n const { isEditable, isFocused } = this.editor\n const { anchor } = selection\n const decorations: Decoration[] = []\n\n if (!isEditable || !isFocused) {\n return DecorationSet.create(doc, [])\n }\n\n // Maximum Levels\n let maxLevels = 0\n\n if (this.options.mode === 'deepest') {\n doc.descendants((node, pos) => {\n if (node.isText) {\n return\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n maxLevels += 1\n })\n }\n\n // Loop through current\n let currentLevel = 0\n\n doc.descendants((node, pos) => {\n if (node.isText) {\n return false\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n currentLevel += 1\n\n const outOfScope =\n (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) ||\n (this.options.mode === 'shallowest' && currentLevel > 1)\n\n if (outOfScope) {\n return this.options.mode === 'deepest'\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: this.options.className,\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n },\n },\n }),\n ]\n },\n})\n"],"mappings":";;;;;;;;AA6BA,MAAa,QAAQ,UAAU,OAAqB;CAClD,MAAM;CAEN,aAAa;EACX,OAAO;GACL,WAAW;GACX,MAAM;EACR;CACF;CAEA,wBAAwB;EACtB,OAAO,CACL,IAAI,OAAO;GACT,KAAK,IAAI,UAAU,OAAO;GAC1B,OAAO,EACL,cAAc,EAAE,KAAK,gBAAgB;IACnC,MAAM,EAAE,YAAY,cAAc,KAAK;IACvC,MAAM,EAAE,WAAW;IACnB,MAAM,cAA4B,CAAC;IAEnC,IAAI,CAAC,cAAc,CAAC,WAClB,OAAO,cAAc,OAAO,KAAK,CAAC,CAAC;IAIrC,IAAI,YAAY;IAEhB,IAAI,KAAK,QAAQ,SAAS,WACxB,IAAI,aAAa,MAAM,QAAQ;KAC7B,IAAI,KAAK,QACP;KAKF,IAAI,EAFc,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW,IAGjE,OAAO;KAGT,aAAa;IACf,CAAC;IAIH,IAAI,eAAe;IAEnB,IAAI,aAAa,MAAM,QAAQ;KAC7B,IAAI,KAAK,QACP,OAAO;KAKT,IAAI,EAFc,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW,IAGjE,OAAO;KAGT,gBAAgB;KAMhB,IAHG,KAAK,QAAQ,SAAS,aAAa,YAAY,eAAe,KAC9D,KAAK,QAAQ,SAAS,gBAAgB,eAAe,GAGtD,OAAO,KAAK,QAAQ,SAAS;KAG/B,YAAY,KACV,WAAW,KAAK,KAAK,MAAM,KAAK,UAAU,EACxC,OAAO,KAAK,QAAQ,UACtB,CAAC,CACH;IACF,CAAC;IAED,OAAO,cAAc,OAAO,KAAK,WAAW;GAC9C,EACF;EACF,CAAC,CACH;CACF;AACF,CAAC"} |
@@ -1,51 +0,28 @@ | ||
| "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/gap-cursor/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| Gapcursor: () => Gapcursor | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_gapcursor = require("@tiptap/pm/gapcursor"); | ||
| //#region src/gap-cursor/gap-cursor.ts | ||
| /** | ||
| * This extension allows you to add a gap cursor to your editor. | ||
| * A gap cursor is a cursor that appears when you click on a place | ||
| * where no content is present, for example inbetween nodes. | ||
| * @see https://tiptap.dev/api/extensions/gapcursor | ||
| */ | ||
| const Gapcursor = _tiptap_core.Extension.create({ | ||
| name: "gapCursor", | ||
| addProseMirrorPlugins() { | ||
| return [(0, _tiptap_pm_gapcursor.gapCursor)()]; | ||
| }, | ||
| extendNodeSchema(extension) { | ||
| var _callOrReturn; | ||
| return { allowGapCursor: (_callOrReturn = (0, _tiptap_core.callOrReturn)((0, _tiptap_core.getExtensionField)(extension, "allowGapCursor", { | ||
| name: extension.name, | ||
| options: extension.options, | ||
| storage: extension.storage | ||
| }))) !== null && _callOrReturn !== void 0 ? _callOrReturn : null }; | ||
| } | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| //#endregion | ||
| exports.Gapcursor = Gapcursor; | ||
| // src/gap-cursor/gap-cursor.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_gapcursor = require("@tiptap/pm/gapcursor"); | ||
| var Gapcursor = import_core.Extension.create({ | ||
| name: "gapCursor", | ||
| addProseMirrorPlugins() { | ||
| return [(0, import_gapcursor.gapCursor)()]; | ||
| }, | ||
| extendNodeSchema(extension) { | ||
| var _a; | ||
| const context = { | ||
| name: extension.name, | ||
| options: extension.options, | ||
| storage: extension.storage | ||
| }; | ||
| return { | ||
| allowGapCursor: (_a = (0, import_core.callOrReturn)((0, import_core.getExtensionField)(extension, "allowGapCursor", context))) != null ? _a : null | ||
| }; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| Gapcursor | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/gap-cursor/index.ts","../../src/gap-cursor/gap-cursor.ts"],"sourcesContent":["export * from './gap-cursor.js'\n","import type { ParentConfig } from '@tiptap/core'\nimport { callOrReturn, Extension, getExtensionField } from '@tiptap/core'\nimport { gapCursor } from '@tiptap/pm/gapcursor'\n\ndeclare module '@tiptap/core' {\n interface NodeConfig<Options, Storage> {\n /**\n * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`.\n * @default null\n */\n allowGapCursor?:\n | boolean\n | null\n | ((this: {\n name: string\n options: Options\n storage: Storage\n parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']\n }) => boolean | null)\n }\n}\n\n/**\n * This extension allows you to add a gap cursor to your editor.\n * A gap cursor is a cursor that appears when you click on a place\n * where no content is present, for example inbetween nodes.\n * @see https://tiptap.dev/api/extensions/gapcursor\n */\nexport const Gapcursor = Extension.create({\n name: 'gapCursor',\n\n addProseMirrorPlugins() {\n return [gapCursor()]\n },\n\n extendNodeSchema(extension) {\n const context = {\n name: extension.name,\n options: extension.options,\n storage: extension.storage,\n }\n\n return {\n allowGapCursor: callOrReturn(getExtensionField(extension, 'allowGapCursor', context)) ?? null,\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,kBAA2D;AAC3D,uBAA0B;AA0BnB,IAAM,YAAY,sBAAU,OAAO;AAAA,EACxC,MAAM;AAAA,EAEN,wBAAwB;AACtB,WAAO,KAAC,4BAAU,CAAC;AAAA,EACrB;AAAA,EAEA,iBAAiB,WAAW;AAnC9B;AAoCI,UAAM,UAAU;AAAA,MACd,MAAM,UAAU;AAAA,MAChB,SAAS,UAAU;AAAA,MACnB,SAAS,UAAU;AAAA,IACrB;AAEA,WAAO;AAAA,MACL,iBAAgB,uCAAa,+BAAkB,WAAW,kBAAkB,OAAO,CAAC,MAApE,YAAyE;AAAA,IAC3F;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.cjs","names":["Extension"],"sources":["../../src/gap-cursor/gap-cursor.ts"],"sourcesContent":["import type { ParentConfig } from '@tiptap/core'\nimport { callOrReturn, Extension, getExtensionField } from '@tiptap/core'\nimport { gapCursor } from '@tiptap/pm/gapcursor'\n\ndeclare module '@tiptap/core' {\n interface NodeConfig<Options, Storage> {\n /**\n * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`.\n * @default null\n */\n allowGapCursor?:\n | boolean\n | null\n | ((this: {\n name: string\n options: Options\n storage: Storage\n parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']\n }) => boolean | null)\n }\n}\n\n/**\n * This extension allows you to add a gap cursor to your editor.\n * A gap cursor is a cursor that appears when you click on a place\n * where no content is present, for example inbetween nodes.\n * @see https://tiptap.dev/api/extensions/gapcursor\n */\nexport const Gapcursor = Extension.create({\n name: 'gapCursor',\n\n addProseMirrorPlugins() {\n return [gapCursor()]\n },\n\n extendNodeSchema(extension) {\n const context = {\n name: extension.name,\n options: extension.options,\n storage: extension.storage,\n }\n\n return {\n allowGapCursor: callOrReturn(getExtensionField(extension, 'allowGapCursor', context)) ?? null,\n }\n },\n})\n"],"mappings":";;;;;;;;;;AA4BA,MAAa,YAAYA,aAAAA,UAAU,OAAO;CACxC,MAAM;CAEN,wBAAwB;EACtB,OAAO,EAAA,GAAA,qBAAA,UAAA,CAAW,CAAC;CACrB;CAEA,iBAAiB,WAAW;;EAO1B,OAAO,EACL,iBAAA,iBAAA,GAAA,aAAA,aAAA,EAAA,GAAA,aAAA,kBAAA,CAA+C,WAAW,kBAAkB;GAN5E,MAAM,UAAU;GAChB,SAAS,UAAU;GACnB,SAAS,UAAU;EAI+D,CAAC,CAAC,OAAA,QAAA,kBAAA,KAAA,IAAA,gBAAK,KAC3F;CACF;AACF,CAAC"} |
@@ -1,16 +0,16 @@ | ||
| import { ParentConfig, Extension } from '@tiptap/core'; | ||
| import { Extension, ParentConfig } from "@tiptap/core"; | ||
| //#region src/gap-cursor/gap-cursor.d.ts | ||
| declare module '@tiptap/core' { | ||
| interface NodeConfig<Options, Storage> { | ||
| /** | ||
| * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`. | ||
| * @default null | ||
| */ | ||
| allowGapCursor?: boolean | null | ((this: { | ||
| name: string; | ||
| options: Options; | ||
| storage: Storage; | ||
| parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']; | ||
| }) => boolean | null); | ||
| } | ||
| interface NodeConfig<Options, Storage> { | ||
| /** | ||
| * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`. | ||
| * @default null | ||
| */ | ||
| allowGapCursor?: boolean | null | ((this: { | ||
| name: string; | ||
| options: Options; | ||
| storage: Storage; | ||
| parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']; | ||
| }) => boolean | null); | ||
| } | ||
| } | ||
@@ -24,3 +24,4 @@ /** | ||
| declare const Gapcursor: Extension<any, any>; | ||
| //#endregion | ||
| export { Gapcursor }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,16 +0,16 @@ | ||
| import { ParentConfig, Extension } from '@tiptap/core'; | ||
| import { Extension, ParentConfig } from "@tiptap/core"; | ||
| //#region src/gap-cursor/gap-cursor.d.ts | ||
| declare module '@tiptap/core' { | ||
| interface NodeConfig<Options, Storage> { | ||
| /** | ||
| * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`. | ||
| * @default null | ||
| */ | ||
| allowGapCursor?: boolean | null | ((this: { | ||
| name: string; | ||
| options: Options; | ||
| storage: Storage; | ||
| parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']; | ||
| }) => boolean | null); | ||
| } | ||
| interface NodeConfig<Options, Storage> { | ||
| /** | ||
| * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`. | ||
| * @default null | ||
| */ | ||
| allowGapCursor?: boolean | null | ((this: { | ||
| name: string; | ||
| options: Options; | ||
| storage: Storage; | ||
| parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']; | ||
| }) => boolean | null); | ||
| } | ||
| } | ||
@@ -24,3 +24,4 @@ /** | ||
| declare const Gapcursor: Extension<any, any>; | ||
| //#endregion | ||
| export { Gapcursor }; | ||
| //# sourceMappingURL=index.d.ts.map |
+24
-21
@@ -1,24 +0,27 @@ | ||
| // src/gap-cursor/gap-cursor.ts | ||
| import { callOrReturn, Extension, getExtensionField } from "@tiptap/core"; | ||
| import { Extension, callOrReturn, getExtensionField } from "@tiptap/core"; | ||
| import { gapCursor } from "@tiptap/pm/gapcursor"; | ||
| var Gapcursor = Extension.create({ | ||
| name: "gapCursor", | ||
| addProseMirrorPlugins() { | ||
| return [gapCursor()]; | ||
| }, | ||
| extendNodeSchema(extension) { | ||
| var _a; | ||
| const context = { | ||
| name: extension.name, | ||
| options: extension.options, | ||
| storage: extension.storage | ||
| }; | ||
| return { | ||
| allowGapCursor: (_a = callOrReturn(getExtensionField(extension, "allowGapCursor", context))) != null ? _a : null | ||
| }; | ||
| } | ||
| //#region src/gap-cursor/gap-cursor.ts | ||
| /** | ||
| * This extension allows you to add a gap cursor to your editor. | ||
| * A gap cursor is a cursor that appears when you click on a place | ||
| * where no content is present, for example inbetween nodes. | ||
| * @see https://tiptap.dev/api/extensions/gapcursor | ||
| */ | ||
| const Gapcursor = Extension.create({ | ||
| name: "gapCursor", | ||
| addProseMirrorPlugins() { | ||
| return [gapCursor()]; | ||
| }, | ||
| extendNodeSchema(extension) { | ||
| var _callOrReturn; | ||
| return { allowGapCursor: (_callOrReturn = callOrReturn(getExtensionField(extension, "allowGapCursor", { | ||
| name: extension.name, | ||
| options: extension.options, | ||
| storage: extension.storage | ||
| }))) !== null && _callOrReturn !== void 0 ? _callOrReturn : null }; | ||
| } | ||
| }); | ||
| export { | ||
| Gapcursor | ||
| }; | ||
| //#endregion | ||
| export { Gapcursor }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/gap-cursor/gap-cursor.ts"],"sourcesContent":["import type { ParentConfig } from '@tiptap/core'\nimport { callOrReturn, Extension, getExtensionField } from '@tiptap/core'\nimport { gapCursor } from '@tiptap/pm/gapcursor'\n\ndeclare module '@tiptap/core' {\n interface NodeConfig<Options, Storage> {\n /**\n * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`.\n * @default null\n */\n allowGapCursor?:\n | boolean\n | null\n | ((this: {\n name: string\n options: Options\n storage: Storage\n parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']\n }) => boolean | null)\n }\n}\n\n/**\n * This extension allows you to add a gap cursor to your editor.\n * A gap cursor is a cursor that appears when you click on a place\n * where no content is present, for example inbetween nodes.\n * @see https://tiptap.dev/api/extensions/gapcursor\n */\nexport const Gapcursor = Extension.create({\n name: 'gapCursor',\n\n addProseMirrorPlugins() {\n return [gapCursor()]\n },\n\n extendNodeSchema(extension) {\n const context = {\n name: extension.name,\n options: extension.options,\n storage: extension.storage,\n }\n\n return {\n allowGapCursor: callOrReturn(getExtensionField(extension, 'allowGapCursor', context)) ?? null,\n }\n },\n})\n"],"mappings":";AACA,SAAS,cAAc,WAAW,yBAAyB;AAC3D,SAAS,iBAAiB;AA0BnB,IAAM,YAAY,UAAU,OAAO;AAAA,EACxC,MAAM;AAAA,EAEN,wBAAwB;AACtB,WAAO,CAAC,UAAU,CAAC;AAAA,EACrB;AAAA,EAEA,iBAAiB,WAAW;AAnC9B;AAoCI,UAAM,UAAU;AAAA,MACd,MAAM,UAAU;AAAA,MAChB,SAAS,UAAU;AAAA,MACnB,SAAS,UAAU;AAAA,IACrB;AAEA,WAAO;AAAA,MACL,iBAAgB,kBAAa,kBAAkB,WAAW,kBAAkB,OAAO,CAAC,MAApE,YAAyE;AAAA,IAC3F;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/gap-cursor/gap-cursor.ts"],"sourcesContent":["import type { ParentConfig } from '@tiptap/core'\nimport { callOrReturn, Extension, getExtensionField } from '@tiptap/core'\nimport { gapCursor } from '@tiptap/pm/gapcursor'\n\ndeclare module '@tiptap/core' {\n interface NodeConfig<Options, Storage> {\n /**\n * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`.\n * @default null\n */\n allowGapCursor?:\n | boolean\n | null\n | ((this: {\n name: string\n options: Options\n storage: Storage\n parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']\n }) => boolean | null)\n }\n}\n\n/**\n * This extension allows you to add a gap cursor to your editor.\n * A gap cursor is a cursor that appears when you click on a place\n * where no content is present, for example inbetween nodes.\n * @see https://tiptap.dev/api/extensions/gapcursor\n */\nexport const Gapcursor = Extension.create({\n name: 'gapCursor',\n\n addProseMirrorPlugins() {\n return [gapCursor()]\n },\n\n extendNodeSchema(extension) {\n const context = {\n name: extension.name,\n options: extension.options,\n storage: extension.storage,\n }\n\n return {\n allowGapCursor: callOrReturn(getExtensionField(extension, 'allowGapCursor', context)) ?? null,\n }\n },\n})\n"],"mappings":";;;;;;;;;AA4BA,MAAa,YAAY,UAAU,OAAO;CACxC,MAAM;CAEN,wBAAwB;EACtB,OAAO,CAAC,UAAU,CAAC;CACrB;CAEA,iBAAiB,WAAW;;EAO1B,OAAO,EACL,iBAAA,gBAAgB,aAAa,kBAAkB,WAAW,kBAAkB;GAN5E,MAAM,UAAU;GAChB,SAAS,UAAU;GACnB,SAAS,UAAU;EAI+D,CAAC,CAAC,OAAA,QAAA,kBAAA,KAAA,IAAA,gBAAK,KAC3F;CACF;AACF,CAAC"} |
+666
-742
@@ -1,786 +0,710 @@ | ||
| "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, { | ||
| CharacterCount: () => CharacterCount, | ||
| DEFAULT_DATA_ATTRIBUTE: () => DEFAULT_DATA_ATTRIBUTE, | ||
| Dropcursor: () => Dropcursor, | ||
| Focus: () => Focus, | ||
| Gapcursor: () => Gapcursor, | ||
| PLUGIN_KEY: () => PLUGIN_KEY, | ||
| Placeholder: () => Placeholder, | ||
| Selection: () => Selection, | ||
| TrailingNode: () => TrailingNode, | ||
| UndoRedo: () => UndoRedo, | ||
| preparePlaceholderAttribute: () => preparePlaceholderAttribute, | ||
| skipTrailingNodeMeta: () => skipTrailingNodeMeta | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_state = require("@tiptap/pm/state"); | ||
| let _tiptap_pm_dropcursor = require("@tiptap/pm/dropcursor"); | ||
| let _tiptap_pm_view = require("@tiptap/pm/view"); | ||
| let _tiptap_pm_gapcursor = require("@tiptap/pm/gapcursor"); | ||
| let _tiptap_pm_history = require("@tiptap/pm/history"); | ||
| //#region src/character-count/character-count.ts | ||
| /** | ||
| * This extension allows you to count the characters and words of your document. | ||
| * @see https://tiptap.dev/api/extensions/character-count | ||
| */ | ||
| const CharacterCount = _tiptap_core.Extension.create({ | ||
| name: "characterCount", | ||
| addOptions() { | ||
| return { | ||
| limit: null, | ||
| autoTrim: true, | ||
| mode: "textSize", | ||
| textCounter: (text) => text.length, | ||
| wordCounter: (text) => text.split(" ").filter((word) => word !== "").length | ||
| }; | ||
| }, | ||
| addStorage() { | ||
| return { | ||
| characters: () => 0, | ||
| words: () => 0 | ||
| }; | ||
| }, | ||
| onBeforeCreate() { | ||
| this.storage.characters = (options) => { | ||
| const node = (options === null || options === void 0 ? void 0 : options.node) || this.editor.state.doc; | ||
| if (((options === null || options === void 0 ? void 0 : options.mode) || this.options.mode) === "textSize") { | ||
| const text = node.textBetween(0, node.content.size, void 0, " "); | ||
| return this.options.textCounter(text); | ||
| } | ||
| return node.nodeSize; | ||
| }; | ||
| this.storage.words = (options) => { | ||
| const node = (options === null || options === void 0 ? void 0 : options.node) || this.editor.state.doc; | ||
| const text = node.textBetween(0, node.content.size, " ", " "); | ||
| return this.options.wordCounter(text); | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| let initialEvaluationDone = false; | ||
| return [new _tiptap_pm_state.Plugin({ | ||
| key: new _tiptap_pm_state.PluginKey("characterCount"), | ||
| appendTransaction: (transactions, oldState, newState) => { | ||
| if (initialEvaluationDone) return; | ||
| const limit = this.options.limit; | ||
| const autoTrim = this.options.autoTrim; | ||
| if (limit === null || limit === void 0 || limit === 0 || autoTrim === false) { | ||
| initialEvaluationDone = true; | ||
| return; | ||
| } | ||
| const initialContentSize = this.storage.characters({ node: newState.doc }); | ||
| if (initialContentSize > limit) { | ||
| const over = initialContentSize - limit; | ||
| const from = 0; | ||
| const to = over; | ||
| console.warn(`[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`); | ||
| const tr = newState.tr.deleteRange(from, to); | ||
| initialEvaluationDone = true; | ||
| return tr; | ||
| } | ||
| initialEvaluationDone = true; | ||
| }, | ||
| filterTransaction: (transaction, state) => { | ||
| const limit = this.options.limit; | ||
| if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) return true; | ||
| const oldSize = this.storage.characters({ node: state.doc }); | ||
| const newSize = this.storage.characters({ node: transaction.doc }); | ||
| if (newSize <= limit) return true; | ||
| if (oldSize > limit && newSize > limit && newSize <= oldSize) return true; | ||
| if (oldSize > limit && newSize > limit && newSize > oldSize) return false; | ||
| if (!transaction.getMeta("paste")) return false; | ||
| const pos = transaction.selection.$head.pos; | ||
| const from = pos - (newSize - limit); | ||
| const to = pos; | ||
| transaction.deleteRange(from, to); | ||
| if (this.storage.characters({ node: transaction.doc }) > limit) return false; | ||
| return true; | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/character-count/character-count.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_state = require("@tiptap/pm/state"); | ||
| var CharacterCount = import_core.Extension.create({ | ||
| name: "characterCount", | ||
| addOptions() { | ||
| return { | ||
| limit: null, | ||
| autoTrim: true, | ||
| mode: "textSize", | ||
| textCounter: (text) => text.length, | ||
| wordCounter: (text) => text.split(" ").filter((word) => word !== "").length | ||
| }; | ||
| }, | ||
| addStorage() { | ||
| return { | ||
| characters: () => 0, | ||
| words: () => 0 | ||
| }; | ||
| }, | ||
| onBeforeCreate() { | ||
| this.storage.characters = (options) => { | ||
| const node = (options == null ? void 0 : options.node) || this.editor.state.doc; | ||
| const mode = (options == null ? void 0 : options.mode) || this.options.mode; | ||
| if (mode === "textSize") { | ||
| const text = node.textBetween(0, node.content.size, void 0, " "); | ||
| return this.options.textCounter(text); | ||
| } | ||
| return node.nodeSize; | ||
| }; | ||
| this.storage.words = (options) => { | ||
| const node = (options == null ? void 0 : options.node) || this.editor.state.doc; | ||
| const text = node.textBetween(0, node.content.size, " ", " "); | ||
| return this.options.wordCounter(text); | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| let initialEvaluationDone = false; | ||
| return [ | ||
| new import_state.Plugin({ | ||
| key: new import_state.PluginKey("characterCount"), | ||
| appendTransaction: (transactions, oldState, newState) => { | ||
| if (initialEvaluationDone) { | ||
| return; | ||
| } | ||
| const limit = this.options.limit; | ||
| const autoTrim = this.options.autoTrim; | ||
| if (limit === null || limit === void 0 || limit === 0 || autoTrim === false) { | ||
| initialEvaluationDone = true; | ||
| return; | ||
| } | ||
| const initialContentSize = this.storage.characters({ node: newState.doc }); | ||
| if (initialContentSize > limit) { | ||
| const over = initialContentSize - limit; | ||
| const from = 0; | ||
| const to = over; | ||
| console.warn( | ||
| `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.` | ||
| ); | ||
| const tr = newState.tr.deleteRange(from, to); | ||
| initialEvaluationDone = true; | ||
| return tr; | ||
| } | ||
| initialEvaluationDone = true; | ||
| }, | ||
| filterTransaction: (transaction, state) => { | ||
| const limit = this.options.limit; | ||
| if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) { | ||
| return true; | ||
| } | ||
| const oldSize = this.storage.characters({ node: state.doc }); | ||
| const newSize = this.storage.characters({ node: transaction.doc }); | ||
| if (newSize <= limit) { | ||
| return true; | ||
| } | ||
| if (oldSize > limit && newSize > limit && newSize <= oldSize) { | ||
| return true; | ||
| } | ||
| if (oldSize > limit && newSize > limit && newSize > oldSize) { | ||
| return false; | ||
| } | ||
| const isPaste = transaction.getMeta("paste"); | ||
| if (!isPaste) { | ||
| return false; | ||
| } | ||
| const pos = transaction.selection.$head.pos; | ||
| const over = newSize - limit; | ||
| const from = pos - over; | ||
| const to = pos; | ||
| transaction.deleteRange(from, to); | ||
| const updatedSize = this.storage.characters({ node: transaction.doc }); | ||
| if (updatedSize > limit) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| //#endregion | ||
| //#region src/drop-cursor/drop-cursor.ts | ||
| /** | ||
| * This extension allows you to add a drop cursor to your editor. | ||
| * A drop cursor is a line that appears when you drag and drop content | ||
| * in-between nodes. | ||
| * @see https://tiptap.dev/api/extensions/dropcursor | ||
| */ | ||
| const Dropcursor = _tiptap_core.Extension.create({ | ||
| name: "dropCursor", | ||
| addOptions() { | ||
| return { | ||
| color: "currentColor", | ||
| width: 1, | ||
| class: void 0 | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [(0, _tiptap_pm_dropcursor.dropCursor)(this.options)]; | ||
| } | ||
| }); | ||
| // src/drop-cursor/drop-cursor.ts | ||
| var import_core2 = require("@tiptap/core"); | ||
| var import_dropcursor = require("@tiptap/pm/dropcursor"); | ||
| var Dropcursor = import_core2.Extension.create({ | ||
| name: "dropCursor", | ||
| addOptions() { | ||
| return { | ||
| color: "currentColor", | ||
| width: 1, | ||
| class: void 0 | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [(0, import_dropcursor.dropCursor)(this.options)]; | ||
| } | ||
| //#endregion | ||
| //#region src/focus/focus.ts | ||
| /** | ||
| * This extension allows you to add a class to the focused node. | ||
| * @see https://www.tiptap.dev/api/extensions/focus | ||
| */ | ||
| const Focus = _tiptap_core.Extension.create({ | ||
| name: "focus", | ||
| addOptions() { | ||
| return { | ||
| className: "has-focus", | ||
| mode: "all" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [new _tiptap_pm_state.Plugin({ | ||
| key: new _tiptap_pm_state.PluginKey("focus"), | ||
| props: { decorations: ({ doc, selection }) => { | ||
| const { isEditable, isFocused } = this.editor; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| if (!isEditable || !isFocused) return _tiptap_pm_view.DecorationSet.create(doc, []); | ||
| let maxLevels = 0; | ||
| if (this.options.mode === "deepest") doc.descendants((node, pos) => { | ||
| if (node.isText) return; | ||
| if (!(anchor >= pos && anchor <= pos + node.nodeSize - 1)) return false; | ||
| maxLevels += 1; | ||
| }); | ||
| let currentLevel = 0; | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) return false; | ||
| if (!(anchor >= pos && anchor <= pos + node.nodeSize - 1)) return false; | ||
| currentLevel += 1; | ||
| if (this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1) return this.options.mode === "deepest"; | ||
| decorations.push(_tiptap_pm_view.Decoration.node(pos, pos + node.nodeSize, { class: this.options.className })); | ||
| }); | ||
| return _tiptap_pm_view.DecorationSet.create(doc, decorations); | ||
| } } | ||
| })]; | ||
| } | ||
| }); | ||
| // src/focus/focus.ts | ||
| var import_core3 = require("@tiptap/core"); | ||
| var import_state2 = require("@tiptap/pm/state"); | ||
| var import_view = require("@tiptap/pm/view"); | ||
| var Focus = import_core3.Extension.create({ | ||
| name: "focus", | ||
| addOptions() { | ||
| return { | ||
| className: "has-focus", | ||
| mode: "all" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [ | ||
| new import_state2.Plugin({ | ||
| key: new import_state2.PluginKey("focus"), | ||
| props: { | ||
| decorations: ({ doc, selection }) => { | ||
| const { isEditable, isFocused } = this.editor; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| if (!isEditable || !isFocused) { | ||
| return import_view.DecorationSet.create(doc, []); | ||
| } | ||
| let maxLevels = 0; | ||
| if (this.options.mode === "deepest") { | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) { | ||
| return; | ||
| } | ||
| const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; | ||
| if (!isCurrent) { | ||
| return false; | ||
| } | ||
| maxLevels += 1; | ||
| }); | ||
| } | ||
| let currentLevel = 0; | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) { | ||
| return false; | ||
| } | ||
| const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; | ||
| if (!isCurrent) { | ||
| return false; | ||
| } | ||
| currentLevel += 1; | ||
| const outOfScope = this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1; | ||
| if (outOfScope) { | ||
| return this.options.mode === "deepest"; | ||
| } | ||
| decorations.push( | ||
| import_view.Decoration.node(pos, pos + node.nodeSize, { | ||
| class: this.options.className | ||
| }) | ||
| ); | ||
| }); | ||
| return import_view.DecorationSet.create(doc, decorations); | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| //#endregion | ||
| //#region src/gap-cursor/gap-cursor.ts | ||
| /** | ||
| * This extension allows you to add a gap cursor to your editor. | ||
| * A gap cursor is a cursor that appears when you click on a place | ||
| * where no content is present, for example inbetween nodes. | ||
| * @see https://tiptap.dev/api/extensions/gapcursor | ||
| */ | ||
| const Gapcursor = _tiptap_core.Extension.create({ | ||
| name: "gapCursor", | ||
| addProseMirrorPlugins() { | ||
| return [(0, _tiptap_pm_gapcursor.gapCursor)()]; | ||
| }, | ||
| extendNodeSchema(extension) { | ||
| var _callOrReturn; | ||
| return { allowGapCursor: (_callOrReturn = (0, _tiptap_core.callOrReturn)((0, _tiptap_core.getExtensionField)(extension, "allowGapCursor", { | ||
| name: extension.name, | ||
| options: extension.options, | ||
| storage: extension.storage | ||
| }))) !== null && _callOrReturn !== void 0 ? _callOrReturn : null }; | ||
| } | ||
| }); | ||
| // src/gap-cursor/gap-cursor.ts | ||
| var import_core4 = require("@tiptap/core"); | ||
| var import_gapcursor = require("@tiptap/pm/gapcursor"); | ||
| var Gapcursor = import_core4.Extension.create({ | ||
| name: "gapCursor", | ||
| addProseMirrorPlugins() { | ||
| return [(0, import_gapcursor.gapCursor)()]; | ||
| }, | ||
| extendNodeSchema(extension) { | ||
| var _a; | ||
| const context = { | ||
| name: extension.name, | ||
| options: extension.options, | ||
| storage: extension.storage | ||
| }; | ||
| return { | ||
| allowGapCursor: (_a = (0, import_core4.callOrReturn)((0, import_core4.getExtensionField)(extension, "allowGapCursor", context))) != null ? _a : null | ||
| }; | ||
| } | ||
| }); | ||
| // src/placeholder/constants.ts | ||
| var import_state3 = require("@tiptap/pm/state"); | ||
| var DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| var PLUGIN_KEY = new import_state3.PluginKey("tiptap__placeholder"); | ||
| // src/placeholder/placeholder.ts | ||
| var import_core7 = require("@tiptap/core"); | ||
| // src/placeholder/plugins/PlaceholderPlugin.ts | ||
| var import_state4 = require("@tiptap/pm/state"); | ||
| var import_view5 = require("@tiptap/pm/view"); | ||
| // src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| var import_core5 = require("@tiptap/core"); | ||
| var import_view3 = require("@tiptap/pm/view"); | ||
| // src/placeholder/utils/createPlaceholderDecoration.ts | ||
| var import_view2 = require("@tiptap/pm/view"); | ||
| //#endregion | ||
| //#region src/placeholder/constants.ts | ||
| /** The default data attribute label */ | ||
| const DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| /** The plugin key used to store and read the placeholder decoration set */ | ||
| const PLUGIN_KEY = new _tiptap_pm_state.PluginKey("tiptap__placeholder"); | ||
| //#endregion | ||
| //#region src/placeholder/utils/createPlaceholderDecoration.ts | ||
| /** | ||
| * Creates a ProseMirror node decoration that applies a placeholder | ||
| * CSS class and data attribute to an empty node. | ||
| * @param options.editor - The editor instance | ||
| * @param options.pos - The position of the node in the document | ||
| * @param options.node - The ProseMirror node | ||
| * @param options.isEmptyDoc - Whether the entire document is empty | ||
| * @param options.hasAnchor - Whether the selection anchor is within the node | ||
| * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`) | ||
| * @param options.classes - CSS classes for empty nodes and the empty editor | ||
| * @param options.placeholder - The placeholder text or a function that returns it | ||
| * @returns A ProseMirror node decoration with placeholder classes and data attribute | ||
| */ | ||
| function createPlaceholderDecoration(options) { | ||
| const { | ||
| editor, | ||
| placeholder, | ||
| dataAttribute, | ||
| pos, | ||
| node, | ||
| isEmptyDoc, | ||
| hasAnchor, | ||
| classes: { emptyNode, emptyEditor } | ||
| } = options; | ||
| const classes = [emptyNode]; | ||
| if (isEmptyDoc) { | ||
| classes.push(emptyEditor); | ||
| } | ||
| return import_view2.Decoration.node(pos, pos + node.nodeSize, { | ||
| class: classes.join(" "), | ||
| [dataAttribute]: typeof placeholder === "function" ? placeholder({ | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) : placeholder | ||
| }); | ||
| const { editor, placeholder, dataAttribute, pos, node, isEmptyDoc, hasAnchor, classes: { emptyNode, emptyEditor } } = options; | ||
| const classes = [emptyNode]; | ||
| if (isEmptyDoc) classes.push(emptyEditor); | ||
| return _tiptap_pm_view.Decoration.node(pos, pos + node.nodeSize, { | ||
| class: classes.join(" "), | ||
| [dataAttribute]: typeof placeholder === "function" ? placeholder({ | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) : placeholder | ||
| }); | ||
| } | ||
| // src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| function resolveEmptyNodeClass(emptyNodeClass, props) { | ||
| return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass; | ||
| return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass; | ||
| } | ||
| function scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }) { | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| doc.nodesBetween(from, to, (node, pos) => { | ||
| const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; | ||
| const isEmpty = !node.isLeaf && (0, import_core5.isNodeEmpty)(node); | ||
| if (!node.type.isTextblock) { | ||
| return options.includeChildren; | ||
| } | ||
| if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) { | ||
| decorations.push( | ||
| createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos | ||
| }) | ||
| ); | ||
| } | ||
| return options.includeChildren; | ||
| }); | ||
| return decorations; | ||
| /** | ||
| * Scans a document range for empty textblocks that should receive placeholder | ||
| * decorations. Used by the slow path and incremental state updates. | ||
| */ | ||
| function scanRangeForDecorations({ editor, options, dataAttribute, doc, selection, from, to }) { | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| doc.nodesBetween(from, to, (node, pos) => { | ||
| const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; | ||
| const isEmpty = !node.isLeaf && (0, _tiptap_core.isNodeEmpty)(node); | ||
| if (!node.type.isTextblock) return options.includeChildren; | ||
| if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) decorations.push(createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos | ||
| })); | ||
| return options.includeChildren; | ||
| }); | ||
| return decorations; | ||
| } | ||
| function buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) { | ||
| const active = editor.isEditable || !options.showOnlyWhenEditable; | ||
| if (!active) { | ||
| return null; | ||
| } | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| if (useResolvedPath) { | ||
| const resolved = doc.resolve(anchor); | ||
| const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter; | ||
| const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor; | ||
| if (node && node.type.isTextblock && (0, import_core5.isNodeEmpty)(node)) { | ||
| const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize; | ||
| decorations.push( | ||
| createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos: nodeStart, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos: nodeStart | ||
| }) | ||
| ); | ||
| } | ||
| } else { | ||
| decorations.push( | ||
| ...scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from: 0, | ||
| to: doc.content.size | ||
| }) | ||
| ); | ||
| } | ||
| return import_view3.DecorationSet.create(doc, decorations); | ||
| /** | ||
| * Builds the placeholder decorations for the current document state. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @param options.dataAttribute - The prepared `data-*` attribute name. | ||
| * @param options.doc - The current document node. | ||
| * @param options.selection - The current selection. | ||
| * @returns A decoration set, or `null` when no placeholders should be shown. | ||
| */ | ||
| function buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) { | ||
| if (!(editor.isEditable || !options.showOnlyWhenEditable)) return null; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| if (options.showOnlyCurrent && !options.includeChildren) { | ||
| const resolved = doc.resolve(anchor); | ||
| const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter; | ||
| const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor; | ||
| if (node && node.type.isTextblock && (0, _tiptap_core.isNodeEmpty)(node)) { | ||
| const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize; | ||
| decorations.push(createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos: nodeStart, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos: nodeStart | ||
| })); | ||
| } | ||
| } else decorations.push(...scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from: 0, | ||
| to: doc.content.size | ||
| })); | ||
| return _tiptap_pm_view.DecorationSet.create(doc, decorations); | ||
| } | ||
| // src/placeholder/utils/placeholderStateField.ts | ||
| var import_core6 = require("@tiptap/core"); | ||
| var import_view4 = require("@tiptap/pm/view"); | ||
| // src/placeholder/utils/resolveTopLevelRange.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/resolveTopLevelRange.ts | ||
| /** | ||
| * Resolves a document position to the `[from, to)` range of its containing | ||
| * top-level block node in absolute document positions. | ||
| */ | ||
| function resolveTopLevelRange(doc, pos) { | ||
| var _a; | ||
| const resolved = doc.resolve(pos); | ||
| if (resolved.depth === 0) { | ||
| const node2 = (_a = resolved.nodeAfter) != null ? _a : resolved.nodeBefore; | ||
| if (!node2) { | ||
| return { from: pos, to: pos }; | ||
| } | ||
| const nodePos = resolved.nodeAfter ? pos : pos - node2.nodeSize; | ||
| return { from: nodePos, to: nodePos + node2.nodeSize }; | ||
| } | ||
| const topLevelPos = resolved.before(1); | ||
| const node = resolved.node(1); | ||
| return { from: topLevelPos, to: topLevelPos + node.nodeSize }; | ||
| const resolved = doc.resolve(pos); | ||
| if (resolved.depth === 0) { | ||
| var _resolved$nodeAfter; | ||
| const node = (_resolved$nodeAfter = resolved.nodeAfter) !== null && _resolved$nodeAfter !== void 0 ? _resolved$nodeAfter : resolved.nodeBefore; | ||
| if (!node) return { | ||
| from: pos, | ||
| to: pos | ||
| }; | ||
| const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize; | ||
| return { | ||
| from: nodePos, | ||
| to: nodePos + node.nodeSize | ||
| }; | ||
| } | ||
| const topLevelPos = resolved.before(1); | ||
| return { | ||
| from: topLevelPos, | ||
| to: topLevelPos + resolved.node(1).nodeSize | ||
| }; | ||
| } | ||
| /** | ||
| * Converts an absolute document range to content-relative positions used by | ||
| * `Node#nodesBetween` and `Node#forEach` offsets. | ||
| */ | ||
| function toContentRelativeRange(doc, range) { | ||
| return { | ||
| from: Math.max(0, range.from - 1), | ||
| to: Math.min(doc.content.size, range.to - 1) | ||
| }; | ||
| return { | ||
| from: Math.max(0, range.from - 1), | ||
| to: Math.min(doc.content.size, range.to - 1) | ||
| }; | ||
| } | ||
| /** | ||
| * Returns the top-level block ranges that intersect a document change range. | ||
| * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`). | ||
| * Returned ranges are content-relative, matching `Node#forEach` offsets. | ||
| */ | ||
| function getTopLevelBlocksInRange(doc, from, to) { | ||
| const ranges = []; | ||
| doc.forEach((node, offset) => { | ||
| const nodeStart = offset; | ||
| const nodeEnd = nodeStart + node.nodeSize; | ||
| const absNodeStart = nodeStart + 1; | ||
| const absNodeEnd = nodeEnd + 1; | ||
| if (absNodeStart < to && absNodeEnd > from) { | ||
| ranges.push({ from: nodeStart, to: nodeEnd }); | ||
| } | ||
| }); | ||
| return ranges; | ||
| const ranges = []; | ||
| doc.forEach((node, offset) => { | ||
| const nodeStart = offset; | ||
| const nodeEnd = nodeStart + node.nodeSize; | ||
| const absNodeStart = nodeStart + 1; | ||
| const absNodeEnd = nodeEnd + 1; | ||
| if (absNodeStart < to && absNodeEnd > from) ranges.push({ | ||
| from: nodeStart, | ||
| to: nodeEnd | ||
| }); | ||
| }); | ||
| return ranges; | ||
| } | ||
| /** | ||
| * Sorts ranges by start position and merges overlapping or adjacent ranges. | ||
| */ | ||
| function mergeRanges(ranges) { | ||
| if (ranges.length === 0) { | ||
| return []; | ||
| } | ||
| const sorted = [...ranges].sort((a, b) => a.from - b.from); | ||
| const merged = [{ ...sorted[0] }]; | ||
| for (let i = 1; i < sorted.length; i += 1) { | ||
| const last = merged[merged.length - 1]; | ||
| const current = sorted[i]; | ||
| if (current.from <= last.to) { | ||
| last.to = Math.max(last.to, current.to); | ||
| } else { | ||
| merged.push({ ...current }); | ||
| } | ||
| } | ||
| return merged; | ||
| if (ranges.length === 0) return []; | ||
| const sorted = [...ranges].sort((a, b) => a.from - b.from); | ||
| const merged = [{ ...sorted[0] }]; | ||
| for (let i = 1; i < sorted.length; i += 1) { | ||
| const last = merged[merged.length - 1]; | ||
| const current = sorted[i]; | ||
| if (current.from <= last.to) last.to = Math.max(last.to, current.to); | ||
| else merged.push({ ...current }); | ||
| } | ||
| return merged; | ||
| } | ||
| // src/placeholder/utils/placeholderStateField.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/placeholderStateField.ts | ||
| /** | ||
| * Expands a single changed range to the top-level blocks it touches. | ||
| * Also resolves blocks at range boundaries so split/merge edits update | ||
| * adjacent empty nodes (e.g. a new paragraph after Enter). | ||
| */ | ||
| function collectBlocksForChange(doc, change) { | ||
| const ranges = getTopLevelBlocksInRange(doc, change.from, change.to); | ||
| ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from))); | ||
| if (change.to > change.from) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| doc, | ||
| resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1) | ||
| ) | ||
| ); | ||
| } else if (change.from < doc.content.size + 1) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| doc, | ||
| resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)) | ||
| ) | ||
| ); | ||
| } | ||
| return ranges; | ||
| const ranges = getTopLevelBlocksInRange(doc, change.from, change.to); | ||
| ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from))); | ||
| if (change.to > change.from) ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1))); | ||
| else if (change.from < doc.content.size + 1) ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)))); | ||
| return ranges; | ||
| } | ||
| /** | ||
| * Collects content-relative top-level block ranges that need placeholder | ||
| * decorations recomputed after a transaction. | ||
| */ | ||
| function collectRescanRanges(tr, oldState, newState) { | ||
| const ranges = []; | ||
| if (tr.docChanged) { | ||
| const changes = (0, import_core6.getChangedRanges)(tr); | ||
| for (const change of changes) { | ||
| ranges.push(...collectBlocksForChange(newState.doc, change.newRange)); | ||
| } | ||
| } | ||
| if (tr.selectionSet) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| newState.doc, | ||
| resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)) | ||
| ) | ||
| ); | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| newState.doc, | ||
| resolveTopLevelRange(newState.doc, newState.selection.anchor) | ||
| ) | ||
| ); | ||
| } | ||
| return mergeRanges(ranges); | ||
| const ranges = []; | ||
| if (tr.docChanged) { | ||
| const changes = (0, _tiptap_core.getChangedRanges)(tr); | ||
| for (const change of changes) ranges.push(...collectBlocksForChange(newState.doc, change.newRange)); | ||
| } | ||
| if (tr.selectionSet) { | ||
| ranges.push(toContentRelativeRange(newState.doc, resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)))); | ||
| ranges.push(toContentRelativeRange(newState.doc, resolveTopLevelRange(newState.doc, newState.selection.anchor))); | ||
| } | ||
| return mergeRanges(ranges); | ||
| } | ||
| /** Clamps a content-relative range to `[0, doc.content.size]`. */ | ||
| function clampRange(from, to, doc) { | ||
| const clampedFrom = Math.max(0, Math.min(from, doc.content.size)); | ||
| const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size)); | ||
| return { from: clampedFrom, to: clampedTo }; | ||
| const clampedFrom = Math.max(0, Math.min(from, doc.content.size)); | ||
| return { | ||
| from: clampedFrom, | ||
| to: Math.max(clampedFrom, Math.min(to, doc.content.size)) | ||
| }; | ||
| } | ||
| function updateDecorationsInRanges({ | ||
| decorations, | ||
| ranges, | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) { | ||
| let next = decorations; | ||
| for (const range of ranges) { | ||
| const { from, to } = clampRange(range.from, range.to, doc); | ||
| const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to); | ||
| if (existing.length) { | ||
| next = next.remove(existing); | ||
| } | ||
| const newDecos = scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }); | ||
| if (newDecos.length) { | ||
| next = next.add(doc, newDecos); | ||
| } | ||
| } | ||
| return next; | ||
| /** | ||
| * Removes and rebuilds placeholder decorations within the given ranges. | ||
| * Only drops decorations fully contained in a range so mapped decorations | ||
| * on neighbouring blocks (e.g. at a block boundary) are kept intact. | ||
| */ | ||
| function updateDecorationsInRanges({ decorations, ranges, editor, options, dataAttribute, doc, selection }) { | ||
| let next = decorations; | ||
| for (const range of ranges) { | ||
| const { from, to } = clampRange(range.from, range.to, doc); | ||
| const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to); | ||
| if (existing.length) next = next.remove(existing); | ||
| const newDecos = scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }); | ||
| if (newDecos.length) next = next.add(doc, newDecos); | ||
| } | ||
| return next; | ||
| } | ||
| function createPlaceholderStateField({ | ||
| editor, | ||
| options, | ||
| dataAttribute | ||
| }) { | ||
| return { | ||
| init(_config, state) { | ||
| const decorations = buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: state.doc, | ||
| selection: state.selection | ||
| }); | ||
| return decorations != null ? decorations : import_view4.DecorationSet.empty; | ||
| }, | ||
| apply(tr, prev, oldState, newState) { | ||
| if (!tr.docChanged && !tr.selectionSet) { | ||
| return prev; | ||
| } | ||
| const mapped = prev.map(tr.mapping, tr.doc); | ||
| const ranges = collectRescanRanges(tr, oldState, newState); | ||
| return updateDecorationsInRanges({ | ||
| decorations: mapped, | ||
| ranges, | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: newState.doc, | ||
| selection: newState.selection | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Creates the incremental `StateField<DecorationSet>` used by the slow path | ||
| * (`showOnlyCurrent: false` or `includeChildren: true`). | ||
| * | ||
| * Decorations are mapped through each transaction and only recomputed for | ||
| * top-level blocks touched by document or selection changes. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @param options.dataAttribute - The prepared `data-*` attribute name. | ||
| * @returns A ProseMirror state field storing the placeholder decoration set. | ||
| */ | ||
| function createPlaceholderStateField({ editor, options, dataAttribute }) { | ||
| return { | ||
| init(_config, state) { | ||
| const decorations = buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: state.doc, | ||
| selection: state.selection | ||
| }); | ||
| return decorations !== null && decorations !== void 0 ? decorations : _tiptap_pm_view.DecorationSet.empty; | ||
| }, | ||
| apply(tr, prev, oldState, newState) { | ||
| if (!tr.docChanged && !tr.selectionSet) return prev; | ||
| return updateDecorationsInRanges({ | ||
| decorations: prev.map(tr.mapping, tr.doc), | ||
| ranges: collectRescanRanges(tr, oldState, newState), | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: newState.doc, | ||
| selection: newState.selection | ||
| }); | ||
| } | ||
| }; | ||
| } | ||
| // src/placeholder/utils/preparePlaceholderAttribute.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/preparePlaceholderAttribute.ts | ||
| /** | ||
| * Prepares the placeholder attribute by ensuring it is properly formatted. | ||
| * @param attr - The placeholder attribute string. | ||
| * @returns The prepared placeholder attribute string. | ||
| */ | ||
| function preparePlaceholderAttribute(attr) { | ||
| return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase(); | ||
| return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase(); | ||
| } | ||
| // src/placeholder/plugins/PlaceholderPlugin.ts | ||
| //#endregion | ||
| //#region src/placeholder/plugins/PlaceholderPlugin.ts | ||
| /** | ||
| * Creates the ProseMirror plugin that renders placeholder decorations. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @returns The configured placeholder plugin. | ||
| */ | ||
| function createPlaceholderPlugin({ editor, options }) { | ||
| const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| return new import_state4.Plugin({ | ||
| key: PLUGIN_KEY, | ||
| ...useResolvedPath ? {} : { | ||
| state: createPlaceholderStateField({ editor, options, dataAttribute }) | ||
| }, | ||
| props: { | ||
| decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) : (state) => { | ||
| var _a; | ||
| if (options.showOnlyWhenEditable && !editor.isEditable) { | ||
| return import_view5.DecorationSet.empty; | ||
| } | ||
| return (_a = PLUGIN_KEY.getState(state)) != null ? _a : import_view5.DecorationSet.empty; | ||
| } | ||
| } | ||
| }); | ||
| const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| return new _tiptap_pm_state.Plugin({ | ||
| key: PLUGIN_KEY, | ||
| ...useResolvedPath ? {} : { state: createPlaceholderStateField({ | ||
| editor, | ||
| options, | ||
| dataAttribute | ||
| }) }, | ||
| props: { decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) : (state) => { | ||
| var _PLUGIN_KEY$getState; | ||
| if (options.showOnlyWhenEditable && !editor.isEditable) return _tiptap_pm_view.DecorationSet.empty; | ||
| return (_PLUGIN_KEY$getState = PLUGIN_KEY.getState(state)) !== null && _PLUGIN_KEY$getState !== void 0 ? _PLUGIN_KEY$getState : _tiptap_pm_view.DecorationSet.empty; | ||
| } } | ||
| }); | ||
| } | ||
| // src/placeholder/placeholder.ts | ||
| var Placeholder = import_core7.Extension.create({ | ||
| name: "placeholder", | ||
| addOptions() { | ||
| return { | ||
| emptyEditorClass: "is-editor-empty", | ||
| emptyNodeClass: "is-empty", | ||
| dataAttribute: DEFAULT_DATA_ATTRIBUTE, | ||
| placeholder: "Write something \u2026", | ||
| showOnlyWhenEditable: true, | ||
| showOnlyCurrent: true, | ||
| includeChildren: false | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]; | ||
| } | ||
| //#endregion | ||
| //#region src/placeholder/placeholder.ts | ||
| /** | ||
| * This extension allows you to add a placeholder to your editor. | ||
| * A placeholder is a text that appears when the editor or a node is empty. | ||
| * @see https://www.tiptap.dev/api/extensions/placeholder | ||
| */ | ||
| const Placeholder = _tiptap_core.Extension.create({ | ||
| name: "placeholder", | ||
| addOptions() { | ||
| return { | ||
| emptyEditorClass: "is-editor-empty", | ||
| emptyNodeClass: "is-empty", | ||
| dataAttribute: DEFAULT_DATA_ATTRIBUTE, | ||
| placeholder: "Write something …", | ||
| showOnlyWhenEditable: true, | ||
| showOnlyCurrent: true, | ||
| includeChildren: false | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [createPlaceholderPlugin({ | ||
| editor: this.editor, | ||
| options: this.options | ||
| })]; | ||
| } | ||
| }); | ||
| // src/selection/selection.ts | ||
| var import_core8 = require("@tiptap/core"); | ||
| var import_state5 = require("@tiptap/pm/state"); | ||
| var import_view6 = require("@tiptap/pm/view"); | ||
| //#endregion | ||
| //#region src/selection/selection.ts | ||
| /** | ||
| * Whether the native browser selection should be cleared on blur and restored on focus. | ||
| * Only applies to non-empty text selections in an editable editor. | ||
| */ | ||
| function shouldSyncDomSelection(state, editor) { | ||
| return !state.selection.empty && !(0, import_core8.isNodeSelection)(state.selection) && editor.isEditable; | ||
| return !state.selection.empty && !(0, _tiptap_core.isNodeSelection)(state.selection) && editor.isEditable; | ||
| } | ||
| /** | ||
| * Whether the selection decoration should be rendered to keep the selection | ||
| * visible while the editor is blurred (and not dragging). | ||
| */ | ||
| function shouldPreserveSelection(state, editor) { | ||
| return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging; | ||
| return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging; | ||
| } | ||
| function clearDomSelection() { | ||
| var _a; | ||
| (_a = window.getSelection()) == null ? void 0 : _a.removeAllRanges(); | ||
| var _window$getSelection; | ||
| (_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 || _window$getSelection.removeAllRanges(); | ||
| } | ||
| /** | ||
| * Sync the native selection from the editor state. | ||
| * @see https://prosemirror.net/docs/ref/#view.EditorView.focus | ||
| */ | ||
| function restoreDomSelection(view) { | ||
| view.focus(); | ||
| view.focus(); | ||
| } | ||
| var Selection = import_core8.Extension.create({ | ||
| name: "selection", | ||
| addOptions() { | ||
| return { | ||
| className: "selection" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| const { editor, options } = this; | ||
| return [ | ||
| new import_state5.Plugin({ | ||
| key: new import_state5.PluginKey("selection"), | ||
| props: { | ||
| decorations(state) { | ||
| if (!shouldPreserveSelection(state, editor)) { | ||
| return null; | ||
| } | ||
| return import_view6.DecorationSet.create(state.doc, [ | ||
| import_view6.Decoration.inline(state.selection.from, state.selection.to, { | ||
| class: options.className | ||
| }) | ||
| ]); | ||
| }, | ||
| handleDOMEvents: { | ||
| blur(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) { | ||
| return false; | ||
| } | ||
| clearDomSelection(); | ||
| return false; | ||
| }, | ||
| focus(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) { | ||
| return false; | ||
| } | ||
| requestAnimationFrame(() => { | ||
| if (!editor.isDestroyed && view.hasFocus()) { | ||
| restoreDomSelection(view); | ||
| } | ||
| }); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| /** | ||
| * This extension allows you to add a class to the selected text when the editor is blurred. | ||
| * It clears the native browser selection on blur (so `::selection` styles do not overlap the | ||
| * decoration) and restores it when the editor is focused again. | ||
| * @see https://www.tiptap.dev/api/extensions/selection | ||
| */ | ||
| const Selection = _tiptap_core.Extension.create({ | ||
| name: "selection", | ||
| addOptions() { | ||
| return { className: "selection" }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| const { editor, options } = this; | ||
| return [new _tiptap_pm_state.Plugin({ | ||
| key: new _tiptap_pm_state.PluginKey("selection"), | ||
| props: { | ||
| decorations(state) { | ||
| if (!shouldPreserveSelection(state, editor)) return null; | ||
| return _tiptap_pm_view.DecorationSet.create(state.doc, [_tiptap_pm_view.Decoration.inline(state.selection.from, state.selection.to, { class: options.className })]); | ||
| }, | ||
| handleDOMEvents: { | ||
| blur(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) return false; | ||
| clearDomSelection(); | ||
| return false; | ||
| }, | ||
| focus(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) return false; | ||
| requestAnimationFrame(() => { | ||
| if (!editor.isDestroyed && view.hasFocus()) restoreDomSelection(view); | ||
| }); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| // src/trailing-node/trailing-node.ts | ||
| var import_core9 = require("@tiptap/core"); | ||
| var import_state6 = require("@tiptap/pm/state"); | ||
| var skipTrailingNodeMeta = "skipTrailingNode"; | ||
| function nodeEqualsType({ | ||
| types, | ||
| node | ||
| }) { | ||
| return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types; | ||
| //#endregion | ||
| //#region src/trailing-node/trailing-node.ts | ||
| const skipTrailingNodeMeta = "skipTrailingNode"; | ||
| function nodeEqualsType({ types, node }) { | ||
| return node && Array.isArray(types) && types.includes(node.type) || (node === null || node === void 0 ? void 0 : node.type) === types; | ||
| } | ||
| var TrailingNode = import_core9.Extension.create({ | ||
| name: "trailingNode", | ||
| addOptions() { | ||
| return { | ||
| node: void 0, | ||
| notAfter: [] | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| var _a; | ||
| const plugin = new import_state6.PluginKey(this.name); | ||
| const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph"; | ||
| const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name)); | ||
| return [ | ||
| new import_state6.Plugin({ | ||
| key: plugin, | ||
| appendTransaction: (transactions, __, state) => { | ||
| const { doc, tr, schema } = state; | ||
| const shouldInsertNodeAtEnd = plugin.getState(state); | ||
| const endPosition = doc.content.size; | ||
| const type = schema.nodes[defaultNode]; | ||
| if (transactions.some((transaction) => transaction.getMeta(skipTrailingNodeMeta))) { | ||
| return; | ||
| } | ||
| if (!shouldInsertNodeAtEnd) { | ||
| return; | ||
| } | ||
| return tr.insert(endPosition, type.create()); | ||
| }, | ||
| state: { | ||
| init: (_, state) => { | ||
| const lastNode = state.tr.doc.lastChild; | ||
| return !nodeEqualsType({ node: lastNode, types: disabledNodes }); | ||
| }, | ||
| apply: (tr, value) => { | ||
| if (!tr.docChanged) { | ||
| return value; | ||
| } | ||
| if (tr.getMeta("__uniqueIDTransaction")) { | ||
| return value; | ||
| } | ||
| const lastNode = tr.doc.lastChild; | ||
| return !nodeEqualsType({ node: lastNode, types: disabledNodes }); | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| /** | ||
| * This extension allows you to add an extra node at the end of the document. | ||
| * @see https://www.tiptap.dev/api/extensions/trailing-node | ||
| */ | ||
| const TrailingNode = _tiptap_core.Extension.create({ | ||
| name: "trailingNode", | ||
| addOptions() { | ||
| return { | ||
| node: void 0, | ||
| notAfter: [] | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| var _this$editor$schema$t; | ||
| const plugin = new _tiptap_pm_state.PluginKey(this.name); | ||
| const defaultNode = this.options.node || ((_this$editor$schema$t = this.editor.schema.topNodeType.contentMatch.defaultType) === null || _this$editor$schema$t === void 0 ? void 0 : _this$editor$schema$t.name) || "paragraph"; | ||
| const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name)); | ||
| return [new _tiptap_pm_state.Plugin({ | ||
| key: plugin, | ||
| appendTransaction: (transactions, __, state) => { | ||
| const { doc, tr, schema } = state; | ||
| const shouldInsertNodeAtEnd = plugin.getState(state); | ||
| const endPosition = doc.content.size; | ||
| const type = schema.nodes[defaultNode]; | ||
| if (transactions.some((transaction) => transaction.getMeta("skipTrailingNode"))) return; | ||
| if (!shouldInsertNodeAtEnd) return; | ||
| return tr.insert(endPosition, type.create()); | ||
| }, | ||
| state: { | ||
| init: (_, state) => { | ||
| const lastNode = state.tr.doc.lastChild; | ||
| return !nodeEqualsType({ | ||
| node: lastNode, | ||
| types: disabledNodes | ||
| }); | ||
| }, | ||
| apply: (tr, value) => { | ||
| if (!tr.docChanged) return value; | ||
| if (tr.getMeta("__uniqueIDTransaction")) return value; | ||
| const lastNode = tr.doc.lastChild; | ||
| return !nodeEqualsType({ | ||
| node: lastNode, | ||
| types: disabledNodes | ||
| }); | ||
| } | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| //#endregion | ||
| //#region src/undo-redo/undo-redo.ts | ||
| /** | ||
| * This extension allows you to undo and redo recent changes. | ||
| * @see https://www.tiptap.dev/api/extensions/undo-redo | ||
| * | ||
| * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove | ||
| * the `undo-redo` extension, as it is not compatible with the `collaboration` extension. | ||
| * | ||
| * `@tiptap/extension-collaboration` uses its own history implementation. | ||
| */ | ||
| const UndoRedo = _tiptap_core.Extension.create({ | ||
| name: "undoRedo", | ||
| addOptions() { | ||
| return { | ||
| depth: 100, | ||
| newGroupDelay: 500 | ||
| }; | ||
| }, | ||
| addCommands() { | ||
| return { | ||
| undo: () => ({ state, dispatch }) => { | ||
| return (0, _tiptap_pm_history.undo)(state, dispatch); | ||
| }, | ||
| redo: () => ({ state, dispatch }) => { | ||
| return (0, _tiptap_pm_history.redo)(state, dispatch); | ||
| } | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [(0, _tiptap_pm_history.history)(this.options)]; | ||
| }, | ||
| addKeyboardShortcuts() { | ||
| return { | ||
| "Mod-z": () => this.editor.commands.undo(), | ||
| "Shift-Mod-z": () => this.editor.commands.redo(), | ||
| "Mod-y": () => this.editor.commands.redo(), | ||
| "Mod-я": () => this.editor.commands.undo(), | ||
| "Shift-Mod-я": () => this.editor.commands.redo() | ||
| }; | ||
| } | ||
| }); | ||
| //#endregion | ||
| exports.CharacterCount = CharacterCount; | ||
| exports.DEFAULT_DATA_ATTRIBUTE = DEFAULT_DATA_ATTRIBUTE; | ||
| exports.Dropcursor = Dropcursor; | ||
| exports.Focus = Focus; | ||
| exports.Gapcursor = Gapcursor; | ||
| exports.PLUGIN_KEY = PLUGIN_KEY; | ||
| exports.Placeholder = Placeholder; | ||
| exports.Selection = Selection; | ||
| exports.TrailingNode = TrailingNode; | ||
| exports.UndoRedo = UndoRedo; | ||
| exports.preparePlaceholderAttribute = preparePlaceholderAttribute; | ||
| exports.skipTrailingNodeMeta = skipTrailingNodeMeta; | ||
| // src/undo-redo/undo-redo.ts | ||
| var import_core10 = require("@tiptap/core"); | ||
| var import_history = require("@tiptap/pm/history"); | ||
| var UndoRedo = import_core10.Extension.create({ | ||
| name: "undoRedo", | ||
| addOptions() { | ||
| return { | ||
| depth: 100, | ||
| newGroupDelay: 500 | ||
| }; | ||
| }, | ||
| addCommands() { | ||
| return { | ||
| undo: () => ({ state, dispatch }) => { | ||
| return (0, import_history.undo)(state, dispatch); | ||
| }, | ||
| redo: () => ({ state, dispatch }) => { | ||
| return (0, import_history.redo)(state, dispatch); | ||
| } | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [(0, import_history.history)(this.options)]; | ||
| }, | ||
| addKeyboardShortcuts() { | ||
| return { | ||
| "Mod-z": () => this.editor.commands.undo(), | ||
| "Shift-Mod-z": () => this.editor.commands.redo(), | ||
| "Mod-y": () => this.editor.commands.redo(), | ||
| // Russian keyboard layouts | ||
| "Mod-\u044F": () => this.editor.commands.undo(), | ||
| "Shift-Mod-\u044F": () => this.editor.commands.redo() | ||
| }; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| CharacterCount, | ||
| DEFAULT_DATA_ATTRIBUTE, | ||
| Dropcursor, | ||
| Focus, | ||
| Gapcursor, | ||
| PLUGIN_KEY, | ||
| Placeholder, | ||
| Selection, | ||
| TrailingNode, | ||
| UndoRedo, | ||
| preparePlaceholderAttribute, | ||
| skipTrailingNodeMeta | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.ts","../src/character-count/character-count.ts","../src/drop-cursor/drop-cursor.ts","../src/focus/focus.ts","../src/gap-cursor/gap-cursor.ts","../src/placeholder/constants.ts","../src/placeholder/placeholder.ts","../src/placeholder/plugins/PlaceholderPlugin.ts","../src/placeholder/utils/buildPlaceholderDecorations.ts","../src/placeholder/utils/createPlaceholderDecoration.ts","../src/placeholder/utils/placeholderStateField.ts","../src/placeholder/utils/resolveTopLevelRange.ts","../src/placeholder/utils/preparePlaceholderAttribute.ts","../src/selection/selection.ts","../src/trailing-node/trailing-node.ts","../src/undo-redo/undo-redo.ts"],"sourcesContent":["export * from './character-count/index.js'\nexport * from './drop-cursor/index.js'\nexport * from './focus/index.js'\nexport * from './gap-cursor/index.js'\nexport * from './placeholder/index.js'\nexport * from './selection/index.js'\nexport * from './trailing-node/index.js'\nexport * from './undo-redo/index.js'\n","import { Extension } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport interface CharacterCountOptions {\n /**\n * The maximum number of characters that should be allowed. Defaults to `0`.\n * @default null\n * @example 180\n */\n limit: number | null | undefined\n /**\n * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n * If set to `nodeSize`, the nodeSize of the document is used.\n * @default 'textSize'\n * @example 'textSize'\n */\n mode: 'textSize' | 'nodeSize'\n /**\n * Sets whether the content will be automatically trimmed when programatically setting content over the limit.\n * If set to false, the user will be able to trim the text manually.\n * @default true\n * @example false\n */\n autoTrim?: boolean\n /**\n * The text counter function to use. Defaults to a simple character count.\n * @default (text) => text.length\n * @example (text) => [...new Intl.Segmenter().segment(text)].length\n */\n textCounter: (text: string) => number\n /**\n * The word counter function to use. Defaults to a simple word count.\n * @default (text) => text.split(' ').filter(word => word !== '').length\n * @example (text) => text.split(/\\s+/).filter(word => word !== '').length\n */\n wordCounter: (text: string) => number\n}\n\nexport interface CharacterCountStorage {\n /**\n * Get the number of characters for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the characters from. Defaults to the current document.\n * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n */\n characters: (options?: { node?: ProseMirrorNode; mode?: 'textSize' | 'nodeSize' }) => number\n\n /**\n * Get the number of words for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the words from. Defaults to the current document.\n */\n words: (options?: { node?: ProseMirrorNode }) => number\n}\n\ndeclare module '@tiptap/core' {\n interface Storage {\n characterCount: CharacterCountStorage\n }\n}\n\n/**\n * This extension allows you to count the characters and words of your document.\n * @see https://tiptap.dev/api/extensions/character-count\n */\nexport const CharacterCount = Extension.create<CharacterCountOptions, CharacterCountStorage>({\n name: 'characterCount',\n\n addOptions() {\n return {\n limit: null,\n autoTrim: true,\n mode: 'textSize',\n textCounter: text => text.length,\n wordCounter: text => text.split(' ').filter(word => word !== '').length,\n }\n },\n\n addStorage() {\n return {\n characters: () => 0,\n words: () => 0,\n }\n },\n\n onBeforeCreate() {\n this.storage.characters = options => {\n const node = options?.node || this.editor.state.doc\n const mode = options?.mode || this.options.mode\n\n if (mode === 'textSize') {\n const text = node.textBetween(0, node.content.size, undefined, ' ')\n\n return this.options.textCounter(text)\n }\n\n return node.nodeSize\n }\n\n this.storage.words = options => {\n const node = options?.node || this.editor.state.doc\n const text = node.textBetween(0, node.content.size, ' ', ' ')\n\n return this.options.wordCounter(text)\n }\n },\n\n addProseMirrorPlugins() {\n let initialEvaluationDone = false\n\n return [\n new Plugin({\n key: new PluginKey('characterCount'),\n appendTransaction: (transactions, oldState, newState) => {\n if (initialEvaluationDone) {\n return\n }\n\n const limit = this.options.limit\n const autoTrim = this.options.autoTrim\n\n if (limit === null || limit === undefined || limit === 0 || autoTrim === false) {\n initialEvaluationDone = true\n return\n }\n\n const initialContentSize = this.storage.characters({ node: newState.doc })\n\n if (initialContentSize > limit) {\n const over = initialContentSize - limit\n const from = 0\n const to = over\n\n console.warn(\n `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`,\n )\n const tr = newState.tr.deleteRange(from, to)\n initialEvaluationDone = true\n return tr\n }\n\n initialEvaluationDone = true\n },\n filterTransaction: (transaction, state) => {\n const limit = this.options.limit\n\n // Nothing has changed or no limit is defined. Ignore it.\n if (!transaction.docChanged || limit === 0 || limit === null || limit === undefined) {\n return true\n }\n\n const oldSize = this.storage.characters({ node: state.doc })\n const newSize = this.storage.characters({ node: transaction.doc })\n\n // Everything is in the limit. Good.\n if (newSize <= limit) {\n return true\n }\n\n // The limit has already been exceeded but will be reduced.\n if (oldSize > limit && newSize > limit && newSize <= oldSize) {\n return true\n }\n\n // The limit has already been exceeded and will be increased further.\n if (oldSize > limit && newSize > limit && newSize > oldSize) {\n return false\n }\n\n const isPaste = transaction.getMeta('paste')\n\n // Block all exceeding transactions that were not pasted.\n if (!isPaste) {\n return false\n }\n\n // For pasted content, we try to remove the exceeding content.\n const pos = transaction.selection.$head.pos\n const over = newSize - limit\n const from = pos - over\n const to = pos\n\n // It’s probably a bad idea to mutate transactions within `filterTransaction`\n // but for now this is working fine.\n transaction.deleteRange(from, to)\n\n // In some situations, the limit will continue to be exceeded after trimming.\n // This happens e.g. when truncating within a complex node (e.g. table)\n // and ProseMirror has to close this node again.\n // If this is the case, we prevent the transaction completely.\n const updatedSize = this.storage.characters({ node: transaction.doc })\n\n if (updatedSize > limit) {\n return false\n }\n\n return true\n },\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { dropCursor } from '@tiptap/pm/dropcursor'\n\nexport interface DropcursorOptions {\n /**\n * The color of the drop cursor. Use `false` to apply no color and rely only on class.\n * @default 'currentColor'\n * @example 'red'\n */\n color?: string | false\n\n /**\n * The width of the drop cursor\n * @default 1\n * @example 2\n */\n width: number | undefined\n\n /**\n * The class of the drop cursor\n * @default undefined\n * @example 'drop-cursor'\n */\n class: string | undefined\n}\n\n/**\n * This extension allows you to add a drop cursor to your editor.\n * A drop cursor is a line that appears when you drag and drop content\n * in-between nodes.\n * @see https://tiptap.dev/api/extensions/dropcursor\n */\nexport const Dropcursor = Extension.create<DropcursorOptions>({\n name: 'dropCursor',\n\n addOptions() {\n return {\n color: 'currentColor',\n width: 1,\n class: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n return [dropCursor(this.options)]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport interface FocusOptions {\n /**\n * The class name that should be added to the focused node.\n * @default 'has-focus'\n * @example 'is-focused'\n */\n className: string\n\n /**\n * The mode by which the focused node is determined.\n * - All: All nodes are marked as focused.\n * - Deepest: Only the deepest node is marked as focused.\n * - Shallowest: Only the shallowest node is marked as focused.\n *\n * @default 'all'\n * @example 'deepest'\n * @example 'shallowest'\n */\n mode: 'all' | 'deepest' | 'shallowest'\n}\n\n/**\n * This extension allows you to add a class to the focused node.\n * @see https://www.tiptap.dev/api/extensions/focus\n */\nexport const Focus = Extension.create<FocusOptions>({\n name: 'focus',\n\n addOptions() {\n return {\n className: 'has-focus',\n mode: 'all',\n }\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('focus'),\n props: {\n decorations: ({ doc, selection }) => {\n const { isEditable, isFocused } = this.editor\n const { anchor } = selection\n const decorations: Decoration[] = []\n\n if (!isEditable || !isFocused) {\n return DecorationSet.create(doc, [])\n }\n\n // Maximum Levels\n let maxLevels = 0\n\n if (this.options.mode === 'deepest') {\n doc.descendants((node, pos) => {\n if (node.isText) {\n return\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n maxLevels += 1\n })\n }\n\n // Loop through current\n let currentLevel = 0\n\n doc.descendants((node, pos) => {\n if (node.isText) {\n return false\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n currentLevel += 1\n\n const outOfScope =\n (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) ||\n (this.options.mode === 'shallowest' && currentLevel > 1)\n\n if (outOfScope) {\n return this.options.mode === 'deepest'\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: this.options.className,\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n },\n },\n }),\n ]\n },\n})\n","import type { ParentConfig } from '@tiptap/core'\nimport { callOrReturn, Extension, getExtensionField } from '@tiptap/core'\nimport { gapCursor } from '@tiptap/pm/gapcursor'\n\ndeclare module '@tiptap/core' {\n interface NodeConfig<Options, Storage> {\n /**\n * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`.\n * @default null\n */\n allowGapCursor?:\n | boolean\n | null\n | ((this: {\n name: string\n options: Options\n storage: Storage\n parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']\n }) => boolean | null)\n }\n}\n\n/**\n * This extension allows you to add a gap cursor to your editor.\n * A gap cursor is a cursor that appears when you click on a place\n * where no content is present, for example inbetween nodes.\n * @see https://tiptap.dev/api/extensions/gapcursor\n */\nexport const Gapcursor = Extension.create({\n name: 'gapCursor',\n\n addProseMirrorPlugins() {\n return [gapCursor()]\n },\n\n extendNodeSchema(extension) {\n const context = {\n name: extension.name,\n options: extension.options,\n storage: extension.storage,\n }\n\n return {\n allowGapCursor: callOrReturn(getExtensionField(extension, 'allowGapCursor', context)) ?? null,\n }\n },\n})\n","import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n","import { Extension, isNodeSelection, type Editor } from '@tiptap/core'\nimport { Plugin, PluginKey, type EditorState } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport type SelectionOptions = {\n /**\n * The class name that should be added to the selected text.\n * @default 'selection'\n * @example 'is-selected'\n */\n className: string\n}\n\n/**\n * Whether the native browser selection should be cleared on blur and restored on focus.\n * Only applies to non-empty text selections in an editable editor.\n */\nfunction shouldSyncDomSelection(state: EditorState, editor: Editor): boolean {\n return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable\n}\n\n/**\n * Whether the selection decoration should be rendered to keep the selection\n * visible while the editor is blurred (and not dragging).\n */\nfunction shouldPreserveSelection(state: EditorState, editor: Editor): boolean {\n return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging\n}\n\nfunction clearDomSelection() {\n window.getSelection()?.removeAllRanges()\n}\n\n/**\n * Sync the native selection from the editor state.\n * @see https://prosemirror.net/docs/ref/#view.EditorView.focus\n */\nfunction restoreDomSelection(view: EditorView) {\n view.focus()\n}\n\n/**\n * This extension allows you to add a class to the selected text when the editor is blurred.\n * It clears the native browser selection on blur (so `::selection` styles do not overlap the\n * decoration) and restores it when the editor is focused again.\n * @see https://www.tiptap.dev/api/extensions/selection\n */\nexport const Selection = Extension.create<SelectionOptions>({\n name: 'selection',\n\n addOptions() {\n return {\n className: 'selection',\n }\n },\n\n addProseMirrorPlugins() {\n const { editor, options } = this\n\n return [\n new Plugin({\n key: new PluginKey('selection'),\n props: {\n decorations(state) {\n if (!shouldPreserveSelection(state, editor)) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(state.selection.from, state.selection.to, {\n class: options.className,\n }),\n ])\n },\n handleDOMEvents: {\n blur(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n clearDomSelection()\n\n return false\n },\n focus(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n requestAnimationFrame(() => {\n if (!editor.isDestroyed && view.hasFocus()) {\n restoreDomSelection(view)\n }\n })\n\n return false\n },\n },\n },\n }),\n ]\n },\n})\n\nexport default Selection\n","import { Extension } from '@tiptap/core'\nimport type { Node, NodeType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport const skipTrailingNodeMeta = 'skipTrailingNode'\n\nfunction nodeEqualsType({\n types,\n node,\n}: {\n types: NodeType | NodeType[]\n node: Node | null | undefined\n}) {\n return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types\n}\n\n/**\n * Extension based on:\n * - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js\n * - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts\n */\n\nexport interface TrailingNodeOptions {\n /**\n * The node type that should be inserted at the end of the document.\n * @note the node will always be added to the `notAfter` lists to\n * prevent an infinite loop.\n * @default undefined\n */\n node?: string\n /**\n * The node types after which the trailing node should not be inserted.\n * @default ['paragraph']\n */\n notAfter?: string | string[]\n}\n\n/**\n * This extension allows you to add an extra node at the end of the document.\n * @see https://www.tiptap.dev/api/extensions/trailing-node\n */\nexport const TrailingNode = Extension.create<TrailingNodeOptions>({\n name: 'trailingNode',\n\n addOptions() {\n return {\n node: undefined,\n notAfter: [],\n }\n },\n\n addProseMirrorPlugins() {\n const plugin = new PluginKey(this.name)\n const defaultNode =\n this.options.node ||\n this.editor.schema.topNodeType.contentMatch.defaultType?.name ||\n 'paragraph'\n\n const disabledNodes = Object.entries(this.editor.schema.nodes)\n .map(([, value]) => value)\n .filter(node => (this.options.notAfter || []).concat(defaultNode).includes(node.name))\n\n return [\n new Plugin({\n key: plugin,\n appendTransaction: (transactions, __, state) => {\n const { doc, tr, schema } = state\n const shouldInsertNodeAtEnd = plugin.getState(state)\n const endPosition = doc.content.size\n const type = schema.nodes[defaultNode]\n\n if (transactions.some(transaction => transaction.getMeta(skipTrailingNodeMeta))) {\n return\n }\n\n if (!shouldInsertNodeAtEnd) {\n return\n }\n\n return tr.insert(endPosition, type.create())\n },\n state: {\n init: (_, state) => {\n const lastNode = state.tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n apply: (tr, value) => {\n if (!tr.docChanged) {\n return value\n }\n\n // Ignore transactions from UniqueID extension to prevent infinite loops\n // when UniqueID adds IDs to newly inserted trailing nodes\n if (tr.getMeta('__uniqueIDTransaction')) {\n return value\n }\n\n const lastNode = tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n },\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { history, redo, undo } from '@tiptap/pm/history'\n\nexport interface UndoRedoOptions {\n /**\n * The amount of history events that are collected before the oldest events are discarded.\n * @default 100\n * @example 50\n */\n depth: number\n\n /**\n * The delay (in milliseconds) between changes after which a new group should be started.\n * @default 500\n * @example 1000\n */\n newGroupDelay: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n undoRedo: {\n /**\n * Undo recent changes\n * @example editor.commands.undo()\n */\n undo: () => ReturnType\n /**\n * Reapply reverted changes\n * @example editor.commands.redo()\n */\n redo: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to undo and redo recent changes.\n * @see https://www.tiptap.dev/api/extensions/undo-redo\n *\n * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove\n * the `undo-redo` extension, as it is not compatible with the `collaboration` extension.\n *\n * `@tiptap/extension-collaboration` uses its own history implementation.\n */\nexport const UndoRedo = Extension.create<UndoRedoOptions>({\n name: 'undoRedo',\n\n addOptions() {\n return {\n depth: 100,\n newGroupDelay: 500,\n }\n },\n\n addCommands() {\n return {\n undo:\n () =>\n ({ state, dispatch }) => {\n return undo(state, dispatch)\n },\n redo:\n () =>\n ({ state, dispatch }) => {\n return redo(state, dispatch)\n },\n }\n },\n\n addProseMirrorPlugins() {\n return [history(this.options)]\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-z': () => this.editor.commands.undo(),\n 'Shift-Mod-z': () => this.editor.commands.redo(),\n 'Mod-y': () => this.editor.commands.redo(),\n\n // Russian keyboard layouts\n 'Mod-я': () => this.editor.commands.undo(),\n 'Shift-Mod-я': () => this.editor.commands.redo(),\n }\n },\n})\n"],"mappings":"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","names":["import_core","import_core","import_state","import_core","import_state","import_core","import_state","import_view","import_core","import_view","import_view","import_core","import_view","node","import_core","import_state","import_view","import_core","import_state","import_core"]} | ||
| {"version":3,"file":"index.cjs","names":["Extension","Plugin","PluginKey","Extension","Extension","Plugin","PluginKey","DecorationSet","Decoration","Extension","PluginKey","Decoration","DecorationSet","DecorationSet","Plugin","DecorationSet","Extension","Extension","Plugin","PluginKey","DecorationSet","Decoration","Extension","PluginKey","Plugin","Extension"],"sources":["../src/character-count/character-count.ts","../src/drop-cursor/drop-cursor.ts","../src/focus/focus.ts","../src/gap-cursor/gap-cursor.ts","../src/placeholder/constants.ts","../src/placeholder/utils/createPlaceholderDecoration.ts","../src/placeholder/utils/buildPlaceholderDecorations.ts","../src/placeholder/utils/resolveTopLevelRange.ts","../src/placeholder/utils/placeholderStateField.ts","../src/placeholder/utils/preparePlaceholderAttribute.ts","../src/placeholder/plugins/PlaceholderPlugin.ts","../src/placeholder/placeholder.ts","../src/selection/selection.ts","../src/trailing-node/trailing-node.ts","../src/undo-redo/undo-redo.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport interface CharacterCountOptions {\n /**\n * The maximum number of characters that should be allowed. Defaults to `0`.\n * @default null\n * @example 180\n */\n limit: number | null | undefined\n /**\n * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n * If set to `nodeSize`, the nodeSize of the document is used.\n * @default 'textSize'\n * @example 'textSize'\n */\n mode: 'textSize' | 'nodeSize'\n /**\n * Sets whether the content will be automatically trimmed when programatically setting content over the limit.\n * If set to false, the user will be able to trim the text manually.\n * @default true\n * @example false\n */\n autoTrim?: boolean\n /**\n * The text counter function to use. Defaults to a simple character count.\n * @default (text) => text.length\n * @example (text) => [...new Intl.Segmenter().segment(text)].length\n */\n textCounter: (text: string) => number\n /**\n * The word counter function to use. Defaults to a simple word count.\n * @default (text) => text.split(' ').filter(word => word !== '').length\n * @example (text) => text.split(/\\s+/).filter(word => word !== '').length\n */\n wordCounter: (text: string) => number\n}\n\nexport interface CharacterCountStorage {\n /**\n * Get the number of characters for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the characters from. Defaults to the current document.\n * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n */\n characters: (options?: { node?: ProseMirrorNode; mode?: 'textSize' | 'nodeSize' }) => number\n\n /**\n * Get the number of words for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the words from. Defaults to the current document.\n */\n words: (options?: { node?: ProseMirrorNode }) => number\n}\n\ndeclare module '@tiptap/core' {\n interface Storage {\n characterCount: CharacterCountStorage\n }\n}\n\n/**\n * This extension allows you to count the characters and words of your document.\n * @see https://tiptap.dev/api/extensions/character-count\n */\nexport const CharacterCount = Extension.create<CharacterCountOptions, CharacterCountStorage>({\n name: 'characterCount',\n\n addOptions() {\n return {\n limit: null,\n autoTrim: true,\n mode: 'textSize',\n textCounter: text => text.length,\n wordCounter: text => text.split(' ').filter(word => word !== '').length,\n }\n },\n\n addStorage() {\n return {\n characters: () => 0,\n words: () => 0,\n }\n },\n\n onBeforeCreate() {\n this.storage.characters = options => {\n const node = options?.node || this.editor.state.doc\n const mode = options?.mode || this.options.mode\n\n if (mode === 'textSize') {\n const text = node.textBetween(0, node.content.size, undefined, ' ')\n\n return this.options.textCounter(text)\n }\n\n return node.nodeSize\n }\n\n this.storage.words = options => {\n const node = options?.node || this.editor.state.doc\n const text = node.textBetween(0, node.content.size, ' ', ' ')\n\n return this.options.wordCounter(text)\n }\n },\n\n addProseMirrorPlugins() {\n let initialEvaluationDone = false\n\n return [\n new Plugin({\n key: new PluginKey('characterCount'),\n appendTransaction: (transactions, oldState, newState) => {\n if (initialEvaluationDone) {\n return\n }\n\n const limit = this.options.limit\n const autoTrim = this.options.autoTrim\n\n if (limit === null || limit === undefined || limit === 0 || autoTrim === false) {\n initialEvaluationDone = true\n return\n }\n\n const initialContentSize = this.storage.characters({ node: newState.doc })\n\n if (initialContentSize > limit) {\n const over = initialContentSize - limit\n const from = 0\n const to = over\n\n console.warn(\n `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`,\n )\n const tr = newState.tr.deleteRange(from, to)\n initialEvaluationDone = true\n return tr\n }\n\n initialEvaluationDone = true\n },\n filterTransaction: (transaction, state) => {\n const limit = this.options.limit\n\n // Nothing has changed or no limit is defined. Ignore it.\n if (!transaction.docChanged || limit === 0 || limit === null || limit === undefined) {\n return true\n }\n\n const oldSize = this.storage.characters({ node: state.doc })\n const newSize = this.storage.characters({ node: transaction.doc })\n\n // Everything is in the limit. Good.\n if (newSize <= limit) {\n return true\n }\n\n // The limit has already been exceeded but will be reduced.\n if (oldSize > limit && newSize > limit && newSize <= oldSize) {\n return true\n }\n\n // The limit has already been exceeded and will be increased further.\n if (oldSize > limit && newSize > limit && newSize > oldSize) {\n return false\n }\n\n const isPaste = transaction.getMeta('paste')\n\n // Block all exceeding transactions that were not pasted.\n if (!isPaste) {\n return false\n }\n\n // For pasted content, we try to remove the exceeding content.\n const pos = transaction.selection.$head.pos\n const over = newSize - limit\n const from = pos - over\n const to = pos\n\n // It’s probably a bad idea to mutate transactions within `filterTransaction`\n // but for now this is working fine.\n transaction.deleteRange(from, to)\n\n // In some situations, the limit will continue to be exceeded after trimming.\n // This happens e.g. when truncating within a complex node (e.g. table)\n // and ProseMirror has to close this node again.\n // If this is the case, we prevent the transaction completely.\n const updatedSize = this.storage.characters({ node: transaction.doc })\n\n if (updatedSize > limit) {\n return false\n }\n\n return true\n },\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { dropCursor } from '@tiptap/pm/dropcursor'\n\nexport interface DropcursorOptions {\n /**\n * The color of the drop cursor. Use `false` to apply no color and rely only on class.\n * @default 'currentColor'\n * @example 'red'\n */\n color?: string | false\n\n /**\n * The width of the drop cursor\n * @default 1\n * @example 2\n */\n width: number | undefined\n\n /**\n * The class of the drop cursor\n * @default undefined\n * @example 'drop-cursor'\n */\n class: string | undefined\n}\n\n/**\n * This extension allows you to add a drop cursor to your editor.\n * A drop cursor is a line that appears when you drag and drop content\n * in-between nodes.\n * @see https://tiptap.dev/api/extensions/dropcursor\n */\nexport const Dropcursor = Extension.create<DropcursorOptions>({\n name: 'dropCursor',\n\n addOptions() {\n return {\n color: 'currentColor',\n width: 1,\n class: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n return [dropCursor(this.options)]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport interface FocusOptions {\n /**\n * The class name that should be added to the focused node.\n * @default 'has-focus'\n * @example 'is-focused'\n */\n className: string\n\n /**\n * The mode by which the focused node is determined.\n * - All: All nodes are marked as focused.\n * - Deepest: Only the deepest node is marked as focused.\n * - Shallowest: Only the shallowest node is marked as focused.\n *\n * @default 'all'\n * @example 'deepest'\n * @example 'shallowest'\n */\n mode: 'all' | 'deepest' | 'shallowest'\n}\n\n/**\n * This extension allows you to add a class to the focused node.\n * @see https://www.tiptap.dev/api/extensions/focus\n */\nexport const Focus = Extension.create<FocusOptions>({\n name: 'focus',\n\n addOptions() {\n return {\n className: 'has-focus',\n mode: 'all',\n }\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('focus'),\n props: {\n decorations: ({ doc, selection }) => {\n const { isEditable, isFocused } = this.editor\n const { anchor } = selection\n const decorations: Decoration[] = []\n\n if (!isEditable || !isFocused) {\n return DecorationSet.create(doc, [])\n }\n\n // Maximum Levels\n let maxLevels = 0\n\n if (this.options.mode === 'deepest') {\n doc.descendants((node, pos) => {\n if (node.isText) {\n return\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n maxLevels += 1\n })\n }\n\n // Loop through current\n let currentLevel = 0\n\n doc.descendants((node, pos) => {\n if (node.isText) {\n return false\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n currentLevel += 1\n\n const outOfScope =\n (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) ||\n (this.options.mode === 'shallowest' && currentLevel > 1)\n\n if (outOfScope) {\n return this.options.mode === 'deepest'\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: this.options.className,\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n },\n },\n }),\n ]\n },\n})\n","import type { ParentConfig } from '@tiptap/core'\nimport { callOrReturn, Extension, getExtensionField } from '@tiptap/core'\nimport { gapCursor } from '@tiptap/pm/gapcursor'\n\ndeclare module '@tiptap/core' {\n interface NodeConfig<Options, Storage> {\n /**\n * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`.\n * @default null\n */\n allowGapCursor?:\n | boolean\n | null\n | ((this: {\n name: string\n options: Options\n storage: Storage\n parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']\n }) => boolean | null)\n }\n}\n\n/**\n * This extension allows you to add a gap cursor to your editor.\n * A gap cursor is a cursor that appears when you click on a place\n * where no content is present, for example inbetween nodes.\n * @see https://tiptap.dev/api/extensions/gapcursor\n */\nexport const Gapcursor = Extension.create({\n name: 'gapCursor',\n\n addProseMirrorPlugins() {\n return [gapCursor()]\n },\n\n extendNodeSchema(extension) {\n const context = {\n name: extension.name,\n options: extension.options,\n storage: extension.storage,\n }\n\n return {\n allowGapCursor: callOrReturn(getExtensionField(extension, 'allowGapCursor', context)) ?? null,\n }\n },\n})\n","import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n","import { Extension, isNodeSelection, type Editor } from '@tiptap/core'\nimport { Plugin, PluginKey, type EditorState } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport type SelectionOptions = {\n /**\n * The class name that should be added to the selected text.\n * @default 'selection'\n * @example 'is-selected'\n */\n className: string\n}\n\n/**\n * Whether the native browser selection should be cleared on blur and restored on focus.\n * Only applies to non-empty text selections in an editable editor.\n */\nfunction shouldSyncDomSelection(state: EditorState, editor: Editor): boolean {\n return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable\n}\n\n/**\n * Whether the selection decoration should be rendered to keep the selection\n * visible while the editor is blurred (and not dragging).\n */\nfunction shouldPreserveSelection(state: EditorState, editor: Editor): boolean {\n return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging\n}\n\nfunction clearDomSelection() {\n window.getSelection()?.removeAllRanges()\n}\n\n/**\n * Sync the native selection from the editor state.\n * @see https://prosemirror.net/docs/ref/#view.EditorView.focus\n */\nfunction restoreDomSelection(view: EditorView) {\n view.focus()\n}\n\n/**\n * This extension allows you to add a class to the selected text when the editor is blurred.\n * It clears the native browser selection on blur (so `::selection` styles do not overlap the\n * decoration) and restores it when the editor is focused again.\n * @see https://www.tiptap.dev/api/extensions/selection\n */\nexport const Selection = Extension.create<SelectionOptions>({\n name: 'selection',\n\n addOptions() {\n return {\n className: 'selection',\n }\n },\n\n addProseMirrorPlugins() {\n const { editor, options } = this\n\n return [\n new Plugin({\n key: new PluginKey('selection'),\n props: {\n decorations(state) {\n if (!shouldPreserveSelection(state, editor)) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(state.selection.from, state.selection.to, {\n class: options.className,\n }),\n ])\n },\n handleDOMEvents: {\n blur(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n clearDomSelection()\n\n return false\n },\n focus(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n requestAnimationFrame(() => {\n if (!editor.isDestroyed && view.hasFocus()) {\n restoreDomSelection(view)\n }\n })\n\n return false\n },\n },\n },\n }),\n ]\n },\n})\n\nexport default Selection\n","import { Extension } from '@tiptap/core'\nimport type { Node, NodeType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport const skipTrailingNodeMeta = 'skipTrailingNode'\n\nfunction nodeEqualsType({\n types,\n node,\n}: {\n types: NodeType | NodeType[]\n node: Node | null | undefined\n}) {\n return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types\n}\n\n/**\n * Extension based on:\n * - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js\n * - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts\n */\n\nexport interface TrailingNodeOptions {\n /**\n * The node type that should be inserted at the end of the document.\n * @note the node will always be added to the `notAfter` lists to\n * prevent an infinite loop.\n * @default undefined\n */\n node?: string\n /**\n * The node types after which the trailing node should not be inserted.\n * @default ['paragraph']\n */\n notAfter?: string | string[]\n}\n\n/**\n * This extension allows you to add an extra node at the end of the document.\n * @see https://www.tiptap.dev/api/extensions/trailing-node\n */\nexport const TrailingNode = Extension.create<TrailingNodeOptions>({\n name: 'trailingNode',\n\n addOptions() {\n return {\n node: undefined,\n notAfter: [],\n }\n },\n\n addProseMirrorPlugins() {\n const plugin = new PluginKey(this.name)\n const defaultNode =\n this.options.node ||\n this.editor.schema.topNodeType.contentMatch.defaultType?.name ||\n 'paragraph'\n\n const disabledNodes = Object.entries(this.editor.schema.nodes)\n .map(([, value]) => value)\n .filter(node => (this.options.notAfter || []).concat(defaultNode).includes(node.name))\n\n return [\n new Plugin({\n key: plugin,\n appendTransaction: (transactions, __, state) => {\n const { doc, tr, schema } = state\n const shouldInsertNodeAtEnd = plugin.getState(state)\n const endPosition = doc.content.size\n const type = schema.nodes[defaultNode]\n\n if (transactions.some(transaction => transaction.getMeta(skipTrailingNodeMeta))) {\n return\n }\n\n if (!shouldInsertNodeAtEnd) {\n return\n }\n\n return tr.insert(endPosition, type.create())\n },\n state: {\n init: (_, state) => {\n const lastNode = state.tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n apply: (tr, value) => {\n if (!tr.docChanged) {\n return value\n }\n\n // Ignore transactions from UniqueID extension to prevent infinite loops\n // when UniqueID adds IDs to newly inserted trailing nodes\n if (tr.getMeta('__uniqueIDTransaction')) {\n return value\n }\n\n const lastNode = tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n },\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { history, redo, undo } from '@tiptap/pm/history'\n\nexport interface UndoRedoOptions {\n /**\n * The amount of history events that are collected before the oldest events are discarded.\n * @default 100\n * @example 50\n */\n depth: number\n\n /**\n * The delay (in milliseconds) between changes after which a new group should be started.\n * @default 500\n * @example 1000\n */\n newGroupDelay: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n undoRedo: {\n /**\n * Undo recent changes\n * @example editor.commands.undo()\n */\n undo: () => ReturnType\n /**\n * Reapply reverted changes\n * @example editor.commands.redo()\n */\n redo: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to undo and redo recent changes.\n * @see https://www.tiptap.dev/api/extensions/undo-redo\n *\n * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove\n * the `undo-redo` extension, as it is not compatible with the `collaboration` extension.\n *\n * `@tiptap/extension-collaboration` uses its own history implementation.\n */\nexport const UndoRedo = Extension.create<UndoRedoOptions>({\n name: 'undoRedo',\n\n addOptions() {\n return {\n depth: 100,\n newGroupDelay: 500,\n }\n },\n\n addCommands() {\n return {\n undo:\n () =>\n ({ state, dispatch }) => {\n return undo(state, dispatch)\n },\n redo:\n () =>\n ({ state, dispatch }) => {\n return redo(state, dispatch)\n },\n }\n },\n\n addProseMirrorPlugins() {\n return [history(this.options)]\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-z': () => this.editor.commands.undo(),\n 'Shift-Mod-z': () => this.editor.commands.redo(),\n 'Mod-y': () => this.editor.commands.redo(),\n\n // Russian keyboard layouts\n 'Mod-я': () => this.editor.commands.undo(),\n 'Shift-Mod-я': () => this.editor.commands.redo(),\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;AAkEA,MAAa,iBAAiBA,aAAAA,UAAU,OAAqD;CAC3F,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,UAAU;GACV,MAAM;GACN,cAAa,SAAQ,KAAK;GAC1B,cAAa,SAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,QAAO,SAAQ,SAAS,EAAE,CAAC,CAAC;EACnE;CACF;CAEA,aAAa;EACX,OAAO;GACL,kBAAkB;GAClB,aAAa;EACf;CACF;CAEA,iBAAiB;EACf,KAAK,QAAQ,cAAa,YAAW;GACnC,MAAM,QAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAO,QAAS,SAAQ,KAAK,OAAO,MAAM;GAGhD,MAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAFa,QAAS,SAAQ,KAAK,QAAQ,UAE9B,YAAY;IACvB,MAAM,OAAO,KAAK,YAAY,GAAG,KAAK,QAAQ,MAAM,KAAA,GAAW,GAAG;IAElE,OAAO,KAAK,QAAQ,YAAY,IAAI;GACtC;GAEA,OAAO,KAAK;EACd;EAEA,KAAK,QAAQ,SAAQ,YAAW;GAC9B,MAAM,QAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAO,QAAS,SAAQ,KAAK,OAAO,MAAM;GAChD,MAAM,OAAO,KAAK,YAAY,GAAG,KAAK,QAAQ,MAAM,KAAK,GAAG;GAE5D,OAAO,KAAK,QAAQ,YAAY,IAAI;EACtC;CACF;CAEA,wBAAwB;EACtB,IAAI,wBAAwB;EAE5B,OAAO,CACL,IAAIC,iBAAAA,OAAO;GACT,KAAK,IAAIC,iBAAAA,UAAU,gBAAgB;GACnC,oBAAoB,cAAc,UAAU,aAAa;IACvD,IAAI,uBACF;IAGF,MAAM,QAAQ,KAAK,QAAQ;IAC3B,MAAM,WAAW,KAAK,QAAQ;IAE9B,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,KAAK,aAAa,OAAO;KAC9E,wBAAwB;KACxB;IACF;IAEA,MAAM,qBAAqB,KAAK,QAAQ,WAAW,EAAE,MAAM,SAAS,IAAI,CAAC;IAEzE,IAAI,qBAAqB,OAAO;KAC9B,MAAM,OAAO,qBAAqB;KAClC,MAAM,OAAO;KACb,MAAM,KAAK;KAEX,QAAQ,KACN,sDAAsD,MAAM,gDAC9D;KACA,MAAM,KAAK,SAAS,GAAG,YAAY,MAAM,EAAE;KAC3C,wBAAwB;KACxB,OAAO;IACT;IAEA,wBAAwB;GAC1B;GACA,oBAAoB,aAAa,UAAU;IACzC,MAAM,QAAQ,KAAK,QAAQ;IAG3B,IAAI,CAAC,YAAY,cAAc,UAAU,KAAK,UAAU,QAAQ,UAAU,KAAA,GACxE,OAAO;IAGT,MAAM,UAAU,KAAK,QAAQ,WAAW,EAAE,MAAM,MAAM,IAAI,CAAC;IAC3D,MAAM,UAAU,KAAK,QAAQ,WAAW,EAAE,MAAM,YAAY,IAAI,CAAC;IAGjE,IAAI,WAAW,OACb,OAAO;IAIT,IAAI,UAAU,SAAS,UAAU,SAAS,WAAW,SACnD,OAAO;IAIT,IAAI,UAAU,SAAS,UAAU,SAAS,UAAU,SAClD,OAAO;IAMT,IAAI,CAHY,YAAY,QAAQ,OAGzB,GACT,OAAO;IAIT,MAAM,MAAM,YAAY,UAAU,MAAM;IAExC,MAAM,OAAO,OADA,UAAU;IAEvB,MAAM,KAAK;IAIX,YAAY,YAAY,MAAM,EAAE;IAQhC,IAFoB,KAAK,QAAQ,WAAW,EAAE,MAAM,YAAY,IAAI,CAEtD,IAAI,OAChB,OAAO;IAGT,OAAO;GACT;EACF,CAAC,CACH;CACF;AACF,CAAC;;;;;;;;;AC1KD,MAAa,aAAaC,aAAAA,UAAU,OAA0B;CAC5D,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,OAAO;GACP,OAAO,KAAA;EACT;CACF;CAEA,wBAAwB;EACtB,OAAO,EAAA,GAAA,sBAAA,WAAA,CAAY,KAAK,OAAO,CAAC;CAClC;AACF,CAAC;;;;;;;ACjBD,MAAa,QAAQC,aAAAA,UAAU,OAAqB;CAClD,MAAM;CAEN,aAAa;EACX,OAAO;GACL,WAAW;GACX,MAAM;EACR;CACF;CAEA,wBAAwB;EACtB,OAAO,CACL,IAAIC,iBAAAA,OAAO;GACT,KAAK,IAAIC,iBAAAA,UAAU,OAAO;GAC1B,OAAO,EACL,cAAc,EAAE,KAAK,gBAAgB;IACnC,MAAM,EAAE,YAAY,cAAc,KAAK;IACvC,MAAM,EAAE,WAAW;IACnB,MAAM,cAA4B,CAAC;IAEnC,IAAI,CAAC,cAAc,CAAC,WAClB,OAAOC,gBAAAA,cAAc,OAAO,KAAK,CAAC,CAAC;IAIrC,IAAI,YAAY;IAEhB,IAAI,KAAK,QAAQ,SAAS,WACxB,IAAI,aAAa,MAAM,QAAQ;KAC7B,IAAI,KAAK,QACP;KAKF,IAAI,EAFc,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW,IAGjE,OAAO;KAGT,aAAa;IACf,CAAC;IAIH,IAAI,eAAe;IAEnB,IAAI,aAAa,MAAM,QAAQ;KAC7B,IAAI,KAAK,QACP,OAAO;KAKT,IAAI,EAFc,UAAU,OAAO,UAAU,MAAM,KAAK,WAAW,IAGjE,OAAO;KAGT,gBAAgB;KAMhB,IAHG,KAAK,QAAQ,SAAS,aAAa,YAAY,eAAe,KAC9D,KAAK,QAAQ,SAAS,gBAAgB,eAAe,GAGtD,OAAO,KAAK,QAAQ,SAAS;KAG/B,YAAY,KACVC,gBAAAA,WAAW,KAAK,KAAK,MAAM,KAAK,UAAU,EACxC,OAAO,KAAK,QAAQ,UACtB,CAAC,CACH;IACF,CAAC;IAED,OAAOD,gBAAAA,cAAc,OAAO,KAAK,WAAW;GAC9C,EACF;EACF,CAAC,CACH;CACF;AACF,CAAC;;;;;;;;;ACjFD,MAAa,YAAYE,aAAAA,UAAU,OAAO;CACxC,MAAM;CAEN,wBAAwB;EACtB,OAAO,EAAA,GAAA,qBAAA,UAAA,CAAW,CAAC;CACrB;CAEA,iBAAiB,WAAW;;EAO1B,OAAO,EACL,iBAAA,iBAAA,GAAA,aAAA,aAAA,EAAA,GAAA,aAAA,kBAAA,CAA+C,WAAW,kBAAkB;GAN5E,MAAM,UAAU;GAChB,SAAS,UAAU;GACnB,SAAS,UAAU;EAI+D,CAAC,CAAC,OAAA,QAAA,kBAAA,KAAA,IAAA,gBAAK,KAC3F;CACF;AACF,CAAC;;;;AC1CD,MAAa,yBAAyB;;AAGtC,MAAa,aAAa,IAAIC,iBAAAA,UAAyB,qBAAqB;;;;;;;;;;;;;;;;ACY5E,SAAgB,4BAA4B,SAYzC;CACD,MAAM,EACJ,QACA,aACA,eACA,KACA,MACA,YACA,WACA,SAAS,EAAE,WAAW,kBACpB;CACJ,MAAM,UAAU,CAAC,SAAS;CAE1B,IAAI,YACF,QAAQ,KAAK,WAAW;CAG1B,OAAOC,gBAAAA,WAAW,KAAK,KAAK,MAAM,KAAK,UAAU;EAC/C,OAAO,QAAQ,KAAK,GAAG;GACtB,gBACC,OAAO,gBAAgB,aACnB,YAAY;GACV;GACA;GACA;GACA;EACF,CAAC,IACD;CACR,CAAC;AACH;;;AClDA,SAAS,sBACP,gBACA,OACQ;CACR,OAAO,OAAO,mBAAmB,aAAa,eAAe,KAAK,IAAI;AACxE;;;;;AAMA,SAAgB,wBAAwB,EACtC,QACA,SACA,eACA,KACA,WACA,MACA,MASe;CACf,MAAM,EAAE,WAAW;CACnB,MAAM,cAA4B,CAAC;CACnC,MAAM,aAAa,OAAO;CAE1B,IAAI,aAAa,MAAM,KAAK,MAAM,QAAQ;EACxC,MAAM,YAAY,UAAU,OAAO,UAAU,MAAM,KAAK;EACxD,MAAM,UAAU,CAAC,KAAK,WAAA,GAAA,aAAA,YAAA,CAAsB,IAAI;EAEhD,IAAI,CAAC,KAAK,KAAK,aACb,OAAO,QAAQ;EAGjB,KAAK,aAAa,CAAC,QAAQ,oBAAoB,SAC7C,YAAY,KACV,4BAA4B;GAC1B;GACA;GACA;GACA;GACA,aAAa,QAAQ;GACrB,SAAS;IACP,aAAa,QAAQ;IACrB,WAAW,sBAAsB,QAAQ,gBAAgB;KACvD;KACA;KACA;KACA;IACF,CAAC;GACH;GACA;GACA;EACF,CAAC,CACH;EAGF,OAAO,QAAQ;CACjB,CAAC;CAED,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,4BAA4B,EAC1C,QACA,SACA,eACA,KACA,aAOuB;CAGvB,IAAI,EAFW,OAAO,cAAc,CAAC,QAAQ,uBAG3C,OAAO;CAGT,MAAM,EAAE,WAAW;CACnB,MAAM,cAA4B,CAAC;CACnC,MAAM,aAAa,OAAO;CAI1B,IAFwB,QAAQ,mBAAmB,CAAC,QAAQ,iBAEvC;EACnB,MAAM,WAAW,IAAI,QAAQ,MAAM;EAOnC,MAAM,OAAO,SAAS,QAAQ,IAAI,SAAS,KAAK,CAAC,IAAI,SAAS;EAC9D,MAAM,YAAY,SAAS,QAAQ,IAAI,SAAS,OAAO,CAAC,IAAI;EAE5D,IAAI,QAAQ,KAAK,KAAK,gBAAA,GAAA,aAAA,YAAA,CAA2B,IAAI,GAAG;GACtD,MAAM,YAAY,UAAU,aAAa,UAAU,YAAY,KAAK;GAEpE,YAAY,KACV,4BAA4B;IAC1B;IACA;IACA;IACA;IACA,aAAa,QAAQ;IACrB,SAAS;KACP,aAAa,QAAQ;KACrB,WAAW,sBAAsB,QAAQ,gBAAgB;MACvD;MACA;MACA,KAAK;MACL;KACF,CAAC;IACH;IACA;IACA,KAAK;GACP,CAAC,CACH;EACF;CACF,OACE,YAAY,KACV,GAAG,wBAAwB;EACzB;EACA;EACA;EACA;EACA;EACA,MAAM;EACN,IAAI,IAAI,QAAQ;CAClB,CAAC,CACH;CAGF,OAAOC,gBAAAA,cAAc,OAAO,KAAK,WAAW;AAC9C;;;;;;;AC7JA,SAAgB,qBAAqB,KAAW,KAA2C;CACzF,MAAM,WAAW,IAAI,QAAQ,GAAG;CAEhC,IAAI,SAAS,UAAU,GAAG;;EACxB,MAAM,QAAA,sBAAO,SAAS,eAAA,QAAA,wBAAA,KAAA,IAAA,sBAAa,SAAS;EAE5C,IAAI,CAAC,MACH,OAAO;GAAE,MAAM;GAAK,IAAI;EAAI;EAG9B,MAAM,UAAU,SAAS,YAAY,MAAM,MAAM,KAAK;EAEtD,OAAO;GAAE,MAAM;GAAS,IAAI,UAAU,KAAK;EAAS;CACtD;CAEA,MAAM,cAAc,SAAS,OAAO,CAAC;CAGrC,OAAO;EAAE,MAAM;EAAa,IAAI,cAFnB,SAAS,KAAK,CAEsB,CAAC,CAAC;CAAS;AAC9D;;;;;AAMA,SAAgB,uBACd,KACA,OAC8B;CAC9B,OAAO;EACL,MAAM,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC;EAChC,IAAI,KAAK,IAAI,IAAI,QAAQ,MAAM,MAAM,KAAK,CAAC;CAC7C;AACF;;;;;;AAOA,SAAgB,yBACd,KACA,MACA,IACqC;CACrC,MAAM,SAA8C,CAAC;CAErD,IAAI,SAAS,MAAM,WAAW;EAC5B,MAAM,YAAY;EAClB,MAAM,UAAU,YAAY,KAAK;EACjC,MAAM,eAAe,YAAY;EACjC,MAAM,aAAa,UAAU;EAE7B,IAAI,eAAe,MAAM,aAAa,MACpC,OAAO,KAAK;GAAE,MAAM;GAAW,IAAI;EAAQ,CAAC;CAEhD,CAAC;CAED,OAAO;AACT;;;;AAKA,SAAgB,YACd,QACqC;CACrC,IAAI,OAAO,WAAW,GACpB,OAAO,CAAC;CAGV,MAAM,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;CACzD,MAAM,SAA8C,CAAC,EAAE,GAAG,OAAO,GAAG,CAAC;CAErE,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;EACzC,MAAM,OAAO,OAAO,OAAO,SAAS;EACpC,MAAM,UAAU,OAAO;EAEvB,IAAI,QAAQ,QAAQ,KAAK,IACvB,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,QAAQ,EAAE;OAEtC,OAAO,KAAK,EAAE,GAAG,QAAQ,CAAC;CAE9B;CAEA,OAAO;AACT;;;;;;;;AC7DA,SAAS,uBACP,KACA,QACqC;CACrC,MAAM,SAAS,yBAAyB,KAAK,OAAO,MAAM,OAAO,EAAE;CAEnE,OAAO,KAAK,uBAAuB,KAAK,qBAAqB,KAAK,OAAO,IAAI,CAAC,CAAC;CAE/E,IAAI,OAAO,KAAK,OAAO,MACrB,OAAO,KACL,uBACE,KACA,qBAAqB,KAAK,KAAK,IAAI,OAAO,IAAI,IAAI,QAAQ,OAAO,CAAC,IAAI,CAAC,CACzE,CACF;MACK,IAAI,OAAO,OAAO,IAAI,QAAQ,OAAO,GAC1C,OAAO,KACL,uBACE,KACA,qBAAqB,KAAK,KAAK,IAAI,OAAO,OAAO,GAAG,IAAI,QAAQ,IAAI,CAAC,CACvE,CACF;CAGF,OAAO;AACT;;;;;AAMA,SAAS,oBACP,IACA,UACA,UACqC;CACrC,MAAM,SAA8C,CAAC;CAErD,IAAI,GAAG,YAAY;EACjB,MAAM,WAAA,GAAA,aAAA,iBAAA,CAA2B,EAAE;EAEnC,KAAK,MAAM,UAAU,SACnB,OAAO,KAAK,GAAG,uBAAuB,SAAS,KAAK,OAAO,QAAQ,CAAC;CAExE;CAEA,IAAI,GAAG,cAAc;EACnB,OAAO,KACL,uBACE,SAAS,KACT,qBAAqB,SAAS,KAAK,GAAG,QAAQ,IAAI,SAAS,UAAU,MAAM,CAAC,CAC9E,CACF;EACA,OAAO,KACL,uBACE,SAAS,KACT,qBAAqB,SAAS,KAAK,SAAS,UAAU,MAAM,CAC9D,CACF;CACF;CAEA,OAAO,YAAY,MAAM;AAC3B;;AAGA,SAAS,WAAW,MAAc,IAAY,KAAyC;CACrF,MAAM,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,IAAI,QAAQ,IAAI,CAAC;CAGhE,OAAO;EAAE,MAAM;EAAa,IAFV,KAAK,IAAI,aAAa,KAAK,IAAI,IAAI,IAAI,QAAQ,IAAI,CAE7B;CAAE;AAC5C;;;;;;AAOA,SAAS,0BAA0B,EACjC,aACA,QACA,QACA,SACA,eACA,KACA,aASgB;CAChB,IAAI,OAAO;CAEX,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,EAAE,MAAM,OAAO,WAAW,MAAM,MAAM,MAAM,IAAI,GAAG;EACzD,MAAM,WAAW,KACd,KAAK,MAAM,EAAE,CAAC,CACd,QAAO,eAAc,WAAW,QAAQ,QAAQ,WAAW,MAAM,EAAE;EAEtE,IAAI,SAAS,QACX,OAAO,KAAK,OAAO,QAAQ;EAG7B,MAAM,WAAW,wBAAwB;GACvC;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EAED,IAAI,SAAS,QACX,OAAO,KAAK,IAAI,KAAK,QAAQ;CAEjC;CAEA,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,4BAA4B,EAC1C,QACA,SACA,iBACgE;CAChE,OAAO;EACL,KAAK,SAAS,OAAoB;GAChC,MAAM,cAAc,4BAA4B;IAC9C;IACA;IACA;IACA,KAAK,MAAM;IACX,WAAW,MAAM;GACnB,CAAC;GAED,OAAO,gBAAA,QAAA,gBAAA,KAAA,IAAA,cAAeC,gBAAAA,cAAc;EACtC;EAEA,MAAM,IAAiB,MAAqB,UAAuB,UAAuB;GACxF,IAAI,CAAC,GAAG,cAAc,CAAC,GAAG,cACxB,OAAO;GAMT,OAAO,0BAA0B;IAC/B,aAJa,KAAK,IAAI,GAAG,SAAS,GAAG,GAInB;IAClB,QAJa,oBAAoB,IAAI,UAAU,QAI1C;IACL;IACA;IACA;IACA,KAAK,SAAS;IACd,WAAW,SAAS;GACtB,CAAC;EACH;CACF;AACF;;;;;;;;ACtMA,SAAgB,4BAA4B,MAAsB;CAChE,OACE,KAEG,QAAQ,QAAQ,GAAG,CAAC,CAIpB,QAAQ,kBAAkB,EAAE,CAAC,CAE7B,QAAQ,YAAY,EAAE,CAAC,CAEvB,QAAQ,OAAO,EAAE,CAAC,CAClB,YAAY;AAEnB;;;;;;;;;ACCA,SAAgB,wBAAwB,EAAE,QAAQ,WAAgC;CAChF,MAAM,gBAAgB,QAAQ,gBAC1B,QAAQ,4BAA4B,QAAQ,aAAa,MACzD,QAAQ;CAEZ,MAAM,kBAAkB,QAAQ,mBAAmB,CAAC,QAAQ;CAE5D,OAAO,IAAIC,iBAAAA,OAAO;EAChB,KAAK;EACL,GAAI,kBACA,CAAC,IACD,EACE,OAAO,4BAA4B;GAAE;GAAQ;GAAS;EAAc,CAAC,EACvE;EACJ,OAAO,EACL,aAAa,mBACR,EAAE,KAAK,gBACN,4BAA4B;GAAE;GAAQ;GAAS;GAAe;GAAK;EAAU,CAAC,KAChF,UAAS;;GACP,IAAI,QAAQ,wBAAwB,CAAC,OAAO,YAC1C,OAAOC,gBAAAA,cAAc;GAGvB,QAAA,uBAAO,WAAW,SAAS,KAAK,OAAA,QAAA,yBAAA,KAAA,IAAA,uBAAKA,gBAAAA,cAAc;EACrD,EACN;CACF,CAAC;AACH;;;;;;;;ACrCA,MAAa,cAAcC,aAAAA,UAAU,OAA2B;CAC9D,MAAM;CAEN,aAAa;EACX,OAAO;GACL,kBAAkB;GAClB,gBAAgB;GAChB,eAAe;GACf,aAAa;GACb,sBAAsB;GACtB,iBAAiB;GACjB,iBAAiB;EACnB;CACF;CAEA,wBAAwB;EACtB,OAAO,CAAC,wBAAwB;GAAE,QAAQ,KAAK;GAAQ,SAAS,KAAK;EAAQ,CAAC,CAAC;CACjF;AACF,CAAC;;;;;;;ACXD,SAAS,uBAAuB,OAAoB,QAAyB;CAC3E,OAAO,CAAC,MAAM,UAAU,SAAS,EAAA,GAAA,aAAA,gBAAA,CAAiB,MAAM,SAAS,KAAK,OAAO;AAC/E;;;;;AAMA,SAAS,wBAAwB,OAAoB,QAAyB;CAC5E,OAAO,uBAAuB,OAAO,MAAM,KAAK,CAAC,OAAO,aAAa,CAAC,OAAO,KAAK;AACpF;AAEA,SAAS,oBAAoB;;CAC3B,CAAA,uBAAA,OAAO,aAAa,OAAA,QAAA,yBAAA,KAAA,KAAA,qBAAG,gBAAgB;AACzC;;;;;AAMA,SAAS,oBAAoB,MAAkB;CAC7C,KAAK,MAAM;AACb;;;;;;;AAQA,MAAa,YAAYC,aAAAA,UAAU,OAAyB;CAC1D,MAAM;CAEN,aAAa;EACX,OAAO,EACL,WAAW,YACb;CACF;CAEA,wBAAwB;EACtB,MAAM,EAAE,QAAQ,YAAY;EAE5B,OAAO,CACL,IAAIC,iBAAAA,OAAO;GACT,KAAK,IAAIC,iBAAAA,UAAU,WAAW;GAC9B,OAAO;IACL,YAAY,OAAO;KACjB,IAAI,CAAC,wBAAwB,OAAO,MAAM,GACxC,OAAO;KAGT,OAAOC,gBAAAA,cAAc,OAAO,MAAM,KAAK,CACrCC,gBAAAA,WAAW,OAAO,MAAM,UAAU,MAAM,MAAM,UAAU,IAAI,EAC1D,OAAO,QAAQ,UACjB,CAAC,CACH,CAAC;IACH;IACA,iBAAiB;KACf,KAAK,MAAM;MACT,IAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAC5C,OAAO;MAGT,kBAAkB;MAElB,OAAO;KACT;KACA,MAAM,MAAM;MACV,IAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAC5C,OAAO;MAGT,4BAA4B;OAC1B,IAAI,CAAC,OAAO,eAAe,KAAK,SAAS,GACvC,oBAAoB,IAAI;MAE5B,CAAC;MAED,OAAO;KACT;IACF;GACF;EACF,CAAC,CACH;CACF;AACF,CAAC;;;ACnGD,MAAa,uBAAuB;AAEpC,SAAS,eAAe,EACtB,OACA,QAIC;CACD,OAAQ,QAAQ,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,IAAI,MAAA,SAAA,QAAA,SAAA,KAAA,IAAA,KAAA,IAAM,KAAM,UAAS;AACvF;;;;;AA2BA,MAAa,eAAeC,aAAAA,UAAU,OAA4B;CAChE,MAAM;CAEN,aAAa;EACX,OAAO;GACL,MAAM,KAAA;GACN,UAAU,CAAC;EACb;CACF;CAEA,wBAAwB;;EACtB,MAAM,SAAS,IAAIC,iBAAAA,UAAU,KAAK,IAAI;EACtC,MAAM,cACJ,KAAK,QAAQ,UAAA,wBACb,KAAK,OAAO,OAAO,YAAY,aAAa,iBAAA,QAAA,0BAAA,KAAA,IAAA,KAAA,IAAA,sBAAa,SACzD;EAEF,MAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,OAAO,KAAK,CAAC,CAC3D,KAAK,GAAG,WAAW,KAAK,CAAC,CACzB,QAAO,UAAS,KAAK,QAAQ,YAAY,CAAC,EAAA,CAAG,OAAO,WAAW,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC;EAEvF,OAAO,CACL,IAAIC,iBAAAA,OAAO;GACT,KAAK;GACL,oBAAoB,cAAc,IAAI,UAAU;IAC9C,MAAM,EAAE,KAAK,IAAI,WAAW;IAC5B,MAAM,wBAAwB,OAAO,SAAS,KAAK;IACnD,MAAM,cAAc,IAAI,QAAQ;IAChC,MAAM,OAAO,OAAO,MAAM;IAE1B,IAAI,aAAa,MAAK,gBAAe,YAAY,QAAA,kBAA4B,CAAC,GAC5E;IAGF,IAAI,CAAC,uBACH;IAGF,OAAO,GAAG,OAAO,aAAa,KAAK,OAAO,CAAC;GAC7C;GACA,OAAO;IACL,OAAO,GAAG,UAAU;KAClB,MAAM,WAAW,MAAM,GAAG,IAAI;KAE9B,OAAO,CAAC,eAAe;MAAE,MAAM;MAAU,OAAO;KAAc,CAAC;IACjE;IACA,QAAQ,IAAI,UAAU;KACpB,IAAI,CAAC,GAAG,YACN,OAAO;KAKT,IAAI,GAAG,QAAQ,uBAAuB,GACpC,OAAO;KAGT,MAAM,WAAW,GAAG,IAAI;KAExB,OAAO,CAAC,eAAe;MAAE,MAAM;MAAU,OAAO;KAAc,CAAC;IACjE;GACF;EACF,CAAC,CACH;CACF;AACF,CAAC;;;;;;;;;;;;AC7DD,MAAa,WAAWC,aAAAA,UAAU,OAAwB;CACxD,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,eAAe;EACjB;CACF;CAEA,cAAc;EACZ,OAAO;GACL,aAEG,EAAE,OAAO,eAAe;IACvB,QAAA,GAAA,mBAAA,KAAA,CAAY,OAAO,QAAQ;GAC7B;GACF,aAEG,EAAE,OAAO,eAAe;IACvB,QAAA,GAAA,mBAAA,KAAA,CAAY,OAAO,QAAQ;GAC7B;EACJ;CACF;CAEA,wBAAwB;EACtB,OAAO,EAAA,GAAA,mBAAA,QAAA,CAAS,KAAK,OAAO,CAAC;CAC/B;CAEA,uBAAuB;EACrB,OAAO;GACL,eAAe,KAAK,OAAO,SAAS,KAAK;GACzC,qBAAqB,KAAK,OAAO,SAAS,KAAK;GAC/C,eAAe,KAAK,OAAO,SAAS,KAAK;GAGzC,eAAe,KAAK,OAAO,SAAS,KAAK;GACzC,qBAAqB,KAAK,OAAO,SAAS,KAAK;EACjD;CACF;AACF,CAAC"} |
+231
-220
@@ -1,64 +0,64 @@ | ||
| import { Extension, ParentConfig, Editor } from '@tiptap/core'; | ||
| import { Node } from '@tiptap/pm/model'; | ||
| import { PluginKey } from '@tiptap/pm/state'; | ||
| import { DecorationSet } from '@tiptap/pm/view'; | ||
| import { Editor, Extension, ParentConfig } from "@tiptap/core"; | ||
| import { Node } from "@tiptap/pm/model"; | ||
| import { PluginKey } from "@tiptap/pm/state"; | ||
| import { DecorationSet } from "@tiptap/pm/view"; | ||
| //#region src/character-count/character-count.d.ts | ||
| interface CharacterCountOptions { | ||
| /** | ||
| * The maximum number of characters that should be allowed. Defaults to `0`. | ||
| * @default null | ||
| * @example 180 | ||
| */ | ||
| limit: number | null | undefined; | ||
| /** | ||
| * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| * If set to `nodeSize`, the nodeSize of the document is used. | ||
| * @default 'textSize' | ||
| * @example 'textSize' | ||
| */ | ||
| mode: 'textSize' | 'nodeSize'; | ||
| /** | ||
| * Sets whether the content will be automatically trimmed when programatically setting content over the limit. | ||
| * If set to false, the user will be able to trim the text manually. | ||
| * @default true | ||
| * @example false | ||
| */ | ||
| autoTrim?: boolean; | ||
| /** | ||
| * The text counter function to use. Defaults to a simple character count. | ||
| * @default (text) => text.length | ||
| * @example (text) => [...new Intl.Segmenter().segment(text)].length | ||
| */ | ||
| textCounter: (text: string) => number; | ||
| /** | ||
| * The word counter function to use. Defaults to a simple word count. | ||
| * @default (text) => text.split(' ').filter(word => word !== '').length | ||
| * @example (text) => text.split(/\s+/).filter(word => word !== '').length | ||
| */ | ||
| wordCounter: (text: string) => number; | ||
| /** | ||
| * The maximum number of characters that should be allowed. Defaults to `0`. | ||
| * @default null | ||
| * @example 180 | ||
| */ | ||
| limit: number | null | undefined; | ||
| /** | ||
| * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| * If set to `nodeSize`, the nodeSize of the document is used. | ||
| * @default 'textSize' | ||
| * @example 'textSize' | ||
| */ | ||
| mode: 'textSize' | 'nodeSize'; | ||
| /** | ||
| * Sets whether the content will be automatically trimmed when programatically setting content over the limit. | ||
| * If set to false, the user will be able to trim the text manually. | ||
| * @default true | ||
| * @example false | ||
| */ | ||
| autoTrim?: boolean; | ||
| /** | ||
| * The text counter function to use. Defaults to a simple character count. | ||
| * @default (text) => text.length | ||
| * @example (text) => [...new Intl.Segmenter().segment(text)].length | ||
| */ | ||
| textCounter: (text: string) => number; | ||
| /** | ||
| * The word counter function to use. Defaults to a simple word count. | ||
| * @default (text) => text.split(' ').filter(word => word !== '').length | ||
| * @example (text) => text.split(/\s+/).filter(word => word !== '').length | ||
| */ | ||
| wordCounter: (text: string) => number; | ||
| } | ||
| interface CharacterCountStorage { | ||
| /** | ||
| * Get the number of characters for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the characters from. Defaults to the current document. | ||
| * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| */ | ||
| characters: (options?: { | ||
| node?: Node; | ||
| mode?: 'textSize' | 'nodeSize'; | ||
| }) => number; | ||
| /** | ||
| * Get the number of words for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the words from. Defaults to the current document. | ||
| */ | ||
| words: (options?: { | ||
| node?: Node; | ||
| }) => number; | ||
| /** | ||
| * Get the number of characters for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the characters from. Defaults to the current document. | ||
| * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| */ | ||
| characters: (options?: { | ||
| node?: Node; | ||
| mode?: 'textSize' | 'nodeSize'; | ||
| }) => number; | ||
| /** | ||
| * Get the number of words for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the words from. Defaults to the current document. | ||
| */ | ||
| words: (options?: { | ||
| node?: Node; | ||
| }) => number; | ||
| } | ||
| declare module '@tiptap/core' { | ||
| interface Storage { | ||
| characterCount: CharacterCountStorage; | ||
| } | ||
| interface Storage { | ||
| characterCount: CharacterCountStorage; | ||
| } | ||
| } | ||
@@ -70,22 +70,23 @@ /** | ||
| declare const CharacterCount: Extension<CharacterCountOptions, CharacterCountStorage>; | ||
| //#endregion | ||
| //#region src/drop-cursor/drop-cursor.d.ts | ||
| interface DropcursorOptions { | ||
| /** | ||
| * The color of the drop cursor. Use `false` to apply no color and rely only on class. | ||
| * @default 'currentColor' | ||
| * @example 'red' | ||
| */ | ||
| color?: string | false; | ||
| /** | ||
| * The width of the drop cursor | ||
| * @default 1 | ||
| * @example 2 | ||
| */ | ||
| width: number | undefined; | ||
| /** | ||
| * The class of the drop cursor | ||
| * @default undefined | ||
| * @example 'drop-cursor' | ||
| */ | ||
| class: string | undefined; | ||
| /** | ||
| * The color of the drop cursor. Use `false` to apply no color and rely only on class. | ||
| * @default 'currentColor' | ||
| * @example 'red' | ||
| */ | ||
| color?: string | false; | ||
| /** | ||
| * The width of the drop cursor | ||
| * @default 1 | ||
| * @example 2 | ||
| */ | ||
| width: number | undefined; | ||
| /** | ||
| * The class of the drop cursor | ||
| * @default undefined | ||
| * @example 'drop-cursor' | ||
| */ | ||
| class: string | undefined; | ||
| } | ||
@@ -99,21 +100,22 @@ /** | ||
| declare const Dropcursor: Extension<DropcursorOptions, any>; | ||
| //#endregion | ||
| //#region src/focus/focus.d.ts | ||
| interface FocusOptions { | ||
| /** | ||
| * The class name that should be added to the focused node. | ||
| * @default 'has-focus' | ||
| * @example 'is-focused' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The mode by which the focused node is determined. | ||
| * - All: All nodes are marked as focused. | ||
| * - Deepest: Only the deepest node is marked as focused. | ||
| * - Shallowest: Only the shallowest node is marked as focused. | ||
| * | ||
| * @default 'all' | ||
| * @example 'deepest' | ||
| * @example 'shallowest' | ||
| */ | ||
| mode: 'all' | 'deepest' | 'shallowest'; | ||
| /** | ||
| * The class name that should be added to the focused node. | ||
| * @default 'has-focus' | ||
| * @example 'is-focused' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The mode by which the focused node is determined. | ||
| * - All: All nodes are marked as focused. | ||
| * - Deepest: Only the deepest node is marked as focused. | ||
| * - Shallowest: Only the shallowest node is marked as focused. | ||
| * | ||
| * @default 'all' | ||
| * @example 'deepest' | ||
| * @example 'shallowest' | ||
| */ | ||
| mode: 'all' | 'deepest' | 'shallowest'; | ||
| } | ||
@@ -125,16 +127,17 @@ /** | ||
| declare const Focus: Extension<FocusOptions, any>; | ||
| //#endregion | ||
| //#region src/gap-cursor/gap-cursor.d.ts | ||
| declare module '@tiptap/core' { | ||
| interface NodeConfig<Options, Storage> { | ||
| /** | ||
| * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`. | ||
| * @default null | ||
| */ | ||
| allowGapCursor?: boolean | null | ((this: { | ||
| name: string; | ||
| options: Options; | ||
| storage: Storage; | ||
| parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']; | ||
| }) => boolean | null); | ||
| } | ||
| interface NodeConfig<Options, Storage> { | ||
| /** | ||
| * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`. | ||
| * @default null | ||
| */ | ||
| allowGapCursor?: boolean | null | ((this: { | ||
| name: string; | ||
| options: Options; | ||
| storage: Storage; | ||
| parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']; | ||
| }) => boolean | null); | ||
| } | ||
| } | ||
@@ -148,3 +151,4 @@ /** | ||
| declare const Gapcursor: Extension<any, any>; | ||
| //#endregion | ||
| //#region src/placeholder/constants.d.ts | ||
| /** The default data attribute label */ | ||
@@ -154,65 +158,67 @@ declare const DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| declare const PLUGIN_KEY: PluginKey<DecorationSet>; | ||
| //#endregion | ||
| //#region src/placeholder/types.d.ts | ||
| interface PlaceholderOptions { | ||
| /** | ||
| * **The class name for the empty editor** | ||
| * @default 'is-editor-empty' | ||
| */ | ||
| emptyEditorClass: string; | ||
| /** | ||
| * **The class name for empty nodes** | ||
| * | ||
| * You can use a function to return a dynamic class name or a string. | ||
| * @default 'is-empty' | ||
| */ | ||
| emptyNodeClass: ((EmptyNodeClassProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **The data-attribute used for the placeholder label** | ||
| * Will be prepended with `data-` and converted to kebab-case and cleaned of special characters. | ||
| * @default 'placeholder' | ||
| */ | ||
| dataAttribute: string; | ||
| /** | ||
| * **The placeholder content** | ||
| * | ||
| * You can use a function to return a dynamic placeholder or a string. | ||
| * @default 'Write something …' | ||
| */ | ||
| placeholder: ((PlaceholderProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the editor is editable.** | ||
| * | ||
| * If true, the placeholder will only be shown when the editor is editable. | ||
| * If false, the placeholder will always be shown. | ||
| * @default true | ||
| */ | ||
| showOnlyWhenEditable: boolean; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the current node is empty.** | ||
| * | ||
| * If true, the placeholder will only be shown when the current node is empty. | ||
| * If false, the placeholder will be shown when any node is empty. | ||
| * @default true | ||
| */ | ||
| showOnlyCurrent: boolean; | ||
| /** | ||
| * **Controls if the placeholder should be shown for all descendants.** | ||
| * | ||
| * If true, the placeholder will be shown for all descendants. | ||
| * If false, the placeholder will only be shown for the current node. | ||
| * @default false | ||
| */ | ||
| includeChildren: boolean; | ||
| /** | ||
| * **The class name for the empty editor** | ||
| * @default 'is-editor-empty' | ||
| */ | ||
| emptyEditorClass: string; | ||
| /** | ||
| * **The class name for empty nodes** | ||
| * | ||
| * You can use a function to return a dynamic class name or a string. | ||
| * @default 'is-empty' | ||
| */ | ||
| emptyNodeClass: ((EmptyNodeClassProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **The data-attribute used for the placeholder label** | ||
| * Will be prepended with `data-` and converted to kebab-case and cleaned of special characters. | ||
| * @default 'placeholder' | ||
| */ | ||
| dataAttribute: string; | ||
| /** | ||
| * **The placeholder content** | ||
| * | ||
| * You can use a function to return a dynamic placeholder or a string. | ||
| * @default 'Write something …' | ||
| */ | ||
| placeholder: ((PlaceholderProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the editor is editable.** | ||
| * | ||
| * If true, the placeholder will only be shown when the editor is editable. | ||
| * If false, the placeholder will always be shown. | ||
| * @default true | ||
| */ | ||
| showOnlyWhenEditable: boolean; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the current node is empty.** | ||
| * | ||
| * If true, the placeholder will only be shown when the current node is empty. | ||
| * If false, the placeholder will be shown when any node is empty. | ||
| * @default true | ||
| */ | ||
| showOnlyCurrent: boolean; | ||
| /** | ||
| * **Controls if the placeholder should be shown for all descendants.** | ||
| * | ||
| * If true, the placeholder will be shown for all descendants. | ||
| * If false, the placeholder will only be shown for the current node. | ||
| * @default false | ||
| */ | ||
| includeChildren: boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/placeholder/placeholder.d.ts | ||
| /** | ||
@@ -224,3 +230,4 @@ * This extension allows you to add a placeholder to your editor. | ||
| declare const Placeholder: Extension<PlaceholderOptions, any>; | ||
| //#endregion | ||
| //#region src/placeholder/utils/preparePlaceholderAttribute.d.ts | ||
| /** | ||
@@ -232,10 +239,11 @@ * Prepares the placeholder attribute by ensuring it is properly formatted. | ||
| declare function preparePlaceholderAttribute(attr: string): string; | ||
| //#endregion | ||
| //#region src/selection/selection.d.ts | ||
| type SelectionOptions = { | ||
| /** | ||
| * The class name that should be added to the selected text. | ||
| * @default 'selection' | ||
| * @example 'is-selected' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The class name that should be added to the selected text. | ||
| * @default 'selection' | ||
| * @example 'is-selected' | ||
| */ | ||
| className: string; | ||
| }; | ||
@@ -249,3 +257,4 @@ /** | ||
| declare const Selection: Extension<SelectionOptions, any>; | ||
| //#endregion | ||
| //#region src/trailing-node/trailing-node.d.ts | ||
| declare const skipTrailingNodeMeta = "skipTrailingNode"; | ||
@@ -258,14 +267,14 @@ /** | ||
| interface TrailingNodeOptions { | ||
| /** | ||
| * The node type that should be inserted at the end of the document. | ||
| * @note the node will always be added to the `notAfter` lists to | ||
| * prevent an infinite loop. | ||
| * @default undefined | ||
| */ | ||
| node?: string; | ||
| /** | ||
| * The node types after which the trailing node should not be inserted. | ||
| * @default ['paragraph'] | ||
| */ | ||
| notAfter?: string | string[]; | ||
| /** | ||
| * The node type that should be inserted at the end of the document. | ||
| * @note the node will always be added to the `notAfter` lists to | ||
| * prevent an infinite loop. | ||
| * @default undefined | ||
| */ | ||
| node?: string; | ||
| /** | ||
| * The node types after which the trailing node should not be inserted. | ||
| * @default ['paragraph'] | ||
| */ | ||
| notAfter?: string | string[]; | ||
| } | ||
@@ -277,32 +286,33 @@ /** | ||
| declare const TrailingNode: Extension<TrailingNodeOptions, any>; | ||
| //#endregion | ||
| //#region src/undo-redo/undo-redo.d.ts | ||
| interface UndoRedoOptions { | ||
| /** | ||
| * The amount of history events that are collected before the oldest events are discarded. | ||
| * @default 100 | ||
| * @example 50 | ||
| */ | ||
| depth: number; | ||
| /** | ||
| * The delay (in milliseconds) between changes after which a new group should be started. | ||
| * @default 500 | ||
| * @example 1000 | ||
| */ | ||
| newGroupDelay: number; | ||
| /** | ||
| * The amount of history events that are collected before the oldest events are discarded. | ||
| * @default 100 | ||
| * @example 50 | ||
| */ | ||
| depth: number; | ||
| /** | ||
| * The delay (in milliseconds) between changes after which a new group should be started. | ||
| * @default 500 | ||
| * @example 1000 | ||
| */ | ||
| newGroupDelay: number; | ||
| } | ||
| declare module '@tiptap/core' { | ||
| interface Commands<ReturnType> { | ||
| undoRedo: { | ||
| /** | ||
| * Undo recent changes | ||
| * @example editor.commands.undo() | ||
| */ | ||
| undo: () => ReturnType; | ||
| /** | ||
| * Reapply reverted changes | ||
| * @example editor.commands.redo() | ||
| */ | ||
| redo: () => ReturnType; | ||
| }; | ||
| } | ||
| interface Commands<ReturnType> { | ||
| undoRedo: { | ||
| /** | ||
| * Undo recent changes | ||
| * @example editor.commands.undo() | ||
| */ | ||
| undo: () => ReturnType; | ||
| /** | ||
| * Reapply reverted changes | ||
| * @example editor.commands.redo() | ||
| */ | ||
| redo: () => ReturnType; | ||
| }; | ||
| } | ||
| } | ||
@@ -319,3 +329,4 @@ /** | ||
| declare const UndoRedo: Extension<UndoRedoOptions, any>; | ||
| export { CharacterCount, type CharacterCountOptions, type CharacterCountStorage, DEFAULT_DATA_ATTRIBUTE, Dropcursor, type DropcursorOptions, Focus, type FocusOptions, Gapcursor, PLUGIN_KEY, Placeholder, type PlaceholderOptions, Selection, type SelectionOptions, TrailingNode, type TrailingNodeOptions, UndoRedo, type UndoRedoOptions, preparePlaceholderAttribute, skipTrailingNodeMeta }; | ||
| //#endregion | ||
| export { CharacterCount, CharacterCountOptions, CharacterCountStorage, DEFAULT_DATA_ATTRIBUTE, Dropcursor, DropcursorOptions, Focus, FocusOptions, Gapcursor, PLUGIN_KEY, Placeholder, PlaceholderOptions, Selection, SelectionOptions, TrailingNode, TrailingNodeOptions, UndoRedo, UndoRedoOptions, preparePlaceholderAttribute, skipTrailingNodeMeta }; | ||
| //# sourceMappingURL=index.d.cts.map |
+231
-220
@@ -1,64 +0,64 @@ | ||
| import { Extension, ParentConfig, Editor } from '@tiptap/core'; | ||
| import { Node } from '@tiptap/pm/model'; | ||
| import { PluginKey } from '@tiptap/pm/state'; | ||
| import { DecorationSet } from '@tiptap/pm/view'; | ||
| import { Editor, Extension, ParentConfig } from "@tiptap/core"; | ||
| import { PluginKey } from "@tiptap/pm/state"; | ||
| import { DecorationSet } from "@tiptap/pm/view"; | ||
| import { Node } from "@tiptap/pm/model"; | ||
| //#region src/character-count/character-count.d.ts | ||
| interface CharacterCountOptions { | ||
| /** | ||
| * The maximum number of characters that should be allowed. Defaults to `0`. | ||
| * @default null | ||
| * @example 180 | ||
| */ | ||
| limit: number | null | undefined; | ||
| /** | ||
| * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| * If set to `nodeSize`, the nodeSize of the document is used. | ||
| * @default 'textSize' | ||
| * @example 'textSize' | ||
| */ | ||
| mode: 'textSize' | 'nodeSize'; | ||
| /** | ||
| * Sets whether the content will be automatically trimmed when programatically setting content over the limit. | ||
| * If set to false, the user will be able to trim the text manually. | ||
| * @default true | ||
| * @example false | ||
| */ | ||
| autoTrim?: boolean; | ||
| /** | ||
| * The text counter function to use. Defaults to a simple character count. | ||
| * @default (text) => text.length | ||
| * @example (text) => [...new Intl.Segmenter().segment(text)].length | ||
| */ | ||
| textCounter: (text: string) => number; | ||
| /** | ||
| * The word counter function to use. Defaults to a simple word count. | ||
| * @default (text) => text.split(' ').filter(word => word !== '').length | ||
| * @example (text) => text.split(/\s+/).filter(word => word !== '').length | ||
| */ | ||
| wordCounter: (text: string) => number; | ||
| /** | ||
| * The maximum number of characters that should be allowed. Defaults to `0`. | ||
| * @default null | ||
| * @example 180 | ||
| */ | ||
| limit: number | null | undefined; | ||
| /** | ||
| * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| * If set to `nodeSize`, the nodeSize of the document is used. | ||
| * @default 'textSize' | ||
| * @example 'textSize' | ||
| */ | ||
| mode: 'textSize' | 'nodeSize'; | ||
| /** | ||
| * Sets whether the content will be automatically trimmed when programatically setting content over the limit. | ||
| * If set to false, the user will be able to trim the text manually. | ||
| * @default true | ||
| * @example false | ||
| */ | ||
| autoTrim?: boolean; | ||
| /** | ||
| * The text counter function to use. Defaults to a simple character count. | ||
| * @default (text) => text.length | ||
| * @example (text) => [...new Intl.Segmenter().segment(text)].length | ||
| */ | ||
| textCounter: (text: string) => number; | ||
| /** | ||
| * The word counter function to use. Defaults to a simple word count. | ||
| * @default (text) => text.split(' ').filter(word => word !== '').length | ||
| * @example (text) => text.split(/\s+/).filter(word => word !== '').length | ||
| */ | ||
| wordCounter: (text: string) => number; | ||
| } | ||
| interface CharacterCountStorage { | ||
| /** | ||
| * Get the number of characters for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the characters from. Defaults to the current document. | ||
| * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| */ | ||
| characters: (options?: { | ||
| node?: Node; | ||
| mode?: 'textSize' | 'nodeSize'; | ||
| }) => number; | ||
| /** | ||
| * Get the number of words for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the words from. Defaults to the current document. | ||
| */ | ||
| words: (options?: { | ||
| node?: Node; | ||
| }) => number; | ||
| /** | ||
| * Get the number of characters for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the characters from. Defaults to the current document. | ||
| * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used. | ||
| */ | ||
| characters: (options?: { | ||
| node?: Node; | ||
| mode?: 'textSize' | 'nodeSize'; | ||
| }) => number; | ||
| /** | ||
| * Get the number of words for the current document. | ||
| * @param options The options for the character count. (optional) | ||
| * @param options.node The node to get the words from. Defaults to the current document. | ||
| */ | ||
| words: (options?: { | ||
| node?: Node; | ||
| }) => number; | ||
| } | ||
| declare module '@tiptap/core' { | ||
| interface Storage { | ||
| characterCount: CharacterCountStorage; | ||
| } | ||
| interface Storage { | ||
| characterCount: CharacterCountStorage; | ||
| } | ||
| } | ||
@@ -70,22 +70,23 @@ /** | ||
| declare const CharacterCount: Extension<CharacterCountOptions, CharacterCountStorage>; | ||
| //#endregion | ||
| //#region src/drop-cursor/drop-cursor.d.ts | ||
| interface DropcursorOptions { | ||
| /** | ||
| * The color of the drop cursor. Use `false` to apply no color and rely only on class. | ||
| * @default 'currentColor' | ||
| * @example 'red' | ||
| */ | ||
| color?: string | false; | ||
| /** | ||
| * The width of the drop cursor | ||
| * @default 1 | ||
| * @example 2 | ||
| */ | ||
| width: number | undefined; | ||
| /** | ||
| * The class of the drop cursor | ||
| * @default undefined | ||
| * @example 'drop-cursor' | ||
| */ | ||
| class: string | undefined; | ||
| /** | ||
| * The color of the drop cursor. Use `false` to apply no color and rely only on class. | ||
| * @default 'currentColor' | ||
| * @example 'red' | ||
| */ | ||
| color?: string | false; | ||
| /** | ||
| * The width of the drop cursor | ||
| * @default 1 | ||
| * @example 2 | ||
| */ | ||
| width: number | undefined; | ||
| /** | ||
| * The class of the drop cursor | ||
| * @default undefined | ||
| * @example 'drop-cursor' | ||
| */ | ||
| class: string | undefined; | ||
| } | ||
@@ -99,21 +100,22 @@ /** | ||
| declare const Dropcursor: Extension<DropcursorOptions, any>; | ||
| //#endregion | ||
| //#region src/focus/focus.d.ts | ||
| interface FocusOptions { | ||
| /** | ||
| * The class name that should be added to the focused node. | ||
| * @default 'has-focus' | ||
| * @example 'is-focused' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The mode by which the focused node is determined. | ||
| * - All: All nodes are marked as focused. | ||
| * - Deepest: Only the deepest node is marked as focused. | ||
| * - Shallowest: Only the shallowest node is marked as focused. | ||
| * | ||
| * @default 'all' | ||
| * @example 'deepest' | ||
| * @example 'shallowest' | ||
| */ | ||
| mode: 'all' | 'deepest' | 'shallowest'; | ||
| /** | ||
| * The class name that should be added to the focused node. | ||
| * @default 'has-focus' | ||
| * @example 'is-focused' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The mode by which the focused node is determined. | ||
| * - All: All nodes are marked as focused. | ||
| * - Deepest: Only the deepest node is marked as focused. | ||
| * - Shallowest: Only the shallowest node is marked as focused. | ||
| * | ||
| * @default 'all' | ||
| * @example 'deepest' | ||
| * @example 'shallowest' | ||
| */ | ||
| mode: 'all' | 'deepest' | 'shallowest'; | ||
| } | ||
@@ -125,16 +127,17 @@ /** | ||
| declare const Focus: Extension<FocusOptions, any>; | ||
| //#endregion | ||
| //#region src/gap-cursor/gap-cursor.d.ts | ||
| declare module '@tiptap/core' { | ||
| interface NodeConfig<Options, Storage> { | ||
| /** | ||
| * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`. | ||
| * @default null | ||
| */ | ||
| allowGapCursor?: boolean | null | ((this: { | ||
| name: string; | ||
| options: Options; | ||
| storage: Storage; | ||
| parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']; | ||
| }) => boolean | null); | ||
| } | ||
| interface NodeConfig<Options, Storage> { | ||
| /** | ||
| * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`. | ||
| * @default null | ||
| */ | ||
| allowGapCursor?: boolean | null | ((this: { | ||
| name: string; | ||
| options: Options; | ||
| storage: Storage; | ||
| parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']; | ||
| }) => boolean | null); | ||
| } | ||
| } | ||
@@ -148,3 +151,4 @@ /** | ||
| declare const Gapcursor: Extension<any, any>; | ||
| //#endregion | ||
| //#region src/placeholder/constants.d.ts | ||
| /** The default data attribute label */ | ||
@@ -154,65 +158,67 @@ declare const DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| declare const PLUGIN_KEY: PluginKey<DecorationSet>; | ||
| //#endregion | ||
| //#region src/placeholder/types.d.ts | ||
| interface PlaceholderOptions { | ||
| /** | ||
| * **The class name for the empty editor** | ||
| * @default 'is-editor-empty' | ||
| */ | ||
| emptyEditorClass: string; | ||
| /** | ||
| * **The class name for empty nodes** | ||
| * | ||
| * You can use a function to return a dynamic class name or a string. | ||
| * @default 'is-empty' | ||
| */ | ||
| emptyNodeClass: ((EmptyNodeClassProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **The data-attribute used for the placeholder label** | ||
| * Will be prepended with `data-` and converted to kebab-case and cleaned of special characters. | ||
| * @default 'placeholder' | ||
| */ | ||
| dataAttribute: string; | ||
| /** | ||
| * **The placeholder content** | ||
| * | ||
| * You can use a function to return a dynamic placeholder or a string. | ||
| * @default 'Write something …' | ||
| */ | ||
| placeholder: ((PlaceholderProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the editor is editable.** | ||
| * | ||
| * If true, the placeholder will only be shown when the editor is editable. | ||
| * If false, the placeholder will always be shown. | ||
| * @default true | ||
| */ | ||
| showOnlyWhenEditable: boolean; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the current node is empty.** | ||
| * | ||
| * If true, the placeholder will only be shown when the current node is empty. | ||
| * If false, the placeholder will be shown when any node is empty. | ||
| * @default true | ||
| */ | ||
| showOnlyCurrent: boolean; | ||
| /** | ||
| * **Controls if the placeholder should be shown for all descendants.** | ||
| * | ||
| * If true, the placeholder will be shown for all descendants. | ||
| * If false, the placeholder will only be shown for the current node. | ||
| * @default false | ||
| */ | ||
| includeChildren: boolean; | ||
| /** | ||
| * **The class name for the empty editor** | ||
| * @default 'is-editor-empty' | ||
| */ | ||
| emptyEditorClass: string; | ||
| /** | ||
| * **The class name for empty nodes** | ||
| * | ||
| * You can use a function to return a dynamic class name or a string. | ||
| * @default 'is-empty' | ||
| */ | ||
| emptyNodeClass: ((EmptyNodeClassProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **The data-attribute used for the placeholder label** | ||
| * Will be prepended with `data-` and converted to kebab-case and cleaned of special characters. | ||
| * @default 'placeholder' | ||
| */ | ||
| dataAttribute: string; | ||
| /** | ||
| * **The placeholder content** | ||
| * | ||
| * You can use a function to return a dynamic placeholder or a string. | ||
| * @default 'Write something …' | ||
| */ | ||
| placeholder: ((PlaceholderProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the editor is editable.** | ||
| * | ||
| * If true, the placeholder will only be shown when the editor is editable. | ||
| * If false, the placeholder will always be shown. | ||
| * @default true | ||
| */ | ||
| showOnlyWhenEditable: boolean; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the current node is empty.** | ||
| * | ||
| * If true, the placeholder will only be shown when the current node is empty. | ||
| * If false, the placeholder will be shown when any node is empty. | ||
| * @default true | ||
| */ | ||
| showOnlyCurrent: boolean; | ||
| /** | ||
| * **Controls if the placeholder should be shown for all descendants.** | ||
| * | ||
| * If true, the placeholder will be shown for all descendants. | ||
| * If false, the placeholder will only be shown for the current node. | ||
| * @default false | ||
| */ | ||
| includeChildren: boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/placeholder/placeholder.d.ts | ||
| /** | ||
@@ -224,3 +230,4 @@ * This extension allows you to add a placeholder to your editor. | ||
| declare const Placeholder: Extension<PlaceholderOptions, any>; | ||
| //#endregion | ||
| //#region src/placeholder/utils/preparePlaceholderAttribute.d.ts | ||
| /** | ||
@@ -232,10 +239,11 @@ * Prepares the placeholder attribute by ensuring it is properly formatted. | ||
| declare function preparePlaceholderAttribute(attr: string): string; | ||
| //#endregion | ||
| //#region src/selection/selection.d.ts | ||
| type SelectionOptions = { | ||
| /** | ||
| * The class name that should be added to the selected text. | ||
| * @default 'selection' | ||
| * @example 'is-selected' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The class name that should be added to the selected text. | ||
| * @default 'selection' | ||
| * @example 'is-selected' | ||
| */ | ||
| className: string; | ||
| }; | ||
@@ -249,3 +257,4 @@ /** | ||
| declare const Selection: Extension<SelectionOptions, any>; | ||
| //#endregion | ||
| //#region src/trailing-node/trailing-node.d.ts | ||
| declare const skipTrailingNodeMeta = "skipTrailingNode"; | ||
@@ -258,14 +267,14 @@ /** | ||
| interface TrailingNodeOptions { | ||
| /** | ||
| * The node type that should be inserted at the end of the document. | ||
| * @note the node will always be added to the `notAfter` lists to | ||
| * prevent an infinite loop. | ||
| * @default undefined | ||
| */ | ||
| node?: string; | ||
| /** | ||
| * The node types after which the trailing node should not be inserted. | ||
| * @default ['paragraph'] | ||
| */ | ||
| notAfter?: string | string[]; | ||
| /** | ||
| * The node type that should be inserted at the end of the document. | ||
| * @note the node will always be added to the `notAfter` lists to | ||
| * prevent an infinite loop. | ||
| * @default undefined | ||
| */ | ||
| node?: string; | ||
| /** | ||
| * The node types after which the trailing node should not be inserted. | ||
| * @default ['paragraph'] | ||
| */ | ||
| notAfter?: string | string[]; | ||
| } | ||
@@ -277,32 +286,33 @@ /** | ||
| declare const TrailingNode: Extension<TrailingNodeOptions, any>; | ||
| //#endregion | ||
| //#region src/undo-redo/undo-redo.d.ts | ||
| interface UndoRedoOptions { | ||
| /** | ||
| * The amount of history events that are collected before the oldest events are discarded. | ||
| * @default 100 | ||
| * @example 50 | ||
| */ | ||
| depth: number; | ||
| /** | ||
| * The delay (in milliseconds) between changes after which a new group should be started. | ||
| * @default 500 | ||
| * @example 1000 | ||
| */ | ||
| newGroupDelay: number; | ||
| /** | ||
| * The amount of history events that are collected before the oldest events are discarded. | ||
| * @default 100 | ||
| * @example 50 | ||
| */ | ||
| depth: number; | ||
| /** | ||
| * The delay (in milliseconds) between changes after which a new group should be started. | ||
| * @default 500 | ||
| * @example 1000 | ||
| */ | ||
| newGroupDelay: number; | ||
| } | ||
| declare module '@tiptap/core' { | ||
| interface Commands<ReturnType> { | ||
| undoRedo: { | ||
| /** | ||
| * Undo recent changes | ||
| * @example editor.commands.undo() | ||
| */ | ||
| undo: () => ReturnType; | ||
| /** | ||
| * Reapply reverted changes | ||
| * @example editor.commands.redo() | ||
| */ | ||
| redo: () => ReturnType; | ||
| }; | ||
| } | ||
| interface Commands<ReturnType> { | ||
| undoRedo: { | ||
| /** | ||
| * Undo recent changes | ||
| * @example editor.commands.undo() | ||
| */ | ||
| undo: () => ReturnType; | ||
| /** | ||
| * Reapply reverted changes | ||
| * @example editor.commands.redo() | ||
| */ | ||
| redo: () => ReturnType; | ||
| }; | ||
| } | ||
| } | ||
@@ -319,3 +329,4 @@ /** | ||
| declare const UndoRedo: Extension<UndoRedoOptions, any>; | ||
| export { CharacterCount, type CharacterCountOptions, type CharacterCountStorage, DEFAULT_DATA_ATTRIBUTE, Dropcursor, type DropcursorOptions, Focus, type FocusOptions, Gapcursor, PLUGIN_KEY, Placeholder, type PlaceholderOptions, Selection, type SelectionOptions, TrailingNode, type TrailingNodeOptions, UndoRedo, type UndoRedoOptions, preparePlaceholderAttribute, skipTrailingNodeMeta }; | ||
| //#endregion | ||
| export { CharacterCount, CharacterCountOptions, CharacterCountStorage, DEFAULT_DATA_ATTRIBUTE, Dropcursor, DropcursorOptions, Focus, FocusOptions, Gapcursor, PLUGIN_KEY, Placeholder, PlaceholderOptions, Selection, SelectionOptions, TrailingNode, TrailingNodeOptions, UndoRedo, UndoRedoOptions, preparePlaceholderAttribute, skipTrailingNodeMeta }; | ||
| //# sourceMappingURL=index.d.ts.map |
+653
-703
@@ -1,748 +0,698 @@ | ||
| // src/character-count/character-count.ts | ||
| import { Extension } from "@tiptap/core"; | ||
| import { Extension, callOrReturn, getChangedRanges, getExtensionField, isNodeEmpty, isNodeSelection } from "@tiptap/core"; | ||
| import { Plugin, PluginKey } from "@tiptap/pm/state"; | ||
| var CharacterCount = Extension.create({ | ||
| name: "characterCount", | ||
| addOptions() { | ||
| return { | ||
| limit: null, | ||
| autoTrim: true, | ||
| mode: "textSize", | ||
| textCounter: (text) => text.length, | ||
| wordCounter: (text) => text.split(" ").filter((word) => word !== "").length | ||
| }; | ||
| }, | ||
| addStorage() { | ||
| return { | ||
| characters: () => 0, | ||
| words: () => 0 | ||
| }; | ||
| }, | ||
| onBeforeCreate() { | ||
| this.storage.characters = (options) => { | ||
| const node = (options == null ? void 0 : options.node) || this.editor.state.doc; | ||
| const mode = (options == null ? void 0 : options.mode) || this.options.mode; | ||
| if (mode === "textSize") { | ||
| const text = node.textBetween(0, node.content.size, void 0, " "); | ||
| return this.options.textCounter(text); | ||
| } | ||
| return node.nodeSize; | ||
| }; | ||
| this.storage.words = (options) => { | ||
| const node = (options == null ? void 0 : options.node) || this.editor.state.doc; | ||
| const text = node.textBetween(0, node.content.size, " ", " "); | ||
| return this.options.wordCounter(text); | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| let initialEvaluationDone = false; | ||
| return [ | ||
| new Plugin({ | ||
| key: new PluginKey("characterCount"), | ||
| appendTransaction: (transactions, oldState, newState) => { | ||
| if (initialEvaluationDone) { | ||
| return; | ||
| } | ||
| const limit = this.options.limit; | ||
| const autoTrim = this.options.autoTrim; | ||
| if (limit === null || limit === void 0 || limit === 0 || autoTrim === false) { | ||
| initialEvaluationDone = true; | ||
| return; | ||
| } | ||
| const initialContentSize = this.storage.characters({ node: newState.doc }); | ||
| if (initialContentSize > limit) { | ||
| const over = initialContentSize - limit; | ||
| const from = 0; | ||
| const to = over; | ||
| console.warn( | ||
| `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.` | ||
| ); | ||
| const tr = newState.tr.deleteRange(from, to); | ||
| initialEvaluationDone = true; | ||
| return tr; | ||
| } | ||
| initialEvaluationDone = true; | ||
| }, | ||
| filterTransaction: (transaction, state) => { | ||
| const limit = this.options.limit; | ||
| if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) { | ||
| return true; | ||
| } | ||
| const oldSize = this.storage.characters({ node: state.doc }); | ||
| const newSize = this.storage.characters({ node: transaction.doc }); | ||
| if (newSize <= limit) { | ||
| return true; | ||
| } | ||
| if (oldSize > limit && newSize > limit && newSize <= oldSize) { | ||
| return true; | ||
| } | ||
| if (oldSize > limit && newSize > limit && newSize > oldSize) { | ||
| return false; | ||
| } | ||
| const isPaste = transaction.getMeta("paste"); | ||
| if (!isPaste) { | ||
| return false; | ||
| } | ||
| const pos = transaction.selection.$head.pos; | ||
| const over = newSize - limit; | ||
| const from = pos - over; | ||
| const to = pos; | ||
| transaction.deleteRange(from, to); | ||
| const updatedSize = this.storage.characters({ node: transaction.doc }); | ||
| if (updatedSize > limit) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| }); | ||
| // src/drop-cursor/drop-cursor.ts | ||
| import { Extension as Extension2 } from "@tiptap/core"; | ||
| import { dropCursor } from "@tiptap/pm/dropcursor"; | ||
| var Dropcursor = Extension2.create({ | ||
| name: "dropCursor", | ||
| addOptions() { | ||
| return { | ||
| color: "currentColor", | ||
| width: 1, | ||
| class: void 0 | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [dropCursor(this.options)]; | ||
| } | ||
| }); | ||
| // src/focus/focus.ts | ||
| import { Extension as Extension3 } from "@tiptap/core"; | ||
| import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state"; | ||
| import { Decoration, DecorationSet } from "@tiptap/pm/view"; | ||
| var Focus = Extension3.create({ | ||
| name: "focus", | ||
| addOptions() { | ||
| return { | ||
| className: "has-focus", | ||
| mode: "all" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [ | ||
| new Plugin2({ | ||
| key: new PluginKey2("focus"), | ||
| props: { | ||
| decorations: ({ doc, selection }) => { | ||
| const { isEditable, isFocused } = this.editor; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| if (!isEditable || !isFocused) { | ||
| return DecorationSet.create(doc, []); | ||
| } | ||
| let maxLevels = 0; | ||
| if (this.options.mode === "deepest") { | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) { | ||
| return; | ||
| } | ||
| const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; | ||
| if (!isCurrent) { | ||
| return false; | ||
| } | ||
| maxLevels += 1; | ||
| }); | ||
| } | ||
| let currentLevel = 0; | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) { | ||
| return false; | ||
| } | ||
| const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; | ||
| if (!isCurrent) { | ||
| return false; | ||
| } | ||
| currentLevel += 1; | ||
| const outOfScope = this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1; | ||
| if (outOfScope) { | ||
| return this.options.mode === "deepest"; | ||
| } | ||
| decorations.push( | ||
| Decoration.node(pos, pos + node.nodeSize, { | ||
| class: this.options.className | ||
| }) | ||
| ); | ||
| }); | ||
| return DecorationSet.create(doc, decorations); | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| }); | ||
| // src/gap-cursor/gap-cursor.ts | ||
| import { callOrReturn, Extension as Extension4, getExtensionField } from "@tiptap/core"; | ||
| import { gapCursor } from "@tiptap/pm/gapcursor"; | ||
| var Gapcursor = Extension4.create({ | ||
| name: "gapCursor", | ||
| addProseMirrorPlugins() { | ||
| return [gapCursor()]; | ||
| }, | ||
| extendNodeSchema(extension) { | ||
| var _a; | ||
| const context = { | ||
| name: extension.name, | ||
| options: extension.options, | ||
| storage: extension.storage | ||
| }; | ||
| return { | ||
| allowGapCursor: (_a = callOrReturn(getExtensionField(extension, "allowGapCursor", context))) != null ? _a : null | ||
| }; | ||
| } | ||
| import { history, redo, undo } from "@tiptap/pm/history"; | ||
| //#region src/character-count/character-count.ts | ||
| /** | ||
| * This extension allows you to count the characters and words of your document. | ||
| * @see https://tiptap.dev/api/extensions/character-count | ||
| */ | ||
| const CharacterCount = Extension.create({ | ||
| name: "characterCount", | ||
| addOptions() { | ||
| return { | ||
| limit: null, | ||
| autoTrim: true, | ||
| mode: "textSize", | ||
| textCounter: (text) => text.length, | ||
| wordCounter: (text) => text.split(" ").filter((word) => word !== "").length | ||
| }; | ||
| }, | ||
| addStorage() { | ||
| return { | ||
| characters: () => 0, | ||
| words: () => 0 | ||
| }; | ||
| }, | ||
| onBeforeCreate() { | ||
| this.storage.characters = (options) => { | ||
| const node = (options === null || options === void 0 ? void 0 : options.node) || this.editor.state.doc; | ||
| if (((options === null || options === void 0 ? void 0 : options.mode) || this.options.mode) === "textSize") { | ||
| const text = node.textBetween(0, node.content.size, void 0, " "); | ||
| return this.options.textCounter(text); | ||
| } | ||
| return node.nodeSize; | ||
| }; | ||
| this.storage.words = (options) => { | ||
| const node = (options === null || options === void 0 ? void 0 : options.node) || this.editor.state.doc; | ||
| const text = node.textBetween(0, node.content.size, " ", " "); | ||
| return this.options.wordCounter(text); | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| let initialEvaluationDone = false; | ||
| return [new Plugin({ | ||
| key: new PluginKey("characterCount"), | ||
| appendTransaction: (transactions, oldState, newState) => { | ||
| if (initialEvaluationDone) return; | ||
| const limit = this.options.limit; | ||
| const autoTrim = this.options.autoTrim; | ||
| if (limit === null || limit === void 0 || limit === 0 || autoTrim === false) { | ||
| initialEvaluationDone = true; | ||
| return; | ||
| } | ||
| const initialContentSize = this.storage.characters({ node: newState.doc }); | ||
| if (initialContentSize > limit) { | ||
| const over = initialContentSize - limit; | ||
| const from = 0; | ||
| const to = over; | ||
| console.warn(`[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`); | ||
| const tr = newState.tr.deleteRange(from, to); | ||
| initialEvaluationDone = true; | ||
| return tr; | ||
| } | ||
| initialEvaluationDone = true; | ||
| }, | ||
| filterTransaction: (transaction, state) => { | ||
| const limit = this.options.limit; | ||
| if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) return true; | ||
| const oldSize = this.storage.characters({ node: state.doc }); | ||
| const newSize = this.storage.characters({ node: transaction.doc }); | ||
| if (newSize <= limit) return true; | ||
| if (oldSize > limit && newSize > limit && newSize <= oldSize) return true; | ||
| if (oldSize > limit && newSize > limit && newSize > oldSize) return false; | ||
| if (!transaction.getMeta("paste")) return false; | ||
| const pos = transaction.selection.$head.pos; | ||
| const from = pos - (newSize - limit); | ||
| const to = pos; | ||
| transaction.deleteRange(from, to); | ||
| if (this.storage.characters({ node: transaction.doc }) > limit) return false; | ||
| return true; | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| // src/placeholder/constants.ts | ||
| import { PluginKey as PluginKey3 } from "@tiptap/pm/state"; | ||
| var DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| var PLUGIN_KEY = new PluginKey3("tiptap__placeholder"); | ||
| // src/placeholder/placeholder.ts | ||
| import { Extension as Extension5 } from "@tiptap/core"; | ||
| // src/placeholder/plugins/PlaceholderPlugin.ts | ||
| import { Plugin as Plugin3 } from "@tiptap/pm/state"; | ||
| import { DecorationSet as DecorationSet4 } from "@tiptap/pm/view"; | ||
| // src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| import { isNodeEmpty } from "@tiptap/core"; | ||
| import { DecorationSet as DecorationSet2 } from "@tiptap/pm/view"; | ||
| // src/placeholder/utils/createPlaceholderDecoration.ts | ||
| import { Decoration as Decoration2 } from "@tiptap/pm/view"; | ||
| //#endregion | ||
| //#region src/drop-cursor/drop-cursor.ts | ||
| /** | ||
| * This extension allows you to add a drop cursor to your editor. | ||
| * A drop cursor is a line that appears when you drag and drop content | ||
| * in-between nodes. | ||
| * @see https://tiptap.dev/api/extensions/dropcursor | ||
| */ | ||
| const Dropcursor = Extension.create({ | ||
| name: "dropCursor", | ||
| addOptions() { | ||
| return { | ||
| color: "currentColor", | ||
| width: 1, | ||
| class: void 0 | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [dropCursor(this.options)]; | ||
| } | ||
| }); | ||
| //#endregion | ||
| //#region src/focus/focus.ts | ||
| /** | ||
| * This extension allows you to add a class to the focused node. | ||
| * @see https://www.tiptap.dev/api/extensions/focus | ||
| */ | ||
| const Focus = Extension.create({ | ||
| name: "focus", | ||
| addOptions() { | ||
| return { | ||
| className: "has-focus", | ||
| mode: "all" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [new Plugin({ | ||
| key: new PluginKey("focus"), | ||
| props: { decorations: ({ doc, selection }) => { | ||
| const { isEditable, isFocused } = this.editor; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| if (!isEditable || !isFocused) return DecorationSet.create(doc, []); | ||
| let maxLevels = 0; | ||
| if (this.options.mode === "deepest") doc.descendants((node, pos) => { | ||
| if (node.isText) return; | ||
| if (!(anchor >= pos && anchor <= pos + node.nodeSize - 1)) return false; | ||
| maxLevels += 1; | ||
| }); | ||
| let currentLevel = 0; | ||
| doc.descendants((node, pos) => { | ||
| if (node.isText) return false; | ||
| if (!(anchor >= pos && anchor <= pos + node.nodeSize - 1)) return false; | ||
| currentLevel += 1; | ||
| if (this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1) return this.options.mode === "deepest"; | ||
| decorations.push(Decoration.node(pos, pos + node.nodeSize, { class: this.options.className })); | ||
| }); | ||
| return DecorationSet.create(doc, decorations); | ||
| } } | ||
| })]; | ||
| } | ||
| }); | ||
| //#endregion | ||
| //#region src/gap-cursor/gap-cursor.ts | ||
| /** | ||
| * This extension allows you to add a gap cursor to your editor. | ||
| * A gap cursor is a cursor that appears when you click on a place | ||
| * where no content is present, for example inbetween nodes. | ||
| * @see https://tiptap.dev/api/extensions/gapcursor | ||
| */ | ||
| const Gapcursor = Extension.create({ | ||
| name: "gapCursor", | ||
| addProseMirrorPlugins() { | ||
| return [gapCursor()]; | ||
| }, | ||
| extendNodeSchema(extension) { | ||
| var _callOrReturn; | ||
| return { allowGapCursor: (_callOrReturn = callOrReturn(getExtensionField(extension, "allowGapCursor", { | ||
| name: extension.name, | ||
| options: extension.options, | ||
| storage: extension.storage | ||
| }))) !== null && _callOrReturn !== void 0 ? _callOrReturn : null }; | ||
| } | ||
| }); | ||
| //#endregion | ||
| //#region src/placeholder/constants.ts | ||
| /** The default data attribute label */ | ||
| const DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| /** The plugin key used to store and read the placeholder decoration set */ | ||
| const PLUGIN_KEY = new PluginKey("tiptap__placeholder"); | ||
| //#endregion | ||
| //#region src/placeholder/utils/createPlaceholderDecoration.ts | ||
| /** | ||
| * Creates a ProseMirror node decoration that applies a placeholder | ||
| * CSS class and data attribute to an empty node. | ||
| * @param options.editor - The editor instance | ||
| * @param options.pos - The position of the node in the document | ||
| * @param options.node - The ProseMirror node | ||
| * @param options.isEmptyDoc - Whether the entire document is empty | ||
| * @param options.hasAnchor - Whether the selection anchor is within the node | ||
| * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`) | ||
| * @param options.classes - CSS classes for empty nodes and the empty editor | ||
| * @param options.placeholder - The placeholder text or a function that returns it | ||
| * @returns A ProseMirror node decoration with placeholder classes and data attribute | ||
| */ | ||
| function createPlaceholderDecoration(options) { | ||
| const { | ||
| editor, | ||
| placeholder, | ||
| dataAttribute, | ||
| pos, | ||
| node, | ||
| isEmptyDoc, | ||
| hasAnchor, | ||
| classes: { emptyNode, emptyEditor } | ||
| } = options; | ||
| const classes = [emptyNode]; | ||
| if (isEmptyDoc) { | ||
| classes.push(emptyEditor); | ||
| } | ||
| return Decoration2.node(pos, pos + node.nodeSize, { | ||
| class: classes.join(" "), | ||
| [dataAttribute]: typeof placeholder === "function" ? placeholder({ | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) : placeholder | ||
| }); | ||
| const { editor, placeholder, dataAttribute, pos, node, isEmptyDoc, hasAnchor, classes: { emptyNode, emptyEditor } } = options; | ||
| const classes = [emptyNode]; | ||
| if (isEmptyDoc) classes.push(emptyEditor); | ||
| return Decoration.node(pos, pos + node.nodeSize, { | ||
| class: classes.join(" "), | ||
| [dataAttribute]: typeof placeholder === "function" ? placeholder({ | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) : placeholder | ||
| }); | ||
| } | ||
| // src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| function resolveEmptyNodeClass(emptyNodeClass, props) { | ||
| return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass; | ||
| return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass; | ||
| } | ||
| function scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }) { | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| doc.nodesBetween(from, to, (node, pos) => { | ||
| const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; | ||
| const isEmpty = !node.isLeaf && isNodeEmpty(node); | ||
| if (!node.type.isTextblock) { | ||
| return options.includeChildren; | ||
| } | ||
| if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) { | ||
| decorations.push( | ||
| createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos | ||
| }) | ||
| ); | ||
| } | ||
| return options.includeChildren; | ||
| }); | ||
| return decorations; | ||
| /** | ||
| * Scans a document range for empty textblocks that should receive placeholder | ||
| * decorations. Used by the slow path and incremental state updates. | ||
| */ | ||
| function scanRangeForDecorations({ editor, options, dataAttribute, doc, selection, from, to }) { | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| doc.nodesBetween(from, to, (node, pos) => { | ||
| const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; | ||
| const isEmpty = !node.isLeaf && isNodeEmpty(node); | ||
| if (!node.type.isTextblock) return options.includeChildren; | ||
| if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) decorations.push(createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos | ||
| })); | ||
| return options.includeChildren; | ||
| }); | ||
| return decorations; | ||
| } | ||
| function buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) { | ||
| const active = editor.isEditable || !options.showOnlyWhenEditable; | ||
| if (!active) { | ||
| return null; | ||
| } | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| if (useResolvedPath) { | ||
| const resolved = doc.resolve(anchor); | ||
| const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter; | ||
| const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor; | ||
| if (node && node.type.isTextblock && isNodeEmpty(node)) { | ||
| const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize; | ||
| decorations.push( | ||
| createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos: nodeStart, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos: nodeStart | ||
| }) | ||
| ); | ||
| } | ||
| } else { | ||
| decorations.push( | ||
| ...scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from: 0, | ||
| to: doc.content.size | ||
| }) | ||
| ); | ||
| } | ||
| return DecorationSet2.create(doc, decorations); | ||
| /** | ||
| * Builds the placeholder decorations for the current document state. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @param options.dataAttribute - The prepared `data-*` attribute name. | ||
| * @param options.doc - The current document node. | ||
| * @param options.selection - The current selection. | ||
| * @returns A decoration set, or `null` when no placeholders should be shown. | ||
| */ | ||
| function buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) { | ||
| if (!(editor.isEditable || !options.showOnlyWhenEditable)) return null; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| if (options.showOnlyCurrent && !options.includeChildren) { | ||
| const resolved = doc.resolve(anchor); | ||
| const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter; | ||
| const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor; | ||
| if (node && node.type.isTextblock && isNodeEmpty(node)) { | ||
| const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize; | ||
| decorations.push(createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos: nodeStart, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos: nodeStart | ||
| })); | ||
| } | ||
| } else decorations.push(...scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from: 0, | ||
| to: doc.content.size | ||
| })); | ||
| return DecorationSet.create(doc, decorations); | ||
| } | ||
| // src/placeholder/utils/placeholderStateField.ts | ||
| import { getChangedRanges } from "@tiptap/core"; | ||
| import { DecorationSet as DecorationSet3 } from "@tiptap/pm/view"; | ||
| // src/placeholder/utils/resolveTopLevelRange.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/resolveTopLevelRange.ts | ||
| /** | ||
| * Resolves a document position to the `[from, to)` range of its containing | ||
| * top-level block node in absolute document positions. | ||
| */ | ||
| function resolveTopLevelRange(doc, pos) { | ||
| var _a; | ||
| const resolved = doc.resolve(pos); | ||
| if (resolved.depth === 0) { | ||
| const node2 = (_a = resolved.nodeAfter) != null ? _a : resolved.nodeBefore; | ||
| if (!node2) { | ||
| return { from: pos, to: pos }; | ||
| } | ||
| const nodePos = resolved.nodeAfter ? pos : pos - node2.nodeSize; | ||
| return { from: nodePos, to: nodePos + node2.nodeSize }; | ||
| } | ||
| const topLevelPos = resolved.before(1); | ||
| const node = resolved.node(1); | ||
| return { from: topLevelPos, to: topLevelPos + node.nodeSize }; | ||
| const resolved = doc.resolve(pos); | ||
| if (resolved.depth === 0) { | ||
| var _resolved$nodeAfter; | ||
| const node = (_resolved$nodeAfter = resolved.nodeAfter) !== null && _resolved$nodeAfter !== void 0 ? _resolved$nodeAfter : resolved.nodeBefore; | ||
| if (!node) return { | ||
| from: pos, | ||
| to: pos | ||
| }; | ||
| const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize; | ||
| return { | ||
| from: nodePos, | ||
| to: nodePos + node.nodeSize | ||
| }; | ||
| } | ||
| const topLevelPos = resolved.before(1); | ||
| return { | ||
| from: topLevelPos, | ||
| to: topLevelPos + resolved.node(1).nodeSize | ||
| }; | ||
| } | ||
| /** | ||
| * Converts an absolute document range to content-relative positions used by | ||
| * `Node#nodesBetween` and `Node#forEach` offsets. | ||
| */ | ||
| function toContentRelativeRange(doc, range) { | ||
| return { | ||
| from: Math.max(0, range.from - 1), | ||
| to: Math.min(doc.content.size, range.to - 1) | ||
| }; | ||
| return { | ||
| from: Math.max(0, range.from - 1), | ||
| to: Math.min(doc.content.size, range.to - 1) | ||
| }; | ||
| } | ||
| /** | ||
| * Returns the top-level block ranges that intersect a document change range. | ||
| * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`). | ||
| * Returned ranges are content-relative, matching `Node#forEach` offsets. | ||
| */ | ||
| function getTopLevelBlocksInRange(doc, from, to) { | ||
| const ranges = []; | ||
| doc.forEach((node, offset) => { | ||
| const nodeStart = offset; | ||
| const nodeEnd = nodeStart + node.nodeSize; | ||
| const absNodeStart = nodeStart + 1; | ||
| const absNodeEnd = nodeEnd + 1; | ||
| if (absNodeStart < to && absNodeEnd > from) { | ||
| ranges.push({ from: nodeStart, to: nodeEnd }); | ||
| } | ||
| }); | ||
| return ranges; | ||
| const ranges = []; | ||
| doc.forEach((node, offset) => { | ||
| const nodeStart = offset; | ||
| const nodeEnd = nodeStart + node.nodeSize; | ||
| const absNodeStart = nodeStart + 1; | ||
| const absNodeEnd = nodeEnd + 1; | ||
| if (absNodeStart < to && absNodeEnd > from) ranges.push({ | ||
| from: nodeStart, | ||
| to: nodeEnd | ||
| }); | ||
| }); | ||
| return ranges; | ||
| } | ||
| /** | ||
| * Sorts ranges by start position and merges overlapping or adjacent ranges. | ||
| */ | ||
| function mergeRanges(ranges) { | ||
| if (ranges.length === 0) { | ||
| return []; | ||
| } | ||
| const sorted = [...ranges].sort((a, b) => a.from - b.from); | ||
| const merged = [{ ...sorted[0] }]; | ||
| for (let i = 1; i < sorted.length; i += 1) { | ||
| const last = merged[merged.length - 1]; | ||
| const current = sorted[i]; | ||
| if (current.from <= last.to) { | ||
| last.to = Math.max(last.to, current.to); | ||
| } else { | ||
| merged.push({ ...current }); | ||
| } | ||
| } | ||
| return merged; | ||
| if (ranges.length === 0) return []; | ||
| const sorted = [...ranges].sort((a, b) => a.from - b.from); | ||
| const merged = [{ ...sorted[0] }]; | ||
| for (let i = 1; i < sorted.length; i += 1) { | ||
| const last = merged[merged.length - 1]; | ||
| const current = sorted[i]; | ||
| if (current.from <= last.to) last.to = Math.max(last.to, current.to); | ||
| else merged.push({ ...current }); | ||
| } | ||
| return merged; | ||
| } | ||
| // src/placeholder/utils/placeholderStateField.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/placeholderStateField.ts | ||
| /** | ||
| * Expands a single changed range to the top-level blocks it touches. | ||
| * Also resolves blocks at range boundaries so split/merge edits update | ||
| * adjacent empty nodes (e.g. a new paragraph after Enter). | ||
| */ | ||
| function collectBlocksForChange(doc, change) { | ||
| const ranges = getTopLevelBlocksInRange(doc, change.from, change.to); | ||
| ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from))); | ||
| if (change.to > change.from) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| doc, | ||
| resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1) | ||
| ) | ||
| ); | ||
| } else if (change.from < doc.content.size + 1) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| doc, | ||
| resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)) | ||
| ) | ||
| ); | ||
| } | ||
| return ranges; | ||
| const ranges = getTopLevelBlocksInRange(doc, change.from, change.to); | ||
| ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from))); | ||
| if (change.to > change.from) ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1))); | ||
| else if (change.from < doc.content.size + 1) ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)))); | ||
| return ranges; | ||
| } | ||
| /** | ||
| * Collects content-relative top-level block ranges that need placeholder | ||
| * decorations recomputed after a transaction. | ||
| */ | ||
| function collectRescanRanges(tr, oldState, newState) { | ||
| const ranges = []; | ||
| if (tr.docChanged) { | ||
| const changes = getChangedRanges(tr); | ||
| for (const change of changes) { | ||
| ranges.push(...collectBlocksForChange(newState.doc, change.newRange)); | ||
| } | ||
| } | ||
| if (tr.selectionSet) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| newState.doc, | ||
| resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)) | ||
| ) | ||
| ); | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| newState.doc, | ||
| resolveTopLevelRange(newState.doc, newState.selection.anchor) | ||
| ) | ||
| ); | ||
| } | ||
| return mergeRanges(ranges); | ||
| const ranges = []; | ||
| if (tr.docChanged) { | ||
| const changes = getChangedRanges(tr); | ||
| for (const change of changes) ranges.push(...collectBlocksForChange(newState.doc, change.newRange)); | ||
| } | ||
| if (tr.selectionSet) { | ||
| ranges.push(toContentRelativeRange(newState.doc, resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)))); | ||
| ranges.push(toContentRelativeRange(newState.doc, resolveTopLevelRange(newState.doc, newState.selection.anchor))); | ||
| } | ||
| return mergeRanges(ranges); | ||
| } | ||
| /** Clamps a content-relative range to `[0, doc.content.size]`. */ | ||
| function clampRange(from, to, doc) { | ||
| const clampedFrom = Math.max(0, Math.min(from, doc.content.size)); | ||
| const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size)); | ||
| return { from: clampedFrom, to: clampedTo }; | ||
| const clampedFrom = Math.max(0, Math.min(from, doc.content.size)); | ||
| return { | ||
| from: clampedFrom, | ||
| to: Math.max(clampedFrom, Math.min(to, doc.content.size)) | ||
| }; | ||
| } | ||
| function updateDecorationsInRanges({ | ||
| decorations, | ||
| ranges, | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) { | ||
| let next = decorations; | ||
| for (const range of ranges) { | ||
| const { from, to } = clampRange(range.from, range.to, doc); | ||
| const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to); | ||
| if (existing.length) { | ||
| next = next.remove(existing); | ||
| } | ||
| const newDecos = scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }); | ||
| if (newDecos.length) { | ||
| next = next.add(doc, newDecos); | ||
| } | ||
| } | ||
| return next; | ||
| /** | ||
| * Removes and rebuilds placeholder decorations within the given ranges. | ||
| * Only drops decorations fully contained in a range so mapped decorations | ||
| * on neighbouring blocks (e.g. at a block boundary) are kept intact. | ||
| */ | ||
| function updateDecorationsInRanges({ decorations, ranges, editor, options, dataAttribute, doc, selection }) { | ||
| let next = decorations; | ||
| for (const range of ranges) { | ||
| const { from, to } = clampRange(range.from, range.to, doc); | ||
| const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to); | ||
| if (existing.length) next = next.remove(existing); | ||
| const newDecos = scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }); | ||
| if (newDecos.length) next = next.add(doc, newDecos); | ||
| } | ||
| return next; | ||
| } | ||
| function createPlaceholderStateField({ | ||
| editor, | ||
| options, | ||
| dataAttribute | ||
| }) { | ||
| return { | ||
| init(_config, state) { | ||
| const decorations = buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: state.doc, | ||
| selection: state.selection | ||
| }); | ||
| return decorations != null ? decorations : DecorationSet3.empty; | ||
| }, | ||
| apply(tr, prev, oldState, newState) { | ||
| if (!tr.docChanged && !tr.selectionSet) { | ||
| return prev; | ||
| } | ||
| const mapped = prev.map(tr.mapping, tr.doc); | ||
| const ranges = collectRescanRanges(tr, oldState, newState); | ||
| return updateDecorationsInRanges({ | ||
| decorations: mapped, | ||
| ranges, | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: newState.doc, | ||
| selection: newState.selection | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Creates the incremental `StateField<DecorationSet>` used by the slow path | ||
| * (`showOnlyCurrent: false` or `includeChildren: true`). | ||
| * | ||
| * Decorations are mapped through each transaction and only recomputed for | ||
| * top-level blocks touched by document or selection changes. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @param options.dataAttribute - The prepared `data-*` attribute name. | ||
| * @returns A ProseMirror state field storing the placeholder decoration set. | ||
| */ | ||
| function createPlaceholderStateField({ editor, options, dataAttribute }) { | ||
| return { | ||
| init(_config, state) { | ||
| const decorations = buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: state.doc, | ||
| selection: state.selection | ||
| }); | ||
| return decorations !== null && decorations !== void 0 ? decorations : DecorationSet.empty; | ||
| }, | ||
| apply(tr, prev, oldState, newState) { | ||
| if (!tr.docChanged && !tr.selectionSet) return prev; | ||
| return updateDecorationsInRanges({ | ||
| decorations: prev.map(tr.mapping, tr.doc), | ||
| ranges: collectRescanRanges(tr, oldState, newState), | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: newState.doc, | ||
| selection: newState.selection | ||
| }); | ||
| } | ||
| }; | ||
| } | ||
| // src/placeholder/utils/preparePlaceholderAttribute.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/preparePlaceholderAttribute.ts | ||
| /** | ||
| * Prepares the placeholder attribute by ensuring it is properly formatted. | ||
| * @param attr - The placeholder attribute string. | ||
| * @returns The prepared placeholder attribute string. | ||
| */ | ||
| function preparePlaceholderAttribute(attr) { | ||
| return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase(); | ||
| return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase(); | ||
| } | ||
| // src/placeholder/plugins/PlaceholderPlugin.ts | ||
| //#endregion | ||
| //#region src/placeholder/plugins/PlaceholderPlugin.ts | ||
| /** | ||
| * Creates the ProseMirror plugin that renders placeholder decorations. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @returns The configured placeholder plugin. | ||
| */ | ||
| function createPlaceholderPlugin({ editor, options }) { | ||
| const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| return new Plugin3({ | ||
| key: PLUGIN_KEY, | ||
| ...useResolvedPath ? {} : { | ||
| state: createPlaceholderStateField({ editor, options, dataAttribute }) | ||
| }, | ||
| props: { | ||
| decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) : (state) => { | ||
| var _a; | ||
| if (options.showOnlyWhenEditable && !editor.isEditable) { | ||
| return DecorationSet4.empty; | ||
| } | ||
| return (_a = PLUGIN_KEY.getState(state)) != null ? _a : DecorationSet4.empty; | ||
| } | ||
| } | ||
| }); | ||
| const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| return new Plugin({ | ||
| key: PLUGIN_KEY, | ||
| ...useResolvedPath ? {} : { state: createPlaceholderStateField({ | ||
| editor, | ||
| options, | ||
| dataAttribute | ||
| }) }, | ||
| props: { decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) : (state) => { | ||
| var _PLUGIN_KEY$getState; | ||
| if (options.showOnlyWhenEditable && !editor.isEditable) return DecorationSet.empty; | ||
| return (_PLUGIN_KEY$getState = PLUGIN_KEY.getState(state)) !== null && _PLUGIN_KEY$getState !== void 0 ? _PLUGIN_KEY$getState : DecorationSet.empty; | ||
| } } | ||
| }); | ||
| } | ||
| // src/placeholder/placeholder.ts | ||
| var Placeholder = Extension5.create({ | ||
| name: "placeholder", | ||
| addOptions() { | ||
| return { | ||
| emptyEditorClass: "is-editor-empty", | ||
| emptyNodeClass: "is-empty", | ||
| dataAttribute: DEFAULT_DATA_ATTRIBUTE, | ||
| placeholder: "Write something \u2026", | ||
| showOnlyWhenEditable: true, | ||
| showOnlyCurrent: true, | ||
| includeChildren: false | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]; | ||
| } | ||
| //#endregion | ||
| //#region src/placeholder/placeholder.ts | ||
| /** | ||
| * This extension allows you to add a placeholder to your editor. | ||
| * A placeholder is a text that appears when the editor or a node is empty. | ||
| * @see https://www.tiptap.dev/api/extensions/placeholder | ||
| */ | ||
| const Placeholder = Extension.create({ | ||
| name: "placeholder", | ||
| addOptions() { | ||
| return { | ||
| emptyEditorClass: "is-editor-empty", | ||
| emptyNodeClass: "is-empty", | ||
| dataAttribute: DEFAULT_DATA_ATTRIBUTE, | ||
| placeholder: "Write something …", | ||
| showOnlyWhenEditable: true, | ||
| showOnlyCurrent: true, | ||
| includeChildren: false | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [createPlaceholderPlugin({ | ||
| editor: this.editor, | ||
| options: this.options | ||
| })]; | ||
| } | ||
| }); | ||
| // src/selection/selection.ts | ||
| import { Extension as Extension6, isNodeSelection } from "@tiptap/core"; | ||
| import { Plugin as Plugin4, PluginKey as PluginKey4 } from "@tiptap/pm/state"; | ||
| import { Decoration as Decoration3, DecorationSet as DecorationSet5 } from "@tiptap/pm/view"; | ||
| //#endregion | ||
| //#region src/selection/selection.ts | ||
| /** | ||
| * Whether the native browser selection should be cleared on blur and restored on focus. | ||
| * Only applies to non-empty text selections in an editable editor. | ||
| */ | ||
| function shouldSyncDomSelection(state, editor) { | ||
| return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable; | ||
| return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable; | ||
| } | ||
| /** | ||
| * Whether the selection decoration should be rendered to keep the selection | ||
| * visible while the editor is blurred (and not dragging). | ||
| */ | ||
| function shouldPreserveSelection(state, editor) { | ||
| return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging; | ||
| return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging; | ||
| } | ||
| function clearDomSelection() { | ||
| var _a; | ||
| (_a = window.getSelection()) == null ? void 0 : _a.removeAllRanges(); | ||
| var _window$getSelection; | ||
| (_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 || _window$getSelection.removeAllRanges(); | ||
| } | ||
| /** | ||
| * Sync the native selection from the editor state. | ||
| * @see https://prosemirror.net/docs/ref/#view.EditorView.focus | ||
| */ | ||
| function restoreDomSelection(view) { | ||
| view.focus(); | ||
| view.focus(); | ||
| } | ||
| var Selection = Extension6.create({ | ||
| name: "selection", | ||
| addOptions() { | ||
| return { | ||
| className: "selection" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| const { editor, options } = this; | ||
| return [ | ||
| new Plugin4({ | ||
| key: new PluginKey4("selection"), | ||
| props: { | ||
| decorations(state) { | ||
| if (!shouldPreserveSelection(state, editor)) { | ||
| return null; | ||
| } | ||
| return DecorationSet5.create(state.doc, [ | ||
| Decoration3.inline(state.selection.from, state.selection.to, { | ||
| class: options.className | ||
| }) | ||
| ]); | ||
| }, | ||
| handleDOMEvents: { | ||
| blur(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) { | ||
| return false; | ||
| } | ||
| clearDomSelection(); | ||
| return false; | ||
| }, | ||
| focus(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) { | ||
| return false; | ||
| } | ||
| requestAnimationFrame(() => { | ||
| if (!editor.isDestroyed && view.hasFocus()) { | ||
| restoreDomSelection(view); | ||
| } | ||
| }); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| /** | ||
| * This extension allows you to add a class to the selected text when the editor is blurred. | ||
| * It clears the native browser selection on blur (so `::selection` styles do not overlap the | ||
| * decoration) and restores it when the editor is focused again. | ||
| * @see https://www.tiptap.dev/api/extensions/selection | ||
| */ | ||
| const Selection = Extension.create({ | ||
| name: "selection", | ||
| addOptions() { | ||
| return { className: "selection" }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| const { editor, options } = this; | ||
| return [new Plugin({ | ||
| key: new PluginKey("selection"), | ||
| props: { | ||
| decorations(state) { | ||
| if (!shouldPreserveSelection(state, editor)) return null; | ||
| return DecorationSet.create(state.doc, [Decoration.inline(state.selection.from, state.selection.to, { class: options.className })]); | ||
| }, | ||
| handleDOMEvents: { | ||
| blur(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) return false; | ||
| clearDomSelection(); | ||
| return false; | ||
| }, | ||
| focus(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) return false; | ||
| requestAnimationFrame(() => { | ||
| if (!editor.isDestroyed && view.hasFocus()) restoreDomSelection(view); | ||
| }); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| // src/trailing-node/trailing-node.ts | ||
| import { Extension as Extension7 } from "@tiptap/core"; | ||
| import { Plugin as Plugin5, PluginKey as PluginKey5 } from "@tiptap/pm/state"; | ||
| var skipTrailingNodeMeta = "skipTrailingNode"; | ||
| function nodeEqualsType({ | ||
| types, | ||
| node | ||
| }) { | ||
| return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types; | ||
| //#endregion | ||
| //#region src/trailing-node/trailing-node.ts | ||
| const skipTrailingNodeMeta = "skipTrailingNode"; | ||
| function nodeEqualsType({ types, node }) { | ||
| return node && Array.isArray(types) && types.includes(node.type) || (node === null || node === void 0 ? void 0 : node.type) === types; | ||
| } | ||
| var TrailingNode = Extension7.create({ | ||
| name: "trailingNode", | ||
| addOptions() { | ||
| return { | ||
| node: void 0, | ||
| notAfter: [] | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| var _a; | ||
| const plugin = new PluginKey5(this.name); | ||
| const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph"; | ||
| const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name)); | ||
| return [ | ||
| new Plugin5({ | ||
| key: plugin, | ||
| appendTransaction: (transactions, __, state) => { | ||
| const { doc, tr, schema } = state; | ||
| const shouldInsertNodeAtEnd = plugin.getState(state); | ||
| const endPosition = doc.content.size; | ||
| const type = schema.nodes[defaultNode]; | ||
| if (transactions.some((transaction) => transaction.getMeta(skipTrailingNodeMeta))) { | ||
| return; | ||
| } | ||
| if (!shouldInsertNodeAtEnd) { | ||
| return; | ||
| } | ||
| return tr.insert(endPosition, type.create()); | ||
| }, | ||
| state: { | ||
| init: (_, state) => { | ||
| const lastNode = state.tr.doc.lastChild; | ||
| return !nodeEqualsType({ node: lastNode, types: disabledNodes }); | ||
| }, | ||
| apply: (tr, value) => { | ||
| if (!tr.docChanged) { | ||
| return value; | ||
| } | ||
| if (tr.getMeta("__uniqueIDTransaction")) { | ||
| return value; | ||
| } | ||
| const lastNode = tr.doc.lastChild; | ||
| return !nodeEqualsType({ node: lastNode, types: disabledNodes }); | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| /** | ||
| * This extension allows you to add an extra node at the end of the document. | ||
| * @see https://www.tiptap.dev/api/extensions/trailing-node | ||
| */ | ||
| const TrailingNode = Extension.create({ | ||
| name: "trailingNode", | ||
| addOptions() { | ||
| return { | ||
| node: void 0, | ||
| notAfter: [] | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| var _this$editor$schema$t; | ||
| const plugin = new PluginKey(this.name); | ||
| const defaultNode = this.options.node || ((_this$editor$schema$t = this.editor.schema.topNodeType.contentMatch.defaultType) === null || _this$editor$schema$t === void 0 ? void 0 : _this$editor$schema$t.name) || "paragraph"; | ||
| const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name)); | ||
| return [new Plugin({ | ||
| key: plugin, | ||
| appendTransaction: (transactions, __, state) => { | ||
| const { doc, tr, schema } = state; | ||
| const shouldInsertNodeAtEnd = plugin.getState(state); | ||
| const endPosition = doc.content.size; | ||
| const type = schema.nodes[defaultNode]; | ||
| if (transactions.some((transaction) => transaction.getMeta("skipTrailingNode"))) return; | ||
| if (!shouldInsertNodeAtEnd) return; | ||
| return tr.insert(endPosition, type.create()); | ||
| }, | ||
| state: { | ||
| init: (_, state) => { | ||
| const lastNode = state.tr.doc.lastChild; | ||
| return !nodeEqualsType({ | ||
| node: lastNode, | ||
| types: disabledNodes | ||
| }); | ||
| }, | ||
| apply: (tr, value) => { | ||
| if (!tr.docChanged) return value; | ||
| if (tr.getMeta("__uniqueIDTransaction")) return value; | ||
| const lastNode = tr.doc.lastChild; | ||
| return !nodeEqualsType({ | ||
| node: lastNode, | ||
| types: disabledNodes | ||
| }); | ||
| } | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| //#endregion | ||
| //#region src/undo-redo/undo-redo.ts | ||
| /** | ||
| * This extension allows you to undo and redo recent changes. | ||
| * @see https://www.tiptap.dev/api/extensions/undo-redo | ||
| * | ||
| * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove | ||
| * the `undo-redo` extension, as it is not compatible with the `collaboration` extension. | ||
| * | ||
| * `@tiptap/extension-collaboration` uses its own history implementation. | ||
| */ | ||
| const UndoRedo = Extension.create({ | ||
| name: "undoRedo", | ||
| addOptions() { | ||
| return { | ||
| depth: 100, | ||
| newGroupDelay: 500 | ||
| }; | ||
| }, | ||
| addCommands() { | ||
| return { | ||
| undo: () => ({ state, dispatch }) => { | ||
| return undo(state, dispatch); | ||
| }, | ||
| redo: () => ({ state, dispatch }) => { | ||
| return redo(state, dispatch); | ||
| } | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [history(this.options)]; | ||
| }, | ||
| addKeyboardShortcuts() { | ||
| return { | ||
| "Mod-z": () => this.editor.commands.undo(), | ||
| "Shift-Mod-z": () => this.editor.commands.redo(), | ||
| "Mod-y": () => this.editor.commands.redo(), | ||
| "Mod-я": () => this.editor.commands.undo(), | ||
| "Shift-Mod-я": () => this.editor.commands.redo() | ||
| }; | ||
| } | ||
| }); | ||
| //#endregion | ||
| export { CharacterCount, DEFAULT_DATA_ATTRIBUTE, Dropcursor, Focus, Gapcursor, PLUGIN_KEY, Placeholder, Selection, TrailingNode, UndoRedo, preparePlaceholderAttribute, skipTrailingNodeMeta }; | ||
| // src/undo-redo/undo-redo.ts | ||
| import { Extension as Extension8 } from "@tiptap/core"; | ||
| import { history, redo, undo } from "@tiptap/pm/history"; | ||
| var UndoRedo = Extension8.create({ | ||
| name: "undoRedo", | ||
| addOptions() { | ||
| return { | ||
| depth: 100, | ||
| newGroupDelay: 500 | ||
| }; | ||
| }, | ||
| addCommands() { | ||
| return { | ||
| undo: () => ({ state, dispatch }) => { | ||
| return undo(state, dispatch); | ||
| }, | ||
| redo: () => ({ state, dispatch }) => { | ||
| return redo(state, dispatch); | ||
| } | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [history(this.options)]; | ||
| }, | ||
| addKeyboardShortcuts() { | ||
| return { | ||
| "Mod-z": () => this.editor.commands.undo(), | ||
| "Shift-Mod-z": () => this.editor.commands.redo(), | ||
| "Mod-y": () => this.editor.commands.redo(), | ||
| // Russian keyboard layouts | ||
| "Mod-\u044F": () => this.editor.commands.undo(), | ||
| "Shift-Mod-\u044F": () => this.editor.commands.redo() | ||
| }; | ||
| } | ||
| }); | ||
| export { | ||
| CharacterCount, | ||
| DEFAULT_DATA_ATTRIBUTE, | ||
| Dropcursor, | ||
| Focus, | ||
| Gapcursor, | ||
| PLUGIN_KEY, | ||
| Placeholder, | ||
| Selection, | ||
| TrailingNode, | ||
| UndoRedo, | ||
| preparePlaceholderAttribute, | ||
| skipTrailingNodeMeta | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/character-count/character-count.ts","../src/drop-cursor/drop-cursor.ts","../src/focus/focus.ts","../src/gap-cursor/gap-cursor.ts","../src/placeholder/constants.ts","../src/placeholder/placeholder.ts","../src/placeholder/plugins/PlaceholderPlugin.ts","../src/placeholder/utils/buildPlaceholderDecorations.ts","../src/placeholder/utils/createPlaceholderDecoration.ts","../src/placeholder/utils/placeholderStateField.ts","../src/placeholder/utils/resolveTopLevelRange.ts","../src/placeholder/utils/preparePlaceholderAttribute.ts","../src/selection/selection.ts","../src/trailing-node/trailing-node.ts","../src/undo-redo/undo-redo.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport interface CharacterCountOptions {\n /**\n * The maximum number of characters that should be allowed. Defaults to `0`.\n * @default null\n * @example 180\n */\n limit: number | null | undefined\n /**\n * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n * If set to `nodeSize`, the nodeSize of the document is used.\n * @default 'textSize'\n * @example 'textSize'\n */\n mode: 'textSize' | 'nodeSize'\n /**\n * Sets whether the content will be automatically trimmed when programatically setting content over the limit.\n * If set to false, the user will be able to trim the text manually.\n * @default true\n * @example false\n */\n autoTrim?: boolean\n /**\n * The text counter function to use. Defaults to a simple character count.\n * @default (text) => text.length\n * @example (text) => [...new Intl.Segmenter().segment(text)].length\n */\n textCounter: (text: string) => number\n /**\n * The word counter function to use. Defaults to a simple word count.\n * @default (text) => text.split(' ').filter(word => word !== '').length\n * @example (text) => text.split(/\\s+/).filter(word => word !== '').length\n */\n wordCounter: (text: string) => number\n}\n\nexport interface CharacterCountStorage {\n /**\n * Get the number of characters for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the characters from. Defaults to the current document.\n * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n */\n characters: (options?: { node?: ProseMirrorNode; mode?: 'textSize' | 'nodeSize' }) => number\n\n /**\n * Get the number of words for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the words from. Defaults to the current document.\n */\n words: (options?: { node?: ProseMirrorNode }) => number\n}\n\ndeclare module '@tiptap/core' {\n interface Storage {\n characterCount: CharacterCountStorage\n }\n}\n\n/**\n * This extension allows you to count the characters and words of your document.\n * @see https://tiptap.dev/api/extensions/character-count\n */\nexport const CharacterCount = Extension.create<CharacterCountOptions, CharacterCountStorage>({\n name: 'characterCount',\n\n addOptions() {\n return {\n limit: null,\n autoTrim: true,\n mode: 'textSize',\n textCounter: text => text.length,\n wordCounter: text => text.split(' ').filter(word => word !== '').length,\n }\n },\n\n addStorage() {\n return {\n characters: () => 0,\n words: () => 0,\n }\n },\n\n onBeforeCreate() {\n this.storage.characters = options => {\n const node = options?.node || this.editor.state.doc\n const mode = options?.mode || this.options.mode\n\n if (mode === 'textSize') {\n const text = node.textBetween(0, node.content.size, undefined, ' ')\n\n return this.options.textCounter(text)\n }\n\n return node.nodeSize\n }\n\n this.storage.words = options => {\n const node = options?.node || this.editor.state.doc\n const text = node.textBetween(0, node.content.size, ' ', ' ')\n\n return this.options.wordCounter(text)\n }\n },\n\n addProseMirrorPlugins() {\n let initialEvaluationDone = false\n\n return [\n new Plugin({\n key: new PluginKey('characterCount'),\n appendTransaction: (transactions, oldState, newState) => {\n if (initialEvaluationDone) {\n return\n }\n\n const limit = this.options.limit\n const autoTrim = this.options.autoTrim\n\n if (limit === null || limit === undefined || limit === 0 || autoTrim === false) {\n initialEvaluationDone = true\n return\n }\n\n const initialContentSize = this.storage.characters({ node: newState.doc })\n\n if (initialContentSize > limit) {\n const over = initialContentSize - limit\n const from = 0\n const to = over\n\n console.warn(\n `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`,\n )\n const tr = newState.tr.deleteRange(from, to)\n initialEvaluationDone = true\n return tr\n }\n\n initialEvaluationDone = true\n },\n filterTransaction: (transaction, state) => {\n const limit = this.options.limit\n\n // Nothing has changed or no limit is defined. Ignore it.\n if (!transaction.docChanged || limit === 0 || limit === null || limit === undefined) {\n return true\n }\n\n const oldSize = this.storage.characters({ node: state.doc })\n const newSize = this.storage.characters({ node: transaction.doc })\n\n // Everything is in the limit. Good.\n if (newSize <= limit) {\n return true\n }\n\n // The limit has already been exceeded but will be reduced.\n if (oldSize > limit && newSize > limit && newSize <= oldSize) {\n return true\n }\n\n // The limit has already been exceeded and will be increased further.\n if (oldSize > limit && newSize > limit && newSize > oldSize) {\n return false\n }\n\n const isPaste = transaction.getMeta('paste')\n\n // Block all exceeding transactions that were not pasted.\n if (!isPaste) {\n return false\n }\n\n // For pasted content, we try to remove the exceeding content.\n const pos = transaction.selection.$head.pos\n const over = newSize - limit\n const from = pos - over\n const to = pos\n\n // It’s probably a bad idea to mutate transactions within `filterTransaction`\n // but for now this is working fine.\n transaction.deleteRange(from, to)\n\n // In some situations, the limit will continue to be exceeded after trimming.\n // This happens e.g. when truncating within a complex node (e.g. table)\n // and ProseMirror has to close this node again.\n // If this is the case, we prevent the transaction completely.\n const updatedSize = this.storage.characters({ node: transaction.doc })\n\n if (updatedSize > limit) {\n return false\n }\n\n return true\n },\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { dropCursor } from '@tiptap/pm/dropcursor'\n\nexport interface DropcursorOptions {\n /**\n * The color of the drop cursor. Use `false` to apply no color and rely only on class.\n * @default 'currentColor'\n * @example 'red'\n */\n color?: string | false\n\n /**\n * The width of the drop cursor\n * @default 1\n * @example 2\n */\n width: number | undefined\n\n /**\n * The class of the drop cursor\n * @default undefined\n * @example 'drop-cursor'\n */\n class: string | undefined\n}\n\n/**\n * This extension allows you to add a drop cursor to your editor.\n * A drop cursor is a line that appears when you drag and drop content\n * in-between nodes.\n * @see https://tiptap.dev/api/extensions/dropcursor\n */\nexport const Dropcursor = Extension.create<DropcursorOptions>({\n name: 'dropCursor',\n\n addOptions() {\n return {\n color: 'currentColor',\n width: 1,\n class: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n return [dropCursor(this.options)]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport interface FocusOptions {\n /**\n * The class name that should be added to the focused node.\n * @default 'has-focus'\n * @example 'is-focused'\n */\n className: string\n\n /**\n * The mode by which the focused node is determined.\n * - All: All nodes are marked as focused.\n * - Deepest: Only the deepest node is marked as focused.\n * - Shallowest: Only the shallowest node is marked as focused.\n *\n * @default 'all'\n * @example 'deepest'\n * @example 'shallowest'\n */\n mode: 'all' | 'deepest' | 'shallowest'\n}\n\n/**\n * This extension allows you to add a class to the focused node.\n * @see https://www.tiptap.dev/api/extensions/focus\n */\nexport const Focus = Extension.create<FocusOptions>({\n name: 'focus',\n\n addOptions() {\n return {\n className: 'has-focus',\n mode: 'all',\n }\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('focus'),\n props: {\n decorations: ({ doc, selection }) => {\n const { isEditable, isFocused } = this.editor\n const { anchor } = selection\n const decorations: Decoration[] = []\n\n if (!isEditable || !isFocused) {\n return DecorationSet.create(doc, [])\n }\n\n // Maximum Levels\n let maxLevels = 0\n\n if (this.options.mode === 'deepest') {\n doc.descendants((node, pos) => {\n if (node.isText) {\n return\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n maxLevels += 1\n })\n }\n\n // Loop through current\n let currentLevel = 0\n\n doc.descendants((node, pos) => {\n if (node.isText) {\n return false\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n currentLevel += 1\n\n const outOfScope =\n (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) ||\n (this.options.mode === 'shallowest' && currentLevel > 1)\n\n if (outOfScope) {\n return this.options.mode === 'deepest'\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: this.options.className,\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n },\n },\n }),\n ]\n },\n})\n","import type { ParentConfig } from '@tiptap/core'\nimport { callOrReturn, Extension, getExtensionField } from '@tiptap/core'\nimport { gapCursor } from '@tiptap/pm/gapcursor'\n\ndeclare module '@tiptap/core' {\n interface NodeConfig<Options, Storage> {\n /**\n * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`.\n * @default null\n */\n allowGapCursor?:\n | boolean\n | null\n | ((this: {\n name: string\n options: Options\n storage: Storage\n parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']\n }) => boolean | null)\n }\n}\n\n/**\n * This extension allows you to add a gap cursor to your editor.\n * A gap cursor is a cursor that appears when you click on a place\n * where no content is present, for example inbetween nodes.\n * @see https://tiptap.dev/api/extensions/gapcursor\n */\nexport const Gapcursor = Extension.create({\n name: 'gapCursor',\n\n addProseMirrorPlugins() {\n return [gapCursor()]\n },\n\n extendNodeSchema(extension) {\n const context = {\n name: extension.name,\n options: extension.options,\n storage: extension.storage,\n }\n\n return {\n allowGapCursor: callOrReturn(getExtensionField(extension, 'allowGapCursor', context)) ?? null,\n }\n },\n})\n","import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n","import { Extension, isNodeSelection, type Editor } from '@tiptap/core'\nimport { Plugin, PluginKey, type EditorState } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport type SelectionOptions = {\n /**\n * The class name that should be added to the selected text.\n * @default 'selection'\n * @example 'is-selected'\n */\n className: string\n}\n\n/**\n * Whether the native browser selection should be cleared on blur and restored on focus.\n * Only applies to non-empty text selections in an editable editor.\n */\nfunction shouldSyncDomSelection(state: EditorState, editor: Editor): boolean {\n return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable\n}\n\n/**\n * Whether the selection decoration should be rendered to keep the selection\n * visible while the editor is blurred (and not dragging).\n */\nfunction shouldPreserveSelection(state: EditorState, editor: Editor): boolean {\n return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging\n}\n\nfunction clearDomSelection() {\n window.getSelection()?.removeAllRanges()\n}\n\n/**\n * Sync the native selection from the editor state.\n * @see https://prosemirror.net/docs/ref/#view.EditorView.focus\n */\nfunction restoreDomSelection(view: EditorView) {\n view.focus()\n}\n\n/**\n * This extension allows you to add a class to the selected text when the editor is blurred.\n * It clears the native browser selection on blur (so `::selection` styles do not overlap the\n * decoration) and restores it when the editor is focused again.\n * @see https://www.tiptap.dev/api/extensions/selection\n */\nexport const Selection = Extension.create<SelectionOptions>({\n name: 'selection',\n\n addOptions() {\n return {\n className: 'selection',\n }\n },\n\n addProseMirrorPlugins() {\n const { editor, options } = this\n\n return [\n new Plugin({\n key: new PluginKey('selection'),\n props: {\n decorations(state) {\n if (!shouldPreserveSelection(state, editor)) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(state.selection.from, state.selection.to, {\n class: options.className,\n }),\n ])\n },\n handleDOMEvents: {\n blur(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n clearDomSelection()\n\n return false\n },\n focus(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n requestAnimationFrame(() => {\n if (!editor.isDestroyed && view.hasFocus()) {\n restoreDomSelection(view)\n }\n })\n\n return false\n },\n },\n },\n }),\n ]\n },\n})\n\nexport default Selection\n","import { Extension } from '@tiptap/core'\nimport type { Node, NodeType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport const skipTrailingNodeMeta = 'skipTrailingNode'\n\nfunction nodeEqualsType({\n types,\n node,\n}: {\n types: NodeType | NodeType[]\n node: Node | null | undefined\n}) {\n return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types\n}\n\n/**\n * Extension based on:\n * - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js\n * - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts\n */\n\nexport interface TrailingNodeOptions {\n /**\n * The node type that should be inserted at the end of the document.\n * @note the node will always be added to the `notAfter` lists to\n * prevent an infinite loop.\n * @default undefined\n */\n node?: string\n /**\n * The node types after which the trailing node should not be inserted.\n * @default ['paragraph']\n */\n notAfter?: string | string[]\n}\n\n/**\n * This extension allows you to add an extra node at the end of the document.\n * @see https://www.tiptap.dev/api/extensions/trailing-node\n */\nexport const TrailingNode = Extension.create<TrailingNodeOptions>({\n name: 'trailingNode',\n\n addOptions() {\n return {\n node: undefined,\n notAfter: [],\n }\n },\n\n addProseMirrorPlugins() {\n const plugin = new PluginKey(this.name)\n const defaultNode =\n this.options.node ||\n this.editor.schema.topNodeType.contentMatch.defaultType?.name ||\n 'paragraph'\n\n const disabledNodes = Object.entries(this.editor.schema.nodes)\n .map(([, value]) => value)\n .filter(node => (this.options.notAfter || []).concat(defaultNode).includes(node.name))\n\n return [\n new Plugin({\n key: plugin,\n appendTransaction: (transactions, __, state) => {\n const { doc, tr, schema } = state\n const shouldInsertNodeAtEnd = plugin.getState(state)\n const endPosition = doc.content.size\n const type = schema.nodes[defaultNode]\n\n if (transactions.some(transaction => transaction.getMeta(skipTrailingNodeMeta))) {\n return\n }\n\n if (!shouldInsertNodeAtEnd) {\n return\n }\n\n return tr.insert(endPosition, type.create())\n },\n state: {\n init: (_, state) => {\n const lastNode = state.tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n apply: (tr, value) => {\n if (!tr.docChanged) {\n return value\n }\n\n // Ignore transactions from UniqueID extension to prevent infinite loops\n // when UniqueID adds IDs to newly inserted trailing nodes\n if (tr.getMeta('__uniqueIDTransaction')) {\n return value\n }\n\n const lastNode = tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n },\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { history, redo, undo } from '@tiptap/pm/history'\n\nexport interface UndoRedoOptions {\n /**\n * The amount of history events that are collected before the oldest events are discarded.\n * @default 100\n * @example 50\n */\n depth: number\n\n /**\n * The delay (in milliseconds) between changes after which a new group should be started.\n * @default 500\n * @example 1000\n */\n newGroupDelay: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n undoRedo: {\n /**\n * Undo recent changes\n * @example editor.commands.undo()\n */\n undo: () => ReturnType\n /**\n * Reapply reverted changes\n * @example editor.commands.redo()\n */\n redo: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to undo and redo recent changes.\n * @see https://www.tiptap.dev/api/extensions/undo-redo\n *\n * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove\n * the `undo-redo` extension, as it is not compatible with the `collaboration` extension.\n *\n * `@tiptap/extension-collaboration` uses its own history implementation.\n */\nexport const UndoRedo = Extension.create<UndoRedoOptions>({\n name: 'undoRedo',\n\n addOptions() {\n return {\n depth: 100,\n newGroupDelay: 500,\n }\n },\n\n addCommands() {\n return {\n undo:\n () =>\n ({ state, dispatch }) => {\n return undo(state, dispatch)\n },\n redo:\n () =>\n ({ state, dispatch }) => {\n return redo(state, dispatch)\n },\n }\n },\n\n addProseMirrorPlugins() {\n return [history(this.options)]\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-z': () => this.editor.commands.undo(),\n 'Shift-Mod-z': () => this.editor.commands.redo(),\n 'Mod-y': () => this.editor.commands.redo(),\n\n // Russian keyboard layouts\n 'Mod-я': () => this.editor.commands.undo(),\n 'Shift-Mod-я': () => this.editor.commands.redo(),\n }\n },\n})\n"],"mappings":"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","names":["Extension","Extension","Plugin","PluginKey","Extension","PluginKey","Extension","Plugin","DecorationSet","DecorationSet","Decoration","DecorationSet","DecorationSet","node","DecorationSet","Plugin","DecorationSet","Extension","Extension","Plugin","PluginKey","Decoration","DecorationSet","Extension","Plugin","PluginKey","Extension"]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../src/character-count/character-count.ts","../src/drop-cursor/drop-cursor.ts","../src/focus/focus.ts","../src/gap-cursor/gap-cursor.ts","../src/placeholder/constants.ts","../src/placeholder/utils/createPlaceholderDecoration.ts","../src/placeholder/utils/buildPlaceholderDecorations.ts","../src/placeholder/utils/resolveTopLevelRange.ts","../src/placeholder/utils/placeholderStateField.ts","../src/placeholder/utils/preparePlaceholderAttribute.ts","../src/placeholder/plugins/PlaceholderPlugin.ts","../src/placeholder/placeholder.ts","../src/selection/selection.ts","../src/trailing-node/trailing-node.ts","../src/undo-redo/undo-redo.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport interface CharacterCountOptions {\n /**\n * The maximum number of characters that should be allowed. Defaults to `0`.\n * @default null\n * @example 180\n */\n limit: number | null | undefined\n /**\n * The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n * If set to `nodeSize`, the nodeSize of the document is used.\n * @default 'textSize'\n * @example 'textSize'\n */\n mode: 'textSize' | 'nodeSize'\n /**\n * Sets whether the content will be automatically trimmed when programatically setting content over the limit.\n * If set to false, the user will be able to trim the text manually.\n * @default true\n * @example false\n */\n autoTrim?: boolean\n /**\n * The text counter function to use. Defaults to a simple character count.\n * @default (text) => text.length\n * @example (text) => [...new Intl.Segmenter().segment(text)].length\n */\n textCounter: (text: string) => number\n /**\n * The word counter function to use. Defaults to a simple word count.\n * @default (text) => text.split(' ').filter(word => word !== '').length\n * @example (text) => text.split(/\\s+/).filter(word => word !== '').length\n */\n wordCounter: (text: string) => number\n}\n\nexport interface CharacterCountStorage {\n /**\n * Get the number of characters for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the characters from. Defaults to the current document.\n * @param options.mode The mode by which the size is calculated. If set to `textSize`, the textContent of the document is used.\n */\n characters: (options?: { node?: ProseMirrorNode; mode?: 'textSize' | 'nodeSize' }) => number\n\n /**\n * Get the number of words for the current document.\n * @param options The options for the character count. (optional)\n * @param options.node The node to get the words from. Defaults to the current document.\n */\n words: (options?: { node?: ProseMirrorNode }) => number\n}\n\ndeclare module '@tiptap/core' {\n interface Storage {\n characterCount: CharacterCountStorage\n }\n}\n\n/**\n * This extension allows you to count the characters and words of your document.\n * @see https://tiptap.dev/api/extensions/character-count\n */\nexport const CharacterCount = Extension.create<CharacterCountOptions, CharacterCountStorage>({\n name: 'characterCount',\n\n addOptions() {\n return {\n limit: null,\n autoTrim: true,\n mode: 'textSize',\n textCounter: text => text.length,\n wordCounter: text => text.split(' ').filter(word => word !== '').length,\n }\n },\n\n addStorage() {\n return {\n characters: () => 0,\n words: () => 0,\n }\n },\n\n onBeforeCreate() {\n this.storage.characters = options => {\n const node = options?.node || this.editor.state.doc\n const mode = options?.mode || this.options.mode\n\n if (mode === 'textSize') {\n const text = node.textBetween(0, node.content.size, undefined, ' ')\n\n return this.options.textCounter(text)\n }\n\n return node.nodeSize\n }\n\n this.storage.words = options => {\n const node = options?.node || this.editor.state.doc\n const text = node.textBetween(0, node.content.size, ' ', ' ')\n\n return this.options.wordCounter(text)\n }\n },\n\n addProseMirrorPlugins() {\n let initialEvaluationDone = false\n\n return [\n new Plugin({\n key: new PluginKey('characterCount'),\n appendTransaction: (transactions, oldState, newState) => {\n if (initialEvaluationDone) {\n return\n }\n\n const limit = this.options.limit\n const autoTrim = this.options.autoTrim\n\n if (limit === null || limit === undefined || limit === 0 || autoTrim === false) {\n initialEvaluationDone = true\n return\n }\n\n const initialContentSize = this.storage.characters({ node: newState.doc })\n\n if (initialContentSize > limit) {\n const over = initialContentSize - limit\n const from = 0\n const to = over\n\n console.warn(\n `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`,\n )\n const tr = newState.tr.deleteRange(from, to)\n initialEvaluationDone = true\n return tr\n }\n\n initialEvaluationDone = true\n },\n filterTransaction: (transaction, state) => {\n const limit = this.options.limit\n\n // Nothing has changed or no limit is defined. Ignore it.\n if (!transaction.docChanged || limit === 0 || limit === null || limit === undefined) {\n return true\n }\n\n const oldSize = this.storage.characters({ node: state.doc })\n const newSize = this.storage.characters({ node: transaction.doc })\n\n // Everything is in the limit. Good.\n if (newSize <= limit) {\n return true\n }\n\n // The limit has already been exceeded but will be reduced.\n if (oldSize > limit && newSize > limit && newSize <= oldSize) {\n return true\n }\n\n // The limit has already been exceeded and will be increased further.\n if (oldSize > limit && newSize > limit && newSize > oldSize) {\n return false\n }\n\n const isPaste = transaction.getMeta('paste')\n\n // Block all exceeding transactions that were not pasted.\n if (!isPaste) {\n return false\n }\n\n // For pasted content, we try to remove the exceeding content.\n const pos = transaction.selection.$head.pos\n const over = newSize - limit\n const from = pos - over\n const to = pos\n\n // It’s probably a bad idea to mutate transactions within `filterTransaction`\n // but for now this is working fine.\n transaction.deleteRange(from, to)\n\n // In some situations, the limit will continue to be exceeded after trimming.\n // This happens e.g. when truncating within a complex node (e.g. table)\n // and ProseMirror has to close this node again.\n // If this is the case, we prevent the transaction completely.\n const updatedSize = this.storage.characters({ node: transaction.doc })\n\n if (updatedSize > limit) {\n return false\n }\n\n return true\n },\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { dropCursor } from '@tiptap/pm/dropcursor'\n\nexport interface DropcursorOptions {\n /**\n * The color of the drop cursor. Use `false` to apply no color and rely only on class.\n * @default 'currentColor'\n * @example 'red'\n */\n color?: string | false\n\n /**\n * The width of the drop cursor\n * @default 1\n * @example 2\n */\n width: number | undefined\n\n /**\n * The class of the drop cursor\n * @default undefined\n * @example 'drop-cursor'\n */\n class: string | undefined\n}\n\n/**\n * This extension allows you to add a drop cursor to your editor.\n * A drop cursor is a line that appears when you drag and drop content\n * in-between nodes.\n * @see https://tiptap.dev/api/extensions/dropcursor\n */\nexport const Dropcursor = Extension.create<DropcursorOptions>({\n name: 'dropCursor',\n\n addOptions() {\n return {\n color: 'currentColor',\n width: 1,\n class: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n return [dropCursor(this.options)]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport interface FocusOptions {\n /**\n * The class name that should be added to the focused node.\n * @default 'has-focus'\n * @example 'is-focused'\n */\n className: string\n\n /**\n * The mode by which the focused node is determined.\n * - All: All nodes are marked as focused.\n * - Deepest: Only the deepest node is marked as focused.\n * - Shallowest: Only the shallowest node is marked as focused.\n *\n * @default 'all'\n * @example 'deepest'\n * @example 'shallowest'\n */\n mode: 'all' | 'deepest' | 'shallowest'\n}\n\n/**\n * This extension allows you to add a class to the focused node.\n * @see https://www.tiptap.dev/api/extensions/focus\n */\nexport const Focus = Extension.create<FocusOptions>({\n name: 'focus',\n\n addOptions() {\n return {\n className: 'has-focus',\n mode: 'all',\n }\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('focus'),\n props: {\n decorations: ({ doc, selection }) => {\n const { isEditable, isFocused } = this.editor\n const { anchor } = selection\n const decorations: Decoration[] = []\n\n if (!isEditable || !isFocused) {\n return DecorationSet.create(doc, [])\n }\n\n // Maximum Levels\n let maxLevels = 0\n\n if (this.options.mode === 'deepest') {\n doc.descendants((node, pos) => {\n if (node.isText) {\n return\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n maxLevels += 1\n })\n }\n\n // Loop through current\n let currentLevel = 0\n\n doc.descendants((node, pos) => {\n if (node.isText) {\n return false\n }\n\n const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1\n\n if (!isCurrent) {\n return false\n }\n\n currentLevel += 1\n\n const outOfScope =\n (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) ||\n (this.options.mode === 'shallowest' && currentLevel > 1)\n\n if (outOfScope) {\n return this.options.mode === 'deepest'\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: this.options.className,\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n },\n },\n }),\n ]\n },\n})\n","import type { ParentConfig } from '@tiptap/core'\nimport { callOrReturn, Extension, getExtensionField } from '@tiptap/core'\nimport { gapCursor } from '@tiptap/pm/gapcursor'\n\ndeclare module '@tiptap/core' {\n interface NodeConfig<Options, Storage> {\n /**\n * A function to determine whether the gap cursor is allowed at the current position. Must return `true` or `false`.\n * @default null\n */\n allowGapCursor?:\n | boolean\n | null\n | ((this: {\n name: string\n options: Options\n storage: Storage\n parent: ParentConfig<NodeConfig<Options>>['allowGapCursor']\n }) => boolean | null)\n }\n}\n\n/**\n * This extension allows you to add a gap cursor to your editor.\n * A gap cursor is a cursor that appears when you click on a place\n * where no content is present, for example inbetween nodes.\n * @see https://tiptap.dev/api/extensions/gapcursor\n */\nexport const Gapcursor = Extension.create({\n name: 'gapCursor',\n\n addProseMirrorPlugins() {\n return [gapCursor()]\n },\n\n extendNodeSchema(extension) {\n const context = {\n name: extension.name,\n options: extension.options,\n storage: extension.storage,\n }\n\n return {\n allowGapCursor: callOrReturn(getExtensionField(extension, 'allowGapCursor', context)) ?? null,\n }\n },\n})\n","import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n","import { Extension, isNodeSelection, type Editor } from '@tiptap/core'\nimport { Plugin, PluginKey, type EditorState } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport type SelectionOptions = {\n /**\n * The class name that should be added to the selected text.\n * @default 'selection'\n * @example 'is-selected'\n */\n className: string\n}\n\n/**\n * Whether the native browser selection should be cleared on blur and restored on focus.\n * Only applies to non-empty text selections in an editable editor.\n */\nfunction shouldSyncDomSelection(state: EditorState, editor: Editor): boolean {\n return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable\n}\n\n/**\n * Whether the selection decoration should be rendered to keep the selection\n * visible while the editor is blurred (and not dragging).\n */\nfunction shouldPreserveSelection(state: EditorState, editor: Editor): boolean {\n return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging\n}\n\nfunction clearDomSelection() {\n window.getSelection()?.removeAllRanges()\n}\n\n/**\n * Sync the native selection from the editor state.\n * @see https://prosemirror.net/docs/ref/#view.EditorView.focus\n */\nfunction restoreDomSelection(view: EditorView) {\n view.focus()\n}\n\n/**\n * This extension allows you to add a class to the selected text when the editor is blurred.\n * It clears the native browser selection on blur (so `::selection` styles do not overlap the\n * decoration) and restores it when the editor is focused again.\n * @see https://www.tiptap.dev/api/extensions/selection\n */\nexport const Selection = Extension.create<SelectionOptions>({\n name: 'selection',\n\n addOptions() {\n return {\n className: 'selection',\n }\n },\n\n addProseMirrorPlugins() {\n const { editor, options } = this\n\n return [\n new Plugin({\n key: new PluginKey('selection'),\n props: {\n decorations(state) {\n if (!shouldPreserveSelection(state, editor)) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(state.selection.from, state.selection.to, {\n class: options.className,\n }),\n ])\n },\n handleDOMEvents: {\n blur(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n clearDomSelection()\n\n return false\n },\n focus(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n requestAnimationFrame(() => {\n if (!editor.isDestroyed && view.hasFocus()) {\n restoreDomSelection(view)\n }\n })\n\n return false\n },\n },\n },\n }),\n ]\n },\n})\n\nexport default Selection\n","import { Extension } from '@tiptap/core'\nimport type { Node, NodeType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport const skipTrailingNodeMeta = 'skipTrailingNode'\n\nfunction nodeEqualsType({\n types,\n node,\n}: {\n types: NodeType | NodeType[]\n node: Node | null | undefined\n}) {\n return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types\n}\n\n/**\n * Extension based on:\n * - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js\n * - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts\n */\n\nexport interface TrailingNodeOptions {\n /**\n * The node type that should be inserted at the end of the document.\n * @note the node will always be added to the `notAfter` lists to\n * prevent an infinite loop.\n * @default undefined\n */\n node?: string\n /**\n * The node types after which the trailing node should not be inserted.\n * @default ['paragraph']\n */\n notAfter?: string | string[]\n}\n\n/**\n * This extension allows you to add an extra node at the end of the document.\n * @see https://www.tiptap.dev/api/extensions/trailing-node\n */\nexport const TrailingNode = Extension.create<TrailingNodeOptions>({\n name: 'trailingNode',\n\n addOptions() {\n return {\n node: undefined,\n notAfter: [],\n }\n },\n\n addProseMirrorPlugins() {\n const plugin = new PluginKey(this.name)\n const defaultNode =\n this.options.node ||\n this.editor.schema.topNodeType.contentMatch.defaultType?.name ||\n 'paragraph'\n\n const disabledNodes = Object.entries(this.editor.schema.nodes)\n .map(([, value]) => value)\n .filter(node => (this.options.notAfter || []).concat(defaultNode).includes(node.name))\n\n return [\n new Plugin({\n key: plugin,\n appendTransaction: (transactions, __, state) => {\n const { doc, tr, schema } = state\n const shouldInsertNodeAtEnd = plugin.getState(state)\n const endPosition = doc.content.size\n const type = schema.nodes[defaultNode]\n\n if (transactions.some(transaction => transaction.getMeta(skipTrailingNodeMeta))) {\n return\n }\n\n if (!shouldInsertNodeAtEnd) {\n return\n }\n\n return tr.insert(endPosition, type.create())\n },\n state: {\n init: (_, state) => {\n const lastNode = state.tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n apply: (tr, value) => {\n if (!tr.docChanged) {\n return value\n }\n\n // Ignore transactions from UniqueID extension to prevent infinite loops\n // when UniqueID adds IDs to newly inserted trailing nodes\n if (tr.getMeta('__uniqueIDTransaction')) {\n return value\n }\n\n const lastNode = tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n },\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { history, redo, undo } from '@tiptap/pm/history'\n\nexport interface UndoRedoOptions {\n /**\n * The amount of history events that are collected before the oldest events are discarded.\n * @default 100\n * @example 50\n */\n depth: number\n\n /**\n * The delay (in milliseconds) between changes after which a new group should be started.\n * @default 500\n * @example 1000\n */\n newGroupDelay: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n undoRedo: {\n /**\n * Undo recent changes\n * @example editor.commands.undo()\n */\n undo: () => ReturnType\n /**\n * Reapply reverted changes\n * @example editor.commands.redo()\n */\n redo: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to undo and redo recent changes.\n * @see https://www.tiptap.dev/api/extensions/undo-redo\n *\n * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove\n * the `undo-redo` extension, as it is not compatible with the `collaboration` extension.\n *\n * `@tiptap/extension-collaboration` uses its own history implementation.\n */\nexport const UndoRedo = Extension.create<UndoRedoOptions>({\n name: 'undoRedo',\n\n addOptions() {\n return {\n depth: 100,\n newGroupDelay: 500,\n }\n },\n\n addCommands() {\n return {\n undo:\n () =>\n ({ state, dispatch }) => {\n return undo(state, dispatch)\n },\n redo:\n () =>\n ({ state, dispatch }) => {\n return redo(state, dispatch)\n },\n }\n },\n\n addProseMirrorPlugins() {\n return [history(this.options)]\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-z': () => this.editor.commands.undo(),\n 'Shift-Mod-z': () => this.editor.commands.redo(),\n 'Mod-y': () => this.editor.commands.redo(),\n\n // Russian keyboard layouts\n 'Mod-я': () => this.editor.commands.undo(),\n 'Shift-Mod-я': () => this.editor.commands.redo(),\n }\n },\n})\n"],"mappings":"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"} |
+344
-376
@@ -1,404 +0,372 @@ | ||
| "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/placeholder/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| DEFAULT_DATA_ATTRIBUTE: () => DEFAULT_DATA_ATTRIBUTE, | ||
| PLUGIN_KEY: () => PLUGIN_KEY, | ||
| Placeholder: () => Placeholder, | ||
| preparePlaceholderAttribute: () => preparePlaceholderAttribute | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/placeholder/constants.ts | ||
| var import_state = require("@tiptap/pm/state"); | ||
| var DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| var PLUGIN_KEY = new import_state.PluginKey("tiptap__placeholder"); | ||
| // src/placeholder/placeholder.ts | ||
| var import_core3 = require("@tiptap/core"); | ||
| // src/placeholder/plugins/PlaceholderPlugin.ts | ||
| var import_state2 = require("@tiptap/pm/state"); | ||
| var import_view4 = require("@tiptap/pm/view"); | ||
| // src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_view2 = require("@tiptap/pm/view"); | ||
| // src/placeholder/utils/createPlaceholderDecoration.ts | ||
| var import_view = require("@tiptap/pm/view"); | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_pm_state = require("@tiptap/pm/state"); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_view = require("@tiptap/pm/view"); | ||
| //#region src/placeholder/constants.ts | ||
| /** The default data attribute label */ | ||
| const DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| /** The plugin key used to store and read the placeholder decoration set */ | ||
| const PLUGIN_KEY = new _tiptap_pm_state.PluginKey("tiptap__placeholder"); | ||
| //#endregion | ||
| //#region src/placeholder/utils/createPlaceholderDecoration.ts | ||
| /** | ||
| * Creates a ProseMirror node decoration that applies a placeholder | ||
| * CSS class and data attribute to an empty node. | ||
| * @param options.editor - The editor instance | ||
| * @param options.pos - The position of the node in the document | ||
| * @param options.node - The ProseMirror node | ||
| * @param options.isEmptyDoc - Whether the entire document is empty | ||
| * @param options.hasAnchor - Whether the selection anchor is within the node | ||
| * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`) | ||
| * @param options.classes - CSS classes for empty nodes and the empty editor | ||
| * @param options.placeholder - The placeholder text or a function that returns it | ||
| * @returns A ProseMirror node decoration with placeholder classes and data attribute | ||
| */ | ||
| function createPlaceholderDecoration(options) { | ||
| const { | ||
| editor, | ||
| placeholder, | ||
| dataAttribute, | ||
| pos, | ||
| node, | ||
| isEmptyDoc, | ||
| hasAnchor, | ||
| classes: { emptyNode, emptyEditor } | ||
| } = options; | ||
| const classes = [emptyNode]; | ||
| if (isEmptyDoc) { | ||
| classes.push(emptyEditor); | ||
| } | ||
| return import_view.Decoration.node(pos, pos + node.nodeSize, { | ||
| class: classes.join(" "), | ||
| [dataAttribute]: typeof placeholder === "function" ? placeholder({ | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) : placeholder | ||
| }); | ||
| const { editor, placeholder, dataAttribute, pos, node, isEmptyDoc, hasAnchor, classes: { emptyNode, emptyEditor } } = options; | ||
| const classes = [emptyNode]; | ||
| if (isEmptyDoc) classes.push(emptyEditor); | ||
| return _tiptap_pm_view.Decoration.node(pos, pos + node.nodeSize, { | ||
| class: classes.join(" "), | ||
| [dataAttribute]: typeof placeholder === "function" ? placeholder({ | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) : placeholder | ||
| }); | ||
| } | ||
| // src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| function resolveEmptyNodeClass(emptyNodeClass, props) { | ||
| return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass; | ||
| return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass; | ||
| } | ||
| function scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }) { | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| doc.nodesBetween(from, to, (node, pos) => { | ||
| const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; | ||
| const isEmpty = !node.isLeaf && (0, import_core.isNodeEmpty)(node); | ||
| if (!node.type.isTextblock) { | ||
| return options.includeChildren; | ||
| } | ||
| if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) { | ||
| decorations.push( | ||
| createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos | ||
| }) | ||
| ); | ||
| } | ||
| return options.includeChildren; | ||
| }); | ||
| return decorations; | ||
| /** | ||
| * Scans a document range for empty textblocks that should receive placeholder | ||
| * decorations. Used by the slow path and incremental state updates. | ||
| */ | ||
| function scanRangeForDecorations({ editor, options, dataAttribute, doc, selection, from, to }) { | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| doc.nodesBetween(from, to, (node, pos) => { | ||
| const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; | ||
| const isEmpty = !node.isLeaf && (0, _tiptap_core.isNodeEmpty)(node); | ||
| if (!node.type.isTextblock) return options.includeChildren; | ||
| if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) decorations.push(createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos | ||
| })); | ||
| return options.includeChildren; | ||
| }); | ||
| return decorations; | ||
| } | ||
| function buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) { | ||
| const active = editor.isEditable || !options.showOnlyWhenEditable; | ||
| if (!active) { | ||
| return null; | ||
| } | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| if (useResolvedPath) { | ||
| const resolved = doc.resolve(anchor); | ||
| const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter; | ||
| const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor; | ||
| if (node && node.type.isTextblock && (0, import_core.isNodeEmpty)(node)) { | ||
| const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize; | ||
| decorations.push( | ||
| createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos: nodeStart, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos: nodeStart | ||
| }) | ||
| ); | ||
| } | ||
| } else { | ||
| decorations.push( | ||
| ...scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from: 0, | ||
| to: doc.content.size | ||
| }) | ||
| ); | ||
| } | ||
| return import_view2.DecorationSet.create(doc, decorations); | ||
| /** | ||
| * Builds the placeholder decorations for the current document state. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @param options.dataAttribute - The prepared `data-*` attribute name. | ||
| * @param options.doc - The current document node. | ||
| * @param options.selection - The current selection. | ||
| * @returns A decoration set, or `null` when no placeholders should be shown. | ||
| */ | ||
| function buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) { | ||
| if (!(editor.isEditable || !options.showOnlyWhenEditable)) return null; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| if (options.showOnlyCurrent && !options.includeChildren) { | ||
| const resolved = doc.resolve(anchor); | ||
| const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter; | ||
| const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor; | ||
| if (node && node.type.isTextblock && (0, _tiptap_core.isNodeEmpty)(node)) { | ||
| const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize; | ||
| decorations.push(createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos: nodeStart, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos: nodeStart | ||
| })); | ||
| } | ||
| } else decorations.push(...scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from: 0, | ||
| to: doc.content.size | ||
| })); | ||
| return _tiptap_pm_view.DecorationSet.create(doc, decorations); | ||
| } | ||
| // src/placeholder/utils/placeholderStateField.ts | ||
| var import_core2 = require("@tiptap/core"); | ||
| var import_view3 = require("@tiptap/pm/view"); | ||
| // src/placeholder/utils/resolveTopLevelRange.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/resolveTopLevelRange.ts | ||
| /** | ||
| * Resolves a document position to the `[from, to)` range of its containing | ||
| * top-level block node in absolute document positions. | ||
| */ | ||
| function resolveTopLevelRange(doc, pos) { | ||
| var _a; | ||
| const resolved = doc.resolve(pos); | ||
| if (resolved.depth === 0) { | ||
| const node2 = (_a = resolved.nodeAfter) != null ? _a : resolved.nodeBefore; | ||
| if (!node2) { | ||
| return { from: pos, to: pos }; | ||
| } | ||
| const nodePos = resolved.nodeAfter ? pos : pos - node2.nodeSize; | ||
| return { from: nodePos, to: nodePos + node2.nodeSize }; | ||
| } | ||
| const topLevelPos = resolved.before(1); | ||
| const node = resolved.node(1); | ||
| return { from: topLevelPos, to: topLevelPos + node.nodeSize }; | ||
| const resolved = doc.resolve(pos); | ||
| if (resolved.depth === 0) { | ||
| var _resolved$nodeAfter; | ||
| const node = (_resolved$nodeAfter = resolved.nodeAfter) !== null && _resolved$nodeAfter !== void 0 ? _resolved$nodeAfter : resolved.nodeBefore; | ||
| if (!node) return { | ||
| from: pos, | ||
| to: pos | ||
| }; | ||
| const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize; | ||
| return { | ||
| from: nodePos, | ||
| to: nodePos + node.nodeSize | ||
| }; | ||
| } | ||
| const topLevelPos = resolved.before(1); | ||
| return { | ||
| from: topLevelPos, | ||
| to: topLevelPos + resolved.node(1).nodeSize | ||
| }; | ||
| } | ||
| /** | ||
| * Converts an absolute document range to content-relative positions used by | ||
| * `Node#nodesBetween` and `Node#forEach` offsets. | ||
| */ | ||
| function toContentRelativeRange(doc, range) { | ||
| return { | ||
| from: Math.max(0, range.from - 1), | ||
| to: Math.min(doc.content.size, range.to - 1) | ||
| }; | ||
| return { | ||
| from: Math.max(0, range.from - 1), | ||
| to: Math.min(doc.content.size, range.to - 1) | ||
| }; | ||
| } | ||
| /** | ||
| * Returns the top-level block ranges that intersect a document change range. | ||
| * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`). | ||
| * Returned ranges are content-relative, matching `Node#forEach` offsets. | ||
| */ | ||
| function getTopLevelBlocksInRange(doc, from, to) { | ||
| const ranges = []; | ||
| doc.forEach((node, offset) => { | ||
| const nodeStart = offset; | ||
| const nodeEnd = nodeStart + node.nodeSize; | ||
| const absNodeStart = nodeStart + 1; | ||
| const absNodeEnd = nodeEnd + 1; | ||
| if (absNodeStart < to && absNodeEnd > from) { | ||
| ranges.push({ from: nodeStart, to: nodeEnd }); | ||
| } | ||
| }); | ||
| return ranges; | ||
| const ranges = []; | ||
| doc.forEach((node, offset) => { | ||
| const nodeStart = offset; | ||
| const nodeEnd = nodeStart + node.nodeSize; | ||
| const absNodeStart = nodeStart + 1; | ||
| const absNodeEnd = nodeEnd + 1; | ||
| if (absNodeStart < to && absNodeEnd > from) ranges.push({ | ||
| from: nodeStart, | ||
| to: nodeEnd | ||
| }); | ||
| }); | ||
| return ranges; | ||
| } | ||
| /** | ||
| * Sorts ranges by start position and merges overlapping or adjacent ranges. | ||
| */ | ||
| function mergeRanges(ranges) { | ||
| if (ranges.length === 0) { | ||
| return []; | ||
| } | ||
| const sorted = [...ranges].sort((a, b) => a.from - b.from); | ||
| const merged = [{ ...sorted[0] }]; | ||
| for (let i = 1; i < sorted.length; i += 1) { | ||
| const last = merged[merged.length - 1]; | ||
| const current = sorted[i]; | ||
| if (current.from <= last.to) { | ||
| last.to = Math.max(last.to, current.to); | ||
| } else { | ||
| merged.push({ ...current }); | ||
| } | ||
| } | ||
| return merged; | ||
| if (ranges.length === 0) return []; | ||
| const sorted = [...ranges].sort((a, b) => a.from - b.from); | ||
| const merged = [{ ...sorted[0] }]; | ||
| for (let i = 1; i < sorted.length; i += 1) { | ||
| const last = merged[merged.length - 1]; | ||
| const current = sorted[i]; | ||
| if (current.from <= last.to) last.to = Math.max(last.to, current.to); | ||
| else merged.push({ ...current }); | ||
| } | ||
| return merged; | ||
| } | ||
| // src/placeholder/utils/placeholderStateField.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/placeholderStateField.ts | ||
| /** | ||
| * Expands a single changed range to the top-level blocks it touches. | ||
| * Also resolves blocks at range boundaries so split/merge edits update | ||
| * adjacent empty nodes (e.g. a new paragraph after Enter). | ||
| */ | ||
| function collectBlocksForChange(doc, change) { | ||
| const ranges = getTopLevelBlocksInRange(doc, change.from, change.to); | ||
| ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from))); | ||
| if (change.to > change.from) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| doc, | ||
| resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1) | ||
| ) | ||
| ); | ||
| } else if (change.from < doc.content.size + 1) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| doc, | ||
| resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)) | ||
| ) | ||
| ); | ||
| } | ||
| return ranges; | ||
| const ranges = getTopLevelBlocksInRange(doc, change.from, change.to); | ||
| ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from))); | ||
| if (change.to > change.from) ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1))); | ||
| else if (change.from < doc.content.size + 1) ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)))); | ||
| return ranges; | ||
| } | ||
| /** | ||
| * Collects content-relative top-level block ranges that need placeholder | ||
| * decorations recomputed after a transaction. | ||
| */ | ||
| function collectRescanRanges(tr, oldState, newState) { | ||
| const ranges = []; | ||
| if (tr.docChanged) { | ||
| const changes = (0, import_core2.getChangedRanges)(tr); | ||
| for (const change of changes) { | ||
| ranges.push(...collectBlocksForChange(newState.doc, change.newRange)); | ||
| } | ||
| } | ||
| if (tr.selectionSet) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| newState.doc, | ||
| resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)) | ||
| ) | ||
| ); | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| newState.doc, | ||
| resolveTopLevelRange(newState.doc, newState.selection.anchor) | ||
| ) | ||
| ); | ||
| } | ||
| return mergeRanges(ranges); | ||
| const ranges = []; | ||
| if (tr.docChanged) { | ||
| const changes = (0, _tiptap_core.getChangedRanges)(tr); | ||
| for (const change of changes) ranges.push(...collectBlocksForChange(newState.doc, change.newRange)); | ||
| } | ||
| if (tr.selectionSet) { | ||
| ranges.push(toContentRelativeRange(newState.doc, resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)))); | ||
| ranges.push(toContentRelativeRange(newState.doc, resolveTopLevelRange(newState.doc, newState.selection.anchor))); | ||
| } | ||
| return mergeRanges(ranges); | ||
| } | ||
| /** Clamps a content-relative range to `[0, doc.content.size]`. */ | ||
| function clampRange(from, to, doc) { | ||
| const clampedFrom = Math.max(0, Math.min(from, doc.content.size)); | ||
| const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size)); | ||
| return { from: clampedFrom, to: clampedTo }; | ||
| const clampedFrom = Math.max(0, Math.min(from, doc.content.size)); | ||
| return { | ||
| from: clampedFrom, | ||
| to: Math.max(clampedFrom, Math.min(to, doc.content.size)) | ||
| }; | ||
| } | ||
| function updateDecorationsInRanges({ | ||
| decorations, | ||
| ranges, | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) { | ||
| let next = decorations; | ||
| for (const range of ranges) { | ||
| const { from, to } = clampRange(range.from, range.to, doc); | ||
| const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to); | ||
| if (existing.length) { | ||
| next = next.remove(existing); | ||
| } | ||
| const newDecos = scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }); | ||
| if (newDecos.length) { | ||
| next = next.add(doc, newDecos); | ||
| } | ||
| } | ||
| return next; | ||
| /** | ||
| * Removes and rebuilds placeholder decorations within the given ranges. | ||
| * Only drops decorations fully contained in a range so mapped decorations | ||
| * on neighbouring blocks (e.g. at a block boundary) are kept intact. | ||
| */ | ||
| function updateDecorationsInRanges({ decorations, ranges, editor, options, dataAttribute, doc, selection }) { | ||
| let next = decorations; | ||
| for (const range of ranges) { | ||
| const { from, to } = clampRange(range.from, range.to, doc); | ||
| const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to); | ||
| if (existing.length) next = next.remove(existing); | ||
| const newDecos = scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }); | ||
| if (newDecos.length) next = next.add(doc, newDecos); | ||
| } | ||
| return next; | ||
| } | ||
| function createPlaceholderStateField({ | ||
| editor, | ||
| options, | ||
| dataAttribute | ||
| }) { | ||
| return { | ||
| init(_config, state) { | ||
| const decorations = buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: state.doc, | ||
| selection: state.selection | ||
| }); | ||
| return decorations != null ? decorations : import_view3.DecorationSet.empty; | ||
| }, | ||
| apply(tr, prev, oldState, newState) { | ||
| if (!tr.docChanged && !tr.selectionSet) { | ||
| return prev; | ||
| } | ||
| const mapped = prev.map(tr.mapping, tr.doc); | ||
| const ranges = collectRescanRanges(tr, oldState, newState); | ||
| return updateDecorationsInRanges({ | ||
| decorations: mapped, | ||
| ranges, | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: newState.doc, | ||
| selection: newState.selection | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Creates the incremental `StateField<DecorationSet>` used by the slow path | ||
| * (`showOnlyCurrent: false` or `includeChildren: true`). | ||
| * | ||
| * Decorations are mapped through each transaction and only recomputed for | ||
| * top-level blocks touched by document or selection changes. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @param options.dataAttribute - The prepared `data-*` attribute name. | ||
| * @returns A ProseMirror state field storing the placeholder decoration set. | ||
| */ | ||
| function createPlaceholderStateField({ editor, options, dataAttribute }) { | ||
| return { | ||
| init(_config, state) { | ||
| const decorations = buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: state.doc, | ||
| selection: state.selection | ||
| }); | ||
| return decorations !== null && decorations !== void 0 ? decorations : _tiptap_pm_view.DecorationSet.empty; | ||
| }, | ||
| apply(tr, prev, oldState, newState) { | ||
| if (!tr.docChanged && !tr.selectionSet) return prev; | ||
| return updateDecorationsInRanges({ | ||
| decorations: prev.map(tr.mapping, tr.doc), | ||
| ranges: collectRescanRanges(tr, oldState, newState), | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: newState.doc, | ||
| selection: newState.selection | ||
| }); | ||
| } | ||
| }; | ||
| } | ||
| // src/placeholder/utils/preparePlaceholderAttribute.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/preparePlaceholderAttribute.ts | ||
| /** | ||
| * Prepares the placeholder attribute by ensuring it is properly formatted. | ||
| * @param attr - The placeholder attribute string. | ||
| * @returns The prepared placeholder attribute string. | ||
| */ | ||
| function preparePlaceholderAttribute(attr) { | ||
| return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase(); | ||
| return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase(); | ||
| } | ||
| // src/placeholder/plugins/PlaceholderPlugin.ts | ||
| //#endregion | ||
| //#region src/placeholder/plugins/PlaceholderPlugin.ts | ||
| /** | ||
| * Creates the ProseMirror plugin that renders placeholder decorations. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @returns The configured placeholder plugin. | ||
| */ | ||
| function createPlaceholderPlugin({ editor, options }) { | ||
| const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| return new import_state2.Plugin({ | ||
| key: PLUGIN_KEY, | ||
| ...useResolvedPath ? {} : { | ||
| state: createPlaceholderStateField({ editor, options, dataAttribute }) | ||
| }, | ||
| props: { | ||
| decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) : (state) => { | ||
| var _a; | ||
| if (options.showOnlyWhenEditable && !editor.isEditable) { | ||
| return import_view4.DecorationSet.empty; | ||
| } | ||
| return (_a = PLUGIN_KEY.getState(state)) != null ? _a : import_view4.DecorationSet.empty; | ||
| } | ||
| } | ||
| }); | ||
| const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| return new _tiptap_pm_state.Plugin({ | ||
| key: PLUGIN_KEY, | ||
| ...useResolvedPath ? {} : { state: createPlaceholderStateField({ | ||
| editor, | ||
| options, | ||
| dataAttribute | ||
| }) }, | ||
| props: { decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) : (state) => { | ||
| var _PLUGIN_KEY$getState; | ||
| if (options.showOnlyWhenEditable && !editor.isEditable) return _tiptap_pm_view.DecorationSet.empty; | ||
| return (_PLUGIN_KEY$getState = PLUGIN_KEY.getState(state)) !== null && _PLUGIN_KEY$getState !== void 0 ? _PLUGIN_KEY$getState : _tiptap_pm_view.DecorationSet.empty; | ||
| } } | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/placeholder/placeholder.ts | ||
| /** | ||
| * This extension allows you to add a placeholder to your editor. | ||
| * A placeholder is a text that appears when the editor or a node is empty. | ||
| * @see https://www.tiptap.dev/api/extensions/placeholder | ||
| */ | ||
| const Placeholder = _tiptap_core.Extension.create({ | ||
| name: "placeholder", | ||
| addOptions() { | ||
| return { | ||
| emptyEditorClass: "is-editor-empty", | ||
| emptyNodeClass: "is-empty", | ||
| dataAttribute: DEFAULT_DATA_ATTRIBUTE, | ||
| placeholder: "Write something …", | ||
| showOnlyWhenEditable: true, | ||
| showOnlyCurrent: true, | ||
| includeChildren: false | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [createPlaceholderPlugin({ | ||
| editor: this.editor, | ||
| options: this.options | ||
| })]; | ||
| } | ||
| }); | ||
| //#endregion | ||
| exports.DEFAULT_DATA_ATTRIBUTE = DEFAULT_DATA_ATTRIBUTE; | ||
| exports.PLUGIN_KEY = PLUGIN_KEY; | ||
| exports.Placeholder = Placeholder; | ||
| exports.preparePlaceholderAttribute = preparePlaceholderAttribute; | ||
| // src/placeholder/placeholder.ts | ||
| var Placeholder = import_core3.Extension.create({ | ||
| name: "placeholder", | ||
| addOptions() { | ||
| return { | ||
| emptyEditorClass: "is-editor-empty", | ||
| emptyNodeClass: "is-empty", | ||
| dataAttribute: DEFAULT_DATA_ATTRIBUTE, | ||
| placeholder: "Write something \u2026", | ||
| showOnlyWhenEditable: true, | ||
| showOnlyCurrent: true, | ||
| includeChildren: false | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| DEFAULT_DATA_ATTRIBUTE, | ||
| PLUGIN_KEY, | ||
| Placeholder, | ||
| preparePlaceholderAttribute | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/placeholder/index.ts","../../src/placeholder/constants.ts","../../src/placeholder/placeholder.ts","../../src/placeholder/plugins/PlaceholderPlugin.ts","../../src/placeholder/utils/buildPlaceholderDecorations.ts","../../src/placeholder/utils/createPlaceholderDecoration.ts","../../src/placeholder/utils/placeholderStateField.ts","../../src/placeholder/utils/resolveTopLevelRange.ts","../../src/placeholder/utils/preparePlaceholderAttribute.ts"],"sourcesContent":["export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from './constants.js'\nexport * from './placeholder.js'\nexport * from './types.js'\nexport { preparePlaceholderAttribute } from './utils/preparePlaceholderAttribute.js'\n","import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n"],"mappings":"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","names":["import_core","import_state","import_view","import_view","import_core","import_view","node"]} | ||
| {"version":3,"file":"index.cjs","names":["PluginKey","Decoration","DecorationSet","DecorationSet","Plugin","DecorationSet","Extension"],"sources":["../../src/placeholder/constants.ts","../../src/placeholder/utils/createPlaceholderDecoration.ts","../../src/placeholder/utils/buildPlaceholderDecorations.ts","../../src/placeholder/utils/resolveTopLevelRange.ts","../../src/placeholder/utils/placeholderStateField.ts","../../src/placeholder/utils/preparePlaceholderAttribute.ts","../../src/placeholder/plugins/PlaceholderPlugin.ts","../../src/placeholder/placeholder.ts"],"sourcesContent":["import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n"],"mappings":"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"} |
@@ -1,6 +0,6 @@ | ||
| import { PluginKey } from '@tiptap/pm/state'; | ||
| import { DecorationSet } from '@tiptap/pm/view'; | ||
| import { Editor, Extension } from '@tiptap/core'; | ||
| import { Node } from '@tiptap/pm/model'; | ||
| import { PluginKey } from "@tiptap/pm/state"; | ||
| import { DecorationSet } from "@tiptap/pm/view"; | ||
| import { Editor, Extension } from "@tiptap/core"; | ||
| import { Node } from "@tiptap/pm/model"; | ||
| //#region src/placeholder/constants.d.ts | ||
| /** The default data attribute label */ | ||
@@ -10,65 +10,67 @@ declare const DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| declare const PLUGIN_KEY: PluginKey<DecorationSet>; | ||
| //#endregion | ||
| //#region src/placeholder/types.d.ts | ||
| interface PlaceholderOptions { | ||
| /** | ||
| * **The class name for the empty editor** | ||
| * @default 'is-editor-empty' | ||
| */ | ||
| emptyEditorClass: string; | ||
| /** | ||
| * **The class name for empty nodes** | ||
| * | ||
| * You can use a function to return a dynamic class name or a string. | ||
| * @default 'is-empty' | ||
| */ | ||
| emptyNodeClass: ((EmptyNodeClassProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **The data-attribute used for the placeholder label** | ||
| * Will be prepended with `data-` and converted to kebab-case and cleaned of special characters. | ||
| * @default 'placeholder' | ||
| */ | ||
| dataAttribute: string; | ||
| /** | ||
| * **The placeholder content** | ||
| * | ||
| * You can use a function to return a dynamic placeholder or a string. | ||
| * @default 'Write something …' | ||
| */ | ||
| placeholder: ((PlaceholderProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the editor is editable.** | ||
| * | ||
| * If true, the placeholder will only be shown when the editor is editable. | ||
| * If false, the placeholder will always be shown. | ||
| * @default true | ||
| */ | ||
| showOnlyWhenEditable: boolean; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the current node is empty.** | ||
| * | ||
| * If true, the placeholder will only be shown when the current node is empty. | ||
| * If false, the placeholder will be shown when any node is empty. | ||
| * @default true | ||
| */ | ||
| showOnlyCurrent: boolean; | ||
| /** | ||
| * **Controls if the placeholder should be shown for all descendants.** | ||
| * | ||
| * If true, the placeholder will be shown for all descendants. | ||
| * If false, the placeholder will only be shown for the current node. | ||
| * @default false | ||
| */ | ||
| includeChildren: boolean; | ||
| /** | ||
| * **The class name for the empty editor** | ||
| * @default 'is-editor-empty' | ||
| */ | ||
| emptyEditorClass: string; | ||
| /** | ||
| * **The class name for empty nodes** | ||
| * | ||
| * You can use a function to return a dynamic class name or a string. | ||
| * @default 'is-empty' | ||
| */ | ||
| emptyNodeClass: ((EmptyNodeClassProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **The data-attribute used for the placeholder label** | ||
| * Will be prepended with `data-` and converted to kebab-case and cleaned of special characters. | ||
| * @default 'placeholder' | ||
| */ | ||
| dataAttribute: string; | ||
| /** | ||
| * **The placeholder content** | ||
| * | ||
| * You can use a function to return a dynamic placeholder or a string. | ||
| * @default 'Write something …' | ||
| */ | ||
| placeholder: ((PlaceholderProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the editor is editable.** | ||
| * | ||
| * If true, the placeholder will only be shown when the editor is editable. | ||
| * If false, the placeholder will always be shown. | ||
| * @default true | ||
| */ | ||
| showOnlyWhenEditable: boolean; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the current node is empty.** | ||
| * | ||
| * If true, the placeholder will only be shown when the current node is empty. | ||
| * If false, the placeholder will be shown when any node is empty. | ||
| * @default true | ||
| */ | ||
| showOnlyCurrent: boolean; | ||
| /** | ||
| * **Controls if the placeholder should be shown for all descendants.** | ||
| * | ||
| * If true, the placeholder will be shown for all descendants. | ||
| * If false, the placeholder will only be shown for the current node. | ||
| * @default false | ||
| */ | ||
| includeChildren: boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/placeholder/placeholder.d.ts | ||
| /** | ||
@@ -80,3 +82,4 @@ * This extension allows you to add a placeholder to your editor. | ||
| declare const Placeholder: Extension<PlaceholderOptions, any>; | ||
| //#endregion | ||
| //#region src/placeholder/utils/preparePlaceholderAttribute.d.ts | ||
| /** | ||
@@ -88,3 +91,4 @@ * Prepares the placeholder attribute by ensuring it is properly formatted. | ||
| declare function preparePlaceholderAttribute(attr: string): string; | ||
| export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY, Placeholder, type PlaceholderOptions, preparePlaceholderAttribute }; | ||
| //#endregion | ||
| export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY, Placeholder, PlaceholderOptions, preparePlaceholderAttribute }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,6 +0,6 @@ | ||
| import { PluginKey } from '@tiptap/pm/state'; | ||
| import { DecorationSet } from '@tiptap/pm/view'; | ||
| import { Editor, Extension } from '@tiptap/core'; | ||
| import { Node } from '@tiptap/pm/model'; | ||
| import { PluginKey } from "@tiptap/pm/state"; | ||
| import { Editor, Extension } from "@tiptap/core"; | ||
| import { DecorationSet } from "@tiptap/pm/view"; | ||
| import { Node } from "@tiptap/pm/model"; | ||
| //#region src/placeholder/constants.d.ts | ||
| /** The default data attribute label */ | ||
@@ -10,65 +10,67 @@ declare const DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| declare const PLUGIN_KEY: PluginKey<DecorationSet>; | ||
| //#endregion | ||
| //#region src/placeholder/types.d.ts | ||
| interface PlaceholderOptions { | ||
| /** | ||
| * **The class name for the empty editor** | ||
| * @default 'is-editor-empty' | ||
| */ | ||
| emptyEditorClass: string; | ||
| /** | ||
| * **The class name for empty nodes** | ||
| * | ||
| * You can use a function to return a dynamic class name or a string. | ||
| * @default 'is-empty' | ||
| */ | ||
| emptyNodeClass: ((EmptyNodeClassProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **The data-attribute used for the placeholder label** | ||
| * Will be prepended with `data-` and converted to kebab-case and cleaned of special characters. | ||
| * @default 'placeholder' | ||
| */ | ||
| dataAttribute: string; | ||
| /** | ||
| * **The placeholder content** | ||
| * | ||
| * You can use a function to return a dynamic placeholder or a string. | ||
| * @default 'Write something …' | ||
| */ | ||
| placeholder: ((PlaceholderProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the editor is editable.** | ||
| * | ||
| * If true, the placeholder will only be shown when the editor is editable. | ||
| * If false, the placeholder will always be shown. | ||
| * @default true | ||
| */ | ||
| showOnlyWhenEditable: boolean; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the current node is empty.** | ||
| * | ||
| * If true, the placeholder will only be shown when the current node is empty. | ||
| * If false, the placeholder will be shown when any node is empty. | ||
| * @default true | ||
| */ | ||
| showOnlyCurrent: boolean; | ||
| /** | ||
| * **Controls if the placeholder should be shown for all descendants.** | ||
| * | ||
| * If true, the placeholder will be shown for all descendants. | ||
| * If false, the placeholder will only be shown for the current node. | ||
| * @default false | ||
| */ | ||
| includeChildren: boolean; | ||
| /** | ||
| * **The class name for the empty editor** | ||
| * @default 'is-editor-empty' | ||
| */ | ||
| emptyEditorClass: string; | ||
| /** | ||
| * **The class name for empty nodes** | ||
| * | ||
| * You can use a function to return a dynamic class name or a string. | ||
| * @default 'is-empty' | ||
| */ | ||
| emptyNodeClass: ((EmptyNodeClassProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **The data-attribute used for the placeholder label** | ||
| * Will be prepended with `data-` and converted to kebab-case and cleaned of special characters. | ||
| * @default 'placeholder' | ||
| */ | ||
| dataAttribute: string; | ||
| /** | ||
| * **The placeholder content** | ||
| * | ||
| * You can use a function to return a dynamic placeholder or a string. | ||
| * @default 'Write something …' | ||
| */ | ||
| placeholder: ((PlaceholderProps: { | ||
| editor: Editor; | ||
| node: Node; | ||
| pos: number; | ||
| hasAnchor: boolean; | ||
| }) => string) | string; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the editor is editable.** | ||
| * | ||
| * If true, the placeholder will only be shown when the editor is editable. | ||
| * If false, the placeholder will always be shown. | ||
| * @default true | ||
| */ | ||
| showOnlyWhenEditable: boolean; | ||
| /** | ||
| * **Checks if the placeholder should be only shown when the current node is empty.** | ||
| * | ||
| * If true, the placeholder will only be shown when the current node is empty. | ||
| * If false, the placeholder will be shown when any node is empty. | ||
| * @default true | ||
| */ | ||
| showOnlyCurrent: boolean; | ||
| /** | ||
| * **Controls if the placeholder should be shown for all descendants.** | ||
| * | ||
| * If true, the placeholder will be shown for all descendants. | ||
| * If false, the placeholder will only be shown for the current node. | ||
| * @default false | ||
| */ | ||
| includeChildren: boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/placeholder/placeholder.d.ts | ||
| /** | ||
@@ -80,3 +82,4 @@ * This extension allows you to add a placeholder to your editor. | ||
| declare const Placeholder: Extension<PlaceholderOptions, any>; | ||
| //#endregion | ||
| //#region src/placeholder/utils/preparePlaceholderAttribute.d.ts | ||
| /** | ||
@@ -88,3 +91,4 @@ * Prepares the placeholder attribute by ensuring it is properly formatted. | ||
| declare function preparePlaceholderAttribute(attr: string): string; | ||
| export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY, Placeholder, type PlaceholderOptions, preparePlaceholderAttribute }; | ||
| //#endregion | ||
| export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY, Placeholder, PlaceholderOptions, preparePlaceholderAttribute }; | ||
| //# sourceMappingURL=index.d.ts.map |
+340
-346
@@ -1,374 +0,368 @@ | ||
| // src/placeholder/constants.ts | ||
| import { PluginKey } from "@tiptap/pm/state"; | ||
| var DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| var PLUGIN_KEY = new PluginKey("tiptap__placeholder"); | ||
| // src/placeholder/placeholder.ts | ||
| import { Extension } from "@tiptap/core"; | ||
| // src/placeholder/plugins/PlaceholderPlugin.ts | ||
| import { Plugin } from "@tiptap/pm/state"; | ||
| import { DecorationSet as DecorationSet3 } from "@tiptap/pm/view"; | ||
| // src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| import { isNodeEmpty } from "@tiptap/core"; | ||
| import { DecorationSet } from "@tiptap/pm/view"; | ||
| // src/placeholder/utils/createPlaceholderDecoration.ts | ||
| import { Decoration } from "@tiptap/pm/view"; | ||
| import { Plugin, PluginKey } from "@tiptap/pm/state"; | ||
| import { Extension, getChangedRanges, isNodeEmpty } from "@tiptap/core"; | ||
| import { Decoration, DecorationSet } from "@tiptap/pm/view"; | ||
| //#region src/placeholder/constants.ts | ||
| /** The default data attribute label */ | ||
| const DEFAULT_DATA_ATTRIBUTE = "placeholder"; | ||
| /** The plugin key used to store and read the placeholder decoration set */ | ||
| const PLUGIN_KEY = new PluginKey("tiptap__placeholder"); | ||
| //#endregion | ||
| //#region src/placeholder/utils/createPlaceholderDecoration.ts | ||
| /** | ||
| * Creates a ProseMirror node decoration that applies a placeholder | ||
| * CSS class and data attribute to an empty node. | ||
| * @param options.editor - The editor instance | ||
| * @param options.pos - The position of the node in the document | ||
| * @param options.node - The ProseMirror node | ||
| * @param options.isEmptyDoc - Whether the entire document is empty | ||
| * @param options.hasAnchor - Whether the selection anchor is within the node | ||
| * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`) | ||
| * @param options.classes - CSS classes for empty nodes and the empty editor | ||
| * @param options.placeholder - The placeholder text or a function that returns it | ||
| * @returns A ProseMirror node decoration with placeholder classes and data attribute | ||
| */ | ||
| function createPlaceholderDecoration(options) { | ||
| const { | ||
| editor, | ||
| placeholder, | ||
| dataAttribute, | ||
| pos, | ||
| node, | ||
| isEmptyDoc, | ||
| hasAnchor, | ||
| classes: { emptyNode, emptyEditor } | ||
| } = options; | ||
| const classes = [emptyNode]; | ||
| if (isEmptyDoc) { | ||
| classes.push(emptyEditor); | ||
| } | ||
| return Decoration.node(pos, pos + node.nodeSize, { | ||
| class: classes.join(" "), | ||
| [dataAttribute]: typeof placeholder === "function" ? placeholder({ | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) : placeholder | ||
| }); | ||
| const { editor, placeholder, dataAttribute, pos, node, isEmptyDoc, hasAnchor, classes: { emptyNode, emptyEditor } } = options; | ||
| const classes = [emptyNode]; | ||
| if (isEmptyDoc) classes.push(emptyEditor); | ||
| return Decoration.node(pos, pos + node.nodeSize, { | ||
| class: classes.join(" "), | ||
| [dataAttribute]: typeof placeholder === "function" ? placeholder({ | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) : placeholder | ||
| }); | ||
| } | ||
| // src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/buildPlaceholderDecorations.ts | ||
| function resolveEmptyNodeClass(emptyNodeClass, props) { | ||
| return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass; | ||
| return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass; | ||
| } | ||
| function scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }) { | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| doc.nodesBetween(from, to, (node, pos) => { | ||
| const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; | ||
| const isEmpty = !node.isLeaf && isNodeEmpty(node); | ||
| if (!node.type.isTextblock) { | ||
| return options.includeChildren; | ||
| } | ||
| if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) { | ||
| decorations.push( | ||
| createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos | ||
| }) | ||
| ); | ||
| } | ||
| return options.includeChildren; | ||
| }); | ||
| return decorations; | ||
| /** | ||
| * Scans a document range for empty textblocks that should receive placeholder | ||
| * decorations. Used by the slow path and incremental state updates. | ||
| */ | ||
| function scanRangeForDecorations({ editor, options, dataAttribute, doc, selection, from, to }) { | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| doc.nodesBetween(from, to, (node, pos) => { | ||
| const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; | ||
| const isEmpty = !node.isLeaf && isNodeEmpty(node); | ||
| if (!node.type.isTextblock) return options.includeChildren; | ||
| if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) decorations.push(createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos | ||
| })); | ||
| return options.includeChildren; | ||
| }); | ||
| return decorations; | ||
| } | ||
| function buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) { | ||
| const active = editor.isEditable || !options.showOnlyWhenEditable; | ||
| if (!active) { | ||
| return null; | ||
| } | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| if (useResolvedPath) { | ||
| const resolved = doc.resolve(anchor); | ||
| const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter; | ||
| const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor; | ||
| if (node && node.type.isTextblock && isNodeEmpty(node)) { | ||
| const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize; | ||
| decorations.push( | ||
| createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos: nodeStart, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos: nodeStart | ||
| }) | ||
| ); | ||
| } | ||
| } else { | ||
| decorations.push( | ||
| ...scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from: 0, | ||
| to: doc.content.size | ||
| }) | ||
| ); | ||
| } | ||
| return DecorationSet.create(doc, decorations); | ||
| /** | ||
| * Builds the placeholder decorations for the current document state. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @param options.dataAttribute - The prepared `data-*` attribute name. | ||
| * @param options.doc - The current document node. | ||
| * @param options.selection - The current selection. | ||
| * @returns A decoration set, or `null` when no placeholders should be shown. | ||
| */ | ||
| function buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) { | ||
| if (!(editor.isEditable || !options.showOnlyWhenEditable)) return null; | ||
| const { anchor } = selection; | ||
| const decorations = []; | ||
| const isEmptyDoc = editor.isEmpty; | ||
| if (options.showOnlyCurrent && !options.includeChildren) { | ||
| const resolved = doc.resolve(anchor); | ||
| const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter; | ||
| const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor; | ||
| if (node && node.type.isTextblock && isNodeEmpty(node)) { | ||
| const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize; | ||
| decorations.push(createPlaceholderDecoration({ | ||
| editor, | ||
| isEmptyDoc, | ||
| dataAttribute, | ||
| hasAnchor, | ||
| placeholder: options.placeholder, | ||
| classes: { | ||
| emptyEditor: options.emptyEditorClass, | ||
| emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, { | ||
| editor, | ||
| node, | ||
| pos: nodeStart, | ||
| hasAnchor | ||
| }) | ||
| }, | ||
| node, | ||
| pos: nodeStart | ||
| })); | ||
| } | ||
| } else decorations.push(...scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from: 0, | ||
| to: doc.content.size | ||
| })); | ||
| return DecorationSet.create(doc, decorations); | ||
| } | ||
| // src/placeholder/utils/placeholderStateField.ts | ||
| import { getChangedRanges } from "@tiptap/core"; | ||
| import { DecorationSet as DecorationSet2 } from "@tiptap/pm/view"; | ||
| // src/placeholder/utils/resolveTopLevelRange.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/resolveTopLevelRange.ts | ||
| /** | ||
| * Resolves a document position to the `[from, to)` range of its containing | ||
| * top-level block node in absolute document positions. | ||
| */ | ||
| function resolveTopLevelRange(doc, pos) { | ||
| var _a; | ||
| const resolved = doc.resolve(pos); | ||
| if (resolved.depth === 0) { | ||
| const node2 = (_a = resolved.nodeAfter) != null ? _a : resolved.nodeBefore; | ||
| if (!node2) { | ||
| return { from: pos, to: pos }; | ||
| } | ||
| const nodePos = resolved.nodeAfter ? pos : pos - node2.nodeSize; | ||
| return { from: nodePos, to: nodePos + node2.nodeSize }; | ||
| } | ||
| const topLevelPos = resolved.before(1); | ||
| const node = resolved.node(1); | ||
| return { from: topLevelPos, to: topLevelPos + node.nodeSize }; | ||
| const resolved = doc.resolve(pos); | ||
| if (resolved.depth === 0) { | ||
| var _resolved$nodeAfter; | ||
| const node = (_resolved$nodeAfter = resolved.nodeAfter) !== null && _resolved$nodeAfter !== void 0 ? _resolved$nodeAfter : resolved.nodeBefore; | ||
| if (!node) return { | ||
| from: pos, | ||
| to: pos | ||
| }; | ||
| const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize; | ||
| return { | ||
| from: nodePos, | ||
| to: nodePos + node.nodeSize | ||
| }; | ||
| } | ||
| const topLevelPos = resolved.before(1); | ||
| return { | ||
| from: topLevelPos, | ||
| to: topLevelPos + resolved.node(1).nodeSize | ||
| }; | ||
| } | ||
| /** | ||
| * Converts an absolute document range to content-relative positions used by | ||
| * `Node#nodesBetween` and `Node#forEach` offsets. | ||
| */ | ||
| function toContentRelativeRange(doc, range) { | ||
| return { | ||
| from: Math.max(0, range.from - 1), | ||
| to: Math.min(doc.content.size, range.to - 1) | ||
| }; | ||
| return { | ||
| from: Math.max(0, range.from - 1), | ||
| to: Math.min(doc.content.size, range.to - 1) | ||
| }; | ||
| } | ||
| /** | ||
| * Returns the top-level block ranges that intersect a document change range. | ||
| * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`). | ||
| * Returned ranges are content-relative, matching `Node#forEach` offsets. | ||
| */ | ||
| function getTopLevelBlocksInRange(doc, from, to) { | ||
| const ranges = []; | ||
| doc.forEach((node, offset) => { | ||
| const nodeStart = offset; | ||
| const nodeEnd = nodeStart + node.nodeSize; | ||
| const absNodeStart = nodeStart + 1; | ||
| const absNodeEnd = nodeEnd + 1; | ||
| if (absNodeStart < to && absNodeEnd > from) { | ||
| ranges.push({ from: nodeStart, to: nodeEnd }); | ||
| } | ||
| }); | ||
| return ranges; | ||
| const ranges = []; | ||
| doc.forEach((node, offset) => { | ||
| const nodeStart = offset; | ||
| const nodeEnd = nodeStart + node.nodeSize; | ||
| const absNodeStart = nodeStart + 1; | ||
| const absNodeEnd = nodeEnd + 1; | ||
| if (absNodeStart < to && absNodeEnd > from) ranges.push({ | ||
| from: nodeStart, | ||
| to: nodeEnd | ||
| }); | ||
| }); | ||
| return ranges; | ||
| } | ||
| /** | ||
| * Sorts ranges by start position and merges overlapping or adjacent ranges. | ||
| */ | ||
| function mergeRanges(ranges) { | ||
| if (ranges.length === 0) { | ||
| return []; | ||
| } | ||
| const sorted = [...ranges].sort((a, b) => a.from - b.from); | ||
| const merged = [{ ...sorted[0] }]; | ||
| for (let i = 1; i < sorted.length; i += 1) { | ||
| const last = merged[merged.length - 1]; | ||
| const current = sorted[i]; | ||
| if (current.from <= last.to) { | ||
| last.to = Math.max(last.to, current.to); | ||
| } else { | ||
| merged.push({ ...current }); | ||
| } | ||
| } | ||
| return merged; | ||
| if (ranges.length === 0) return []; | ||
| const sorted = [...ranges].sort((a, b) => a.from - b.from); | ||
| const merged = [{ ...sorted[0] }]; | ||
| for (let i = 1; i < sorted.length; i += 1) { | ||
| const last = merged[merged.length - 1]; | ||
| const current = sorted[i]; | ||
| if (current.from <= last.to) last.to = Math.max(last.to, current.to); | ||
| else merged.push({ ...current }); | ||
| } | ||
| return merged; | ||
| } | ||
| // src/placeholder/utils/placeholderStateField.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/placeholderStateField.ts | ||
| /** | ||
| * Expands a single changed range to the top-level blocks it touches. | ||
| * Also resolves blocks at range boundaries so split/merge edits update | ||
| * adjacent empty nodes (e.g. a new paragraph after Enter). | ||
| */ | ||
| function collectBlocksForChange(doc, change) { | ||
| const ranges = getTopLevelBlocksInRange(doc, change.from, change.to); | ||
| ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from))); | ||
| if (change.to > change.from) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| doc, | ||
| resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1) | ||
| ) | ||
| ); | ||
| } else if (change.from < doc.content.size + 1) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| doc, | ||
| resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)) | ||
| ) | ||
| ); | ||
| } | ||
| return ranges; | ||
| const ranges = getTopLevelBlocksInRange(doc, change.from, change.to); | ||
| ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from))); | ||
| if (change.to > change.from) ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1))); | ||
| else if (change.from < doc.content.size + 1) ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)))); | ||
| return ranges; | ||
| } | ||
| /** | ||
| * Collects content-relative top-level block ranges that need placeholder | ||
| * decorations recomputed after a transaction. | ||
| */ | ||
| function collectRescanRanges(tr, oldState, newState) { | ||
| const ranges = []; | ||
| if (tr.docChanged) { | ||
| const changes = getChangedRanges(tr); | ||
| for (const change of changes) { | ||
| ranges.push(...collectBlocksForChange(newState.doc, change.newRange)); | ||
| } | ||
| } | ||
| if (tr.selectionSet) { | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| newState.doc, | ||
| resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)) | ||
| ) | ||
| ); | ||
| ranges.push( | ||
| toContentRelativeRange( | ||
| newState.doc, | ||
| resolveTopLevelRange(newState.doc, newState.selection.anchor) | ||
| ) | ||
| ); | ||
| } | ||
| return mergeRanges(ranges); | ||
| const ranges = []; | ||
| if (tr.docChanged) { | ||
| const changes = getChangedRanges(tr); | ||
| for (const change of changes) ranges.push(...collectBlocksForChange(newState.doc, change.newRange)); | ||
| } | ||
| if (tr.selectionSet) { | ||
| ranges.push(toContentRelativeRange(newState.doc, resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)))); | ||
| ranges.push(toContentRelativeRange(newState.doc, resolveTopLevelRange(newState.doc, newState.selection.anchor))); | ||
| } | ||
| return mergeRanges(ranges); | ||
| } | ||
| /** Clamps a content-relative range to `[0, doc.content.size]`. */ | ||
| function clampRange(from, to, doc) { | ||
| const clampedFrom = Math.max(0, Math.min(from, doc.content.size)); | ||
| const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size)); | ||
| return { from: clampedFrom, to: clampedTo }; | ||
| const clampedFrom = Math.max(0, Math.min(from, doc.content.size)); | ||
| return { | ||
| from: clampedFrom, | ||
| to: Math.max(clampedFrom, Math.min(to, doc.content.size)) | ||
| }; | ||
| } | ||
| function updateDecorationsInRanges({ | ||
| decorations, | ||
| ranges, | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) { | ||
| let next = decorations; | ||
| for (const range of ranges) { | ||
| const { from, to } = clampRange(range.from, range.to, doc); | ||
| const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to); | ||
| if (existing.length) { | ||
| next = next.remove(existing); | ||
| } | ||
| const newDecos = scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }); | ||
| if (newDecos.length) { | ||
| next = next.add(doc, newDecos); | ||
| } | ||
| } | ||
| return next; | ||
| /** | ||
| * Removes and rebuilds placeholder decorations within the given ranges. | ||
| * Only drops decorations fully contained in a range so mapped decorations | ||
| * on neighbouring blocks (e.g. at a block boundary) are kept intact. | ||
| */ | ||
| function updateDecorationsInRanges({ decorations, ranges, editor, options, dataAttribute, doc, selection }) { | ||
| let next = decorations; | ||
| for (const range of ranges) { | ||
| const { from, to } = clampRange(range.from, range.to, doc); | ||
| const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to); | ||
| if (existing.length) next = next.remove(existing); | ||
| const newDecos = scanRangeForDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection, | ||
| from, | ||
| to | ||
| }); | ||
| if (newDecos.length) next = next.add(doc, newDecos); | ||
| } | ||
| return next; | ||
| } | ||
| function createPlaceholderStateField({ | ||
| editor, | ||
| options, | ||
| dataAttribute | ||
| }) { | ||
| return { | ||
| init(_config, state) { | ||
| const decorations = buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: state.doc, | ||
| selection: state.selection | ||
| }); | ||
| return decorations != null ? decorations : DecorationSet2.empty; | ||
| }, | ||
| apply(tr, prev, oldState, newState) { | ||
| if (!tr.docChanged && !tr.selectionSet) { | ||
| return prev; | ||
| } | ||
| const mapped = prev.map(tr.mapping, tr.doc); | ||
| const ranges = collectRescanRanges(tr, oldState, newState); | ||
| return updateDecorationsInRanges({ | ||
| decorations: mapped, | ||
| ranges, | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: newState.doc, | ||
| selection: newState.selection | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Creates the incremental `StateField<DecorationSet>` used by the slow path | ||
| * (`showOnlyCurrent: false` or `includeChildren: true`). | ||
| * | ||
| * Decorations are mapped through each transaction and only recomputed for | ||
| * top-level blocks touched by document or selection changes. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @param options.dataAttribute - The prepared `data-*` attribute name. | ||
| * @returns A ProseMirror state field storing the placeholder decoration set. | ||
| */ | ||
| function createPlaceholderStateField({ editor, options, dataAttribute }) { | ||
| return { | ||
| init(_config, state) { | ||
| const decorations = buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: state.doc, | ||
| selection: state.selection | ||
| }); | ||
| return decorations !== null && decorations !== void 0 ? decorations : DecorationSet.empty; | ||
| }, | ||
| apply(tr, prev, oldState, newState) { | ||
| if (!tr.docChanged && !tr.selectionSet) return prev; | ||
| return updateDecorationsInRanges({ | ||
| decorations: prev.map(tr.mapping, tr.doc), | ||
| ranges: collectRescanRanges(tr, oldState, newState), | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc: newState.doc, | ||
| selection: newState.selection | ||
| }); | ||
| } | ||
| }; | ||
| } | ||
| // src/placeholder/utils/preparePlaceholderAttribute.ts | ||
| //#endregion | ||
| //#region src/placeholder/utils/preparePlaceholderAttribute.ts | ||
| /** | ||
| * Prepares the placeholder attribute by ensuring it is properly formatted. | ||
| * @param attr - The placeholder attribute string. | ||
| * @returns The prepared placeholder attribute string. | ||
| */ | ||
| function preparePlaceholderAttribute(attr) { | ||
| return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase(); | ||
| return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase(); | ||
| } | ||
| // src/placeholder/plugins/PlaceholderPlugin.ts | ||
| //#endregion | ||
| //#region src/placeholder/plugins/PlaceholderPlugin.ts | ||
| /** | ||
| * Creates the ProseMirror plugin that renders placeholder decorations. | ||
| * @param options.editor - The editor instance. | ||
| * @param options.options - The resolved placeholder options. | ||
| * @returns The configured placeholder plugin. | ||
| */ | ||
| function createPlaceholderPlugin({ editor, options }) { | ||
| const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| return new Plugin({ | ||
| key: PLUGIN_KEY, | ||
| ...useResolvedPath ? {} : { | ||
| state: createPlaceholderStateField({ editor, options, dataAttribute }) | ||
| }, | ||
| props: { | ||
| decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) : (state) => { | ||
| var _a; | ||
| if (options.showOnlyWhenEditable && !editor.isEditable) { | ||
| return DecorationSet3.empty; | ||
| } | ||
| return (_a = PLUGIN_KEY.getState(state)) != null ? _a : DecorationSet3.empty; | ||
| } | ||
| } | ||
| }); | ||
| const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`; | ||
| const useResolvedPath = options.showOnlyCurrent && !options.includeChildren; | ||
| return new Plugin({ | ||
| key: PLUGIN_KEY, | ||
| ...useResolvedPath ? {} : { state: createPlaceholderStateField({ | ||
| editor, | ||
| options, | ||
| dataAttribute | ||
| }) }, | ||
| props: { decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ | ||
| editor, | ||
| options, | ||
| dataAttribute, | ||
| doc, | ||
| selection | ||
| }) : (state) => { | ||
| var _PLUGIN_KEY$getState; | ||
| if (options.showOnlyWhenEditable && !editor.isEditable) return DecorationSet.empty; | ||
| return (_PLUGIN_KEY$getState = PLUGIN_KEY.getState(state)) !== null && _PLUGIN_KEY$getState !== void 0 ? _PLUGIN_KEY$getState : DecorationSet.empty; | ||
| } } | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/placeholder/placeholder.ts | ||
| /** | ||
| * This extension allows you to add a placeholder to your editor. | ||
| * A placeholder is a text that appears when the editor or a node is empty. | ||
| * @see https://www.tiptap.dev/api/extensions/placeholder | ||
| */ | ||
| const Placeholder = Extension.create({ | ||
| name: "placeholder", | ||
| addOptions() { | ||
| return { | ||
| emptyEditorClass: "is-editor-empty", | ||
| emptyNodeClass: "is-empty", | ||
| dataAttribute: DEFAULT_DATA_ATTRIBUTE, | ||
| placeholder: "Write something …", | ||
| showOnlyWhenEditable: true, | ||
| showOnlyCurrent: true, | ||
| includeChildren: false | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [createPlaceholderPlugin({ | ||
| editor: this.editor, | ||
| options: this.options | ||
| })]; | ||
| } | ||
| }); | ||
| //#endregion | ||
| export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY, Placeholder, preparePlaceholderAttribute }; | ||
| // src/placeholder/placeholder.ts | ||
| var Placeholder = Extension.create({ | ||
| name: "placeholder", | ||
| addOptions() { | ||
| return { | ||
| emptyEditorClass: "is-editor-empty", | ||
| emptyNodeClass: "is-empty", | ||
| dataAttribute: DEFAULT_DATA_ATTRIBUTE, | ||
| placeholder: "Write something \u2026", | ||
| showOnlyWhenEditable: true, | ||
| showOnlyCurrent: true, | ||
| includeChildren: false | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]; | ||
| } | ||
| }); | ||
| export { | ||
| DEFAULT_DATA_ATTRIBUTE, | ||
| PLUGIN_KEY, | ||
| Placeholder, | ||
| preparePlaceholderAttribute | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/placeholder/constants.ts","../../src/placeholder/placeholder.ts","../../src/placeholder/plugins/PlaceholderPlugin.ts","../../src/placeholder/utils/buildPlaceholderDecorations.ts","../../src/placeholder/utils/createPlaceholderDecoration.ts","../../src/placeholder/utils/placeholderStateField.ts","../../src/placeholder/utils/resolveTopLevelRange.ts","../../src/placeholder/utils/preparePlaceholderAttribute.ts"],"sourcesContent":["import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n"],"mappings":"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","names":["DecorationSet","DecorationSet","node","DecorationSet","DecorationSet"]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/placeholder/constants.ts","../../src/placeholder/utils/createPlaceholderDecoration.ts","../../src/placeholder/utils/buildPlaceholderDecorations.ts","../../src/placeholder/utils/resolveTopLevelRange.ts","../../src/placeholder/utils/placeholderStateField.ts","../../src/placeholder/utils/preparePlaceholderAttribute.ts","../../src/placeholder/plugins/PlaceholderPlugin.ts","../../src/placeholder/placeholder.ts"],"sourcesContent":["import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n"],"mappings":"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"} |
+62
-86
@@ -1,96 +0,72 @@ | ||
| "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/selection/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| Selection: () => Selection | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/selection/selection.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_state = require("@tiptap/pm/state"); | ||
| var import_view = require("@tiptap/pm/view"); | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_state = require("@tiptap/pm/state"); | ||
| let _tiptap_pm_view = require("@tiptap/pm/view"); | ||
| //#region src/selection/selection.ts | ||
| /** | ||
| * Whether the native browser selection should be cleared on blur and restored on focus. | ||
| * Only applies to non-empty text selections in an editable editor. | ||
| */ | ||
| function shouldSyncDomSelection(state, editor) { | ||
| return !state.selection.empty && !(0, import_core.isNodeSelection)(state.selection) && editor.isEditable; | ||
| return !state.selection.empty && !(0, _tiptap_core.isNodeSelection)(state.selection) && editor.isEditable; | ||
| } | ||
| /** | ||
| * Whether the selection decoration should be rendered to keep the selection | ||
| * visible while the editor is blurred (and not dragging). | ||
| */ | ||
| function shouldPreserveSelection(state, editor) { | ||
| return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging; | ||
| return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging; | ||
| } | ||
| function clearDomSelection() { | ||
| var _a; | ||
| (_a = window.getSelection()) == null ? void 0 : _a.removeAllRanges(); | ||
| var _window$getSelection; | ||
| (_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 || _window$getSelection.removeAllRanges(); | ||
| } | ||
| /** | ||
| * Sync the native selection from the editor state. | ||
| * @see https://prosemirror.net/docs/ref/#view.EditorView.focus | ||
| */ | ||
| function restoreDomSelection(view) { | ||
| view.focus(); | ||
| view.focus(); | ||
| } | ||
| var Selection = import_core.Extension.create({ | ||
| name: "selection", | ||
| addOptions() { | ||
| return { | ||
| className: "selection" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| const { editor, options } = this; | ||
| return [ | ||
| new import_state.Plugin({ | ||
| key: new import_state.PluginKey("selection"), | ||
| props: { | ||
| decorations(state) { | ||
| if (!shouldPreserveSelection(state, editor)) { | ||
| return null; | ||
| } | ||
| return import_view.DecorationSet.create(state.doc, [ | ||
| import_view.Decoration.inline(state.selection.from, state.selection.to, { | ||
| class: options.className | ||
| }) | ||
| ]); | ||
| }, | ||
| handleDOMEvents: { | ||
| blur(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) { | ||
| return false; | ||
| } | ||
| clearDomSelection(); | ||
| return false; | ||
| }, | ||
| focus(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) { | ||
| return false; | ||
| } | ||
| requestAnimationFrame(() => { | ||
| if (!editor.isDestroyed && view.hasFocus()) { | ||
| restoreDomSelection(view); | ||
| } | ||
| }); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| /** | ||
| * This extension allows you to add a class to the selected text when the editor is blurred. | ||
| * It clears the native browser selection on blur (so `::selection` styles do not overlap the | ||
| * decoration) and restores it when the editor is focused again. | ||
| * @see https://www.tiptap.dev/api/extensions/selection | ||
| */ | ||
| const Selection = _tiptap_core.Extension.create({ | ||
| name: "selection", | ||
| addOptions() { | ||
| return { className: "selection" }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| const { editor, options } = this; | ||
| return [new _tiptap_pm_state.Plugin({ | ||
| key: new _tiptap_pm_state.PluginKey("selection"), | ||
| props: { | ||
| decorations(state) { | ||
| if (!shouldPreserveSelection(state, editor)) return null; | ||
| return _tiptap_pm_view.DecorationSet.create(state.doc, [_tiptap_pm_view.Decoration.inline(state.selection.from, state.selection.to, { class: options.className })]); | ||
| }, | ||
| handleDOMEvents: { | ||
| blur(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) return false; | ||
| clearDomSelection(); | ||
| return false; | ||
| }, | ||
| focus(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) return false; | ||
| requestAnimationFrame(() => { | ||
| if (!editor.isDestroyed && view.hasFocus()) restoreDomSelection(view); | ||
| }); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| Selection | ||
| }); | ||
| //#endregion | ||
| exports.Selection = Selection; | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/selection/index.ts","../../src/selection/selection.ts"],"sourcesContent":["export * from './selection.js'\n","import { Extension, isNodeSelection, type Editor } from '@tiptap/core'\nimport { Plugin, PluginKey, type EditorState } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport type SelectionOptions = {\n /**\n * The class name that should be added to the selected text.\n * @default 'selection'\n * @example 'is-selected'\n */\n className: string\n}\n\n/**\n * Whether the native browser selection should be cleared on blur and restored on focus.\n * Only applies to non-empty text selections in an editable editor.\n */\nfunction shouldSyncDomSelection(state: EditorState, editor: Editor): boolean {\n return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable\n}\n\n/**\n * Whether the selection decoration should be rendered to keep the selection\n * visible while the editor is blurred (and not dragging).\n */\nfunction shouldPreserveSelection(state: EditorState, editor: Editor): boolean {\n return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging\n}\n\nfunction clearDomSelection() {\n window.getSelection()?.removeAllRanges()\n}\n\n/**\n * Sync the native selection from the editor state.\n * @see https://prosemirror.net/docs/ref/#view.EditorView.focus\n */\nfunction restoreDomSelection(view: EditorView) {\n view.focus()\n}\n\n/**\n * This extension allows you to add a class to the selected text when the editor is blurred.\n * It clears the native browser selection on blur (so `::selection` styles do not overlap the\n * decoration) and restores it when the editor is focused again.\n * @see https://www.tiptap.dev/api/extensions/selection\n */\nexport const Selection = Extension.create<SelectionOptions>({\n name: 'selection',\n\n addOptions() {\n return {\n className: 'selection',\n }\n },\n\n addProseMirrorPlugins() {\n const { editor, options } = this\n\n return [\n new Plugin({\n key: new PluginKey('selection'),\n props: {\n decorations(state) {\n if (!shouldPreserveSelection(state, editor)) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(state.selection.from, state.selection.to, {\n class: options.className,\n }),\n ])\n },\n handleDOMEvents: {\n blur(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n clearDomSelection()\n\n return false\n },\n focus(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n requestAnimationFrame(() => {\n if (!editor.isDestroyed && view.hasFocus()) {\n restoreDomSelection(view)\n }\n })\n\n return false\n },\n },\n },\n }),\n ]\n },\n})\n\nexport default Selection\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAwD;AACxD,mBAAoD;AAEpD,kBAA0C;AAe1C,SAAS,uBAAuB,OAAoB,QAAyB;AAC3E,SAAO,CAAC,MAAM,UAAU,SAAS,KAAC,6BAAgB,MAAM,SAAS,KAAK,OAAO;AAC/E;AAMA,SAAS,wBAAwB,OAAoB,QAAyB;AAC5E,SAAO,uBAAuB,OAAO,MAAM,KAAK,CAAC,OAAO,aAAa,CAAC,OAAO,KAAK;AACpF;AAEA,SAAS,oBAAoB;AA9B7B;AA+BE,eAAO,aAAa,MAApB,mBAAuB;AACzB;AAMA,SAAS,oBAAoB,MAAkB;AAC7C,OAAK,MAAM;AACb;AAQO,IAAM,YAAY,sBAAU,OAAyB;AAAA,EAC1D,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,UAAM,EAAE,QAAQ,QAAQ,IAAI;AAE5B,WAAO;AAAA,MACL,IAAI,oBAAO;AAAA,QACT,KAAK,IAAI,uBAAU,WAAW;AAAA,QAC9B,OAAO;AAAA,UACL,YAAY,OAAO;AACjB,gBAAI,CAAC,wBAAwB,OAAO,MAAM,GAAG;AAC3C,qBAAO;AAAA,YACT;AAEA,mBAAO,0BAAc,OAAO,MAAM,KAAK;AAAA,cACrC,uBAAW,OAAO,MAAM,UAAU,MAAM,MAAM,UAAU,IAAI;AAAA,gBAC1D,OAAO,QAAQ;AAAA,cACjB,CAAC;AAAA,YACH,CAAC;AAAA,UACH;AAAA,UACA,iBAAiB;AAAA,YACf,KAAK,MAAM;AACT,kBAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAAG;AAC/C,uBAAO;AAAA,cACT;AAEA,gCAAkB;AAElB,qBAAO;AAAA,YACT;AAAA,YACA,MAAM,MAAM;AACV,kBAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAAG;AAC/C,uBAAO;AAAA,cACT;AAEA,oCAAsB,MAAM;AAC1B,oBAAI,CAAC,OAAO,eAAe,KAAK,SAAS,GAAG;AAC1C,sCAAoB,IAAI;AAAA,gBAC1B;AAAA,cACF,CAAC;AAED,qBAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.cjs","names":["Extension","Plugin","PluginKey","DecorationSet","Decoration"],"sources":["../../src/selection/selection.ts"],"sourcesContent":["import { Extension, isNodeSelection, type Editor } from '@tiptap/core'\nimport { Plugin, PluginKey, type EditorState } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport type SelectionOptions = {\n /**\n * The class name that should be added to the selected text.\n * @default 'selection'\n * @example 'is-selected'\n */\n className: string\n}\n\n/**\n * Whether the native browser selection should be cleared on blur and restored on focus.\n * Only applies to non-empty text selections in an editable editor.\n */\nfunction shouldSyncDomSelection(state: EditorState, editor: Editor): boolean {\n return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable\n}\n\n/**\n * Whether the selection decoration should be rendered to keep the selection\n * visible while the editor is blurred (and not dragging).\n */\nfunction shouldPreserveSelection(state: EditorState, editor: Editor): boolean {\n return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging\n}\n\nfunction clearDomSelection() {\n window.getSelection()?.removeAllRanges()\n}\n\n/**\n * Sync the native selection from the editor state.\n * @see https://prosemirror.net/docs/ref/#view.EditorView.focus\n */\nfunction restoreDomSelection(view: EditorView) {\n view.focus()\n}\n\n/**\n * This extension allows you to add a class to the selected text when the editor is blurred.\n * It clears the native browser selection on blur (so `::selection` styles do not overlap the\n * decoration) and restores it when the editor is focused again.\n * @see https://www.tiptap.dev/api/extensions/selection\n */\nexport const Selection = Extension.create<SelectionOptions>({\n name: 'selection',\n\n addOptions() {\n return {\n className: 'selection',\n }\n },\n\n addProseMirrorPlugins() {\n const { editor, options } = this\n\n return [\n new Plugin({\n key: new PluginKey('selection'),\n props: {\n decorations(state) {\n if (!shouldPreserveSelection(state, editor)) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(state.selection.from, state.selection.to, {\n class: options.className,\n }),\n ])\n },\n handleDOMEvents: {\n blur(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n clearDomSelection()\n\n return false\n },\n focus(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n requestAnimationFrame(() => {\n if (!editor.isDestroyed && view.hasFocus()) {\n restoreDomSelection(view)\n }\n })\n\n return false\n },\n },\n },\n }),\n ]\n },\n})\n\nexport default Selection\n"],"mappings":";;;;;;;;;AAkBA,SAAS,uBAAuB,OAAoB,QAAyB;CAC3E,OAAO,CAAC,MAAM,UAAU,SAAS,EAAA,GAAA,aAAA,gBAAA,CAAiB,MAAM,SAAS,KAAK,OAAO;AAC/E;;;;;AAMA,SAAS,wBAAwB,OAAoB,QAAyB;CAC5E,OAAO,uBAAuB,OAAO,MAAM,KAAK,CAAC,OAAO,aAAa,CAAC,OAAO,KAAK;AACpF;AAEA,SAAS,oBAAoB;;CAC3B,CAAA,uBAAA,OAAO,aAAa,OAAA,QAAA,yBAAA,KAAA,KAAA,qBAAG,gBAAgB;AACzC;;;;;AAMA,SAAS,oBAAoB,MAAkB;CAC7C,KAAK,MAAM;AACb;;;;;;;AAQA,MAAa,YAAYA,aAAAA,UAAU,OAAyB;CAC1D,MAAM;CAEN,aAAa;EACX,OAAO,EACL,WAAW,YACb;CACF;CAEA,wBAAwB;EACtB,MAAM,EAAE,QAAQ,YAAY;EAE5B,OAAO,CACL,IAAIC,iBAAAA,OAAO;GACT,KAAK,IAAIC,iBAAAA,UAAU,WAAW;GAC9B,OAAO;IACL,YAAY,OAAO;KACjB,IAAI,CAAC,wBAAwB,OAAO,MAAM,GACxC,OAAO;KAGT,OAAOC,gBAAAA,cAAc,OAAO,MAAM,KAAK,CACrCC,gBAAAA,WAAW,OAAO,MAAM,UAAU,MAAM,MAAM,UAAU,IAAI,EAC1D,OAAO,QAAQ,UACjB,CAAC,CACH,CAAC;IACH;IACA,iBAAiB;KACf,KAAK,MAAM;MACT,IAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAC5C,OAAO;MAGT,kBAAkB;MAElB,OAAO;KACT;KACA,MAAM,MAAM;MACV,IAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAC5C,OAAO;MAGT,4BAA4B;OAC1B,IAAI,CAAC,OAAO,eAAe,KAAK,SAAS,GACvC,oBAAoB,IAAI;MAE5B,CAAC;MAED,OAAO;KACT;IACF;GACF;EACF,CAAC,CACH;CACF;AACF,CAAC"} |
@@ -1,10 +0,10 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/selection/selection.d.ts | ||
| type SelectionOptions = { | ||
| /** | ||
| * The class name that should be added to the selected text. | ||
| * @default 'selection' | ||
| * @example 'is-selected' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The class name that should be added to the selected text. | ||
| * @default 'selection' | ||
| * @example 'is-selected' | ||
| */ | ||
| className: string; | ||
| }; | ||
@@ -18,3 +18,4 @@ /** | ||
| declare const Selection: Extension<SelectionOptions, any>; | ||
| export { Selection, type SelectionOptions }; | ||
| //#endregion | ||
| export { Selection, SelectionOptions }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,10 +0,10 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/selection/selection.d.ts | ||
| type SelectionOptions = { | ||
| /** | ||
| * The class name that should be added to the selected text. | ||
| * @default 'selection' | ||
| * @example 'is-selected' | ||
| */ | ||
| className: string; | ||
| /** | ||
| * The class name that should be added to the selected text. | ||
| * @default 'selection' | ||
| * @example 'is-selected' | ||
| */ | ||
| className: string; | ||
| }; | ||
@@ -18,3 +18,4 @@ /** | ||
| declare const Selection: Extension<SelectionOptions, any>; | ||
| export { Selection, type SelectionOptions }; | ||
| //#endregion | ||
| export { Selection, SelectionOptions }; | ||
| //# sourceMappingURL=index.d.ts.map |
+58
-56
@@ -1,69 +0,71 @@ | ||
| // src/selection/selection.ts | ||
| import { Extension, isNodeSelection } from "@tiptap/core"; | ||
| import { Plugin, PluginKey } from "@tiptap/pm/state"; | ||
| import { Decoration, DecorationSet } from "@tiptap/pm/view"; | ||
| //#region src/selection/selection.ts | ||
| /** | ||
| * Whether the native browser selection should be cleared on blur and restored on focus. | ||
| * Only applies to non-empty text selections in an editable editor. | ||
| */ | ||
| function shouldSyncDomSelection(state, editor) { | ||
| return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable; | ||
| return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable; | ||
| } | ||
| /** | ||
| * Whether the selection decoration should be rendered to keep the selection | ||
| * visible while the editor is blurred (and not dragging). | ||
| */ | ||
| function shouldPreserveSelection(state, editor) { | ||
| return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging; | ||
| return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging; | ||
| } | ||
| function clearDomSelection() { | ||
| var _a; | ||
| (_a = window.getSelection()) == null ? void 0 : _a.removeAllRanges(); | ||
| var _window$getSelection; | ||
| (_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 || _window$getSelection.removeAllRanges(); | ||
| } | ||
| /** | ||
| * Sync the native selection from the editor state. | ||
| * @see https://prosemirror.net/docs/ref/#view.EditorView.focus | ||
| */ | ||
| function restoreDomSelection(view) { | ||
| view.focus(); | ||
| view.focus(); | ||
| } | ||
| var Selection = Extension.create({ | ||
| name: "selection", | ||
| addOptions() { | ||
| return { | ||
| className: "selection" | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| const { editor, options } = this; | ||
| return [ | ||
| new Plugin({ | ||
| key: new PluginKey("selection"), | ||
| props: { | ||
| decorations(state) { | ||
| if (!shouldPreserveSelection(state, editor)) { | ||
| return null; | ||
| } | ||
| return DecorationSet.create(state.doc, [ | ||
| Decoration.inline(state.selection.from, state.selection.to, { | ||
| class: options.className | ||
| }) | ||
| ]); | ||
| }, | ||
| handleDOMEvents: { | ||
| blur(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) { | ||
| return false; | ||
| } | ||
| clearDomSelection(); | ||
| return false; | ||
| }, | ||
| focus(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) { | ||
| return false; | ||
| } | ||
| requestAnimationFrame(() => { | ||
| if (!editor.isDestroyed && view.hasFocus()) { | ||
| restoreDomSelection(view); | ||
| } | ||
| }); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| /** | ||
| * This extension allows you to add a class to the selected text when the editor is blurred. | ||
| * It clears the native browser selection on blur (so `::selection` styles do not overlap the | ||
| * decoration) and restores it when the editor is focused again. | ||
| * @see https://www.tiptap.dev/api/extensions/selection | ||
| */ | ||
| const Selection = Extension.create({ | ||
| name: "selection", | ||
| addOptions() { | ||
| return { className: "selection" }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| const { editor, options } = this; | ||
| return [new Plugin({ | ||
| key: new PluginKey("selection"), | ||
| props: { | ||
| decorations(state) { | ||
| if (!shouldPreserveSelection(state, editor)) return null; | ||
| return DecorationSet.create(state.doc, [Decoration.inline(state.selection.from, state.selection.to, { class: options.className })]); | ||
| }, | ||
| handleDOMEvents: { | ||
| blur(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) return false; | ||
| clearDomSelection(); | ||
| return false; | ||
| }, | ||
| focus(view) { | ||
| if (!shouldSyncDomSelection(view.state, editor)) return false; | ||
| requestAnimationFrame(() => { | ||
| if (!editor.isDestroyed && view.hasFocus()) restoreDomSelection(view); | ||
| }); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| export { | ||
| Selection | ||
| }; | ||
| //#endregion | ||
| export { Selection }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/selection/selection.ts"],"sourcesContent":["import { Extension, isNodeSelection, type Editor } from '@tiptap/core'\nimport { Plugin, PluginKey, type EditorState } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport type SelectionOptions = {\n /**\n * The class name that should be added to the selected text.\n * @default 'selection'\n * @example 'is-selected'\n */\n className: string\n}\n\n/**\n * Whether the native browser selection should be cleared on blur and restored on focus.\n * Only applies to non-empty text selections in an editable editor.\n */\nfunction shouldSyncDomSelection(state: EditorState, editor: Editor): boolean {\n return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable\n}\n\n/**\n * Whether the selection decoration should be rendered to keep the selection\n * visible while the editor is blurred (and not dragging).\n */\nfunction shouldPreserveSelection(state: EditorState, editor: Editor): boolean {\n return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging\n}\n\nfunction clearDomSelection() {\n window.getSelection()?.removeAllRanges()\n}\n\n/**\n * Sync the native selection from the editor state.\n * @see https://prosemirror.net/docs/ref/#view.EditorView.focus\n */\nfunction restoreDomSelection(view: EditorView) {\n view.focus()\n}\n\n/**\n * This extension allows you to add a class to the selected text when the editor is blurred.\n * It clears the native browser selection on blur (so `::selection` styles do not overlap the\n * decoration) and restores it when the editor is focused again.\n * @see https://www.tiptap.dev/api/extensions/selection\n */\nexport const Selection = Extension.create<SelectionOptions>({\n name: 'selection',\n\n addOptions() {\n return {\n className: 'selection',\n }\n },\n\n addProseMirrorPlugins() {\n const { editor, options } = this\n\n return [\n new Plugin({\n key: new PluginKey('selection'),\n props: {\n decorations(state) {\n if (!shouldPreserveSelection(state, editor)) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(state.selection.from, state.selection.to, {\n class: options.className,\n }),\n ])\n },\n handleDOMEvents: {\n blur(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n clearDomSelection()\n\n return false\n },\n focus(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n requestAnimationFrame(() => {\n if (!editor.isDestroyed && view.hasFocus()) {\n restoreDomSelection(view)\n }\n })\n\n return false\n },\n },\n },\n }),\n ]\n },\n})\n\nexport default Selection\n"],"mappings":";AAAA,SAAS,WAAW,uBAAoC;AACxD,SAAS,QAAQ,iBAAmC;AAEpD,SAAS,YAAY,qBAAqB;AAe1C,SAAS,uBAAuB,OAAoB,QAAyB;AAC3E,SAAO,CAAC,MAAM,UAAU,SAAS,CAAC,gBAAgB,MAAM,SAAS,KAAK,OAAO;AAC/E;AAMA,SAAS,wBAAwB,OAAoB,QAAyB;AAC5E,SAAO,uBAAuB,OAAO,MAAM,KAAK,CAAC,OAAO,aAAa,CAAC,OAAO,KAAK;AACpF;AAEA,SAAS,oBAAoB;AA9B7B;AA+BE,eAAO,aAAa,MAApB,mBAAuB;AACzB;AAMA,SAAS,oBAAoB,MAAkB;AAC7C,OAAK,MAAM;AACb;AAQO,IAAM,YAAY,UAAU,OAAyB;AAAA,EAC1D,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,UAAM,EAAE,QAAQ,QAAQ,IAAI;AAE5B,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,QACT,KAAK,IAAI,UAAU,WAAW;AAAA,QAC9B,OAAO;AAAA,UACL,YAAY,OAAO;AACjB,gBAAI,CAAC,wBAAwB,OAAO,MAAM,GAAG;AAC3C,qBAAO;AAAA,YACT;AAEA,mBAAO,cAAc,OAAO,MAAM,KAAK;AAAA,cACrC,WAAW,OAAO,MAAM,UAAU,MAAM,MAAM,UAAU,IAAI;AAAA,gBAC1D,OAAO,QAAQ;AAAA,cACjB,CAAC;AAAA,YACH,CAAC;AAAA,UACH;AAAA,UACA,iBAAiB;AAAA,YACf,KAAK,MAAM;AACT,kBAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAAG;AAC/C,uBAAO;AAAA,cACT;AAEA,gCAAkB;AAElB,qBAAO;AAAA,YACT;AAAA,YACA,MAAM,MAAM;AACV,kBAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAAG;AAC/C,uBAAO;AAAA,cACT;AAEA,oCAAsB,MAAM;AAC1B,oBAAI,CAAC,OAAO,eAAe,KAAK,SAAS,GAAG;AAC1C,sCAAoB,IAAI;AAAA,gBAC1B;AAAA,cACF,CAAC;AAED,qBAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/selection/selection.ts"],"sourcesContent":["import { Extension, isNodeSelection, type Editor } from '@tiptap/core'\nimport { Plugin, PluginKey, type EditorState } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport type SelectionOptions = {\n /**\n * The class name that should be added to the selected text.\n * @default 'selection'\n * @example 'is-selected'\n */\n className: string\n}\n\n/**\n * Whether the native browser selection should be cleared on blur and restored on focus.\n * Only applies to non-empty text selections in an editable editor.\n */\nfunction shouldSyncDomSelection(state: EditorState, editor: Editor): boolean {\n return !state.selection.empty && !isNodeSelection(state.selection) && editor.isEditable\n}\n\n/**\n * Whether the selection decoration should be rendered to keep the selection\n * visible while the editor is blurred (and not dragging).\n */\nfunction shouldPreserveSelection(state: EditorState, editor: Editor): boolean {\n return shouldSyncDomSelection(state, editor) && !editor.isFocused && !editor.view.dragging\n}\n\nfunction clearDomSelection() {\n window.getSelection()?.removeAllRanges()\n}\n\n/**\n * Sync the native selection from the editor state.\n * @see https://prosemirror.net/docs/ref/#view.EditorView.focus\n */\nfunction restoreDomSelection(view: EditorView) {\n view.focus()\n}\n\n/**\n * This extension allows you to add a class to the selected text when the editor is blurred.\n * It clears the native browser selection on blur (so `::selection` styles do not overlap the\n * decoration) and restores it when the editor is focused again.\n * @see https://www.tiptap.dev/api/extensions/selection\n */\nexport const Selection = Extension.create<SelectionOptions>({\n name: 'selection',\n\n addOptions() {\n return {\n className: 'selection',\n }\n },\n\n addProseMirrorPlugins() {\n const { editor, options } = this\n\n return [\n new Plugin({\n key: new PluginKey('selection'),\n props: {\n decorations(state) {\n if (!shouldPreserveSelection(state, editor)) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(state.selection.from, state.selection.to, {\n class: options.className,\n }),\n ])\n },\n handleDOMEvents: {\n blur(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n clearDomSelection()\n\n return false\n },\n focus(view) {\n if (!shouldSyncDomSelection(view.state, editor)) {\n return false\n }\n\n requestAnimationFrame(() => {\n if (!editor.isDestroyed && view.hasFocus()) {\n restoreDomSelection(view)\n }\n })\n\n return false\n },\n },\n },\n }),\n ]\n },\n})\n\nexport default Selection\n"],"mappings":";;;;;;;;AAkBA,SAAS,uBAAuB,OAAoB,QAAyB;CAC3E,OAAO,CAAC,MAAM,UAAU,SAAS,CAAC,gBAAgB,MAAM,SAAS,KAAK,OAAO;AAC/E;;;;;AAMA,SAAS,wBAAwB,OAAoB,QAAyB;CAC5E,OAAO,uBAAuB,OAAO,MAAM,KAAK,CAAC,OAAO,aAAa,CAAC,OAAO,KAAK;AACpF;AAEA,SAAS,oBAAoB;;CAC3B,CAAA,uBAAA,OAAO,aAAa,OAAA,QAAA,yBAAA,KAAA,KAAA,qBAAG,gBAAgB;AACzC;;;;;AAMA,SAAS,oBAAoB,MAAkB;CAC7C,KAAK,MAAM;AACb;;;;;;;AAQA,MAAa,YAAY,UAAU,OAAyB;CAC1D,MAAM;CAEN,aAAa;EACX,OAAO,EACL,WAAW,YACb;CACF;CAEA,wBAAwB;EACtB,MAAM,EAAE,QAAQ,YAAY;EAE5B,OAAO,CACL,IAAI,OAAO;GACT,KAAK,IAAI,UAAU,WAAW;GAC9B,OAAO;IACL,YAAY,OAAO;KACjB,IAAI,CAAC,wBAAwB,OAAO,MAAM,GACxC,OAAO;KAGT,OAAO,cAAc,OAAO,MAAM,KAAK,CACrC,WAAW,OAAO,MAAM,UAAU,MAAM,MAAM,UAAU,IAAI,EAC1D,OAAO,QAAQ,UACjB,CAAC,CACH,CAAC;IACH;IACA,iBAAiB;KACf,KAAK,MAAM;MACT,IAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAC5C,OAAO;MAGT,kBAAkB;MAElB,OAAO;KACT;KACA,MAAM,MAAM;MACV,IAAI,CAAC,uBAAuB,KAAK,OAAO,MAAM,GAC5C,OAAO;MAGT,4BAA4B;OAC1B,IAAI,CAAC,OAAO,eAAe,KAAK,SAAS,GACvC,oBAAoB,IAAI;MAE5B,CAAC;MAED,OAAO;KACT;IACF;GACF;EACF,CAAC,CACH;CACF;AACF,CAAC"} |
@@ -1,92 +0,62 @@ | ||
| "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/trailing-node/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| TrailingNode: () => TrailingNode, | ||
| skipTrailingNodeMeta: () => skipTrailingNodeMeta | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_state = require("@tiptap/pm/state"); | ||
| //#region src/trailing-node/trailing-node.ts | ||
| const skipTrailingNodeMeta = "skipTrailingNode"; | ||
| function nodeEqualsType({ types, node }) { | ||
| return node && Array.isArray(types) && types.includes(node.type) || (node === null || node === void 0 ? void 0 : node.type) === types; | ||
| } | ||
| /** | ||
| * This extension allows you to add an extra node at the end of the document. | ||
| * @see https://www.tiptap.dev/api/extensions/trailing-node | ||
| */ | ||
| const TrailingNode = _tiptap_core.Extension.create({ | ||
| name: "trailingNode", | ||
| addOptions() { | ||
| return { | ||
| node: void 0, | ||
| notAfter: [] | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| var _this$editor$schema$t; | ||
| const plugin = new _tiptap_pm_state.PluginKey(this.name); | ||
| const defaultNode = this.options.node || ((_this$editor$schema$t = this.editor.schema.topNodeType.contentMatch.defaultType) === null || _this$editor$schema$t === void 0 ? void 0 : _this$editor$schema$t.name) || "paragraph"; | ||
| const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name)); | ||
| return [new _tiptap_pm_state.Plugin({ | ||
| key: plugin, | ||
| appendTransaction: (transactions, __, state) => { | ||
| const { doc, tr, schema } = state; | ||
| const shouldInsertNodeAtEnd = plugin.getState(state); | ||
| const endPosition = doc.content.size; | ||
| const type = schema.nodes[defaultNode]; | ||
| if (transactions.some((transaction) => transaction.getMeta("skipTrailingNode"))) return; | ||
| if (!shouldInsertNodeAtEnd) return; | ||
| return tr.insert(endPosition, type.create()); | ||
| }, | ||
| state: { | ||
| init: (_, state) => { | ||
| const lastNode = state.tr.doc.lastChild; | ||
| return !nodeEqualsType({ | ||
| node: lastNode, | ||
| types: disabledNodes | ||
| }); | ||
| }, | ||
| apply: (tr, value) => { | ||
| if (!tr.docChanged) return value; | ||
| if (tr.getMeta("__uniqueIDTransaction")) return value; | ||
| const lastNode = tr.doc.lastChild; | ||
| return !nodeEqualsType({ | ||
| node: lastNode, | ||
| types: disabledNodes | ||
| }); | ||
| } | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| //#endregion | ||
| exports.TrailingNode = TrailingNode; | ||
| exports.skipTrailingNodeMeta = skipTrailingNodeMeta; | ||
| // src/trailing-node/trailing-node.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_state = require("@tiptap/pm/state"); | ||
| var skipTrailingNodeMeta = "skipTrailingNode"; | ||
| function nodeEqualsType({ | ||
| types, | ||
| node | ||
| }) { | ||
| return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types; | ||
| } | ||
| var TrailingNode = import_core.Extension.create({ | ||
| name: "trailingNode", | ||
| addOptions() { | ||
| return { | ||
| node: void 0, | ||
| notAfter: [] | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| var _a; | ||
| const plugin = new import_state.PluginKey(this.name); | ||
| const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph"; | ||
| const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name)); | ||
| return [ | ||
| new import_state.Plugin({ | ||
| key: plugin, | ||
| appendTransaction: (transactions, __, state) => { | ||
| const { doc, tr, schema } = state; | ||
| const shouldInsertNodeAtEnd = plugin.getState(state); | ||
| const endPosition = doc.content.size; | ||
| const type = schema.nodes[defaultNode]; | ||
| if (transactions.some((transaction) => transaction.getMeta(skipTrailingNodeMeta))) { | ||
| return; | ||
| } | ||
| if (!shouldInsertNodeAtEnd) { | ||
| return; | ||
| } | ||
| return tr.insert(endPosition, type.create()); | ||
| }, | ||
| state: { | ||
| init: (_, state) => { | ||
| const lastNode = state.tr.doc.lastChild; | ||
| return !nodeEqualsType({ node: lastNode, types: disabledNodes }); | ||
| }, | ||
| apply: (tr, value) => { | ||
| if (!tr.docChanged) { | ||
| return value; | ||
| } | ||
| if (tr.getMeta("__uniqueIDTransaction")) { | ||
| return value; | ||
| } | ||
| const lastNode = tr.doc.lastChild; | ||
| return !nodeEqualsType({ node: lastNode, types: disabledNodes }); | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| TrailingNode, | ||
| skipTrailingNodeMeta | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/trailing-node/index.ts","../../src/trailing-node/trailing-node.ts"],"sourcesContent":["export * from './trailing-node.js'\n","import { Extension } from '@tiptap/core'\nimport type { Node, NodeType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport const skipTrailingNodeMeta = 'skipTrailingNode'\n\nfunction nodeEqualsType({\n types,\n node,\n}: {\n types: NodeType | NodeType[]\n node: Node | null | undefined\n}) {\n return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types\n}\n\n/**\n * Extension based on:\n * - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js\n * - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts\n */\n\nexport interface TrailingNodeOptions {\n /**\n * The node type that should be inserted at the end of the document.\n * @note the node will always be added to the `notAfter` lists to\n * prevent an infinite loop.\n * @default undefined\n */\n node?: string\n /**\n * The node types after which the trailing node should not be inserted.\n * @default ['paragraph']\n */\n notAfter?: string | string[]\n}\n\n/**\n * This extension allows you to add an extra node at the end of the document.\n * @see https://www.tiptap.dev/api/extensions/trailing-node\n */\nexport const TrailingNode = Extension.create<TrailingNodeOptions>({\n name: 'trailingNode',\n\n addOptions() {\n return {\n node: undefined,\n notAfter: [],\n }\n },\n\n addProseMirrorPlugins() {\n const plugin = new PluginKey(this.name)\n const defaultNode =\n this.options.node ||\n this.editor.schema.topNodeType.contentMatch.defaultType?.name ||\n 'paragraph'\n\n const disabledNodes = Object.entries(this.editor.schema.nodes)\n .map(([, value]) => value)\n .filter(node => (this.options.notAfter || []).concat(defaultNode).includes(node.name))\n\n return [\n new Plugin({\n key: plugin,\n appendTransaction: (transactions, __, state) => {\n const { doc, tr, schema } = state\n const shouldInsertNodeAtEnd = plugin.getState(state)\n const endPosition = doc.content.size\n const type = schema.nodes[defaultNode]\n\n if (transactions.some(transaction => transaction.getMeta(skipTrailingNodeMeta))) {\n return\n }\n\n if (!shouldInsertNodeAtEnd) {\n return\n }\n\n return tr.insert(endPosition, type.create())\n },\n state: {\n init: (_, state) => {\n const lastNode = state.tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n apply: (tr, value) => {\n if (!tr.docChanged) {\n return value\n }\n\n // Ignore transactions from UniqueID extension to prevent infinite loops\n // when UniqueID adds IDs to newly inserted trailing nodes\n if (tr.getMeta('__uniqueIDTransaction')) {\n return value\n }\n\n const lastNode = tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n },\n }),\n ]\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA0B;AAE1B,mBAAkC;AAE3B,IAAM,uBAAuB;AAEpC,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AACF,GAGG;AACD,SAAQ,QAAQ,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,IAAI,MAAM,6BAAM,UAAS;AACvF;AA2BO,IAAM,eAAe,sBAAU,OAA4B;AAAA,EAChE,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU,CAAC;AAAA,IACb;AAAA,EACF;AAAA,EAEA,wBAAwB;AAnD1B;AAoDI,UAAM,SAAS,IAAI,uBAAU,KAAK,IAAI;AACtC,UAAM,cACJ,KAAK,QAAQ,UACb,UAAK,OAAO,OAAO,YAAY,aAAa,gBAA5C,mBAAyD,SACzD;AAEF,UAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,OAAO,KAAK,EAC1D,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,KAAK,EACxB,OAAO,WAAS,KAAK,QAAQ,YAAY,CAAC,GAAG,OAAO,WAAW,EAAE,SAAS,KAAK,IAAI,CAAC;AAEvF,WAAO;AAAA,MACL,IAAI,oBAAO;AAAA,QACT,KAAK;AAAA,QACL,mBAAmB,CAAC,cAAc,IAAI,UAAU;AAC9C,gBAAM,EAAE,KAAK,IAAI,OAAO,IAAI;AAC5B,gBAAM,wBAAwB,OAAO,SAAS,KAAK;AACnD,gBAAM,cAAc,IAAI,QAAQ;AAChC,gBAAM,OAAO,OAAO,MAAM,WAAW;AAErC,cAAI,aAAa,KAAK,iBAAe,YAAY,QAAQ,oBAAoB,CAAC,GAAG;AAC/E;AAAA,UACF;AAEA,cAAI,CAAC,uBAAuB;AAC1B;AAAA,UACF;AAEA,iBAAO,GAAG,OAAO,aAAa,KAAK,OAAO,CAAC;AAAA,QAC7C;AAAA,QACA,OAAO;AAAA,UACL,MAAM,CAAC,GAAG,UAAU;AAClB,kBAAM,WAAW,MAAM,GAAG,IAAI;AAE9B,mBAAO,CAAC,eAAe,EAAE,MAAM,UAAU,OAAO,cAAc,CAAC;AAAA,UACjE;AAAA,UACA,OAAO,CAAC,IAAI,UAAU;AACpB,gBAAI,CAAC,GAAG,YAAY;AAClB,qBAAO;AAAA,YACT;AAIA,gBAAI,GAAG,QAAQ,uBAAuB,GAAG;AACvC,qBAAO;AAAA,YACT;AAEA,kBAAM,WAAW,GAAG,IAAI;AAExB,mBAAO,CAAC,eAAe,EAAE,MAAM,UAAU,OAAO,cAAc,CAAC;AAAA,UACjE;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.cjs","names":["Extension","PluginKey","Plugin"],"sources":["../../src/trailing-node/trailing-node.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node, NodeType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport const skipTrailingNodeMeta = 'skipTrailingNode'\n\nfunction nodeEqualsType({\n types,\n node,\n}: {\n types: NodeType | NodeType[]\n node: Node | null | undefined\n}) {\n return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types\n}\n\n/**\n * Extension based on:\n * - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js\n * - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts\n */\n\nexport interface TrailingNodeOptions {\n /**\n * The node type that should be inserted at the end of the document.\n * @note the node will always be added to the `notAfter` lists to\n * prevent an infinite loop.\n * @default undefined\n */\n node?: string\n /**\n * The node types after which the trailing node should not be inserted.\n * @default ['paragraph']\n */\n notAfter?: string | string[]\n}\n\n/**\n * This extension allows you to add an extra node at the end of the document.\n * @see https://www.tiptap.dev/api/extensions/trailing-node\n */\nexport const TrailingNode = Extension.create<TrailingNodeOptions>({\n name: 'trailingNode',\n\n addOptions() {\n return {\n node: undefined,\n notAfter: [],\n }\n },\n\n addProseMirrorPlugins() {\n const plugin = new PluginKey(this.name)\n const defaultNode =\n this.options.node ||\n this.editor.schema.topNodeType.contentMatch.defaultType?.name ||\n 'paragraph'\n\n const disabledNodes = Object.entries(this.editor.schema.nodes)\n .map(([, value]) => value)\n .filter(node => (this.options.notAfter || []).concat(defaultNode).includes(node.name))\n\n return [\n new Plugin({\n key: plugin,\n appendTransaction: (transactions, __, state) => {\n const { doc, tr, schema } = state\n const shouldInsertNodeAtEnd = plugin.getState(state)\n const endPosition = doc.content.size\n const type = schema.nodes[defaultNode]\n\n if (transactions.some(transaction => transaction.getMeta(skipTrailingNodeMeta))) {\n return\n }\n\n if (!shouldInsertNodeAtEnd) {\n return\n }\n\n return tr.insert(endPosition, type.create())\n },\n state: {\n init: (_, state) => {\n const lastNode = state.tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n apply: (tr, value) => {\n if (!tr.docChanged) {\n return value\n }\n\n // Ignore transactions from UniqueID extension to prevent infinite loops\n // when UniqueID adds IDs to newly inserted trailing nodes\n if (tr.getMeta('__uniqueIDTransaction')) {\n return value\n }\n\n const lastNode = tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n },\n }),\n ]\n },\n})\n"],"mappings":";;;;AAIA,MAAa,uBAAuB;AAEpC,SAAS,eAAe,EACtB,OACA,QAIC;CACD,OAAQ,QAAQ,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,IAAI,MAAA,SAAA,QAAA,SAAA,KAAA,IAAA,KAAA,IAAM,KAAM,UAAS;AACvF;;;;;AA2BA,MAAa,eAAeA,aAAAA,UAAU,OAA4B;CAChE,MAAM;CAEN,aAAa;EACX,OAAO;GACL,MAAM,KAAA;GACN,UAAU,CAAC;EACb;CACF;CAEA,wBAAwB;;EACtB,MAAM,SAAS,IAAIC,iBAAAA,UAAU,KAAK,IAAI;EACtC,MAAM,cACJ,KAAK,QAAQ,UAAA,wBACb,KAAK,OAAO,OAAO,YAAY,aAAa,iBAAA,QAAA,0BAAA,KAAA,IAAA,KAAA,IAAA,sBAAa,SACzD;EAEF,MAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,OAAO,KAAK,CAAC,CAC3D,KAAK,GAAG,WAAW,KAAK,CAAC,CACzB,QAAO,UAAS,KAAK,QAAQ,YAAY,CAAC,EAAA,CAAG,OAAO,WAAW,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC;EAEvF,OAAO,CACL,IAAIC,iBAAAA,OAAO;GACT,KAAK;GACL,oBAAoB,cAAc,IAAI,UAAU;IAC9C,MAAM,EAAE,KAAK,IAAI,WAAW;IAC5B,MAAM,wBAAwB,OAAO,SAAS,KAAK;IACnD,MAAM,cAAc,IAAI,QAAQ;IAChC,MAAM,OAAO,OAAO,MAAM;IAE1B,IAAI,aAAa,MAAK,gBAAe,YAAY,QAAA,kBAA4B,CAAC,GAC5E;IAGF,IAAI,CAAC,uBACH;IAGF,OAAO,GAAG,OAAO,aAAa,KAAK,OAAO,CAAC;GAC7C;GACA,OAAO;IACL,OAAO,GAAG,UAAU;KAClB,MAAM,WAAW,MAAM,GAAG,IAAI;KAE9B,OAAO,CAAC,eAAe;MAAE,MAAM;MAAU,OAAO;KAAc,CAAC;IACjE;IACA,QAAQ,IAAI,UAAU;KACpB,IAAI,CAAC,GAAG,YACN,OAAO;KAKT,IAAI,GAAG,QAAQ,uBAAuB,GACpC,OAAO;KAGT,MAAM,WAAW,GAAG,IAAI;KAExB,OAAO,CAAC,eAAe;MAAE,MAAM;MAAU,OAAO;KAAc,CAAC;IACjE;GACF;EACF,CAAC,CACH;CACF;AACF,CAAC"} |
@@ -1,3 +0,3 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/trailing-node/trailing-node.d.ts | ||
| declare const skipTrailingNodeMeta = "skipTrailingNode"; | ||
@@ -10,14 +10,14 @@ /** | ||
| interface TrailingNodeOptions { | ||
| /** | ||
| * The node type that should be inserted at the end of the document. | ||
| * @note the node will always be added to the `notAfter` lists to | ||
| * prevent an infinite loop. | ||
| * @default undefined | ||
| */ | ||
| node?: string; | ||
| /** | ||
| * The node types after which the trailing node should not be inserted. | ||
| * @default ['paragraph'] | ||
| */ | ||
| notAfter?: string | string[]; | ||
| /** | ||
| * The node type that should be inserted at the end of the document. | ||
| * @note the node will always be added to the `notAfter` lists to | ||
| * prevent an infinite loop. | ||
| * @default undefined | ||
| */ | ||
| node?: string; | ||
| /** | ||
| * The node types after which the trailing node should not be inserted. | ||
| * @default ['paragraph'] | ||
| */ | ||
| notAfter?: string | string[]; | ||
| } | ||
@@ -29,3 +29,4 @@ /** | ||
| declare const TrailingNode: Extension<TrailingNodeOptions, any>; | ||
| export { TrailingNode, type TrailingNodeOptions, skipTrailingNodeMeta }; | ||
| //#endregion | ||
| export { TrailingNode, TrailingNodeOptions, skipTrailingNodeMeta }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,3 +0,3 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/trailing-node/trailing-node.d.ts | ||
| declare const skipTrailingNodeMeta = "skipTrailingNode"; | ||
@@ -10,14 +10,14 @@ /** | ||
| interface TrailingNodeOptions { | ||
| /** | ||
| * The node type that should be inserted at the end of the document. | ||
| * @note the node will always be added to the `notAfter` lists to | ||
| * prevent an infinite loop. | ||
| * @default undefined | ||
| */ | ||
| node?: string; | ||
| /** | ||
| * The node types after which the trailing node should not be inserted. | ||
| * @default ['paragraph'] | ||
| */ | ||
| notAfter?: string | string[]; | ||
| /** | ||
| * The node type that should be inserted at the end of the document. | ||
| * @note the node will always be added to the `notAfter` lists to | ||
| * prevent an infinite loop. | ||
| * @default undefined | ||
| */ | ||
| node?: string; | ||
| /** | ||
| * The node types after which the trailing node should not be inserted. | ||
| * @default ['paragraph'] | ||
| */ | ||
| notAfter?: string | string[]; | ||
| } | ||
@@ -29,3 +29,4 @@ /** | ||
| declare const TrailingNode: Extension<TrailingNodeOptions, any>; | ||
| export { TrailingNode, type TrailingNodeOptions, skipTrailingNodeMeta }; | ||
| //#endregion | ||
| export { TrailingNode, TrailingNodeOptions, skipTrailingNodeMeta }; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,64 +0,60 @@ | ||
| // src/trailing-node/trailing-node.ts | ||
| import { Extension } from "@tiptap/core"; | ||
| import { Plugin, PluginKey } from "@tiptap/pm/state"; | ||
| var skipTrailingNodeMeta = "skipTrailingNode"; | ||
| function nodeEqualsType({ | ||
| types, | ||
| node | ||
| }) { | ||
| return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types; | ||
| //#region src/trailing-node/trailing-node.ts | ||
| const skipTrailingNodeMeta = "skipTrailingNode"; | ||
| function nodeEqualsType({ types, node }) { | ||
| return node && Array.isArray(types) && types.includes(node.type) || (node === null || node === void 0 ? void 0 : node.type) === types; | ||
| } | ||
| var TrailingNode = Extension.create({ | ||
| name: "trailingNode", | ||
| addOptions() { | ||
| return { | ||
| node: void 0, | ||
| notAfter: [] | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| var _a; | ||
| const plugin = new PluginKey(this.name); | ||
| const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph"; | ||
| const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name)); | ||
| return [ | ||
| new Plugin({ | ||
| key: plugin, | ||
| appendTransaction: (transactions, __, state) => { | ||
| const { doc, tr, schema } = state; | ||
| const shouldInsertNodeAtEnd = plugin.getState(state); | ||
| const endPosition = doc.content.size; | ||
| const type = schema.nodes[defaultNode]; | ||
| if (transactions.some((transaction) => transaction.getMeta(skipTrailingNodeMeta))) { | ||
| return; | ||
| } | ||
| if (!shouldInsertNodeAtEnd) { | ||
| return; | ||
| } | ||
| return tr.insert(endPosition, type.create()); | ||
| }, | ||
| state: { | ||
| init: (_, state) => { | ||
| const lastNode = state.tr.doc.lastChild; | ||
| return !nodeEqualsType({ node: lastNode, types: disabledNodes }); | ||
| }, | ||
| apply: (tr, value) => { | ||
| if (!tr.docChanged) { | ||
| return value; | ||
| } | ||
| if (tr.getMeta("__uniqueIDTransaction")) { | ||
| return value; | ||
| } | ||
| const lastNode = tr.doc.lastChild; | ||
| return !nodeEqualsType({ node: lastNode, types: disabledNodes }); | ||
| } | ||
| } | ||
| }) | ||
| ]; | ||
| } | ||
| /** | ||
| * This extension allows you to add an extra node at the end of the document. | ||
| * @see https://www.tiptap.dev/api/extensions/trailing-node | ||
| */ | ||
| const TrailingNode = Extension.create({ | ||
| name: "trailingNode", | ||
| addOptions() { | ||
| return { | ||
| node: void 0, | ||
| notAfter: [] | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| var _this$editor$schema$t; | ||
| const plugin = new PluginKey(this.name); | ||
| const defaultNode = this.options.node || ((_this$editor$schema$t = this.editor.schema.topNodeType.contentMatch.defaultType) === null || _this$editor$schema$t === void 0 ? void 0 : _this$editor$schema$t.name) || "paragraph"; | ||
| const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name)); | ||
| return [new Plugin({ | ||
| key: plugin, | ||
| appendTransaction: (transactions, __, state) => { | ||
| const { doc, tr, schema } = state; | ||
| const shouldInsertNodeAtEnd = plugin.getState(state); | ||
| const endPosition = doc.content.size; | ||
| const type = schema.nodes[defaultNode]; | ||
| if (transactions.some((transaction) => transaction.getMeta("skipTrailingNode"))) return; | ||
| if (!shouldInsertNodeAtEnd) return; | ||
| return tr.insert(endPosition, type.create()); | ||
| }, | ||
| state: { | ||
| init: (_, state) => { | ||
| const lastNode = state.tr.doc.lastChild; | ||
| return !nodeEqualsType({ | ||
| node: lastNode, | ||
| types: disabledNodes | ||
| }); | ||
| }, | ||
| apply: (tr, value) => { | ||
| if (!tr.docChanged) return value; | ||
| if (tr.getMeta("__uniqueIDTransaction")) return value; | ||
| const lastNode = tr.doc.lastChild; | ||
| return !nodeEqualsType({ | ||
| node: lastNode, | ||
| types: disabledNodes | ||
| }); | ||
| } | ||
| } | ||
| })]; | ||
| } | ||
| }); | ||
| export { | ||
| TrailingNode, | ||
| skipTrailingNodeMeta | ||
| }; | ||
| //#endregion | ||
| export { TrailingNode, skipTrailingNodeMeta }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/trailing-node/trailing-node.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node, NodeType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport const skipTrailingNodeMeta = 'skipTrailingNode'\n\nfunction nodeEqualsType({\n types,\n node,\n}: {\n types: NodeType | NodeType[]\n node: Node | null | undefined\n}) {\n return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types\n}\n\n/**\n * Extension based on:\n * - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js\n * - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts\n */\n\nexport interface TrailingNodeOptions {\n /**\n * The node type that should be inserted at the end of the document.\n * @note the node will always be added to the `notAfter` lists to\n * prevent an infinite loop.\n * @default undefined\n */\n node?: string\n /**\n * The node types after which the trailing node should not be inserted.\n * @default ['paragraph']\n */\n notAfter?: string | string[]\n}\n\n/**\n * This extension allows you to add an extra node at the end of the document.\n * @see https://www.tiptap.dev/api/extensions/trailing-node\n */\nexport const TrailingNode = Extension.create<TrailingNodeOptions>({\n name: 'trailingNode',\n\n addOptions() {\n return {\n node: undefined,\n notAfter: [],\n }\n },\n\n addProseMirrorPlugins() {\n const plugin = new PluginKey(this.name)\n const defaultNode =\n this.options.node ||\n this.editor.schema.topNodeType.contentMatch.defaultType?.name ||\n 'paragraph'\n\n const disabledNodes = Object.entries(this.editor.schema.nodes)\n .map(([, value]) => value)\n .filter(node => (this.options.notAfter || []).concat(defaultNode).includes(node.name))\n\n return [\n new Plugin({\n key: plugin,\n appendTransaction: (transactions, __, state) => {\n const { doc, tr, schema } = state\n const shouldInsertNodeAtEnd = plugin.getState(state)\n const endPosition = doc.content.size\n const type = schema.nodes[defaultNode]\n\n if (transactions.some(transaction => transaction.getMeta(skipTrailingNodeMeta))) {\n return\n }\n\n if (!shouldInsertNodeAtEnd) {\n return\n }\n\n return tr.insert(endPosition, type.create())\n },\n state: {\n init: (_, state) => {\n const lastNode = state.tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n apply: (tr, value) => {\n if (!tr.docChanged) {\n return value\n }\n\n // Ignore transactions from UniqueID extension to prevent infinite loops\n // when UniqueID adds IDs to newly inserted trailing nodes\n if (tr.getMeta('__uniqueIDTransaction')) {\n return value\n }\n\n const lastNode = tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n },\n }),\n ]\n },\n})\n"],"mappings":";AAAA,SAAS,iBAAiB;AAE1B,SAAS,QAAQ,iBAAiB;AAE3B,IAAM,uBAAuB;AAEpC,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AACF,GAGG;AACD,SAAQ,QAAQ,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,IAAI,MAAM,6BAAM,UAAS;AACvF;AA2BO,IAAM,eAAe,UAAU,OAA4B;AAAA,EAChE,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU,CAAC;AAAA,IACb;AAAA,EACF;AAAA,EAEA,wBAAwB;AAnD1B;AAoDI,UAAM,SAAS,IAAI,UAAU,KAAK,IAAI;AACtC,UAAM,cACJ,KAAK,QAAQ,UACb,UAAK,OAAO,OAAO,YAAY,aAAa,gBAA5C,mBAAyD,SACzD;AAEF,UAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,OAAO,KAAK,EAC1D,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,KAAK,EACxB,OAAO,WAAS,KAAK,QAAQ,YAAY,CAAC,GAAG,OAAO,WAAW,EAAE,SAAS,KAAK,IAAI,CAAC;AAEvF,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,QACT,KAAK;AAAA,QACL,mBAAmB,CAAC,cAAc,IAAI,UAAU;AAC9C,gBAAM,EAAE,KAAK,IAAI,OAAO,IAAI;AAC5B,gBAAM,wBAAwB,OAAO,SAAS,KAAK;AACnD,gBAAM,cAAc,IAAI,QAAQ;AAChC,gBAAM,OAAO,OAAO,MAAM,WAAW;AAErC,cAAI,aAAa,KAAK,iBAAe,YAAY,QAAQ,oBAAoB,CAAC,GAAG;AAC/E;AAAA,UACF;AAEA,cAAI,CAAC,uBAAuB;AAC1B;AAAA,UACF;AAEA,iBAAO,GAAG,OAAO,aAAa,KAAK,OAAO,CAAC;AAAA,QAC7C;AAAA,QACA,OAAO;AAAA,UACL,MAAM,CAAC,GAAG,UAAU;AAClB,kBAAM,WAAW,MAAM,GAAG,IAAI;AAE9B,mBAAO,CAAC,eAAe,EAAE,MAAM,UAAU,OAAO,cAAc,CAAC;AAAA,UACjE;AAAA,UACA,OAAO,CAAC,IAAI,UAAU;AACpB,gBAAI,CAAC,GAAG,YAAY;AAClB,qBAAO;AAAA,YACT;AAIA,gBAAI,GAAG,QAAQ,uBAAuB,GAAG;AACvC,qBAAO;AAAA,YACT;AAEA,kBAAM,WAAW,GAAG,IAAI;AAExB,mBAAO,CAAC,eAAe,EAAE,MAAM,UAAU,OAAO,cAAc,CAAC;AAAA,UACjE;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/trailing-node/trailing-node.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node, NodeType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nexport const skipTrailingNodeMeta = 'skipTrailingNode'\n\nfunction nodeEqualsType({\n types,\n node,\n}: {\n types: NodeType | NodeType[]\n node: Node | null | undefined\n}) {\n return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types\n}\n\n/**\n * Extension based on:\n * - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js\n * - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts\n */\n\nexport interface TrailingNodeOptions {\n /**\n * The node type that should be inserted at the end of the document.\n * @note the node will always be added to the `notAfter` lists to\n * prevent an infinite loop.\n * @default undefined\n */\n node?: string\n /**\n * The node types after which the trailing node should not be inserted.\n * @default ['paragraph']\n */\n notAfter?: string | string[]\n}\n\n/**\n * This extension allows you to add an extra node at the end of the document.\n * @see https://www.tiptap.dev/api/extensions/trailing-node\n */\nexport const TrailingNode = Extension.create<TrailingNodeOptions>({\n name: 'trailingNode',\n\n addOptions() {\n return {\n node: undefined,\n notAfter: [],\n }\n },\n\n addProseMirrorPlugins() {\n const plugin = new PluginKey(this.name)\n const defaultNode =\n this.options.node ||\n this.editor.schema.topNodeType.contentMatch.defaultType?.name ||\n 'paragraph'\n\n const disabledNodes = Object.entries(this.editor.schema.nodes)\n .map(([, value]) => value)\n .filter(node => (this.options.notAfter || []).concat(defaultNode).includes(node.name))\n\n return [\n new Plugin({\n key: plugin,\n appendTransaction: (transactions, __, state) => {\n const { doc, tr, schema } = state\n const shouldInsertNodeAtEnd = plugin.getState(state)\n const endPosition = doc.content.size\n const type = schema.nodes[defaultNode]\n\n if (transactions.some(transaction => transaction.getMeta(skipTrailingNodeMeta))) {\n return\n }\n\n if (!shouldInsertNodeAtEnd) {\n return\n }\n\n return tr.insert(endPosition, type.create())\n },\n state: {\n init: (_, state) => {\n const lastNode = state.tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n apply: (tr, value) => {\n if (!tr.docChanged) {\n return value\n }\n\n // Ignore transactions from UniqueID extension to prevent infinite loops\n // when UniqueID adds IDs to newly inserted trailing nodes\n if (tr.getMeta('__uniqueIDTransaction')) {\n return value\n }\n\n const lastNode = tr.doc.lastChild\n\n return !nodeEqualsType({ node: lastNode, types: disabledNodes })\n },\n },\n }),\n ]\n },\n})\n"],"mappings":";;;AAIA,MAAa,uBAAuB;AAEpC,SAAS,eAAe,EACtB,OACA,QAIC;CACD,OAAQ,QAAQ,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,IAAI,MAAA,SAAA,QAAA,SAAA,KAAA,IAAA,KAAA,IAAM,KAAM,UAAS;AACvF;;;;;AA2BA,MAAa,eAAe,UAAU,OAA4B;CAChE,MAAM;CAEN,aAAa;EACX,OAAO;GACL,MAAM,KAAA;GACN,UAAU,CAAC;EACb;CACF;CAEA,wBAAwB;;EACtB,MAAM,SAAS,IAAI,UAAU,KAAK,IAAI;EACtC,MAAM,cACJ,KAAK,QAAQ,UAAA,wBACb,KAAK,OAAO,OAAO,YAAY,aAAa,iBAAA,QAAA,0BAAA,KAAA,IAAA,KAAA,IAAA,sBAAa,SACzD;EAEF,MAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,OAAO,KAAK,CAAC,CAC3D,KAAK,GAAG,WAAW,KAAK,CAAC,CACzB,QAAO,UAAS,KAAK,QAAQ,YAAY,CAAC,EAAA,CAAG,OAAO,WAAW,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC;EAEvF,OAAO,CACL,IAAI,OAAO;GACT,KAAK;GACL,oBAAoB,cAAc,IAAI,UAAU;IAC9C,MAAM,EAAE,KAAK,IAAI,WAAW;IAC5B,MAAM,wBAAwB,OAAO,SAAS,KAAK;IACnD,MAAM,cAAc,IAAI,QAAQ;IAChC,MAAM,OAAO,OAAO,MAAM;IAE1B,IAAI,aAAa,MAAK,gBAAe,YAAY,QAAA,kBAA4B,CAAC,GAC5E;IAGF,IAAI,CAAC,uBACH;IAGF,OAAO,GAAG,OAAO,aAAa,KAAK,OAAO,CAAC;GAC7C;GACA,OAAO;IACL,OAAO,GAAG,UAAU;KAClB,MAAM,WAAW,MAAM,GAAG,IAAI;KAE9B,OAAO,CAAC,eAAe;MAAE,MAAM;MAAU,OAAO;KAAc,CAAC;IACjE;IACA,QAAQ,IAAI,UAAU;KACpB,IAAI,CAAC,GAAG,YACN,OAAO;KAKT,IAAI,GAAG,QAAQ,uBAAuB,GACpC,OAAO;KAGT,MAAM,WAAW,GAAG,IAAI;KAExB,OAAO,CAAC,eAAe;MAAE,MAAM;MAAU,OAAO;KAAc,CAAC;IACjE;GACF;EACF,CAAC,CACH;CACF;AACF,CAAC"} |
+45
-63
@@ -1,66 +0,48 @@ | ||
| "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/undo-redo/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| UndoRedo: () => UndoRedo | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let _tiptap_core = require("@tiptap/core"); | ||
| let _tiptap_pm_history = require("@tiptap/pm/history"); | ||
| //#region src/undo-redo/undo-redo.ts | ||
| /** | ||
| * This extension allows you to undo and redo recent changes. | ||
| * @see https://www.tiptap.dev/api/extensions/undo-redo | ||
| * | ||
| * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove | ||
| * the `undo-redo` extension, as it is not compatible with the `collaboration` extension. | ||
| * | ||
| * `@tiptap/extension-collaboration` uses its own history implementation. | ||
| */ | ||
| const UndoRedo = _tiptap_core.Extension.create({ | ||
| name: "undoRedo", | ||
| addOptions() { | ||
| return { | ||
| depth: 100, | ||
| newGroupDelay: 500 | ||
| }; | ||
| }, | ||
| addCommands() { | ||
| return { | ||
| undo: () => ({ state, dispatch }) => { | ||
| return (0, _tiptap_pm_history.undo)(state, dispatch); | ||
| }, | ||
| redo: () => ({ state, dispatch }) => { | ||
| return (0, _tiptap_pm_history.redo)(state, dispatch); | ||
| } | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [(0, _tiptap_pm_history.history)(this.options)]; | ||
| }, | ||
| addKeyboardShortcuts() { | ||
| return { | ||
| "Mod-z": () => this.editor.commands.undo(), | ||
| "Shift-Mod-z": () => this.editor.commands.redo(), | ||
| "Mod-y": () => this.editor.commands.redo(), | ||
| "Mod-я": () => this.editor.commands.undo(), | ||
| "Shift-Mod-я": () => this.editor.commands.redo() | ||
| }; | ||
| } | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| //#endregion | ||
| exports.UndoRedo = UndoRedo; | ||
| // src/undo-redo/undo-redo.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_history = require("@tiptap/pm/history"); | ||
| var UndoRedo = import_core.Extension.create({ | ||
| name: "undoRedo", | ||
| addOptions() { | ||
| return { | ||
| depth: 100, | ||
| newGroupDelay: 500 | ||
| }; | ||
| }, | ||
| addCommands() { | ||
| return { | ||
| undo: () => ({ state, dispatch }) => { | ||
| return (0, import_history.undo)(state, dispatch); | ||
| }, | ||
| redo: () => ({ state, dispatch }) => { | ||
| return (0, import_history.redo)(state, dispatch); | ||
| } | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [(0, import_history.history)(this.options)]; | ||
| }, | ||
| addKeyboardShortcuts() { | ||
| return { | ||
| "Mod-z": () => this.editor.commands.undo(), | ||
| "Shift-Mod-z": () => this.editor.commands.redo(), | ||
| "Mod-y": () => this.editor.commands.redo(), | ||
| // Russian keyboard layouts | ||
| "Mod-\u044F": () => this.editor.commands.undo(), | ||
| "Shift-Mod-\u044F": () => this.editor.commands.redo() | ||
| }; | ||
| } | ||
| }); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| UndoRedo | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/undo-redo/index.ts","../../src/undo-redo/undo-redo.ts"],"sourcesContent":["export * from './undo-redo.js'\n","import { Extension } from '@tiptap/core'\nimport { history, redo, undo } from '@tiptap/pm/history'\n\nexport interface UndoRedoOptions {\n /**\n * The amount of history events that are collected before the oldest events are discarded.\n * @default 100\n * @example 50\n */\n depth: number\n\n /**\n * The delay (in milliseconds) between changes after which a new group should be started.\n * @default 500\n * @example 1000\n */\n newGroupDelay: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n undoRedo: {\n /**\n * Undo recent changes\n * @example editor.commands.undo()\n */\n undo: () => ReturnType\n /**\n * Reapply reverted changes\n * @example editor.commands.redo()\n */\n redo: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to undo and redo recent changes.\n * @see https://www.tiptap.dev/api/extensions/undo-redo\n *\n * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove\n * the `undo-redo` extension, as it is not compatible with the `collaboration` extension.\n *\n * `@tiptap/extension-collaboration` uses its own history implementation.\n */\nexport const UndoRedo = Extension.create<UndoRedoOptions>({\n name: 'undoRedo',\n\n addOptions() {\n return {\n depth: 100,\n newGroupDelay: 500,\n }\n },\n\n addCommands() {\n return {\n undo:\n () =>\n ({ state, dispatch }) => {\n return undo(state, dispatch)\n },\n redo:\n () =>\n ({ state, dispatch }) => {\n return redo(state, dispatch)\n },\n }\n },\n\n addProseMirrorPlugins() {\n return [history(this.options)]\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-z': () => this.editor.commands.undo(),\n 'Shift-Mod-z': () => this.editor.commands.redo(),\n 'Mod-y': () => this.editor.commands.redo(),\n\n // Russian keyboard layouts\n 'Mod-я': () => this.editor.commands.undo(),\n 'Shift-Mod-я': () => this.editor.commands.redo(),\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA0B;AAC1B,qBAAoC;AA4C7B,IAAM,WAAW,sBAAU,OAAwB;AAAA,EACxD,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,OAAO;AAAA,MACP,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,MACE,MACA,CAAC,EAAE,OAAO,SAAS,MAAM;AACvB,mBAAO,qBAAK,OAAO,QAAQ;AAAA,MAC7B;AAAA,MACF,MACE,MACA,CAAC,EAAE,OAAO,SAAS,MAAM;AACvB,mBAAO,qBAAK,OAAO,QAAQ;AAAA,MAC7B;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO,KAAC,wBAAQ,KAAK,OAAO,CAAC;AAAA,EAC/B;AAAA,EAEA,uBAAuB;AACrB,WAAO;AAAA,MACL,SAAS,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA,MACzC,eAAe,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA,MAC/C,SAAS,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA;AAAA,MAGzC,cAAS,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA,MACzC,oBAAe,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA,IACjD;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.cjs","names":["Extension"],"sources":["../../src/undo-redo/undo-redo.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { history, redo, undo } from '@tiptap/pm/history'\n\nexport interface UndoRedoOptions {\n /**\n * The amount of history events that are collected before the oldest events are discarded.\n * @default 100\n * @example 50\n */\n depth: number\n\n /**\n * The delay (in milliseconds) between changes after which a new group should be started.\n * @default 500\n * @example 1000\n */\n newGroupDelay: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n undoRedo: {\n /**\n * Undo recent changes\n * @example editor.commands.undo()\n */\n undo: () => ReturnType\n /**\n * Reapply reverted changes\n * @example editor.commands.redo()\n */\n redo: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to undo and redo recent changes.\n * @see https://www.tiptap.dev/api/extensions/undo-redo\n *\n * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove\n * the `undo-redo` extension, as it is not compatible with the `collaboration` extension.\n *\n * `@tiptap/extension-collaboration` uses its own history implementation.\n */\nexport const UndoRedo = Extension.create<UndoRedoOptions>({\n name: 'undoRedo',\n\n addOptions() {\n return {\n depth: 100,\n newGroupDelay: 500,\n }\n },\n\n addCommands() {\n return {\n undo:\n () =>\n ({ state, dispatch }) => {\n return undo(state, dispatch)\n },\n redo:\n () =>\n ({ state, dispatch }) => {\n return redo(state, dispatch)\n },\n }\n },\n\n addProseMirrorPlugins() {\n return [history(this.options)]\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-z': () => this.editor.commands.undo(),\n 'Shift-Mod-z': () => this.editor.commands.redo(),\n 'Mod-y': () => this.editor.commands.redo(),\n\n // Russian keyboard layouts\n 'Mod-я': () => this.editor.commands.undo(),\n 'Shift-Mod-я': () => this.editor.commands.redo(),\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;;AA6CA,MAAa,WAAWA,aAAAA,UAAU,OAAwB;CACxD,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,eAAe;EACjB;CACF;CAEA,cAAc;EACZ,OAAO;GACL,aAEG,EAAE,OAAO,eAAe;IACvB,QAAA,GAAA,mBAAA,KAAA,CAAY,OAAO,QAAQ;GAC7B;GACF,aAEG,EAAE,OAAO,eAAe;IACvB,QAAA,GAAA,mBAAA,KAAA,CAAY,OAAO,QAAQ;GAC7B;EACJ;CACF;CAEA,wBAAwB;EACtB,OAAO,EAAA,GAAA,mBAAA,QAAA,CAAS,KAAK,OAAO,CAAC;CAC/B;CAEA,uBAAuB;EACrB,OAAO;GACL,eAAe,KAAK,OAAO,SAAS,KAAK;GACzC,qBAAqB,KAAK,OAAO,SAAS,KAAK;GAC/C,eAAe,KAAK,OAAO,SAAS,KAAK;GAGzC,eAAe,KAAK,OAAO,SAAS,KAAK;GACzC,qBAAqB,KAAK,OAAO,SAAS,KAAK;EACjD;CACF;AACF,CAAC"} |
@@ -1,32 +0,32 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/undo-redo/undo-redo.d.ts | ||
| interface UndoRedoOptions { | ||
| /** | ||
| * The amount of history events that are collected before the oldest events are discarded. | ||
| * @default 100 | ||
| * @example 50 | ||
| */ | ||
| depth: number; | ||
| /** | ||
| * The delay (in milliseconds) between changes after which a new group should be started. | ||
| * @default 500 | ||
| * @example 1000 | ||
| */ | ||
| newGroupDelay: number; | ||
| /** | ||
| * The amount of history events that are collected before the oldest events are discarded. | ||
| * @default 100 | ||
| * @example 50 | ||
| */ | ||
| depth: number; | ||
| /** | ||
| * The delay (in milliseconds) between changes after which a new group should be started. | ||
| * @default 500 | ||
| * @example 1000 | ||
| */ | ||
| newGroupDelay: number; | ||
| } | ||
| declare module '@tiptap/core' { | ||
| interface Commands<ReturnType> { | ||
| undoRedo: { | ||
| /** | ||
| * Undo recent changes | ||
| * @example editor.commands.undo() | ||
| */ | ||
| undo: () => ReturnType; | ||
| /** | ||
| * Reapply reverted changes | ||
| * @example editor.commands.redo() | ||
| */ | ||
| redo: () => ReturnType; | ||
| }; | ||
| } | ||
| interface Commands<ReturnType> { | ||
| undoRedo: { | ||
| /** | ||
| * Undo recent changes | ||
| * @example editor.commands.undo() | ||
| */ | ||
| undo: () => ReturnType; | ||
| /** | ||
| * Reapply reverted changes | ||
| * @example editor.commands.redo() | ||
| */ | ||
| redo: () => ReturnType; | ||
| }; | ||
| } | ||
| } | ||
@@ -43,3 +43,4 @@ /** | ||
| declare const UndoRedo: Extension<UndoRedoOptions, any>; | ||
| export { UndoRedo, type UndoRedoOptions }; | ||
| //#endregion | ||
| export { UndoRedo, UndoRedoOptions }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,32 +0,32 @@ | ||
| import { Extension } from '@tiptap/core'; | ||
| import { Extension } from "@tiptap/core"; | ||
| //#region src/undo-redo/undo-redo.d.ts | ||
| interface UndoRedoOptions { | ||
| /** | ||
| * The amount of history events that are collected before the oldest events are discarded. | ||
| * @default 100 | ||
| * @example 50 | ||
| */ | ||
| depth: number; | ||
| /** | ||
| * The delay (in milliseconds) between changes after which a new group should be started. | ||
| * @default 500 | ||
| * @example 1000 | ||
| */ | ||
| newGroupDelay: number; | ||
| /** | ||
| * The amount of history events that are collected before the oldest events are discarded. | ||
| * @default 100 | ||
| * @example 50 | ||
| */ | ||
| depth: number; | ||
| /** | ||
| * The delay (in milliseconds) between changes after which a new group should be started. | ||
| * @default 500 | ||
| * @example 1000 | ||
| */ | ||
| newGroupDelay: number; | ||
| } | ||
| declare module '@tiptap/core' { | ||
| interface Commands<ReturnType> { | ||
| undoRedo: { | ||
| /** | ||
| * Undo recent changes | ||
| * @example editor.commands.undo() | ||
| */ | ||
| undo: () => ReturnType; | ||
| /** | ||
| * Reapply reverted changes | ||
| * @example editor.commands.redo() | ||
| */ | ||
| redo: () => ReturnType; | ||
| }; | ||
| } | ||
| interface Commands<ReturnType> { | ||
| undoRedo: { | ||
| /** | ||
| * Undo recent changes | ||
| * @example editor.commands.undo() | ||
| */ | ||
| undo: () => ReturnType; | ||
| /** | ||
| * Reapply reverted changes | ||
| * @example editor.commands.redo() | ||
| */ | ||
| redo: () => ReturnType; | ||
| }; | ||
| } | ||
| } | ||
@@ -43,3 +43,4 @@ /** | ||
| declare const UndoRedo: Extension<UndoRedoOptions, any>; | ||
| export { UndoRedo, type UndoRedoOptions }; | ||
| //#endregion | ||
| export { UndoRedo, UndoRedoOptions }; | ||
| //# sourceMappingURL=index.d.ts.map |
+43
-35
@@ -1,39 +0,47 @@ | ||
| // src/undo-redo/undo-redo.ts | ||
| import { Extension } from "@tiptap/core"; | ||
| import { history, redo, undo } from "@tiptap/pm/history"; | ||
| var UndoRedo = Extension.create({ | ||
| name: "undoRedo", | ||
| addOptions() { | ||
| return { | ||
| depth: 100, | ||
| newGroupDelay: 500 | ||
| }; | ||
| }, | ||
| addCommands() { | ||
| return { | ||
| undo: () => ({ state, dispatch }) => { | ||
| return undo(state, dispatch); | ||
| }, | ||
| redo: () => ({ state, dispatch }) => { | ||
| return redo(state, dispatch); | ||
| } | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [history(this.options)]; | ||
| }, | ||
| addKeyboardShortcuts() { | ||
| return { | ||
| "Mod-z": () => this.editor.commands.undo(), | ||
| "Shift-Mod-z": () => this.editor.commands.redo(), | ||
| "Mod-y": () => this.editor.commands.redo(), | ||
| // Russian keyboard layouts | ||
| "Mod-\u044F": () => this.editor.commands.undo(), | ||
| "Shift-Mod-\u044F": () => this.editor.commands.redo() | ||
| }; | ||
| } | ||
| //#region src/undo-redo/undo-redo.ts | ||
| /** | ||
| * This extension allows you to undo and redo recent changes. | ||
| * @see https://www.tiptap.dev/api/extensions/undo-redo | ||
| * | ||
| * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove | ||
| * the `undo-redo` extension, as it is not compatible with the `collaboration` extension. | ||
| * | ||
| * `@tiptap/extension-collaboration` uses its own history implementation. | ||
| */ | ||
| const UndoRedo = Extension.create({ | ||
| name: "undoRedo", | ||
| addOptions() { | ||
| return { | ||
| depth: 100, | ||
| newGroupDelay: 500 | ||
| }; | ||
| }, | ||
| addCommands() { | ||
| return { | ||
| undo: () => ({ state, dispatch }) => { | ||
| return undo(state, dispatch); | ||
| }, | ||
| redo: () => ({ state, dispatch }) => { | ||
| return redo(state, dispatch); | ||
| } | ||
| }; | ||
| }, | ||
| addProseMirrorPlugins() { | ||
| return [history(this.options)]; | ||
| }, | ||
| addKeyboardShortcuts() { | ||
| return { | ||
| "Mod-z": () => this.editor.commands.undo(), | ||
| "Shift-Mod-z": () => this.editor.commands.redo(), | ||
| "Mod-y": () => this.editor.commands.redo(), | ||
| "Mod-я": () => this.editor.commands.undo(), | ||
| "Shift-Mod-я": () => this.editor.commands.redo() | ||
| }; | ||
| } | ||
| }); | ||
| export { | ||
| UndoRedo | ||
| }; | ||
| //#endregion | ||
| export { UndoRedo }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../src/undo-redo/undo-redo.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { history, redo, undo } from '@tiptap/pm/history'\n\nexport interface UndoRedoOptions {\n /**\n * The amount of history events that are collected before the oldest events are discarded.\n * @default 100\n * @example 50\n */\n depth: number\n\n /**\n * The delay (in milliseconds) between changes after which a new group should be started.\n * @default 500\n * @example 1000\n */\n newGroupDelay: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n undoRedo: {\n /**\n * Undo recent changes\n * @example editor.commands.undo()\n */\n undo: () => ReturnType\n /**\n * Reapply reverted changes\n * @example editor.commands.redo()\n */\n redo: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to undo and redo recent changes.\n * @see https://www.tiptap.dev/api/extensions/undo-redo\n *\n * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove\n * the `undo-redo` extension, as it is not compatible with the `collaboration` extension.\n *\n * `@tiptap/extension-collaboration` uses its own history implementation.\n */\nexport const UndoRedo = Extension.create<UndoRedoOptions>({\n name: 'undoRedo',\n\n addOptions() {\n return {\n depth: 100,\n newGroupDelay: 500,\n }\n },\n\n addCommands() {\n return {\n undo:\n () =>\n ({ state, dispatch }) => {\n return undo(state, dispatch)\n },\n redo:\n () =>\n ({ state, dispatch }) => {\n return redo(state, dispatch)\n },\n }\n },\n\n addProseMirrorPlugins() {\n return [history(this.options)]\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-z': () => this.editor.commands.undo(),\n 'Shift-Mod-z': () => this.editor.commands.redo(),\n 'Mod-y': () => this.editor.commands.redo(),\n\n // Russian keyboard layouts\n 'Mod-я': () => this.editor.commands.undo(),\n 'Shift-Mod-я': () => this.editor.commands.redo(),\n }\n },\n})\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,SAAS,MAAM,YAAY;AA4C7B,IAAM,WAAW,UAAU,OAAwB;AAAA,EACxD,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,OAAO;AAAA,MACP,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,MACE,MACA,CAAC,EAAE,OAAO,SAAS,MAAM;AACvB,eAAO,KAAK,OAAO,QAAQ;AAAA,MAC7B;AAAA,MACF,MACE,MACA,CAAC,EAAE,OAAO,SAAS,MAAM;AACvB,eAAO,KAAK,OAAO,QAAQ;AAAA,MAC7B;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAAA,EAC/B;AAAA,EAEA,uBAAuB;AACrB,WAAO;AAAA,MACL,SAAS,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA,MACzC,eAAe,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA,MAC/C,SAAS,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA;AAAA,MAGzC,cAAS,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA,MACzC,oBAAe,MAAM,KAAK,OAAO,SAAS,KAAK;AAAA,IACjD;AAAA,EACF;AACF,CAAC;","names":[]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/undo-redo/undo-redo.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { history, redo, undo } from '@tiptap/pm/history'\n\nexport interface UndoRedoOptions {\n /**\n * The amount of history events that are collected before the oldest events are discarded.\n * @default 100\n * @example 50\n */\n depth: number\n\n /**\n * The delay (in milliseconds) between changes after which a new group should be started.\n * @default 500\n * @example 1000\n */\n newGroupDelay: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n undoRedo: {\n /**\n * Undo recent changes\n * @example editor.commands.undo()\n */\n undo: () => ReturnType\n /**\n * Reapply reverted changes\n * @example editor.commands.redo()\n */\n redo: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to undo and redo recent changes.\n * @see https://www.tiptap.dev/api/extensions/undo-redo\n *\n * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove\n * the `undo-redo` extension, as it is not compatible with the `collaboration` extension.\n *\n * `@tiptap/extension-collaboration` uses its own history implementation.\n */\nexport const UndoRedo = Extension.create<UndoRedoOptions>({\n name: 'undoRedo',\n\n addOptions() {\n return {\n depth: 100,\n newGroupDelay: 500,\n }\n },\n\n addCommands() {\n return {\n undo:\n () =>\n ({ state, dispatch }) => {\n return undo(state, dispatch)\n },\n redo:\n () =>\n ({ state, dispatch }) => {\n return redo(state, dispatch)\n },\n }\n },\n\n addProseMirrorPlugins() {\n return [history(this.options)]\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-z': () => this.editor.commands.undo(),\n 'Shift-Mod-z': () => this.editor.commands.redo(),\n 'Mod-y': () => this.editor.commands.redo(),\n\n // Russian keyboard layouts\n 'Mod-я': () => this.editor.commands.undo(),\n 'Shift-Mod-я': () => this.editor.commands.redo(),\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;AA6CA,MAAa,WAAW,UAAU,OAAwB;CACxD,MAAM;CAEN,aAAa;EACX,OAAO;GACL,OAAO;GACP,eAAe;EACjB;CACF;CAEA,cAAc;EACZ,OAAO;GACL,aAEG,EAAE,OAAO,eAAe;IACvB,OAAO,KAAK,OAAO,QAAQ;GAC7B;GACF,aAEG,EAAE,OAAO,eAAe;IACvB,OAAO,KAAK,OAAO,QAAQ;GAC7B;EACJ;CACF;CAEA,wBAAwB;EACtB,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;CAC/B;CAEA,uBAAuB;EACrB,OAAO;GACL,eAAe,KAAK,OAAO,SAAS,KAAK;GACzC,qBAAqB,KAAK,OAAO,SAAS,KAAK;GAC/C,eAAe,KAAK,OAAO,SAAS,KAAK;GAGzC,eAAe,KAAK,OAAO,SAAS,KAAK;GACzC,qBAAqB,KAAK,OAAO,SAAS,KAAK;EACjD;CACF;AACF,CAAC"} |
+6
-6
| { | ||
| "name": "@tiptap/extensions", | ||
| "version": "3.30.2", | ||
| "version": "3.30.3", | ||
| "description": "various extensions for tiptap", | ||
@@ -106,12 +106,12 @@ "keywords": [ | ||
| "devDependencies": { | ||
| "@tiptap/core": "^3.30.2", | ||
| "@tiptap/pm": "^3.30.2" | ||
| "@tiptap/core": "^3.30.3", | ||
| "@tiptap/pm": "^3.30.3" | ||
| }, | ||
| "peerDependencies": { | ||
| "@tiptap/core": "3.30.2", | ||
| "@tiptap/pm": "3.30.2" | ||
| "@tiptap/core": "3.30.3", | ||
| "@tiptap/pm": "3.30.3" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsup" | ||
| "build": "vp pack" | ||
| } | ||
| } |
101
21.69%411105
-0.75%4751
-5.79%