Compare commits
324
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f07212337f | ||
|
|
63852c5934 | ||
|
|
05895dc0dc | ||
|
|
bb7643f697 | ||
|
|
5f7e81aafc | ||
|
|
5f1a71580b | ||
|
|
fa2b70ffc6 | ||
|
|
54385384b9 | ||
|
|
0630245bb6 | ||
|
|
3677e5d08f | ||
|
|
51ca5c0156 | ||
|
|
e7a202a0bf | ||
|
|
7e4820f0b0 | ||
|
|
666788be70 | ||
|
|
68e9572fbb | ||
|
|
5e47353ad4 | ||
|
|
a4e7d90cf1 | ||
|
|
016296cc07 | ||
|
|
9daa56becc | ||
|
|
26b9d029f3 | ||
|
|
dddefc9e30 | ||
|
|
08975f4ba2 | ||
|
|
21902e7a55 | ||
|
|
d610f404e7 | ||
|
|
4f192564f2 | ||
|
|
5d0becf76b | ||
|
|
ad193d81c5 | ||
|
|
b6b03a2091 | ||
|
|
bc8008737c | ||
|
|
94894779cf | ||
|
|
a47f6443f8 | ||
|
|
984b8e3f4a | ||
|
|
14b04ad4b8 | ||
|
|
78dd0a8681 | ||
|
|
fa26367aa9 | ||
|
|
8af5c2c346 | ||
|
|
4cfaea4621 | ||
|
|
8ce9d184cb | ||
|
|
287e619967 | ||
|
|
52a8a447aa | ||
|
|
0565a778d8 | ||
|
|
577ff63eca | ||
|
|
c7b56ff295 | ||
|
|
01624f1387 | ||
|
|
f4565a677b | ||
|
|
3bd908d963 | ||
|
|
3d71f51435 | ||
|
|
09f03381fd | ||
|
|
ab4f23ab86 | ||
|
|
b8e689b3fe | ||
|
|
9ae5663423 | ||
|
|
eb9d586bdb | ||
|
|
24ba0b86b1 | ||
|
|
ae00bcbeca | ||
|
|
6fa708982b | ||
|
|
afaef6a1ef | ||
|
|
bf1dd6986f | ||
|
|
496f110465 | ||
|
|
fc3dbc1aad | ||
|
|
88eb599d4e | ||
|
|
192fd7c28c | ||
|
|
803ba735ca | ||
|
|
014ac37704 | ||
|
|
793d5c79fb | ||
|
|
9d79cc89ec | ||
|
|
eddec90cb1 | ||
|
|
5cfd7b6c6a | ||
|
|
bb0dc07d22 | ||
|
|
ad651a22fa | ||
|
|
35681dd3b2 | ||
|
|
f9c53fe147 | ||
|
|
107189cd5f | ||
|
|
2233ac9b1d | ||
|
|
5d4f38e57a | ||
|
|
b4a76fee6d | ||
|
|
9acf3b8e7f | ||
|
|
2f32b9d61e | ||
|
|
2f08f6f6eb | ||
|
|
34d272afa9 | ||
|
|
1d959af16e | ||
|
|
7eed283b11 | ||
|
|
efab968153 | ||
|
|
7c8e89923d | ||
|
|
d8c33cc34c | ||
|
|
c0acddbfaf | ||
|
|
3dbbf95fbb | ||
|
|
30edd837e1 | ||
|
|
b5654c5a01 | ||
|
|
ce6f9b8e8c | ||
|
|
c0111fec29 | ||
|
|
1f8371e0a3 | ||
|
|
293d89a4cb | ||
|
|
b79d432deb | ||
|
|
99148d5956 | ||
|
|
60860aa202 | ||
|
|
b1a0bb16db | ||
|
|
2f5d838e0f | ||
|
|
e9aedce4ab | ||
|
|
19ad00c329 | ||
|
|
2913e462ec | ||
|
|
a3682af6e4 | ||
|
|
9e42d3a530 | ||
|
|
0089c0af08 | ||
|
|
868da835b6 | ||
|
|
d388e48a99 | ||
|
|
6c267e68d3 | ||
|
|
25dfaab2a1 | ||
|
|
ba016d798e | ||
|
|
ac961f1d40 | ||
|
|
6538c00742 | ||
|
|
3980b9e580 | ||
|
|
5a886aa9fb | ||
|
|
248e149701 | ||
|
|
529c332159 | ||
|
|
0c8e93bb33 | ||
|
|
a3a508d193 | ||
|
|
75a12dda17 | ||
|
|
ff53705bfa | ||
|
|
fc66bbfb26 | ||
|
|
d203a62834 | ||
|
|
6776f82952 | ||
|
|
00ddd8185c | ||
|
|
b62d4814c3 | ||
|
|
d8df893593 | ||
|
|
bc2f5ae746 | ||
|
|
5e3561aedc | ||
|
|
7c50dc9990 | ||
|
|
b1931d0b3b | ||
|
|
13e426b14c | ||
|
|
e04be692e2 | ||
|
|
d48af6dc1b | ||
|
|
85e55ebf5a | ||
|
|
2f25793609 | ||
|
|
5e94f8abce | ||
|
|
c383674cd8 | ||
|
|
943961ca18 | ||
|
|
569f36d01b | ||
|
|
aca60c02c0 | ||
|
|
0f3ae5ac5b | ||
|
|
ba13ccdb11 | ||
|
|
6a911e4ec3 | ||
|
|
1df98c028d | ||
|
|
6b28a4ee5a | ||
|
|
62d92b70dd | ||
|
|
64d706f114 | ||
|
|
5c5a80923a | ||
|
|
17dd108ce1 | ||
|
|
b906de86db | ||
|
|
8be321fd73 | ||
|
|
c9d4edbb5b | ||
|
|
b3beca0e8d | ||
|
|
ea94771624 | ||
|
|
931ce5f590 | ||
|
|
8869dd5f32 | ||
|
|
45005d2783 | ||
|
|
7bf7102b98 | ||
|
|
e0488a8d3b | ||
|
|
893005cb88 | ||
|
|
3940f8c2a7 | ||
|
|
11d35bd065 | ||
|
|
5d171066c2 | ||
|
|
c37e46e1ad | ||
|
|
47f80317e4 | ||
|
|
2bb35fafb8 | ||
|
|
8c0589df7f | ||
|
|
32da6548c8 | ||
|
|
ae31ad218c | ||
|
|
5dc67db403 | ||
|
|
eddcc5a42e | ||
|
|
44efc9d745 | ||
|
|
95f1e711da | ||
|
|
c2c8b1d1f1 | ||
|
|
bd604063e6 | ||
|
|
40954ae2dc | ||
|
|
498af9b07b | ||
|
|
0d40b7aca0 | ||
|
|
76fbb56107 | ||
|
|
ff6ad4b41e | ||
|
|
fc4e17d30f | ||
|
|
d3f5738158 | ||
|
|
faec27d7b0 | ||
|
|
de9b07f0b9 | ||
|
|
5c348078c2 | ||
|
|
a6c8ce32c3 | ||
|
|
61f480ca4e | ||
|
|
df01c7e9e1 | ||
|
|
8f0979d6a2 | ||
|
|
ad58ec2c79 | ||
|
|
672e01c703 | ||
|
|
d97a659f92 | ||
|
|
c63d048dd3 | ||
|
|
37a68a9c5e | ||
|
|
32f5ac48c7 | ||
|
|
d658dba53c | ||
|
|
3da2d53acb | ||
|
|
98def949ec | ||
|
|
1aa6f03b3c | ||
|
|
637ff3aa88 | ||
|
|
8b47dedbea | ||
|
|
77d25eed14 | ||
|
|
479e302027 | ||
|
|
590c2ab2e2 | ||
|
|
cff35c743f | ||
|
|
f434b1fc28 | ||
|
|
6a6bb625db | ||
|
|
cf0f4cb339 | ||
|
|
d61e3f5521 | ||
|
|
37cc65ec7e | ||
|
|
e2de08ca86 | ||
|
|
47473bc5f4 | ||
|
|
ed3f9517fd | ||
|
|
5727643852 | ||
|
|
fdc939fe0a | ||
|
|
799dcf618b | ||
|
|
a6ac7bdd41 | ||
|
|
d73e2b063a | ||
|
|
f977a813db | ||
|
|
04b6a9762f | ||
|
|
feb4f49859 | ||
|
|
9a670805c4 | ||
|
|
4954c738da | ||
|
|
c67885919b | ||
|
|
ffafccdba3 | ||
|
|
30bedf3913 | ||
|
|
42f7bca37e | ||
|
|
823f11426b | ||
|
|
30e0e6adaf | ||
|
|
2ebe0bd513 | ||
|
|
8da6222a89 | ||
|
|
dd44f4e2e8 | ||
|
|
353c681d51 | ||
|
|
e3f88791da | ||
|
|
f00dab5cc0 | ||
|
|
94018faafc | ||
|
|
664cc65ab0 | ||
|
|
c19c25d113 | ||
|
|
8a8679eb78 | ||
|
|
7f7556cf6a | ||
|
|
eec3be362b | ||
|
|
970bc85b78 | ||
|
|
a478c1a829 | ||
|
|
50abfe8f0c | ||
|
|
4e138e6f99 | ||
|
|
41b96ec8d6 | ||
|
|
730ff8f0ea | ||
|
|
994932b8e3 | ||
|
|
fe59b9cdb5 | ||
|
|
35dc3ab790 | ||
|
|
20f08ab8c1 | ||
|
|
e7cfcbdc66 | ||
|
|
117a783f21 | ||
|
|
9e677a2c1e | ||
|
|
a46ca0705f | ||
|
|
6c9560cd4e | ||
|
|
7c0595f9a9 | ||
|
|
92bbba5e50 | ||
|
|
e59cbc61cf | ||
|
|
2f51ad5180 | ||
|
|
9f8e5582d5 | ||
|
|
da11cf2669 | ||
|
|
396e158ecd | ||
|
|
da027f46cf | ||
|
|
ba182f8275 | ||
|
|
6296e7911c | ||
|
|
68c41e9b66 | ||
|
|
f3ec774e6b | ||
|
|
0d64a88a46 | ||
|
|
ed0fb0110a | ||
|
|
557da351c4 | ||
|
|
50648d6ed9 | ||
|
|
18b2308887 | ||
|
|
6b78b42469 | ||
|
|
7eee5809bb | ||
|
|
966a2f9bfe | ||
|
|
4e51eee4f8 | ||
|
|
e3a1bce113 | ||
|
|
3a232c44eb | ||
|
|
09af91a936 | ||
|
|
6ccbefc3f8 | ||
|
|
e6861ab6fa | ||
|
|
8a331bc943 | ||
|
|
73cc1b571b | ||
|
|
816f8c45eb | ||
|
|
5d3d58e8f4 | ||
|
|
b556fd40b5 | ||
|
|
4db3982c08 | ||
|
|
a52c47ebee | ||
|
|
e8ea298ee4 | ||
|
|
8fcf007a73 | ||
|
|
fb6e2f17cb | ||
|
|
633705e604 | ||
|
|
901ec15bc1 | ||
|
|
c46fba951e | ||
|
|
5e675664c6 | ||
|
|
5c7db11d9b | ||
|
|
713a0c15ef | ||
|
|
60cc551c4d | ||
|
|
2fdecb5d19 | ||
|
|
54d953cfff | ||
|
|
d3b60bcb46 | ||
|
|
879c082587 | ||
|
|
7ff114bbef | ||
|
|
08701ca5aa | ||
|
|
be5a7b56c8 | ||
|
|
b6ec7945f4 | ||
|
|
a64d1bbb6a | ||
|
|
9a129e7cab | ||
|
|
6ad758830a | ||
|
|
5c317644bd | ||
|
|
faf2fd2287 | ||
|
|
dece22ba92 | ||
|
|
2e5d7675ba | ||
|
|
1d37483e73 | ||
|
|
36a8447bb3 | ||
|
|
b378f2b094 | ||
|
|
fdadfaab45 | ||
|
|
ce4ca192ee | ||
|
|
f45eec25f4 | ||
|
|
f07aaf1979 | ||
|
|
77b9d2fd1b | ||
|
|
926d6bccd7 | ||
|
|
98dbb48c59 | ||
|
|
bddc1d3fab | ||
|
|
b98688b190 |
@@ -0,0 +1,16 @@
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index b781835b37a1262510bdd66d8d3b399a076e9d68..312c314186d85bb3bc2ab2620e99bca259ef7167 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -44,10 +44,9 @@ import { z as z2 } from "zod/v4";
|
||||
|
||||
// src/tool/types.ts
|
||||
import { z } from "zod/v4";
|
||||
-var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
||||
+var LATEST_PROTOCOL_VERSION = "2025-06-18";
|
||||
var SUPPORTED_PROTOCOL_VERSIONS = [
|
||||
LATEST_PROTOCOL_VERSION,
|
||||
- "2025-06-18",
|
||||
"2025-03-26",
|
||||
"2024-11-05"
|
||||
];
|
||||
@@ -0,0 +1,831 @@
|
||||
diff --git a/dist/index.cjs b/dist/index.cjs
|
||||
index 39215ae..b44cb76 100644
|
||||
--- a/dist/index.cjs
|
||||
+++ b/dist/index.cjs
|
||||
@@ -187,16 +187,23 @@ Helper function that returns a transaction spec which inserts a
|
||||
completion's text in the main selection range, and any other
|
||||
selection range that has the same text in front of it.
|
||||
*/
|
||||
-function insertCompletionText(state$1, text, from, to) {
|
||||
+function insertCompletionText(state$1, text, from, to, extend) {
|
||||
let { main } = state$1.selection, fromOff = from - main.from, toOff = to - main.from;
|
||||
return Object.assign(Object.assign({}, state$1.changeByRange(range => {
|
||||
if (range != main && from != to &&
|
||||
state$1.sliceDoc(range.from + fromOff, range.from + toOff) != state$1.sliceDoc(from, to))
|
||||
return { range };
|
||||
- let lines = state$1.toText(text);
|
||||
+ let change = {
|
||||
+ from: range.from + fromOff,
|
||||
+ to: to == main.from ? range.to : range.from + toOff,
|
||||
+ insert: text instanceof state.Text ? text : state$1.toText(text),
|
||||
+ };
|
||||
+ if (extend) {
|
||||
+ extend(state$1, change);
|
||||
+ }
|
||||
return {
|
||||
- changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
|
||||
- range: state.EditorSelection.cursor(range.from + fromOff + lines.length)
|
||||
+ changes: change,
|
||||
+ range: state.EditorSelection.cursor(change.from + change.insert.length)
|
||||
};
|
||||
})), { scrollIntoView: true, userEvent: "input.complete" });
|
||||
}
|
||||
@@ -389,7 +396,9 @@ const completionConfig = state.Facet.define({
|
||||
filterStrict: false,
|
||||
compareCompletions: (a, b) => a.label.localeCompare(b.label),
|
||||
interactionDelay: 75,
|
||||
- updateSyncTime: 100
|
||||
+ updateSyncTime: 100,
|
||||
+ // overleaf: default to at top which is default CM6 behaviour
|
||||
+ unfilteredResultsAtEnd: false
|
||||
}, {
|
||||
defaultKeymap: (a, b) => a && b,
|
||||
closeOnBlur: (a, b) => a && b,
|
||||
@@ -744,6 +753,7 @@ function score(option) {
|
||||
(option.type ? 1 : 0);
|
||||
}
|
||||
function sortOptions(active, state) {
|
||||
+ var _a;
|
||||
let options = [];
|
||||
let sections = null;
|
||||
let addOption = (option) => {
|
||||
@@ -763,7 +773,8 @@ function sortOptions(active, state) {
|
||||
let getMatch = a.result.getMatch;
|
||||
if (a.result.filter === false) {
|
||||
for (let option of a.result.options) {
|
||||
- addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
|
||||
+ let defaultScore = conf.unfilteredResultsAtEnd ? -1e9 : 1e9;
|
||||
+ addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], defaultScore - options.length));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -790,15 +801,42 @@ function sortOptions(active, state) {
|
||||
}
|
||||
}
|
||||
let result = [], prev = null;
|
||||
+ const priorityIndices = new Map();
|
||||
let compare = conf.compareCompletions;
|
||||
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
|
||||
+ // overleaf: Deduplicate results with dedup options
|
||||
+ // The goal is to keep only the highest priority option, in the
|
||||
+ // highest scoring position.
|
||||
+ const key = (_a = opt.completion.deduplicate) === null || _a === void 0 ? void 0 : _a.key;
|
||||
+ if (key) {
|
||||
+ // Handle merging specifically for deduplicated items item
|
||||
+ const currentOptionIndex = priorityIndices.get(key);
|
||||
+ if (currentOptionIndex === undefined) {
|
||||
+ priorityIndices.set(key, result.length);
|
||||
+ result.push(opt);
|
||||
+ prev = opt.completion;
|
||||
+ }
|
||||
+ else {
|
||||
+ if (result[currentOptionIndex].completion.deduplicate.priority < opt.completion.deduplicate.priority) {
|
||||
+ result[currentOptionIndex] = opt;
|
||||
+ if (currentOptionIndex === result.length - 1) {
|
||||
+ prev = opt.completion;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ continue;
|
||||
+ }
|
||||
+ // overleaf: end
|
||||
let cur = opt.completion;
|
||||
- if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
|
||||
- (prev.type != null && cur.type != null && prev.type != cur.type) ||
|
||||
- prev.apply != cur.apply || prev.boost != cur.boost)
|
||||
+ if (!prev || prev.label != cur.label)
|
||||
+ result.push(opt);
|
||||
+ // overleaf: we're already handling deduplication, so skip extra merges
|
||||
+ else if (prev.deduplicate)
|
||||
result.push(opt);
|
||||
else if (score(opt.completion) > score(prev))
|
||||
result[result.length - 1] = opt;
|
||||
+ else if (opt.completion.info)
|
||||
+ result[result.length - 1] = opt;
|
||||
prev = opt.completion;
|
||||
}
|
||||
return result;
|
||||
@@ -817,8 +855,9 @@ class CompletionDialog {
|
||||
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
|
||||
}
|
||||
static build(active, state, id, prev, conf, didSetActive) {
|
||||
- if (prev && !didSetActive && active.some(s => s.isPending))
|
||||
- return prev.setDisabled();
|
||||
+ // Overleaf: avoid setting the previous completion state to disabled while completion sources are pending
|
||||
+ // if (prev && !didSetActive && active.some(s => s.isPending))
|
||||
+ // return prev.setDisabled()
|
||||
let options = sortOptions(active, state);
|
||||
if (!options.length)
|
||||
return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
|
||||
@@ -1017,13 +1056,14 @@ const completionState = state.StateField.define({
|
||||
view.EditorView.contentAttributes.from(f, state => state.attrs)
|
||||
]
|
||||
});
|
||||
+const getCompletionTooltip = (state) => { var _a; return (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.tooltip; };
|
||||
function applyCompletion(view, option) {
|
||||
const apply = option.completion.apply || option.completion.label;
|
||||
let result = view.state.field(completionState).active.find(a => a.source == option.source);
|
||||
if (!(result instanceof ActiveResult))
|
||||
return false;
|
||||
if (typeof apply == "string")
|
||||
- view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
|
||||
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to, option.completion.extend)), { annotations: pickedCompletion.of(option.completion) }));
|
||||
else
|
||||
apply(view, option.completion, result.from, result.to);
|
||||
return true;
|
||||
@@ -1559,20 +1599,42 @@ interpreted as indicating a placeholder.
|
||||
function snippet(template) {
|
||||
let snippet = Snippet.parse(template);
|
||||
return (editor, completion, from, to) => {
|
||||
- let { text, ranges } = snippet.instantiate(editor.state, from);
|
||||
- let { main } = editor.state.selection;
|
||||
- let spec = {
|
||||
- changes: { from, to: to == main.from ? main.to : to, insert: state.Text.of(text) },
|
||||
- scrollIntoView: true,
|
||||
- annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined
|
||||
- };
|
||||
+ let { main } = editor.state.selection, fromOff = from - main.from, toOff = to - main.from;
|
||||
+ let ranges = [];
|
||||
+ let totalOffset = 0;
|
||||
+ let spec = Object.assign(Object.assign({}, editor.state.changeByRange(range => {
|
||||
+ if (range != main && from != to &&
|
||||
+ editor.state.sliceDoc(range.from + fromOff, range.from + toOff) != editor.state.sliceDoc(from, to))
|
||||
+ return { range };
|
||||
+ let { text, ranges: fieldRanges } = snippet.instantiate(editor.state, range.from + fromOff);
|
||||
+ let change = {
|
||||
+ from: range.from + fromOff,
|
||||
+ to: range.from + toOff,
|
||||
+ insert: state.Text.of(text)
|
||||
+ };
|
||||
+ let originalTo = change.to;
|
||||
+ let offset = change.insert.length + fromOff;
|
||||
+ if (completion.extend) {
|
||||
+ completion.extend(editor.state, change);
|
||||
+ offset += originalTo - change.to;
|
||||
+ }
|
||||
+ for (const fieldRange of fieldRanges) {
|
||||
+ ranges.push(new FieldRange(fieldRange.field, fieldRange.from + totalOffset, fieldRange.to + totalOffset));
|
||||
+ }
|
||||
+ totalOffset += offset;
|
||||
+ return {
|
||||
+ changes: change,
|
||||
+ range: state.EditorSelection.cursor(change.from + change.insert.length)
|
||||
+ };
|
||||
+ })), { scrollIntoView: true, annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined, effects: [] });
|
||||
if (ranges.length)
|
||||
spec.selection = fieldSelection(ranges, 0);
|
||||
if (ranges.some(r => r.field > 0)) {
|
||||
let active = new ActiveSnippet(ranges, 0);
|
||||
- let effects = spec.effects = [setActive.of(active)];
|
||||
- if (editor.state.field(snippetState, false) === undefined)
|
||||
- effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
|
||||
+ spec.effects.push(setActive.of(active));
|
||||
+ if (editor.state.field(snippetState, false) === undefined) {
|
||||
+ spec.effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
|
||||
+ }
|
||||
}
|
||||
editor.dispatch(editor.state.update(spec));
|
||||
};
|
||||
@@ -1746,7 +1808,8 @@ const completeAnyWord = context => {
|
||||
const defaults = {
|
||||
brackets: ["(", "[", "{", "'", '"'],
|
||||
before: ")]}:;>",
|
||||
- stringPrefixes: []
|
||||
+ stringPrefixes: [],
|
||||
+ buildInsert: (state, range, open, close) => open + close,
|
||||
};
|
||||
const closeBracketEffect = state.StateEffect.define({
|
||||
map(value, mapping) {
|
||||
@@ -1854,8 +1917,8 @@ function insertBracket(state$1, bracket) {
|
||||
for (let tok of tokens) {
|
||||
let closed = closing(state.codePointAt(tok, 0));
|
||||
if (bracket == tok)
|
||||
- return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
|
||||
- : handleOpen(state$1, tok, closed, conf.before || defaults.before);
|
||||
+ return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok) > -1, tokens.indexOf(tok + tok + tok) > -1, conf)
|
||||
+ : handleOpen(state$1, tok, closed, conf.before || defaults.before, conf);
|
||||
if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
|
||||
return handleClose(state$1, tok, closed);
|
||||
}
|
||||
@@ -1877,17 +1940,21 @@ function prevChar(doc, pos) {
|
||||
let prev = doc.sliceString(pos - 2, pos);
|
||||
return state.codePointSize(state.codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
|
||||
}
|
||||
-function handleOpen(state$1, open, close, closeBefore) {
|
||||
+function handleOpen(state$1, open, close, closeBefore, config) {
|
||||
+ let buildInsert = config.buildInsert || defaults.buildInsert;
|
||||
let dont = null, changes = state$1.changeByRange(range => {
|
||||
+ var _a;
|
||||
if (!range.empty)
|
||||
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
|
||||
effects: closeBracketEffect.of(range.to + open.length),
|
||||
range: state.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
|
||||
let next = nextChar(state$1.doc, range.head);
|
||||
- if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
|
||||
- return { changes: { insert: open + close, from: range.head },
|
||||
- effects: closeBracketEffect.of(range.head + open.length),
|
||||
+ if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) {
|
||||
+ const insert = (_a = buildInsert(state$1, range, open, close)) !== null && _a !== void 0 ? _a : open + close;
|
||||
+ return { changes: { insert, from: range.head },
|
||||
+ effects: insert === open ? [] : closeBracketEffect.of(range.head + open.length),
|
||||
range: state.EditorSelection.cursor(range.head + open.length) };
|
||||
+ }
|
||||
return { range: dont = range };
|
||||
});
|
||||
return dont ? null : state$1.update(changes, {
|
||||
@@ -1909,18 +1976,36 @@ function handleClose(state$1, _open, close) {
|
||||
}
|
||||
// Handles cases where the open and close token are the same, and
|
||||
// possibly triple quotes (as in `"""abc"""`-style quoting).
|
||||
-function handleSame(state$1, token, allowTriple, config) {
|
||||
+function handleSame(state$1, token, allowDouble, allowTriple, config) {
|
||||
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
|
||||
+ let buildInsert = config.buildInsert || defaults.buildInsert;
|
||||
let dont = null, changes = state$1.changeByRange(range => {
|
||||
+ var _a, _b, _c;
|
||||
if (!range.empty)
|
||||
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
|
||||
effects: closeBracketEffect.of(range.to + token.length),
|
||||
range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
|
||||
let pos = range.head, next = nextChar(state$1.doc, pos), start;
|
||||
- if (next == token) {
|
||||
+ if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
||||
+ (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
|
||||
+ nodeStart(state$1, start)) {
|
||||
+ return { changes: { insert: token + token + token + token, from: pos },
|
||||
+ effects: closeBracketEffect.of(pos + token.length),
|
||||
+ range: state.EditorSelection.cursor(pos + token.length) };
|
||||
+ }
|
||||
+ else if (allowDouble && state$1.sliceDoc(pos - token.length, pos) == token &&
|
||||
+ (start = canStartStringAt(state$1, pos - token.length, stringPrefixes)) > -1 &&
|
||||
+ nodeStart(state$1, start)) {
|
||||
+ let insert = (_a = buildInsert(state$1, range, token, token)) !== null && _a !== void 0 ? _a : token + token;
|
||||
+ return { changes: { insert, from: pos },
|
||||
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
|
||||
+ range: state.EditorSelection.cursor(pos + token.length) };
|
||||
+ }
|
||||
+ else if (next == token) {
|
||||
if (nodeStart(state$1, pos)) {
|
||||
- return { changes: { insert: token + token, from: pos },
|
||||
- effects: closeBracketEffect.of(pos + token.length),
|
||||
+ let insert = (_b = buildInsert(state$1, range, token, token)) !== null && _b !== void 0 ? _b : token + token;
|
||||
+ return { changes: { insert, from: pos },
|
||||
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
|
||||
range: state.EditorSelection.cursor(pos + token.length) };
|
||||
}
|
||||
else if (closedBracketAt(state$1, pos)) {
|
||||
@@ -1930,18 +2015,13 @@ function handleSame(state$1, token, allowTriple, config) {
|
||||
range: state.EditorSelection.cursor(pos + content.length) };
|
||||
}
|
||||
}
|
||||
- else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
||||
- (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
|
||||
- nodeStart(state$1, start)) {
|
||||
- return { changes: { insert: token + token + token + token, from: pos },
|
||||
- effects: closeBracketEffect.of(pos + token.length),
|
||||
- range: state.EditorSelection.cursor(pos + token.length) };
|
||||
- }
|
||||
else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
|
||||
- if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes))
|
||||
- return { changes: { insert: token + token, from: pos },
|
||||
- effects: closeBracketEffect.of(pos + token.length),
|
||||
+ if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes)) {
|
||||
+ const insert = (_c = buildInsert(state$1, range, token, token)) !== null && _c !== void 0 ? _c : token + token;
|
||||
+ return { changes: { insert, from: pos },
|
||||
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
|
||||
range: state.EditorSelection.cursor(pos + token.length) };
|
||||
+ }
|
||||
}
|
||||
return { range: dont = range };
|
||||
});
|
||||
@@ -2086,6 +2166,7 @@ exports.completionKeymap = completionKeymap;
|
||||
exports.completionStatus = completionStatus;
|
||||
exports.currentCompletions = currentCompletions;
|
||||
exports.deleteBracketPair = deleteBracketPair;
|
||||
+exports.getCompletionTooltip = getCompletionTooltip;
|
||||
exports.hasNextSnippetField = hasNextSnippetField;
|
||||
exports.hasPrevSnippetField = hasPrevSnippetField;
|
||||
exports.ifIn = ifIn;
|
||||
@@ -2093,8 +2174,10 @@ exports.ifNotIn = ifNotIn;
|
||||
exports.insertBracket = insertBracket;
|
||||
exports.insertCompletionText = insertCompletionText;
|
||||
exports.moveCompletionSelection = moveCompletionSelection;
|
||||
+exports.nextChar = nextChar;
|
||||
exports.nextSnippetField = nextSnippetField;
|
||||
exports.pickedCompletion = pickedCompletion;
|
||||
+exports.prevChar = prevChar;
|
||||
exports.prevSnippetField = prevSnippetField;
|
||||
exports.selectedCompletion = selectedCompletion;
|
||||
exports.selectedCompletionIndex = selectedCompletionIndex;
|
||||
diff --git a/dist/index.d.cts b/dist/index.d.cts
|
||||
index b57b8f6..fce47ab 100644
|
||||
--- a/dist/index.d.cts
|
||||
+++ b/dist/index.d.cts
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as _codemirror_state from '@codemirror/state';
|
||||
-import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
|
||||
-import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
|
||||
+import { EditorState, Text, ChangeDesc, TransactionSpec, StateCommand, Transaction, Facet, SelectionRange, Extension, StateEffect } from '@codemirror/state';
|
||||
+import { EditorView, Rect, KeyBinding, Tooltip, Command } from '@codemirror/view';
|
||||
import * as _lezer_common from '@lezer/common';
|
||||
|
||||
/**
|
||||
@@ -73,6 +73,19 @@ interface Completion {
|
||||
a `{name}` object.
|
||||
*/
|
||||
section?: string | CompletionSection;
|
||||
+ /**
|
||||
+ Can be used to alter the change created when the completion is applied
|
||||
+ */
|
||||
+ extend?: ExtendCompletion;
|
||||
+ /**
|
||||
+ If multiple sources return the same result, use this field to specifiy a
|
||||
+ deduplication key as well as a priority. For each unique key, only the
|
||||
+ completion with the highest priority will be shown.
|
||||
+ */
|
||||
+ deduplicate?: {
|
||||
+ key: string;
|
||||
+ priority: number;
|
||||
+ };
|
||||
}
|
||||
/**
|
||||
The type returned from
|
||||
@@ -306,12 +319,17 @@ This annotation is added to transactions that are produced by
|
||||
picking a completion.
|
||||
*/
|
||||
declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
|
||||
+type ExtendCompletion = (state: EditorState, change: {
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ insert: string | Text;
|
||||
+}) => void;
|
||||
/**
|
||||
Helper function that returns a transaction spec which inserts a
|
||||
completion's text in the main selection range, and any other
|
||||
selection range that has the same text in front of it.
|
||||
*/
|
||||
-declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
|
||||
+declare function insertCompletionText(state: EditorState, text: string | Text, from: number, to: number, extend?: ExtendCompletion): TransactionSpec;
|
||||
|
||||
interface CompletionConfig {
|
||||
/**
|
||||
@@ -441,6 +459,10 @@ interface CompletionConfig {
|
||||
milliseconds.
|
||||
*/
|
||||
updateSyncTime?: number;
|
||||
+ /**
|
||||
+ overleaf: Move unfiltered results after the filtered ones
|
||||
+ */
|
||||
+ unfilteredResultsAtEnd?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,6 +536,8 @@ applies the snippet.
|
||||
*/
|
||||
declare function snippetCompletion(template: string, completion: Completion): Completion;
|
||||
|
||||
+declare const getCompletionTooltip: (state: EditorState) => Tooltip | undefined | null;
|
||||
+
|
||||
/**
|
||||
Returns a command that moves the completion selection forward or
|
||||
backward by the given amount.
|
||||
@@ -562,6 +586,11 @@ interface CloseBracketConfig {
|
||||
these prefixes before the opening quote.
|
||||
*/
|
||||
stringPrefixes?: string[];
|
||||
+ /**
|
||||
+ An optional callback for overriding the content that's inserted
|
||||
+ based on surrounding characters
|
||||
+ */
|
||||
+ buildInsert?: (state: EditorState, range: SelectionRange, open: string, close: string) => string;
|
||||
}
|
||||
/**
|
||||
Extension to enable bracket-closing behavior. When a closeable
|
||||
@@ -593,6 +622,8 @@ to programmatically insert brackets—the
|
||||
take care of running this for user input.)
|
||||
*/
|
||||
declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
|
||||
+declare function nextChar(doc: Text, pos: number): string;
|
||||
+declare function prevChar(doc: Text, pos: number): string;
|
||||
|
||||
/**
|
||||
Returns an extension that enables autocompletion.
|
||||
@@ -636,4 +667,5 @@ the currently selected completion.
|
||||
*/
|
||||
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
|
||||
|
||||
-export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
||||
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
||||
+export type { CloseBracketConfig, Completion, CompletionInfo, CompletionResult, CompletionSection, CompletionSource };
|
||||
diff --git a/dist/index.d.ts b/dist/index.d.ts
|
||||
index b57b8f6..fce47ab 100644
|
||||
--- a/dist/index.d.ts
|
||||
+++ b/dist/index.d.ts
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as _codemirror_state from '@codemirror/state';
|
||||
-import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
|
||||
-import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
|
||||
+import { EditorState, Text, ChangeDesc, TransactionSpec, StateCommand, Transaction, Facet, SelectionRange, Extension, StateEffect } from '@codemirror/state';
|
||||
+import { EditorView, Rect, KeyBinding, Tooltip, Command } from '@codemirror/view';
|
||||
import * as _lezer_common from '@lezer/common';
|
||||
|
||||
/**
|
||||
@@ -73,6 +73,19 @@ interface Completion {
|
||||
a `{name}` object.
|
||||
*/
|
||||
section?: string | CompletionSection;
|
||||
+ /**
|
||||
+ Can be used to alter the change created when the completion is applied
|
||||
+ */
|
||||
+ extend?: ExtendCompletion;
|
||||
+ /**
|
||||
+ If multiple sources return the same result, use this field to specifiy a
|
||||
+ deduplication key as well as a priority. For each unique key, only the
|
||||
+ completion with the highest priority will be shown.
|
||||
+ */
|
||||
+ deduplicate?: {
|
||||
+ key: string;
|
||||
+ priority: number;
|
||||
+ };
|
||||
}
|
||||
/**
|
||||
The type returned from
|
||||
@@ -306,12 +319,17 @@ This annotation is added to transactions that are produced by
|
||||
picking a completion.
|
||||
*/
|
||||
declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
|
||||
+type ExtendCompletion = (state: EditorState, change: {
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ insert: string | Text;
|
||||
+}) => void;
|
||||
/**
|
||||
Helper function that returns a transaction spec which inserts a
|
||||
completion's text in the main selection range, and any other
|
||||
selection range that has the same text in front of it.
|
||||
*/
|
||||
-declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
|
||||
+declare function insertCompletionText(state: EditorState, text: string | Text, from: number, to: number, extend?: ExtendCompletion): TransactionSpec;
|
||||
|
||||
interface CompletionConfig {
|
||||
/**
|
||||
@@ -441,6 +459,10 @@ interface CompletionConfig {
|
||||
milliseconds.
|
||||
*/
|
||||
updateSyncTime?: number;
|
||||
+ /**
|
||||
+ overleaf: Move unfiltered results after the filtered ones
|
||||
+ */
|
||||
+ unfilteredResultsAtEnd?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,6 +536,8 @@ applies the snippet.
|
||||
*/
|
||||
declare function snippetCompletion(template: string, completion: Completion): Completion;
|
||||
|
||||
+declare const getCompletionTooltip: (state: EditorState) => Tooltip | undefined | null;
|
||||
+
|
||||
/**
|
||||
Returns a command that moves the completion selection forward or
|
||||
backward by the given amount.
|
||||
@@ -562,6 +586,11 @@ interface CloseBracketConfig {
|
||||
these prefixes before the opening quote.
|
||||
*/
|
||||
stringPrefixes?: string[];
|
||||
+ /**
|
||||
+ An optional callback for overriding the content that's inserted
|
||||
+ based on surrounding characters
|
||||
+ */
|
||||
+ buildInsert?: (state: EditorState, range: SelectionRange, open: string, close: string) => string;
|
||||
}
|
||||
/**
|
||||
Extension to enable bracket-closing behavior. When a closeable
|
||||
@@ -593,6 +622,8 @@ to programmatically insert brackets—the
|
||||
take care of running this for user input.)
|
||||
*/
|
||||
declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
|
||||
+declare function nextChar(doc: Text, pos: number): string;
|
||||
+declare function prevChar(doc: Text, pos: number): string;
|
||||
|
||||
/**
|
||||
Returns an extension that enables autocompletion.
|
||||
@@ -636,4 +667,5 @@ the currently selected completion.
|
||||
*/
|
||||
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
|
||||
|
||||
-export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
||||
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
||||
+export type { CloseBracketConfig, Completion, CompletionInfo, CompletionResult, CompletionSection, CompletionSource };
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 4729223..9361a53 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -1,4 +1,4 @@
|
||||
-import { Annotation, StateEffect, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Text, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
|
||||
+import { Annotation, StateEffect, Text, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
|
||||
import { Direction, logException, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
|
||||
import { syntaxTree, indentUnit } from '@codemirror/language';
|
||||
|
||||
@@ -185,16 +185,23 @@ Helper function that returns a transaction spec which inserts a
|
||||
completion's text in the main selection range, and any other
|
||||
selection range that has the same text in front of it.
|
||||
*/
|
||||
-function insertCompletionText(state, text, from, to) {
|
||||
+function insertCompletionText(state, text, from, to, extend) {
|
||||
let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from;
|
||||
return Object.assign(Object.assign({}, state.changeByRange(range => {
|
||||
if (range != main && from != to &&
|
||||
state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
|
||||
return { range };
|
||||
- let lines = state.toText(text);
|
||||
+ let change = {
|
||||
+ from: range.from + fromOff,
|
||||
+ to: to == main.from ? range.to : range.from + toOff,
|
||||
+ insert: text instanceof Text ? text : state.toText(text),
|
||||
+ };
|
||||
+ if (extend) {
|
||||
+ extend(state, change);
|
||||
+ }
|
||||
return {
|
||||
- changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
|
||||
- range: EditorSelection.cursor(range.from + fromOff + lines.length)
|
||||
+ changes: change,
|
||||
+ range: EditorSelection.cursor(change.from + change.insert.length)
|
||||
};
|
||||
})), { scrollIntoView: true, userEvent: "input.complete" });
|
||||
}
|
||||
@@ -387,7 +394,9 @@ const completionConfig = /*@__PURE__*/Facet.define({
|
||||
filterStrict: false,
|
||||
compareCompletions: (a, b) => a.label.localeCompare(b.label),
|
||||
interactionDelay: 75,
|
||||
- updateSyncTime: 100
|
||||
+ updateSyncTime: 100,
|
||||
+ // overleaf: default to at top which is default CM6 behaviour
|
||||
+ unfilteredResultsAtEnd: false
|
||||
}, {
|
||||
defaultKeymap: (a, b) => a && b,
|
||||
closeOnBlur: (a, b) => a && b,
|
||||
@@ -742,6 +751,7 @@ function score(option) {
|
||||
(option.type ? 1 : 0);
|
||||
}
|
||||
function sortOptions(active, state) {
|
||||
+ var _a;
|
||||
let options = [];
|
||||
let sections = null;
|
||||
let addOption = (option) => {
|
||||
@@ -761,7 +771,8 @@ function sortOptions(active, state) {
|
||||
let getMatch = a.result.getMatch;
|
||||
if (a.result.filter === false) {
|
||||
for (let option of a.result.options) {
|
||||
- addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
|
||||
+ let defaultScore = conf.unfilteredResultsAtEnd ? -1e9 : 1e9;
|
||||
+ addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], defaultScore - options.length));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -788,15 +799,42 @@ function sortOptions(active, state) {
|
||||
}
|
||||
}
|
||||
let result = [], prev = null;
|
||||
+ const priorityIndices = new Map();
|
||||
let compare = conf.compareCompletions;
|
||||
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
|
||||
+ // overleaf: Deduplicate results with dedup options
|
||||
+ // The goal is to keep only the highest priority option, in the
|
||||
+ // highest scoring position.
|
||||
+ const key = (_a = opt.completion.deduplicate) === null || _a === void 0 ? void 0 : _a.key;
|
||||
+ if (key) {
|
||||
+ // Handle merging specifically for deduplicated items item
|
||||
+ const currentOptionIndex = priorityIndices.get(key);
|
||||
+ if (currentOptionIndex === undefined) {
|
||||
+ priorityIndices.set(key, result.length);
|
||||
+ result.push(opt);
|
||||
+ prev = opt.completion;
|
||||
+ }
|
||||
+ else {
|
||||
+ if (result[currentOptionIndex].completion.deduplicate.priority < opt.completion.deduplicate.priority) {
|
||||
+ result[currentOptionIndex] = opt;
|
||||
+ if (currentOptionIndex === result.length - 1) {
|
||||
+ prev = opt.completion;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ continue;
|
||||
+ }
|
||||
+ // overleaf: end
|
||||
let cur = opt.completion;
|
||||
- if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
|
||||
- (prev.type != null && cur.type != null && prev.type != cur.type) ||
|
||||
- prev.apply != cur.apply || prev.boost != cur.boost)
|
||||
+ if (!prev || prev.label != cur.label)
|
||||
+ result.push(opt);
|
||||
+ // overleaf: we're already handling deduplication, so skip extra merges
|
||||
+ else if (prev.deduplicate)
|
||||
result.push(opt);
|
||||
else if (score(opt.completion) > score(prev))
|
||||
result[result.length - 1] = opt;
|
||||
+ else if (opt.completion.info)
|
||||
+ result[result.length - 1] = opt;
|
||||
prev = opt.completion;
|
||||
}
|
||||
return result;
|
||||
@@ -815,8 +853,9 @@ class CompletionDialog {
|
||||
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
|
||||
}
|
||||
static build(active, state, id, prev, conf, didSetActive) {
|
||||
- if (prev && !didSetActive && active.some(s => s.isPending))
|
||||
- return prev.setDisabled();
|
||||
+ // Overleaf: avoid setting the previous completion state to disabled while completion sources are pending
|
||||
+ // if (prev && !didSetActive && active.some(s => s.isPending))
|
||||
+ // return prev.setDisabled()
|
||||
let options = sortOptions(active, state);
|
||||
if (!options.length)
|
||||
return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
|
||||
@@ -1015,13 +1054,14 @@ const completionState = /*@__PURE__*/StateField.define({
|
||||
EditorView.contentAttributes.from(f, state => state.attrs)
|
||||
]
|
||||
});
|
||||
+const getCompletionTooltip = (state) => { var _a; return (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.tooltip; };
|
||||
function applyCompletion(view, option) {
|
||||
const apply = option.completion.apply || option.completion.label;
|
||||
let result = view.state.field(completionState).active.find(a => a.source == option.source);
|
||||
if (!(result instanceof ActiveResult))
|
||||
return false;
|
||||
if (typeof apply == "string")
|
||||
- view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
|
||||
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to, option.completion.extend)), { annotations: pickedCompletion.of(option.completion) }));
|
||||
else
|
||||
apply(view, option.completion, result.from, result.to);
|
||||
return true;
|
||||
@@ -1557,20 +1597,42 @@ interpreted as indicating a placeholder.
|
||||
function snippet(template) {
|
||||
let snippet = Snippet.parse(template);
|
||||
return (editor, completion, from, to) => {
|
||||
- let { text, ranges } = snippet.instantiate(editor.state, from);
|
||||
- let { main } = editor.state.selection;
|
||||
- let spec = {
|
||||
- changes: { from, to: to == main.from ? main.to : to, insert: Text.of(text) },
|
||||
- scrollIntoView: true,
|
||||
- annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined
|
||||
- };
|
||||
+ let { main } = editor.state.selection, fromOff = from - main.from, toOff = to - main.from;
|
||||
+ let ranges = [];
|
||||
+ let totalOffset = 0;
|
||||
+ let spec = Object.assign(Object.assign({}, editor.state.changeByRange(range => {
|
||||
+ if (range != main && from != to &&
|
||||
+ editor.state.sliceDoc(range.from + fromOff, range.from + toOff) != editor.state.sliceDoc(from, to))
|
||||
+ return { range };
|
||||
+ let { text, ranges: fieldRanges } = snippet.instantiate(editor.state, range.from + fromOff);
|
||||
+ let change = {
|
||||
+ from: range.from + fromOff,
|
||||
+ to: range.from + toOff,
|
||||
+ insert: Text.of(text)
|
||||
+ };
|
||||
+ let originalTo = change.to;
|
||||
+ let offset = change.insert.length + fromOff;
|
||||
+ if (completion.extend) {
|
||||
+ completion.extend(editor.state, change);
|
||||
+ offset += originalTo - change.to;
|
||||
+ }
|
||||
+ for (const fieldRange of fieldRanges) {
|
||||
+ ranges.push(new FieldRange(fieldRange.field, fieldRange.from + totalOffset, fieldRange.to + totalOffset));
|
||||
+ }
|
||||
+ totalOffset += offset;
|
||||
+ return {
|
||||
+ changes: change,
|
||||
+ range: EditorSelection.cursor(change.from + change.insert.length)
|
||||
+ };
|
||||
+ })), { scrollIntoView: true, annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined, effects: [] });
|
||||
if (ranges.length)
|
||||
spec.selection = fieldSelection(ranges, 0);
|
||||
if (ranges.some(r => r.field > 0)) {
|
||||
let active = new ActiveSnippet(ranges, 0);
|
||||
- let effects = spec.effects = [setActive.of(active)];
|
||||
- if (editor.state.field(snippetState, false) === undefined)
|
||||
- effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
|
||||
+ spec.effects.push(setActive.of(active));
|
||||
+ if (editor.state.field(snippetState, false) === undefined) {
|
||||
+ spec.effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
|
||||
+ }
|
||||
}
|
||||
editor.dispatch(editor.state.update(spec));
|
||||
};
|
||||
@@ -1744,7 +1806,8 @@ const completeAnyWord = context => {
|
||||
const defaults = {
|
||||
brackets: ["(", "[", "{", "'", '"'],
|
||||
before: ")]}:;>",
|
||||
- stringPrefixes: []
|
||||
+ stringPrefixes: [],
|
||||
+ buildInsert: (state, range, open, close) => open + close,
|
||||
};
|
||||
const closeBracketEffect = /*@__PURE__*/StateEffect.define({
|
||||
map(value, mapping) {
|
||||
@@ -1852,8 +1915,8 @@ function insertBracket(state, bracket) {
|
||||
for (let tok of tokens) {
|
||||
let closed = closing(codePointAt(tok, 0));
|
||||
if (bracket == tok)
|
||||
- return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
|
||||
- : handleOpen(state, tok, closed, conf.before || defaults.before);
|
||||
+ return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok) > -1, tokens.indexOf(tok + tok + tok) > -1, conf)
|
||||
+ : handleOpen(state, tok, closed, conf.before || defaults.before, conf);
|
||||
if (bracket == closed && closedBracketAt(state, state.selection.main.from))
|
||||
return handleClose(state, tok, closed);
|
||||
}
|
||||
@@ -1875,17 +1938,21 @@ function prevChar(doc, pos) {
|
||||
let prev = doc.sliceString(pos - 2, pos);
|
||||
return codePointSize(codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
|
||||
}
|
||||
-function handleOpen(state, open, close, closeBefore) {
|
||||
+function handleOpen(state, open, close, closeBefore, config) {
|
||||
+ let buildInsert = config.buildInsert || defaults.buildInsert;
|
||||
let dont = null, changes = state.changeByRange(range => {
|
||||
+ var _a;
|
||||
if (!range.empty)
|
||||
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
|
||||
effects: closeBracketEffect.of(range.to + open.length),
|
||||
range: EditorSelection.range(range.anchor + open.length, range.head + open.length) };
|
||||
let next = nextChar(state.doc, range.head);
|
||||
- if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
|
||||
- return { changes: { insert: open + close, from: range.head },
|
||||
- effects: closeBracketEffect.of(range.head + open.length),
|
||||
+ if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) {
|
||||
+ const insert = (_a = buildInsert(state, range, open, close)) !== null && _a !== void 0 ? _a : open + close;
|
||||
+ return { changes: { insert, from: range.head },
|
||||
+ effects: insert === open ? [] : closeBracketEffect.of(range.head + open.length),
|
||||
range: EditorSelection.cursor(range.head + open.length) };
|
||||
+ }
|
||||
return { range: dont = range };
|
||||
});
|
||||
return dont ? null : state.update(changes, {
|
||||
@@ -1907,18 +1974,36 @@ function handleClose(state, _open, close) {
|
||||
}
|
||||
// Handles cases where the open and close token are the same, and
|
||||
// possibly triple quotes (as in `"""abc"""`-style quoting).
|
||||
-function handleSame(state, token, allowTriple, config) {
|
||||
+function handleSame(state, token, allowDouble, allowTriple, config) {
|
||||
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
|
||||
+ let buildInsert = config.buildInsert || defaults.buildInsert;
|
||||
let dont = null, changes = state.changeByRange(range => {
|
||||
+ var _a, _b, _c;
|
||||
if (!range.empty)
|
||||
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
|
||||
effects: closeBracketEffect.of(range.to + token.length),
|
||||
range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
|
||||
let pos = range.head, next = nextChar(state.doc, pos), start;
|
||||
- if (next == token) {
|
||||
+ if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
||||
+ (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
|
||||
+ nodeStart(state, start)) {
|
||||
+ return { changes: { insert: token + token + token + token, from: pos },
|
||||
+ effects: closeBracketEffect.of(pos + token.length),
|
||||
+ range: EditorSelection.cursor(pos + token.length) };
|
||||
+ }
|
||||
+ else if (allowDouble && state.sliceDoc(pos - token.length, pos) == token &&
|
||||
+ (start = canStartStringAt(state, pos - token.length, stringPrefixes)) > -1 &&
|
||||
+ nodeStart(state, start)) {
|
||||
+ let insert = (_a = buildInsert(state, range, token, token)) !== null && _a !== void 0 ? _a : token + token;
|
||||
+ return { changes: { insert, from: pos },
|
||||
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
|
||||
+ range: EditorSelection.cursor(pos + token.length) };
|
||||
+ }
|
||||
+ else if (next == token) {
|
||||
if (nodeStart(state, pos)) {
|
||||
- return { changes: { insert: token + token, from: pos },
|
||||
- effects: closeBracketEffect.of(pos + token.length),
|
||||
+ let insert = (_b = buildInsert(state, range, token, token)) !== null && _b !== void 0 ? _b : token + token;
|
||||
+ return { changes: { insert, from: pos },
|
||||
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
|
||||
range: EditorSelection.cursor(pos + token.length) };
|
||||
}
|
||||
else if (closedBracketAt(state, pos)) {
|
||||
@@ -1928,18 +2013,13 @@ function handleSame(state, token, allowTriple, config) {
|
||||
range: EditorSelection.cursor(pos + content.length) };
|
||||
}
|
||||
}
|
||||
- else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
||||
- (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
|
||||
- nodeStart(state, start)) {
|
||||
- return { changes: { insert: token + token + token + token, from: pos },
|
||||
- effects: closeBracketEffect.of(pos + token.length),
|
||||
- range: EditorSelection.cursor(pos + token.length) };
|
||||
- }
|
||||
else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
|
||||
- if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
|
||||
- return { changes: { insert: token + token, from: pos },
|
||||
- effects: closeBracketEffect.of(pos + token.length),
|
||||
+ if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes)) {
|
||||
+ const insert = (_c = buildInsert(state, range, token, token)) !== null && _c !== void 0 ? _c : token + token;
|
||||
+ return { changes: { insert, from: pos },
|
||||
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
|
||||
range: EditorSelection.cursor(pos + token.length) };
|
||||
+ }
|
||||
}
|
||||
return { range: dont = range };
|
||||
});
|
||||
@@ -2071,4 +2151,4 @@ function setSelectedCompletion(index) {
|
||||
return setSelectedEffect.of(index);
|
||||
}
|
||||
|
||||
-export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
||||
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
||||
@@ -0,0 +1,381 @@
|
||||
diff --git a/dist/index.cjs b/dist/index.cjs
|
||||
index 46231ae..fb0f9aa 100644
|
||||
--- a/dist/index.cjs
|
||||
+++ b/dist/index.cjs
|
||||
@@ -592,6 +592,7 @@ class SearchQuery {
|
||||
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
|
||||
this.unquoted = this.unquote(this.search);
|
||||
this.wholeWord = !!config.wholeWord;
|
||||
+ this.scope = config.scope;
|
||||
}
|
||||
/**
|
||||
@internal
|
||||
@@ -606,7 +607,7 @@ class SearchQuery {
|
||||
eq(other) {
|
||||
return this.search == other.search && this.replace == other.replace &&
|
||||
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
|
||||
- this.wholeWord == other.wholeWord;
|
||||
+ this.wholeWord == other.wholeWord && this.scope == other.scope;
|
||||
}
|
||||
/**
|
||||
@internal
|
||||
@@ -631,7 +632,12 @@ class QueryType {
|
||||
}
|
||||
}
|
||||
function stringCursor(spec, state, from, to) {
|
||||
- return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
|
||||
+ const test = spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined;
|
||||
+ const testWithinScope = (from, to, buffer, bufferPos) => {
|
||||
+ return (!test || test(from, to, buffer, bufferPos))
|
||||
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
|
||||
+ };
|
||||
+ return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), testWithinScope);
|
||||
}
|
||||
function stringWordTest(doc, categorizer) {
|
||||
return (from, to, buf, bufPos) => {
|
||||
@@ -695,9 +701,14 @@ class StringQuery extends QueryType {
|
||||
}
|
||||
}
|
||||
function regexpCursor(spec, state, from, to) {
|
||||
+ const test = spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined;
|
||||
+ const testWithinScope = (from, to, match) => {
|
||||
+ return (!test || test(from, to, match))
|
||||
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
|
||||
+ };
|
||||
return new RegExpCursor(state.doc, spec.search, {
|
||||
ignoreCase: !spec.caseSensitive,
|
||||
- test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
|
||||
+ test: testWithinScope,
|
||||
}, from, to);
|
||||
}
|
||||
function charBefore(str, index) {
|
||||
@@ -737,10 +748,18 @@ class RegExpQuery extends QueryType {
|
||||
this.prevMatchInRange(state, curTo, state.doc.length);
|
||||
}
|
||||
getReplacement(result) {
|
||||
- return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
|
||||
- : i == "&" ? result.match[0]
|
||||
- : i != "0" && +i < result.match.length ? result.match[i]
|
||||
- : m);
|
||||
+ return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
|
||||
+ if (i == "&")
|
||||
+ return result.match[0];
|
||||
+ if (i == "$")
|
||||
+ return "$";
|
||||
+ for (let l = i.length; l > 0; l--) {
|
||||
+ let n = +i.slice(0, l);
|
||||
+ if (n > 0 && n < result.match.length)
|
||||
+ return result.match[n] + i.slice(l);
|
||||
+ }
|
||||
+ return m;
|
||||
+ });
|
||||
}
|
||||
matchAll(state, limit) {
|
||||
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
|
||||
@@ -1227,7 +1246,9 @@ const searchExtensions = [
|
||||
exports.RegExpCursor = RegExpCursor;
|
||||
exports.SearchCursor = SearchCursor;
|
||||
exports.SearchQuery = SearchQuery;
|
||||
+exports.StringQuery = StringQuery;
|
||||
exports.closeSearchPanel = closeSearchPanel;
|
||||
+exports.createSearchPanel = createSearchPanel;
|
||||
exports.findNext = findNext;
|
||||
exports.findPrevious = findPrevious;
|
||||
exports.getSearchQuery = getSearchQuery;
|
||||
@@ -1242,4 +1263,6 @@ exports.searchPanelOpen = searchPanelOpen;
|
||||
exports.selectMatches = selectMatches;
|
||||
exports.selectNextOccurrence = selectNextOccurrence;
|
||||
exports.selectSelectionMatches = selectSelectionMatches;
|
||||
+exports.selectWord = selectWord;
|
||||
exports.setSearchQuery = setSearchQuery;
|
||||
+exports.togglePanel = togglePanel;
|
||||
diff --git a/dist/index.d.cts b/dist/index.d.cts
|
||||
index 08f5696..663d192 100644
|
||||
--- a/dist/index.d.cts
|
||||
+++ b/dist/index.d.cts
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as _codemirror_state from '@codemirror/state';
|
||||
import { Text, Extension, StateCommand, EditorState, SelectionRange, StateEffect } from '@codemirror/state';
|
||||
-import { Command, KeyBinding, EditorView, Panel } from '@codemirror/view';
|
||||
+import { Command, EditorView, Panel, KeyBinding } from '@codemirror/view';
|
||||
|
||||
/**
|
||||
A search cursor provides an iterator over text matches in a
|
||||
@@ -161,6 +161,7 @@ the `"cm-selectionMatch"` class for the highlighting. When
|
||||
itself will be highlighted with `"cm-selectionMatch-main"`.
|
||||
*/
|
||||
declare function highlightSelectionMatches(options?: HighlightOptions): Extension;
|
||||
+declare const selectWord: StateCommand;
|
||||
/**
|
||||
Select next occurrence of the current selection. Expand selection
|
||||
to the surrounding word when the selection is empty.
|
||||
@@ -264,6 +265,13 @@ declare class SearchQuery {
|
||||
*/
|
||||
readonly wholeWord: boolean;
|
||||
/**
|
||||
+ When set, only include search matches within these ranges
|
||||
+ */
|
||||
+ readonly scope?: Readonly<{
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ }[]>;
|
||||
+ /**
|
||||
Create a query object.
|
||||
*/
|
||||
constructor(config: {
|
||||
@@ -293,6 +301,13 @@ declare class SearchQuery {
|
||||
Enable whole-word matching.
|
||||
*/
|
||||
wholeWord?: boolean;
|
||||
+ /**
|
||||
+ The ranges to match within
|
||||
+ */
|
||||
+ scope?: Readonly<{
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ }[]>;
|
||||
});
|
||||
/**
|
||||
Compare this query to another query.
|
||||
@@ -307,6 +322,34 @@ declare class SearchQuery {
|
||||
to: number;
|
||||
}>;
|
||||
}
|
||||
+type SearchResult = typeof SearchCursor.prototype.value;
|
||||
+declare abstract class QueryType<Result extends SearchResult = SearchResult> {
|
||||
+ readonly spec: SearchQuery;
|
||||
+ constructor(spec: SearchQuery);
|
||||
+ abstract nextMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
|
||||
+ abstract prevMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
|
||||
+ abstract getReplacement(result: Result): string;
|
||||
+ abstract matchAll(state: EditorState, limit: number): readonly Result[] | null;
|
||||
+ abstract highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
|
||||
+}
|
||||
+declare class StringQuery extends QueryType<SearchResult> {
|
||||
+ constructor(spec: SearchQuery);
|
||||
+ nextMatch(state: EditorState, curFrom: number, curTo: number): {
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ } | null;
|
||||
+ private prevMatchInRange;
|
||||
+ prevMatch(state: EditorState, curFrom: number, curTo: number): {
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ } | null;
|
||||
+ getReplacement(_result: SearchResult): string;
|
||||
+ matchAll(state: EditorState, limit: number): {
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ }[] | null;
|
||||
+ highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
|
||||
+}
|
||||
/**
|
||||
A state effect that updates the current search query. Note that
|
||||
this only has an effect if the search state has been initialized
|
||||
@@ -315,6 +358,7 @@ by running [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSea
|
||||
once).
|
||||
*/
|
||||
declare const setSearchQuery: _codemirror_state.StateEffectType<SearchQuery>;
|
||||
+declare const togglePanel: _codemirror_state.StateEffectType<boolean>;
|
||||
/**
|
||||
Get the current search query from an editor state.
|
||||
*/
|
||||
@@ -353,6 +397,7 @@ Replace all instances of the search query with the given
|
||||
replacement.
|
||||
*/
|
||||
declare const replaceAll: Command;
|
||||
+declare function createSearchPanel(view: EditorView): Panel;
|
||||
/**
|
||||
Make sure the search panel is open and focused.
|
||||
*/
|
||||
@@ -372,4 +417,4 @@ Default search-related key bindings.
|
||||
*/
|
||||
declare const searchKeymap: readonly KeyBinding[];
|
||||
|
||||
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
|
||||
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
|
||||
diff --git a/dist/index.d.ts b/dist/index.d.ts
|
||||
index 08f5696..663d192 100644
|
||||
--- a/dist/index.d.ts
|
||||
+++ b/dist/index.d.ts
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as _codemirror_state from '@codemirror/state';
|
||||
import { Text, Extension, StateCommand, EditorState, SelectionRange, StateEffect } from '@codemirror/state';
|
||||
-import { Command, KeyBinding, EditorView, Panel } from '@codemirror/view';
|
||||
+import { Command, EditorView, Panel, KeyBinding } from '@codemirror/view';
|
||||
|
||||
/**
|
||||
A search cursor provides an iterator over text matches in a
|
||||
@@ -161,6 +161,7 @@ the `"cm-selectionMatch"` class for the highlighting. When
|
||||
itself will be highlighted with `"cm-selectionMatch-main"`.
|
||||
*/
|
||||
declare function highlightSelectionMatches(options?: HighlightOptions): Extension;
|
||||
+declare const selectWord: StateCommand;
|
||||
/**
|
||||
Select next occurrence of the current selection. Expand selection
|
||||
to the surrounding word when the selection is empty.
|
||||
@@ -264,6 +265,13 @@ declare class SearchQuery {
|
||||
*/
|
||||
readonly wholeWord: boolean;
|
||||
/**
|
||||
+ When set, only include search matches within these ranges
|
||||
+ */
|
||||
+ readonly scope?: Readonly<{
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ }[]>;
|
||||
+ /**
|
||||
Create a query object.
|
||||
*/
|
||||
constructor(config: {
|
||||
@@ -293,6 +301,13 @@ declare class SearchQuery {
|
||||
Enable whole-word matching.
|
||||
*/
|
||||
wholeWord?: boolean;
|
||||
+ /**
|
||||
+ The ranges to match within
|
||||
+ */
|
||||
+ scope?: Readonly<{
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ }[]>;
|
||||
});
|
||||
/**
|
||||
Compare this query to another query.
|
||||
@@ -307,6 +322,34 @@ declare class SearchQuery {
|
||||
to: number;
|
||||
}>;
|
||||
}
|
||||
+type SearchResult = typeof SearchCursor.prototype.value;
|
||||
+declare abstract class QueryType<Result extends SearchResult = SearchResult> {
|
||||
+ readonly spec: SearchQuery;
|
||||
+ constructor(spec: SearchQuery);
|
||||
+ abstract nextMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
|
||||
+ abstract prevMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
|
||||
+ abstract getReplacement(result: Result): string;
|
||||
+ abstract matchAll(state: EditorState, limit: number): readonly Result[] | null;
|
||||
+ abstract highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
|
||||
+}
|
||||
+declare class StringQuery extends QueryType<SearchResult> {
|
||||
+ constructor(spec: SearchQuery);
|
||||
+ nextMatch(state: EditorState, curFrom: number, curTo: number): {
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ } | null;
|
||||
+ private prevMatchInRange;
|
||||
+ prevMatch(state: EditorState, curFrom: number, curTo: number): {
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ } | null;
|
||||
+ getReplacement(_result: SearchResult): string;
|
||||
+ matchAll(state: EditorState, limit: number): {
|
||||
+ from: number;
|
||||
+ to: number;
|
||||
+ }[] | null;
|
||||
+ highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
|
||||
+}
|
||||
/**
|
||||
A state effect that updates the current search query. Note that
|
||||
this only has an effect if the search state has been initialized
|
||||
@@ -315,6 +358,7 @@ by running [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSea
|
||||
once).
|
||||
*/
|
||||
declare const setSearchQuery: _codemirror_state.StateEffectType<SearchQuery>;
|
||||
+declare const togglePanel: _codemirror_state.StateEffectType<boolean>;
|
||||
/**
|
||||
Get the current search query from an editor state.
|
||||
*/
|
||||
@@ -353,6 +397,7 @@ Replace all instances of the search query with the given
|
||||
replacement.
|
||||
*/
|
||||
declare const replaceAll: Command;
|
||||
+declare function createSearchPanel(view: EditorView): Panel;
|
||||
/**
|
||||
Make sure the search panel is open and focused.
|
||||
*/
|
||||
@@ -372,4 +417,4 @@ Default search-related key bindings.
|
||||
*/
|
||||
declare const searchKeymap: readonly KeyBinding[];
|
||||
|
||||
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
|
||||
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 22172ef..08a9974 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -590,6 +590,7 @@ class SearchQuery {
|
||||
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
|
||||
this.unquoted = this.unquote(this.search);
|
||||
this.wholeWord = !!config.wholeWord;
|
||||
+ this.scope = config.scope;
|
||||
}
|
||||
/**
|
||||
@internal
|
||||
@@ -604,7 +605,7 @@ class SearchQuery {
|
||||
eq(other) {
|
||||
return this.search == other.search && this.replace == other.replace &&
|
||||
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
|
||||
- this.wholeWord == other.wholeWord;
|
||||
+ this.wholeWord == other.wholeWord && this.scope == other.scope;
|
||||
}
|
||||
/**
|
||||
@internal
|
||||
@@ -629,7 +630,12 @@ class QueryType {
|
||||
}
|
||||
}
|
||||
function stringCursor(spec, state, from, to) {
|
||||
- return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
|
||||
+ const test = spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined;
|
||||
+ const testWithinScope = (from, to, buffer, bufferPos) => {
|
||||
+ return (!test || test(from, to, buffer, bufferPos))
|
||||
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
|
||||
+ };
|
||||
+ return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), testWithinScope);
|
||||
}
|
||||
function stringWordTest(doc, categorizer) {
|
||||
return (from, to, buf, bufPos) => {
|
||||
@@ -693,9 +699,14 @@ class StringQuery extends QueryType {
|
||||
}
|
||||
}
|
||||
function regexpCursor(spec, state, from, to) {
|
||||
+ const test = spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined;
|
||||
+ const testWithinScope = (from, to, match) => {
|
||||
+ return (!test || test(from, to, match))
|
||||
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
|
||||
+ };
|
||||
return new RegExpCursor(state.doc, spec.search, {
|
||||
ignoreCase: !spec.caseSensitive,
|
||||
- test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
|
||||
+ test: testWithinScope,
|
||||
}, from, to);
|
||||
}
|
||||
function charBefore(str, index) {
|
||||
@@ -735,10 +746,18 @@ class RegExpQuery extends QueryType {
|
||||
this.prevMatchInRange(state, curTo, state.doc.length);
|
||||
}
|
||||
getReplacement(result) {
|
||||
- return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
|
||||
- : i == "&" ? result.match[0]
|
||||
- : i != "0" && +i < result.match.length ? result.match[i]
|
||||
- : m);
|
||||
+ return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
|
||||
+ if (i == "&")
|
||||
+ return result.match[0];
|
||||
+ if (i == "$")
|
||||
+ return "$";
|
||||
+ for (let l = i.length; l > 0; l--) {
|
||||
+ let n = +i.slice(0, l);
|
||||
+ if (n > 0 && n < result.match.length)
|
||||
+ return result.match[n] + i.slice(l);
|
||||
+ }
|
||||
+ return m;
|
||||
+ });
|
||||
}
|
||||
matchAll(state, limit) {
|
||||
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
|
||||
@@ -1222,4 +1241,4 @@ const searchExtensions = [
|
||||
baseTheme
|
||||
];
|
||||
|
||||
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
|
||||
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/body-parser/lib/read.js b/node_modules/body-parser/lib/read.js
|
||||
diff --git a/lib/read.js b/lib/read.js
|
||||
index fce6283..6131c31 100644
|
||||
--- a/node_modules/body-parser/lib/read.js
|
||||
+++ b/node_modules/body-parser/lib/read.js
|
||||
--- a/lib/read.js
|
||||
+++ b/lib/read.js
|
||||
@@ -18,7 +18,7 @@ var iconv = require('iconv-lite')
|
||||
var onFinished = require('on-finished')
|
||||
var unpipe = require('unpipe')
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/cypress-multi-reporters/lib/MultiReporters.js b/node_modules/cypress-multi-reporters/lib/MultiReporters.js
|
||||
diff --git a/lib/MultiReporters.js b/lib/MultiReporters.js
|
||||
index 98dc4ef..b2a97bf 100644
|
||||
--- a/node_modules/cypress-multi-reporters/lib/MultiReporters.js
|
||||
+++ b/node_modules/cypress-multi-reporters/lib/MultiReporters.js
|
||||
--- a/lib/MultiReporters.js
|
||||
+++ b/lib/MultiReporters.js
|
||||
@@ -160,7 +160,7 @@ MultiReporters.prototype.getCustomOptions = function (options) {
|
||||
debug('options file (custom)', customOptionsFile);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/forwarded/index.js b/node_modules/forwarded/index.js
|
||||
diff --git a/index.js b/index.js
|
||||
index b2b6bdd..75e6254 100644
|
||||
--- a/node_modules/forwarded/index.js
|
||||
+++ b/node_modules/forwarded/index.js
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -46,7 +46,7 @@ function forwarded (req) {
|
||||
function getSocketAddr (req) {
|
||||
return req.socket
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/mocha-multi-reporters/lib/MultiReporters.js b/node_modules/mocha-multi-reporters/lib/MultiReporters.js
|
||||
index d61e019..e7a9515 100644
|
||||
--- a/node_modules/mocha-multi-reporters/lib/MultiReporters.js
|
||||
+++ b/node_modules/mocha-multi-reporters/lib/MultiReporters.js
|
||||
diff --git a/lib/MultiReporters.js b/lib/MultiReporters.js
|
||||
index d61e019711d5ac7f82c0fb90548bb0eb41ebbb85..e7a9515e05287621301b831191884a84d72ad0a1 100644
|
||||
--- a/lib/MultiReporters.js
|
||||
+++ b/lib/MultiReporters.js
|
||||
@@ -153,7 +153,7 @@ MultiReporters.prototype.getCustomOptions = function (options) {
|
||||
debug('options file (custom)', customOptionsFile);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/multer/lib/make-middleware.js b/node_modules/multer/lib/make-middleware.js
|
||||
diff --git a/lib/make-middleware.js b/lib/make-middleware.js
|
||||
index ee50988..de77364 100644
|
||||
--- a/node_modules/multer/lib/make-middleware.js
|
||||
+++ b/node_modules/multer/lib/make-middleware.js
|
||||
--- a/lib/make-middleware.js
|
||||
+++ b/lib/make-middleware.js
|
||||
@@ -164,7 +164,7 @@ function makeMiddleware (setup) {
|
||||
if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/node-fetch/lib/index.js b/node_modules/node-fetch/lib/index.js
|
||||
diff --git a/lib/index.js b/lib/index.js
|
||||
index 567ff5d..8eb45f7 100644
|
||||
--- a/node_modules/node-fetch/lib/index.js
|
||||
+++ b/node_modules/node-fetch/lib/index.js
|
||||
--- a/lib/index.js
|
||||
+++ b/lib/index.js
|
||||
@@ -545,8 +545,8 @@ function clone(instance) {
|
||||
// tee instance body
|
||||
p1 = new PassThrough();
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/passport-oauth2/lib/utils.js b/node_modules/passport-oauth2/lib/utils.js
|
||||
diff --git a/lib/utils.js b/lib/utils.js
|
||||
index 486f9e1..4584507 100644
|
||||
--- a/node_modules/passport-oauth2/lib/utils.js
|
||||
+++ b/node_modules/passport-oauth2/lib/utils.js
|
||||
--- a/lib/utils.js
|
||||
+++ b/lib/utils.js
|
||||
@@ -24,7 +24,7 @@ exports.originalURL = function(req, options) {
|
||||
var trustProxy = options.proxy;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/pdfjs-dist/build/pdf.worker.mjs b/node_modules/pdfjs-dist/build/pdf.worker.mjs
|
||||
diff --git a/build/pdf.worker.mjs b/build/pdf.worker.mjs
|
||||
index 6c5c6f1..bb6b7d1 100644
|
||||
--- a/node_modules/pdfjs-dist/build/pdf.worker.mjs
|
||||
+++ b/node_modules/pdfjs-dist/build/pdf.worker.mjs
|
||||
--- a/build/pdf.worker.mjs
|
||||
+++ b/build/pdf.worker.mjs
|
||||
@@ -1830,7 +1830,7 @@ async function __wbg_init(module_or_path) {
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -1,12 +1,12 @@
|
||||
diff --git a/node_modules/google-gax/node_modules/retry-request/index.js b/node_modules/google-gax/node_modules/retry-request/index.js
|
||||
index 2fae107..5721c54 100644
|
||||
--- a/node_modules/google-gax/node_modules/retry-request/index.js
|
||||
+++ b/node_modules/google-gax/node_modules/retry-request/index.js
|
||||
diff --git a/index.js b/index.js
|
||||
index 2fae107d03b30aff9320d135ec79c049c51f298a..32ec707ddbf8937e3e130c3deac1060c308bf439 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
-const {PassThrough} = require('stream');
|
||||
+const { PassThrough, pipeline } = require('stream');
|
||||
+const {PassThrough, pipeline} = require('stream');
|
||||
const extend = require('extend');
|
||||
|
||||
let debug = () => {};
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
diff --git a/node_modules/@google-cloud/logging/node_modules/retry-request/index.js b/node_modules/@google-cloud/logging/node_modules/retry-request/index.js
|
||||
index 2fae107..5721c54 100644
|
||||
--- a/node_modules/@google-cloud/logging/node_modules/retry-request/index.js
|
||||
+++ b/node_modules/@google-cloud/logging/node_modules/retry-request/index.js
|
||||
diff --git a/index.js b/index.js
|
||||
index 298a351097d70a7fb005b6961f4d58247e391d8f..6a809ace0349d40cb2b6732ad66fd8ad208698ca 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
-const {PassThrough} = require('stream');
|
||||
+const { PassThrough, pipeline } = require('stream');
|
||||
+const {PassThrough, pipeline} = require('stream');
|
||||
const extend = require('extend');
|
||||
|
||||
let debug = () => {};
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/lib/sandboxed_module.js b/lib/sandboxed_module.js
|
||||
index 1cd6743fe221cbe91ea92fea3707ed07a8a2ded3..46889217d96d5534a206549ae7bd97100e41c3e4 100644
|
||||
--- a/lib/sandboxed_module.js
|
||||
+++ b/lib/sandboxed_module.js
|
||||
@@ -4,7 +4,7 @@ var Module = require('module');
|
||||
var fs = require('fs');
|
||||
var vm = require('vm');
|
||||
var path = require('path');
|
||||
-var builtinModules = require('./builtin_modules.json');
|
||||
+var builtinModules = Module.builtinModules || require('./builtin_modules.json');
|
||||
var parent = module.parent;
|
||||
var globalOptions = {};
|
||||
var registeredBuiltInSourceTransformers = ['coffee'];
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/send/index.js b/node_modules/send/index.js
|
||||
diff --git a/index.js b/index.js
|
||||
index 768f8ca..a882f4d 100644
|
||||
--- a/node_modules/send/index.js
|
||||
+++ b/node_modules/send/index.js
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -788,29 +788,29 @@ SendStream.prototype.stream = function stream (path, options) {
|
||||
// pipe
|
||||
var stream = fs.createReadStream(path, options)
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/teeny-request/build/src/index.js b/node_modules/teeny-request/build/src/index.js
|
||||
diff --git a/build/src/index.js b/build/src/index.js
|
||||
index a101736..a87f6b9 100644
|
||||
--- a/node_modules/teeny-request/build/src/index.js
|
||||
+++ b/node_modules/teeny-request/build/src/index.js
|
||||
--- a/build/src/index.js
|
||||
+++ b/build/src/index.js
|
||||
@@ -130,6 +130,9 @@ function createMultipartStream(boundary, multipart) {
|
||||
}
|
||||
else {
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/google-gax/node_modules/teeny-request/build/src/index.js b/node_modules/google-gax/node_modules/teeny-request/build/src/index.js
|
||||
index af5d15e..2b63d0c 100644
|
||||
--- a/node_modules/google-gax/node_modules/teeny-request/build/src/index.js
|
||||
+++ b/node_modules/google-gax/node_modules/teeny-request/build/src/index.js
|
||||
diff --git a/build/src/index.js b/build/src/index.js
|
||||
index af5d15e260e2a47588c7c536447fe84bd3f86136..2b63d0c0b1eb6595c7a0bb314c1df792454c1a72 100644
|
||||
--- a/build/src/index.js
|
||||
+++ b/build/src/index.js
|
||||
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
|
||||
}
|
||||
else {
|
||||
+12
-12
@@ -1,7 +1,7 @@
|
||||
diff --git a/node_modules/thread-loader/dist/WorkerPool.js b/node_modules/thread-loader/dist/WorkerPool.js
|
||||
index 4145779..f0ff068 100644
|
||||
--- a/node_modules/thread-loader/dist/WorkerPool.js
|
||||
+++ b/node_modules/thread-loader/dist/WorkerPool.js
|
||||
diff --git a/dist/WorkerPool.js b/dist/WorkerPool.js
|
||||
index 4145779f08eefafd0c18394806b6409c595ac5bb..aa16dd6a0f463804455164493a1e75e80cdf656a 100644
|
||||
--- a/dist/WorkerPool.js
|
||||
+++ b/dist/WorkerPool.js
|
||||
@@ -258,6 +258,19 @@ class PoolWorker {
|
||||
finalCallback();
|
||||
break;
|
||||
@@ -22,10 +22,10 @@ index 4145779..f0ff068 100644
|
||||
case 'emitWarning':
|
||||
{
|
||||
const {
|
||||
diff --git a/node_modules/thread-loader/dist/index.js b/node_modules/thread-loader/dist/index.js
|
||||
index 75cd30f..d834af6 100644
|
||||
--- a/node_modules/thread-loader/dist/index.js
|
||||
+++ b/node_modules/thread-loader/dist/index.js
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 75cd30fb63dc864057c1afc866f43fc7cc0a8020..d834af6ce1e2cd4473c4ae1b325d63eb1190c17e 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -43,6 +43,7 @@ function pitch() {
|
||||
sourceMap: this.sourceMap,
|
||||
emitError: this.emitError,
|
||||
@@ -34,10 +34,10 @@ index 75cd30f..d834af6 100644
|
||||
loadModule: this.loadModule,
|
||||
resolve: this.resolve,
|
||||
getResolve: this.getResolve,
|
||||
diff --git a/node_modules/thread-loader/dist/worker.js b/node_modules/thread-loader/dist/worker.js
|
||||
index 8e67959..aca94f1 100644
|
||||
--- a/node_modules/thread-loader/dist/worker.js
|
||||
+++ b/node_modules/thread-loader/dist/worker.js
|
||||
diff --git a/dist/worker.js b/dist/worker.js
|
||||
index 8e67959e4b7c9fd0db116509b1459636ba13a097..aca94f1442906baf179ada8e6a56501bbf71c480 100644
|
||||
--- a/dist/worker.js
|
||||
+++ b/dist/worker.js
|
||||
@@ -90,6 +90,22 @@ function writeJson(data) {
|
||||
writePipeWrite(lengthBuffer);
|
||||
writePipeWrite(messageBuffer);
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
approvedGitRepositories:
|
||||
- "**"
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
enableScripts: true
|
||||
|
||||
enableTelemetry: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmMinimalAgeGate: 3d
|
||||
|
||||
supportedArchitectures:
|
||||
cpu:
|
||||
- current
|
||||
- arm64
|
||||
- x64
|
||||
libc:
|
||||
- current
|
||||
- glibc
|
||||
os:
|
||||
- current
|
||||
- darwin
|
||||
- linux
|
||||
@@ -59,7 +59,6 @@ each service:
|
||||
| `web` | 9229 |
|
||||
| `clsi` | 9230 |
|
||||
| `chat` | 9231 |
|
||||
| `contacts` | 9232 |
|
||||
| `docstore` | 9233 |
|
||||
| `document-updater` | 9234 |
|
||||
| `filestore` | 9235 |
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
CHAT_HOST=chat
|
||||
CLSI_HOST=clsi
|
||||
DOWNLOAD_HOST=clsi-nginx
|
||||
CONTACTS_HOST=contacts
|
||||
DOCSTORE_HOST=docstore
|
||||
DOCUMENT_UPDATER_HOST=document-updater
|
||||
FILESTORE_HOST=filestore
|
||||
|
||||
@@ -21,17 +21,6 @@ services:
|
||||
- ../services/chat/app.js:/overleaf/services/chat/app.js
|
||||
- ../services/chat/config:/overleaf/services/chat/config
|
||||
|
||||
contacts:
|
||||
command: ["node", "--watch", "app.js"]
|
||||
environment:
|
||||
- NODE_OPTIONS=--inspect=0.0.0.0:9229
|
||||
ports:
|
||||
- "127.0.0.1:9232:9229"
|
||||
volumes:
|
||||
- ../services/contacts/app:/overleaf/services/contacts/app
|
||||
- ../services/contacts/app.js:/overleaf/services/contacts/app.js
|
||||
- ../services/contacts/config:/overleaf/services/contacts/config
|
||||
|
||||
docstore:
|
||||
command: ["node", "--watch", "app.js"]
|
||||
environment:
|
||||
|
||||
@@ -45,13 +45,6 @@ services:
|
||||
- ${PWD}/output:/output:ro
|
||||
- ../services/clsi/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
|
||||
|
||||
contacts:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: services/contacts/Dockerfile
|
||||
env_file:
|
||||
- dev.env
|
||||
|
||||
docstore:
|
||||
build:
|
||||
context: ..
|
||||
@@ -161,7 +154,6 @@ services:
|
||||
- redis
|
||||
- chat
|
||||
- clsi
|
||||
- contacts
|
||||
- docstore
|
||||
- document-updater
|
||||
- filestore
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ services:
|
||||
|
||||
mongo:
|
||||
restart: always
|
||||
image: mongo:6.0
|
||||
image: mongo:8.0
|
||||
container_name: mongo
|
||||
command: "--replSet overleaf"
|
||||
volumes:
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
FROM cypress/included:13.13.2
|
||||
FROM cypress/included:15.12.0
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=1000
|
||||
|
||||
# Corepack setup, shared between all the images.
|
||||
ENV PATH="/overleaf/node_modules/.bin:$PATH"
|
||||
ENV COREPACK_HOME=/opt/corepack
|
||||
RUN corepack enable && corepack install -g yarn@4.14.1
|
||||
ENV COREPACK_ENABLE_NETWORK=0
|
||||
|
||||
WORKDIR /overleaf
|
||||
|
||||
RUN sed -i s/node:x:1000:/node:x:${USER_GID}:/ /etc/group \
|
||||
|
||||
@@ -5,5 +5,6 @@ access-token-encryptor
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/access-token-encryptor
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"author": "",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21"
|
||||
"lodash": "^4.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.6",
|
||||
|
||||
@@ -8,5 +8,6 @@ module.exports = {
|
||||
'require-vi-doMock-valid-path': require('./require-vi-doMock-valid-path'),
|
||||
'require-loading-label': require('./require-loading-label'),
|
||||
'require-cio-snake-case-properties': require('./require-cio-snake-case-properties'),
|
||||
'no-throw-in-callback': require('./no-throw-in-callback'),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
const CALLBACK_PARAM_NAMES = new Set(['cb', 'callback', 'done', 'next'])
|
||||
|
||||
function isCallbackParam(param) {
|
||||
return (
|
||||
param && param.type === 'Identifier' && CALLBACK_PARAM_NAMES.has(param.name)
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'error',
|
||||
docs: {
|
||||
description: 'Disallow throw statements inside callback-based functions',
|
||||
},
|
||||
messages: {
|
||||
noThrowInCallback:
|
||||
'Pass the error to the callback instead of throwing in callback-based code.',
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
// Stack tracks whether each enclosing function is a callback-style function.
|
||||
// A callback-style function is non-async and has a last param named cb/callback/done/next.
|
||||
const stack = []
|
||||
|
||||
function enterFunction(node) {
|
||||
const params = node.params
|
||||
const isCallback =
|
||||
!node.async &&
|
||||
params.length > 0 &&
|
||||
isCallbackParam(params[params.length - 1])
|
||||
stack.push(isCallback)
|
||||
}
|
||||
|
||||
function exitFunction() {
|
||||
stack.pop()
|
||||
}
|
||||
|
||||
return {
|
||||
FunctionDeclaration: enterFunction,
|
||||
'FunctionDeclaration:exit': exitFunction,
|
||||
FunctionExpression: enterFunction,
|
||||
'FunctionExpression:exit': exitFunction,
|
||||
ArrowFunctionExpression: enterFunction,
|
||||
'ArrowFunctionExpression:exit': exitFunction,
|
||||
ThrowStatement(node) {
|
||||
if (stack[stack.length - 1]) {
|
||||
context.report({ node, messageId: 'noThrowInCallback' })
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -5,12 +5,14 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"eslint": "^8.51.0",
|
||||
"lodash": "^4.17.21"
|
||||
"lodash": "^4.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "^8.50.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.51.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node rules.test.js"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const { RuleTester } = require('eslint')
|
||||
const noThrowInCallback = require('./no-throw-in-callback')
|
||||
const preferKebabUrl = require('./prefer-kebab-url')
|
||||
const noUnnecessaryTrans = require('./no-unnecessary-trans')
|
||||
const shouldUnescapeTrans = require('./should-unescape-trans')
|
||||
@@ -267,3 +268,53 @@ ruleTester.run(
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
const noThrowInCallbackMessage =
|
||||
'Pass the error to the callback instead of throwing in callback-based code.'
|
||||
ruleTester.run('no-throw-in-callback', noThrowInCallback, {
|
||||
valid: [
|
||||
// Calling the callback with an error is fine
|
||||
{ code: `function foo(cb) { cb(new Error()) }` },
|
||||
// async functions may throw (they return a rejected promise)
|
||||
{ code: `async function foo(cb) { throw new Error() }` },
|
||||
// Last param not a callback name — not a callback-style function
|
||||
{ code: `function foo(data) { throw new Error() }` },
|
||||
// No params at all
|
||||
{ code: `function foo() { throw new Error() }` },
|
||||
// throw inside a nested non-callback function is fine
|
||||
{ code: `function foo(cb) { [1].map(function() { throw new Error() }) }` },
|
||||
// throw inside a nested async arrow is fine
|
||||
{ code: `function foo(cb) { [1].map(async () => { throw new Error() }) }` },
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `function foo(cb) { throw new Error() }`,
|
||||
errors: [{ message: noThrowInCallbackMessage }],
|
||||
},
|
||||
{
|
||||
code: `function foo(callback) { throw new Error() }`,
|
||||
errors: [{ message: noThrowInCallbackMessage }],
|
||||
},
|
||||
{
|
||||
code: `function foo(done) { throw new Error() }`,
|
||||
errors: [{ message: noThrowInCallbackMessage }],
|
||||
},
|
||||
{
|
||||
code: `function foo(next) { throw new Error() }`,
|
||||
errors: [{ message: noThrowInCallbackMessage }],
|
||||
},
|
||||
{
|
||||
code: `function foo(data, cb) { throw new Error() }`,
|
||||
errors: [{ message: noThrowInCallbackMessage }],
|
||||
},
|
||||
{
|
||||
code: `const foo = (cb) => { throw new Error() }`,
|
||||
errors: [{ message: noThrowInCallbackMessage }],
|
||||
},
|
||||
// throw in a nested callback-style function inside another callback function
|
||||
{
|
||||
code: `function foo(cb) { bar(function(done) { throw new Error() }) }`,
|
||||
errors: [{ message: noThrowInCallbackMessage }],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -5,5 +5,6 @@ fetch-utils
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/fetch-utils
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "utilities for node-fetch",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
@@ -26,8 +26,8 @@
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@overleaf/o-error": "*",
|
||||
"lodash": "^4.17.21",
|
||||
"@overleaf/o-error": "workspace:*",
|
||||
"lodash": "^4.18.1",
|
||||
"node-fetch": "^2.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@ logger
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/logger
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -10,17 +10,17 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"version": "3.1.1",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/logging-bunyan": "^5.1.0",
|
||||
"@overleaf/fetch-utils": "*",
|
||||
"@overleaf/o-error": "*",
|
||||
"@overleaf/fetch-utils": "workspace:*",
|
||||
"@overleaf/o-error": "workspace:*",
|
||||
"bunyan": "^1.8.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -34,6 +34,6 @@
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@overleaf/metrics": "*"
|
||||
"@overleaf/metrics": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@ metrics
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/metrics
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
|
||||
"@google-cloud/profiler": "^6.0.3",
|
||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
|
||||
"@google-cloud/profiler": "^6.0.4",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.70.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.212.0",
|
||||
"@opentelemetry/resources": "^1.30.1",
|
||||
"@opentelemetry/sdk-node": "^0.212.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.72.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.214.0",
|
||||
"@opentelemetry/resources": "^2.6.0",
|
||||
"@opentelemetry/sdk-node": "^0.214.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.39.0",
|
||||
"compression": "^1.7.4",
|
||||
"prom-client": "^14.1.1",
|
||||
@@ -34,12 +34,12 @@
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"test:acceptance": "mocha --recursive --exit --grep=$MOCHA_GREP test/acceptance",
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:acceptance": "mocha --recursive --exit --grep=${MOCHA_GREP:-} test/acceptance",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@overleaf/logger": "*"
|
||||
"@overleaf/logger": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ mongo-utils
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/mongo-utils
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
--tsconfig-no-implicit-any=True
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
"description": "utilities to help working with mongo",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"author": "Overleaf (https://www.overleaf.com)",
|
||||
|
||||
@@ -5,5 +5,6 @@ o-error
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/o-error
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
"index.cjs"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npm run --silent test",
|
||||
"build": "yarn run --silent test",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
|
||||
@@ -304,7 +304,7 @@ In order to prevent accidental deletion from outside this mechanism, an event-ba
|
||||
Contributions should pass lint, formatting and unit test checks. To run these, use
|
||||
|
||||
```
|
||||
npm run test
|
||||
yarn run test
|
||||
```
|
||||
|
||||
There are no acceptance tests in this module, but https://github.com/overleaf/filestore/ contains a comprehensive set of acceptance tests that use this module. These should also pass, with the changes.
|
||||
|
||||
@@ -5,6 +5,7 @@ object-persistor
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/object-persistor
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
--tsconfig-no-implicit-any=True
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
"description": "Module for storing objects in multiple backends, with fallback on 404 to assist migration between them",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"repository": {
|
||||
@@ -23,10 +23,10 @@
|
||||
"@aws-sdk/node-http-handler": "^3.374.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.994.0",
|
||||
"@google-cloud/storage": "^7.19.0",
|
||||
"@overleaf/logger": "*",
|
||||
"@overleaf/metrics": "*",
|
||||
"@overleaf/o-error": "*",
|
||||
"@overleaf/stream-utils": "*",
|
||||
"@overleaf/logger": "workspace:*",
|
||||
"@overleaf/metrics": "workspace:*",
|
||||
"@overleaf/o-error": "workspace:*",
|
||||
"@overleaf/stream-utils": "workspace:*",
|
||||
"fast-crc32c": "overleaf/node-fast-crc32c#aae6b2a4c7a7a159395df9cc6c38dfde702d6f51",
|
||||
"glob": "^12.0.0",
|
||||
"range-parser": "^1.2.1",
|
||||
|
||||
@@ -5,5 +5,6 @@ overleaf-editor-core
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=overleaf-editor-core
|
||||
--pipeline-owner=44
|
||||
--public-repo=False
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
const _ = require('lodash')
|
||||
const assert = require('check-types').assert
|
||||
|
||||
const OError = require('@overleaf/o-error')
|
||||
const Blob = require('../blob')
|
||||
const FileData = require('./')
|
||||
const EagerStringFileData = require('./string_file_data')
|
||||
const EditOperation = require('../operation/edit_operation')
|
||||
const EditOperationBuilder = require('../operation/edit_operation_builder')
|
||||
const TextOperation = require('../operation/text_operation')
|
||||
|
||||
/**
|
||||
* @import { BlobStore, ReadonlyBlobStore, RangesBlob, RawHashFileData, RawLazyStringFileData } from '../types'
|
||||
@@ -152,7 +154,25 @@ class LazyStringFileData extends FileData {
|
||||
ranges?.comments,
|
||||
ranges?.trackedChanges
|
||||
)
|
||||
applyOperations(this.operations, file)
|
||||
try {
|
||||
applyOperations(this.operations, file)
|
||||
} catch (err) {
|
||||
const firstOp = this.operations[0]
|
||||
const firstOpBaseLength =
|
||||
firstOp instanceof TextOperation ? firstOp.baseLength : undefined
|
||||
throw OError.tag(err, 'failed to apply operations in toEager', {
|
||||
blobHash: this.hash,
|
||||
blobContentLength: content.length,
|
||||
metadataStringLength: this.stringLength,
|
||||
totalOperations: this.operations.length,
|
||||
firstOpBaseLength,
|
||||
contentMatchesMetadata: content.length === this.stringLength,
|
||||
contentMatchesFirstOp:
|
||||
typeof firstOpBaseLength === 'number'
|
||||
? content.length === firstOpBaseLength
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
@@ -172,7 +192,18 @@ class LazyStringFileData extends FileData {
|
||||
* @param {EditOperation} operation
|
||||
*/
|
||||
edit(operation) {
|
||||
this.stringLength = operation.applyToLength(this.stringLength)
|
||||
try {
|
||||
this.stringLength = operation.applyToLength(this.stringLength)
|
||||
} catch (err) {
|
||||
const baseLength =
|
||||
operation instanceof TextOperation ? operation.baseLength : undefined
|
||||
throw OError.tag(err, 'failed to apply operation length in edit', {
|
||||
blobHash: this.hash,
|
||||
metadataStringLength: this.stringLength,
|
||||
operationBaseLength: baseLength,
|
||||
totalExistingOperations: this.operations.length,
|
||||
})
|
||||
}
|
||||
this.operations.push(operation)
|
||||
}
|
||||
|
||||
@@ -205,7 +236,17 @@ class LazyStringFileData extends FileData {
|
||||
* @returns {void}
|
||||
*/
|
||||
function applyOperations(operations, file) {
|
||||
_.each(operations, operation => operation.apply(file))
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
try {
|
||||
operations[i].apply(file)
|
||||
} catch (err) {
|
||||
throw OError.tag(err, 'operation failed during applyOperations', {
|
||||
operationIndex: i,
|
||||
totalOperations: operations.length,
|
||||
currentContentLength: file.getStringLength(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LazyStringFileData
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Library shared between the editor server and clients.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
@@ -25,9 +25,9 @@
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@overleaf/o-error": "*",
|
||||
"@overleaf/o-error": "workspace:*",
|
||||
"check-types": "^5.1.0",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash": "^4.18.1",
|
||||
"p-map": "^4.0.0",
|
||||
"path-browserify": "^1.0.1"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
const _ = require('lodash')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const OError = require('@overleaf/o-error')
|
||||
|
||||
const ot = require('../..')
|
||||
const File = ot.File
|
||||
@@ -202,4 +203,85 @@ describe('LazyStringFileData', function () {
|
||||
expect(fileData.hash).to.equal(stored.hash)
|
||||
expect(fileData.operations).to.deep.equal([])
|
||||
})
|
||||
|
||||
describe('error annotation', function () {
|
||||
it('annotates errors in toEager with blob and operation metadata', async function () {
|
||||
const testHash = this.fileHash
|
||||
// Create a file with content length 19 ('the quick brown fox')
|
||||
// then queue an operation that expects a different base length
|
||||
const fileData = new LazyStringFileData(testHash, undefined, 19)
|
||||
// This operation expects base length 999, which won't match the blob
|
||||
const badOp = new TextOperation()
|
||||
badOp.retain(999)
|
||||
fileData.operations.push(badOp)
|
||||
// Manually set stringLength to match the op's baseLength: pushing directly
|
||||
// to operations bypasses edit(), which is what normally updates stringLength
|
||||
fileData.stringLength = 999
|
||||
|
||||
try {
|
||||
await fileData.toEager(this.blobStore)
|
||||
expect.fail('should have thrown')
|
||||
} catch (err) {
|
||||
const info = OError.getFullInfo(err)
|
||||
expect(info).to.have.property('blobHash', testHash)
|
||||
expect(info).to.have.property('blobContentLength', 19)
|
||||
expect(info).to.have.property('metadataStringLength', 999)
|
||||
expect(info).to.have.property('totalOperations', 1)
|
||||
expect(info).to.have.property('operationIndex', 0)
|
||||
expect(info).to.have.property('currentContentLength', 19)
|
||||
expect(info).to.have.property('firstOpBaseLength', 999)
|
||||
expect(info).to.have.property('contentMatchesMetadata', false)
|
||||
expect(info).to.have.property('contentMatchesFirstOp', false)
|
||||
}
|
||||
})
|
||||
|
||||
it('annotates errors in edit with operation and metadata context', function () {
|
||||
const testHash = this.fileHash
|
||||
const fileData = new LazyStringFileData(testHash, undefined, 19)
|
||||
// Queue one valid operation first so totalExistingOperations > 0
|
||||
fileData.edit(new TextOperation().retain(19).insert('!'))
|
||||
// This operation expects base length 999, mismatching stringLength of 20
|
||||
const badOp = new TextOperation()
|
||||
badOp.retain(999)
|
||||
try {
|
||||
fileData.edit(badOp)
|
||||
expect.fail('should have thrown')
|
||||
} catch (err) {
|
||||
const info = OError.getFullInfo(err)
|
||||
expect(info).to.have.property('blobHash', testHash)
|
||||
expect(info).to.have.property('metadataStringLength', 20)
|
||||
expect(info).to.have.property('operationBaseLength', 999)
|
||||
expect(info).to.have.property('totalExistingOperations', 1)
|
||||
}
|
||||
})
|
||||
|
||||
it('annotates errors in applyOperations with the failing operation index', async function () {
|
||||
const testHash = this.fileHash
|
||||
// Content is 'the quick brown fox' (length 19)
|
||||
const fileData = new LazyStringFileData(testHash, undefined, 19)
|
||||
// First op is valid: insert at end
|
||||
const goodOp = new TextOperation().retain(19).insert('!')
|
||||
// Second op expects length 999 — will fail
|
||||
const badOp = new TextOperation()
|
||||
badOp.retain(999)
|
||||
fileData.operations.push(goodOp)
|
||||
fileData.operations.push(badOp)
|
||||
fileData.stringLength = 999
|
||||
|
||||
try {
|
||||
await fileData.toEager(this.blobStore)
|
||||
expect.fail('should have thrown')
|
||||
} catch (err) {
|
||||
const info = OError.getFullInfo(err)
|
||||
// The second operation (index 1) should be the one that fails
|
||||
expect(info).to.have.property('operationIndex', 1)
|
||||
expect(info).to.have.property('totalOperations', 2)
|
||||
expect(info).to.have.property('currentContentLength', 20)
|
||||
// toEager also tags with blob metadata
|
||||
expect(info).to.have.property('blobHash', testHash)
|
||||
expect(info).to.have.property('blobContentLength', 19)
|
||||
expect(info).to.have.property('metadataStringLength', 999)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,6 +17,10 @@ const { RetainOp, InsertOp, RemoveOp } = require('../../lib/operation/scan_op')
|
||||
const TrackingProps = require('../../lib/file_data/tracking_props')
|
||||
const ClearTrackingProps = require('../../lib/file_data/clear_tracking_props')
|
||||
|
||||
function fuzzingErrorMessage(obj) {
|
||||
return `Failed randomized test with input: ${JSON.stringify(obj)}`
|
||||
}
|
||||
|
||||
describe('TextOperation', function () {
|
||||
const numTrials = 500
|
||||
|
||||
@@ -145,18 +149,12 @@ describe('TextOperation', function () {
|
||||
const str = random.string(50)
|
||||
const comments = random.comments(6)
|
||||
const o = randomOperation(str, comments.ids)
|
||||
try {
|
||||
expect(str.length).to.equal(o.baseLength)
|
||||
const file = new StringFileData(str, comments.comments)
|
||||
o.apply(file)
|
||||
const result = file.getContent()
|
||||
expect(result.length).to.equal(o.targetLength)
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
err.message = `Failing inputs:\n str: ${JSON.stringify(str)}\n comments: ${JSON.stringify(comments)}\n o: ${JSON.stringify(o.toJSON())}\n\n${err.message}`
|
||||
}
|
||||
throw err
|
||||
}
|
||||
const fuzzingError = fuzzingErrorMessage({ str, comments, o: o.toJSON() })
|
||||
expect(str.length).to.equal(o.baseLength, fuzzingError)
|
||||
const file = new StringFileData(str, comments.comments)
|
||||
o.apply(file)
|
||||
const result = file.getContent()
|
||||
expect(result.length).to.equal(o.targetLength, fuzzingError)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -166,15 +164,11 @@ describe('TextOperation', function () {
|
||||
const doc = random.string(50)
|
||||
const comments = random.comments(2)
|
||||
const operation = randomOperation(doc, comments.ids)
|
||||
try {
|
||||
const roundTripOperation = TextOperation.fromJSON(operation.toJSON())
|
||||
expect(operation.equals(roundTripOperation)).to.be.true
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
err.message = `Failing inputs:\n doc: ${JSON.stringify(doc)}\n comments: ${JSON.stringify(comments)}\n operation: ${JSON.stringify(operation.toJSON())}\n\n${err.message}`
|
||||
}
|
||||
throw err
|
||||
}
|
||||
const roundTripOperation = TextOperation.fromJSON(operation.toJSON())
|
||||
expect(operation.equals(roundTripOperation)).to.equal(
|
||||
true,
|
||||
fuzzingErrorMessage({ operation })
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -227,22 +221,20 @@ describe('TextOperation', function () {
|
||||
const str = random.string(50)
|
||||
const comments = random.comments(6)
|
||||
const o = randomOperation(str, comments.ids)
|
||||
try {
|
||||
const originalFile = new StringFileData(str, comments.comments)
|
||||
const p = o.invert(originalFile)
|
||||
expect(o.baseLength).to.equal(p.targetLength)
|
||||
expect(o.targetLength).to.equal(p.baseLength)
|
||||
const file = new StringFileData(str, comments.comments)
|
||||
o.apply(file)
|
||||
p.apply(file)
|
||||
const result = file.toRaw()
|
||||
expect(result).to.deep.equal(originalFile.toRaw())
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
err.message = `Failing inputs:\n str: ${JSON.stringify(str)}\n comments: ${JSON.stringify(comments)}\n o: ${JSON.stringify(o.toJSON())}\n\n${err.message}`
|
||||
}
|
||||
throw err
|
||||
}
|
||||
const originalFile = new StringFileData(str, comments.comments)
|
||||
const p = o.invert(originalFile)
|
||||
const fuzzingError = fuzzingErrorMessage({
|
||||
str,
|
||||
comments,
|
||||
o: o.toJSON(),
|
||||
})
|
||||
expect(o.baseLength).to.equal(p.targetLength, fuzzingError)
|
||||
expect(o.targetLength).to.equal(p.baseLength, fuzzingError)
|
||||
const file = new StringFileData(str, comments.comments)
|
||||
o.apply(file)
|
||||
p.apply(file)
|
||||
const result = file.toRaw()
|
||||
expect(result).to.deep.equal(originalFile.toRaw(), fuzzingError)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -394,26 +386,33 @@ describe('TextOperation', function () {
|
||||
const str = random.string(20)
|
||||
const comments = random.comments(6)
|
||||
const a = randomOperation(str, comments.ids)
|
||||
const fuzzingError = fuzzingErrorMessage({
|
||||
str,
|
||||
comments,
|
||||
a: a.toJSON(),
|
||||
})
|
||||
const file = new StringFileData(str, comments.comments)
|
||||
a.apply(file)
|
||||
const afterA = file.toRaw()
|
||||
expect(afterA.content.length).to.equal(a.targetLength, fuzzingError)
|
||||
const b = randomOperation(afterA.content, comments.ids)
|
||||
try {
|
||||
expect(afterA.content.length).to.equal(a.targetLength)
|
||||
b.apply(file)
|
||||
const afterB = file.toRaw()
|
||||
expect(afterB.content.length).to.equal(b.targetLength)
|
||||
const ab = a.compose(b)
|
||||
expect(ab.targetLength).to.equal(b.targetLength)
|
||||
ab.apply(new StringFileData(str, comments.comments))
|
||||
const afterAB = file.toRaw()
|
||||
expect(afterAB).to.deep.equal(afterB)
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
err.message = `Failing inputs:\n str: ${JSON.stringify(str)}\n comments: ${JSON.stringify(comments)}\n a: ${JSON.stringify(a.toJSON())}\n b: ${JSON.stringify(b.toJSON())}\n\n${err.message}`
|
||||
}
|
||||
throw err
|
||||
}
|
||||
const fuzzingErrorWithB = fuzzingErrorMessage({
|
||||
str,
|
||||
comments,
|
||||
a: a.toJSON(),
|
||||
b: b.toJSON(),
|
||||
})
|
||||
b.apply(file)
|
||||
const afterB = file.toRaw()
|
||||
expect(afterB.content.length).to.equal(
|
||||
b.targetLength,
|
||||
fuzzingErrorWithB
|
||||
)
|
||||
const ab = a.compose(b)
|
||||
expect(ab.targetLength).to.equal(b.targetLength, fuzzingErrorWithB)
|
||||
ab.apply(new StringFileData(str, comments.comments))
|
||||
const afterAB = file.toRaw()
|
||||
expect(afterAB).to.deep.equal(afterB, fuzzingErrorWithB)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -622,24 +621,23 @@ describe('TextOperation', function () {
|
||||
const comments = random.comments(6)
|
||||
const a = randomOperation(str, comments.ids)
|
||||
const b = randomOperation(str, comments.ids)
|
||||
try {
|
||||
const primes = TextOperation.transform(a, b)
|
||||
const aPrime = primes[0]
|
||||
const bPrime = primes[1]
|
||||
const abPrime = a.compose(bPrime)
|
||||
const baPrime = b.compose(aPrime)
|
||||
const abFile = new StringFileData(str, comments.comments)
|
||||
const baFile = new StringFileData(str, comments.comments)
|
||||
abPrime.apply(abFile)
|
||||
baPrime.apply(baFile)
|
||||
expect(abPrime.equals(baPrime)).to.be.true
|
||||
expect(abFile.toRaw()).to.deep.equal(baFile.toRaw())
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
err.message = `Failing inputs:\n str: ${JSON.stringify(str)}\n comments: ${JSON.stringify(comments)}\n a: ${JSON.stringify(a.toJSON())}\n b: ${JSON.stringify(b.toJSON())}\n\n${err.message}`
|
||||
}
|
||||
throw err
|
||||
}
|
||||
const primes = TextOperation.transform(a, b)
|
||||
const aPrime = primes[0]
|
||||
const bPrime = primes[1]
|
||||
const abPrime = a.compose(bPrime)
|
||||
const baPrime = b.compose(aPrime)
|
||||
const abFile = new StringFileData(str, comments.comments)
|
||||
const baFile = new StringFileData(str, comments.comments)
|
||||
abPrime.apply(abFile)
|
||||
baPrime.apply(baFile)
|
||||
const fuzzingError = fuzzingErrorMessage({
|
||||
str,
|
||||
comments,
|
||||
a: a.toJSON(),
|
||||
b: b.toJSON(),
|
||||
})
|
||||
expect(abPrime.equals(baPrime)).to.be.equal(true, fuzzingError)
|
||||
expect(abFile.toRaw()).to.deep.equal(baFile.toRaw(), fuzzingError)
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -5,5 +5,6 @@ promise-utils
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/promise-utils
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
"description": "utilities to help working with promises",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"author": "Overleaf (https://www.overleaf.com)",
|
||||
|
||||
@@ -5,5 +5,6 @@ ranges-tracker
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/ranges-tracker
|
||||
--pipeline-owner=44
|
||||
--public-repo=False
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
"scripts": {
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
|
||||
@@ -5,5 +5,6 @@ redis-wrapper
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/redis-wrapper
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -15,23 +15,23 @@
|
||||
"scripts": {
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@overleaf/logger": "*",
|
||||
"@overleaf/metrics": "*",
|
||||
"@overleaf/o-error": "*"
|
||||
"@overleaf/logger": "workspace:*",
|
||||
"@overleaf/metrics": "workspace:*",
|
||||
"@overleaf/o-error": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^3.2.5",
|
||||
"ioredis": "~4.27.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@overleaf/logger": "*",
|
||||
"@overleaf/o-error": "*",
|
||||
"@overleaf/logger": "workspace:*",
|
||||
"@overleaf/o-error": "workspace:*",
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^11.1.0",
|
||||
"mocha-junit-reporter": "^2.2.1",
|
||||
|
||||
@@ -5,5 +5,6 @@ settings
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/settings
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"scripts": {
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
|
||||
@@ -5,5 +5,6 @@ stream-utils
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/stream-utils
|
||||
--pipeline-owner=32
|
||||
--public-repo=False
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
"description": "stream handling utilities",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"author": "Overleaf (https://www.overleaf.com)",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
// @ts-check
|
||||
|
||||
const OError = require('@overleaf/o-error')
|
||||
|
||||
class ParamsError extends OError {}
|
||||
/**
|
||||
* @typedef {import('zod').ZodError} ZodError
|
||||
*/
|
||||
|
||||
module.exports = { ParamsError }
|
||||
class InvalidRequestError extends OError {
|
||||
/**
|
||||
* @param {ZodError} zodError
|
||||
*/
|
||||
constructor(zodError) {
|
||||
super('Invalid request', {}, zodError)
|
||||
this.zodError = zodError
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidParamsError extends OError {
|
||||
/**
|
||||
* @param {ZodError} zodError
|
||||
*/
|
||||
constructor(zodError) {
|
||||
super('Invalid request parameters', {}, zodError)
|
||||
this.zodError = zodError
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { InvalidParamsError, InvalidRequestError }
|
||||
|
||||
@@ -5,6 +5,7 @@ validation-tools
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=24.14.1
|
||||
--package-name=@overleaf/validation-tools
|
||||
--public-repo=False
|
||||
--test-acceptance-vitest=True
|
||||
--test-unit-vitest=True
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
const { isZodErrorLike, fromError } = require('zod-validation-error')
|
||||
const { fromError } = require('zod-validation-error')
|
||||
const { InvalidParamsError, InvalidRequestError } = require('./Errors')
|
||||
|
||||
function createHandleValidationError(statusCode = 400) {
|
||||
return [
|
||||
(err, req, res, next) => {
|
||||
if (!isZodErrorLike(err)) {
|
||||
return next(err)
|
||||
}
|
||||
|
||||
res.status(statusCode).json({ ...fromError(err), statusCode })
|
||||
},
|
||||
]
|
||||
return (err, req, res, next) => {
|
||||
if (err instanceof InvalidParamsError) {
|
||||
res
|
||||
.status(404)
|
||||
.json({ error: fromError(err.zodError).toString(), statusCode: 404 })
|
||||
} else if (err instanceof InvalidRequestError) {
|
||||
res
|
||||
.status(statusCode)
|
||||
.json({ error: fromError(err.zodError).toString(), statusCode })
|
||||
} else {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleValidationError = createHandleValidationError(400)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { ParamsError } = require('./Errors')
|
||||
const { InvalidParamsError, InvalidRequestError } = require('./Errors')
|
||||
const { z } = require('zod')
|
||||
const { zz } = require('./zodHelpers')
|
||||
const { parseReq } = require('./parseReq')
|
||||
@@ -15,5 +15,6 @@ module.exports = {
|
||||
parseReq,
|
||||
handleValidationError,
|
||||
createHandleValidationError,
|
||||
ParamsError,
|
||||
InvalidRequestError,
|
||||
InvalidParamsError,
|
||||
}
|
||||
|
||||
@@ -10,21 +10,21 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run types:check && npm run test:unit",
|
||||
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
|
||||
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
|
||||
"test:ci": "npm run test:unit",
|
||||
"test:ci": "yarn run test:unit",
|
||||
"test:unit": "vitest --config vitest.config.ts",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@overleaf/o-error": "*",
|
||||
"@overleaf/o-error": "workspace:*",
|
||||
"mongodb": "^6.12.0",
|
||||
"zod": "^4.1.8",
|
||||
"zod-validation-error": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.4",
|
||||
"vitest": "^4.0.0"
|
||||
"vitest": "4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-check
|
||||
const { ParamsError } = require('./Errors')
|
||||
const { InvalidRequestError, InvalidParamsError } = require('./Errors')
|
||||
|
||||
/**
|
||||
* @typedef {import('zod').ZodType} ZodType
|
||||
@@ -25,9 +25,9 @@ function parseReq(req, schema) {
|
||||
return parsed.data
|
||||
} else if (parsed.error.issues.some(issue => issue.path[0] === 'params')) {
|
||||
// Parts of the URL path failed to validate; throw a specific error
|
||||
throw new ParamsError('Invalid params').withCause(parsed.error)
|
||||
throw new InvalidParamsError(parsed.error)
|
||||
} else {
|
||||
throw parsed.error
|
||||
throw new InvalidRequestError(parsed.error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('parseReq', () => {
|
||||
}),
|
||||
})
|
||||
)
|
||||
).toThrowError(expect.objectContaining({ name: 'ParamsError' }))
|
||||
).toThrowError(expect.objectContaining({ name: 'InvalidParamsError' }))
|
||||
})
|
||||
|
||||
it('should throw an error containing issues if the schema is invalid', () => {
|
||||
@@ -75,9 +75,11 @@ describe('parseReq', () => {
|
||||
)
|
||||
).toThrowError(
|
||||
expect.objectContaining({
|
||||
issues: expect.arrayContaining([
|
||||
expect.objectContaining({ path: ['body', 'name'] }),
|
||||
]),
|
||||
zodError: expect.objectContaining({
|
||||
issues: expect.arrayContaining([
|
||||
expect.objectContaining({ path: ['body', 'name'] }),
|
||||
]),
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -217,4 +217,121 @@ describe('zodHelpers', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
describe('buildId', () => {
|
||||
it('fails to parse when provided with an invalid buildId', () => {
|
||||
const parsed = zz.buildId().safeParse('aa')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'invalid buildId',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('parses successfully when provided with a valid buildId', () => {
|
||||
const parsed = zz.buildId().safeParse('19d6c341530-878fff6cdab7fb0c')
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe('19d6c341530-878fff6cdab7fb0c')
|
||||
})
|
||||
|
||||
it('fails to parse when provided with an editorBuildId', () => {
|
||||
const parsed = zz
|
||||
.buildId()
|
||||
.safeParse(
|
||||
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
|
||||
)
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'invalid buildId',
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('editorBuildId', () => {
|
||||
it('fails to parse when provided with an invalid buildId', () => {
|
||||
const parsed = zz.editorBuildId().safeParse('aa')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'invalid editorId-buildId',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('fails to parse when provided with a buildId', () => {
|
||||
const parsed = zz
|
||||
.editorBuildId()
|
||||
.safeParse('19d6c341530-878fff6cdab7fb0c')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'invalid editorId-buildId',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('parses successfully when provided with a valid editorId-buildId', () => {
|
||||
const parsed = zz
|
||||
.editorBuildId()
|
||||
.safeParse(
|
||||
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
|
||||
)
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe(
|
||||
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('filepath', () => {
|
||||
it('fails to parse with empty input', () => {
|
||||
const parsed = zz.filepath().safeParse('')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'path is empty',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('fails to parse with absolute path', () => {
|
||||
const parsed = zz.filepath().safeParse('/output.pdf')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'path is absolute',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('fails to parse when provided with path traversal', () => {
|
||||
const parsed = zz.filepath().safeParse('../output.pdf')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'path traversal detected',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('parses successfully when provided a valid path', () => {
|
||||
const parsed = zz.filepath().safeParse('output.pdf')
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe('output.pdf')
|
||||
})
|
||||
|
||||
it('parses successfully when provided a valid nested path', () => {
|
||||
const parsed = zz.filepath().safeParse('foo/output.pdf')
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe('foo/output.pdf')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
function asZodError(...def) {
|
||||
return {
|
||||
name: 'ZodError',
|
||||
_zod: { def },
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { asZodError }
|
||||
@@ -33,6 +33,31 @@ const zz = {
|
||||
datetimeNullable: options => datetimeSchema({ ...options, allowNull: true }),
|
||||
datetimeNullish: options =>
|
||||
datetimeSchema({ ...options, allowNull: true, allowUndefined: true }),
|
||||
buildId: () =>
|
||||
z.string().regex(/^[0-9a-f]+-[0-9a-f]+$/, { message: 'invalid buildId' }),
|
||||
editorBuildId: () =>
|
||||
z.string().regex(/^[a-f0-9-]{36}-[0-9a-f]+-[0-9a-f]+$/, {
|
||||
message: 'invalid editorId-buildId',
|
||||
}),
|
||||
clsiServerId: () =>
|
||||
z.string().regex(/^[a-z0-9-]+$/, { message: 'invalid clsiServerId' }),
|
||||
compileBackendClass: () =>
|
||||
z
|
||||
.string()
|
||||
.regex(/^[a-z0-9-]+$/, { message: 'invalid compileBackendClass' }),
|
||||
compileGroup: () =>
|
||||
z.enum(['alpha', 'gvisor', 'standard', 'priority'], {
|
||||
message: 'invalid compileGroup',
|
||||
}),
|
||||
submissionId: () => z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
||||
filepath: () =>
|
||||
z
|
||||
.string()
|
||||
.nonempty({ message: 'path is empty' })
|
||||
.refine(s => !s.startsWith('/'), { message: 'path is absolute' })
|
||||
.refine(s => !s.split('/').includes('..'), {
|
||||
message: 'path traversal detected',
|
||||
}),
|
||||
}
|
||||
|
||||
module.exports = { zz }
|
||||
|
||||
Generated
-55115
File diff suppressed because it is too large
Load Diff
+177
-24
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "overleaf",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"patch-package": "^8.0.0"
|
||||
},
|
||||
"packageManager": "yarn@4.14.1",
|
||||
"devDependencies": {
|
||||
"@overleaf/eslint-plugin": "workspace:*",
|
||||
"@prettier/plugin-pug": "^3.4.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/chai-as-promised": "^7.1.8",
|
||||
@@ -30,23 +29,180 @@
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"npm": "11.11.0"
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
"request@2.88.2": {
|
||||
"tough-cookie": "5.1.2",
|
||||
"form-data": "2.5.5",
|
||||
"qs": "6.14.1"
|
||||
},
|
||||
"cypress@13.13.2": {
|
||||
"@cypress/request@3.0.9": {
|
||||
"qs": "6.14.1"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"@xmldom/xmldom": "0.8.13",
|
||||
"argparse/underscore": "1.13.8",
|
||||
"east/underscore": "1.13.8",
|
||||
"referer-parser/js-yaml": "^4.1.0",
|
||||
"sandboxed-module": "patch:sandboxed-module@npm%3A2.0.4#~/.yarn/patches/sandboxed-module-npm-2.0.4-f8b45aacc9.patch",
|
||||
"request/tough-cookie": "5.1.2",
|
||||
"request/form-data": "2.5.5",
|
||||
"request/qs": "6.14.1",
|
||||
"@cypress/request/qs": "6.14.1",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
"mocha@^11.1.0": {
|
||||
"serialize-javascript": "7.0.5"
|
||||
}
|
||||
"mocha/serialize-javascript": "7.0.5",
|
||||
"pprof/protobufjs": "7.5.5",
|
||||
"@google-cloud/profiler/protobufjs": "7.5.5",
|
||||
"mocha-multi-reporters": "patch:mocha-multi-reporters@npm%3A1.5.1#~/.yarn/patches/mocha-multi-reporters-npm-1.5.1-0a1088aed5.patch",
|
||||
"pdfjs-dist": "patch:pdfjs-dist@npm%3A5.1.91#~/.yarn/patches/pdfjs-dist-npm-5.1.91.patch",
|
||||
"referer-parser": "patch:referer-parser@npm%3A0.0.3#~/.yarn/patches/referer-parser-npm-0.0.3.patch",
|
||||
"sass": "1.77.1",
|
||||
"@codemirror/autocomplete": "patch:@codemirror/autocomplete@npm%3A6.18.4#~/.yarn/patches/@codemirror-autocomplete-npm-6.18.4.patch",
|
||||
"@codemirror/commands": "6.10.1",
|
||||
"@codemirror/language": "6.12.1",
|
||||
"@codemirror/lint": "6.9.2",
|
||||
"@codemirror/search": "patch:@codemirror/search@npm%3A6.5.8#~/.yarn/patches/@codemirror-search-npm-6.5.8.patch",
|
||||
"@codemirror/state": "6.5.4",
|
||||
"@codemirror/view": "6.38.6",
|
||||
"@lezer/common": "1.5.0",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lezer/lr": "1.4.7",
|
||||
"@types/react": "18.3.28",
|
||||
"@types/react-dom": "18.3.7",
|
||||
"@vitest/expect": "4.1.5",
|
||||
"@vitest/mocker": "4.1.5",
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"sinon-chai": "3.7.0",
|
||||
"i18next-scanner/i18next": "23.16.8",
|
||||
"downshift": "9.0.9",
|
||||
"body-parser@npm:1.20.4": "patch:body-parser@npm%3A1.20.4#~/.yarn/patches/body-parser-npm-1.20.4.patch",
|
||||
"cypress-multi-reporters": "patch:cypress-multi-reporters@npm%3A2.0.5#~/.yarn/patches/cypress-multi-reporters-npm-2.0.5.patch",
|
||||
"forwarded@npm:0.2.0": "patch:forwarded@npm%3A0.2.0#~/.yarn/patches/forwarded-npm-0.2.0.patch",
|
||||
"multer@npm:2.1.1": "patch:multer@npm%3A2.1.1#~/.yarn/patches/multer-npm-2.1.1.patch",
|
||||
"node-fetch": "patch:node-fetch@npm%3A2.7.0#~/.yarn/patches/node-fetch-npm-2.7.0.patch",
|
||||
"passport-oauth2": "patch:passport-oauth2@npm%3A1.6.1#~/.yarn/patches/passport-oauth2-npm-1.6.1.patch",
|
||||
"send": "patch:send@npm%3A0.19.0#~/.yarn/patches/send-npm-0.19.0.patch",
|
||||
"serve-static": "1.16.2",
|
||||
"@uppy/xhr-upload": "3.6.0",
|
||||
"recurly": "4.12.0",
|
||||
"mongoose": "8.22.1",
|
||||
"pg": "8.7.1",
|
||||
"pg-query-stream": "4.7.1",
|
||||
"@aws-sdk/client-s3": "3.994.0",
|
||||
"@aws-sdk/client-ses": "3.994.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.994.0",
|
||||
"contentful": "10.8.5",
|
||||
"@contentful/rich-text-html-renderer": "16.0.2",
|
||||
"@contentful/rich-text-types": "16.0.2",
|
||||
"i18next": "23.10.0",
|
||||
"sanitize-html": "2.17.4",
|
||||
"lodash": "4.18.1",
|
||||
"express-session": "1.17.2",
|
||||
"ioredis": "4.27.11",
|
||||
"knip": "5.64.1",
|
||||
"eslint-plugin-testing-library": "7.5.3",
|
||||
"chart.js": "4.0.1",
|
||||
"mock-fs": "5.2.0",
|
||||
"@customerio/cdp-analytics-node": "0.3.9",
|
||||
"@google-cloud/bigquery": "8.1.1",
|
||||
"moment": "2.29.4",
|
||||
"sequelize-cli": "6.6.0",
|
||||
"async": "3.2.5",
|
||||
"dockerode": "4.0.9",
|
||||
"tar-fs": "3.1.1",
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"@octokit/request": "9.2.2",
|
||||
"randomstring": "1.2.2",
|
||||
"vite": "7.3.1",
|
||||
"isomorphic-git": "1.33.1",
|
||||
"http-status": "1.5.0",
|
||||
"knex": "2.4.0",
|
||||
"utf-8-validate": "5.0.8",
|
||||
"samlp": "7.0.2",
|
||||
"compression": "1.7.4",
|
||||
"cookie-parser": "1.4.6",
|
||||
"react-cookie": "7.2.0",
|
||||
"react-dropzone": "14.2.3",
|
||||
"@babel/core": "7.28.5",
|
||||
"@babel/preset-env": "7.28.5",
|
||||
"@babel/register": "7.28.3",
|
||||
"@vitejs/plugin-react": "4.4.1",
|
||||
"cssnano": "7.1.4",
|
||||
"mini-css-extract-plugin": "2.7.6",
|
||||
"nodemon": "3.0.1",
|
||||
"postcss": "8.5.8",
|
||||
"postcss-reporter": "7.0.5",
|
||||
"zod": "4.1.11",
|
||||
"zod-validation-error": "4.0.1",
|
||||
"simple-oauth2": "5.0.0",
|
||||
"@types/simple-oauth2": "5.0.7",
|
||||
"@node-oauth/oauth2-server": "5.3.0",
|
||||
"@phosphor-icons/react": "2.1.7",
|
||||
"@slack/webhook": "7.0.2",
|
||||
"@stripe/react-stripe-js": "3.9.0",
|
||||
"@stripe/stripe-js": "7.7.0",
|
||||
"cache-flow": "1.9.0",
|
||||
"focus-trap-react": "11.0.4",
|
||||
"i18next-http-middleware": "3.5.0",
|
||||
"jose": "4.15.5",
|
||||
"nodemailer": "8.0.5",
|
||||
"on-headers": "1.0.2",
|
||||
"pug": "3.0.3",
|
||||
"rate-limiter-flexible": "2.4.1",
|
||||
"react-hook-form": "7.71.1",
|
||||
"stripe": "18.4.0",
|
||||
"@babel/plugin-proposal-decorators": "7.28.0",
|
||||
"@floating-ui/react": "0.27.16",
|
||||
"@juggle/resize-observer": "3.3.1",
|
||||
"@storybook/addon-a11y": "10.3.5",
|
||||
"@storybook/addon-essentials": "10.3.5",
|
||||
"@storybook/addon-interactions": "10.3.5",
|
||||
"@storybook/addon-links": "10.3.5",
|
||||
"@storybook/cli": "10.3.5",
|
||||
"@storybook/react": "10.3.5",
|
||||
"@storybook/react-webpack5": "10.3.5",
|
||||
"@storybook/theming": "10.3.5",
|
||||
"@streamdown/cjk": "1.0.2",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/user-event": "14.5.2",
|
||||
"@types/express": "4.17.23",
|
||||
"@types/recurly__recurly-js": "4.38.0",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@uppy/dashboard": "3.7.1",
|
||||
"@uppy/drag-drop": "3.0.3",
|
||||
"@uppy/file-input": "3.0.4",
|
||||
"@uppy/progress-bar": "3.0.4",
|
||||
"@uppy/react": "3.2.1",
|
||||
"autoprefixer": "10.4.16",
|
||||
"babel-plugin-module-resolver": "5.0.2",
|
||||
"backbone": "1.6.0",
|
||||
"dompurify": "3.4.0",
|
||||
"eventsource-client": "1.1.4",
|
||||
"fake-indexeddb": "6.0.0",
|
||||
"formik": "2.2.9",
|
||||
"katex": "0.16.28",
|
||||
"match-sorter": "6.3.1",
|
||||
"micromark": "4.0.0",
|
||||
"pirates": "4.0.6",
|
||||
"qrcode": "1.5.0",
|
||||
"react-chartjs-2": "5.0.1",
|
||||
"react-i18next": "13.3.1",
|
||||
"react-resizable-panels": "2.1.1",
|
||||
"rehype-harden": "1.1.7",
|
||||
"scroll-into-view-if-needed": "2.2.28",
|
||||
"storybook": "10.3.5",
|
||||
"streamdown": "2.2.0",
|
||||
"tailwindcss": "3.4.17",
|
||||
"thread-loader": "patch:thread-loader@npm%3A4.0.2#~/.yarn/patches/thread-loader-npm-4.0.2-dab5735f54.patch",
|
||||
"unist-util-visit": "5.0.0",
|
||||
"use-stick-to-bottom": "1.1.1",
|
||||
"zustand": "5.0.8",
|
||||
"retry-request@npm:^8.0.0": "patch:retry-request@npm%3A8.0.2#~/.yarn/patches/retry-request-npm-8.0.2-448ad084c8.patch",
|
||||
"retry-request@npm:^7.0.0": "patch:retry-request@npm%3A7.0.2#~/.yarn/patches/retry-request-npm-7.0.2-a41087680c.patch",
|
||||
"teeny-request@npm:^10.0.0": "patch:teeny-request@npm%3A10.1.0#~/.yarn/patches/teeny-request-npm-10.1.0.patch",
|
||||
"teeny-request@npm:^9.0.0": "patch:teeny-request@npm%3A9.0.0#~/.yarn/patches/teeny-request-npm-9.0.0-4d571e3c55.patch",
|
||||
"@babel/plugin-transform-modules-systemjs": "7.29.4",
|
||||
"fast-xml-builder": "1.1.7",
|
||||
"ip-address": "10.1.1",
|
||||
"systeminformation": "5.31.6",
|
||||
"fast-uri": "3.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --check",
|
||||
@@ -57,11 +213,10 @@
|
||||
"format:pug:fix": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --write --check '**/*.pug'",
|
||||
"format:jenkins": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --check '**/Jenkinsfile'",
|
||||
"format:jenkins:fix": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile'",
|
||||
"format:monorepo-check": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --check '**/Jenkinsfile' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml'",
|
||||
"format:monorepo-check:fix": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml'",
|
||||
"format:monorepo-check": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --check '**/Jenkinsfile' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml' '.agents/**/*.js' '.agents/**/*.md'",
|
||||
"format:monorepo-check:fix": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml' '.agents/**/*.js' '.agents/**/*.md'",
|
||||
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint/ --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --cache --cache-location ./node_modules/.cache/eslint/ --fix .",
|
||||
"postinstall": "patch-package"
|
||||
"lint:fix": "eslint --cache --cache-location ./node_modules/.cache/eslint/ --fix ."
|
||||
},
|
||||
"workspaces": [
|
||||
"jobs/mirror-documentation",
|
||||
@@ -72,7 +227,6 @@
|
||||
"services/clsi",
|
||||
"services/clsi-cache",
|
||||
"services/clsi-perf",
|
||||
"services/contacts",
|
||||
"services/docstore",
|
||||
"services/document-updater",
|
||||
"services/filestore",
|
||||
@@ -87,7 +241,6 @@
|
||||
"services/templates",
|
||||
"services/third-party-datastore",
|
||||
"services/third-party-references",
|
||||
"services/tpdsworker",
|
||||
"services/web",
|
||||
"tools/dependency-management",
|
||||
"tools/npm-overrides-helper",
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
diff --git a/node_modules/@google-cloud/logging/node_modules/teeny-request/build/src/index.js b/node_modules/@google-cloud/logging/node_modules/teeny-request/build/src/index.js
|
||||
index af5d15e..2b63d0c 100644
|
||||
--- a/node_modules/@google-cloud/logging/node_modules/teeny-request/build/src/index.js
|
||||
+++ b/node_modules/@google-cloud/logging/node_modules/teeny-request/build/src/index.js
|
||||
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
|
||||
}
|
||||
else {
|
||||
part.body.pipe(stream, { end: false });
|
||||
+ part.body.on('error', (err) => {
|
||||
+ stream.destroy(err);
|
||||
+ });
|
||||
part.body.on('end', () => {
|
||||
stream.write('\r\n');
|
||||
stream.write(finale);
|
||||
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
|
||||
// Stream mode
|
||||
const requestStream = streamEvents(new stream_1.PassThrough());
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
- let responseStream;
|
||||
- requestStream.once('reading', () => {
|
||||
- if (responseStream) {
|
||||
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
- }
|
||||
- else {
|
||||
- requestStream.once('response', () => {
|
||||
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
- });
|
||||
- }
|
||||
- });
|
||||
+ // let responseStream;
|
||||
+ // requestStream.once('reading', () => {
|
||||
+ // if (responseStream) {
|
||||
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
+ // }
|
||||
+ // else {
|
||||
+ // requestStream.once('response', () => {
|
||||
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
+ // });
|
||||
+ // }
|
||||
+ // });
|
||||
+
|
||||
+
|
||||
options.compress = false;
|
||||
teenyRequest.stats.requestStarting();
|
||||
(0, node_fetch_1.default)(uri, options).then(res => {
|
||||
- teenyRequest.stats.requestFinished();
|
||||
- responseStream = res.body;
|
||||
- responseStream.on('error', (err) => {
|
||||
- requestStream.emit('error', err);
|
||||
- });
|
||||
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
|
||||
+ // responseStream = res.body;
|
||||
+ // responseStream.on('error', (err) => {
|
||||
+ // requestStream.emit('error', err);
|
||||
+ // });
|
||||
const response = fetchToRequestResponse(options, res);
|
||||
requestStream.emit('response', response);
|
||||
}, err => {
|
||||
@@ -1,30 +0,0 @@
|
||||
diff --git a/node_modules/@google-cloud/logging-min/node_modules/retry-request/index.js b/node_modules/@google-cloud/logging-min/node_modules/retry-request/index.js
|
||||
index 2fae107..5721c54 100644
|
||||
--- a/node_modules/@google-cloud/logging-min/node_modules/retry-request/index.js
|
||||
+++ b/node_modules/@google-cloud/logging-min/node_modules/retry-request/index.js
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
-const {PassThrough} = require('stream');
|
||||
+const { PassThrough, pipeline } = require('stream');
|
||||
const extend = require('extend');
|
||||
|
||||
let debug = () => {};
|
||||
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
|
||||
.on('complete', (...params) => handleFinish(params))
|
||||
.on('finish', (...params) => handleFinish(params));
|
||||
|
||||
- requestStream.pipe(delayStream);
|
||||
+ pipeline(requestStream, delayStream, () => {});
|
||||
} else {
|
||||
activeRequest = opts.request(requestOpts, onResponse);
|
||||
}
|
||||
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
|
||||
// No more attempts need to be made, just continue on.
|
||||
if (streamMode) {
|
||||
retryStream.emit('response', response);
|
||||
- delayStream.pipe(retryStream);
|
||||
+ pipeline(delayStream, retryStream, () => {});
|
||||
requestStream.on('error', err => {
|
||||
retryStream.destroy(err);
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
diff --git a/node_modules/@google-cloud/logging-min/node_modules/teeny-request/build/src/index.js b/node_modules/@google-cloud/logging-min/node_modules/teeny-request/build/src/index.js
|
||||
index af5d15e..2b63d0c 100644
|
||||
--- a/node_modules/@google-cloud/logging-min/node_modules/teeny-request/build/src/index.js
|
||||
+++ b/node_modules/@google-cloud/logging-min/node_modules/teeny-request/build/src/index.js
|
||||
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
|
||||
}
|
||||
else {
|
||||
part.body.pipe(stream, { end: false });
|
||||
+ part.body.on('error', (err) => {
|
||||
+ stream.destroy(err);
|
||||
+ });
|
||||
part.body.on('end', () => {
|
||||
stream.write('\r\n');
|
||||
stream.write(finale);
|
||||
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
|
||||
// Stream mode
|
||||
const requestStream = streamEvents(new stream_1.PassThrough());
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
- let responseStream;
|
||||
- requestStream.once('reading', () => {
|
||||
- if (responseStream) {
|
||||
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
- }
|
||||
- else {
|
||||
- requestStream.once('response', () => {
|
||||
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
- });
|
||||
- }
|
||||
- });
|
||||
+ // let responseStream;
|
||||
+ // requestStream.once('reading', () => {
|
||||
+ // if (responseStream) {
|
||||
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
+ // }
|
||||
+ // else {
|
||||
+ // requestStream.once('response', () => {
|
||||
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
+ // });
|
||||
+ // }
|
||||
+ // });
|
||||
+
|
||||
+
|
||||
options.compress = false;
|
||||
teenyRequest.stats.requestStarting();
|
||||
(0, node_fetch_1.default)(uri, options).then(res => {
|
||||
- teenyRequest.stats.requestFinished();
|
||||
- responseStream = res.body;
|
||||
- responseStream.on('error', (err) => {
|
||||
- requestStream.emit('error', err);
|
||||
- });
|
||||
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
|
||||
+ // responseStream = res.body;
|
||||
+ // responseStream.on('error', (err) => {
|
||||
+ // requestStream.emit('error', err);
|
||||
+ // });
|
||||
const response = fetchToRequestResponse(options, res);
|
||||
requestStream.emit('response', response);
|
||||
}, err => {
|
||||
@@ -1,30 +0,0 @@
|
||||
diff --git a/node_modules/@google-cloud/profiler/node_modules/retry-request/index.js b/node_modules/@google-cloud/profiler/node_modules/retry-request/index.js
|
||||
index 2fae107..5721c54 100644
|
||||
--- a/node_modules/@google-cloud/profiler/node_modules/retry-request/index.js
|
||||
+++ b/node_modules/@google-cloud/profiler/node_modules/retry-request/index.js
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
-const {PassThrough} = require('stream');
|
||||
+const { PassThrough, pipeline } = require('stream');
|
||||
const extend = require('extend');
|
||||
|
||||
let debug = () => {};
|
||||
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
|
||||
.on('complete', (...params) => handleFinish(params))
|
||||
.on('finish', (...params) => handleFinish(params));
|
||||
|
||||
- requestStream.pipe(delayStream);
|
||||
+ pipeline(requestStream, delayStream, () => {});
|
||||
} else {
|
||||
activeRequest = opts.request(requestOpts, onResponse);
|
||||
}
|
||||
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
|
||||
// No more attempts need to be made, just continue on.
|
||||
if (streamMode) {
|
||||
retryStream.emit('response', response);
|
||||
- delayStream.pipe(retryStream);
|
||||
+ pipeline(delayStream, retryStream, () => {});
|
||||
requestStream.on('error', err => {
|
||||
retryStream.destroy(err);
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
diff --git a/node_modules/@google-cloud/profiler/node_modules/teeny-request/build/src/index.js b/node_modules/@google-cloud/profiler/node_modules/teeny-request/build/src/index.js
|
||||
index af5d15e..2b63d0c 100644
|
||||
--- a/node_modules/@google-cloud/profiler/node_modules/teeny-request/build/src/index.js
|
||||
+++ b/node_modules/@google-cloud/profiler/node_modules/teeny-request/build/src/index.js
|
||||
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
|
||||
}
|
||||
else {
|
||||
part.body.pipe(stream, { end: false });
|
||||
+ part.body.on('error', (err) => {
|
||||
+ stream.destroy(err);
|
||||
+ });
|
||||
part.body.on('end', () => {
|
||||
stream.write('\r\n');
|
||||
stream.write(finale);
|
||||
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
|
||||
// Stream mode
|
||||
const requestStream = streamEvents(new stream_1.PassThrough());
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
- let responseStream;
|
||||
- requestStream.once('reading', () => {
|
||||
- if (responseStream) {
|
||||
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
- }
|
||||
- else {
|
||||
- requestStream.once('response', () => {
|
||||
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
- });
|
||||
- }
|
||||
- });
|
||||
+ // let responseStream;
|
||||
+ // requestStream.once('reading', () => {
|
||||
+ // if (responseStream) {
|
||||
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
+ // }
|
||||
+ // else {
|
||||
+ // requestStream.once('response', () => {
|
||||
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
+ // });
|
||||
+ // }
|
||||
+ // });
|
||||
+
|
||||
+
|
||||
options.compress = false;
|
||||
teenyRequest.stats.requestStarting();
|
||||
(0, node_fetch_1.default)(uri, options).then(res => {
|
||||
- teenyRequest.stats.requestFinished();
|
||||
- responseStream = res.body;
|
||||
- responseStream.on('error', (err) => {
|
||||
- requestStream.emit('error', err);
|
||||
- });
|
||||
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
|
||||
+ // responseStream = res.body;
|
||||
+ // responseStream.on('error', (err) => {
|
||||
+ // requestStream.emit('error', err);
|
||||
+ // });
|
||||
const response = fetchToRequestResponse(options, res);
|
||||
requestStream.emit('response', response);
|
||||
}, err => {
|
||||
@@ -1,30 +0,0 @@
|
||||
diff --git a/node_modules/@google-cloud/storage/node_modules/retry-request/index.js b/node_modules/@google-cloud/storage/node_modules/retry-request/index.js
|
||||
index 2fae107..5721c54 100644
|
||||
--- a/node_modules/@google-cloud/storage/node_modules/retry-request/index.js
|
||||
+++ b/node_modules/@google-cloud/storage/node_modules/retry-request/index.js
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
-const {PassThrough} = require('stream');
|
||||
+const { PassThrough, pipeline } = require('stream');
|
||||
const extend = require('extend');
|
||||
|
||||
let debug = () => {};
|
||||
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
|
||||
.on('complete', (...params) => handleFinish(params))
|
||||
.on('finish', (...params) => handleFinish(params));
|
||||
|
||||
- requestStream.pipe(delayStream);
|
||||
+ pipeline(requestStream, delayStream, () => {});
|
||||
} else {
|
||||
activeRequest = opts.request(requestOpts, onResponse);
|
||||
}
|
||||
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
|
||||
// No more attempts need to be made, just continue on.
|
||||
if (streamMode) {
|
||||
retryStream.emit('response', response);
|
||||
- delayStream.pipe(retryStream);
|
||||
+ pipeline(delayStream, retryStream, () => {});
|
||||
requestStream.on('error', err => {
|
||||
retryStream.destroy(err);
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
diff --git a/node_modules/@google-cloud/storage/node_modules/teeny-request/build/src/index.js b/node_modules/@google-cloud/storage/node_modules/teeny-request/build/src/index.js
|
||||
index af5d15e..2b63d0c 100644
|
||||
--- a/node_modules/@google-cloud/storage/node_modules/teeny-request/build/src/index.js
|
||||
+++ b/node_modules/@google-cloud/storage/node_modules/teeny-request/build/src/index.js
|
||||
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
|
||||
}
|
||||
else {
|
||||
part.body.pipe(stream, { end: false });
|
||||
+ part.body.on('error', (err) => {
|
||||
+ stream.destroy(err);
|
||||
+ });
|
||||
part.body.on('end', () => {
|
||||
stream.write('\r\n');
|
||||
stream.write(finale);
|
||||
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
|
||||
// Stream mode
|
||||
const requestStream = streamEvents(new stream_1.PassThrough());
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
- let responseStream;
|
||||
- requestStream.once('reading', () => {
|
||||
- if (responseStream) {
|
||||
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
- }
|
||||
- else {
|
||||
- requestStream.once('response', () => {
|
||||
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
- });
|
||||
- }
|
||||
- });
|
||||
+ // let responseStream;
|
||||
+ // requestStream.once('reading', () => {
|
||||
+ // if (responseStream) {
|
||||
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
+ // }
|
||||
+ // else {
|
||||
+ // requestStream.once('response', () => {
|
||||
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
|
||||
+ // });
|
||||
+ // }
|
||||
+ // });
|
||||
+
|
||||
+
|
||||
options.compress = false;
|
||||
teenyRequest.stats.requestStarting();
|
||||
(0, node_fetch_1.default)(uri, options).then(res => {
|
||||
- teenyRequest.stats.requestFinished();
|
||||
- responseStream = res.body;
|
||||
- responseStream.on('error', (err) => {
|
||||
- requestStream.emit('error', err);
|
||||
- });
|
||||
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
|
||||
+ // responseStream = res.body;
|
||||
+ // responseStream.on('error', (err) => {
|
||||
+ // requestStream.emit('error', err);
|
||||
+ // });
|
||||
const response = fetchToRequestResponse(options, res);
|
||||
requestStream.emit('response', response);
|
||||
}, err => {
|
||||
@@ -1 +0,0 @@
|
||||
The patches in this folder are applied by `patch-package` to dependencies, particularly those which need changes that are difficult to apply upstream.
|
||||
@@ -1,22 +0,0 @@
|
||||
diff --git a/node_modules/retry-request/index.js b/node_modules/retry-request/index.js
|
||||
index 298a351..7ec1ef8 100644
|
||||
--- a/node_modules/retry-request/index.js
|
||||
+++ b/node_modules/retry-request/index.js
|
||||
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
|
||||
.on('complete', (...params) => handleFinish(params))
|
||||
.on('finish', (...params) => handleFinish(params));
|
||||
|
||||
- requestStream.pipe(delayStream);
|
||||
+ pipeline(requestStream, delayStream, () => {});
|
||||
} else {
|
||||
activeRequest = opts.request(requestOpts, onResponse);
|
||||
}
|
||||
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
|
||||
// No more attempts need to be made, just continue on.
|
||||
if (streamMode) {
|
||||
retryStream.emit('response', response);
|
||||
- delayStream.pipe(retryStream);
|
||||
+ pipeline(delayStream, retryStream, () => {});
|
||||
requestStream.on('error', err => {
|
||||
retryStream.destroy(err);
|
||||
});
|
||||
+19
-9
@@ -8,25 +8,35 @@ FROM $OVERLEAF_BASE_TAG
|
||||
|
||||
WORKDIR /overleaf
|
||||
|
||||
# Add required source files for npm install
|
||||
# -----------------------------------------
|
||||
COPY --parents libraries/*/package.json patches/ services/*/package.json tools/migrations/ package.json package-lock.json /overleaf/
|
||||
# Add required source files for yarn install
|
||||
# -------------------------------------------
|
||||
COPY --parents libraries/*/package.json .yarn/patches/ services/*/package.json tools/migrations/ package.json yarn.lock .yarnrc.yml /overleaf/
|
||||
COPY server-ce/genScript.js server-ce/services.js /overleaf/
|
||||
|
||||
# Install npm dependencies
|
||||
# ------------------------
|
||||
# Pre-install yarn via corepack so it is available at runtime for all users
|
||||
# -------------------------------------------------------------------------
|
||||
# Corepack setup, shared between all the images.
|
||||
ENV PATH="/overleaf/node_modules/.bin:$PATH"
|
||||
ENV COREPACK_HOME=/opt/corepack
|
||||
RUN corepack enable && corepack install -g yarn@4.14.1
|
||||
ENV COREPACK_ENABLE_NETWORK=0
|
||||
|
||||
# Install yarn dependencies
|
||||
# -------------------------
|
||||
RUN --mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/root/.npm \
|
||||
--mount=type=cache,target=/overleaf/services/web/node_modules/.cache,id=server-ce-webpack-cache \
|
||||
--mount=type=cache,target=/root/.yarn/berry/cache,id=server-ce-yarn-cache \
|
||||
--mount=type=cache,target=/usr/local/share/.cache/yarn,id=server-ce-yarn-fallback-cache \
|
||||
--mount=type=tmpfs,target=/tmp node genScript install | bash
|
||||
|
||||
# Add the actual source files
|
||||
# ---------------------------
|
||||
COPY --parents libraries/ services/ tools/migrations/ /overleaf/
|
||||
RUN --mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/root/.npm \
|
||||
--mount=type=cache,target=/root/.yarn/berry/cache,id=server-ce-yarn-cache \
|
||||
--mount=type=cache,target=/usr/local/share/.cache/yarn,id=server-ce-yarn-fallback-cache \
|
||||
--mount=type=cache,target=/overleaf/services/web/node_modules/.cache,id=server-ce-webpack-cache \
|
||||
--mount=type=tmpfs,target=/tmp node genScript compile | bash
|
||||
--mount=type=tmpfs,target=/tmp \
|
||||
node genScript compile | bash
|
||||
|
||||
# Copy runit service startup scripts to its location
|
||||
# --------------------------------------------------
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Overleaf Base Image (sharelatex/sharelatex-base)
|
||||
# --------------------------------------------------
|
||||
|
||||
FROM phusion/baseimage:noble-1.0.2
|
||||
FROM phusion/baseimage:noble-1.0.3
|
||||
|
||||
# Makes sure LuaTex cache is writable
|
||||
# -----------------------------------
|
||||
@@ -10,7 +10,7 @@ ENV TEXMFVAR=/var/lib/overleaf/tmp/texmf-var
|
||||
|
||||
# Update to ensure dependencies are updated
|
||||
# ------------------------------------------
|
||||
ENV REBUILT_AFTER="2026-01-13"
|
||||
ENV REBUILT_AFTER="2026-05-21"
|
||||
|
||||
# Install dependencies
|
||||
# --------------------
|
||||
|
||||
+5
-4
@@ -4,12 +4,13 @@ MONOREPO_ROOT := ../
|
||||
HERE=$(shell pwd)
|
||||
export MONOREPO_REVISION := $(shell git rev-parse HEAD)
|
||||
export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
export OVERLEAF_BASE_BRANCH ?= sharelatex/sharelatex-base:$(BRANCH_NAME)
|
||||
export BRANCH_NAME_TAG_SAFE ?= $(subst /,--,$(BRANCH_NAME))
|
||||
export OVERLEAF_BASE_BRANCH ?= sharelatex/sharelatex-base:$(BRANCH_NAME_TAG_SAFE)
|
||||
export OVERLEAF_BASE_LATEST ?= sharelatex/sharelatex-base
|
||||
export OVERLEAF_BASE_TAG ?= sharelatex/sharelatex-base:$(BRANCH_NAME)-$(MONOREPO_REVISION)
|
||||
export OVERLEAF_BRANCH ?= sharelatex/sharelatex:$(BRANCH_NAME)
|
||||
export OVERLEAF_BASE_TAG ?= sharelatex/sharelatex-base:$(BRANCH_NAME_TAG_SAFE)-$(MONOREPO_REVISION)
|
||||
export OVERLEAF_BRANCH ?= sharelatex/sharelatex:$(BRANCH_NAME_TAG_SAFE)
|
||||
export OVERLEAF_LATEST ?= sharelatex/sharelatex
|
||||
export OVERLEAF_TAG ?= sharelatex/sharelatex:$(BRANCH_NAME)-$(MONOREPO_REVISION)
|
||||
export OVERLEAF_TAG ?= sharelatex/sharelatex:$(BRANCH_NAME_TAG_SAFE)-$(MONOREPO_REVISION)
|
||||
|
||||
all: build-base build-community
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ steps:
|
||||
script: |
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
if [ ${BRANCH_NAME} == main ]; then
|
||||
docker tag ${_IMAGE_BASE} ${_IMAGE_BASE_LATEST};
|
||||
docker push ${_IMAGE_BASE_LATEST};
|
||||
if [ "${BRANCH_NAME}" = "main" ]; then
|
||||
docker tag "${_IMAGE_BASE}" "${_IMAGE_BASE_LATEST}";
|
||||
docker push "${_IMAGE_BASE_LATEST}";
|
||||
fi
|
||||
automapSubstitutions: true
|
||||
- id: prefetch_ce
|
||||
@@ -39,9 +39,9 @@ steps:
|
||||
script: |
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
if [ $BRANCH_NAME == main ]; then
|
||||
docker tag ${_IMAGE_TAG} ${_IMAGE_TAG_LATEST};
|
||||
docker push ${_IMAGE_TAG_LATEST};
|
||||
if [ "${BRANCH_NAME}" = "main" ]; then
|
||||
docker tag "${_IMAGE_TAG}" "${_IMAGE_TAG_LATEST}";
|
||||
docker push "${_IMAGE_TAG_LATEST}";
|
||||
fi
|
||||
automapSubstitutions: true
|
||||
waitFor:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export CHAT_HOST=127.0.0.1
|
||||
export CLSI_HOST=127.0.0.1
|
||||
export CONTACTS_HOST=127.0.0.1
|
||||
export DOCSTORE_HOST=127.0.0.1
|
||||
export DOCUMENT_UPDATER_HOST=127.0.0.1
|
||||
export DOCUPDATER_HOST=127.0.0.1
|
||||
|
||||
@@ -5,7 +5,7 @@ console.log('set -ex')
|
||||
|
||||
switch (process.argv.pop()) {
|
||||
case 'install':
|
||||
console.log('npm install --omit=dev')
|
||||
console.log('yarn workspaces focus --all --production')
|
||||
break
|
||||
case 'compile':
|
||||
for (const service of services) {
|
||||
@@ -13,18 +13,18 @@ switch (process.argv.pop()) {
|
||||
switch (service.name) {
|
||||
case 'web':
|
||||
// precompile pug in background
|
||||
console.log('npm run precompile-pug &')
|
||||
console.log('yarn run precompile-pug &')
|
||||
console.log('pug_precompile=$!')
|
||||
|
||||
// Avoid downloading of cypress
|
||||
console.log('export CYPRESS_INSTALL_BINARY=0')
|
||||
|
||||
// install webpack and frontend dependencies
|
||||
console.log('npm install --include=dev')
|
||||
console.log('yarn install')
|
||||
// run webpack
|
||||
console.log('npm run webpack:production')
|
||||
console.log('yarn run webpack:production')
|
||||
// uninstall webpack and frontend dependencies
|
||||
console.log('npm prune --omit=dev')
|
||||
console.log('yarn workspaces focus --all --production')
|
||||
|
||||
// Wait for pug precompile to finish
|
||||
console.log('wait "$pug_precompile"')
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
set -e
|
||||
|
||||
mkdir -p /var/lib/overleaf/data
|
||||
chown www-data:www-data /var/lib/overleaf
|
||||
chown www-data:www-data /var/lib/overleaf/data
|
||||
|
||||
mkdir -p /var/lib/overleaf/data/compiles
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Up to version 2.5.0 the logs of the contacts service were written into a
|
||||
# file that was not picked up by logrotate.
|
||||
# The service is stable and we can safely discard any logs.
|
||||
rm -vf /var/log/overleaf/contacts
|
||||
@@ -9,5 +9,5 @@ fi
|
||||
|
||||
echo "Running migrations for $environment"
|
||||
cd /overleaf/tools/migrations
|
||||
/sbin/setuser www-data npm run migrations -- migrate -t "$environment"
|
||||
/sbin/setuser www-data yarn run migrations migrate -t "$environment"
|
||||
echo "Finished migrations"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
NODE_PARAMS=""
|
||||
if [ "$DEBUG_NODE" == "true" ]; then
|
||||
echo "running debug - contacts"
|
||||
NODE_PARAMS="--inspect=0.0.0.0:30360"
|
||||
fi
|
||||
|
||||
source /etc/overleaf/env.sh
|
||||
export LISTEN_ADDRESS=127.0.0.1
|
||||
|
||||
exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /overleaf/services/contacts/app.js >> /var/log/overleaf/contacts.log 2>&1
|
||||
@@ -20,9 +20,6 @@ module.exports = [
|
||||
{
|
||||
name: 'chat',
|
||||
},
|
||||
{
|
||||
name: 'contacts',
|
||||
},
|
||||
{
|
||||
name: 'notifications',
|
||||
},
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
FROM node:24.14.1
|
||||
|
||||
# Corepack setup, shared between all the images.
|
||||
ENV PATH="/overleaf/node_modules/.bin:$PATH"
|
||||
ENV COREPACK_HOME=/opt/corepack
|
||||
RUN corepack enable && corepack install -g yarn@4.14.1
|
||||
ENV COREPACK_ENABLE_NETWORK=0
|
||||
|
||||
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
|
||||
&& echo \
|
||||
"deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
|
||||
|
||||
+15
-4
@@ -17,13 +17,25 @@ export USER_UID=$(shell id -u)
|
||||
export USER_GID=$(shell id -g)
|
||||
IMAGE_TAG_PRO_BASE := $(shell echo $(IMAGE_TAG_PRO) | sed -E s/:.+//)
|
||||
|
||||
print-debug-logs:
|
||||
-docker compose logs --timestamps --tail=100 e2e
|
||||
-docker compose logs --timestamps --tail=100 mongo
|
||||
-docker compose logs --timestamps --tail=100 redis
|
||||
-docker compose logs --timestamps --tail=100 mailtrap
|
||||
-docker compose logs --timestamps --tail=100 saml
|
||||
-docker compose logs --timestamps --tail=100 ldap
|
||||
-docker compose logs --timestamps --tail=100 git-bridge
|
||||
-docker compose logs --timestamps --tail=100 host-admin
|
||||
-docker compose logs --timestamps sharelatex
|
||||
-docker compose exec sharelatex bash -c 'tail -n100 /var/log/overleaf/*.log'
|
||||
|
||||
test-e2e-native:
|
||||
docker compose -f docker-compose.yml -f docker-compose.native.yml up --no-log-prefix sharelatex host-admin -d
|
||||
CYPRESS_BASE_URL='http://127.0.0.1:3231' \
|
||||
CYPRESS_HOST_ADMIN_URL='http://127.0.0.1:3232' \
|
||||
CYPRESS_SAML_URL='http://127.0.0.1:3233' \
|
||||
CYPRESS_MAILTRAP_URL='http://127.0.0.1:3234' \
|
||||
npm run cypress:open
|
||||
yarn run cypress:open
|
||||
|
||||
# For testing "native" on Linux
|
||||
test-e2e-native-linux:
|
||||
@@ -35,7 +47,7 @@ test-e2e-native-linux:
|
||||
docker compose -f docker-compose.yml -f docker-compose.native.yml up --no-log-prefix cypress
|
||||
|
||||
test-e2e:
|
||||
docker compose build host-admin
|
||||
docker compose build host-admin e2e
|
||||
docker compose up --detach --wait host-admin
|
||||
docker compose up --detach --wait mongo
|
||||
docker compose up --no-log-prefix --exit-code-from=e2e e2e host-admin
|
||||
@@ -69,7 +81,7 @@ prefetch_default_compose:
|
||||
|
||||
prefetch_default: prefetch_default_compose_build
|
||||
prefetch_default_compose_build:
|
||||
docker compose build host-admin
|
||||
docker compose build host-admin e2e
|
||||
|
||||
prefetch: prefetch_custom
|
||||
prefetch_custom: prefetch_custom_compose_pull
|
||||
@@ -92,7 +104,6 @@ prefetch_old_4_2:
|
||||
|
||||
prefetch_custom: prefetch_old_5_0
|
||||
prefetch_old_5_0:
|
||||
docker pull $(IMAGE_TAG_PRO_BASE):5.0.1-RC1
|
||||
docker pull $(IMAGE_TAG_PRO_BASE):5.0
|
||||
|
||||
prefetch_custom: build_hotfix_5_5_5
|
||||
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
- USER_UID
|
||||
- USER_GID
|
||||
stop_grace_period: 0s
|
||||
entrypoint: npm
|
||||
entrypoint: yarn
|
||||
command: run cypress:run
|
||||
# See comment in Makefile regarding matching file paths
|
||||
working_dir: $PWD
|
||||
@@ -80,6 +80,10 @@ services:
|
||||
- $PWD:$PWD
|
||||
- $MONOREPO/libraries:$MONOREPO/libraries:ro
|
||||
- $MONOREPO/node_modules:$MONOREPO/node_modules:ro
|
||||
- $MONOREPO/.yarn:$MONOREPO/.yarn:ro
|
||||
- $MONOREPO/.yarnrc.yml:$MONOREPO/.yarnrc.yml:ro
|
||||
- $MONOREPO/package.json:$MONOREPO/package.json:ro
|
||||
- $MONOREPO/yarn.lock:$MONOREPO/yarn.lock:ro
|
||||
environment:
|
||||
MONOREPO:
|
||||
CYPRESS_SHARD:
|
||||
@@ -110,6 +114,10 @@ services:
|
||||
- $PWD:$PWD
|
||||
- $MONOREPO/libraries:$MONOREPO/libraries:ro
|
||||
- $MONOREPO/node_modules:$MONOREPO/node_modules:ro
|
||||
- $MONOREPO/.yarn:$MONOREPO/.yarn:ro
|
||||
- $MONOREPO/.yarnrc.yml:$MONOREPO/.yarnrc.yml:ro
|
||||
- $MONOREPO/package.json:$MONOREPO/package.json:ro
|
||||
- $MONOREPO/yarn.lock:$MONOREPO/yarn.lock:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
stop_grace_period: 0s
|
||||
environment:
|
||||
|
||||
@@ -140,6 +140,7 @@ describe('editor', function () {
|
||||
describe('file menu', function () {
|
||||
it('can download project sources', function () {
|
||||
cy.findByRole('button', { name: 'File' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Download' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Download as source (.zip)' }).click()
|
||||
const zipName = projectName.replaceAll('-', '_')
|
||||
cy.task('readFileInZip', {
|
||||
@@ -156,6 +157,7 @@ describe('editor', function () {
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'File' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Download' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Download as PDF' }).click()
|
||||
const pdfName = projectName.replaceAll('-', '_')
|
||||
cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should(
|
||||
|
||||
@@ -9,7 +9,10 @@ export function stopCompile(options: { delay?: number } = {}) {
|
||||
cy.wait(delay)
|
||||
cy.log('Stop compile')
|
||||
cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Stop compilation' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Stop compilation' })
|
||||
.should('not.have.class', 'disabled')
|
||||
.and('not.have.attr', 'aria-disabled', 'true')
|
||||
.click()
|
||||
}
|
||||
|
||||
export function prepareWaitForNextCompileSlot() {
|
||||
|
||||
@@ -245,7 +245,8 @@ export function getSpamSafeProjectName() {
|
||||
// Move from hex/16 to base64/64 possible characters per char in string
|
||||
const name = Buffer.from(uuid().replaceAll('-', ''), 'hex')
|
||||
.toString('base64')
|
||||
.replace('/', '_')
|
||||
.replaceAll('/', 'x')
|
||||
.replaceAll('+', 'y')
|
||||
.slice(0, 10)
|
||||
const nDigits = (name.match(/\d/g) || []).length
|
||||
if (nDigits < 6) return name
|
||||
|
||||
@@ -5,8 +5,12 @@ import { execFile as execFileCb } from 'node:child_process'
|
||||
import bodyParser from 'body-parser'
|
||||
import express from 'express'
|
||||
import YAML from 'js-yaml'
|
||||
import { isZodErrorLike } from 'zod-validation-error'
|
||||
import { ParamsError, parseReq, z } from '@overleaf/validation-tools'
|
||||
import {
|
||||
InvalidParamsError,
|
||||
InvalidRequestError,
|
||||
parseReq,
|
||||
z,
|
||||
} from '@overleaf/validation-tools'
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
|
||||
const execFile = promisify(execFileCb)
|
||||
@@ -474,12 +478,13 @@ app.delete(
|
||||
)
|
||||
|
||||
app.use((error, req, res, next) => {
|
||||
if (error instanceof ParamsError) {
|
||||
if (error instanceof InvalidParamsError) {
|
||||
res.status(404).json({ error })
|
||||
} else if (isZodErrorLike(error)) {
|
||||
res.status(400).json({ error })
|
||||
} else if (error instanceof InvalidRequestError) {
|
||||
res.status(400).json({ error: error.zodError })
|
||||
} else {
|
||||
next(error)
|
||||
}
|
||||
next(error)
|
||||
})
|
||||
|
||||
purgeDataDir()
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@isomorphic-git/lightning-fs": "^4.6.0",
|
||||
"@overleaf/promise-utils": "*",
|
||||
"@overleaf/validation-tools": "*",
|
||||
"@testing-library/cypress": "10.0.3",
|
||||
"@overleaf/promise-utils": "workspace:*",
|
||||
"@overleaf/validation-tools": "workspace:*",
|
||||
"@testing-library/cypress": "10.1.0",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"adm-zip": "^0.5.12",
|
||||
"body-parser": "1.20.4",
|
||||
"cypress": "13.13.2",
|
||||
"cypress": "15.12.0",
|
||||
"cypress-multi-reporters": "^2.0.5",
|
||||
"express": "4.22.1",
|
||||
"isomorphic-git": "^1.33.1",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user