@tiptap/extension-find-and-replace 3.30.2 → 3.30.4
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/index.cjs +699 -831
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +177 -169
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +177 -169
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +686 -796
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,59 +1,44 @@
|
|
|
1
|
-
// src/find-and-replace.ts
|
|
2
1
|
import { Extension } from "@tiptap/core";
|
|
3
|
-
import { TextSelection } from "@tiptap/pm/state";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
3
|
+
import { RE2JS } from "re2js";
|
|
4
|
+
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
5
|
+
//#region src/state/debounced-search-state.ts
|
|
6
|
+
const debouncedSearchState = /* @__PURE__ */ new WeakMap();
|
|
7
7
|
function getDebouncedSearchState(editor) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
debouncedSearchState.set(editor, nextState);
|
|
17
|
-
return nextState;
|
|
8
|
+
const existingState = debouncedSearchState.get(editor);
|
|
9
|
+
if (existingState) return existingState;
|
|
10
|
+
const nextState = {
|
|
11
|
+
timeout: null,
|
|
12
|
+
pendingTerm: null
|
|
13
|
+
};
|
|
14
|
+
debouncedSearchState.set(editor, nextState);
|
|
15
|
+
return nextState;
|
|
18
16
|
}
|
|
19
17
|
function clearDebouncedSearchState(editor) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"style[data-tiptap-extension-find-and-replace-style]"
|
|
41
|
-
);
|
|
42
|
-
if (existing !== null) {
|
|
43
|
-
return existing;
|
|
44
|
-
}
|
|
45
|
-
const styleNode = document.createElement("style");
|
|
46
|
-
if (nonce) {
|
|
47
|
-
styleNode.setAttribute("nonce", nonce);
|
|
48
|
-
}
|
|
49
|
-
styleNode.setAttribute("data-tiptap-extension-find-and-replace-style", "");
|
|
50
|
-
styleNode.textContent = style2;
|
|
51
|
-
document.getElementsByTagName("head")[0].appendChild(styleNode);
|
|
52
|
-
return styleNode;
|
|
18
|
+
const state = debouncedSearchState.get(editor);
|
|
19
|
+
if (!state) return;
|
|
20
|
+
if (state.timeout !== null) clearTimeout(state.timeout);
|
|
21
|
+
debouncedSearchState.delete(editor);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/constants/constants.ts
|
|
25
|
+
const resultClass = "find-and-replace-result";
|
|
26
|
+
const currentResultClass = "find-and-replace-result-current";
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/style/create-style-tag.ts
|
|
29
|
+
const createStyleTag = (style, nonce) => {
|
|
30
|
+
const existing = document.querySelector("style[data-tiptap-extension-find-and-replace-style]");
|
|
31
|
+
if (existing !== null) return existing;
|
|
32
|
+
const styleNode = document.createElement("style");
|
|
33
|
+
if (nonce) styleNode.setAttribute("nonce", nonce);
|
|
34
|
+
styleNode.setAttribute("data-tiptap-extension-find-and-replace-style", "");
|
|
35
|
+
styleNode.textContent = style;
|
|
36
|
+
document.getElementsByTagName("head")[0].appendChild(styleNode);
|
|
37
|
+
return styleNode;
|
|
53
38
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/style/style.ts
|
|
41
|
+
const style = `.find-and-replace-result {
|
|
57
42
|
background-color: rgb(255 225 0 / 0.4);
|
|
58
43
|
}
|
|
59
44
|
|
|
@@ -61,855 +46,760 @@ var style = `.find-and-replace-result {
|
|
|
61
46
|
background-color: rgb(255 165 0 / 0.55);
|
|
62
47
|
}
|
|
63
48
|
`;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/search/regex.ts
|
|
51
|
+
const escapeRegExp = (value) => {
|
|
52
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
69
53
|
};
|
|
70
|
-
|
|
54
|
+
/** Unicode word characters like letters, accents, numbers, and underscore-like punctuation. */
|
|
55
|
+
const unicodeWordCharacter = "[\\p{L}\\p{M}\\p{N}\\p{Pc}]";
|
|
71
56
|
function compileNativeRegex(source, caseSensitive) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
57
|
+
try {
|
|
58
|
+
return new RegExp(source, caseSensitive ? "gu" : "giu");
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
77
62
|
}
|
|
78
63
|
function compileSafeRegex(source, caseSensitive) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
64
|
+
try {
|
|
65
|
+
const flags = caseSensitive ? 0 : RE2JS.CASE_INSENSITIVE;
|
|
66
|
+
return RE2JS.compile(source, flags);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
85
70
|
}
|
|
86
71
|
function compileRegex(source, options) {
|
|
87
|
-
|
|
88
|
-
}
|
|
72
|
+
return options.useRegex ? compileSafeRegex(source, options.caseSensitive) : compileNativeRegex(source, options.caseSensitive);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates a search matcher for a term.
|
|
76
|
+
* Regex mode uses RE2-compatible syntax to avoid catastrophic backtracking.
|
|
77
|
+
* @param term The search term, treated as regex source when `useRegex` is enabled.
|
|
78
|
+
* @param options Case sensitivity, regex mode, and whole word matching.
|
|
79
|
+
* @returns A compiled matcher, or `null` when the term is invalid or unsupported.
|
|
80
|
+
*/
|
|
89
81
|
function createSearchRegex(term, options) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// src/search/matches.ts
|
|
82
|
+
if (!term) return null;
|
|
83
|
+
let source;
|
|
84
|
+
if (options.useRegex) source = term;
|
|
85
|
+
else {
|
|
86
|
+
const escaped = escapeRegExp(term);
|
|
87
|
+
source = options.wholeWord ? `(?<!${unicodeWordCharacter})${escaped}(?!${unicodeWordCharacter})` : escaped;
|
|
88
|
+
}
|
|
89
|
+
return compileRegex(source, options);
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/search/matches.ts
|
|
104
93
|
function* findNativeMatches(regex, text) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
for (const match of text.matchAll(regex)) yield {
|
|
95
|
+
index: match.index,
|
|
96
|
+
value: match[0]
|
|
97
|
+
};
|
|
108
98
|
}
|
|
109
99
|
function* findSafeMatches(regex, text) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
100
|
+
const matcher = regex.matcher(text);
|
|
101
|
+
while (matcher.find()) {
|
|
102
|
+
var _matcher$group;
|
|
103
|
+
yield {
|
|
104
|
+
index: matcher.start(),
|
|
105
|
+
value: (_matcher$group = matcher.group()) !== null && _matcher$group !== void 0 ? _matcher$group : ""
|
|
106
|
+
};
|
|
107
|
+
}
|
|
115
108
|
}
|
|
116
109
|
function findMatches(regex, text) {
|
|
117
|
-
|
|
110
|
+
return regex instanceof RegExp ? findNativeMatches(regex, text) : findSafeMatches(regex, text);
|
|
118
111
|
}
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/search/text-segments.ts
|
|
121
114
|
function getTextSegments(textblock, pos) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
115
|
+
const segments = [];
|
|
116
|
+
let textOffset = 0;
|
|
117
|
+
textblock.forEach((child, offset) => {
|
|
118
|
+
var _child$text;
|
|
119
|
+
const text = child.isText ? (_child$text = child.text) !== null && _child$text !== void 0 ? _child$text : "" : "\n";
|
|
120
|
+
segments.push({
|
|
121
|
+
isText: child.isText,
|
|
122
|
+
pos: pos + 1 + offset,
|
|
123
|
+
length: child.nodeSize,
|
|
124
|
+
text,
|
|
125
|
+
textOffset
|
|
126
|
+
});
|
|
127
|
+
textOffset += text.length;
|
|
128
|
+
});
|
|
129
|
+
return segments;
|
|
137
130
|
}
|
|
138
131
|
function findOffsetSegment(segments, offset) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
low = middle + 1;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return (_a = segments[low]) != null ? _a : segments.at(-1);
|
|
132
|
+
var _segments$low;
|
|
133
|
+
let low = 0;
|
|
134
|
+
let high = segments.length;
|
|
135
|
+
while (low < high) {
|
|
136
|
+
const middle = Math.floor((low + high) / 2);
|
|
137
|
+
const segment = segments[middle];
|
|
138
|
+
if (offset < segment.textOffset + segment.text.length) high = middle;
|
|
139
|
+
else low = middle + 1;
|
|
140
|
+
}
|
|
141
|
+
return (_segments$low = segments[low]) !== null && _segments$low !== void 0 ? _segments$low : segments.at(-1);
|
|
153
142
|
}
|
|
154
143
|
function offsetToPos(segments, offset) {
|
|
155
|
-
|
|
156
|
-
|
|
144
|
+
const segment = findOffsetSegment(segments, offset);
|
|
145
|
+
return segment ? segment.pos + Math.min(offset - segment.textOffset, segment.length) : 0;
|
|
157
146
|
}
|
|
158
147
|
function overlapsNonTextSegment(segments, from, to) {
|
|
159
|
-
|
|
160
|
-
(segment) => !segment.isText && from < segment.textOffset + segment.text.length && to > segment.textOffset
|
|
161
|
-
);
|
|
148
|
+
return segments.some((segment) => !segment.isText && from < segment.textOffset + segment.text.length && to > segment.textOffset);
|
|
162
149
|
}
|
|
163
|
-
|
|
164
|
-
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/search/textblock-search.ts
|
|
165
152
|
function searchTextblock(regex, textblock, pos) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
153
|
+
const segments = getTextSegments(textblock, pos);
|
|
154
|
+
const text = segments.map((segment) => segment.text).join("");
|
|
155
|
+
const results = [];
|
|
156
|
+
for (const match of findMatches(regex, text)) {
|
|
157
|
+
if (match.value.length === 0) continue;
|
|
158
|
+
const matchEnd = match.index + match.value.length;
|
|
159
|
+
if (overlapsNonTextSegment(segments, match.index, matchEnd)) continue;
|
|
160
|
+
results.push({
|
|
161
|
+
from: offsetToPos(segments, match.index),
|
|
162
|
+
to: offsetToPos(segments, matchEnd)
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return results;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/search/search.ts
|
|
169
|
+
/**
|
|
170
|
+
* Searches textblocks for a term and returns their matched ranges.
|
|
171
|
+
* @param textblocks The textblocks and their document positions.
|
|
172
|
+
* @param term The search term, treated as regex source when `useRegex` is enabled.
|
|
173
|
+
* @param options Case sensitivity, regex mode, and whole word matching.
|
|
174
|
+
* @returns The list of matched ranges in textblock order.
|
|
175
|
+
*/
|
|
186
176
|
function searchTextblocks(textblocks, term, options) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
177
|
+
const regex = createSearchRegex(term, options);
|
|
178
|
+
if (!regex) return [];
|
|
179
|
+
return textblocks.flatMap(({ node, pos }) => searchTextblock(regex, node, pos));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Searches all textblocks of a document for a term and returns the matched ranges.
|
|
183
|
+
* Matches may span multiple text nodes (e.g. across marks), but never leave a textblock.
|
|
184
|
+
* @param doc The document to search in.
|
|
185
|
+
* @param term The search term, treated as regex source when `useRegex` is enabled.
|
|
186
|
+
* @param options Case sensitivity, regex mode, and whole word matching.
|
|
187
|
+
* @returns The list of matched ranges in document order.
|
|
188
|
+
*/
|
|
193
189
|
function searchDocument(doc, term, options) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
190
|
+
const textblocks = [];
|
|
191
|
+
doc.descendants((node, pos) => {
|
|
192
|
+
if (!node.isTextblock) return true;
|
|
193
|
+
textblocks.push({
|
|
194
|
+
node,
|
|
195
|
+
pos
|
|
196
|
+
});
|
|
197
|
+
return false;
|
|
198
|
+
});
|
|
199
|
+
return searchTextblocks(textblocks, term, options);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Finds the index of the first result at or after a position, wrapping around
|
|
203
|
+
* to the first result when the position is behind the last result.
|
|
204
|
+
* @param results The current search results.
|
|
205
|
+
* @param from The position to search from.
|
|
206
|
+
* @returns The result index, or `null` when there are no results.
|
|
207
|
+
*/
|
|
204
208
|
function findNextIndex(results, from) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const index = results.findIndex((result) => result.from >= from);
|
|
209
|
-
return index === -1 ? 0 : index;
|
|
209
|
+
if (results.length === 0) return null;
|
|
210
|
+
const index = results.findIndex((result) => result.from >= from);
|
|
211
|
+
return index === -1 ? 0 : index;
|
|
210
212
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
import { DecorationSet } from "@tiptap/pm/view";
|
|
214
|
-
|
|
215
|
-
// src/utils/createDecoration.ts
|
|
216
|
-
import { Decoration } from "@tiptap/pm/view";
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/utils/createDecoration.ts
|
|
217
215
|
function createDecoration(result, currentIndex, index) {
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
const className = index === currentIndex ? `${resultClass} ${currentResultClass}` : resultClass;
|
|
217
|
+
return Decoration.inline(result.from, result.to, { class: className });
|
|
220
218
|
}
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/utils/createDecorations.ts
|
|
223
221
|
function createDecorations(doc, results, currentIndex) {
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
const decorations = results.map((result, index) => createDecoration(result, currentIndex, index));
|
|
223
|
+
return DecorationSet.create(doc, decorations);
|
|
226
224
|
}
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/utils/createState.ts
|
|
229
227
|
function createState(doc, options) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
228
|
+
const results = searchDocument(doc, options.searchTerm, options);
|
|
229
|
+
const currentIndex = results.length > 0 ? 0 : null;
|
|
230
|
+
return {
|
|
231
|
+
searchTerm: options.searchTerm,
|
|
232
|
+
replaceTerm: options.replaceTerm,
|
|
233
|
+
caseSensitive: options.caseSensitive,
|
|
234
|
+
useRegex: options.useRegex,
|
|
235
|
+
wholeWord: options.wholeWord,
|
|
236
|
+
results,
|
|
237
|
+
currentIndex,
|
|
238
|
+
decorations: createDecorations(doc, results, currentIndex)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/utils/hasCurrentIndex.ts
|
|
245
243
|
function hasCurrentIndex(meta) {
|
|
246
|
-
|
|
244
|
+
return !!meta && "currentIndex" in meta;
|
|
247
245
|
}
|
|
248
|
-
|
|
249
|
-
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/utils/findDecorations.ts
|
|
250
248
|
function findDecorations(decorations, results, textblocks) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
249
|
+
const found = /* @__PURE__ */ new Set();
|
|
250
|
+
textblocks.forEach((textblock) => {
|
|
251
|
+
decorations.find(textblock.from, textblock.to).forEach((decoration) => found.add(decoration));
|
|
252
|
+
});
|
|
253
|
+
results.forEach((result) => {
|
|
254
|
+
decorations.find(result.from, result.to).filter((decoration) => {
|
|
255
|
+
return decoration.from === result.from && decoration.to === result.to;
|
|
256
|
+
}).forEach((decoration) => found.add(decoration));
|
|
257
|
+
});
|
|
258
|
+
return [...found];
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/utils/sameResult.ts
|
|
264
262
|
function sameResult(first, second) {
|
|
265
|
-
|
|
263
|
+
return first.from === second.from && first.to === second.to;
|
|
266
264
|
}
|
|
267
|
-
|
|
268
|
-
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/utils/isCurrentResult.ts
|
|
269
267
|
function isCurrentResult(result, current) {
|
|
270
|
-
|
|
268
|
+
return current !== null && sameResult(result, current);
|
|
271
269
|
}
|
|
272
|
-
|
|
273
|
-
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/utils/resultInTextblocks.ts
|
|
274
272
|
function resultInTextblocks(result, textblocks) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
273
|
+
let start = 0;
|
|
274
|
+
let end = textblocks.length - 1;
|
|
275
|
+
while (start <= end) {
|
|
276
|
+
const index = Math.floor((start + end) / 2);
|
|
277
|
+
const textblock = textblocks[index];
|
|
278
|
+
if (result.from < textblock.from) {
|
|
279
|
+
end = index - 1;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (result.to > textblock.to) {
|
|
283
|
+
start = index + 1;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/utils/shouldRefreshResult.ts
|
|
294
292
|
function shouldRefreshResult(result, textblocks, previousCurrent, current) {
|
|
295
|
-
|
|
293
|
+
return resultInTextblocks(result, textblocks) || isCurrentResult(result, previousCurrent) || isCurrentResult(result, current);
|
|
296
294
|
}
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/utils/getResultsToRefresh.ts
|
|
299
297
|
function getResultsToRefresh(results, textblocks, previousCurrent, currentIndex) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
298
|
+
var _results$currentIndex;
|
|
299
|
+
const current = currentIndex === null ? null : (_results$currentIndex = results[currentIndex]) !== null && _results$currentIndex !== void 0 ? _results$currentIndex : null;
|
|
300
|
+
const refreshedResults = [];
|
|
301
|
+
results.forEach((result, index) => {
|
|
302
|
+
if (shouldRefreshResult(result, textblocks, previousCurrent, current)) refreshedResults.push({
|
|
303
|
+
result,
|
|
304
|
+
index
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
return refreshedResults;
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/utils/refreshDecorations.ts
|
|
312
311
|
function refreshDecorations(decorations, doc, results, currentIndex, textblocks, previousCurrent) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
// src/utils/normalizeCurrentIndex.ts
|
|
312
|
+
const resultsToRefresh = getResultsToRefresh(results, textblocks, previousCurrent, currentIndex);
|
|
313
|
+
const refreshedResults = resultsToRefresh.map(({ result }) => result);
|
|
314
|
+
const decorationsToRemove = findDecorations(decorations, previousCurrent ? [...refreshedResults, previousCurrent] : refreshedResults, textblocks);
|
|
315
|
+
const decorationsToAdd = resultsToRefresh.map(({ result, index }) => {
|
|
316
|
+
return createDecoration(result, currentIndex, index);
|
|
317
|
+
});
|
|
318
|
+
return decorations.remove(decorationsToRemove).add(doc, decorationsToAdd);
|
|
319
|
+
}
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/utils/normalizeCurrentIndex.ts
|
|
324
322
|
function normalizeCurrentIndex(currentIndex, length) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
return Math.max(0, Math.min(currentIndex, length - 1));
|
|
323
|
+
if (currentIndex === void 0 || currentIndex === null || length === 0) return null;
|
|
324
|
+
return Math.max(0, Math.min(currentIndex, length - 1));
|
|
329
325
|
}
|
|
330
|
-
|
|
331
|
-
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/utils/getMetaCurrentIndex.ts
|
|
332
328
|
function getMetaCurrentIndex(meta, length) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (!hasCurrentIndex(meta)) {
|
|
337
|
-
return void 0;
|
|
338
|
-
}
|
|
339
|
-
return normalizeCurrentIndex(meta.currentIndex, length);
|
|
329
|
+
if (!meta) return;
|
|
330
|
+
if (!hasCurrentIndex(meta)) return;
|
|
331
|
+
return normalizeCurrentIndex(meta.currentIndex, length);
|
|
340
332
|
}
|
|
341
|
-
|
|
342
|
-
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/utils/getFallbackCurrentIndex.ts
|
|
343
335
|
function getFallbackCurrentIndex(previousState, length) {
|
|
344
|
-
|
|
336
|
+
return previousState.currentIndex === null ? 0 : Math.min(previousState.currentIndex, length - 1);
|
|
345
337
|
}
|
|
346
|
-
|
|
347
|
-
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/utils/getPreviousCurrentResult.ts
|
|
348
340
|
function getPreviousCurrentResult(previousState) {
|
|
349
|
-
|
|
341
|
+
return previousState.currentIndex === null ? void 0 : previousState.results[previousState.currentIndex];
|
|
350
342
|
}
|
|
351
|
-
|
|
352
|
-
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/utils/getMappedCurrentIndex.ts
|
|
353
345
|
function getMappedCurrentIndex(previousState, results, docChanged, mapping) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return void 0;
|
|
363
|
-
}
|
|
364
|
-
const mappedFrom = mapping.map(previousResult.from, 1);
|
|
365
|
-
return findNextIndex(results, mappedFrom);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// src/utils/isNewSearch.ts
|
|
346
|
+
if (!docChanged) return;
|
|
347
|
+
if (!mapping) return;
|
|
348
|
+
const previousResult = getPreviousCurrentResult(previousState);
|
|
349
|
+
if (!previousResult) return;
|
|
350
|
+
return findNextIndex(results, mapping.map(previousResult.from, 1));
|
|
351
|
+
}
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region src/utils/isNewSearch.ts
|
|
369
354
|
function isNewSearch(meta) {
|
|
370
|
-
|
|
355
|
+
return !!meta && "searchTerm" in meta;
|
|
371
356
|
}
|
|
372
|
-
|
|
373
|
-
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/utils/getSearchCurrentIndex.ts
|
|
374
359
|
function getSearchCurrentIndex(previousState, results, meta, docChanged, mapping) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const mappedIndex = getMappedCurrentIndex(previousState, results, docChanged, mapping);
|
|
379
|
-
return mappedIndex != null ? mappedIndex : getFallbackCurrentIndex(previousState, results.length);
|
|
360
|
+
if (isNewSearch(meta)) return 0;
|
|
361
|
+
const mappedIndex = getMappedCurrentIndex(previousState, results, docChanged, mapping);
|
|
362
|
+
return mappedIndex !== null && mappedIndex !== void 0 ? mappedIndex : getFallbackCurrentIndex(previousState, results.length);
|
|
380
363
|
}
|
|
381
|
-
|
|
382
|
-
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/utils/resolveCurrentIndex.ts
|
|
383
366
|
function resolveCurrentIndex(previousState, results, meta, docChanged, mapping) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (metaIndex !== void 0) {
|
|
389
|
-
return metaIndex;
|
|
390
|
-
}
|
|
391
|
-
return getSearchCurrentIndex(previousState, results, meta, docChanged, mapping);
|
|
367
|
+
if (results.length === 0) return null;
|
|
368
|
+
const metaIndex = getMetaCurrentIndex(meta, results.length);
|
|
369
|
+
if (metaIndex !== void 0) return metaIndex;
|
|
370
|
+
return getSearchCurrentIndex(previousState, results, meta, docChanged, mapping);
|
|
392
371
|
}
|
|
393
|
-
|
|
394
|
-
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/utils/refreshCurrentIndexState.ts
|
|
395
374
|
function refreshCurrentIndexState(previousState, state, doc, meta) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
[],
|
|
407
|
-
previousCurrent
|
|
408
|
-
)
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// src/utils/getChangedTextblocks.ts
|
|
375
|
+
const currentIndex = resolveCurrentIndex(previousState, state.results, meta, false, void 0);
|
|
376
|
+
const previousCurrent = previousState.currentIndex === null ? null : previousState.results[previousState.currentIndex];
|
|
377
|
+
return {
|
|
378
|
+
...state,
|
|
379
|
+
currentIndex,
|
|
380
|
+
decorations: refreshDecorations(previousState.decorations, doc, state.results, currentIndex, [], previousCurrent)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/utils/getChangedTextblocks.ts
|
|
413
385
|
function getChangedTextblocks(doc, mapping) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
386
|
+
const textblocks = /* @__PURE__ */ new Map();
|
|
387
|
+
mapping.maps.forEach((stepMap, index) => {
|
|
388
|
+
const remaining = mapping.slice(index + 1);
|
|
389
|
+
stepMap.forEach((_oldStart, _oldEnd, newStart, newEnd) => {
|
|
390
|
+
const from = Math.max(0, remaining.map(newStart, -1) - 1);
|
|
391
|
+
const to = Math.min(doc.content.size, remaining.map(newEnd, 1) + 1);
|
|
392
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
393
|
+
if (!node.isTextblock) return true;
|
|
394
|
+
textblocks.set(pos, {
|
|
395
|
+
node,
|
|
396
|
+
pos,
|
|
397
|
+
from: pos,
|
|
398
|
+
to: pos + node.nodeSize
|
|
399
|
+
});
|
|
400
|
+
return false;
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
return [...textblocks.values()].sort((first, second) => first.pos - second.pos);
|
|
405
|
+
}
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/utils/mapResult.ts
|
|
433
408
|
function mapResult(result, mapping) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
409
|
+
const from = mapping.map(result.from, 1);
|
|
410
|
+
const to = mapping.map(result.to, -1);
|
|
411
|
+
return from < to ? {
|
|
412
|
+
from,
|
|
413
|
+
to
|
|
414
|
+
} : null;
|
|
415
|
+
}
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/utils/mapResults.ts
|
|
440
418
|
function mapResults(results, mapping) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
// src/utils/mergeResults.ts
|
|
419
|
+
const mappedResults = [];
|
|
420
|
+
results.forEach((result) => {
|
|
421
|
+
const mapped = mapResult(result, mapping);
|
|
422
|
+
if (mapped) mappedResults.push(mapped);
|
|
423
|
+
});
|
|
424
|
+
return mappedResults;
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/utils/mergeResults.ts
|
|
452
428
|
function mergeResults(first, second) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
|
|
429
|
+
const results = [];
|
|
430
|
+
let firstIndex = 0;
|
|
431
|
+
let secondIndex = 0;
|
|
432
|
+
while (firstIndex < first.length && secondIndex < second.length) {
|
|
433
|
+
if (first[firstIndex].from < second[secondIndex].from) {
|
|
434
|
+
results.push(first[firstIndex++]);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
results.push(second[secondIndex++]);
|
|
438
|
+
}
|
|
439
|
+
return results.concat(first.slice(firstIndex), second.slice(secondIndex));
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/utils/refreshDocumentState.ts
|
|
467
443
|
function refreshDocumentState(previousState, state, doc, meta, mapping) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
doc,
|
|
483
|
-
results,
|
|
484
|
-
currentIndex,
|
|
485
|
-
textblocks,
|
|
486
|
-
previousCurrent
|
|
487
|
-
)
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// src/utils/refreshState.ts
|
|
444
|
+
const textblocks = getChangedTextblocks(doc, mapping);
|
|
445
|
+
const results = mergeResults(mapResults(previousState.results, mapping).filter((result) => !resultInTextblocks(result, textblocks)), searchTextblocks(textblocks, state.searchTerm, state));
|
|
446
|
+
const currentIndex = resolveCurrentIndex(previousState, results, meta, true, mapping);
|
|
447
|
+
const previousCurrent = previousState.currentIndex === null ? null : mapResult(previousState.results[previousState.currentIndex], mapping);
|
|
448
|
+
const decorations = previousState.decorations.map(mapping, doc);
|
|
449
|
+
return {
|
|
450
|
+
...state,
|
|
451
|
+
results,
|
|
452
|
+
currentIndex,
|
|
453
|
+
decorations: refreshDecorations(decorations, doc, results, currentIndex, textblocks, previousCurrent)
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region src/utils/refreshState.ts
|
|
492
458
|
function refreshState(previousState, state, doc, meta, docChanged, mapping) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
459
|
+
const results = searchDocument(doc, state.searchTerm, state);
|
|
460
|
+
const currentIndex = resolveCurrentIndex(previousState, results, meta, docChanged, mapping);
|
|
461
|
+
return {
|
|
462
|
+
...state,
|
|
463
|
+
results,
|
|
464
|
+
currentIndex,
|
|
465
|
+
decorations: createDecorations(doc, results, currentIndex)
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/utils/touchesSearch.ts
|
|
470
|
+
const searchKeys = [
|
|
471
|
+
"searchTerm",
|
|
472
|
+
"caseSensitive",
|
|
473
|
+
"useRegex",
|
|
474
|
+
"wholeWord"
|
|
475
|
+
];
|
|
505
476
|
function touchesSearch(meta) {
|
|
506
|
-
|
|
477
|
+
return !!meta && searchKeys.some((key) => key in meta);
|
|
507
478
|
}
|
|
508
|
-
|
|
509
|
-
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/utils/updateState.ts
|
|
510
481
|
function updateState(previousState, state, doc, meta, docChanged, mapping) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
state,
|
|
544
|
-
{ ...state, ...meta },
|
|
545
|
-
newState.doc,
|
|
546
|
-
meta,
|
|
547
|
-
tr.docChanged,
|
|
548
|
-
tr.mapping
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
},
|
|
552
|
-
props: {
|
|
553
|
-
decorations(state) {
|
|
554
|
-
var _a;
|
|
555
|
-
return (_a = FindAndReplacePluginKey.getState(state)) == null ? void 0 : _a.decorations;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
});
|
|
482
|
+
if (touchesSearch(meta)) return refreshState(previousState, state, doc, meta, docChanged, mapping);
|
|
483
|
+
if (docChanged) return refreshDocumentState(previousState, state, doc, meta, mapping);
|
|
484
|
+
if (hasCurrentIndex(meta)) return refreshCurrentIndexState(previousState, state, doc, meta);
|
|
485
|
+
return state;
|
|
486
|
+
}
|
|
487
|
+
//#endregion
|
|
488
|
+
//#region src/plugin/plugin.ts
|
|
489
|
+
const FindAndReplacePluginKey = new PluginKey("findAndReplace");
|
|
490
|
+
const FindAndReplacePlugin = (options, onCreate) => {
|
|
491
|
+
return new Plugin({
|
|
492
|
+
key: FindAndReplacePluginKey,
|
|
493
|
+
state: {
|
|
494
|
+
init: (_config, state) => {
|
|
495
|
+
if (options.injectCSS && typeof document !== "undefined") createStyleTag(style, options.injectNonce);
|
|
496
|
+
const pluginState = createState(state.doc, options);
|
|
497
|
+
onCreate(pluginState);
|
|
498
|
+
return pluginState;
|
|
499
|
+
},
|
|
500
|
+
apply: (tr, state, _oldState, newState) => {
|
|
501
|
+
const meta = tr.getMeta(FindAndReplacePluginKey);
|
|
502
|
+
if (!meta && !tr.docChanged) return state;
|
|
503
|
+
return updateState(state, {
|
|
504
|
+
...state,
|
|
505
|
+
...meta
|
|
506
|
+
}, newState.doc, meta, tr.docChanged, tr.mapping);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
props: { decorations(state) {
|
|
510
|
+
var _FindAndReplacePlugin;
|
|
511
|
+
return (_FindAndReplacePlugin = FindAndReplacePluginKey.getState(state)) === null || _FindAndReplacePlugin === void 0 ? void 0 : _FindAndReplacePlugin.decorations;
|
|
512
|
+
} }
|
|
513
|
+
});
|
|
559
514
|
};
|
|
560
|
-
|
|
561
|
-
|
|
515
|
+
//#endregion
|
|
516
|
+
//#region src/utils/getTextNodeResult.ts
|
|
562
517
|
function getTextNodeResult(state, result) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
|
|
518
|
+
const $from = state.doc.resolve(result.from);
|
|
519
|
+
const node = $from.parent.child($from.index());
|
|
520
|
+
if (!node.isText || !node.text) return null;
|
|
521
|
+
const from = result.from - $from.textOffset;
|
|
522
|
+
return result.to <= from + node.nodeSize ? {
|
|
523
|
+
from,
|
|
524
|
+
text: node.text
|
|
525
|
+
} : null;
|
|
526
|
+
}
|
|
527
|
+
//#endregion
|
|
528
|
+
//#region src/utils/groupResults.ts
|
|
573
529
|
function shouldMergeResult(group, textNode) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
return group.from === textNode.from && group.text !== null;
|
|
530
|
+
if (!group || !textNode) return false;
|
|
531
|
+
return group.from === textNode.from && group.text !== null;
|
|
578
532
|
}
|
|
579
533
|
function createTextResultGroup(result, textNode) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
534
|
+
return {
|
|
535
|
+
from: textNode.from,
|
|
536
|
+
text: textNode.text,
|
|
537
|
+
results: [result]
|
|
538
|
+
};
|
|
585
539
|
}
|
|
586
540
|
function createResultGroup(result) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
541
|
+
return {
|
|
542
|
+
from: result.from,
|
|
543
|
+
text: null,
|
|
544
|
+
results: [result]
|
|
545
|
+
};
|
|
592
546
|
}
|
|
593
547
|
function appendResult(groups, result, textNode) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
548
|
+
const group = groups.at(-1);
|
|
549
|
+
if (shouldMergeResult(group, textNode)) {
|
|
550
|
+
group.results.push(result);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (textNode) {
|
|
554
|
+
groups.push(createTextResultGroup(result, textNode));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
groups.push(createResultGroup(result));
|
|
604
558
|
}
|
|
605
559
|
function groupResults(state, results) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
return groups;
|
|
560
|
+
const groups = [];
|
|
561
|
+
for (const result of results) appendResult(groups, result, getTextNodeResult(state, result));
|
|
562
|
+
return groups;
|
|
611
563
|
}
|
|
612
|
-
|
|
613
|
-
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region src/utils/replaceGroup.ts
|
|
614
566
|
function replaceSeparateResults(tr, results, replaceTerm) {
|
|
615
|
-
|
|
616
|
-
tr.insertText(replaceTerm, result.from, result.to);
|
|
617
|
-
}
|
|
567
|
+
for (const result of [...results].reverse()) tr.insertText(replaceTerm, result.from, result.to);
|
|
618
568
|
}
|
|
619
569
|
function createReplacement(group, text, firstResult, replaceTerm) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
570
|
+
let offset = firstResult.from - group.from;
|
|
571
|
+
const replacement = [];
|
|
572
|
+
for (const result of group.results) {
|
|
573
|
+
const from = result.from - group.from;
|
|
574
|
+
const to = result.to - group.from;
|
|
575
|
+
replacement.push(text.slice(offset, from), replaceTerm);
|
|
576
|
+
offset = to;
|
|
577
|
+
}
|
|
578
|
+
return replacement.join("");
|
|
629
579
|
}
|
|
630
580
|
function replaceGroup(tr, group, replaceTerm) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
firstResult.from,
|
|
643
|
-
lastResult.to
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// src/utils/replaceAllResults.ts
|
|
581
|
+
const firstResult = group.results[0];
|
|
582
|
+
const lastResult = group.results.at(-1);
|
|
583
|
+
if (!lastResult) return;
|
|
584
|
+
if (group.text === null) {
|
|
585
|
+
replaceSeparateResults(tr, group.results, replaceTerm);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
tr.insertText(createReplacement(group, group.text, firstResult, replaceTerm), firstResult.from, lastResult.to);
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/utils/replaceAllResults.ts
|
|
648
592
|
function replaceAllResults(tr, state, results, replaceTerm) {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
replaceGroup(tr, group, replaceTerm);
|
|
652
|
-
}
|
|
593
|
+
const groups = groupResults(state, results);
|
|
594
|
+
for (const group of groups.reverse()) replaceGroup(tr, group, replaceTerm);
|
|
653
595
|
}
|
|
654
|
-
|
|
655
|
-
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region src/find-and-replace.ts
|
|
656
598
|
function mergePluginMeta(tr, meta) {
|
|
657
|
-
|
|
658
|
-
|
|
599
|
+
const currentMeta = tr.getMeta(FindAndReplacePluginKey);
|
|
600
|
+
tr.setMeta(FindAndReplacePluginKey, {
|
|
601
|
+
...currentMeta,
|
|
602
|
+
...meta
|
|
603
|
+
});
|
|
659
604
|
}
|
|
660
605
|
function setPluginMeta(meta) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
return true;
|
|
666
|
-
};
|
|
606
|
+
return ({ tr, dispatch }) => {
|
|
607
|
+
if (dispatch) mergePluginMeta(tr, meta);
|
|
608
|
+
return true;
|
|
609
|
+
};
|
|
667
610
|
}
|
|
668
611
|
function applySearchTerm(editor, term) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
612
|
+
const tr = editor.state.tr;
|
|
613
|
+
mergePluginMeta(tr, { searchTerm: term });
|
|
614
|
+
editor.view.dispatch(tr);
|
|
672
615
|
}
|
|
673
616
|
function consumePendingSearch(editor) {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
return pendingTerm;
|
|
617
|
+
const state = getDebouncedSearchState(editor);
|
|
618
|
+
if (state.pendingTerm === null) return null;
|
|
619
|
+
const pendingTerm = state.pendingTerm;
|
|
620
|
+
state.pendingTerm = null;
|
|
621
|
+
if (state.timeout !== null) {
|
|
622
|
+
clearTimeout(state.timeout);
|
|
623
|
+
state.timeout = null;
|
|
624
|
+
}
|
|
625
|
+
return pendingTerm;
|
|
685
626
|
}
|
|
686
627
|
function getPendingAwarePluginState(state, pendingTerm) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
return {
|
|
703
|
-
...pluginState,
|
|
704
|
-
searchTerm: pendingTerm,
|
|
705
|
-
results,
|
|
706
|
-
currentIndex: 0
|
|
707
|
-
};
|
|
628
|
+
const pluginState = FindAndReplacePluginKey.getState(state);
|
|
629
|
+
if (!pluginState) return null;
|
|
630
|
+
if (pendingTerm === null) return pluginState.results.length === 0 ? null : pluginState;
|
|
631
|
+
const results = searchDocument(state.doc, pendingTerm, {
|
|
632
|
+
caseSensitive: pluginState.caseSensitive,
|
|
633
|
+
useRegex: pluginState.useRegex,
|
|
634
|
+
wholeWord: pluginState.wholeWord
|
|
635
|
+
});
|
|
636
|
+
if (results.length === 0) return null;
|
|
637
|
+
return {
|
|
638
|
+
...pluginState,
|
|
639
|
+
searchTerm: pendingTerm,
|
|
640
|
+
results,
|
|
641
|
+
currentIndex: 0
|
|
642
|
+
};
|
|
708
643
|
}
|
|
709
644
|
function currentResult(pluginState) {
|
|
710
|
-
|
|
711
|
-
|
|
645
|
+
var _pluginState$currentI;
|
|
646
|
+
return pluginState.results[(_pluginState$currentI = pluginState.currentIndex) !== null && _pluginState$currentI !== void 0 ? _pluginState$currentI : 0];
|
|
712
647
|
}
|
|
713
648
|
function shiftIndex(currentIndex, length, direction) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
return (currentIndex + direction + length) % length;
|
|
649
|
+
if (currentIndex === null) return direction === 1 ? 0 : length - 1;
|
|
650
|
+
return (currentIndex + direction + length) % length;
|
|
718
651
|
}
|
|
719
652
|
function selectResult(tr, result, index) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
653
|
+
tr.setSelection(TextSelection.create(tr.doc, result.from, result.to));
|
|
654
|
+
mergePluginMeta(tr, { currentIndex: index });
|
|
655
|
+
tr.scrollIntoView();
|
|
723
656
|
}
|
|
724
657
|
function replaceResult(tr, pluginState, result) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const pendingTerm = dispatch ? consumePendingSearch(this.editor) : null;
|
|
866
|
-
if (dispatch && pendingTerm !== null) {
|
|
867
|
-
mergePluginMeta(tr, { searchTerm: pendingTerm });
|
|
868
|
-
}
|
|
869
|
-
const pluginState = getPendingAwarePluginState(state, pendingTerm);
|
|
870
|
-
if (!pluginState) {
|
|
871
|
-
return false;
|
|
872
|
-
}
|
|
873
|
-
const nextIndex = shiftIndex(pluginState.currentIndex, pluginState.results.length, 1);
|
|
874
|
-
if (dispatch) {
|
|
875
|
-
selectResult(tr, pluginState.results[nextIndex], nextIndex);
|
|
876
|
-
}
|
|
877
|
-
return true;
|
|
878
|
-
},
|
|
879
|
-
goToPreviousResult: () => ({ tr, state, dispatch }) => {
|
|
880
|
-
const pendingTerm = dispatch ? consumePendingSearch(this.editor) : null;
|
|
881
|
-
if (dispatch && pendingTerm !== null) {
|
|
882
|
-
mergePluginMeta(tr, { searchTerm: pendingTerm });
|
|
883
|
-
}
|
|
884
|
-
const pluginState = getPendingAwarePluginState(state, pendingTerm);
|
|
885
|
-
if (!pluginState) {
|
|
886
|
-
return false;
|
|
887
|
-
}
|
|
888
|
-
const previousIndex = shiftIndex(pluginState.currentIndex, pluginState.results.length, -1);
|
|
889
|
-
if (dispatch) {
|
|
890
|
-
selectResult(tr, pluginState.results[previousIndex], previousIndex);
|
|
891
|
-
}
|
|
892
|
-
return true;
|
|
893
|
-
}
|
|
894
|
-
};
|
|
895
|
-
},
|
|
896
|
-
onDestroy() {
|
|
897
|
-
clearDebouncedSearchState(this.editor);
|
|
898
|
-
}
|
|
658
|
+
const { searchTerm, replaceTerm, caseSensitive, useRegex, wholeWord } = pluginState;
|
|
659
|
+
tr.insertText(replaceTerm, result.from, result.to);
|
|
660
|
+
const results = searchDocument(tr.doc, searchTerm, {
|
|
661
|
+
caseSensitive,
|
|
662
|
+
useRegex,
|
|
663
|
+
wholeWord
|
|
664
|
+
});
|
|
665
|
+
const nextIndex = findNextIndex(results, result.from + replaceTerm.length);
|
|
666
|
+
if (nextIndex === null) {
|
|
667
|
+
mergePluginMeta(tr, { currentIndex: null });
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
selectResult(tr, results[nextIndex], nextIndex);
|
|
671
|
+
}
|
|
672
|
+
const FindAndReplace = Extension.create({
|
|
673
|
+
name: "findAndReplace",
|
|
674
|
+
addOptions() {
|
|
675
|
+
return {
|
|
676
|
+
searchTerm: "",
|
|
677
|
+
replaceTerm: "",
|
|
678
|
+
caseSensitive: false,
|
|
679
|
+
useRegex: false,
|
|
680
|
+
wholeWord: false,
|
|
681
|
+
searchDebounceMs: 250,
|
|
682
|
+
injectCSS: true,
|
|
683
|
+
injectNonce: void 0
|
|
684
|
+
};
|
|
685
|
+
},
|
|
686
|
+
addStorage() {
|
|
687
|
+
return {
|
|
688
|
+
searchTerm: this.options.searchTerm,
|
|
689
|
+
replaceTerm: this.options.replaceTerm,
|
|
690
|
+
caseSensitive: this.options.caseSensitive,
|
|
691
|
+
useRegex: this.options.useRegex,
|
|
692
|
+
wholeWord: this.options.wholeWord,
|
|
693
|
+
results: [],
|
|
694
|
+
currentIndex: null
|
|
695
|
+
};
|
|
696
|
+
},
|
|
697
|
+
addProseMirrorPlugins() {
|
|
698
|
+
return [FindAndReplacePlugin(this.options, (pluginState) => {
|
|
699
|
+
this.storage.results = pluginState.results;
|
|
700
|
+
this.storage.currentIndex = pluginState.currentIndex;
|
|
701
|
+
})];
|
|
702
|
+
},
|
|
703
|
+
onTransaction() {
|
|
704
|
+
const pluginState = FindAndReplacePluginKey.getState(this.editor.state);
|
|
705
|
+
if (!pluginState) return;
|
|
706
|
+
this.storage.searchTerm = pluginState.searchTerm;
|
|
707
|
+
this.storage.replaceTerm = pluginState.replaceTerm;
|
|
708
|
+
this.storage.caseSensitive = pluginState.caseSensitive;
|
|
709
|
+
this.storage.useRegex = pluginState.useRegex;
|
|
710
|
+
this.storage.wholeWord = pluginState.wholeWord;
|
|
711
|
+
this.storage.results = pluginState.results;
|
|
712
|
+
this.storage.currentIndex = pluginState.currentIndex;
|
|
713
|
+
},
|
|
714
|
+
addCommands() {
|
|
715
|
+
const scheduleSearchTerm = (term) => {
|
|
716
|
+
const state = getDebouncedSearchState(this.editor);
|
|
717
|
+
if (this.options.searchDebounceMs <= 0) {
|
|
718
|
+
if (state.timeout !== null) {
|
|
719
|
+
clearTimeout(state.timeout);
|
|
720
|
+
state.timeout = null;
|
|
721
|
+
}
|
|
722
|
+
state.pendingTerm = null;
|
|
723
|
+
applySearchTerm(this.editor, term);
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
if (state.timeout !== null) clearTimeout(state.timeout);
|
|
727
|
+
state.pendingTerm = term;
|
|
728
|
+
state.timeout = setTimeout(() => {
|
|
729
|
+
const debouncedState = getDebouncedSearchState(this.editor);
|
|
730
|
+
debouncedState.timeout = null;
|
|
731
|
+
if (debouncedState.pendingTerm === null) return;
|
|
732
|
+
const pendingTerm = debouncedState.pendingTerm;
|
|
733
|
+
debouncedState.pendingTerm = null;
|
|
734
|
+
applySearchTerm(this.editor, pendingTerm);
|
|
735
|
+
}, this.options.searchDebounceMs);
|
|
736
|
+
return true;
|
|
737
|
+
};
|
|
738
|
+
return {
|
|
739
|
+
setSearchTerm: (term) => ({ dispatch }) => {
|
|
740
|
+
if (!dispatch) return true;
|
|
741
|
+
return scheduleSearchTerm(term);
|
|
742
|
+
},
|
|
743
|
+
setReplaceTerm: (term) => setPluginMeta({ replaceTerm: term }),
|
|
744
|
+
setCaseSensitive: (caseSensitive) => setPluginMeta({ caseSensitive }),
|
|
745
|
+
setUseRegex: (useRegex) => setPluginMeta({ useRegex }),
|
|
746
|
+
setWholeWord: (wholeWord) => setPluginMeta({ wholeWord }),
|
|
747
|
+
clearSearch: () => ({ tr, dispatch }) => {
|
|
748
|
+
const state = getDebouncedSearchState(this.editor);
|
|
749
|
+
if (state.timeout !== null) {
|
|
750
|
+
clearTimeout(state.timeout);
|
|
751
|
+
state.timeout = null;
|
|
752
|
+
}
|
|
753
|
+
state.pendingTerm = null;
|
|
754
|
+
if (dispatch) mergePluginMeta(tr, { searchTerm: "" });
|
|
755
|
+
return true;
|
|
756
|
+
},
|
|
757
|
+
replace: () => ({ tr, state, dispatch }) => {
|
|
758
|
+
const pendingTerm = dispatch ? consumePendingSearch(this.editor) : null;
|
|
759
|
+
if (dispatch && pendingTerm !== null) mergePluginMeta(tr, { searchTerm: pendingTerm });
|
|
760
|
+
const pluginState = getPendingAwarePluginState(state, pendingTerm);
|
|
761
|
+
if (!pluginState) return false;
|
|
762
|
+
const result = currentResult(pluginState);
|
|
763
|
+
if (!result) return false;
|
|
764
|
+
if (dispatch) replaceResult(tr, pluginState, result);
|
|
765
|
+
return true;
|
|
766
|
+
},
|
|
767
|
+
replaceAll: () => ({ tr, state, dispatch }) => {
|
|
768
|
+
const pendingTerm = dispatch ? consumePendingSearch(this.editor) : null;
|
|
769
|
+
if (dispatch && pendingTerm !== null) mergePluginMeta(tr, { searchTerm: pendingTerm });
|
|
770
|
+
const pluginState = getPendingAwarePluginState(state, pendingTerm);
|
|
771
|
+
if (!pluginState) return false;
|
|
772
|
+
if (dispatch) replaceAllResults(tr, state, pluginState.results, pluginState.replaceTerm);
|
|
773
|
+
return true;
|
|
774
|
+
},
|
|
775
|
+
goToNextResult: () => ({ tr, state, dispatch }) => {
|
|
776
|
+
const pendingTerm = dispatch ? consumePendingSearch(this.editor) : null;
|
|
777
|
+
if (dispatch && pendingTerm !== null) mergePluginMeta(tr, { searchTerm: pendingTerm });
|
|
778
|
+
const pluginState = getPendingAwarePluginState(state, pendingTerm);
|
|
779
|
+
if (!pluginState) return false;
|
|
780
|
+
const nextIndex = shiftIndex(pluginState.currentIndex, pluginState.results.length, 1);
|
|
781
|
+
if (dispatch) selectResult(tr, pluginState.results[nextIndex], nextIndex);
|
|
782
|
+
return true;
|
|
783
|
+
},
|
|
784
|
+
goToPreviousResult: () => ({ tr, state, dispatch }) => {
|
|
785
|
+
const pendingTerm = dispatch ? consumePendingSearch(this.editor) : null;
|
|
786
|
+
if (dispatch && pendingTerm !== null) mergePluginMeta(tr, { searchTerm: pendingTerm });
|
|
787
|
+
const pluginState = getPendingAwarePluginState(state, pendingTerm);
|
|
788
|
+
if (!pluginState) return false;
|
|
789
|
+
const previousIndex = shiftIndex(pluginState.currentIndex, pluginState.results.length, -1);
|
|
790
|
+
if (dispatch) selectResult(tr, pluginState.results[previousIndex], previousIndex);
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
},
|
|
795
|
+
onDestroy() {
|
|
796
|
+
clearDebouncedSearchState(this.editor);
|
|
797
|
+
}
|
|
899
798
|
});
|
|
799
|
+
//#endregion
|
|
800
|
+
//#region src/index.ts
|
|
801
|
+
var src_default = FindAndReplace;
|
|
802
|
+
//#endregion
|
|
803
|
+
export { FindAndReplace, FindAndReplacePlugin, FindAndReplacePluginKey, createSearchRegex, currentResultClass, src_default as default, findNextIndex, resultClass, searchDocument, searchTextblocks };
|
|
900
804
|
|
|
901
|
-
// src/index.ts
|
|
902
|
-
var index_default = FindAndReplace;
|
|
903
|
-
export {
|
|
904
|
-
FindAndReplace,
|
|
905
|
-
FindAndReplacePlugin,
|
|
906
|
-
FindAndReplacePluginKey,
|
|
907
|
-
createSearchRegex,
|
|
908
|
-
currentResultClass,
|
|
909
|
-
index_default as default,
|
|
910
|
-
findNextIndex,
|
|
911
|
-
resultClass,
|
|
912
|
-
searchDocument,
|
|
913
|
-
searchTextblocks
|
|
914
|
-
};
|
|
915
805
|
//# sourceMappingURL=index.js.map
|