@bobfrankston/rmfmail 1.1.101 → 1.1.103

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.
@@ -2693,7 +2693,7 @@ export class MailxDB {
2693
2693
  * Multi-name-per-email is supported: the same email can carry distinct
2694
2694
  * (source, name) rows — Bob's wife and Bob Smith both at bob@example.com
2695
2695
  * surface as two rows, each typing-completable by their own name. */
2696
- searchContacts(query: string, limit = 10): { name: string; email: string; source: string; useCount: number }[] {
2696
+ searchContacts(query: string, limit = 10): { name: string; email: string; source: string; sources: string[]; useCount: number }[] {
2697
2697
  query = (query || "").trim();
2698
2698
  if (!query) return [];
2699
2699
  // Split into whitespace-separated tokens. Each token must appear in
@@ -2753,21 +2753,33 @@ export class MailxDB {
2753
2753
  rows.sort((a, b) => score(b) - score(a));
2754
2754
  // Dedup by lowercased email — same address often appears as both
2755
2755
  // source='google' (synced from Google Contacts) and source='discovered'
2756
- // (auto-collected from sent mail). The user only wants to see the
2757
- // best entry; we keep the higher-ranked source (which the sort above
2758
- // has already put first), and silently drop the duplicate. Without
2759
- // this, the autocomplete dropdown showed two identical Kevin Healy
2760
- // rows just labeled GOOGLE and DISCOVERED.
2761
- const seenEmails = new Set<string>();
2762
- rows = rows.filter(r => {
2756
+ // (auto-collected from sent mail). Keep the higher-ranked row (already
2757
+ // sorted first) and FOLD subsequent rows' sources into a `sources` array
2758
+ // on the survivor so the dropdown can display "google, discovered"
2759
+ // instead of showing two duplicate lines.
2760
+ const merged = new Map<string, any>();
2761
+ for (const r of rows) {
2763
2762
  const k = (r.email || "").toLowerCase();
2764
- if (!k) return true;
2765
- if (seenEmails.has(k)) return false;
2766
- seenEmails.add(k);
2767
- return true;
2768
- });
2769
- rows = rows.slice(0, limit);
2770
- return rows.map(r => ({ name: r.name, email: r.email, source: r.source, useCount: r.use_count }));
2763
+ if (!k) { merged.set(`__noemail_${merged.size}`, { ...r, _sources: new Set([r.source]) }); continue; }
2764
+ const existing = merged.get(k);
2765
+ if (!existing) {
2766
+ merged.set(k, { ...r, _sources: new Set([r.source]) });
2767
+ } else {
2768
+ existing._sources.add(r.source);
2769
+ // If this row has a non-empty name and the kept one didn't,
2770
+ // promote the name — Google rows often have proper names while
2771
+ // discovered rows are bare-email.
2772
+ if (!existing.name && r.name) existing.name = r.name;
2773
+ }
2774
+ }
2775
+ const out = Array.from(merged.values()).slice(0, limit);
2776
+ return out.map(r => ({
2777
+ name: r.name,
2778
+ email: r.email,
2779
+ source: r.source,
2780
+ sources: Array.from(r._sources as Set<string>).filter(Boolean),
2781
+ useCount: r.use_count,
2782
+ }));
2771
2783
  }
2772
2784
 
2773
2785
  /** List all contacts (address-book view) with pagination + optional filter. */
@@ -2904,6 +2916,13 @@ export class MailxDB {
2904
2916
  const v = folderMatch[1].replace(/"/g, "");
2905
2917
  extraWhere.push("LOWER(f.name) LIKE ?");
2906
2918
  extraParams.push(`%${v.toLowerCase()}%`);
2919
+ } else if (/^(AND|OR|NOT)$/.test(part)) {
2920
+ // FTS5 boolean operators — pass through verbatim (must be uppercase).
2921
+ // Without this branch, `hoddie AND git` got wildcarded into
2922
+ // `hoddie* AND* git*`, which FTS5 reads as three required terms
2923
+ // (one of them being any word starting with "AND") — so a real
2924
+ // match like "Peter Hoddie" + "github" returned zero hits.
2925
+ ftsQuery += `${part} `;
2907
2926
  } else {
2908
2927
  // Unqualified — search everything.
2909
2928
  let term = part.replace(/^\/|\/$/g, "");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",