UNPKG

@tiptap/extension-node-range

Version:

node range extension for tiptap

337 lines (327 loc) 12 kB
"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, { NodeRange: () => NodeRange2, NodeRangeSelection: () => NodeRangeSelection, default: () => index_default, getNodeRangeDecorations: () => getNodeRangeDecorations, getSelectionRanges: () => getSelectionRanges, isNodeRangeSelection: () => isNodeRangeSelection }); module.exports = __toCommonJS(index_exports); // src/node-range.ts var import_core = require("@tiptap/core"); var import_state3 = require("@tiptap/pm/state"); // src/helpers/getNodeRangeDecorations.ts var import_view = require("@tiptap/pm/view"); function getNodeRangeDecorations(ranges) { if (!ranges.length) { return import_view.DecorationSet.empty; } const decorations = []; const doc = ranges[0].$from.node(0); ranges.forEach((range) => { const pos = range.$from.pos; const node = range.$from.nodeAfter; if (!node) { return; } decorations.push( import_view.Decoration.node(pos, pos + node.nodeSize, { class: "ProseMirror-selectednoderange" }) ); }); return import_view.DecorationSet.create(doc, decorations); } // src/helpers/getSelectionRanges.ts var import_model = require("@tiptap/pm/model"); var import_state = require("@tiptap/pm/state"); function getSelectionRanges($from, $to, depth) { const ranges = []; const doc = $from.node(0); if (typeof depth === "number" && depth >= 0) { } else if ($from.sameParent($to)) { depth = Math.max(0, $from.sharedDepth($to.pos) - 1); } else { depth = $from.sharedDepth($to.pos); } const nodeRange = new import_model.NodeRange($from, $to, depth); const offset = nodeRange.depth === 0 ? 0 : doc.resolve(nodeRange.start).posAtIndex(0); nodeRange.parent.forEach((node, pos) => { const from = offset + pos; const to = from + node.nodeSize; if (from < nodeRange.start || from >= nodeRange.end) { return; } const selectionRange = new import_state.SelectionRange(doc.resolve(from), doc.resolve(to)); ranges.push(selectionRange); }); return ranges; } // src/helpers/NodeRangeSelection.ts var import_state2 = require("@tiptap/pm/state"); // src/helpers/NodeRangeBookmark.ts var NodeRangeBookmark = class _NodeRangeBookmark { constructor(anchor, head) { this.anchor = anchor; this.head = head; } map(mapping) { return new _NodeRangeBookmark(mapping.map(this.anchor), mapping.map(this.head)); } resolve(doc) { const $anchor = doc.resolve(this.anchor); const $head = doc.resolve(this.head); return new NodeRangeSelection($anchor, $head); } }; // src/helpers/NodeRangeSelection.ts var NodeRangeSelection = class _NodeRangeSelection extends import_state2.Selection { constructor($anchor, $head, depth, bias = 1) { const { doc } = $anchor; const isCursor = $anchor === $head; const isCursorAtEnd = $anchor.pos === doc.content.size && $head.pos === doc.content.size; const $correctedHead = isCursor && !isCursorAtEnd ? doc.resolve($head.pos + (bias > 0 ? 1 : -1)) : $head; const $correctedAnchor = isCursor && isCursorAtEnd ? doc.resolve($anchor.pos - (bias > 0 ? 1 : -1)) : $anchor; const ranges = getSelectionRanges($correctedAnchor.min($correctedHead), $correctedAnchor.max($correctedHead), depth); const $rangeFrom = $correctedHead.pos >= $anchor.pos ? ranges[0].$from : ranges[ranges.length - 1].$to; const $rangeTo = $correctedHead.pos >= $anchor.pos ? ranges[ranges.length - 1].$to : ranges[0].$from; super($rangeFrom, $rangeTo, ranges); this.depth = depth; } // we can safely ignore this TypeScript error: https://github.com/Microsoft/TypeScript/issues/338 // @ts-ignore get $to() { return this.ranges[this.ranges.length - 1].$to; } eq(other) { return other instanceof _NodeRangeSelection && other.$from.pos === this.$from.pos && other.$to.pos === this.$to.pos; } map(doc, mapping) { const $anchor = doc.resolve(mapping.map(this.anchor)); const $head = doc.resolve(mapping.map(this.head)); return new _NodeRangeSelection($anchor, $head); } toJSON() { return { type: "nodeRange", anchor: this.anchor, head: this.head }; } get isForwards() { return this.head >= this.anchor; } get isBackwards() { return !this.isForwards; } extendBackwards() { const { doc } = this.$from; if (this.isForwards && this.ranges.length > 1) { const ranges = this.ranges.slice(0, -1); const $from2 = ranges[0].$from; const $to = ranges[ranges.length - 1].$to; return new _NodeRangeSelection($from2, $to, this.depth); } const firstRange = this.ranges[0]; const $from = doc.resolve(Math.max(0, firstRange.$from.pos - 1)); return new _NodeRangeSelection(this.$anchor, $from, this.depth); } extendForwards() { const { doc } = this.$from; if (this.isBackwards && this.ranges.length > 1) { const ranges = this.ranges.slice(1); const $from = ranges[0].$from; const $to2 = ranges[ranges.length - 1].$to; return new _NodeRangeSelection($to2, $from, this.depth); } const lastRange = this.ranges[this.ranges.length - 1]; const $to = doc.resolve(Math.min(doc.content.size, lastRange.$to.pos + 1)); return new _NodeRangeSelection(this.$anchor, $to, this.depth); } static fromJSON(doc, json) { return new _NodeRangeSelection(doc.resolve(json.anchor), doc.resolve(json.head)); } static create(doc, anchor, head, depth, bias = 1) { return new this(doc.resolve(anchor), doc.resolve(head), depth, bias); } getBookmark() { return new NodeRangeBookmark(this.anchor, this.head); } }; NodeRangeSelection.prototype.visible = false; // src/helpers/isNodeRangeSelection.ts function isNodeRangeSelection(value) { return value instanceof NodeRangeSelection; } // src/node-range.ts var NodeRange2 = import_core.Extension.create({ name: "nodeRange", addOptions() { return { depth: void 0, key: "Mod" }; }, addKeyboardShortcuts() { return { // extend NodeRangeSelection upwards "Shift-ArrowUp": ({ editor }) => { const { depth } = this.options; const { view, state } = editor; const { doc, selection, tr } = state; const { anchor, head } = selection; if (!isNodeRangeSelection(selection)) { const nodeRangeSelection2 = NodeRangeSelection.create(doc, anchor, head, depth, -1); tr.setSelection(nodeRangeSelection2); view.dispatch(tr); return true; } const nodeRangeSelection = selection.extendBackwards(); tr.setSelection(nodeRangeSelection); view.dispatch(tr); return true; }, // extend NodeRangeSelection downwards "Shift-ArrowDown": ({ editor }) => { const { depth } = this.options; const { view, state } = editor; const { doc, selection, tr } = state; const { anchor, head } = selection; if (!isNodeRangeSelection(selection)) { const nodeRangeSelection2 = NodeRangeSelection.create(doc, anchor, head, depth); tr.setSelection(nodeRangeSelection2); view.dispatch(tr); return true; } const nodeRangeSelection = selection.extendForwards(); tr.setSelection(nodeRangeSelection); view.dispatch(tr); return true; }, // add `NodeRangeSelection` to all nodes "Mod-a": ({ editor }) => { const { depth } = this.options; const { view, state } = editor; const { doc, tr } = state; const nodeRangeSelection = NodeRangeSelection.create(doc, 0, doc.content.size, depth); tr.setSelection(nodeRangeSelection); view.dispatch(tr); return true; } }; }, onSelectionUpdate() { const { selection } = this.editor.state; if (isNodeRangeSelection(selection)) { this.editor.view.dom.classList.add("ProseMirror-noderangeselection"); } }, addProseMirrorPlugins() { let hideTextSelection = false; let activeMouseSelection = false; return [ new import_state3.Plugin({ key: new import_state3.PluginKey("nodeRange"), props: { attributes: () => { if (hideTextSelection) { return { class: "ProseMirror-noderangeselection" }; } return { class: "" }; }, handleDOMEvents: { mousedown: (view, event) => { const { key } = this.options; const isMac = /Mac/.test(navigator.platform); const isShift = !!event.shiftKey; const isControl = !!event.ctrlKey; const isAlt = !!event.altKey; const isMeta = !!event.metaKey; const isMod = isMac ? isMeta : isControl; if (key === null || key === void 0 || key === "Shift" && isShift || key === "Control" && isControl || key === "Alt" && isAlt || key === "Meta" && isMeta || key === "Mod" && isMod) { activeMouseSelection = true; } if (!activeMouseSelection) { return false; } document.addEventListener( "mouseup", () => { activeMouseSelection = false; const { state } = view; const { doc, selection, tr } = state; const { $anchor, $head } = selection; if ($anchor.sameParent($head)) { return; } const nodeRangeSelection = NodeRangeSelection.create(doc, $anchor.pos, $head.pos, this.options.depth); tr.setSelection(nodeRangeSelection); view.dispatch(tr); }, { once: true } ); return false; } }, // when selecting some text we want to render some decorations // to preview a `NodeRangeSelection` decorations: (state) => { const { selection } = state; const isNodeRange = isNodeRangeSelection(selection); hideTextSelection = false; if (!activeMouseSelection) { if (!isNodeRange) { return null; } hideTextSelection = true; return getNodeRangeDecorations(selection.ranges); } const { $from, $to } = selection; if (!isNodeRange && $from.sameParent($to)) { return null; } const nodeRanges = getSelectionRanges($from, $to, this.options.depth); if (!nodeRanges.length) { return null; } hideTextSelection = true; return getNodeRangeDecorations(nodeRanges); } } }) ]; } }); // src/index.ts var index_default = NodeRange2; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { NodeRange, NodeRangeSelection, getNodeRangeDecorations, getSelectionRanges, isNodeRangeSelection }); //# sourceMappingURL=index.cjs.map