@agent-native/core 0.102.2 → 0.103.1
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/docs/content/local-file-mode.mdx +99 -489
- package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/corpus/core/docs/content/template-content.mdx +26 -42
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/content-local.ts +86 -18
- package/corpus/core/src/cli/index.ts +2 -2
- package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
- package/corpus/core/src/cli/skills.ts +47 -13
- package/corpus/core/src/local-artifacts/index.ts +65 -0
- package/corpus/core/src/vite/client.ts +2 -0
- package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/assets/app/i18n-data.ts +215 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +12 -2
- package/corpus/templates/clips/app/components/player/settings-panel.tsx +30 -12
- package/corpus/templates/clips/app/components/player/video-player.tsx +5 -1
- package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +3 -2
- package/corpus/templates/clips/app/i18n/de-DE.ts +4 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -2
- package/corpus/templates/clips/app/i18n/es-ES.ts +4 -2
- package/corpus/templates/clips/app/i18n/fr-FR.ts +3 -2
- package/corpus/templates/clips/app/i18n/hi-IN.ts +3 -2
- package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +3 -2
- package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -2
- package/corpus/templates/clips/app/i18n/zh-CN.ts +3 -2
- package/corpus/templates/clips/app/i18n/zh-TW.ts +3 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +27 -1
- package/corpus/templates/clips/server/lib/share-password.ts +6 -3
- package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
- package/corpus/templates/content/AGENTS.md +25 -30
- package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
- package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
- package/corpus/templates/content/actions/_content-files.ts +338 -0
- package/corpus/templates/content/actions/_content-space-access.ts +135 -0
- package/corpus/templates/content/actions/_content-spaces.ts +640 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
- package/corpus/templates/content/actions/_database-utils.ts +7 -1
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
- package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
- package/corpus/templates/content/actions/_property-utils.ts +9 -4
- package/corpus/templates/content/actions/add-database-item.ts +108 -65
- package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
- package/corpus/templates/content/actions/create-content-database.ts +129 -3
- package/corpus/templates/content/actions/create-document.ts +65 -55
- package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
- package/corpus/templates/content/actions/delete-content-database.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +7 -11
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
- package/corpus/templates/content/actions/edit-document.ts +2 -28
- package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
- package/corpus/templates/content/actions/export-content-source.ts +47 -9
- package/corpus/templates/content/actions/get-document.ts +0 -14
- package/corpus/templates/content/actions/import-content-source.ts +43 -3
- package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
- package/corpus/templates/content/actions/list-documents.ts +12 -9
- package/corpus/templates/content/actions/move-document.ts +3 -14
- package/corpus/templates/content/actions/pull-document.ts +0 -23
- package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
- package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
- package/corpus/templates/content/actions/search-documents.ts +0 -34
- package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
- package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
- package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
- package/corpus/templates/content/actions/update-document.ts +0 -15
- package/corpus/templates/content/actions/view-screen.ts +2 -50
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
- package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
- package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
- package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
- package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
- package/corpus/templates/content/app/i18n-data.ts +75 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
- package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +37 -1
- package/corpus/templates/content/scripts/dev-database.mjs +7 -5
- package/corpus/templates/content/server/db/schema.ts +113 -40
- package/corpus/templates/content/server/lib/document-context.ts +15 -1
- package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
- package/corpus/templates/content/server/plugins/db.ts +153 -0
- package/corpus/templates/content/shared/api.ts +17 -3
- package/corpus/templates/content/shared/builder-mdx.ts +163 -16
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
- package/corpus/templates/design/AGENTS.md +6 -1
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
- package/corpus/templates/design/actions/list-design-templates.ts +69 -21
- package/corpus/templates/design/actions/navigate.ts +2 -2
- package/corpus/templates/design/actions/view-screen.ts +15 -2
- package/corpus/templates/design/agent-native.app-skill.json +5 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
- package/corpus/templates/design/app/i18n-data.ts +21 -2
- package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
- package/corpus/templates/design/app/pages/Index.tsx +195 -24
- package/corpus/templates/design/app/pages/Templates.tsx +144 -55
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
- package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/mail/AGENTS.md +23 -18
- package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
- package/corpus/templates/mail/actions/mark-read.ts +131 -20
- package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
- package/corpus/templates/mail/actions/move-email.ts +30 -24
- package/corpus/templates/mail/actions/send-email.ts +73 -68
- package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +218 -209
- package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
- package/corpus/templates/mail/server/lib/email-state.ts +198 -113
- package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
- package/corpus/templates/mail/server/lib/jobs.ts +69 -74
- package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/dist/cli/content-local.d.ts +1 -0
- package/dist/cli/content-local.d.ts.map +1 -1
- package/dist/cli/content-local.js +75 -17
- package/dist/cli/content-local.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills-content/content-skill.d.ts +1 -1
- package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/content-skill.js +36 -25
- package/dist/cli/skills-content/content-skill.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +47 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/local-artifacts/index.d.ts +10 -0
- package/dist/local-artifacts/index.d.ts.map +1 -1
- package/dist/local-artifacts/index.js +39 -0
- package/dist/local-artifacts/index.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/local-file-mode.mdx +99 -489
- package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/docs/content/template-content.mdx +26 -42
- package/package.json +1 -1
- package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
|
@@ -63,6 +63,11 @@ import {
|
|
|
63
63
|
} from "../lib/google-auth.js";
|
|
64
64
|
import { getSyntheticEmailsForView, getSnoozedThreadIds } from "../lib/jobs.js";
|
|
65
65
|
import { listInboxEmails } from "../lib/list-inbox-emails.js";
|
|
66
|
+
import {
|
|
67
|
+
readLocalEmails as readEmails,
|
|
68
|
+
withLocalEmailMutationLock,
|
|
69
|
+
writeLocalEmails as writeEmails,
|
|
70
|
+
} from "../lib/local-email-store.js";
|
|
66
71
|
import {
|
|
67
72
|
bodyToHtml as outgoingBodyToHtml,
|
|
68
73
|
buildRawEmail as buildOutgoingRawEmail,
|
|
@@ -294,26 +299,10 @@ const DEFAULT_SETTINGS: UserSettings = {
|
|
|
294
299
|
|
|
295
300
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
296
301
|
|
|
297
|
-
async function readEmails(email: string): Promise<EmailMessage[]> {
|
|
298
|
-
const data = await getUserSetting(email, "local-emails");
|
|
299
|
-
if (data && Array.isArray((data as any).emails)) {
|
|
300
|
-
return (data as any).emails;
|
|
301
|
-
}
|
|
302
|
-
return [];
|
|
303
|
-
}
|
|
304
|
-
|
|
305
302
|
function reqSource(event: H3Event) {
|
|
306
303
|
return getHeader(event, "x-request-source") || undefined;
|
|
307
304
|
}
|
|
308
305
|
|
|
309
|
-
async function writeEmails(
|
|
310
|
-
email: string,
|
|
311
|
-
emails: EmailMessage[],
|
|
312
|
-
options?: { requestSource?: string },
|
|
313
|
-
): Promise<void> {
|
|
314
|
-
await putUserSetting(email, "local-emails", { emails }, options);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
306
|
async function readGmailComposeAttachment(
|
|
318
307
|
ownerEmail: string,
|
|
319
308
|
requestAccountEmail: string | undefined,
|
|
@@ -779,27 +768,32 @@ export const reportSpam = defineEventHandler(async (event: H3Event) => {
|
|
|
779
768
|
}
|
|
780
769
|
|
|
781
770
|
// Local fallback: move to trash with a spam label
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const threadId = target.threadId || target.id;
|
|
789
|
-
for (let i = 0; i < emails.length; i++) {
|
|
790
|
-
const eid = emails[i].threadId || emails[i].id;
|
|
791
|
-
if (eid === threadId) {
|
|
792
|
-
emails[i] = {
|
|
793
|
-
...emails[i],
|
|
794
|
-
isTrashed: true,
|
|
795
|
-
labelIds: [...emails[i].labelIds.filter((l) => l !== "inbox"), "spam"],
|
|
796
|
-
};
|
|
771
|
+
return withLocalEmailMutationLock(email, async () => {
|
|
772
|
+
const emails = await readEmails(email);
|
|
773
|
+
const target = emails.find((e) => e.id === getRouterParam(event, "id"));
|
|
774
|
+
if (!target) {
|
|
775
|
+
setResponseStatus(event, 404);
|
|
776
|
+
return { error: "Email not found" };
|
|
797
777
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
778
|
+
const threadId = target.threadId || target.id;
|
|
779
|
+
for (let i = 0; i < emails.length; i++) {
|
|
780
|
+
const eid = emails[i].threadId || emails[i].id;
|
|
781
|
+
if (eid === threadId) {
|
|
782
|
+
emails[i] = {
|
|
783
|
+
...emails[i],
|
|
784
|
+
isTrashed: true,
|
|
785
|
+
labelIds: [
|
|
786
|
+
...emails[i].labelIds.filter((l) => l !== "inbox"),
|
|
787
|
+
"spam",
|
|
788
|
+
],
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
await writeEmails(email, emails, { requestSource: reqSource(event) });
|
|
793
|
+
const labels = recomputeUnreadCounts(emails, await readLabels(email));
|
|
794
|
+
await writeLabels(email, labels, { requestSource: reqSource(event) });
|
|
795
|
+
return { id: getRouterParam(event, "id"), threadId, spam: true };
|
|
796
|
+
});
|
|
803
797
|
});
|
|
804
798
|
|
|
805
799
|
// ─── Block sender ─────────────────────────────────────────────────────────────
|
|
@@ -888,27 +882,32 @@ export const blockSender = defineEventHandler(async (event: H3Event) => {
|
|
|
888
882
|
});
|
|
889
883
|
}
|
|
890
884
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const threadId = target.threadId || target.id;
|
|
898
|
-
for (let i = 0; i < emails.length; i++) {
|
|
899
|
-
const eid = emails[i].threadId || emails[i].id;
|
|
900
|
-
if (eid === threadId) {
|
|
901
|
-
emails[i] = {
|
|
902
|
-
...emails[i],
|
|
903
|
-
isTrashed: true,
|
|
904
|
-
labelIds: [...emails[i].labelIds.filter((l) => l !== "inbox"), "spam"],
|
|
905
|
-
};
|
|
885
|
+
return withLocalEmailMutationLock(email, async () => {
|
|
886
|
+
const emails = await readEmails(email);
|
|
887
|
+
const target = emails.find((e) => e.id === getRouterParam(event, "id"));
|
|
888
|
+
if (!target) {
|
|
889
|
+
setResponseStatus(event, 404);
|
|
890
|
+
return { error: "Email not found" };
|
|
906
891
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
892
|
+
const threadId = target.threadId || target.id;
|
|
893
|
+
for (let i = 0; i < emails.length; i++) {
|
|
894
|
+
const eid = emails[i].threadId || emails[i].id;
|
|
895
|
+
if (eid === threadId) {
|
|
896
|
+
emails[i] = {
|
|
897
|
+
...emails[i],
|
|
898
|
+
isTrashed: true,
|
|
899
|
+
labelIds: [
|
|
900
|
+
...emails[i].labelIds.filter((l) => l !== "inbox"),
|
|
901
|
+
"spam",
|
|
902
|
+
],
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
await writeEmails(email, emails, { requestSource: reqSource(event) });
|
|
907
|
+
const labels = recomputeUnreadCounts(emails, await readLabels(email));
|
|
908
|
+
await writeLabels(email, labels, { requestSource: reqSource(event) });
|
|
909
|
+
return { id: getRouterParam(event, "id"), threadId, blocked: senderEmail };
|
|
910
|
+
});
|
|
912
911
|
});
|
|
913
912
|
|
|
914
913
|
// ─── Mute thread ──────────────────────────────────────────────────────────────
|
|
@@ -964,35 +963,39 @@ export const muteThread = defineEventHandler(async (event: H3Event) => {
|
|
|
964
963
|
await writeMutedThreads(email, muted, { requestSource: reqSource(event) });
|
|
965
964
|
}
|
|
966
965
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
966
|
+
return withLocalEmailMutationLock(email, async () => {
|
|
967
|
+
const emails = await readEmails(email);
|
|
968
|
+
for (let i = 0; i < emails.length; i++) {
|
|
969
|
+
const eid = emails[i].threadId || emails[i].id;
|
|
970
|
+
if (eid === threadId) {
|
|
971
|
+
emails[i] = {
|
|
972
|
+
...emails[i],
|
|
973
|
+
isArchived: true,
|
|
974
|
+
labelIds: emails[i].labelIds.filter((l) => l !== "inbox"),
|
|
975
|
+
};
|
|
976
|
+
}
|
|
976
977
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
978
|
+
await writeEmails(email, emails, { requestSource: reqSource(event) });
|
|
979
|
+
const labels = recomputeUnreadCounts(emails, await readLabels(email));
|
|
980
|
+
await writeLabels(email, labels, { requestSource: reqSource(event) });
|
|
981
|
+
return { threadId, muted: true };
|
|
982
|
+
});
|
|
982
983
|
});
|
|
983
984
|
|
|
984
985
|
// ─── Delete permanently ───────────────────────────────────────────────────────
|
|
985
986
|
|
|
986
987
|
export const deleteEmail = defineEventHandler(async (event: H3Event) => {
|
|
987
988
|
const email = await userEmail(event);
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
989
|
+
return withLocalEmailMutationLock(email, async () => {
|
|
990
|
+
const emails = await readEmails(email);
|
|
991
|
+
const filtered = emails.filter((e) => e.id !== getRouterParam(event, "id"));
|
|
992
|
+
if (filtered.length === emails.length) {
|
|
993
|
+
setResponseStatus(event, 404);
|
|
994
|
+
return { error: "Email not found" };
|
|
995
|
+
}
|
|
996
|
+
await writeEmails(email, filtered, { requestSource: reqSource(event) });
|
|
997
|
+
return { ok: true };
|
|
998
|
+
});
|
|
996
999
|
});
|
|
997
1000
|
|
|
998
1001
|
// ─── Send / compose ───────────────────────────────────────────────────────────
|
|
@@ -1197,62 +1200,64 @@ export const sendEmail = defineEventHandler(async (event: H3Event) => {
|
|
|
1197
1200
|
}
|
|
1198
1201
|
|
|
1199
1202
|
// Local fallback: store as sent email
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1203
|
+
return withLocalEmailMutationLock(email, async () => {
|
|
1204
|
+
const emails = await readEmails(email);
|
|
1205
|
+
|
|
1206
|
+
const newEmail: EmailMessage = {
|
|
1207
|
+
id: `msg-${nanoid(8)}`,
|
|
1208
|
+
threadId: replyToId
|
|
1209
|
+
? (emails.find((e) => e.id === replyToId)?.threadId ??
|
|
1210
|
+
`thread-${nanoid(8)}`)
|
|
1211
|
+
: `thread-${nanoid(8)}`,
|
|
1212
|
+
from: { name: settings.name, email: settings.email },
|
|
1213
|
+
to: (to as string).split(",").map((t: string) => {
|
|
1214
|
+
const trimmed = t.trim();
|
|
1215
|
+
return { name: trimmed, email: trimmed };
|
|
1216
|
+
}),
|
|
1217
|
+
...(cc
|
|
1218
|
+
? {
|
|
1219
|
+
cc: (cc as string)
|
|
1220
|
+
.split(",")
|
|
1221
|
+
.map((t: string) => ({ name: t.trim(), email: t.trim() })),
|
|
1222
|
+
}
|
|
1223
|
+
: {}),
|
|
1224
|
+
...(bcc
|
|
1225
|
+
? {
|
|
1226
|
+
bcc: (bcc as string)
|
|
1227
|
+
.split(",")
|
|
1228
|
+
.map((t: string) => ({ name: t.trim(), email: t.trim() })),
|
|
1229
|
+
}
|
|
1230
|
+
: {}),
|
|
1231
|
+
subject,
|
|
1232
|
+
snippet: markdownPreviewSnippet(body),
|
|
1233
|
+
body,
|
|
1234
|
+
bodyHtml: outgoingBodyToHtml(body),
|
|
1235
|
+
date: new Date().toISOString(),
|
|
1236
|
+
isRead: true,
|
|
1237
|
+
isStarred: false,
|
|
1238
|
+
isSent: true,
|
|
1239
|
+
isArchived: false,
|
|
1240
|
+
isTrashed: false,
|
|
1241
|
+
labelIds: ["sent"],
|
|
1242
|
+
...(attachments.length > 0
|
|
1243
|
+
? {
|
|
1244
|
+
attachments: attachments.map((att) => ({
|
|
1245
|
+
id: att.filename,
|
|
1246
|
+
filename: att.originalName,
|
|
1247
|
+
mimeType: att.mimeType,
|
|
1248
|
+
size: att.size,
|
|
1249
|
+
url: att.url,
|
|
1250
|
+
})),
|
|
1251
|
+
}
|
|
1252
|
+
: {}),
|
|
1253
|
+
};
|
|
1250
1254
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1255
|
+
emails.push(newEmail);
|
|
1256
|
+
await writeEmails(email, emails, { requestSource: reqSource(event) });
|
|
1253
1257
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1258
|
+
setResponseStatus(event, 201);
|
|
1259
|
+
return newEmail;
|
|
1260
|
+
});
|
|
1256
1261
|
});
|
|
1257
1262
|
|
|
1258
1263
|
// ─── Save draft (persistent, Gmail-style) ─────────────────────────────────────
|
|
@@ -1353,80 +1358,82 @@ export const saveDraft = defineEventHandler(async (event: H3Event) => {
|
|
|
1353
1358
|
}
|
|
1354
1359
|
|
|
1355
1360
|
// Local fallback: save as EmailMessage with isDraft=true
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
.filter((t: string) => t.trim())
|
|
1375
|
-
.map((t: string) => ({ name: t.trim(), email: t.trim() }))
|
|
1376
|
-
: [],
|
|
1377
|
-
...(cc
|
|
1378
|
-
? {
|
|
1379
|
-
cc: (cc as string)
|
|
1380
|
-
.split(",")
|
|
1381
|
-
.filter((t: string) => t.trim())
|
|
1382
|
-
.map((t: string) => ({ name: t.trim(), email: t.trim() })),
|
|
1383
|
-
}
|
|
1384
|
-
: {}),
|
|
1385
|
-
...(bcc
|
|
1386
|
-
? {
|
|
1387
|
-
bcc: (bcc as string)
|
|
1361
|
+
return withLocalEmailMutationLock(email, async () => {
|
|
1362
|
+
const emails = await readEmails(email);
|
|
1363
|
+
const existingIdx = draftId
|
|
1364
|
+
? emails.findIndex((e) => e.id === draftId && e.isDraft)
|
|
1365
|
+
: -1;
|
|
1366
|
+
|
|
1367
|
+
const draftEmail: EmailMessage = {
|
|
1368
|
+
id: existingIdx >= 0 ? emails[existingIdx].id : `draft-${nanoid(8)}`,
|
|
1369
|
+
threadId:
|
|
1370
|
+
existingIdx >= 0
|
|
1371
|
+
? emails[existingIdx].threadId
|
|
1372
|
+
: replyToId
|
|
1373
|
+
? (emails.find((e) => e.id === replyToId)?.threadId ??
|
|
1374
|
+
`thread-${nanoid(8)}`)
|
|
1375
|
+
: `thread-${nanoid(8)}`,
|
|
1376
|
+
from: { name: settings.name, email: settings.email },
|
|
1377
|
+
to: to
|
|
1378
|
+
? (to as string)
|
|
1388
1379
|
.split(",")
|
|
1389
1380
|
.filter((t: string) => t.trim())
|
|
1390
|
-
.map((t: string) => ({ name: t.trim(), email: t.trim() }))
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
:
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1381
|
+
.map((t: string) => ({ name: t.trim(), email: t.trim() }))
|
|
1382
|
+
: [],
|
|
1383
|
+
...(cc
|
|
1384
|
+
? {
|
|
1385
|
+
cc: (cc as string)
|
|
1386
|
+
.split(",")
|
|
1387
|
+
.filter((t: string) => t.trim())
|
|
1388
|
+
.map((t: string) => ({ name: t.trim(), email: t.trim() })),
|
|
1389
|
+
}
|
|
1390
|
+
: {}),
|
|
1391
|
+
...(bcc
|
|
1392
|
+
? {
|
|
1393
|
+
bcc: (bcc as string)
|
|
1394
|
+
.split(",")
|
|
1395
|
+
.filter((t: string) => t.trim())
|
|
1396
|
+
.map((t: string) => ({ name: t.trim(), email: t.trim() })),
|
|
1397
|
+
}
|
|
1398
|
+
: {}),
|
|
1399
|
+
subject: subject || "(no subject)",
|
|
1400
|
+
snippet: markdownPreviewSnippet(body || ""),
|
|
1401
|
+
body: body || "",
|
|
1402
|
+
bodyHtml: outgoingBodyToHtml(body || ""),
|
|
1403
|
+
date: new Date().toISOString(),
|
|
1404
|
+
isRead: true,
|
|
1405
|
+
isStarred: false,
|
|
1406
|
+
isDraft: true,
|
|
1407
|
+
isArchived: false,
|
|
1408
|
+
isTrashed: false,
|
|
1409
|
+
labelIds: ["drafts"],
|
|
1410
|
+
...(attachments.length > 0
|
|
1411
|
+
? {
|
|
1412
|
+
attachments: attachments.map((att) => ({
|
|
1413
|
+
id: att.filename,
|
|
1414
|
+
filename: att.originalName,
|
|
1415
|
+
mimeType: att.mimeType,
|
|
1416
|
+
size: att.size,
|
|
1417
|
+
url: att.url,
|
|
1418
|
+
})),
|
|
1419
|
+
}
|
|
1420
|
+
: {}),
|
|
1421
|
+
...(replyToId ? { replyToId } : {}),
|
|
1422
|
+
...(replyToThreadId ? { replyToThreadId } : {}),
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
if (existingIdx >= 0) {
|
|
1426
|
+
emails[existingIdx] = draftEmail;
|
|
1427
|
+
} else {
|
|
1428
|
+
emails.push(draftEmail);
|
|
1429
|
+
}
|
|
1430
|
+
await writeEmails(email, emails, { requestSource: reqSource(event) });
|
|
1425
1431
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1432
|
+
return {
|
|
1433
|
+
draftId: draftEmail.id,
|
|
1434
|
+
[existingIdx >= 0 ? "updated" : "created"]: true,
|
|
1435
|
+
};
|
|
1436
|
+
});
|
|
1430
1437
|
});
|
|
1431
1438
|
|
|
1432
1439
|
/**
|
|
@@ -1488,12 +1495,14 @@ export const deleteDraft = defineEventHandler(async (event: H3Event) => {
|
|
|
1488
1495
|
}
|
|
1489
1496
|
|
|
1490
1497
|
// Local fallback
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1498
|
+
return withLocalEmailMutationLock(email, async () => {
|
|
1499
|
+
const emails = await readEmails(email);
|
|
1500
|
+
const filtered = emails.filter((e) => !(e.id === id && e.isDraft));
|
|
1501
|
+
if (filtered.length !== emails.length) {
|
|
1502
|
+
await writeEmails(email, filtered, { requestSource: reqSource(event) });
|
|
1503
|
+
}
|
|
1504
|
+
return { ok: true };
|
|
1505
|
+
});
|
|
1497
1506
|
});
|
|
1498
1507
|
|
|
1499
1508
|
// ─── Contacts (extracted from email history) ─────────────────────────────────
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface BulkMarkReadFailure {
|
|
2
|
+
id: string;
|
|
3
|
+
error: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface BulkMarkReadResult {
|
|
7
|
+
mode: "all-unread";
|
|
8
|
+
accountEmail: string;
|
|
9
|
+
matchedMessages: number;
|
|
10
|
+
matchedThreads: number;
|
|
11
|
+
excludedMessages: number;
|
|
12
|
+
excludedThreads: number;
|
|
13
|
+
changedMessages: number;
|
|
14
|
+
batchCount: number;
|
|
15
|
+
failures: BulkMarkReadFailure[];
|
|
16
|
+
remainingUnreadMessages: number | null;
|
|
17
|
+
remainingUnreadThreads: number | null;
|
|
18
|
+
remainingProtectedMessages: number | null;
|
|
19
|
+
remainingProtectedThreads: number | null;
|
|
20
|
+
/** Initially selected messages that were still unread during verification. */
|
|
21
|
+
unexpectedUnreadMessages: number | null;
|
|
22
|
+
/** Threads containing initially selected messages still unread at verification. */
|
|
23
|
+
unexpectedUnreadThreads: number | null;
|
|
24
|
+
/** Messages not present in the initial unread snapshot. */
|
|
25
|
+
newUnreadMessages?: number | null;
|
|
26
|
+
/** Threads containing messages not present in the initial unread snapshot. */
|
|
27
|
+
newUnreadThreads?: number | null;
|
|
28
|
+
verificationComplete: boolean;
|
|
29
|
+
verificationError?: string;
|
|
30
|
+
}
|