@harbour-enterprises/superdoc 1.6.0-next.1 → 1.6.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{PdfViewer-Dr4K1JKD.cjs → PdfViewer-D_YseVgi.cjs} +2 -2
- package/dist/chunks/{PdfViewer-DzppMlfu.es.js → PdfViewer-Djd4zd7C.es.js} +2 -2
- package/dist/chunks/{SuperConverter-Cyn9peRO.es.js → SuperConverter-BAUfsE-s.es.js} +1 -1
- package/dist/chunks/{SuperConverter-DpKjVrPl.cjs → SuperConverter-DrhNM5Cd.cjs} +1 -1
- package/dist/chunks/{index-CmZ15rIJ.cjs → index-CyiEB9wO.cjs} +4 -4
- package/dist/chunks/{index-Cto-XsBE.cjs → index-D-7HfDnq.cjs} +636 -54
- package/dist/chunks/{index-DrcLOCfC.es.js → index-ajHpG3f-.es.js} +4 -4
- package/dist/chunks/{index-C7KECpDt.es.js → index-fbohTSaQ.es.js} +636 -54
- package/dist/super-editor/converter.cjs +1 -1
- package/dist/super-editor/converter.es.js +1 -1
- package/dist/super-editor.cjs +2 -2
- package/dist/super-editor.es.js +3 -3
- package/dist/superdoc/src/dev/components/sidebar/SidebarSearch.vue.d.ts +5 -0
- package/dist/superdoc/src/dev/components/sidebar/SidebarSearch.vue.d.ts.map +1 -0
- package/dist/superdoc.cjs +3 -3
- package/dist/superdoc.es.js +3 -3
- package/dist/superdoc.umd.js +637 -55
- package/dist/superdoc.umd.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const jszip = require("./jszip-C8_CqJxM.cjs");
|
|
3
3
|
const helpers$1 = require("./helpers-nOdwpmwb.cjs");
|
|
4
|
-
const superEditor_converter = require("./SuperConverter-
|
|
4
|
+
const superEditor_converter = require("./SuperConverter-DrhNM5Cd.cjs");
|
|
5
5
|
const vue = require("./vue-De9wkgLl.cjs");
|
|
6
6
|
require("./jszip.min-BPh2MMAa.cjs");
|
|
7
7
|
const eventemitter3 = require("./eventemitter3-BQuRcMPI.cjs");
|
|
@@ -9387,6 +9387,158 @@ class Schema {
|
|
|
9387
9387
|
return Object.fromEntries(markEntries);
|
|
9388
9388
|
}
|
|
9389
9389
|
}
|
|
9390
|
+
const positionTrackerKey = new superEditor_converter.PluginKey("positionTracker");
|
|
9391
|
+
function createPositionTrackerPlugin() {
|
|
9392
|
+
return new superEditor_converter.Plugin({
|
|
9393
|
+
key: positionTrackerKey,
|
|
9394
|
+
state: {
|
|
9395
|
+
init() {
|
|
9396
|
+
return {
|
|
9397
|
+
decorations: DecorationSet.empty,
|
|
9398
|
+
generation: 0
|
|
9399
|
+
};
|
|
9400
|
+
},
|
|
9401
|
+
apply(tr, state) {
|
|
9402
|
+
let { decorations, generation } = state;
|
|
9403
|
+
const meta = tr.getMeta(positionTrackerKey);
|
|
9404
|
+
if (meta?.action === "add") {
|
|
9405
|
+
decorations = decorations.add(tr.doc, meta.decorations);
|
|
9406
|
+
} else if (meta?.action === "remove") {
|
|
9407
|
+
const toRemove = decorations.find().filter((decoration) => meta.ids.includes(decoration.spec.id));
|
|
9408
|
+
decorations = decorations.remove(toRemove);
|
|
9409
|
+
} else if (meta?.action === "removeByType") {
|
|
9410
|
+
const toRemove = decorations.find().filter((decoration) => decoration.spec.type === meta.type);
|
|
9411
|
+
decorations = decorations.remove(toRemove);
|
|
9412
|
+
}
|
|
9413
|
+
if (tr.docChanged) {
|
|
9414
|
+
decorations = decorations.map(tr.mapping, tr.doc);
|
|
9415
|
+
generation += 1;
|
|
9416
|
+
}
|
|
9417
|
+
return { decorations, generation };
|
|
9418
|
+
}
|
|
9419
|
+
},
|
|
9420
|
+
props: {
|
|
9421
|
+
decorations() {
|
|
9422
|
+
return DecorationSet.empty;
|
|
9423
|
+
}
|
|
9424
|
+
}
|
|
9425
|
+
});
|
|
9426
|
+
}
|
|
9427
|
+
class PositionTracker {
|
|
9428
|
+
#editor;
|
|
9429
|
+
constructor(editor) {
|
|
9430
|
+
this.#editor = editor;
|
|
9431
|
+
}
|
|
9432
|
+
#getState() {
|
|
9433
|
+
if (!this.#editor?.state) return null;
|
|
9434
|
+
return positionTrackerKey.getState(this.#editor.state) ?? null;
|
|
9435
|
+
}
|
|
9436
|
+
track(from3, to, spec) {
|
|
9437
|
+
const id = uuid.v4();
|
|
9438
|
+
if (!this.#editor?.state) return id;
|
|
9439
|
+
const fullSpec = { kind: "range", ...spec, id };
|
|
9440
|
+
const deco = Decoration.inline(from3, to, {}, fullSpec);
|
|
9441
|
+
const tr = this.#editor.state.tr.setMeta(positionTrackerKey, {
|
|
9442
|
+
action: "add",
|
|
9443
|
+
decorations: [deco]
|
|
9444
|
+
}).setMeta("addToHistory", false);
|
|
9445
|
+
this.#editor.dispatch(tr);
|
|
9446
|
+
return id;
|
|
9447
|
+
}
|
|
9448
|
+
trackMany(ranges) {
|
|
9449
|
+
if (!this.#editor?.state) {
|
|
9450
|
+
return ranges.map(() => uuid.v4());
|
|
9451
|
+
}
|
|
9452
|
+
const ids = [];
|
|
9453
|
+
const decorations = [];
|
|
9454
|
+
for (const { from: from3, to, spec } of ranges) {
|
|
9455
|
+
const id = uuid.v4();
|
|
9456
|
+
ids.push(id);
|
|
9457
|
+
const fullSpec = { kind: "range", ...spec, id };
|
|
9458
|
+
decorations.push(Decoration.inline(from3, to, {}, fullSpec));
|
|
9459
|
+
}
|
|
9460
|
+
const tr = this.#editor.state.tr.setMeta(positionTrackerKey, {
|
|
9461
|
+
action: "add",
|
|
9462
|
+
decorations
|
|
9463
|
+
}).setMeta("addToHistory", false);
|
|
9464
|
+
this.#editor.dispatch(tr);
|
|
9465
|
+
return ids;
|
|
9466
|
+
}
|
|
9467
|
+
untrack(id) {
|
|
9468
|
+
if (!this.#editor?.state) return;
|
|
9469
|
+
const tr = this.#editor.state.tr.setMeta(positionTrackerKey, {
|
|
9470
|
+
action: "remove",
|
|
9471
|
+
ids: [id]
|
|
9472
|
+
}).setMeta("addToHistory", false);
|
|
9473
|
+
this.#editor.dispatch(tr);
|
|
9474
|
+
}
|
|
9475
|
+
untrackMany(ids) {
|
|
9476
|
+
if (!this.#editor?.state || ids.length === 0) return;
|
|
9477
|
+
const tr = this.#editor.state.tr.setMeta(positionTrackerKey, {
|
|
9478
|
+
action: "remove",
|
|
9479
|
+
ids
|
|
9480
|
+
}).setMeta("addToHistory", false);
|
|
9481
|
+
this.#editor.dispatch(tr);
|
|
9482
|
+
}
|
|
9483
|
+
untrackByType(type) {
|
|
9484
|
+
if (!this.#editor?.state) return;
|
|
9485
|
+
const tr = this.#editor.state.tr.setMeta(positionTrackerKey, {
|
|
9486
|
+
action: "removeByType",
|
|
9487
|
+
type
|
|
9488
|
+
}).setMeta("addToHistory", false);
|
|
9489
|
+
this.#editor.dispatch(tr);
|
|
9490
|
+
}
|
|
9491
|
+
resolve(id) {
|
|
9492
|
+
const state = this.#getState();
|
|
9493
|
+
if (!state) return null;
|
|
9494
|
+
const found = state.decorations.find().find((decoration) => decoration.spec.id === id);
|
|
9495
|
+
if (!found) return null;
|
|
9496
|
+
const spec = found.spec;
|
|
9497
|
+
return {
|
|
9498
|
+
id: spec.id,
|
|
9499
|
+
from: found.from,
|
|
9500
|
+
to: found.to,
|
|
9501
|
+
spec
|
|
9502
|
+
};
|
|
9503
|
+
}
|
|
9504
|
+
resolveMany(ids) {
|
|
9505
|
+
const result = /* @__PURE__ */ new Map();
|
|
9506
|
+
for (const id of ids) {
|
|
9507
|
+
result.set(id, null);
|
|
9508
|
+
}
|
|
9509
|
+
const state = this.#getState();
|
|
9510
|
+
if (!state || ids.length === 0) return result;
|
|
9511
|
+
const idSet = new Set(ids);
|
|
9512
|
+
for (const decoration of state.decorations.find()) {
|
|
9513
|
+
const spec = decoration.spec;
|
|
9514
|
+
if (idSet.has(spec.id)) {
|
|
9515
|
+
result.set(spec.id, {
|
|
9516
|
+
id: spec.id,
|
|
9517
|
+
from: decoration.from,
|
|
9518
|
+
to: decoration.to,
|
|
9519
|
+
spec
|
|
9520
|
+
});
|
|
9521
|
+
}
|
|
9522
|
+
}
|
|
9523
|
+
return result;
|
|
9524
|
+
}
|
|
9525
|
+
findByType(type) {
|
|
9526
|
+
const state = this.#getState();
|
|
9527
|
+
if (!state) return [];
|
|
9528
|
+
return state.decorations.find().filter((decoration) => decoration.spec.type === type).map((decoration) => {
|
|
9529
|
+
const spec = decoration.spec;
|
|
9530
|
+
return {
|
|
9531
|
+
id: spec.id,
|
|
9532
|
+
from: decoration.from,
|
|
9533
|
+
to: decoration.to,
|
|
9534
|
+
spec
|
|
9535
|
+
};
|
|
9536
|
+
});
|
|
9537
|
+
}
|
|
9538
|
+
get generation() {
|
|
9539
|
+
return this.#getState()?.generation ?? 0;
|
|
9540
|
+
}
|
|
9541
|
+
}
|
|
9390
9542
|
class OxmlNode extends Node$1 {
|
|
9391
9543
|
constructor(config) {
|
|
9392
9544
|
super(config);
|
|
@@ -11747,6 +11899,34 @@ const EditorFocus = Extension.create({
|
|
|
11747
11899
|
return [editorFocusPlugin];
|
|
11748
11900
|
}
|
|
11749
11901
|
});
|
|
11902
|
+
const PositionTrackerExtension = Extension.create({
|
|
11903
|
+
name: "positionTracker",
|
|
11904
|
+
addStorage() {
|
|
11905
|
+
return {
|
|
11906
|
+
tracker: null
|
|
11907
|
+
};
|
|
11908
|
+
},
|
|
11909
|
+
addPmPlugins() {
|
|
11910
|
+
return [createPositionTrackerPlugin()];
|
|
11911
|
+
},
|
|
11912
|
+
onCreate() {
|
|
11913
|
+
const existing = this.editor?.positionTracker ?? this.storage.tracker;
|
|
11914
|
+
if (existing) {
|
|
11915
|
+
this.storage.tracker = existing;
|
|
11916
|
+
this.editor.positionTracker = existing;
|
|
11917
|
+
return;
|
|
11918
|
+
}
|
|
11919
|
+
const tracker = new PositionTracker(this.editor);
|
|
11920
|
+
this.storage.tracker = tracker;
|
|
11921
|
+
this.editor.positionTracker = tracker;
|
|
11922
|
+
},
|
|
11923
|
+
onDestroy() {
|
|
11924
|
+
if (this.editor?.positionTracker === this.storage.tracker) {
|
|
11925
|
+
this.editor.positionTracker = null;
|
|
11926
|
+
}
|
|
11927
|
+
this.storage.tracker = null;
|
|
11928
|
+
}
|
|
11929
|
+
});
|
|
11750
11930
|
class EventEmitter {
|
|
11751
11931
|
#events = /* @__PURE__ */ new Map();
|
|
11752
11932
|
/**
|
|
@@ -15524,7 +15704,7 @@ const canUseDOM = () => {
|
|
|
15524
15704
|
return false;
|
|
15525
15705
|
}
|
|
15526
15706
|
};
|
|
15527
|
-
const summaryVersion = "1.6.0-next.
|
|
15707
|
+
const summaryVersion = "1.6.0-next.2";
|
|
15528
15708
|
const nodeKeys = ["group", "content", "marks", "inline", "atom", "defining", "code", "tableRole", "summary"];
|
|
15529
15709
|
const markKeys = ["group", "inclusive", "excludes", "spanning", "code"];
|
|
15530
15710
|
function mapAttributes(attrs) {
|
|
@@ -17017,7 +17197,7 @@ class Editor extends EventEmitter {
|
|
|
17017
17197
|
*/
|
|
17018
17198
|
#createExtensionService() {
|
|
17019
17199
|
const allowedExtensions = ["extension", "node", "mark"];
|
|
17020
|
-
const coreExtensions = [Editable, Commands, EditorFocus, Keymap];
|
|
17200
|
+
const coreExtensions = [Editable, Commands, EditorFocus, Keymap, PositionTrackerExtension];
|
|
17021
17201
|
const externalExtensions = this.options.externalExtensions || [];
|
|
17022
17202
|
const allExtensions = [...coreExtensions, ...this.options.extensions].filter((extension) => {
|
|
17023
17203
|
const extensionType = typeof extension?.type === "string" ? extension.type : void 0;
|
|
@@ -18191,7 +18371,7 @@ class Editor extends EventEmitter {
|
|
|
18191
18371
|
* Process collaboration migrations
|
|
18192
18372
|
*/
|
|
18193
18373
|
processCollaborationMigrations() {
|
|
18194
|
-
console.debug("[checkVersionMigrations] Current editor version", "1.6.0-next.
|
|
18374
|
+
console.debug("[checkVersionMigrations] Current editor version", "1.6.0-next.2");
|
|
18195
18375
|
if (!this.options.ydoc) return;
|
|
18196
18376
|
const metaMap = this.options.ydoc.getMap("meta");
|
|
18197
18377
|
let docVersion = metaMap.get("version");
|
|
@@ -75544,14 +75724,317 @@ function getMatchHighlights(state) {
|
|
|
75544
75724
|
let search2 = searchKey.getState(state);
|
|
75545
75725
|
return search2 ? search2.deco : DecorationSet.empty;
|
|
75546
75726
|
}
|
|
75547
|
-
|
|
75548
|
-
|
|
75549
|
-
|
|
75727
|
+
const BLOCK_SEPARATOR = "\n";
|
|
75728
|
+
const ATOM_PLACEHOLDER = "";
|
|
75729
|
+
class SearchIndex {
|
|
75730
|
+
/** @type {string} */
|
|
75731
|
+
text = "";
|
|
75732
|
+
/** @type {Segment[]} */
|
|
75733
|
+
segments = [];
|
|
75734
|
+
/** @type {boolean} */
|
|
75735
|
+
valid = false;
|
|
75736
|
+
/** @type {number} */
|
|
75737
|
+
docSize = 0;
|
|
75738
|
+
/**
|
|
75739
|
+
* Build the search index from a ProseMirror document.
|
|
75740
|
+
* Uses doc.textBetween for the flattened string and walks
|
|
75741
|
+
* the document to build the segment offset map.
|
|
75742
|
+
*
|
|
75743
|
+
* @param {import('prosemirror-model').Node} doc - The ProseMirror document
|
|
75744
|
+
*/
|
|
75745
|
+
build(doc2) {
|
|
75746
|
+
this.text = doc2.textBetween(0, doc2.content.size, BLOCK_SEPARATOR, ATOM_PLACEHOLDER);
|
|
75747
|
+
this.segments = [];
|
|
75748
|
+
this.docSize = doc2.content.size;
|
|
75749
|
+
let offset2 = 0;
|
|
75750
|
+
this.#walkNodeContent(doc2, 0, offset2, (segment) => {
|
|
75751
|
+
this.segments.push(segment);
|
|
75752
|
+
offset2 = segment.offsetEnd;
|
|
75753
|
+
});
|
|
75754
|
+
this.valid = true;
|
|
75755
|
+
}
|
|
75756
|
+
/**
|
|
75757
|
+
* Walk the content of a node to build segments.
|
|
75758
|
+
* This method processes the children of a node, given the position
|
|
75759
|
+
* where the node's content starts.
|
|
75760
|
+
*
|
|
75761
|
+
* @param {import('prosemirror-model').Node} node - Current node
|
|
75762
|
+
* @param {number} contentStart - Document position where this node's content starts
|
|
75763
|
+
* @param {number} offset - Current offset in flattened string
|
|
75764
|
+
* @param {(segment: Segment) => void} addSegment - Callback to add a segment
|
|
75765
|
+
* @returns {number} The new offset after processing this node's content
|
|
75766
|
+
*/
|
|
75767
|
+
#walkNodeContent(node, contentStart, offset2, addSegment) {
|
|
75768
|
+
let currentOffset = offset2;
|
|
75769
|
+
let isFirstChild = true;
|
|
75770
|
+
node.forEach((child, childContentOffset) => {
|
|
75771
|
+
const childDocPos = contentStart + childContentOffset;
|
|
75772
|
+
if (child.isBlock && !isFirstChild) {
|
|
75773
|
+
addSegment({
|
|
75774
|
+
offsetStart: currentOffset,
|
|
75775
|
+
offsetEnd: currentOffset + 1,
|
|
75776
|
+
docFrom: childDocPos,
|
|
75777
|
+
docTo: childDocPos,
|
|
75778
|
+
kind: "blockSep"
|
|
75779
|
+
});
|
|
75780
|
+
currentOffset += 1;
|
|
75781
|
+
}
|
|
75782
|
+
currentOffset = this.#walkNode(child, childDocPos, currentOffset, addSegment);
|
|
75783
|
+
isFirstChild = false;
|
|
75784
|
+
});
|
|
75785
|
+
return currentOffset;
|
|
75786
|
+
}
|
|
75787
|
+
/**
|
|
75788
|
+
* Recursively walk a node and its descendants to build segments.
|
|
75789
|
+
*
|
|
75790
|
+
* @param {import('prosemirror-model').Node} node - Current node
|
|
75791
|
+
* @param {number} docPos - Document position at start of this node
|
|
75792
|
+
* @param {number} offset - Current offset in flattened string
|
|
75793
|
+
* @param {(segment: Segment) => void} addSegment - Callback to add a segment
|
|
75794
|
+
* @returns {number} The new offset after processing this node
|
|
75795
|
+
*/
|
|
75796
|
+
#walkNode(node, docPos, offset2, addSegment) {
|
|
75797
|
+
if (node.isText) {
|
|
75798
|
+
const text = node.text || "";
|
|
75799
|
+
if (text.length > 0) {
|
|
75800
|
+
addSegment({
|
|
75801
|
+
offsetStart: offset2,
|
|
75802
|
+
offsetEnd: offset2 + text.length,
|
|
75803
|
+
docFrom: docPos,
|
|
75804
|
+
docTo: docPos + text.length,
|
|
75805
|
+
kind: "text"
|
|
75806
|
+
});
|
|
75807
|
+
return offset2 + text.length;
|
|
75808
|
+
}
|
|
75809
|
+
return offset2;
|
|
75810
|
+
}
|
|
75811
|
+
if (node.isLeaf) {
|
|
75812
|
+
if (node.type.name === "hard_break") {
|
|
75813
|
+
addSegment({
|
|
75814
|
+
offsetStart: offset2,
|
|
75815
|
+
offsetEnd: offset2 + 1,
|
|
75816
|
+
docFrom: docPos,
|
|
75817
|
+
docTo: docPos + node.nodeSize,
|
|
75818
|
+
kind: "hardBreak"
|
|
75819
|
+
});
|
|
75820
|
+
return offset2 + 1;
|
|
75821
|
+
}
|
|
75822
|
+
addSegment({
|
|
75823
|
+
offsetStart: offset2,
|
|
75824
|
+
offsetEnd: offset2 + 1,
|
|
75825
|
+
docFrom: docPos,
|
|
75826
|
+
docTo: docPos + node.nodeSize,
|
|
75827
|
+
kind: "atom"
|
|
75828
|
+
});
|
|
75829
|
+
return offset2 + 1;
|
|
75830
|
+
}
|
|
75831
|
+
return this.#walkNodeContent(node, docPos + 1, offset2, addSegment);
|
|
75832
|
+
}
|
|
75833
|
+
/**
|
|
75834
|
+
* Mark the index as stale. It will be rebuilt on next search.
|
|
75835
|
+
*/
|
|
75836
|
+
invalidate() {
|
|
75837
|
+
this.valid = false;
|
|
75838
|
+
}
|
|
75839
|
+
/**
|
|
75840
|
+
* Check if the index needs rebuilding for the given document.
|
|
75841
|
+
*
|
|
75842
|
+
* @param {import('prosemirror-model').Node} doc - The document to check against
|
|
75843
|
+
* @returns {boolean} True if index is stale and needs rebuilding
|
|
75844
|
+
*/
|
|
75845
|
+
isStale(doc2) {
|
|
75846
|
+
return !this.valid || doc2.content.size !== this.docSize;
|
|
75847
|
+
}
|
|
75848
|
+
/**
|
|
75849
|
+
* Ensure the index is valid for the given document.
|
|
75850
|
+
* Rebuilds if stale.
|
|
75851
|
+
*
|
|
75852
|
+
* @param {import('prosemirror-model').Node} doc - The document
|
|
75853
|
+
*/
|
|
75854
|
+
ensureValid(doc2) {
|
|
75855
|
+
if (this.isStale(doc2)) {
|
|
75856
|
+
this.build(doc2);
|
|
75857
|
+
}
|
|
75858
|
+
}
|
|
75859
|
+
/**
|
|
75860
|
+
* Convert an offset range in the flattened string to document ranges.
|
|
75861
|
+
* Skips separator/atom segments and returns only text ranges.
|
|
75862
|
+
*
|
|
75863
|
+
* @param {number} start - Start offset in flattened string
|
|
75864
|
+
* @param {number} end - End offset in flattened string
|
|
75865
|
+
* @returns {DocRange[]} Array of document ranges (text segments only)
|
|
75866
|
+
*/
|
|
75867
|
+
offsetRangeToDocRanges(start2, end2) {
|
|
75868
|
+
const ranges = [];
|
|
75869
|
+
for (const segment of this.segments) {
|
|
75870
|
+
if (segment.offsetEnd <= start2) continue;
|
|
75871
|
+
if (segment.offsetStart >= end2) break;
|
|
75872
|
+
if (segment.kind !== "text") continue;
|
|
75873
|
+
const overlapStart = Math.max(start2, segment.offsetStart);
|
|
75874
|
+
const overlapEnd = Math.min(end2, segment.offsetEnd);
|
|
75875
|
+
if (overlapStart < overlapEnd) {
|
|
75876
|
+
const startInSegment = overlapStart - segment.offsetStart;
|
|
75877
|
+
const endInSegment = overlapEnd - segment.offsetStart;
|
|
75878
|
+
ranges.push({
|
|
75879
|
+
from: segment.docFrom + startInSegment,
|
|
75880
|
+
to: segment.docFrom + endInSegment
|
|
75881
|
+
});
|
|
75882
|
+
}
|
|
75883
|
+
}
|
|
75884
|
+
return ranges;
|
|
75885
|
+
}
|
|
75886
|
+
/**
|
|
75887
|
+
* Find the document position for a given offset in the flattened string.
|
|
75888
|
+
*
|
|
75889
|
+
* @param {number} offset - Offset in flattened string
|
|
75890
|
+
* @returns {number|null} Document position, or null if not found
|
|
75891
|
+
*/
|
|
75892
|
+
offsetToDocPos(offset2) {
|
|
75893
|
+
for (const segment of this.segments) {
|
|
75894
|
+
if (offset2 >= segment.offsetStart && offset2 < segment.offsetEnd) {
|
|
75895
|
+
if (segment.kind === "text") {
|
|
75896
|
+
return segment.docFrom + (offset2 - segment.offsetStart);
|
|
75897
|
+
}
|
|
75898
|
+
return segment.docFrom;
|
|
75899
|
+
}
|
|
75900
|
+
}
|
|
75901
|
+
if (this.segments.length > 0 && offset2 === this.segments[this.segments.length - 1].offsetEnd) {
|
|
75902
|
+
const lastSeg = this.segments[this.segments.length - 1];
|
|
75903
|
+
return lastSeg.docTo;
|
|
75904
|
+
}
|
|
75905
|
+
return null;
|
|
75906
|
+
}
|
|
75907
|
+
/**
|
|
75908
|
+
* Escape special regex characters in a string.
|
|
75909
|
+
*
|
|
75910
|
+
* @param {string} str - String to escape
|
|
75911
|
+
* @returns {string} Escaped string safe for use in RegExp
|
|
75912
|
+
*/
|
|
75913
|
+
static escapeRegex(str) {
|
|
75914
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
75915
|
+
}
|
|
75916
|
+
/**
|
|
75917
|
+
* Convert a plain search string to a whitespace-flexible regex pattern.
|
|
75918
|
+
* This allows matching across paragraph boundaries.
|
|
75919
|
+
*
|
|
75920
|
+
* @param {string} searchString - The search string
|
|
75921
|
+
* @returns {string} Regex pattern string
|
|
75922
|
+
*/
|
|
75923
|
+
static toFlexiblePattern(searchString) {
|
|
75924
|
+
const parts = searchString.split(/\s+/).filter((part) => part.length > 0);
|
|
75925
|
+
if (parts.length === 0) return "";
|
|
75926
|
+
return parts.map((part) => SearchIndex.escapeRegex(part)).join("\\s+");
|
|
75927
|
+
}
|
|
75928
|
+
/**
|
|
75929
|
+
* Search the index for matches.
|
|
75930
|
+
*
|
|
75931
|
+
* @param {string | RegExp} pattern - Search pattern (string or regex)
|
|
75932
|
+
* @param {Object} options - Search options
|
|
75933
|
+
* @param {boolean} [options.caseSensitive=false] - Case sensitive search
|
|
75934
|
+
* @param {number} [options.maxMatches=1000] - Maximum number of matches to return
|
|
75935
|
+
* @returns {Array<{start: number, end: number, text: string}>} Array of matches with offsets
|
|
75936
|
+
*/
|
|
75937
|
+
search(pattern, options = {}) {
|
|
75938
|
+
const { caseSensitive = false, maxMatches = 1e3 } = options;
|
|
75939
|
+
const matches = [];
|
|
75940
|
+
let regex;
|
|
75941
|
+
if (pattern instanceof RegExp) {
|
|
75942
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
75943
|
+
regex = new RegExp(pattern.source, flags);
|
|
75944
|
+
} else if (typeof pattern === "string") {
|
|
75945
|
+
if (pattern.length === 0) return matches;
|
|
75946
|
+
const flexiblePattern = SearchIndex.toFlexiblePattern(pattern);
|
|
75947
|
+
if (flexiblePattern.length === 0) return matches;
|
|
75948
|
+
const flags = caseSensitive ? "g" : "gi";
|
|
75949
|
+
regex = new RegExp(flexiblePattern, flags);
|
|
75950
|
+
} else {
|
|
75951
|
+
return matches;
|
|
75952
|
+
}
|
|
75953
|
+
let match;
|
|
75954
|
+
while ((match = regex.exec(this.text)) !== null && matches.length < maxMatches) {
|
|
75955
|
+
matches.push({
|
|
75956
|
+
start: match.index,
|
|
75957
|
+
end: match.index + match[0].length,
|
|
75958
|
+
text: match[0]
|
|
75959
|
+
});
|
|
75960
|
+
if (match[0].length === 0) {
|
|
75961
|
+
regex.lastIndex++;
|
|
75962
|
+
}
|
|
75963
|
+
}
|
|
75964
|
+
return matches;
|
|
75550
75965
|
}
|
|
75551
|
-
const highlight = typeof options?.highlight === "boolean" ? options.highlight : true;
|
|
75552
|
-
return tr.setMeta(searchKey, { query, range, highlight });
|
|
75553
75966
|
}
|
|
75967
|
+
const customSearchHighlightsKey = new superEditor_converter.PluginKey("customSearchHighlights");
|
|
75554
75968
|
const isRegExp = (value) => Object.prototype.toString.call(value) === "[object RegExp]";
|
|
75969
|
+
const resolveInlineTextPosition = (doc2, position, direction) => {
|
|
75970
|
+
const docSize = doc2.content.size;
|
|
75971
|
+
if (!Number.isFinite(position) || position < 0 || position > docSize) {
|
|
75972
|
+
return position;
|
|
75973
|
+
}
|
|
75974
|
+
const step = direction === "forward" ? 1 : -1;
|
|
75975
|
+
let current = position;
|
|
75976
|
+
let iterations = 0;
|
|
75977
|
+
while (iterations < 8) {
|
|
75978
|
+
iterations += 1;
|
|
75979
|
+
const resolved = doc2.resolve(current);
|
|
75980
|
+
const boundaryNode = direction === "forward" ? resolved.nodeAfter : resolved.nodeBefore;
|
|
75981
|
+
if (!boundaryNode) break;
|
|
75982
|
+
if (boundaryNode.isText) break;
|
|
75983
|
+
if (!boundaryNode.isInline || boundaryNode.isAtom || boundaryNode.content.size === 0) break;
|
|
75984
|
+
const next = current + step;
|
|
75985
|
+
if (next < 0 || next > docSize) break;
|
|
75986
|
+
current = next;
|
|
75987
|
+
const adjacent = doc2.resolve(current);
|
|
75988
|
+
const checkNode = direction === "forward" ? adjacent.nodeAfter : adjacent.nodeBefore;
|
|
75989
|
+
if (checkNode && checkNode.isText) break;
|
|
75990
|
+
}
|
|
75991
|
+
return current;
|
|
75992
|
+
};
|
|
75993
|
+
const resolveSearchRange = ({ doc: doc2, from: from3, to, expectedText, highlights }) => {
|
|
75994
|
+
const docSize = doc2.content.size;
|
|
75995
|
+
let resolvedFrom = Math.max(0, Math.min(from3, docSize));
|
|
75996
|
+
let resolvedTo = Math.max(0, Math.min(to, docSize));
|
|
75997
|
+
if (highlights) {
|
|
75998
|
+
const windowStart = Math.max(0, resolvedFrom - 4);
|
|
75999
|
+
const windowEnd = Math.min(docSize, resolvedTo + 4);
|
|
76000
|
+
const candidates = highlights.find(windowStart, windowEnd);
|
|
76001
|
+
if (candidates.length > 0) {
|
|
76002
|
+
let chosen = candidates[0];
|
|
76003
|
+
if (expectedText) {
|
|
76004
|
+
const matching = candidates.filter(
|
|
76005
|
+
(decoration) => doc2.textBetween(decoration.from, decoration.to) === expectedText
|
|
76006
|
+
);
|
|
76007
|
+
if (matching.length > 0) {
|
|
76008
|
+
chosen = matching[0];
|
|
76009
|
+
}
|
|
76010
|
+
}
|
|
76011
|
+
resolvedFrom = chosen.from;
|
|
76012
|
+
resolvedTo = chosen.to;
|
|
76013
|
+
}
|
|
76014
|
+
}
|
|
76015
|
+
const normalizedFrom = resolveInlineTextPosition(doc2, resolvedFrom, "forward");
|
|
76016
|
+
const normalizedTo = resolveInlineTextPosition(doc2, resolvedTo, "backward");
|
|
76017
|
+
if (Number.isFinite(normalizedFrom) && Number.isFinite(normalizedTo) && normalizedFrom <= normalizedTo) {
|
|
76018
|
+
resolvedFrom = normalizedFrom;
|
|
76019
|
+
resolvedTo = normalizedTo;
|
|
76020
|
+
}
|
|
76021
|
+
return { from: resolvedFrom, to: resolvedTo };
|
|
76022
|
+
};
|
|
76023
|
+
const getPositionTracker = (editor) => {
|
|
76024
|
+
if (!editor) return null;
|
|
76025
|
+
if (editor.positionTracker) return editor.positionTracker;
|
|
76026
|
+
const storageTracker = editor.storage?.positionTracker?.tracker;
|
|
76027
|
+
if (storageTracker) {
|
|
76028
|
+
editor.positionTracker = storageTracker;
|
|
76029
|
+
return storageTracker;
|
|
76030
|
+
}
|
|
76031
|
+
const tracker = new PositionTracker(editor);
|
|
76032
|
+
if (editor.storage?.positionTracker) {
|
|
76033
|
+
editor.storage.positionTracker.tracker = tracker;
|
|
76034
|
+
}
|
|
76035
|
+
editor.positionTracker = tracker;
|
|
76036
|
+
return tracker;
|
|
76037
|
+
};
|
|
75555
76038
|
const Search = Extension.create({
|
|
75556
76039
|
// @ts-expect-error - Storage type mismatch will be fixed in TS migration
|
|
75557
76040
|
addStorage() {
|
|
@@ -75560,29 +76043,58 @@ const Search = Extension.create({
|
|
|
75560
76043
|
* @private
|
|
75561
76044
|
* @type {SearchMatch[]|null}
|
|
75562
76045
|
*/
|
|
75563
|
-
searchResults: []
|
|
76046
|
+
searchResults: [],
|
|
76047
|
+
/**
|
|
76048
|
+
* @private
|
|
76049
|
+
* @type {boolean}
|
|
76050
|
+
* Whether to apply CSS highlight classes to matches
|
|
76051
|
+
*/
|
|
76052
|
+
highlightEnabled: true,
|
|
76053
|
+
/**
|
|
76054
|
+
* @private
|
|
76055
|
+
* @type {SearchIndex}
|
|
76056
|
+
* Lazily-built search index for cross-paragraph matching
|
|
76057
|
+
*/
|
|
76058
|
+
searchIndex: new SearchIndex()
|
|
75564
76059
|
};
|
|
75565
76060
|
},
|
|
75566
76061
|
addPmPlugins() {
|
|
75567
76062
|
const editor = this.editor;
|
|
75568
76063
|
const storage = this.storage;
|
|
76064
|
+
const searchIndexInvalidatorPlugin = new superEditor_converter.Plugin({
|
|
76065
|
+
key: new superEditor_converter.PluginKey("searchIndexInvalidator"),
|
|
76066
|
+
appendTransaction(transactions, oldState, newState) {
|
|
76067
|
+
const docChanged = transactions.some((tr) => tr.docChanged);
|
|
76068
|
+
if (docChanged && storage?.searchIndex) {
|
|
76069
|
+
storage.searchIndex.invalidate();
|
|
76070
|
+
}
|
|
76071
|
+
return null;
|
|
76072
|
+
}
|
|
76073
|
+
});
|
|
75569
76074
|
const searchHighlightWithIdPlugin = new superEditor_converter.Plugin({
|
|
75570
|
-
key:
|
|
76075
|
+
key: customSearchHighlightsKey,
|
|
75571
76076
|
props: {
|
|
75572
76077
|
decorations(state) {
|
|
75573
76078
|
if (!editor) return null;
|
|
75574
76079
|
const matches = storage?.searchResults;
|
|
75575
76080
|
if (!matches?.length) return null;
|
|
75576
|
-
const
|
|
75577
|
-
|
|
75578
|
-
|
|
75579
|
-
}
|
|
75580
|
-
|
|
76081
|
+
const highlightEnabled = storage?.highlightEnabled !== false;
|
|
76082
|
+
const decorations = [];
|
|
76083
|
+
for (const match of matches) {
|
|
76084
|
+
const attrs = highlightEnabled ? { id: `search-match-${match.id}`, class: "ProseMirror-search-match" } : { id: `search-match-${match.id}` };
|
|
76085
|
+
if (match.ranges && match.ranges.length > 0) {
|
|
76086
|
+
for (const range of match.ranges) {
|
|
76087
|
+
decorations.push(Decoration.inline(range.from, range.to, attrs));
|
|
76088
|
+
}
|
|
76089
|
+
} else {
|
|
76090
|
+
decorations.push(Decoration.inline(match.from, match.to, attrs));
|
|
76091
|
+
}
|
|
76092
|
+
}
|
|
75581
76093
|
return DecorationSet.create(state.doc, decorations);
|
|
75582
76094
|
}
|
|
75583
76095
|
}
|
|
75584
76096
|
});
|
|
75585
|
-
return [search(), searchHighlightWithIdPlugin];
|
|
76097
|
+
return [search(), searchIndexInvalidatorPlugin, searchHighlightWithIdPlugin];
|
|
75586
76098
|
},
|
|
75587
76099
|
addCommands() {
|
|
75588
76100
|
return {
|
|
@@ -75596,21 +76108,51 @@ const Search = Extension.create({
|
|
|
75596
76108
|
goToFirstMatch: () => (
|
|
75597
76109
|
/** @returns {boolean} */
|
|
75598
76110
|
({ state, editor, dispatch }) => {
|
|
76111
|
+
const searchResults = this.storage?.searchResults;
|
|
76112
|
+
if (Array.isArray(searchResults) && searchResults.length > 0) {
|
|
76113
|
+
const firstMatch = searchResults[0];
|
|
76114
|
+
const from3 = firstMatch.ranges?.[0]?.from ?? firstMatch.from;
|
|
76115
|
+
const to = firstMatch.ranges?.[0]?.to ?? firstMatch.to;
|
|
76116
|
+
if (typeof from3 !== "number" || typeof to !== "number") {
|
|
76117
|
+
return false;
|
|
76118
|
+
}
|
|
76119
|
+
editor.view.focus();
|
|
76120
|
+
const tr2 = state.tr.setSelection(superEditor_converter.TextSelection.create(state.doc, from3, to)).scrollIntoView();
|
|
76121
|
+
if (dispatch) dispatch(tr2);
|
|
76122
|
+
const presentationEditor2 = editor.presentationEditor;
|
|
76123
|
+
if (presentationEditor2 && typeof presentationEditor2.scrollToPosition === "function") {
|
|
76124
|
+
const didScroll = presentationEditor2.scrollToPosition(from3, { block: "center" });
|
|
76125
|
+
if (didScroll) return true;
|
|
76126
|
+
}
|
|
76127
|
+
try {
|
|
76128
|
+
const domPos = editor.view.domAtPos(from3);
|
|
76129
|
+
if (domPos?.node?.scrollIntoView) {
|
|
76130
|
+
domPos.node.scrollIntoView(true);
|
|
76131
|
+
}
|
|
76132
|
+
} catch {
|
|
76133
|
+
}
|
|
76134
|
+
return true;
|
|
76135
|
+
}
|
|
75599
76136
|
const highlights = getMatchHighlights(state);
|
|
75600
76137
|
if (!highlights) return false;
|
|
75601
76138
|
const decorations = highlights.find();
|
|
75602
76139
|
if (!decorations?.length) return false;
|
|
75603
|
-
const
|
|
76140
|
+
const firstDeco = decorations[0];
|
|
75604
76141
|
editor.view.focus();
|
|
75605
|
-
const tr = state.tr.setSelection(superEditor_converter.TextSelection.create(state.doc,
|
|
76142
|
+
const tr = state.tr.setSelection(superEditor_converter.TextSelection.create(state.doc, firstDeco.from, firstDeco.to)).scrollIntoView();
|
|
75606
76143
|
if (dispatch) dispatch(tr);
|
|
75607
76144
|
const presentationEditor = editor.presentationEditor;
|
|
75608
76145
|
if (presentationEditor && typeof presentationEditor.scrollToPosition === "function") {
|
|
75609
|
-
const didScroll = presentationEditor.scrollToPosition(
|
|
76146
|
+
const didScroll = presentationEditor.scrollToPosition(firstDeco.from, { block: "center" });
|
|
75610
76147
|
if (didScroll) return true;
|
|
75611
76148
|
}
|
|
75612
|
-
|
|
75613
|
-
|
|
76149
|
+
try {
|
|
76150
|
+
const domPos = editor.view.domAtPos(firstDeco.from);
|
|
76151
|
+
if (domPos?.node?.scrollIntoView) {
|
|
76152
|
+
domPos.node.scrollIntoView(true);
|
|
76153
|
+
}
|
|
76154
|
+
} catch {
|
|
76155
|
+
}
|
|
75614
76156
|
return true;
|
|
75615
76157
|
}
|
|
75616
76158
|
),
|
|
@@ -75628,53 +76170,57 @@ const Search = Extension.create({
|
|
|
75628
76170
|
*
|
|
75629
76171
|
* // Search without visual highlighting
|
|
75630
76172
|
* const silentMatches = editor.commands.search('test', { highlight: false })
|
|
75631
|
-
*
|
|
76173
|
+
*
|
|
76174
|
+
* // Cross-paragraph search (works by default for plain strings)
|
|
76175
|
+
* const crossParagraphMatches = editor.commands.search('end of paragraph start of next')
|
|
76176
|
+
* @note Returns array of SearchMatch objects with positions and IDs.
|
|
76177
|
+
* Plain string searches are whitespace-flexible and match across paragraphs.
|
|
76178
|
+
* Regex searches match exactly as specified.
|
|
75632
76179
|
*/
|
|
75633
76180
|
search: (patternInput, options = {}) => (
|
|
75634
76181
|
/** @returns {SearchMatch[]} */
|
|
75635
|
-
({ state, dispatch }) => {
|
|
76182
|
+
({ state, dispatch, editor }) => {
|
|
75636
76183
|
if (options != null && (typeof options !== "object" || Array.isArray(options))) {
|
|
75637
76184
|
throw new TypeError("Search options must be an object");
|
|
75638
76185
|
}
|
|
75639
76186
|
const highlight = typeof options?.highlight === "boolean" ? options.highlight : true;
|
|
75640
|
-
|
|
76187
|
+
const maxMatches = typeof options?.maxMatches === "number" ? options.maxMatches : 1e3;
|
|
75641
76188
|
let caseSensitive = false;
|
|
75642
|
-
let
|
|
75643
|
-
const wholeWord = false;
|
|
76189
|
+
let searchPattern = patternInput;
|
|
75644
76190
|
if (isRegExp(patternInput)) {
|
|
75645
|
-
|
|
75646
|
-
|
|
75647
|
-
patternInput
|
|
75648
|
-
);
|
|
75649
|
-
regexp = true;
|
|
75650
|
-
pattern = regexPattern.source;
|
|
75651
|
-
caseSensitive = !regexPattern.flags.includes("i");
|
|
76191
|
+
caseSensitive = !patternInput.flags.includes("i");
|
|
76192
|
+
searchPattern = patternInput;
|
|
75652
76193
|
} else if (typeof patternInput === "string" && /^\/(.+)\/([gimsuy]*)$/.test(patternInput)) {
|
|
75653
76194
|
const [, body, flags] = patternInput.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
75654
|
-
regexp = true;
|
|
75655
|
-
pattern = body;
|
|
75656
76195
|
caseSensitive = !flags.includes("i");
|
|
76196
|
+
searchPattern = new RegExp(body, flags.includes("g") ? flags : flags + "g");
|
|
75657
76197
|
} else {
|
|
75658
|
-
|
|
76198
|
+
searchPattern = String(patternInput);
|
|
75659
76199
|
}
|
|
75660
|
-
const
|
|
75661
|
-
|
|
76200
|
+
const searchIndex = this.storage.searchIndex;
|
|
76201
|
+
searchIndex.ensureValid(state.doc);
|
|
76202
|
+
const indexMatches = searchIndex.search(searchPattern, {
|
|
75662
76203
|
caseSensitive,
|
|
75663
|
-
|
|
75664
|
-
wholeWord
|
|
76204
|
+
maxMatches
|
|
75665
76205
|
});
|
|
75666
|
-
const
|
|
75667
|
-
|
|
75668
|
-
|
|
75669
|
-
|
|
75670
|
-
|
|
75671
|
-
|
|
75672
|
-
|
|
75673
|
-
|
|
75674
|
-
|
|
75675
|
-
|
|
75676
|
-
|
|
76206
|
+
const resultMatches = [];
|
|
76207
|
+
for (const indexMatch of indexMatches) {
|
|
76208
|
+
const ranges = searchIndex.offsetRangeToDocRanges(indexMatch.start, indexMatch.end);
|
|
76209
|
+
if (ranges.length === 0) continue;
|
|
76210
|
+
const matchTexts = ranges.map((r2) => state.doc.textBetween(r2.from, r2.to));
|
|
76211
|
+
const combinedText = matchTexts.join("");
|
|
76212
|
+
const match = {
|
|
76213
|
+
from: ranges[0].from,
|
|
76214
|
+
to: ranges[ranges.length - 1].to,
|
|
76215
|
+
text: combinedText,
|
|
76216
|
+
id: uuid.v4(),
|
|
76217
|
+
ranges,
|
|
76218
|
+
trackerIds: []
|
|
76219
|
+
};
|
|
76220
|
+
resultMatches.push(match);
|
|
76221
|
+
}
|
|
75677
76222
|
this.storage.searchResults = resultMatches;
|
|
76223
|
+
this.storage.highlightEnabled = highlight;
|
|
75678
76224
|
return resultMatches;
|
|
75679
76225
|
}
|
|
75680
76226
|
),
|
|
@@ -75685,12 +76231,48 @@ const Search = Extension.create({
|
|
|
75685
76231
|
* @example
|
|
75686
76232
|
* const searchResults = editor.commands.search('test string')
|
|
75687
76233
|
* editor.commands.goToSearchResult(searchResults[3])
|
|
75688
|
-
* @note Scrolls to match and selects it
|
|
76234
|
+
* @note Scrolls to match and selects it. For multi-range matches (cross-paragraph),
|
|
76235
|
+
* selects the first range and scrolls to it.
|
|
75689
76236
|
*/
|
|
75690
76237
|
goToSearchResult: (match) => (
|
|
75691
76238
|
/** @returns {boolean} */
|
|
75692
76239
|
({ state, dispatch, editor }) => {
|
|
75693
|
-
const
|
|
76240
|
+
const positionTracker = getPositionTracker(editor);
|
|
76241
|
+
const doc2 = state.doc;
|
|
76242
|
+
const highlights = getMatchHighlights(state);
|
|
76243
|
+
let from3, to;
|
|
76244
|
+
if (match?.ranges && match.ranges.length > 0 && match?.trackerIds && match.trackerIds.length > 0) {
|
|
76245
|
+
if (positionTracker?.resolve && match.trackerIds[0]) {
|
|
76246
|
+
const resolved = positionTracker.resolve(match.trackerIds[0]);
|
|
76247
|
+
if (resolved) {
|
|
76248
|
+
from3 = resolved.from;
|
|
76249
|
+
to = resolved.to;
|
|
76250
|
+
}
|
|
76251
|
+
}
|
|
76252
|
+
if (from3 === void 0) {
|
|
76253
|
+
from3 = match.ranges[0].from;
|
|
76254
|
+
to = match.ranges[0].to;
|
|
76255
|
+
}
|
|
76256
|
+
} else {
|
|
76257
|
+
from3 = match.from;
|
|
76258
|
+
to = match.to;
|
|
76259
|
+
if (positionTracker?.resolve && match?.id) {
|
|
76260
|
+
const resolved = positionTracker.resolve(match.id);
|
|
76261
|
+
if (resolved) {
|
|
76262
|
+
from3 = resolved.from;
|
|
76263
|
+
to = resolved.to;
|
|
76264
|
+
}
|
|
76265
|
+
}
|
|
76266
|
+
}
|
|
76267
|
+
const normalized = resolveSearchRange({
|
|
76268
|
+
doc: doc2,
|
|
76269
|
+
from: from3,
|
|
76270
|
+
to,
|
|
76271
|
+
expectedText: match?.text ?? null,
|
|
76272
|
+
highlights
|
|
76273
|
+
});
|
|
76274
|
+
from3 = normalized.from;
|
|
76275
|
+
to = normalized.to;
|
|
75694
76276
|
editor.view.focus();
|
|
75695
76277
|
const tr = state.tr.setSelection(superEditor_converter.TextSelection.create(state.doc, from3, to)).scrollIntoView();
|
|
75696
76278
|
if (dispatch) dispatch(tr);
|