proton-mail-bridge-client 2.2.2 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/services/local-index-service.d.ts.map +1 -1
- package/dist/services/local-index-service.js +52 -54
- package/dist/services/local-index-service.js.map +1 -1
- package/dist/services/simple-imap-service.d.ts +1 -0
- package/dist/services/simple-imap-service.d.ts.map +1 -1
- package/dist/services/simple-imap-service.js +20 -6
- package/dist/services/simple-imap-service.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-index-service.d.ts","sourceRoot":"","sources":["../../src/services/local-index-service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,uBAAuB,EACvB,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,cAAc,EAEd,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACd,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"local-index-service.d.ts","sourceRoot":"","sources":["../../src/services/local-index-service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,uBAAuB,EACvB,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,cAAc,EAEd,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACd,MAAM,mBAAmB,CAAC;AAe3B,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAuEzD,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3K,MAAM,EAAE,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG;IACvB,qBAAqB,CAAC,EAAE,IAAI,CAAC;IAC7B,mBAAmB,CAAC,EAAE,IAAI,CAAC;IAC3B,eAAe,CAAC,EAAE,IAAI,CAAC;CACxB,CAuBA;AAgfD,qBAAa,iBAAiB;IAqB1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IArBtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,EAAE,CAAC,CAAoB;IAC/B,OAAO,CAAC,WAAW,CAAS;IAc5B,OAAO,CAAC,aAAa,CAAuC;IAE5D,YACmB,MAAM,EAAE,gBAAgB,EACxB,GAAG,GAAE,MAAe,EAItC;IAEK,cAAc,CAAC,KAAK,EAAE;QAC1B,OAAO,EAAE,UAAU,EAAE,CAAC;QAMtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,MAAM,EAAE,YAAY,EAAE,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,KAAK,CAAC,qBAAqB,CAAC,CAAC;KAC3C,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAY5B;IAEK,SAAS,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAa3C;YAaa,iBAAiB;IAezB,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;QAChD,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,YAAY,EAAE,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,KAAK,CAAC,EAAE,IAAI,CAAC;QACb,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC,CA0ED;IAEK,KAAK,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CA8BzD;IAEK,kBAAkB,CAAC,KAAK,SAAK,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAG9D;IAEK,SAAS,CAAC,KAAK,SAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA4IpD;IAEK,UAAU,CAAC,KAAK,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAC1H,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,aAAa,EAAE,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,KAAK,CAAC,EAAE,IAAI,CAAC;QACb,cAAc,CAAC,EAAE,OAAO,CAAC;KAC1B,CAAC,CA4ED;IAEK,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CA4C3D;IAEK,oBAAoB,CAAC,KAAK,GAAE;QAChC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;QACnC,MAAM,CAAC,EAAE,MAAM,CAAC;KACZ,GAAG,OAAO,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,uBAAuB,EAAE,CAAC;QACnC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,KAAK,CAAC,EAAE,IAAI,CAAC;QACb,cAAc,CAAC,EAAE,OAAO,CAAC;KAC1B,CAAC,CAyHD;IAEK,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAO3E;IAEK,cAAc,CAAC,KAAK,GAAE;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA+FxC;IAEK,qBAAqB,CAAC,KAAK,GAAE;QACjC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;QACnC,MAAM,CAAC,EAAE,MAAM,CAAC;KACZ,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiFxC;IAEK,mBAAmB,CAAC,KAAK,GAAE;QAC/B,QAAQ,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;QACvE,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KACX,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAmGxC;IAEK,cAAc,CAAC,KAAK,EAAE;QAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoFnC;IAEK,iBAAiB,IAAI,OAAO,CAAC;QACjC,EAAE,EAAE,OAAO,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;QACpB,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC,CAsBD;YAEa,QAAQ;IA+DtB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,aAAa;IA4brB,OAAO,CAAC,aAAa;IAwFrB,OAAO,CAAC,qBAAqB;YAkCf,sBAAsB;IAmDpC,OAAO,CAAC,mBAAmB;IA2G3B,OAAO,CAAC,YAAY;IA8CpB,OAAO,CAAC,sBAAsB;IA2C9B,OAAO,CAAC,mBAAmB;IAuC3B,OAAO,CAAC,YAAY;IAgDpB,OAAO,CAAC,2BAA2B;IA6EnC,OAAO,CAAC,+BAA+B;IAyBvC,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,6BAA6B;YA8BvB,YAAY;IAgB1B,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,iBAAiB;IAgCzB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,oBAAoB;IAwC5B,OAAO,CAAC,YAAY;IA+DpB,OAAO,CAAC,wBAAwB;IAgEhC,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,oBAAoB;IAoBtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;IAED,OAAO,CAAC,OAAO;CAOhB"}
|
|
@@ -3,7 +3,7 @@ import { chmodSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { ensureAccountIdentityMatches } from "../utils/account-identity.js";
|
|
6
|
-
import { createEmailId, dedupeEmails, extractDomain, extractMessageIdList, lowerCaseAddress, nextDay, normalizeMailboxLabel, normalizeMessageId, normalizeSubjectForThread, sortEmailsByNewest, } from "../utils/helpers.js";
|
|
6
|
+
import { createEmailId, dedupeEmails, extractDomain, extractMessageIdList, foldSearchText, lowerCaseAddress, nextDay, normalizeMailboxLabel, normalizeMessageId, normalizeSubjectForThread, sortEmailsByNewest, } from "../utils/helpers.js";
|
|
7
7
|
import { logger } from "../utils/logger.js";
|
|
8
8
|
const DB_SCHEMA_VERSION = 3;
|
|
9
9
|
const STALE_THRESHOLD_MINUTES = 60;
|
|
@@ -100,9 +100,9 @@ function matchesIndexedSearch(email, filters) {
|
|
|
100
100
|
return false;
|
|
101
101
|
}
|
|
102
102
|
if (normalizedFilters.label) {
|
|
103
|
-
const labelNeedle = normalizedFilters.label
|
|
104
|
-
const folderMatch = normalizeMailboxLabel(email.folder)
|
|
105
|
-
const labelMatch = email.labels.some((label) => normalizeMailboxLabel(label)
|
|
103
|
+
const labelNeedle = foldSearchText(normalizedFilters.label);
|
|
104
|
+
const folderMatch = foldSearchText(normalizeMailboxLabel(email.folder) ?? "") === labelNeedle;
|
|
105
|
+
const labelMatch = email.labels.some((label) => foldSearchText(normalizeMailboxLabel(label) ?? "") === labelNeedle);
|
|
106
106
|
if (!folderMatch && !labelMatch) {
|
|
107
107
|
return false;
|
|
108
108
|
}
|
|
@@ -131,8 +131,8 @@ function matchesIndexedSearch(email, filters) {
|
|
|
131
131
|
return false;
|
|
132
132
|
}
|
|
133
133
|
if (normalizedFilters.attachmentName) {
|
|
134
|
-
const attachmentNeedle = normalizedFilters.attachmentName
|
|
135
|
-
const match = email.attachments.some((attachment) => (attachment.filename || "").
|
|
134
|
+
const attachmentNeedle = foldSearchText(normalizedFilters.attachmentName);
|
|
135
|
+
const match = email.attachments.some((attachment) => foldSearchText(attachment.filename || "").includes(attachmentNeedle));
|
|
136
136
|
if (!match) {
|
|
137
137
|
return false;
|
|
138
138
|
}
|
|
@@ -152,35 +152,34 @@ function matchesIndexedSearch(email, filters) {
|
|
|
152
152
|
...email.from.map((value) => `${value.name ?? ""} ${value.address ?? ""}`),
|
|
153
153
|
...email.to.map((value) => `${value.name ?? ""} ${value.address ?? ""}`),
|
|
154
154
|
...email.cc.map((value) => `${value.name ?? ""} ${value.address ?? ""}`),
|
|
155
|
-
]
|
|
156
|
-
|
|
157
|
-
.toLowerCase();
|
|
155
|
+
].join("\n");
|
|
156
|
+
const foldedHaystacks = foldSearchText(haystacks);
|
|
158
157
|
// searchFtsIds() ANDs every whitespace-separated query token as its own quoted FTS5 term
|
|
159
158
|
// (so "invoice payment" matches a preview like "payment for the invoice is overdue"), but
|
|
160
159
|
// this post-filter used to require the ENTIRE joined query as one literal substring —
|
|
161
160
|
// rejecting anything FTS already matched whose words were merely out of order or separated
|
|
162
161
|
// by other words. Match FTS's own AND-of-terms semantics instead of a single-phrase substring.
|
|
163
162
|
if (normalizedFilters.query) {
|
|
164
|
-
const terms = normalizedFilters.query
|
|
165
|
-
if (terms.length > 0 && !terms.every((term) =>
|
|
163
|
+
const terms = foldSearchText(normalizedFilters.query).split(/\s+/).filter(Boolean);
|
|
164
|
+
if (terms.length > 0 && !terms.every((term) => foldedHaystacks.includes(term))) {
|
|
166
165
|
return false;
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
168
|
if (normalizedFilters.subject &&
|
|
170
|
-
!email.subject
|
|
169
|
+
!foldSearchText(email.subject).includes(foldSearchText(normalizedFilters.subject))) {
|
|
171
170
|
return false;
|
|
172
171
|
}
|
|
173
172
|
if (normalizedFilters.from) {
|
|
174
|
-
const fromNeedle = normalizedFilters.from
|
|
175
|
-
const match = email.from.some((value) => `${value.name ?? ""} ${value.address ?? ""}
|
|
173
|
+
const fromNeedle = foldSearchText(normalizedFilters.from);
|
|
174
|
+
const match = email.from.some((value) => foldSearchText(`${value.name ?? ""} ${value.address ?? ""}`).includes(fromNeedle));
|
|
176
175
|
if (!match) {
|
|
177
176
|
return false;
|
|
178
177
|
}
|
|
179
178
|
}
|
|
180
179
|
if (normalizedFilters.to) {
|
|
181
|
-
const toNeedle = normalizedFilters.to
|
|
180
|
+
const toNeedle = foldSearchText(normalizedFilters.to);
|
|
182
181
|
const recipients = [...email.to, ...email.cc, ...email.bcc];
|
|
183
|
-
const match = recipients.some((value) => `${value.name ?? ""} ${value.address ?? ""}
|
|
182
|
+
const match = recipients.some((value) => foldSearchText(`${value.name ?? ""} ${value.address ?? ""}`).includes(toNeedle));
|
|
184
183
|
if (!match) {
|
|
185
184
|
return false;
|
|
186
185
|
}
|
|
@@ -763,20 +762,18 @@ export class LocalIndexService {
|
|
|
763
762
|
// get plain ThreadSummary shapes, not the full per-message detail.
|
|
764
763
|
const threads = this.buildThreads(snapshot, true).filter((thread) => {
|
|
765
764
|
if (input.label) {
|
|
766
|
-
const labelNeedle = input.label
|
|
767
|
-
if (!thread.normalizedLabels.some((label) => label
|
|
765
|
+
const labelNeedle = foldSearchText(input.label);
|
|
766
|
+
if (!thread.normalizedLabels.some((label) => foldSearchText(label) === labelNeedle)) {
|
|
768
767
|
return false;
|
|
769
768
|
}
|
|
770
769
|
}
|
|
771
770
|
if (input.query) {
|
|
772
|
-
const queryNeedle = input.query
|
|
773
|
-
const threadLevelHaystack = [
|
|
771
|
+
const queryNeedle = foldSearchText(input.query);
|
|
772
|
+
const threadLevelHaystack = foldSearchText([
|
|
774
773
|
...thread.participants.map((participant) => `${participant.name ?? ""} ${participant.address ?? ""}`),
|
|
775
774
|
...thread.normalizedLabels,
|
|
776
|
-
]
|
|
777
|
-
|
|
778
|
-
.toLowerCase();
|
|
779
|
-
const matchesAnyMessage = thread.messages.some((message) => message.subject.toLowerCase().includes(queryNeedle));
|
|
775
|
+
].join("\n"));
|
|
776
|
+
const matchesAnyMessage = thread.messages.some((message) => foldSearchText(message.subject).includes(queryNeedle));
|
|
780
777
|
if (!matchesAnyMessage && !threadLevelHaystack.includes(queryNeedle)) {
|
|
781
778
|
return false;
|
|
782
779
|
}
|
|
@@ -868,13 +865,13 @@ export class LocalIndexService {
|
|
|
868
865
|
conditions.push(`is_read = 0`);
|
|
869
866
|
}
|
|
870
867
|
if (input.label) {
|
|
871
|
-
const labelNeedle = escapeLike(input.label
|
|
872
|
-
conditions.push(`(
|
|
868
|
+
const labelNeedle = escapeLike(foldSearchText(input.label));
|
|
869
|
+
conditions.push(`(FOLD(folder) LIKE ? ESCAPE '\\' OR FOLD(labels_json) LIKE ? ESCAPE '\\')`);
|
|
873
870
|
params.push(`%${labelNeedle}%`, `%${labelNeedle}%`);
|
|
874
871
|
}
|
|
875
872
|
if (input.query) {
|
|
876
|
-
const needle = `%${escapeLike(input.query
|
|
877
|
-
conditions.push(`(
|
|
873
|
+
const needle = `%${escapeLike(foldSearchText(input.query))}%`;
|
|
874
|
+
conditions.push(`(FOLD(subject) LIKE ? ESCAPE '\\' OR FOLD(preview) LIKE ? ESCAPE '\\' OR FOLD(from_json) LIKE ? ESCAPE '\\' OR FOLD(labels_json) LIKE ? ESCAPE '\\')`);
|
|
878
875
|
params.push(needle, needle, needle, needle);
|
|
879
876
|
}
|
|
880
877
|
const sql = `SELECT * FROM messages${conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : ""}`;
|
|
@@ -916,21 +913,19 @@ export class LocalIndexService {
|
|
|
916
913
|
return false;
|
|
917
914
|
}
|
|
918
915
|
if (input.label) {
|
|
919
|
-
const labelNeedle = input.label
|
|
920
|
-
if (!thread.normalizedLabels.some((label) => label
|
|
916
|
+
const labelNeedle = foldSearchText(input.label);
|
|
917
|
+
if (!thread.normalizedLabels.some((label) => foldSearchText(label) === labelNeedle)) {
|
|
921
918
|
return false;
|
|
922
919
|
}
|
|
923
920
|
}
|
|
924
921
|
if (input.query) {
|
|
925
|
-
const haystack = [
|
|
922
|
+
const haystack = foldSearchText([
|
|
926
923
|
thread.subject,
|
|
927
924
|
thread.latestPreview || "",
|
|
928
925
|
...thread.latestFrom.map((value) => `${value.name ?? ""} ${value.address ?? ""}`),
|
|
929
926
|
...thread.normalizedLabels,
|
|
930
|
-
]
|
|
931
|
-
|
|
932
|
-
.toLowerCase();
|
|
933
|
-
if (!haystack.includes(input.query.toLowerCase())) {
|
|
927
|
+
].join("\n"));
|
|
928
|
+
if (!haystack.includes(foldSearchText(input.query))) {
|
|
934
929
|
return false;
|
|
935
930
|
}
|
|
936
931
|
}
|
|
@@ -1151,7 +1146,7 @@ export class LocalIndexService {
|
|
|
1151
1146
|
// membership so a matching document thread outside DEFAULT_SNAPSHOT_LIMIT is never
|
|
1152
1147
|
// silently missed. A message with no attachments can never contribute a document
|
|
1153
1148
|
// match regardless of its text, so has_attachments = 1 narrows this further.
|
|
1154
|
-
const keywordConditions = keywords.map(() => `(
|
|
1149
|
+
const keywordConditions = keywords.map(() => `(FOLD(attachments_json) LIKE ? ESCAPE '\\' OR FOLD(subject) LIKE ? ESCAPE '\\' OR FOLD(preview) LIKE ? ESCAPE '\\' OR FOLD(attachment_text) LIKE ? ESCAPE '\\')`);
|
|
1155
1150
|
const keywordParams = [];
|
|
1156
1151
|
for (const keyword of keywords) {
|
|
1157
1152
|
const needle = `%${escapeLike(keyword)}%`;
|
|
@@ -1225,19 +1220,19 @@ export class LocalIndexService {
|
|
|
1225
1220
|
const db = await this.ensureDb();
|
|
1226
1221
|
const { ownerEmail, updatedAt, folders, indexedFolders } = this.loadFoldersAndMetadata(db);
|
|
1227
1222
|
const syncCheckpoints = this.loadCheckpointsSync(db);
|
|
1228
|
-
const personNeedle = input.person
|
|
1223
|
+
const personNeedle = input.person ? foldSearchText(input.person) : undefined;
|
|
1229
1224
|
const domainNeedle = input.domain?.toLowerCase();
|
|
1230
1225
|
const limit = input.limit ?? 10;
|
|
1231
1226
|
const conditions = [];
|
|
1232
1227
|
const params = [];
|
|
1233
1228
|
if (personNeedle) {
|
|
1234
1229
|
const needle = `%${escapeLike(personNeedle)}%`;
|
|
1235
|
-
conditions.push(`(
|
|
1230
|
+
conditions.push(`(FOLD(from_json) LIKE ? ESCAPE '\\' OR FOLD(to_json) LIKE ? ESCAPE '\\' OR FOLD(cc_json) LIKE ? ESCAPE '\\')`);
|
|
1236
1231
|
params.push(needle, needle, needle);
|
|
1237
1232
|
}
|
|
1238
1233
|
if (domainNeedle) {
|
|
1239
1234
|
const needle = `%@${escapeLike(domainNeedle)}%`;
|
|
1240
|
-
conditions.push(`(
|
|
1235
|
+
conditions.push(`(FOLD(from_json) LIKE ? ESCAPE '\\' OR FOLD(to_json) LIKE ? ESCAPE '\\' OR FOLD(cc_json) LIKE ? ESCAPE '\\')`);
|
|
1241
1236
|
params.push(needle, needle, needle);
|
|
1242
1237
|
}
|
|
1243
1238
|
let messages;
|
|
@@ -1263,7 +1258,7 @@ export class LocalIndexService {
|
|
|
1263
1258
|
const snapshot = { ownerEmail, updatedAt, folders, indexedFolders, syncCheckpoints, messages };
|
|
1264
1259
|
const threads = this.buildThreads(snapshot, true)
|
|
1265
1260
|
.filter((thread) => thread.participants.some((participant) => {
|
|
1266
|
-
const participantText = `${participant.name ?? ""} ${participant.address ?? ""}
|
|
1261
|
+
const participantText = foldSearchText(`${participant.name ?? ""} ${participant.address ?? ""}`);
|
|
1267
1262
|
const participantDomain = extractDomain(participant.address || "");
|
|
1268
1263
|
if (personNeedle && participantText.includes(personNeedle)) {
|
|
1269
1264
|
return true;
|
|
@@ -1327,6 +1322,9 @@ export class LocalIndexService {
|
|
|
1327
1322
|
const db = this.db ?? new Database(this.dbPath);
|
|
1328
1323
|
if (isFirstOpen) {
|
|
1329
1324
|
this.chmodDbFiles();
|
|
1325
|
+
// SQLite's LOWER() only folds ASCII and LIKE does not ignore accents, so "łódź" could not find
|
|
1326
|
+
// "Łódź" and "pelcova" could not find "Pelcová". Compare folded text on both sides instead.
|
|
1327
|
+
db.function("FOLD", { deterministic: true }, (value) => (value == null ? null : foldSearchText(String(value))));
|
|
1330
1328
|
}
|
|
1331
1329
|
// auto_vacuum only takes effect on a brand-new/empty database (page_count 0) —
|
|
1332
1330
|
// setting the pragma alone does NOT retroactively enable incremental vacuuming
|
|
@@ -1963,12 +1961,12 @@ export class LocalIndexService {
|
|
|
1963
1961
|
params.push(filters.hasAttachment ? 1 : 0);
|
|
1964
1962
|
}
|
|
1965
1963
|
if (filters.subject) {
|
|
1966
|
-
conditions.push(`
|
|
1967
|
-
params.push(`%${escapeLike(filters.subject
|
|
1964
|
+
conditions.push(`FOLD(subject) LIKE ? ESCAPE '\\'`);
|
|
1965
|
+
params.push(`%${escapeLike(foldSearchText(filters.subject))}%`);
|
|
1968
1966
|
}
|
|
1969
1967
|
if (filters.senderDomain) {
|
|
1970
|
-
conditions.push(`
|
|
1971
|
-
params.push(`%${escapeLike(filters.senderDomain
|
|
1968
|
+
conditions.push(`FOLD(from_json) LIKE ? ESCAPE '\\'`);
|
|
1969
|
+
params.push(`%${escapeLike(foldSearchText(filters.senderDomain))}%`);
|
|
1972
1970
|
}
|
|
1973
1971
|
// from/to/messageId were accepted by search_indexed_emails but never
|
|
1974
1972
|
// narrowed the SQL candidate scan — only applied afterward by
|
|
@@ -1978,12 +1976,12 @@ export class LocalIndexService {
|
|
|
1978
1976
|
// genuine match older than the window instead of finding it. Mirror the
|
|
1979
1977
|
// existing senderDomain LIKE pattern (a safe superset pre-filter).
|
|
1980
1978
|
if (filters.from) {
|
|
1981
|
-
conditions.push(`
|
|
1982
|
-
params.push(`%${escapeLike(filters.from
|
|
1979
|
+
conditions.push(`FOLD(from_json) LIKE ? ESCAPE '\\'`);
|
|
1980
|
+
params.push(`%${escapeLike(foldSearchText(filters.from))}%`);
|
|
1983
1981
|
}
|
|
1984
1982
|
if (filters.to) {
|
|
1985
|
-
conditions.push(`(
|
|
1986
|
-
const toNeedle = `%${escapeLike(filters.to
|
|
1983
|
+
conditions.push(`(FOLD(to_json) LIKE ? ESCAPE '\\' OR FOLD(cc_json) LIKE ? ESCAPE '\\' OR FOLD(bcc_json) LIKE ? ESCAPE '\\')`);
|
|
1984
|
+
const toNeedle = `%${escapeLike(foldSearchText(filters.to))}%`;
|
|
1987
1985
|
params.push(toNeedle, toNeedle, toNeedle);
|
|
1988
1986
|
}
|
|
1989
1987
|
if (filters.messageId) {
|
|
@@ -2148,8 +2146,8 @@ export class LocalIndexService {
|
|
|
2148
2146
|
messageParams.push(options.folder);
|
|
2149
2147
|
}
|
|
2150
2148
|
if (options.label) {
|
|
2151
|
-
const labelNeedle = escapeLike(options.label
|
|
2152
|
-
messageConditions.push(`(
|
|
2149
|
+
const labelNeedle = escapeLike(foldSearchText(options.label));
|
|
2150
|
+
messageConditions.push(`(FOLD(folder) LIKE ? ESCAPE '\\' OR FOLD(labels_json) LIKE ? ESCAPE '\\')`);
|
|
2153
2151
|
messageParams.push(`%${labelNeedle}%`, `%${labelNeedle}%`);
|
|
2154
2152
|
}
|
|
2155
2153
|
if (typeof options.isRead === "boolean") {
|
|
@@ -2190,13 +2188,13 @@ export class LocalIndexService {
|
|
|
2190
2188
|
params.push(input.folder);
|
|
2191
2189
|
}
|
|
2192
2190
|
if (input.label) {
|
|
2193
|
-
const labelNeedle = escapeLike(input.label
|
|
2194
|
-
conditions.push(`(
|
|
2191
|
+
const labelNeedle = escapeLike(foldSearchText(input.label));
|
|
2192
|
+
conditions.push(`(FOLD(folder) LIKE ? ESCAPE '\\' OR FOLD(labels_json) LIKE ? ESCAPE '\\')`);
|
|
2195
2193
|
params.push(`%${labelNeedle}%`, `%${labelNeedle}%`);
|
|
2196
2194
|
}
|
|
2197
2195
|
if (input.query) {
|
|
2198
|
-
const needle = `%${escapeLike(input.query
|
|
2199
|
-
conditions.push(`(
|
|
2196
|
+
const needle = `%${escapeLike(foldSearchText(input.query))}%`;
|
|
2197
|
+
conditions.push(`(FOLD(subject) LIKE ? ESCAPE '\\' OR FOLD(from_json) LIKE ? ESCAPE '\\' OR FOLD(to_json) LIKE ? ESCAPE '\\' OR FOLD(cc_json) LIKE ? ESCAPE '\\' OR FOLD(bcc_json) LIKE ? ESCAPE '\\' OR FOLD(labels_json) LIKE ? ESCAPE '\\')`);
|
|
2200
2198
|
params.push(needle, needle, needle, needle, needle, needle);
|
|
2201
2199
|
}
|
|
2202
2200
|
const sql = `SELECT * FROM messages${conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : ""}`;
|