@bobfrankston/rmfmail 1.2.196 → 1.2.197
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/client/app.bundle.js +1 -1
- package/client/app.bundle.js.map +1 -1
- package/client/help/search-help.js +1 -1
- package/client/help/search-help.ts +1 -1
- package/client/package.json +1 -1
- package/package.json +1 -1
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +43 -1
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +39 -1
- package/packages/mailx-store/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-50260 → node_modules.npmglobalize-stash-11396}/.package-lock.json +0 -0
|
@@ -3946,8 +3946,23 @@ export class MailxDB {
|
|
|
3946
3946
|
const LIKE_SUBJ = ["m.subject"];
|
|
3947
3947
|
const LIKE_ANY = ["m.subject", "m.from_name", "m.from_address"];
|
|
3948
3948
|
|
|
3949
|
+
// `NOT <item>` — exclusion, usable ANYWHERE including as the leading
|
|
3950
|
+
// (or only) token. FTS5's own NOT is strictly binary (`a NOT b`), so
|
|
3951
|
+
// a leading NOT was an FTS5 syntax error that the dangling-operator
|
|
3952
|
+
// trim "fixed" by dropping the NOT — silently searching FOR the term
|
|
3953
|
+
// the user asked to exclude (Bob 2026-07-30). Instead of leaning on
|
|
3954
|
+
// FTS5's operator, every `NOT x` pair is lifted OUT of the MATCH and
|
|
3955
|
+
// applied as a SQL-side exclusion (`m.id NOT IN (SELECT rowid …
|
|
3956
|
+
// MATCH x)`), which also negates qualifier tokens (`NOT from:…`,
|
|
3957
|
+
// `NOT is:read`) uniformly. Uppercase only, same as AND/OR — a
|
|
3958
|
+
// lowercase "not" stays an ordinary search term.
|
|
3959
|
+
const notFrags: string[] = [];
|
|
3960
|
+
let negateNext = false;
|
|
3949
3961
|
for (let pi = 0; pi < parts.length; pi++) {
|
|
3950
3962
|
const part = parts[pi];
|
|
3963
|
+
if (part === "NOT") { negateNext = true; continue; }
|
|
3964
|
+
const wPre = extraWhere.length;
|
|
3965
|
+
const fPre = frags.length;
|
|
3951
3966
|
// A 1-2 char term next to a user-typed OR must be DROPPED, not
|
|
3952
3967
|
// LIKE'd: LIKE clauses AND against the MATCH, so "ab OR sprinkler"
|
|
3953
3968
|
// would intersect down to zero. Over-matching (just "sprinkler")
|
|
@@ -4029,12 +4044,14 @@ export class MailxDB {
|
|
|
4029
4044
|
const v = folderMatch[1].replace(/"/g, "");
|
|
4030
4045
|
extraWhere.push("LOWER(f.name) LIKE ?");
|
|
4031
4046
|
extraParams.push(`%${v.toLowerCase()}%`);
|
|
4032
|
-
} else if (/^(AND|OR
|
|
4047
|
+
} else if (/^(AND|OR)$/.test(part)) {
|
|
4033
4048
|
// FTS5 boolean operators — pass through verbatim (must be uppercase).
|
|
4034
4049
|
// Without this branch, `hoddie AND git` got wildcarded into
|
|
4035
4050
|
// `hoddie* AND* git*`, which FTS5 reads as three required terms
|
|
4036
4051
|
// (one of them being any word starting with "AND") — so a real
|
|
4037
4052
|
// match like "Peter Hoddie" + "github" returned zero hits.
|
|
4053
|
+
// (NOT is handled above as an exclusion prefix, not passed
|
|
4054
|
+
// through — FTS5's binary-only NOT can't stand at the front.)
|
|
4038
4055
|
frags.push({ s: part, op: true });
|
|
4039
4056
|
} else if (isTri) {
|
|
4040
4057
|
// Unqualified, trigram dialect — quoted substring match. A
|
|
@@ -4087,6 +4104,18 @@ export class MailxDB {
|
|
|
4087
4104
|
}
|
|
4088
4105
|
}
|
|
4089
4106
|
}
|
|
4107
|
+
// Divert whatever this token contributed into the exclusion set.
|
|
4108
|
+
// FTS frags move to notFrags (applied as a NOT IN subquery below);
|
|
4109
|
+
// SQL-side qualifier clauses get wrapped in NOT(...) in place. An
|
|
4110
|
+
// AND/OR right after NOT is nonsense input — the op frag stands
|
|
4111
|
+
// and the stray NOT is dropped.
|
|
4112
|
+
if (negateNext) {
|
|
4113
|
+
if (!/^(AND|OR)$/.test(part)) {
|
|
4114
|
+
for (let w = wPre; w < extraWhere.length; w++) extraWhere[w] = `NOT (${extraWhere[w]})`;
|
|
4115
|
+
while (frags.length > fPre) notFrags.push(frags.pop()!.s);
|
|
4116
|
+
}
|
|
4117
|
+
negateNext = false;
|
|
4118
|
+
}
|
|
4090
4119
|
}
|
|
4091
4120
|
|
|
4092
4121
|
// A term diverted to the LIKE fallback can strand a user-typed
|
|
@@ -4096,6 +4125,15 @@ export class MailxDB {
|
|
|
4096
4125
|
while (frags.length && frags[0].op) frags.shift();
|
|
4097
4126
|
while (frags.length && frags[frags.length - 1].op) frags.pop();
|
|
4098
4127
|
|
|
4128
|
+
// NOT'd terms exclude via a subquery on the same FTS index: a message
|
|
4129
|
+
// matching ANY excluded term is out. Works whether or not there are
|
|
4130
|
+
// positive FTS terms — a pure `NOT foo` query takes the qualifier-only
|
|
4131
|
+
// path below and returns everything except matches.
|
|
4132
|
+
if (notFrags.length > 0) {
|
|
4133
|
+
extraWhere.push("m.id NOT IN (SELECT rowid FROM messages_fts WHERE messages_fts MATCH ?)");
|
|
4134
|
+
extraParams.push(notFrags.join(" OR "));
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4099
4137
|
// Join fragments. Two adjacent value fragments need an explicit `AND`
|
|
4100
4138
|
// (implicit-AND after a `(...)` / `{...}:` group is an FTS5 syntax
|
|
4101
4139
|
// error); a user operator fragment glues itself, so no insert around it.
|