From 0d23965fac4d5f2aa33c9373299aa06d3baba104 Mon Sep 17 00:00:00 2001 From: Adam <13007539+MrgSub@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:01:19 -0700 Subject: [PATCH 01/26] getqoutauser (#1713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Refactor Google Mail Manager quota user handling ## Description Added a new `getQuotaUser()` method to the GoogleMailManager class that combines the user's email with the current environment (`NODE_ENV`). Updated all Google API calls to use this method instead of directly using the email as the quota user. This change helps prevent quota limit issues by creating environment-specific quota user identifiers, which is particularly useful when running multiple environments (development, staging, production) with the same Google account. --- ## Type of Change - [x] ⚡ Performance improvement ## Areas Affected - [x] Email Integration (Gmail, IMAP, etc.) ## Testing Done - [x] Manual testing performed ## Checklist - [x] I have performed a self-review of my code - [x] My changes generate no new warnings ## Additional Notes This change helps prevent hitting Google API quota limits by differentiating quota usage across different environments. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ ## Summary by CodeRabbit * **Refactor** * Improved handling of user identification for Gmail API requests to enhance consistency across different environments. --- apps/server/src/lib/driver/google.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/server/src/lib/driver/google.ts b/apps/server/src/lib/driver/google.ts index c65f7adb04..b44542ef6f 100644 --- a/apps/server/src/lib/driver/google.ts +++ b/apps/server/src/lib/driver/google.ts @@ -214,6 +214,10 @@ export class GoogleMailManager implements MailManager { { email: this.config.auth?.email }, ); } + + private getQuotaUser() { + return this.config.auth?.email ? `${this.config.auth.email}-${env.NODE_ENV}` : undefined; + } public list(params: { folder: string; query?: string; @@ -235,7 +239,7 @@ export class GoogleMailManager implements MailManager { labelIds: folder === 'inbox' ? labelIds : [], maxResults, pageToken: pageToken ? pageToken : undefined, - quotaUser: this.config.auth?.email, + quotaUser: this.getQuotaUser(), }); const threads = res.data.threads ?? []; @@ -263,7 +267,7 @@ export class GoogleMailManager implements MailManager { userId: 'me', id, format: 'full', - quotaUser: this.config.auth?.email, + quotaUser: this.getQuotaUser(), }); if (!res.data.messages) @@ -787,7 +791,7 @@ export class GoogleMailManager implements MailManager { userId: 'me', id: threadId, format: 'metadata', // Fetch only metadata, - quotaUser: this.config.auth?.email, + quotaUser: this.getQuotaUser(), }); // Process res.data.messages to extract id and labelIds return { From 4af9d179a99dd9519f550b82bf5a849e79747c07 Mon Sep 17 00:00:00 2001 From: Fynn Bauer <80108547+Kaseax@users.noreply.github.com> Date: Fri, 11 Jul 2025 22:11:56 +0200 Subject: [PATCH 02/26] fix(docs): update locale file path (#1675) --- .github/CONTRIBUTING.md | 2 +- .github/TRANSLATION.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ebc0a5587f..0c84913252 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -195,7 +195,7 @@ When implementing new features, follow these guidelines: 1. **Add English Source Strings** - - Place all user-facing text in `apps/mail/locales/en.json` + - Place all user-facing text in `apps/mail/messages/en.json` - Organize strings according to the existing structure - Use descriptive, hierarchical keys that identify the feature and context - Example: `"pages.settings.connections.disconnectSuccess": "Account disconnected successfully"` diff --git a/.github/TRANSLATION.md b/.github/TRANSLATION.md index a436e371cf..63ecabad5d 100644 --- a/.github/TRANSLATION.md +++ b/.github/TRANSLATION.md @@ -31,7 +31,7 @@ Here's an example of our i18n.json configuration: }, "buckets": { "json": { - "include": ["apps/mail/locales/[locale].json"] + "include": ["apps/mail/messages/[locale].json"] } } } From a85622d2e47895f23e7ed34de59211b51602a64d Mon Sep 17 00:00:00 2001 From: abhix4 Date: Sat, 12 Jul 2025 01:46:09 +0530 Subject: [PATCH 03/26] fix: hide the remove option for single connection (#1655) --- apps/mail/app/(routes)/settings/connections/page.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/mail/app/(routes)/settings/connections/page.tsx b/apps/mail/app/(routes)/settings/connections/page.tsx index 189f749f27..d8e192716d 100644 --- a/apps/mail/app/(routes)/settings/connections/page.tsx +++ b/apps/mail/app/(routes)/settings/connections/page.tsx @@ -164,7 +164,8 @@ export default function ConnectionsPage() { From 0590ff71f80accd9e146e08854c3142b14339eb4 Mon Sep 17 00:00:00 2001 From: abhix4 Date: Sat, 12 Jul 2025 01:54:23 +0530 Subject: [PATCH 04/26] fix: replace # with {count} in en.json (#1650) --- apps/mail/messages/en.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/mail/messages/en.json b/apps/mail/messages/en.json index 57068b5dfe..8ebc695908 100644 --- a/apps/mail/messages/en.json +++ b/apps/mail/messages/en.json @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Add notes", - "countPlural=one": "# note", - "countPlural=other": "# notes" + "countPlural=one": "{count} note", + "countPlural=other": "{count} notes" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "replies", - "countPlural=one": "# reply", - "countPlural=other": "# replies" + "countPlural=one": "{count} reply", + "countPlural=other": "{count} replies" } } ], From b519d09191982492fd586186692c1a0959827d0f Mon Sep 17 00:00:00 2001 From: Samrath <102617759+samrathreddy@users.noreply.github.com> Date: Sat, 12 Jul 2025 01:56:21 +0530 Subject: [PATCH 05/26] fix: (Mobile) Bottom blank area in mail-list (#1651) Co-authored-by: Adam <13007539+MrgSub@users.noreply.github.com> --- apps/mail/components/mail/mail-list.tsx | 4 ++-- apps/mail/components/mail/mail.tsx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/mail/components/mail/mail-list.tsx b/apps/mail/components/mail/mail-list.tsx index 314bb540dd..e19a41c719 100644 --- a/apps/mail/components/mail/mail-list.tsx +++ b/apps/mail/components/mail/mail-list.tsx @@ -957,13 +957,13 @@ export const MailList = memo( )} -
+
{isFetching ? (
) : ( -
+
)}
diff --git a/apps/mail/components/mail/mail.tsx b/apps/mail/components/mail/mail.tsx index 0f275cedb9..6420847c71 100644 --- a/apps/mail/components/mail/mail.tsx +++ b/apps/mail/components/mail/mail.tsx @@ -591,8 +591,7 @@ export function MailLayout() { isFetching ? 'opacity-100' : 'opacity-0', )} /> - {/* removed 88px because there is no category picker add it back in height dvh calc when category picker is added */} -
+
From 3ceab96b1e370af870566b0826e15eb130fb9028 Mon Sep 17 00:00:00 2001 From: Samrath <102617759+samrathreddy@users.noreply.github.com> Date: Sat, 12 Jul 2025 01:56:47 +0530 Subject: [PATCH 06/26] =?UTF-8?q?fix:=20(Mobile=20UI)=20Inconsistent=20fil?= =?UTF-8?q?ter=20placeholder=20text=20visibility=20on=20m=E2=80=A6=20(#164?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/mail/components/mail/mail.tsx | 2 +- apps/mail/messages/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/mail/components/mail/mail.tsx b/apps/mail/components/mail/mail.tsx index 6420847c71..082ec09144 100644 --- a/apps/mail/components/mail/mail.tsx +++ b/apps/mail/components/mail/mail.tsx @@ -545,7 +545,7 @@ export function MailLayout() { {activeFilters.length > 0 ? `${activeFilters.length} filter${activeFilters.length > 1 ? 's' : ''}` - : 'Search...'} + : 'Search & Filter'} diff --git a/apps/mail/messages/en.json b/apps/mail/messages/en.json index 8ebc695908..c02bd0bd35 100644 --- a/apps/mail/messages/en.json +++ b/apps/mail/messages/en.json @@ -94,7 +94,7 @@ "commandPalette": "Command Palette" }, "quickResults": "Quick Results", - "searchPlaceholder": "Search...", + "searchPlaceholder": "Search & Filter", "noSearchResults": "No search results", "actions": "Actions", "addFilters": "Add filters", From a12810b24cd6a015f10815ec7e0592b6ced8782d Mon Sep 17 00:00:00 2001 From: Adam <13007539+MrgSub@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:29:06 -0700 Subject: [PATCH 07/26] disallow last conn del (#1716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Prevent users from deleting their last connection ## Description Added a validation check before deleting a connection to ensure users always maintain at least one connection. The system now throws an error if a user attempts to delete their last remaining connection. --- ## Type of Change - [x] 🐛 Bug fix (non-breaking change which fixes an issue) ## Areas Affected - [x] Data Storage/Management - [x] API Endpoints ## Testing Done - [x] Manual testing performed ## Security Considerations - [x] No sensitive data is exposed - [x] Input validation is implemented ## Checklist - [x] I have performed a self-review of my code - [x] My changes generate no new warnings ## Additional Notes This change prevents users from getting into a state where they have no connections, which could lead to unexpected behavior in the application. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ --- apps/server/src/main.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index e13727dfe2..160e781ad9 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -201,6 +201,10 @@ class ZeroDB extends DurableObject { } async deleteConnection(connectionId: string, userId: string) { + const connections = await this.findManyConnections(userId); + if (connections.length <= 1) { + throw new Error('Cannot delete the last connection. At least one connection is required.'); + } return await this.db .delete(connection) .where(and(eq(connection.id, connectionId), eq(connection.userId, userId))); From 5b2d76a5d4a7bf9f2dd99fb06c84800789035173 Mon Sep 17 00:00:00 2001 From: Adam <13007539+MrgSub@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:57:19 -0700 Subject: [PATCH 08/26] prefetch email html (#1719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Email Content Prefetching and Processing Optimization ## Description This PR improves email loading performance by implementing prefetching and caching of processed email HTML content. It splits the email processing logic into two parts: 1. Server-side preprocessing that handles sanitization and structure 2. Client-side processing that applies theme-specific styling and image loading preferences The changes also add prefetching of the latest message in a thread to improve perceived loading speed when users open emails. --- ## Type of Change - [x] ⚡ Performance improvement - [x] 🎨 UI/UX improvement ## Areas Affected - [x] Email Integration (Gmail, IMAP, etc.) - [x] User Interface/Experience - [x] Performance Optimization ## Testing Done - [x] Manual testing performed - [x] Cross-browser testing (if UI changes) ## Checklist - [x] I have performed a self-review of my code - [x] My changes generate no new warnings - [x] My code follows the project's style guidelines ## Additional Notes The email processing logic has been refactored to: 1. Separate heavy sanitization work (which can be done once) from theme/preference application 2. Cache processed content with a 30-minute stale time and 1-hour garbage collection time 3. Respect user preferences for external image loading and trusted senders 4. Apply theme-specific styling based on user settings or system preference This should significantly improve the perceived performance when opening emails, especially for threads with complex HTML content. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ ## Summary by CodeRabbit * **New Features** * Improved email thread view by displaying the latest non-draft message. * Enhanced email content processing to apply user settings and theme preferences, including external image loading and dark/light mode support. * **Bug Fixes** * More accurate handling of external images and theme styling in emails based on user preferences. * **Chores** * Updated internal configuration for local development environment. --- apps/mail/hooks/use-threads.ts | 63 +++++++++++++++++-- apps/server/src/lib/email-processor.ts | 85 +++++++++++++++++--------- apps/server/wrangler.jsonc | 4 -- 3 files changed, 114 insertions(+), 38 deletions(-) diff --git a/apps/mail/hooks/use-threads.ts b/apps/mail/hooks/use-threads.ts index 2968d1d7fe..8126749b9f 100644 --- a/apps/mail/hooks/use-threads.ts +++ b/apps/mail/hooks/use-threads.ts @@ -1,12 +1,14 @@ import { backgroundQueueAtom, isThreadInBackgroundQueueAtom } from '@/store/backgroundQueue'; +import { useInfiniteQuery, useQuery, useMutation } from '@tanstack/react-query'; import type { IGetThreadResponse } from '../../server/src/lib/driver/types'; -import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { useSearchValue } from '@/hooks/use-search-value'; import { useTRPC } from '@/providers/query-provider'; import useSearchLabels from './use-labels-search'; import { useSession } from '@/lib/auth-client'; import { useAtom, useAtomValue } from 'jotai'; +import { useSettings } from './use-settings'; import { useParams } from 'react-router'; +import { useTheme } from 'next-themes'; import { useQueryState } from 'nuqs'; import { useMemo } from 'react'; @@ -65,6 +67,8 @@ export const useThread = (threadId: string | null) => { const [_threadId] = useQueryState('threadId'); const id = threadId ? threadId : _threadId; const trpc = useTRPC(); + const { data: settings } = useSettings(); + const { theme: systemTheme } = useTheme(); const threadQuery = useQuery( trpc.mail.get.queryOptions( @@ -77,12 +81,14 @@ export const useThread = (threadId: string | null) => { }, ), ); - const { latestDraft, isGroupThread, finalData } = useMemo(() => { + + const { latestDraft, isGroupThread, finalData, latestMessage } = useMemo(() => { if (!threadQuery.data) { return { latestDraft: undefined, isGroupThread: false, finalData: undefined, + latestMessage: undefined, }; } @@ -101,13 +107,62 @@ export const useThread = (threadId: string | null) => { })() : false; + const nonDraftMessages = threadQuery.data.messages.filter((e) => !e.isDraft); + const latestMessage = nonDraftMessages[nonDraftMessages.length - 1]; + const finalData: IGetThreadResponse = { ...threadQuery.data, - messages: threadQuery.data.messages.filter((e) => !e.isDraft), + messages: nonDraftMessages, }; - return { latestDraft, isGroupThread, finalData }; + return { latestDraft, isGroupThread, finalData, latestMessage }; }, [threadQuery.data]); + const { mutateAsync: processEmailContent } = useMutation( + trpc.mail.processEmailContent.mutationOptions(), + ); + + // Extract image loading condition to avoid duplication + const shouldLoadImages = useMemo(() => { + if (!settings?.settings || !latestMessage?.sender?.email) return false; + + return settings.settings.externalImages || + settings.settings.trustedSenders?.includes(latestMessage.sender.email) || + false; + }, [settings?.settings, latestMessage?.sender?.email]); + + // Prefetch query - intentionally unused, just for caching + useQuery({ + queryKey: [ + 'email-content', + latestMessage?.id, + shouldLoadImages, + systemTheme, + ], + queryFn: async () => { + if (!latestMessage?.decodedBody || !settings?.settings) return null; + + const userTheme = + settings.settings.colorTheme === 'system' ? systemTheme : settings.settings.colorTheme; + const theme = userTheme === 'dark' ? 'dark' : 'light'; + + const result = await processEmailContent({ + html: latestMessage.decodedBody, + shouldLoadImages, + theme, + }); + + return { + html: result.processedHtml, + hasBlockedImages: result.hasBlockedImages, + }; + }, + enabled: !!latestMessage?.decodedBody && !!settings?.settings, + staleTime: 30 * 60 * 1000, // 30 minutes + gcTime: 60 * 60 * 1000, // 1 hour + refetchOnWindowFocus: false, + refetchOnMount: false, + }); + return { ...threadQuery, data: finalData, isGroupThread, latestDraft }; }; diff --git a/apps/server/src/lib/email-processor.ts b/apps/server/src/lib/email-processor.ts index 5340e7967b..b163acb6cc 100644 --- a/apps/server/src/lib/email-processor.ts +++ b/apps/server/src/lib/email-processor.ts @@ -7,15 +7,8 @@ interface ProcessEmailOptions { theme: 'light' | 'dark'; } -export function processEmailHtml({ html, shouldLoadImages, theme }: ProcessEmailOptions): { - processedHtml: string; - hasBlockedImages: boolean; -} { - let hasBlockedImages = false; - - const validatedTheme: 'light' | 'dark' = theme === 'dark' ? 'dark' : 'light'; - const isDarkTheme = validatedTheme === 'dark'; - +// Server-side: Heavy lifting, preference-independent processing +export function preprocessEmailHtml(html: string): string { const sanitizeConfig: sanitizeHtml.IOptions = { allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'title', 'details', 'summary']), @@ -38,21 +31,13 @@ export function processEmailHtml({ html, shouldLoadImages, theme }: ProcessEmail img: ['src', 'alt', 'width', 'height', 'class', 'style'], }, - allowedSchemes: shouldLoadImages - ? ['http', 'https', 'mailto', 'tel', 'data', 'cid', 'blob'] - : ['http', 'https', 'mailto', 'tel', 'cid'], + // Allow only safe schemes - no blob for security + allowedSchemes: ['http', 'https', 'mailto', 'tel', 'data', 'cid'], allowedSchemesByTag: { - img: shouldLoadImages ? ['http', 'https', 'data', 'cid', 'blob'] : ['cid'], + img: ['http', 'https', 'data', 'cid'], }, transformTags: { - img: (tagName, attribs) => { - if (!shouldLoadImages && attribs.src && !attribs.src.startsWith('cid:')) { - hasBlockedImages = true; - return { tagName: 'span', attribs: { style: 'display:none;' } }; - } - return { tagName, attribs }; - }, a: (tagName, attribs) => { return { tagName, @@ -67,9 +52,9 @@ export function processEmailHtml({ html, shouldLoadImages, theme }: ProcessEmail }; const sanitized = sanitizeHtml(html, sanitizeConfig); - const $ = cheerio.load(sanitized); + // Collapse quoted text (structure only, no theme colors) const collapseQuoted = (selector: string) => { $(selector).each((_, el) => { const $el = $(el); @@ -78,7 +63,7 @@ export function processEmailHtml({ html, shouldLoadImages, theme }: ProcessEmail const innerHtml = $el.html(); if (typeof innerHtml !== 'string') return; const detailsHtml = `
- + Show quoted text ${innerHtml} @@ -91,11 +76,12 @@ export function processEmailHtml({ html, shouldLoadImages, theme }: ProcessEmail collapseQuoted('blockquote'); collapseQuoted('.gmail_quote'); + // Remove unwanted elements $('title').remove(); - $('img[width="1"][height="1"]').remove(); $('img[width="0"][height="0"]').remove(); + // Remove preheader content $('.preheader, .preheaderText, [class*="preheader"]').each((_, el) => { const $el = $(el); const style = $el.attr('style') || ''; @@ -116,13 +102,41 @@ export function processEmailHtml({ html, shouldLoadImages, theme }: ProcessEmail } }); - const minimalStyles = ` + return $.html(); +} + +// Client-side: Light styling + image preferences +export function applyEmailPreferences( + preprocessedHtml: string, + theme: 'light' | 'dark', + shouldLoadImages: boolean +): { processedHtml: string; hasBlockedImages: boolean } { + let hasBlockedImages = false; + const isDarkTheme = theme === 'dark'; + + const $ = cheerio.load(preprocessedHtml); + + // Handle image blocking if needed + if (!shouldLoadImages) { + $('img').each((_, el) => { + const $img = $(el); + const src = $img.attr('src'); + + // Allow CID images (inline attachments) + if (src && !src.startsWith('cid:')) { + hasBlockedImages = true; + $img.replaceWith(``); + } + }); + } + + const html = $.html(); + + // Apply theme-specific styles + const themeStyles = ` `; - const fullHtml = $.html(); - - const finalHtml = `${minimalStyles}${fullHtml}`; + const finalHtml = `${themeStyles}${html}`; return { processedHtml: finalHtml, hasBlockedImages, }; } + +// Original function for backward compatibility +export function processEmailHtml({ html, shouldLoadImages, theme }: ProcessEmailOptions): { + processedHtml: string; + hasBlockedImages: boolean; +} { + const preprocessed = preprocessEmailHtml(html); + return applyEmailPreferences(preprocessed, theme, shouldLoadImages); +} diff --git a/apps/server/wrangler.jsonc b/apps/server/wrangler.jsonc index 16ea126b5f..64ddbefdcd 100644 --- a/apps/server/wrangler.jsonc +++ b/apps/server/wrangler.jsonc @@ -78,10 +78,6 @@ "tag": "v3", "new_classes": ["ZeroDB"], }, - { - "tag": "v4", - "new_sqlite_classes": ["ZeroAgent"], - }, ], "observability": { From 414a44d3b4a4a769033baa4e9da822a0c06476fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 12:58:32 -0700 Subject: [PATCH 09/26] feat: update translations via @LingoDotDev (#1717) Hey team, [**Lingo.dev**](https://lingo.dev) here with fresh translations! ### In this update - Added missing translations - Performed brand voice, context and glossary checks - Enhanced translations using Lingo.dev Localization Engine ### Next Steps - [ ] Review the changes - [ ] Merge when ready --- ## Summary by cubic Updated translations for all supported languages in the mail app to improve clarity and consistency, including better pluralization and more accurate search placeholders. --- apps/mail/messages/ar.json | 10 +++++----- apps/mail/messages/ca.json | 10 +++++----- apps/mail/messages/cs.json | 10 +++++----- apps/mail/messages/de.json | 10 +++++----- apps/mail/messages/es.json | 10 +++++----- apps/mail/messages/fa.json | 10 +++++----- apps/mail/messages/fr.json | 10 +++++----- apps/mail/messages/hi.json | 10 +++++----- apps/mail/messages/hu.json | 10 +++++----- apps/mail/messages/ja.json | 10 +++++----- apps/mail/messages/ko.json | 10 +++++----- apps/mail/messages/lv.json | 10 +++++----- apps/mail/messages/nl.json | 10 +++++----- apps/mail/messages/pl.json | 10 +++++----- apps/mail/messages/pt.json | 10 +++++----- apps/mail/messages/ru.json | 10 +++++----- apps/mail/messages/tr.json | 10 +++++----- apps/mail/messages/vi.json | 10 +++++----- apps/mail/messages/zh_CN.json | 10 +++++----- apps/mail/messages/zh_TW.json | 10 +++++----- i18n.lock | 10 +++++----- 21 files changed, 105 insertions(+), 105 deletions(-) diff --git a/apps/mail/messages/ar.json b/apps/mail/messages/ar.json index c2bab474e8..83e7897b97 100644 --- a/apps/mail/messages/ar.json +++ b/apps/mail/messages/ar.json @@ -94,7 +94,7 @@ "commandPalette": "لوحة الأوامر" }, "quickResults": "نتائج سريعة", - "searchPlaceholder": "بحث...", + "searchPlaceholder": "بحث وتصفية", "noSearchResults": "لا توجد نتائج بحث", "actions": "الإجراءات", "addFilters": "إضافة عوامل تصفية", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "إضافة ملاحظات", - "countPlural=one": "ملاحظة واحدة", - "countPlural=other": "# ملاحظة" + "countPlural=one": "ملاحظة واحدة ({count})", + "countPlural=other": "{count} ملاحظات" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "ردود", - "countPlural=one": "رد واحد", - "countPlural=other": "# رد" + "countPlural=one": "رد واحد ({count})", + "countPlural=other": "{count} ردود" } } ], diff --git a/apps/mail/messages/ca.json b/apps/mail/messages/ca.json index ef9715fe61..11933f8494 100644 --- a/apps/mail/messages/ca.json +++ b/apps/mail/messages/ca.json @@ -94,7 +94,7 @@ "commandPalette": "Paleta de comandes" }, "quickResults": "Resultats ràpids", - "searchPlaceholder": "Cerca...", + "searchPlaceholder": "Cerca i filtra", "noSearchResults": "Sense resultats de cerca", "actions": "Accions", "addFilters": "Afegir filtres", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Afegeix notes", - "countPlural=one": "# nota", - "countPlural=other": "# notes" + "countPlural=one": "{count} nota", + "countPlural=other": "{count} notes" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "respostes", - "countPlural=one": "# resposta", - "countPlural=other": "# respostes" + "countPlural=one": "{count} resposta", + "countPlural=other": "{count} respostes" } } ], diff --git a/apps/mail/messages/cs.json b/apps/mail/messages/cs.json index 4afc8eb50c..224c18004a 100644 --- a/apps/mail/messages/cs.json +++ b/apps/mail/messages/cs.json @@ -94,7 +94,7 @@ "commandPalette": "Paleta příkazů" }, "quickResults": "Rychlé výsledky", - "searchPlaceholder": "Hledat...", + "searchPlaceholder": "Hledat a filtrovat", "noSearchResults": "Žádné výsledky vyhledávání", "actions": "Akce", "addFilters": "Přidat filtry", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Přidat poznámku", - "countPlural=one": "# poznámka", - "countPlural=other": "# poznámek" + "countPlural=one": "{count} poznámka", + "countPlural=other": "{count} poznámek" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "odpovědí", - "countPlural=one": "#odpověď", - "countPlural=other": "#odpovědí" + "countPlural=one": "{count} odpověď", + "countPlural=other": "{count} odpovědí" } } ], diff --git a/apps/mail/messages/de.json b/apps/mail/messages/de.json index 9a5925911c..5aca8ad476 100644 --- a/apps/mail/messages/de.json +++ b/apps/mail/messages/de.json @@ -94,7 +94,7 @@ "commandPalette": "Befehlspalette" }, "quickResults": "Schnelle Ergebnisse", - "searchPlaceholder": "Suchen...", + "searchPlaceholder": "Suchen & Filtern", "noSearchResults": "Keine Suchergebnisse", "actions": "Aktionen", "addFilters": "Filter hinzufügen", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Füge Notizen hinzu", - "countPlural=one": "# Notiz", - "countPlural=other": "# Notizen" + "countPlural=one": "{count} Notiz", + "countPlural=other": "{count} Notizen" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Antworten", - "countPlural=one": "# Antwort", - "countPlural=other": "# Antworten" + "countPlural=one": "{count} Antwort", + "countPlural=other": "{count} Antworten" } } ], diff --git a/apps/mail/messages/es.json b/apps/mail/messages/es.json index d2b3ec5b71..404137b7ca 100644 --- a/apps/mail/messages/es.json +++ b/apps/mail/messages/es.json @@ -94,7 +94,7 @@ "commandPalette": "Paleta de comandos" }, "quickResults": "Resultados rápidos", - "searchPlaceholder": "Buscar...", + "searchPlaceholder": "Buscar y filtrar", "noSearchResults": "Sin resultados de búsqueda", "actions": "Acciones", "addFilters": "Añadir filtros", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Añade notas", - "countPlural=one": "# nota", - "countPlural=other": "# notas" + "countPlural=one": "{count} nota", + "countPlural=other": "{count} notas" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "respuestas", - "countPlural=one": "# respuesta", - "countPlural=other": "# respuestas" + "countPlural=one": "{count} respuesta", + "countPlural=other": "{count} respuestas" } } ], diff --git a/apps/mail/messages/fa.json b/apps/mail/messages/fa.json index 407cddf703..bd3127f383 100644 --- a/apps/mail/messages/fa.json +++ b/apps/mail/messages/fa.json @@ -94,7 +94,7 @@ "commandPalette": "پالت فرمان" }, "quickResults": "نتایج سریع", - "searchPlaceholder": "جستجو...", + "searchPlaceholder": "جستجو و فیلتر", "noSearchResults": "نتیجه‌ای یافت نشد", "actions": "اقدامات", "addFilters": "افزودن فیلترها", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "افزودن یادداشت‌ها", - "countPlural=one": "# یادداشت", - "countPlural=other": "# یادداشت" + "countPlural=one": "{count} یادداشت", + "countPlural=other": "{count} یادداشت" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "پاسخ‌ها", - "countPlural=one": "# پاسخ", - "countPlural=other": "# پاسخ" + "countPlural=one": "{count} پاسخ", + "countPlural=other": "{count} پاسخ" } } ], diff --git a/apps/mail/messages/fr.json b/apps/mail/messages/fr.json index dca5c74f1f..0b6e597928 100644 --- a/apps/mail/messages/fr.json +++ b/apps/mail/messages/fr.json @@ -94,7 +94,7 @@ "commandPalette": "Palette de commandes" }, "quickResults": "Résultats rapides", - "searchPlaceholder": "Rechercher...", + "searchPlaceholder": "Rechercher et filtrer", "noSearchResults": "Aucun résultat de recherche", "actions": "Actions", "addFilters": "Ajouter des filtres", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Ajoutez des notes", - "countPlural=one": "# note", - "countPlural=other": "# notes" + "countPlural=one": "{count} note", + "countPlural=other": "{count} notes" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "réponses", - "countPlural=one": "# réponse", - "countPlural=other": "# réponses" + "countPlural=one": "{count} réponse", + "countPlural=other": "{count} réponses" } } ], diff --git a/apps/mail/messages/hi.json b/apps/mail/messages/hi.json index 1913534b7f..e645b72e6e 100644 --- a/apps/mail/messages/hi.json +++ b/apps/mail/messages/hi.json @@ -94,7 +94,7 @@ "commandPalette": "कमांड पैलेट" }, "quickResults": "त्वरित परिणाम", - "searchPlaceholder": "खोजें...", + "searchPlaceholder": "खोजें और फ़िल्टर करें", "noSearchResults": "कोई खोज परिणाम नहीं", "actions": "एक्शन", "addFilters": "फ़िल्टर जोड़ें", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "नोट्स जोड़ें", - "countPlural=one": "# नोट", - "countPlural=other": "# नोट्स" + "countPlural=one": "{count} नोट", + "countPlural=other": "{count} नोट्स" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "जवाब", - "countPlural=one": "# जवाब", - "countPlural=other": "# जवाब" + "countPlural=one": "{count} जवाब", + "countPlural=other": "{count} जवाब" } } ], diff --git a/apps/mail/messages/hu.json b/apps/mail/messages/hu.json index 7ff2a472a1..4cc89b4afa 100644 --- a/apps/mail/messages/hu.json +++ b/apps/mail/messages/hu.json @@ -94,7 +94,7 @@ "commandPalette": "Parancspaletta" }, "quickResults": "Gyors eredmények", - "searchPlaceholder": "Keresés...", + "searchPlaceholder": "Keresés és szűrés", "noSearchResults": "Nincs találat", "actions": "Műveletek", "addFilters": "Szűrők hozzáadása", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Jegyzetek hozzáadása", - "countPlural=one": "# jegyzet", - "countPlural=other": "# jegyzet" + "countPlural=one": "{count} jegyzet", + "countPlural=other": "{count} jegyzet" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "válasz", - "countPlural=one": "# válasz", - "countPlural=other": "# válasz" + "countPlural=one": "{count} válasz", + "countPlural=other": "{count} válasz" } } ], diff --git a/apps/mail/messages/ja.json b/apps/mail/messages/ja.json index 7e13a817e3..04ff3da7ee 100644 --- a/apps/mail/messages/ja.json +++ b/apps/mail/messages/ja.json @@ -94,7 +94,7 @@ "commandPalette": "コマンド パレット" }, "quickResults": "クイック結果", - "searchPlaceholder": "検索...", + "searchPlaceholder": "検索とフィルター", "noSearchResults": "検索結果がありません", "actions": "アクション", "addFilters": "フィルターを追加", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Add notes", - "countPlural=one": "# note", - "countPlural=other": "# notes" + "countPlural=one": "{count}件のノート", + "countPlural=other": "{count}件のノート" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "返信", - "countPlural=one": "返信 #個", - "countPlural=other": "返信 #個" + "countPlural=one": "{count}件の返信", + "countPlural=other": "{count}件の返信" } } ], diff --git a/apps/mail/messages/ko.json b/apps/mail/messages/ko.json index 1e21b21426..a4c099a656 100644 --- a/apps/mail/messages/ko.json +++ b/apps/mail/messages/ko.json @@ -94,7 +94,7 @@ "commandPalette": "명령 팔레트" }, "quickResults": "빠른 결과", - "searchPlaceholder": "검색...", + "searchPlaceholder": "검색 및 필터", "noSearchResults": "검색 결과 없음", "actions": "동작", "addFilters": "필터 추가", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "메모 추가", - "countPlural=one": "메모 #개", - "countPlural=other": "메모 #개" + "countPlural=one": "노트 {count}개", + "countPlural=other": "노트 {count}개" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "답장", - "countPlural=one": "답장 #개", - "countPlural=other": "답장 #개" + "countPlural=one": "답장 {count}개", + "countPlural=other": "답장 {count}개" } } ], diff --git a/apps/mail/messages/lv.json b/apps/mail/messages/lv.json index 86e938b2d4..96c2212828 100644 --- a/apps/mail/messages/lv.json +++ b/apps/mail/messages/lv.json @@ -94,7 +94,7 @@ "commandPalette": "Komandu palete" }, "quickResults": "Ātrie rezultāti", - "searchPlaceholder": "Meklēt...", + "searchPlaceholder": "Meklēt un filtrēt", "noSearchResults": "Nav meklēšanas rezultātu", "actions": "Darbības", "addFilters": "Pievienot filtrus", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Pievienot piezīmes", - "countPlural=one": "# piezīme", - "countPlural=other": "# piezīmes" + "countPlural=one": "{count} piezīme", + "countPlural=other": "{count} piezīmes" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "atbildes", - "countPlural=one": "# atbilde", - "countPlural=other": "# atbildes" + "countPlural=one": "{count} atbilde", + "countPlural=other": "{count} atbildes" } } ], diff --git a/apps/mail/messages/nl.json b/apps/mail/messages/nl.json index 5d259db1ef..ab7fa37577 100644 --- a/apps/mail/messages/nl.json +++ b/apps/mail/messages/nl.json @@ -94,7 +94,7 @@ "commandPalette": "Opdrachtenpalet" }, "quickResults": "Snelle resultaten", - "searchPlaceholder": "Zoeken...", + "searchPlaceholder": "Zoeken & filteren", "noSearchResults": "Geen zoekresultaten", "actions": "Acties", "addFilters": "Filters toevoegen", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Notities toevoegen", - "countPlural=one": "# notitie", - "countPlural=other": "# notities" + "countPlural=one": "{count} notitie", + "countPlural=other": "{count} notities" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "reacties", - "countPlural=one": "# reactie", - "countPlural=other": "# reacties" + "countPlural=one": "{count} antwoord", + "countPlural=other": "{count} antwoorden" } } ], diff --git a/apps/mail/messages/pl.json b/apps/mail/messages/pl.json index fa847d5c20..0ff91456ad 100644 --- a/apps/mail/messages/pl.json +++ b/apps/mail/messages/pl.json @@ -94,7 +94,7 @@ "commandPalette": "Paleta poleceń" }, "quickResults": "Szybkie wyniki", - "searchPlaceholder": "Szukaj...", + "searchPlaceholder": "Szukaj i filtruj", "noSearchResults": "Brak wyników wyszukiwania", "actions": "Akcje", "addFilters": "Dodaj filtry", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Dodaj notatki", - "countPlural=one": "# notatka", - "countPlural=other": "# notatek" + "countPlural=one": "{count} notatka", + "countPlural=other": "{count} notatki" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "odpowiedzi", - "countPlural=one": "# odpowiedź", - "countPlural=other": "# odpowiedzi" + "countPlural=one": "{count} odpowiedź", + "countPlural=other": "{count} odpowiedzi" } } ], diff --git a/apps/mail/messages/pt.json b/apps/mail/messages/pt.json index f76c3c0fd7..0a6ca7ba73 100644 --- a/apps/mail/messages/pt.json +++ b/apps/mail/messages/pt.json @@ -94,7 +94,7 @@ "commandPalette": "Paleta de Comandos" }, "quickResults": "Resultados rápidos", - "searchPlaceholder": "Pesquisar...", + "searchPlaceholder": "Pesquisar e filtrar", "noSearchResults": "Sem resultados de pesquisa", "actions": "Ações", "addFilters": "Adicionar filtros", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Adicione notas", - "countPlural=one": "# nota", - "countPlural=other": "# notas" + "countPlural=one": "{count} nota", + "countPlural=other": "{count} notas" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "arquivos", - "countPlural=one": "arquivo", - "countPlural=other": "arquivos" + "countPlural=one": "{count} resposta", + "countPlural=other": "{count} respostas" } } ], diff --git a/apps/mail/messages/ru.json b/apps/mail/messages/ru.json index a5f1448552..44699845bd 100644 --- a/apps/mail/messages/ru.json +++ b/apps/mail/messages/ru.json @@ -94,7 +94,7 @@ "commandPalette": "Командная палитра" }, "quickResults": "Быстрые результаты", - "searchPlaceholder": "Поиск...", + "searchPlaceholder": "Поиск и фильтр", "noSearchResults": "Результаты не найдены", "actions": "Действия", "addFilters": "Добавить фильтры", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Добавьте заметки", - "countPlural=one": "# заметка", - "countPlural=other": "# заметки" + "countPlural=one": "{count} заметка", + "countPlural=other": "{count} заметок" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "ответы", - "countPlural=one": "# ответ", - "countPlural=other": "# ответы" + "countPlural=one": "{count} ответ", + "countPlural=other": "{count} ответов" } } ], diff --git a/apps/mail/messages/tr.json b/apps/mail/messages/tr.json index bb8071b7fd..c8bdfc7512 100644 --- a/apps/mail/messages/tr.json +++ b/apps/mail/messages/tr.json @@ -94,7 +94,7 @@ "commandPalette": "Komut Paleti" }, "quickResults": "Hızlı Sonuçlar", - "searchPlaceholder": "Ara...", + "searchPlaceholder": "Ara & Filtrele", "noSearchResults": "Arama sonucu bulunamadı", "actions": "İşlemler", "addFilters": "Filtre ekle", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Not ekle", - "countPlural=one": "# not", - "countPlural=other": "# notlar" + "countPlural=one": "{count} not", + "countPlural=other": "{count} not" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "cevaplar", - "countPlural=one": "# cevap", - "countPlural=other": "# cevaplar" + "countPlural=one": "{count} yanıt", + "countPlural=other": "{count} yanıt" } } ], diff --git a/apps/mail/messages/vi.json b/apps/mail/messages/vi.json index 22b8870784..7e910d2bb6 100644 --- a/apps/mail/messages/vi.json +++ b/apps/mail/messages/vi.json @@ -94,7 +94,7 @@ "commandPalette": "Bảng lệnh" }, "quickResults": "Kết quả nhanh", - "searchPlaceholder": "Tìm kiếm...", + "searchPlaceholder": "Tìm kiếm & Lọc", "noSearchResults": "Không có kết quả tìm kiếm", "actions": "Hành động", "addFilters": "Thêm bộ lọc", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "Thêm ghi chú", - "countPlural=one": "# ghi chú", - "countPlural=other": "# ghi chú" + "countPlural=one": "{count} ghi chú", + "countPlural=other": "{count} ghi chú" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "phản hồi", - "countPlural=one": "# phản hồi", - "countPlural=other": "# phản hồi" + "countPlural=one": "{count} phản hồi", + "countPlural=other": "{count} phản hồi" } } ], diff --git a/apps/mail/messages/zh_CN.json b/apps/mail/messages/zh_CN.json index a41a91115a..4beb0e2edd 100644 --- a/apps/mail/messages/zh_CN.json +++ b/apps/mail/messages/zh_CN.json @@ -94,7 +94,7 @@ "commandPalette": "命令面板" }, "quickResults": "快速结果", - "searchPlaceholder": "搜索...", + "searchPlaceholder": "搜索和筛选", "noSearchResults": "没有搜索结果", "actions": "操作", "addFilters": "添加筛选器", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "添加笔记", - "countPlural=one": "# 条笔记", - "countPlural=other": "# 条笔记" + "countPlural=one": "{count} 条笔记", + "countPlural=other": "{count} 条笔记" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "回复", - "countPlural=one": "# 条回复", - "countPlural=other": "# 条回复" + "countPlural=one": "{count} 条回复", + "countPlural=other": "{count} 条回复" } } ], diff --git a/apps/mail/messages/zh_TW.json b/apps/mail/messages/zh_TW.json index f1fa4d5f22..f838f10512 100644 --- a/apps/mail/messages/zh_TW.json +++ b/apps/mail/messages/zh_TW.json @@ -94,7 +94,7 @@ "commandPalette": "指令面板" }, "quickResults": "快速結果", - "searchPlaceholder": "搜尋...", + "searchPlaceholder": "搜尋與篩選", "noSearchResults": "沒有搜尋結果", "actions": "操作", "addFilters": "新增篩選條件", @@ -253,8 +253,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "添加筆記", - "countPlural=one": "# 則筆記", - "countPlural=other": "# 則筆記" + "countPlural=one": "{count} 則筆記", + "countPlural=other": "{count} 則筆記" } } ], @@ -316,8 +316,8 @@ "selectors": ["countPlural"], "match": { "countPlural=0": "回覆", - "countPlural=one": "# 則回覆", - "countPlural=other": "# 則回覆" + "countPlural=one": "{count} 則回覆", + "countPlural=other": "{count} 則回覆" } } ], diff --git a/i18n.lock b/i18n.lock index 8b777ebbcf..be711335f6 100644 --- a/i18n.lock +++ b/i18n.lock @@ -597,7 +597,7 @@ checksums: common/commandPalette/commands/helpWithShortcuts: f8b89d2e465e3a93ff9c5397a114a094 common/commandPalette/commands/commandPalette: 27374fec801ecad6a2a5c58d98c7f531 common/commandPalette/quickResults: 7edcae7952feb7fbb89ddb6c774fddcd - common/commandPalette/searchPlaceholder: fe877a75eac472fc5b188c135c78a558 + common/commandPalette/searchPlaceholder: 1c65d0255e0c94df9099f961513304bd common/commandPalette/noSearchResults: 622e132be46733cb640c78d62c8041ba common/commandPalette/actions: c46571856723b03262fd33f511116298 common/commandPalette/addFilters: 54a0a9e1e7e5f5082186da23ca60c647 @@ -730,8 +730,8 @@ checksums: common/notes/noteCount/0/declarations/1: b65b792281f6c606eb95b21bd2da44ac common/notes/noteCount/0/selectors/0: 4206517b68ca230406fe826568921689 common/notes/noteCount/0/match/countPlural%3D0: 2e162d880384fd0e3438311f8f8931b3 - common/notes/noteCount/0/match/countPlural%3Done: a513f458fdfe98ef846cc11186f24558 - common/notes/noteCount/0/match/countPlural%3Dother: a781cd44d959c189b285d940506ac68e + common/notes/noteCount/0/match/countPlural%3Done: 9af13ee2743c5ef2edf3f1d857386c59 + common/notes/noteCount/0/match/countPlural%3Dother: 531b2cc34700266aeb5799cde779a169 common/notes/notePinned: a94078d0939ef0b72fff4e0fb2227dd9 common/notes/noteUnpinned: 4ca3023fe22a27ec118baa24c1a45ccc common/notes/colorChanged: fc4d8c2c42673be1b4af09be42f4784d @@ -778,8 +778,8 @@ checksums: common/mail/replies/0/declarations/1: b65b792281f6c606eb95b21bd2da44ac common/mail/replies/0/selectors/0: 4206517b68ca230406fe826568921689 common/mail/replies/0/match/countPlural%3D0: a787a6c9cb22ad1ea7be33c56ceb70ba - common/mail/replies/0/match/countPlural%3Done: f502c63daacea0c9429cd03e91f87d2b - common/mail/replies/0/match/countPlural%3Dother: 6bd4863d8e8882188009a083d2791fbe + common/mail/replies/0/match/countPlural%3Done: cc9aa09dc65fe2d95a50d75b4e6c2105 + common/mail/replies/0/match/countPlural%3Dother: 99c4b93b407b0a56335b9641d770e8cf common/mail/deselectAll: d06b551a6d60cdc4dfc51c7683644bad common/mail/selectedEmails: 8070ccaf317cb4216c364b2cb1223fd7 common/mail/noEmailsToSelect: a797578836d0bf18e69a2537429b119a From 3bb42e5357cf01a5d0170750c8a60a1fbe28c3cf Mon Sep 17 00:00:00 2001 From: Adam <13007539+MrgSub@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:34:03 -0700 Subject: [PATCH 10/26] better ai (#1724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Refactored the AI chat component and email assistant to improve performance, reliability, and user experience. This PR introduces a new `inboxRag` tool for natural language email search, replaces the previous `listThreads` implementation, and enhances the prompt design for more efficient inbox management. --- ## Type of Change - ✨ New feature (non-breaking change which adds functionality) - ⚡ Performance improvement ## Areas Affected - [x] Email Integration (Gmail, IMAP, etc.) - [x] User Interface/Experience - [x] API Endpoints ## Testing Done - [x] Manual testing performed ## Checklist - [x] I have performed a self-review of my code - [x] My changes generate no new warnings ## Additional Notes Key improvements: - Added `ThreadPreview` component to replace the previous thread rendering approach - Refactored `ToolResponse` to use specialized subcomponents for different tool types - Updated AI prompts with better instructions for multi-step and parallel operations - Switched backend models to Claude 3.5 for improved reasoning capabilities - Fixed key prop issues in attachment rendering to prevent React warnings - Increased agent max steps from 5 to 10 for more complex operations - Added better markdown styling support for lists and other elements ## Summary by CodeRabbit * **New Features** * Introduced advanced AI-powered inbox search and organization with new tools and richer assistant guidance. * Added support for Anthropic AI models for chat and search tasks. * Enabled new streaming tool for natural language inbox search. * **Improvements** * Enhanced AI assistant prompt with detailed instructions, expanded use cases, and improved communication style. * Modularized and streamlined AI chat component for clearer tool response rendering and better markdown styling. * Increased chat step limit in the AI sidebar for longer conversations. * Improved uniqueness of attachment keys to prevent display issues. * **Bug Fixes** * Corrected parameter naming for thread and draft listing to ensure accurate pagination. * **Chores** * Updated dependencies and environment variables for improved configuration and model support. * Cleaned up unused code and imports for better maintainability. --- apps/mail/components/create/ai-chat.tsx | 218 +++--- apps/mail/components/mail/mail-display.tsx | 7 +- .../components/mail/select-all-checkbox.tsx | 2 +- apps/mail/components/ui/ai-sidebar.tsx | 13 +- apps/mail/lib/prompts.ts | 663 ++++++++++++------ apps/mail/types/tools.ts | 1 + apps/server/package.json | 1 + apps/server/src/lib/prompts.ts | 212 ++++-- apps/server/src/routes/agent/orchestrator.ts | 26 +- apps/server/src/routes/agent/tools.ts | 161 +++-- apps/server/src/routes/ai.ts | 109 ++- apps/server/src/routes/chat.ts | 131 +++- apps/server/src/trpc/routes/drafts.ts | 8 +- apps/server/src/trpc/routes/mail.ts | 20 +- apps/server/src/types.ts | 2 +- apps/server/wrangler.jsonc | 3 +- pnpm-lock.yaml | 15 + 17 files changed, 1020 insertions(+), 572 deletions(-) diff --git a/apps/mail/components/create/ai-chat.tsx b/apps/mail/components/create/ai-chat.tsx index e9c4051040..ea0fe02b03 100644 --- a/apps/mail/components/create/ai-chat.tsx +++ b/apps/mail/components/create/ai-chat.tsx @@ -1,4 +1,3 @@ -import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'; import { useAIFullScreen, useAISidebar } from '../ui/ai-sidebar'; import { VoiceProvider } from '@/providers/voice-provider'; @@ -14,25 +13,26 @@ import { VoiceButton } from '../voice-button'; import { EditorContent } from '@tiptap/react'; import { CurvedArrow } from '../icons/icons'; import { Tools } from '../../types/tools'; -import { InfoIcon } from 'lucide-react'; import { Button } from '../ui/button'; import { format } from 'date-fns-tz'; import { useQueryState } from 'nuqs'; -const renderThread = (thread: { id: string; title: string; snippet: string }) => { +const ThreadPreview = ({ threadId }: { threadId: string }) => { const [, setThreadId] = useQueryState('threadId'); - const { data: getThread } = useThread(thread.id); + const { data: getThread } = useThread(threadId); const [, setIsFullScreen] = useQueryState('isFullScreen'); const handleClick = () => { - setThreadId(thread.id); + setThreadId(threadId); setIsFullScreen(null); }; - return getThread?.latest ? ( + if (!getThread?.latest) return null; + + return (
@@ -65,15 +65,7 @@ const renderThread = (thread: { id: string; title: string; snippet: string }) =>
- ) : null; -}; - -const RenderThreads = ({ - threads, -}: { - threads: { id: string; title: string; snippet: string }[]; -}) => { - return
{threads.map(renderThread)}
; + ); }; const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => void }) => { @@ -148,82 +140,55 @@ export interface AIChatProps { onModelChange?: (model: string) => void; } -declare global { - var DEBUG: boolean; -} - -const ToolResponse = ({ toolName, result, args }: { toolName: string; result: any; args: any }) => { - const renderContent = () => { - switch (toolName) { - case Tools.ListThreads: - case Tools.AskZeroMailbox: - return result?.threads ? : null; - - case Tools.GetThread: - return result?.thread ? ( -
-
- - - {result.thread.sender?.name?.[0]?.toUpperCase()} - -
-

{result.thread.sender?.name}

-

{result.thread.subject}

-
-
-
- {result.thread.body} -
-
- ) : null; - - case Tools.GetUserLabels: - return result?.labels ? ( -
- {result.labels.map((label: any) => ( - - ))} -
- ) : null; - - case Tools.ComposeEmail: - return result?.newBody ? ( -
-
- {result.newBody} -
-
- ) : null; - - default: - return null; - } - }; +// Subcomponents for ToolResponse +const GetThreadToolResponse = ({ result, args }: { result: any; args: any }) => { + // Extract threadId from result or args + let threadId: string | null = null; + if (typeof result === 'string') { + const match = result.match(//); + if (match?.[1]) threadId = match[1]; + } + if (!threadId && args?.id && typeof args.id === 'string') threadId = args.id; + if (!threadId) return null; + return ; +}; - const content = renderContent(); - if (!content) return null; +const GetUserLabelsToolResponse = ({ result }: { result: any }) => { + if (!result?.labels) return null; + return ( +
+ {result.labels.map((label: any) => ( + + ))} +
+ ); +}; +const ComposeEmailToolResponse = ({ result }: { result: any }) => { + if (!result?.newBody) return null; return ( -
- {globalThis.DEBUG ? ( - - - - - -
-

Tool Arguments:

-
{JSON.stringify(args, null, 2)}
-
-
-
- ) : null} - {content} +
+
+ {result.newBody} +
); }; +// Main ToolResponse switcher +const ToolResponse = ({ toolName, result, args }: { toolName: string; result: any; args: any }) => { + switch (toolName) { + case Tools.GetThread: + return ; + case Tools.GetUserLabels: + return ; + case Tools.ComposeEmail: + return ; + default: + return null; + } +}; + export function AIChat({ messages, setInput, @@ -320,28 +285,22 @@ export function AIChat({ messages.map((message, index) => { const textParts = message.parts.filter((part) => part.type === 'text'); const toolParts = message.parts.filter((part) => part.type === 'tool-invocation'); - const streamingTools = new Set([Tools.WebSearch]); - const doesIncludeStreamingTool = toolParts.some( - (part) => - streamingTools.has(part.toolInvocation?.toolName as Tools) && - part.toolInvocation?.result, - ); + return ( -
- {toolParts.map((part) => - part.toolInvocation && - part.toolInvocation.result && - !streamingTools.has(part.toolInvocation.toolName as Tools) ? ( - - ) : null, +
+ {toolParts.map( + (part, index) => + part.toolInvocation?.result && ( + + ), )} - {!doesIncludeStreamingTool && textParts.length > 0 && ( -

0 && ( +

@@ -382,7 +340,7 @@ export function AIChat({ ), )} -

+
)}
); @@ -390,12 +348,10 @@ export function AIChat({ )} {(status === 'submitted' || status === 'streaming') && ( -
-
- - zero is thinking... - -
+
+ + zero is thinking... +
)} {(status === 'error' || !!error) && ( diff --git a/apps/mail/components/mail/mail-display.tsx b/apps/mail/components/mail/mail-display.tsx index 25fd702890..49a35203a8 100644 --- a/apps/mail/components/mail/mail-display.tsx +++ b/apps/mail/components/mail/mail-display.tsx @@ -297,7 +297,7 @@ const ThreadAttachments = ({ attachments }: { attachments: Attachment[] }) => {
{attachments.map((attachment) => (
)} -
+
); } From 6b2c04ad1b470ac62e89b42086d6474f5c696eeb Mon Sep 17 00:00:00 2001 From: amrit Date: Mon, 14 Jul 2025 22:05:47 +0530 Subject: [PATCH 13/26] fix: alignment of inputs in settings/general (#1725) --- .../app/(routes)/settings/general/page.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/mail/app/(routes)/settings/general/page.tsx b/apps/mail/app/(routes)/settings/general/page.tsx index b9bf5a2962..ff39f653f1 100644 --- a/apps/mail/app/(routes)/settings/general/page.tsx +++ b/apps/mail/app/(routes)/settings/general/page.tsx @@ -61,7 +61,7 @@ const TimezoneSelect = memo( variant="outline" role="combobox" aria-expanded={open} - className="md:w-46 flex !h-9 w-full items-center justify-start rounded-md hover:bg-transparent" + className="flex !h-9 w-full items-center justify-start rounded-md hover:bg-transparent" > {field.value} @@ -196,11 +196,11 @@ export default function GeneralPage() { control={form.control} name="language" render={({ field }) => ( - - {m['pages.settings.general.language']()} + + {m['pages.settings.general.language']()} - + Date: Mon, 14 Jul 2025 22:10:34 +0530 Subject: [PATCH 14/26] fix/ai-chat-example-query-buttons-and-hover (#1440) --- apps/mail/components/create/ai-chat.tsx | 41 +++++++++++++------------ apps/mail/components/ui/ai-sidebar.tsx | 2 +- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/mail/components/create/ai-chat.tsx b/apps/mail/components/create/ai-chat.tsx index ea0fe02b03..e0c7a4e352 100644 --- a/apps/mail/components/create/ai-chat.tsx +++ b/apps/mail/components/create/ai-chat.tsx @@ -69,6 +69,7 @@ const ThreadPreview = ({ threadId }: { threadId: string }) => { }; const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => void }) => { + const firstRowQueries = [ 'Find invoice from Stripe', 'Show unpaid invoices', @@ -78,7 +79,7 @@ const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => voi const secondRowQueries = ['Find all work meetings', 'What projects do i have coming up']; return ( -
+
{/* First row */}
@@ -86,14 +87,12 @@ const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => voi ))}
- {/* Second row */}
@@ -108,6 +107,10 @@ const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => voi ))}
+ {/* Left mask */} +
+ {/* Right mask */} +
); }; @@ -198,7 +201,6 @@ export function AIChat({ }: AIChatProps): React.ReactElement { const messagesEndRef = useRef(null); const messagesContainerRef = useRef(null); - const inputRef = useRef(null); const { chatMessages } = useBilling(); const { isFullScreen } = useAIFullScreen(); const [, setPricingDialog] = useQueryState('pricingDialog'); @@ -240,6 +242,12 @@ export function AIChat({ }, 100); }; + const handleQueryClick = (query: string) => { + editor.commands.setContent(query); + setInput(query); + editor.commands.focus(); + }; + useEffect(() => { if (aiSidebarOpen === 'true') { editor.commands.focus(); @@ -252,13 +260,13 @@ export function AIChat({
{chatMessages && !chatMessages.enabled ? (
setPricingDialog('true')} - className="absolute inset-0 flex flex-col items-center justify-center" - > - - Upgrade to Zero Pro for unlimited AI chat - - + onClick={() => setPricingDialog('true')} + className="absolute inset-0 flex flex-col items-center justify-center" + > + + Upgrade to Zero Pro for unlimited AI chat + +
) : !messages.length ? (
@@ -274,12 +282,7 @@ export function AIChat({

{/* Example Thread */} - { - setInput(query); - inputRef.current?.focus(); - }} - /> +
) : ( messages.map((message, index) => { @@ -399,7 +402,7 @@ export function AIChat({
- {/*
+ {/*