@harbour-enterprises/superdoc 1.6.0-next.1 → 1.6.0-next.3
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-DzppMlfu.es.js → PdfViewer-DpUAoC7K.es.js} +2 -2
- package/dist/chunks/{PdfViewer-Dr4K1JKD.cjs → PdfViewer-Dy4TDA_a.cjs} +2 -2
- package/dist/chunks/{SuperConverter-Cyn9peRO.es.js → SuperConverter-B4dnd1SO.es.js} +1 -1
- package/dist/chunks/{SuperConverter-DpKjVrPl.cjs → SuperConverter-UL6mfKlt.cjs} +1 -1
- package/dist/chunks/{index-C7KECpDt.es.js → index-BB7PV6qA.es.js} +720 -54
- package/dist/chunks/{index-CmZ15rIJ.cjs → index-CM_lykaH.cjs} +4 -4
- package/dist/chunks/{index-DrcLOCfC.es.js → index-DEYAgfRu.es.js} +4 -4
- package/dist/chunks/{index-Cto-XsBE.cjs → index-vSyQ2hF9.cjs} +720 -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 +721 -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-UL6mfKlt.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
|
/**
|
|
@@ -13081,6 +13261,90 @@ const CommentsPlugin = Extension.create({
|
|
|
13081
13261
|
name: "comments",
|
|
13082
13262
|
addCommands() {
|
|
13083
13263
|
return {
|
|
13264
|
+
/**
|
|
13265
|
+
* Add a comment to the current selection
|
|
13266
|
+
* @category Command
|
|
13267
|
+
* @param {string|Object} contentOrOptions - Comment content as a string, or an options object
|
|
13268
|
+
* @param {string} [contentOrOptions.content] - The comment content (text or HTML)
|
|
13269
|
+
* @param {string} [contentOrOptions.author] - Author name (defaults to user from editor config)
|
|
13270
|
+
* @param {string} [contentOrOptions.authorEmail] - Author email (defaults to user from editor config)
|
|
13271
|
+
* @param {string} [contentOrOptions.authorImage] - Author image URL (defaults to user from editor config)
|
|
13272
|
+
* @param {boolean} [contentOrOptions.isInternal=false] - Whether the comment is internal/private
|
|
13273
|
+
* @returns {boolean} True if the comment was added successfully, false otherwise
|
|
13274
|
+
* @example
|
|
13275
|
+
* // Simple usage with just content
|
|
13276
|
+
* editor.commands.addComment('This needs review')
|
|
13277
|
+
*
|
|
13278
|
+
* // With options
|
|
13279
|
+
* editor.commands.addComment({
|
|
13280
|
+
* content: 'Please clarify this section',
|
|
13281
|
+
* author: 'Jane Doe',
|
|
13282
|
+
* isInternal: true
|
|
13283
|
+
* })
|
|
13284
|
+
*
|
|
13285
|
+
* // To get the comment ID, listen to the commentsUpdate event
|
|
13286
|
+
* editor.on('commentsUpdate', (event) => {
|
|
13287
|
+
* if (event.type === 'add') {
|
|
13288
|
+
* console.log('New comment ID:', event.activeCommentId)
|
|
13289
|
+
* }
|
|
13290
|
+
* })
|
|
13291
|
+
*/
|
|
13292
|
+
addComment: (contentOrOptions) => ({ tr, dispatch, editor }) => {
|
|
13293
|
+
const { selection } = tr;
|
|
13294
|
+
const { $from, $to } = selection;
|
|
13295
|
+
if ($from.pos === $to.pos) {
|
|
13296
|
+
console.warn("addComment requires a text selection. Please select text before adding a comment.");
|
|
13297
|
+
return false;
|
|
13298
|
+
}
|
|
13299
|
+
let content, author, authorEmail, authorImage, isInternal;
|
|
13300
|
+
if (typeof contentOrOptions === "string") {
|
|
13301
|
+
content = contentOrOptions;
|
|
13302
|
+
} else if (contentOrOptions && typeof contentOrOptions === "object") {
|
|
13303
|
+
content = contentOrOptions.content;
|
|
13304
|
+
author = contentOrOptions.author;
|
|
13305
|
+
authorEmail = contentOrOptions.authorEmail;
|
|
13306
|
+
authorImage = contentOrOptions.authorImage;
|
|
13307
|
+
isInternal = contentOrOptions.isInternal;
|
|
13308
|
+
}
|
|
13309
|
+
const commentId = uuid.v4();
|
|
13310
|
+
const resolvedInternal = isInternal ?? false;
|
|
13311
|
+
const configUser = editor.options?.user || {};
|
|
13312
|
+
tr.setMeta(CommentsPluginKey, { event: "add" });
|
|
13313
|
+
tr.addMark(
|
|
13314
|
+
$from.pos,
|
|
13315
|
+
$to.pos,
|
|
13316
|
+
editor.schema.marks[CommentMarkName$1].create({
|
|
13317
|
+
commentId,
|
|
13318
|
+
internal: resolvedInternal
|
|
13319
|
+
})
|
|
13320
|
+
);
|
|
13321
|
+
if (dispatch) dispatch(tr);
|
|
13322
|
+
const commentPayload = normalizeCommentEventPayload({
|
|
13323
|
+
conversation: {
|
|
13324
|
+
commentId,
|
|
13325
|
+
isInternal: resolvedInternal,
|
|
13326
|
+
commentText: content,
|
|
13327
|
+
creatorName: author ?? configUser.name,
|
|
13328
|
+
creatorEmail: authorEmail ?? configUser.email,
|
|
13329
|
+
creatorImage: authorImage ?? configUser.image,
|
|
13330
|
+
createdTime: Date.now()
|
|
13331
|
+
},
|
|
13332
|
+
editorOptions: editor.options,
|
|
13333
|
+
fallbackCommentId: commentId,
|
|
13334
|
+
fallbackInternal: resolvedInternal
|
|
13335
|
+
});
|
|
13336
|
+
editor.emit("commentsUpdate", {
|
|
13337
|
+
type: comments_module_events.ADD,
|
|
13338
|
+
comment: commentPayload,
|
|
13339
|
+
activeCommentId: commentId
|
|
13340
|
+
});
|
|
13341
|
+
return true;
|
|
13342
|
+
},
|
|
13343
|
+
/**
|
|
13344
|
+
* @private
|
|
13345
|
+
* Internal command to insert a comment mark at the current selection.
|
|
13346
|
+
* Use `addComment` for the public API.
|
|
13347
|
+
*/
|
|
13084
13348
|
insertComment: (conversation = {}) => ({ tr, dispatch }) => {
|
|
13085
13349
|
const { selection } = tr;
|
|
13086
13350
|
const { $from, $to } = selection;
|
|
@@ -15524,7 +15788,7 @@ const canUseDOM = () => {
|
|
|
15524
15788
|
return false;
|
|
15525
15789
|
}
|
|
15526
15790
|
};
|
|
15527
|
-
const summaryVersion = "1.6.0-next.
|
|
15791
|
+
const summaryVersion = "1.6.0-next.3";
|
|
15528
15792
|
const nodeKeys = ["group", "content", "marks", "inline", "atom", "defining", "code", "tableRole", "summary"];
|
|
15529
15793
|
const markKeys = ["group", "inclusive", "excludes", "spanning", "code"];
|
|
15530
15794
|
function mapAttributes(attrs) {
|
|
@@ -17017,7 +17281,7 @@ class Editor extends EventEmitter {
|
|
|
17017
17281
|
*/
|
|
17018
17282
|
#createExtensionService() {
|
|
17019
17283
|
const allowedExtensions = ["extension", "node", "mark"];
|
|
17020
|
-
const coreExtensions = [Editable, Commands, EditorFocus, Keymap];
|
|
17284
|
+
const coreExtensions = [Editable, Commands, EditorFocus, Keymap, PositionTrackerExtension];
|
|
17021
17285
|
const externalExtensions = this.options.externalExtensions || [];
|
|
17022
17286
|
const allExtensions = [...coreExtensions, ...this.options.extensions].filter((extension) => {
|
|
17023
17287
|
const extensionType = typeof extension?.type === "string" ? extension.type : void 0;
|
|
@@ -18191,7 +18455,7 @@ class Editor extends EventEmitter {
|
|
|
18191
18455
|
* Process collaboration migrations
|
|
18192
18456
|
*/
|
|
18193
18457
|
processCollaborationMigrations() {
|
|
18194
|
-
console.debug("[checkVersionMigrations] Current editor version", "1.6.0-next.
|
|
18458
|
+
console.debug("[checkVersionMigrations] Current editor version", "1.6.0-next.3");
|
|
18195
18459
|
if (!this.options.ydoc) return;
|
|
18196
18460
|
const metaMap = this.options.ydoc.getMap("meta");
|
|
18197
18461
|
let docVersion = metaMap.get("version");
|
|
@@ -75544,14 +75808,317 @@ function getMatchHighlights(state) {
|
|
|
75544
75808
|
let search2 = searchKey.getState(state);
|
|
75545
75809
|
return search2 ? search2.deco : DecorationSet.empty;
|
|
75546
75810
|
}
|
|
75547
|
-
|
|
75548
|
-
|
|
75549
|
-
|
|
75811
|
+
const BLOCK_SEPARATOR = "\n";
|
|
75812
|
+
const ATOM_PLACEHOLDER = "";
|
|
75813
|
+
class SearchIndex {
|
|
75814
|
+
/** @type {string} */
|
|
75815
|
+
text = "";
|
|
75816
|
+
/** @type {Segment[]} */
|
|
75817
|
+
segments = [];
|
|
75818
|
+
/** @type {boolean} */
|
|
75819
|
+
valid = false;
|
|
75820
|
+
/** @type {number} */
|
|
75821
|
+
docSize = 0;
|
|
75822
|
+
/**
|
|
75823
|
+
* Build the search index from a ProseMirror document.
|
|
75824
|
+
* Uses doc.textBetween for the flattened string and walks
|
|
75825
|
+
* the document to build the segment offset map.
|
|
75826
|
+
*
|
|
75827
|
+
* @param {import('prosemirror-model').Node} doc - The ProseMirror document
|
|
75828
|
+
*/
|
|
75829
|
+
build(doc2) {
|
|
75830
|
+
this.text = doc2.textBetween(0, doc2.content.size, BLOCK_SEPARATOR, ATOM_PLACEHOLDER);
|
|
75831
|
+
this.segments = [];
|
|
75832
|
+
this.docSize = doc2.content.size;
|
|
75833
|
+
let offset2 = 0;
|
|
75834
|
+
this.#walkNodeContent(doc2, 0, offset2, (segment) => {
|
|
75835
|
+
this.segments.push(segment);
|
|
75836
|
+
offset2 = segment.offsetEnd;
|
|
75837
|
+
});
|
|
75838
|
+
this.valid = true;
|
|
75839
|
+
}
|
|
75840
|
+
/**
|
|
75841
|
+
* Walk the content of a node to build segments.
|
|
75842
|
+
* This method processes the children of a node, given the position
|
|
75843
|
+
* where the node's content starts.
|
|
75844
|
+
*
|
|
75845
|
+
* @param {import('prosemirror-model').Node} node - Current node
|
|
75846
|
+
* @param {number} contentStart - Document position where this node's content starts
|
|
75847
|
+
* @param {number} offset - Current offset in flattened string
|
|
75848
|
+
* @param {(segment: Segment) => void} addSegment - Callback to add a segment
|
|
75849
|
+
* @returns {number} The new offset after processing this node's content
|
|
75850
|
+
*/
|
|
75851
|
+
#walkNodeContent(node, contentStart, offset2, addSegment) {
|
|
75852
|
+
let currentOffset = offset2;
|
|
75853
|
+
let isFirstChild = true;
|
|
75854
|
+
node.forEach((child, childContentOffset) => {
|
|
75855
|
+
const childDocPos = contentStart + childContentOffset;
|
|
75856
|
+
if (child.isBlock && !isFirstChild) {
|
|
75857
|
+
addSegment({
|
|
75858
|
+
offsetStart: currentOffset,
|
|
75859
|
+
offsetEnd: currentOffset + 1,
|
|
75860
|
+
docFrom: childDocPos,
|
|
75861
|
+
docTo: childDocPos,
|
|
75862
|
+
kind: "blockSep"
|
|
75863
|
+
});
|
|
75864
|
+
currentOffset += 1;
|
|
75865
|
+
}
|
|
75866
|
+
currentOffset = this.#walkNode(child, childDocPos, currentOffset, addSegment);
|
|
75867
|
+
isFirstChild = false;
|
|
75868
|
+
});
|
|
75869
|
+
return currentOffset;
|
|
75870
|
+
}
|
|
75871
|
+
/**
|
|
75872
|
+
* Recursively walk a node and its descendants to build segments.
|
|
75873
|
+
*
|
|
75874
|
+
* @param {import('prosemirror-model').Node} node - Current node
|
|
75875
|
+
* @param {number} docPos - Document position at start of this node
|
|
75876
|
+
* @param {number} offset - Current offset in flattened string
|
|
75877
|
+
* @param {(segment: Segment) => void} addSegment - Callback to add a segment
|
|
75878
|
+
* @returns {number} The new offset after processing this node
|
|
75879
|
+
*/
|
|
75880
|
+
#walkNode(node, docPos, offset2, addSegment) {
|
|
75881
|
+
if (node.isText) {
|
|
75882
|
+
const text = node.text || "";
|
|
75883
|
+
if (text.length > 0) {
|
|
75884
|
+
addSegment({
|
|
75885
|
+
offsetStart: offset2,
|
|
75886
|
+
offsetEnd: offset2 + text.length,
|
|
75887
|
+
docFrom: docPos,
|
|
75888
|
+
docTo: docPos + text.length,
|
|
75889
|
+
kind: "text"
|
|
75890
|
+
});
|
|
75891
|
+
return offset2 + text.length;
|
|
75892
|
+
}
|
|
75893
|
+
return offset2;
|
|
75894
|
+
}
|
|
75895
|
+
if (node.isLeaf) {
|
|
75896
|
+
if (node.type.name === "hard_break") {
|
|
75897
|
+
addSegment({
|
|
75898
|
+
offsetStart: offset2,
|
|
75899
|
+
offsetEnd: offset2 + 1,
|
|
75900
|
+
docFrom: docPos,
|
|
75901
|
+
docTo: docPos + node.nodeSize,
|
|
75902
|
+
kind: "hardBreak"
|
|
75903
|
+
});
|
|
75904
|
+
return offset2 + 1;
|
|
75905
|
+
}
|
|
75906
|
+
addSegment({
|
|
75907
|
+
offsetStart: offset2,
|
|
75908
|
+
offsetEnd: offset2 + 1,
|
|
75909
|
+
docFrom: docPos,
|
|
75910
|
+
docTo: docPos + node.nodeSize,
|
|
75911
|
+
kind: "atom"
|
|
75912
|
+
});
|
|
75913
|
+
return offset2 + 1;
|
|
75914
|
+
}
|
|
75915
|
+
return this.#walkNodeContent(node, docPos + 1, offset2, addSegment);
|
|
75916
|
+
}
|
|
75917
|
+
/**
|
|
75918
|
+
* Mark the index as stale. It will be rebuilt on next search.
|
|
75919
|
+
*/
|
|
75920
|
+
invalidate() {
|
|
75921
|
+
this.valid = false;
|
|
75922
|
+
}
|
|
75923
|
+
/**
|
|
75924
|
+
* Check if the index needs rebuilding for the given document.
|
|
75925
|
+
*
|
|
75926
|
+
* @param {import('prosemirror-model').Node} doc - The document to check against
|
|
75927
|
+
* @returns {boolean} True if index is stale and needs rebuilding
|
|
75928
|
+
*/
|
|
75929
|
+
isStale(doc2) {
|
|
75930
|
+
return !this.valid || doc2.content.size !== this.docSize;
|
|
75931
|
+
}
|
|
75932
|
+
/**
|
|
75933
|
+
* Ensure the index is valid for the given document.
|
|
75934
|
+
* Rebuilds if stale.
|
|
75935
|
+
*
|
|
75936
|
+
* @param {import('prosemirror-model').Node} doc - The document
|
|
75937
|
+
*/
|
|
75938
|
+
ensureValid(doc2) {
|
|
75939
|
+
if (this.isStale(doc2)) {
|
|
75940
|
+
this.build(doc2);
|
|
75941
|
+
}
|
|
75942
|
+
}
|
|
75943
|
+
/**
|
|
75944
|
+
* Convert an offset range in the flattened string to document ranges.
|
|
75945
|
+
* Skips separator/atom segments and returns only text ranges.
|
|
75946
|
+
*
|
|
75947
|
+
* @param {number} start - Start offset in flattened string
|
|
75948
|
+
* @param {number} end - End offset in flattened string
|
|
75949
|
+
* @returns {DocRange[]} Array of document ranges (text segments only)
|
|
75950
|
+
*/
|
|
75951
|
+
offsetRangeToDocRanges(start2, end2) {
|
|
75952
|
+
const ranges = [];
|
|
75953
|
+
for (const segment of this.segments) {
|
|
75954
|
+
if (segment.offsetEnd <= start2) continue;
|
|
75955
|
+
if (segment.offsetStart >= end2) break;
|
|
75956
|
+
if (segment.kind !== "text") continue;
|
|
75957
|
+
const overlapStart = Math.max(start2, segment.offsetStart);
|
|
75958
|
+
const overlapEnd = Math.min(end2, segment.offsetEnd);
|
|
75959
|
+
if (overlapStart < overlapEnd) {
|
|
75960
|
+
const startInSegment = overlapStart - segment.offsetStart;
|
|
75961
|
+
const endInSegment = overlapEnd - segment.offsetStart;
|
|
75962
|
+
ranges.push({
|
|
75963
|
+
from: segment.docFrom + startInSegment,
|
|
75964
|
+
to: segment.docFrom + endInSegment
|
|
75965
|
+
});
|
|
75966
|
+
}
|
|
75967
|
+
}
|
|
75968
|
+
return ranges;
|
|
75969
|
+
}
|
|
75970
|
+
/**
|
|
75971
|
+
* Find the document position for a given offset in the flattened string.
|
|
75972
|
+
*
|
|
75973
|
+
* @param {number} offset - Offset in flattened string
|
|
75974
|
+
* @returns {number|null} Document position, or null if not found
|
|
75975
|
+
*/
|
|
75976
|
+
offsetToDocPos(offset2) {
|
|
75977
|
+
for (const segment of this.segments) {
|
|
75978
|
+
if (offset2 >= segment.offsetStart && offset2 < segment.offsetEnd) {
|
|
75979
|
+
if (segment.kind === "text") {
|
|
75980
|
+
return segment.docFrom + (offset2 - segment.offsetStart);
|
|
75981
|
+
}
|
|
75982
|
+
return segment.docFrom;
|
|
75983
|
+
}
|
|
75984
|
+
}
|
|
75985
|
+
if (this.segments.length > 0 && offset2 === this.segments[this.segments.length - 1].offsetEnd) {
|
|
75986
|
+
const lastSeg = this.segments[this.segments.length - 1];
|
|
75987
|
+
return lastSeg.docTo;
|
|
75988
|
+
}
|
|
75989
|
+
return null;
|
|
75990
|
+
}
|
|
75991
|
+
/**
|
|
75992
|
+
* Escape special regex characters in a string.
|
|
75993
|
+
*
|
|
75994
|
+
* @param {string} str - String to escape
|
|
75995
|
+
* @returns {string} Escaped string safe for use in RegExp
|
|
75996
|
+
*/
|
|
75997
|
+
static escapeRegex(str) {
|
|
75998
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
75999
|
+
}
|
|
76000
|
+
/**
|
|
76001
|
+
* Convert a plain search string to a whitespace-flexible regex pattern.
|
|
76002
|
+
* This allows matching across paragraph boundaries.
|
|
76003
|
+
*
|
|
76004
|
+
* @param {string} searchString - The search string
|
|
76005
|
+
* @returns {string} Regex pattern string
|
|
76006
|
+
*/
|
|
76007
|
+
static toFlexiblePattern(searchString) {
|
|
76008
|
+
const parts = searchString.split(/\s+/).filter((part) => part.length > 0);
|
|
76009
|
+
if (parts.length === 0) return "";
|
|
76010
|
+
return parts.map((part) => SearchIndex.escapeRegex(part)).join("\\s+");
|
|
76011
|
+
}
|
|
76012
|
+
/**
|
|
76013
|
+
* Search the index for matches.
|
|
76014
|
+
*
|
|
76015
|
+
* @param {string | RegExp} pattern - Search pattern (string or regex)
|
|
76016
|
+
* @param {Object} options - Search options
|
|
76017
|
+
* @param {boolean} [options.caseSensitive=false] - Case sensitive search
|
|
76018
|
+
* @param {number} [options.maxMatches=1000] - Maximum number of matches to return
|
|
76019
|
+
* @returns {Array<{start: number, end: number, text: string}>} Array of matches with offsets
|
|
76020
|
+
*/
|
|
76021
|
+
search(pattern, options = {}) {
|
|
76022
|
+
const { caseSensitive = false, maxMatches = 1e3 } = options;
|
|
76023
|
+
const matches = [];
|
|
76024
|
+
let regex;
|
|
76025
|
+
if (pattern instanceof RegExp) {
|
|
76026
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
76027
|
+
regex = new RegExp(pattern.source, flags);
|
|
76028
|
+
} else if (typeof pattern === "string") {
|
|
76029
|
+
if (pattern.length === 0) return matches;
|
|
76030
|
+
const flexiblePattern = SearchIndex.toFlexiblePattern(pattern);
|
|
76031
|
+
if (flexiblePattern.length === 0) return matches;
|
|
76032
|
+
const flags = caseSensitive ? "g" : "gi";
|
|
76033
|
+
regex = new RegExp(flexiblePattern, flags);
|
|
76034
|
+
} else {
|
|
76035
|
+
return matches;
|
|
76036
|
+
}
|
|
76037
|
+
let match;
|
|
76038
|
+
while ((match = regex.exec(this.text)) !== null && matches.length < maxMatches) {
|
|
76039
|
+
matches.push({
|
|
76040
|
+
start: match.index,
|
|
76041
|
+
end: match.index + match[0].length,
|
|
76042
|
+
text: match[0]
|
|
76043
|
+
});
|
|
76044
|
+
if (match[0].length === 0) {
|
|
76045
|
+
regex.lastIndex++;
|
|
76046
|
+
}
|
|
76047
|
+
}
|
|
76048
|
+
return matches;
|
|
75550
76049
|
}
|
|
75551
|
-
const highlight = typeof options?.highlight === "boolean" ? options.highlight : true;
|
|
75552
|
-
return tr.setMeta(searchKey, { query, range, highlight });
|
|
75553
76050
|
}
|
|
76051
|
+
const customSearchHighlightsKey = new superEditor_converter.PluginKey("customSearchHighlights");
|
|
75554
76052
|
const isRegExp = (value) => Object.prototype.toString.call(value) === "[object RegExp]";
|
|
76053
|
+
const resolveInlineTextPosition = (doc2, position, direction) => {
|
|
76054
|
+
const docSize = doc2.content.size;
|
|
76055
|
+
if (!Number.isFinite(position) || position < 0 || position > docSize) {
|
|
76056
|
+
return position;
|
|
76057
|
+
}
|
|
76058
|
+
const step = direction === "forward" ? 1 : -1;
|
|
76059
|
+
let current = position;
|
|
76060
|
+
let iterations = 0;
|
|
76061
|
+
while (iterations < 8) {
|
|
76062
|
+
iterations += 1;
|
|
76063
|
+
const resolved = doc2.resolve(current);
|
|
76064
|
+
const boundaryNode = direction === "forward" ? resolved.nodeAfter : resolved.nodeBefore;
|
|
76065
|
+
if (!boundaryNode) break;
|
|
76066
|
+
if (boundaryNode.isText) break;
|
|
76067
|
+
if (!boundaryNode.isInline || boundaryNode.isAtom || boundaryNode.content.size === 0) break;
|
|
76068
|
+
const next = current + step;
|
|
76069
|
+
if (next < 0 || next > docSize) break;
|
|
76070
|
+
current = next;
|
|
76071
|
+
const adjacent = doc2.resolve(current);
|
|
76072
|
+
const checkNode = direction === "forward" ? adjacent.nodeAfter : adjacent.nodeBefore;
|
|
76073
|
+
if (checkNode && checkNode.isText) break;
|
|
76074
|
+
}
|
|
76075
|
+
return current;
|
|
76076
|
+
};
|
|
76077
|
+
const resolveSearchRange = ({ doc: doc2, from: from3, to, expectedText, highlights }) => {
|
|
76078
|
+
const docSize = doc2.content.size;
|
|
76079
|
+
let resolvedFrom = Math.max(0, Math.min(from3, docSize));
|
|
76080
|
+
let resolvedTo = Math.max(0, Math.min(to, docSize));
|
|
76081
|
+
if (highlights) {
|
|
76082
|
+
const windowStart = Math.max(0, resolvedFrom - 4);
|
|
76083
|
+
const windowEnd = Math.min(docSize, resolvedTo + 4);
|
|
76084
|
+
const candidates = highlights.find(windowStart, windowEnd);
|
|
76085
|
+
if (candidates.length > 0) {
|
|
76086
|
+
let chosen = candidates[0];
|
|
76087
|
+
if (expectedText) {
|
|
76088
|
+
const matching = candidates.filter(
|
|
76089
|
+
(decoration) => doc2.textBetween(decoration.from, decoration.to) === expectedText
|
|
76090
|
+
);
|
|
76091
|
+
if (matching.length > 0) {
|
|
76092
|
+
chosen = matching[0];
|
|
76093
|
+
}
|
|
76094
|
+
}
|
|
76095
|
+
resolvedFrom = chosen.from;
|
|
76096
|
+
resolvedTo = chosen.to;
|
|
76097
|
+
}
|
|
76098
|
+
}
|
|
76099
|
+
const normalizedFrom = resolveInlineTextPosition(doc2, resolvedFrom, "forward");
|
|
76100
|
+
const normalizedTo = resolveInlineTextPosition(doc2, resolvedTo, "backward");
|
|
76101
|
+
if (Number.isFinite(normalizedFrom) && Number.isFinite(normalizedTo) && normalizedFrom <= normalizedTo) {
|
|
76102
|
+
resolvedFrom = normalizedFrom;
|
|
76103
|
+
resolvedTo = normalizedTo;
|
|
76104
|
+
}
|
|
76105
|
+
return { from: resolvedFrom, to: resolvedTo };
|
|
76106
|
+
};
|
|
76107
|
+
const getPositionTracker = (editor) => {
|
|
76108
|
+
if (!editor) return null;
|
|
76109
|
+
if (editor.positionTracker) return editor.positionTracker;
|
|
76110
|
+
const storageTracker = editor.storage?.positionTracker?.tracker;
|
|
76111
|
+
if (storageTracker) {
|
|
76112
|
+
editor.positionTracker = storageTracker;
|
|
76113
|
+
return storageTracker;
|
|
76114
|
+
}
|
|
76115
|
+
const tracker = new PositionTracker(editor);
|
|
76116
|
+
if (editor.storage?.positionTracker) {
|
|
76117
|
+
editor.storage.positionTracker.tracker = tracker;
|
|
76118
|
+
}
|
|
76119
|
+
editor.positionTracker = tracker;
|
|
76120
|
+
return tracker;
|
|
76121
|
+
};
|
|
75555
76122
|
const Search = Extension.create({
|
|
75556
76123
|
// @ts-expect-error - Storage type mismatch will be fixed in TS migration
|
|
75557
76124
|
addStorage() {
|
|
@@ -75560,29 +76127,58 @@ const Search = Extension.create({
|
|
|
75560
76127
|
* @private
|
|
75561
76128
|
* @type {SearchMatch[]|null}
|
|
75562
76129
|
*/
|
|
75563
|
-
searchResults: []
|
|
76130
|
+
searchResults: [],
|
|
76131
|
+
/**
|
|
76132
|
+
* @private
|
|
76133
|
+
* @type {boolean}
|
|
76134
|
+
* Whether to apply CSS highlight classes to matches
|
|
76135
|
+
*/
|
|
76136
|
+
highlightEnabled: true,
|
|
76137
|
+
/**
|
|
76138
|
+
* @private
|
|
76139
|
+
* @type {SearchIndex}
|
|
76140
|
+
* Lazily-built search index for cross-paragraph matching
|
|
76141
|
+
*/
|
|
76142
|
+
searchIndex: new SearchIndex()
|
|
75564
76143
|
};
|
|
75565
76144
|
},
|
|
75566
76145
|
addPmPlugins() {
|
|
75567
76146
|
const editor = this.editor;
|
|
75568
76147
|
const storage = this.storage;
|
|
76148
|
+
const searchIndexInvalidatorPlugin = new superEditor_converter.Plugin({
|
|
76149
|
+
key: new superEditor_converter.PluginKey("searchIndexInvalidator"),
|
|
76150
|
+
appendTransaction(transactions, oldState, newState) {
|
|
76151
|
+
const docChanged = transactions.some((tr) => tr.docChanged);
|
|
76152
|
+
if (docChanged && storage?.searchIndex) {
|
|
76153
|
+
storage.searchIndex.invalidate();
|
|
76154
|
+
}
|
|
76155
|
+
return null;
|
|
76156
|
+
}
|
|
76157
|
+
});
|
|
75569
76158
|
const searchHighlightWithIdPlugin = new superEditor_converter.Plugin({
|
|
75570
|
-
key:
|
|
76159
|
+
key: customSearchHighlightsKey,
|
|
75571
76160
|
props: {
|
|
75572
76161
|
decorations(state) {
|
|
75573
76162
|
if (!editor) return null;
|
|
75574
76163
|
const matches = storage?.searchResults;
|
|
75575
76164
|
if (!matches?.length) return null;
|
|
75576
|
-
const
|
|
75577
|
-
|
|
75578
|
-
|
|
75579
|
-
}
|
|
75580
|
-
|
|
76165
|
+
const highlightEnabled = storage?.highlightEnabled !== false;
|
|
76166
|
+
const decorations = [];
|
|
76167
|
+
for (const match of matches) {
|
|
76168
|
+
const attrs = highlightEnabled ? { id: `search-match-${match.id}`, class: "ProseMirror-search-match" } : { id: `search-match-${match.id}` };
|
|
76169
|
+
if (match.ranges && match.ranges.length > 0) {
|
|
76170
|
+
for (const range of match.ranges) {
|
|
76171
|
+
decorations.push(Decoration.inline(range.from, range.to, attrs));
|
|
76172
|
+
}
|
|
76173
|
+
} else {
|
|
76174
|
+
decorations.push(Decoration.inline(match.from, match.to, attrs));
|
|
76175
|
+
}
|
|
76176
|
+
}
|
|
75581
76177
|
return DecorationSet.create(state.doc, decorations);
|
|
75582
76178
|
}
|
|
75583
76179
|
}
|
|
75584
76180
|
});
|
|
75585
|
-
return [search(), searchHighlightWithIdPlugin];
|
|
76181
|
+
return [search(), searchIndexInvalidatorPlugin, searchHighlightWithIdPlugin];
|
|
75586
76182
|
},
|
|
75587
76183
|
addCommands() {
|
|
75588
76184
|
return {
|
|
@@ -75596,21 +76192,51 @@ const Search = Extension.create({
|
|
|
75596
76192
|
goToFirstMatch: () => (
|
|
75597
76193
|
/** @returns {boolean} */
|
|
75598
76194
|
({ state, editor, dispatch }) => {
|
|
76195
|
+
const searchResults = this.storage?.searchResults;
|
|
76196
|
+
if (Array.isArray(searchResults) && searchResults.length > 0) {
|
|
76197
|
+
const firstMatch = searchResults[0];
|
|
76198
|
+
const from3 = firstMatch.ranges?.[0]?.from ?? firstMatch.from;
|
|
76199
|
+
const to = firstMatch.ranges?.[0]?.to ?? firstMatch.to;
|
|
76200
|
+
if (typeof from3 !== "number" || typeof to !== "number") {
|
|
76201
|
+
return false;
|
|
76202
|
+
}
|
|
76203
|
+
editor.view.focus();
|
|
76204
|
+
const tr2 = state.tr.setSelection(superEditor_converter.TextSelection.create(state.doc, from3, to)).scrollIntoView();
|
|
76205
|
+
if (dispatch) dispatch(tr2);
|
|
76206
|
+
const presentationEditor2 = editor.presentationEditor;
|
|
76207
|
+
if (presentationEditor2 && typeof presentationEditor2.scrollToPosition === "function") {
|
|
76208
|
+
const didScroll = presentationEditor2.scrollToPosition(from3, { block: "center" });
|
|
76209
|
+
if (didScroll) return true;
|
|
76210
|
+
}
|
|
76211
|
+
try {
|
|
76212
|
+
const domPos = editor.view.domAtPos(from3);
|
|
76213
|
+
if (domPos?.node?.scrollIntoView) {
|
|
76214
|
+
domPos.node.scrollIntoView(true);
|
|
76215
|
+
}
|
|
76216
|
+
} catch {
|
|
76217
|
+
}
|
|
76218
|
+
return true;
|
|
76219
|
+
}
|
|
75599
76220
|
const highlights = getMatchHighlights(state);
|
|
75600
76221
|
if (!highlights) return false;
|
|
75601
76222
|
const decorations = highlights.find();
|
|
75602
76223
|
if (!decorations?.length) return false;
|
|
75603
|
-
const
|
|
76224
|
+
const firstDeco = decorations[0];
|
|
75604
76225
|
editor.view.focus();
|
|
75605
|
-
const tr = state.tr.setSelection(superEditor_converter.TextSelection.create(state.doc,
|
|
76226
|
+
const tr = state.tr.setSelection(superEditor_converter.TextSelection.create(state.doc, firstDeco.from, firstDeco.to)).scrollIntoView();
|
|
75606
76227
|
if (dispatch) dispatch(tr);
|
|
75607
76228
|
const presentationEditor = editor.presentationEditor;
|
|
75608
76229
|
if (presentationEditor && typeof presentationEditor.scrollToPosition === "function") {
|
|
75609
|
-
const didScroll = presentationEditor.scrollToPosition(
|
|
76230
|
+
const didScroll = presentationEditor.scrollToPosition(firstDeco.from, { block: "center" });
|
|
75610
76231
|
if (didScroll) return true;
|
|
75611
76232
|
}
|
|
75612
|
-
|
|
75613
|
-
|
|
76233
|
+
try {
|
|
76234
|
+
const domPos = editor.view.domAtPos(firstDeco.from);
|
|
76235
|
+
if (domPos?.node?.scrollIntoView) {
|
|
76236
|
+
domPos.node.scrollIntoView(true);
|
|
76237
|
+
}
|
|
76238
|
+
} catch {
|
|
76239
|
+
}
|
|
75614
76240
|
return true;
|
|
75615
76241
|
}
|
|
75616
76242
|
),
|
|
@@ -75628,53 +76254,57 @@ const Search = Extension.create({
|
|
|
75628
76254
|
*
|
|
75629
76255
|
* // Search without visual highlighting
|
|
75630
76256
|
* const silentMatches = editor.commands.search('test', { highlight: false })
|
|
75631
|
-
*
|
|
76257
|
+
*
|
|
76258
|
+
* // Cross-paragraph search (works by default for plain strings)
|
|
76259
|
+
* const crossParagraphMatches = editor.commands.search('end of paragraph start of next')
|
|
76260
|
+
* @note Returns array of SearchMatch objects with positions and IDs.
|
|
76261
|
+
* Plain string searches are whitespace-flexible and match across paragraphs.
|
|
76262
|
+
* Regex searches match exactly as specified.
|
|
75632
76263
|
*/
|
|
75633
76264
|
search: (patternInput, options = {}) => (
|
|
75634
76265
|
/** @returns {SearchMatch[]} */
|
|
75635
|
-
({ state, dispatch }) => {
|
|
76266
|
+
({ state, dispatch, editor }) => {
|
|
75636
76267
|
if (options != null && (typeof options !== "object" || Array.isArray(options))) {
|
|
75637
76268
|
throw new TypeError("Search options must be an object");
|
|
75638
76269
|
}
|
|
75639
76270
|
const highlight = typeof options?.highlight === "boolean" ? options.highlight : true;
|
|
75640
|
-
|
|
76271
|
+
const maxMatches = typeof options?.maxMatches === "number" ? options.maxMatches : 1e3;
|
|
75641
76272
|
let caseSensitive = false;
|
|
75642
|
-
let
|
|
75643
|
-
const wholeWord = false;
|
|
76273
|
+
let searchPattern = patternInput;
|
|
75644
76274
|
if (isRegExp(patternInput)) {
|
|
75645
|
-
|
|
75646
|
-
|
|
75647
|
-
patternInput
|
|
75648
|
-
);
|
|
75649
|
-
regexp = true;
|
|
75650
|
-
pattern = regexPattern.source;
|
|
75651
|
-
caseSensitive = !regexPattern.flags.includes("i");
|
|
76275
|
+
caseSensitive = !patternInput.flags.includes("i");
|
|
76276
|
+
searchPattern = patternInput;
|
|
75652
76277
|
} else if (typeof patternInput === "string" && /^\/(.+)\/([gimsuy]*)$/.test(patternInput)) {
|
|
75653
76278
|
const [, body, flags] = patternInput.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
75654
|
-
regexp = true;
|
|
75655
|
-
pattern = body;
|
|
75656
76279
|
caseSensitive = !flags.includes("i");
|
|
76280
|
+
searchPattern = new RegExp(body, flags.includes("g") ? flags : flags + "g");
|
|
75657
76281
|
} else {
|
|
75658
|
-
|
|
76282
|
+
searchPattern = String(patternInput);
|
|
75659
76283
|
}
|
|
75660
|
-
const
|
|
75661
|
-
|
|
76284
|
+
const searchIndex = this.storage.searchIndex;
|
|
76285
|
+
searchIndex.ensureValid(state.doc);
|
|
76286
|
+
const indexMatches = searchIndex.search(searchPattern, {
|
|
75662
76287
|
caseSensitive,
|
|
75663
|
-
|
|
75664
|
-
wholeWord
|
|
76288
|
+
maxMatches
|
|
75665
76289
|
});
|
|
75666
|
-
const
|
|
75667
|
-
|
|
75668
|
-
|
|
75669
|
-
|
|
75670
|
-
|
|
75671
|
-
|
|
75672
|
-
|
|
75673
|
-
|
|
75674
|
-
|
|
75675
|
-
|
|
75676
|
-
|
|
76290
|
+
const resultMatches = [];
|
|
76291
|
+
for (const indexMatch of indexMatches) {
|
|
76292
|
+
const ranges = searchIndex.offsetRangeToDocRanges(indexMatch.start, indexMatch.end);
|
|
76293
|
+
if (ranges.length === 0) continue;
|
|
76294
|
+
const matchTexts = ranges.map((r2) => state.doc.textBetween(r2.from, r2.to));
|
|
76295
|
+
const combinedText = matchTexts.join("");
|
|
76296
|
+
const match = {
|
|
76297
|
+
from: ranges[0].from,
|
|
76298
|
+
to: ranges[ranges.length - 1].to,
|
|
76299
|
+
text: combinedText,
|
|
76300
|
+
id: uuid.v4(),
|
|
76301
|
+
ranges,
|
|
76302
|
+
trackerIds: []
|
|
76303
|
+
};
|
|
76304
|
+
resultMatches.push(match);
|
|
76305
|
+
}
|
|
75677
76306
|
this.storage.searchResults = resultMatches;
|
|
76307
|
+
this.storage.highlightEnabled = highlight;
|
|
75678
76308
|
return resultMatches;
|
|
75679
76309
|
}
|
|
75680
76310
|
),
|
|
@@ -75685,12 +76315,48 @@ const Search = Extension.create({
|
|
|
75685
76315
|
* @example
|
|
75686
76316
|
* const searchResults = editor.commands.search('test string')
|
|
75687
76317
|
* editor.commands.goToSearchResult(searchResults[3])
|
|
75688
|
-
* @note Scrolls to match and selects it
|
|
76318
|
+
* @note Scrolls to match and selects it. For multi-range matches (cross-paragraph),
|
|
76319
|
+
* selects the first range and scrolls to it.
|
|
75689
76320
|
*/
|
|
75690
76321
|
goToSearchResult: (match) => (
|
|
75691
76322
|
/** @returns {boolean} */
|
|
75692
76323
|
({ state, dispatch, editor }) => {
|
|
75693
|
-
const
|
|
76324
|
+
const positionTracker = getPositionTracker(editor);
|
|
76325
|
+
const doc2 = state.doc;
|
|
76326
|
+
const highlights = getMatchHighlights(state);
|
|
76327
|
+
let from3, to;
|
|
76328
|
+
if (match?.ranges && match.ranges.length > 0 && match?.trackerIds && match.trackerIds.length > 0) {
|
|
76329
|
+
if (positionTracker?.resolve && match.trackerIds[0]) {
|
|
76330
|
+
const resolved = positionTracker.resolve(match.trackerIds[0]);
|
|
76331
|
+
if (resolved) {
|
|
76332
|
+
from3 = resolved.from;
|
|
76333
|
+
to = resolved.to;
|
|
76334
|
+
}
|
|
76335
|
+
}
|
|
76336
|
+
if (from3 === void 0) {
|
|
76337
|
+
from3 = match.ranges[0].from;
|
|
76338
|
+
to = match.ranges[0].to;
|
|
76339
|
+
}
|
|
76340
|
+
} else {
|
|
76341
|
+
from3 = match.from;
|
|
76342
|
+
to = match.to;
|
|
76343
|
+
if (positionTracker?.resolve && match?.id) {
|
|
76344
|
+
const resolved = positionTracker.resolve(match.id);
|
|
76345
|
+
if (resolved) {
|
|
76346
|
+
from3 = resolved.from;
|
|
76347
|
+
to = resolved.to;
|
|
76348
|
+
}
|
|
76349
|
+
}
|
|
76350
|
+
}
|
|
76351
|
+
const normalized = resolveSearchRange({
|
|
76352
|
+
doc: doc2,
|
|
76353
|
+
from: from3,
|
|
76354
|
+
to,
|
|
76355
|
+
expectedText: match?.text ?? null,
|
|
76356
|
+
highlights
|
|
76357
|
+
});
|
|
76358
|
+
from3 = normalized.from;
|
|
76359
|
+
to = normalized.to;
|
|
75694
76360
|
editor.view.focus();
|
|
75695
76361
|
const tr = state.tr.setSelection(superEditor_converter.TextSelection.create(state.doc, from3, to)).scrollIntoView();
|
|
75696
76362
|
if (dispatch) dispatch(tr);
|