mirror of
https://github.com/ChrispyBacon-dev/DockFlare.git
synced 2026-04-28 03:39:32 +00:00
v3.1.1 - RC - more testing needed - Details in Changelog.md
Some checks failed
Docker Image - Webmail / build_self_hosted (push) Waiting to run
Docker Image - Webmail / build_github_hosted_fallback (push) Blocked by required conditions
Docker Image - DockFlare / build_self_hosted (push) Has been cancelled
Docker Image - Mail Manager / build_self_hosted (push) Has been cancelled
Docker Image - DockFlare / build_github_hosted_fallback (push) Has been cancelled
Docker Image - Mail Manager / build_github_hosted_fallback (push) Has been cancelled
Some checks failed
Docker Image - Webmail / build_self_hosted (push) Waiting to run
Docker Image - Webmail / build_github_hosted_fallback (push) Blocked by required conditions
Docker Image - DockFlare / build_self_hosted (push) Has been cancelled
Docker Image - Mail Manager / build_self_hosted (push) Has been cancelled
Docker Image - DockFlare / build_github_hosted_fallback (push) Has been cancelled
Docker Image - Mail Manager / build_github_hosted_fallback (push) Has been cancelled
This commit is contained in:
parent
b3c0da2d75
commit
0fad7c592f
65 changed files with 5980 additions and 1619 deletions
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#0f172a" />
|
||||
<title>DockFlare Mail</title>
|
||||
<link rel="apple-touch-icon" href="/favicon/apple-touch-icon.png">
|
||||
|
|
|
|||
14
webmail/package-lock.json
generated
14
webmail/package-lock.json
generated
|
|
@ -8,6 +8,7 @@
|
|||
"name": "dockflare-webmail",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@tiptap/extension-color": "^2.27.2",
|
||||
"@tiptap/extension-font-family": "^2.27.2",
|
||||
"@tiptap/extension-highlight": "^2.27.2",
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
"clsx": "^2.1.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dompurify": "^3.4.0",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"lucide-vue-next": "^0.400.0",
|
||||
"pinia": "^2.2.0",
|
||||
"radix-vue": "^1.9.0",
|
||||
|
|
@ -1595,6 +1597,12 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emoji-mart/data": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz",
|
||||
"integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
|
|
@ -4285,6 +4293,12 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/emoji-mart": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz",
|
||||
"integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@tiptap/extension-color": "^2.27.2",
|
||||
"@tiptap/extension-font-family": "^2.27.2",
|
||||
"@tiptap/extension-highlight": "^2.27.2",
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
"clsx": "^2.1.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dompurify": "^3.4.0",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"lucide-vue-next": "^0.400.0",
|
||||
"pinia": "^2.2.0",
|
||||
"radix-vue": "^1.9.0",
|
||||
|
|
|
|||
|
|
@ -9,5 +9,17 @@ export const authApi = {
|
|||
});
|
||||
return response.json();
|
||||
},
|
||||
changePassword: async (currentPassword, newPassword) => {
|
||||
const token = localStorage.getItem('jwt_token');
|
||||
const response = await fetch('/email/auth/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }),
|
||||
});
|
||||
return response.json();
|
||||
},
|
||||
};
|
||||
//# sourceMappingURL=auth.js.map
|
||||
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,UAAU,CAAA;AAEhC,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;IAE1C,iBAAiB,EAAE,KAAK,EAAE,KAAa,EAAE,QAAgB,EAAE,EAAE;QAC3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,mBAAmB,EAAE;YAChD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;SAC1C,CAAC,CAAA;QACF,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAA;IACxB,CAAC;CACF,CAAA"}
|
||||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,UAAU,CAAA;AAEhC,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;IAE1C,iBAAiB,EAAE,KAAK,EAAE,KAAa,EAAE,QAAgB,EAAE,EAAE;QAC3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,mBAAmB,EAAE;YAChD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;SAC1C,CAAC,CAAA;QACF,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAA;IACxB,CAAC;IAED,cAAc,EAAE,KAAK,EAAE,eAAuB,EAAE,WAAmB,EAAE,EAAE;QACrE,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;QAC/C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,6BAA6B,EAAE;YAC1D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,eAAe,EAAE,UAAU,KAAK,EAAE;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;SACvF,CAAC,CAAA;QACF,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAA;IACxB,CAAC;CACF,CAAA"}
|
||||
|
|
@ -12,7 +12,6 @@ apiClient.interceptors.request.use(config => {
|
|||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
// Let the browser set Content-Type (with boundary) for FormData
|
||||
if (config.data instanceof FormData) {
|
||||
delete config.headers['Content-Type'];
|
||||
}
|
||||
|
|
@ -21,6 +20,10 @@ apiClient.interceptors.request.use(config => {
|
|||
apiClient.interceptors.response.use(response => response, error => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('jwt_token');
|
||||
import('../stores/auth').then(({ useAuthStore }) => {
|
||||
const authStore = useAuthStore();
|
||||
authStore.token = '';
|
||||
});
|
||||
router.push('/login');
|
||||
}
|
||||
return Promise.reject(error);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"client.js","sourceRoot":"","sources":["client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,MAAM,MAAM,WAAW,CAAA;AAE9B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;IAC7B,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,KAAK;IACd,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;CACF,CAAC,CAAA;AAEF,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;IAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC/C,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,KAAK,EAAE,CAAA;IAClD,CAAC;IACD,gEAAgE;IAChE,IAAI,MAAM,CAAC,IAAI,YAAY,QAAQ,EAAE,CAAC;QACpC,OAAO,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;IACvC,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC,CAAC,CAAA;AAEF,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CACjC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EACpB,KAAK,CAAC,EAAE;IACN,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,EAAE,CAAC;QACnC,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;QACpC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACvB,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC,CACF,CAAA;AAED,eAAe,SAAS,CAAA"}
|
||||
{"version":3,"file":"client.js","sourceRoot":"","sources":["client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,MAAM,MAAM,WAAW,CAAA;AAE9B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;IAC7B,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,KAAK;IACd,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;CACF,CAAC,CAAA;AAEF,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;IAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC/C,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,KAAK,EAAE,CAAA;IAClD,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,YAAY,QAAQ,EAAE,CAAC;QACpC,OAAO,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;IACvC,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC,CAAC,CAAA;AAEF,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CACjC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EACpB,KAAK,CAAC,EAAE;IACN,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,EAAE,CAAC;QACnC,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;QACpC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE;YACjD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;YAChC,SAAS,CAAC,KAAK,GAAG,EAAE,CAAA;QACtB,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACvB,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC,CACF,CAAA;AAED,eAAe,SAAS,CAAA"}
|
||||
|
|
@ -15,9 +15,20 @@ export const mailApi = {
|
|||
sendMessage: (address, data) => apiClient.post(`/mailboxes/${address}/send`, data),
|
||||
searchMessages: (address, params) => apiClient.get(`/mailboxes/${address}/search`, { params }),
|
||||
getMailboxStatus: () => apiClient.get('/mailboxes/status'),
|
||||
getMailboxPreferences: (address) => apiClient.get(`/mailboxes/${address}/preferences`),
|
||||
updateMailboxPreferences: (address, data) => apiClient.patch(`/mailboxes/${address}/preferences`, data),
|
||||
getAttachmentUrl: (id) => `/api/v1/attachments/${id}/download`,
|
||||
downloadAttachment: (id) => apiClient.get(`/attachments/${id}/download`, { responseType: 'blob' }).then(r => r.data),
|
||||
createDraft: (address, data) => apiClient.post(`/mailboxes/${address}/drafts`, data),
|
||||
updateDraft: (address, id, data) => apiClient.put(`/mailboxes/${address}/drafts/${id}`, data),
|
||||
getAutoResponder: (address) => apiClient.get(`/mailboxes/${address}/auto-responder`),
|
||||
setAutoResponder: (address, data) => apiClient.post(`/mailboxes/${address}/auto-responder`, data),
|
||||
deleteAutoResponder: (address) => apiClient.delete(`/mailboxes/${address}/auto-responder`),
|
||||
getAliases: (mailbox) => apiClient.get('/aliases', { params: { mailbox, active: 1 } }),
|
||||
getAllAliases: (mailbox) => apiClient.get('/aliases', { params: { mailbox } }),
|
||||
createAlias: (data) => apiClient.post('/aliases', data),
|
||||
updateAlias: (address, data) => apiClient.patch(`/aliases/${encodeURIComponent(address)}`, data),
|
||||
deleteAlias: (address) => apiClient.delete(`/aliases/${encodeURIComponent(address)}`),
|
||||
generateAlias: (mailbox, domain, style) => apiClient.post('/aliases/generate', { mailbox_address: mailbox, domain, style: style || 'word-word-num' }),
|
||||
};
|
||||
//# sourceMappingURL=mail.js.map
|
||||
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"mail.js","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"mail.js","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,UAAU,CAAA;AAEhC,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC;IAC/C,UAAU,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,OAAO,UAAU,CAAC;IAC/E,YAAY,EAAE,CAAC,OAAe,EAAE,IAAY,EAAE,KAAc,EAAE,EAAE,CAC9D,SAAS,CAAC,IAAI,CAAC,cAAc,OAAO,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAClE,YAAY,EAAE,CAAC,OAAe,EAAE,EAAU,EAAE,EAAE,CAC5C,SAAS,CAAC,MAAM,CAAC,cAAc,OAAO,YAAY,EAAE,EAAE,CAAC;IACzD,WAAW,EAAE,CAAC,OAAe,EAAE,EAAU,EAAE,EAAE,CAC3C,SAAS,CAAC,MAAM,CAAC,cAAc,OAAO,YAAY,EAAE,QAAQ,CAAC;IAC/D,YAAY,EAAE,CAAC,OAAe,EAAE,EAAU,EAAE,IAAY,EAAE,KAAc,EAAE,EAAE,CAC1E,SAAS,CAAC,KAAK,CAAC,cAAc,OAAO,YAAY,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACzE,WAAW,EAAE,CAAC,OAAe,EAAE,MAAW,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,OAAO,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC;IAC1G,UAAU,EAAE,CAAC,OAAe,EAAE,EAAU,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,OAAO,aAAa,EAAE,EAAE,CAAC;IAClG,aAAa,EAAE,CAAC,OAAe,EAAE,EAAU,EAAE,IAAS,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,OAAO,aAAa,EAAE,EAAE,EAAE,IAAI,CAAC;IACxH,aAAa,EAAE,CAAC,OAAe,EAAE,EAAU,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,OAAO,aAAa,EAAE,EAAE,CAAC;IACxG,YAAY,EAAE,CAAC,OAAe,EAAE,IAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,OAAO,gBAAgB,EAAE,IAAI,CAAC;IACzG,YAAY,EAAE,CAAC,OAAe,EAAE,IAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,OAAO,gBAAgB,EAAE,IAAI,CAAC;IACzG,WAAW,EAAE,CAAC,OAAe,EAAE,IAAoC,EAAE,EAAE,CACrE,SAAS,CAAC,IAAI,CAAC,cAAc,OAAO,OAAO,EAAE,IAAI,CAAC;IACpD,cAAc,EAAE,CAAC,OAAe,EAAE,MAAW,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,OAAO,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC;IAC3G,gBAAgB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAC1D,qBAAqB,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,OAAO,cAAc,CAAC;IAC9F,wBAAwB,EAAE,CAAC,OAAe,EAAE,IAAyB,EAAE,EAAE,CACvE,SAAS,CAAC,KAAK,CAAC,cAAc,OAAO,cAAc,EAAE,IAAI,CAAC;IAC5D,gBAAgB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,uBAAuB,EAAE,WAAW;IACtE,kBAAkB,EAAE,CAAC,EAAmB,EAAE,EAAE,CAC1C,SAAS,CAAC,GAAG,CAAC,gBAAgB,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAY,CAAC;IAClG,WAAW,EAAE,CAAC,OAAe,EAAE,IAAyB,EAAE,EAAE,CAC1D,SAAS,CAAC,IAAI,CAAC,cAAc,OAAO,SAAS,EAAE,IAAI,CAAC;IACtD,WAAW,EAAE,CAAC,OAAe,EAAE,EAAU,EAAE,IAAyB,EAAE,EAAE,CACtE,SAAS,CAAC,GAAG,CAAC,cAAc,OAAO,WAAW,EAAE,EAAE,EAAE,IAAI,CAAC;IAC3D,gBAAgB,EAAE,CAAC,OAAe,EAAE,EAAE,CACpC,SAAS,CAAC,GAAG,CAAC,cAAc,OAAO,iBAAiB,CAAC;IACvD,gBAAgB,EAAE,CAAC,OAAe,EAAE,IAAyB,EAAE,EAAE,CAC/D,SAAS,CAAC,IAAI,CAAC,cAAc,OAAO,iBAAiB,EAAE,IAAI,CAAC;IAC9D,mBAAmB,EAAE,CAAC,OAAe,EAAE,EAAE,CACvC,SAAS,CAAC,MAAM,CAAC,cAAc,OAAO,iBAAiB,CAAC;IAC1D,UAAU,EAAE,CAAC,OAAe,EAAE,EAAE,CAC9B,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;IAC/D,aAAa,EAAE,CAAC,OAAe,EAAE,EAAE,CACjC,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC;IACpD,WAAW,EAAE,CAAC,IAAyB,EAAE,EAAE,CACzC,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC;IAClC,WAAW,EAAE,CAAC,OAAe,EAAE,IAAyB,EAAE,EAAE,CAC1D,SAAS,CAAC,KAAK,CAAC,YAAY,kBAAkB,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC;IAClE,WAAW,EAAE,CAAC,OAAe,EAAE,EAAE,CAC/B,SAAS,CAAC,MAAM,CAAC,YAAY,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;IAC7D,aAAa,EAAE,CAAC,OAAe,EAAE,MAAc,EAAE,KAAc,EAAE,EAAE,CACjE,SAAS,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,eAAe,EAAE,CAAC;CAC7G,CAAA"}
|
||||
|
|
@ -37,4 +37,16 @@ export const mailApi = {
|
|||
apiClient.post(`/mailboxes/${address}/auto-responder`, data),
|
||||
deleteAutoResponder: (address: string) =>
|
||||
apiClient.delete(`/mailboxes/${address}/auto-responder`),
|
||||
getAliases: (mailbox: string) =>
|
||||
apiClient.get('/aliases', { params: { mailbox, active: 1 } }),
|
||||
getAllAliases: (mailbox: string) =>
|
||||
apiClient.get('/aliases', { params: { mailbox } }),
|
||||
createAlias: (data: Record<string, any>) =>
|
||||
apiClient.post('/aliases', data),
|
||||
updateAlias: (address: string, data: Record<string, any>) =>
|
||||
apiClient.patch(`/aliases/${encodeURIComponent(address)}`, data),
|
||||
deleteAlias: (address: string) =>
|
||||
apiClient.delete(`/aliases/${encodeURIComponent(address)}`),
|
||||
generateAlias: (mailbox: string, domain: string, style?: string) =>
|
||||
apiClient.post('/aliases/generate', { mailbox_address: mailbox, domain, style: style || 'word-word-num' }),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,15 @@
|
|||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.pb-safe {
|
||||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||||
}
|
||||
.h-\[100dvh\] {
|
||||
height: 100dvh;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick, onUnmounted } from 'vue'
|
||||
import { ref, watch, nextTick, onUnmounted, onMounted, type Ref, computed } from 'vue'
|
||||
import { useBreakpoint } from '../../composables/useBreakpoint'
|
||||
import {
|
||||
Paperclip, X, Bold as BoldIcon, Italic as ItalicIcon, Link2, List as ListIcon, ListOrdered, Minus,
|
||||
Underline as UnderlineIcon, AlignLeft as AlignLeftIcon, AlignCenter as AlignCenterIcon, AlignRight as AlignRightIcon, AlignJustify as AlignJustifyIcon,
|
||||
Quote as QuoteIcon, RemoveFormatting, Baseline, Trash2, Strikethrough as StrikethroughIcon, Type, BookmarkCheck, Maximize2, Minimize2
|
||||
Quote as QuoteIcon, RemoveFormatting, Baseline, Trash2, Strikethrough as StrikethroughIcon, Type, BookmarkCheck, Maximize2, Minimize2, Smile
|
||||
} from 'lucide-vue-next'
|
||||
import { useEditor, EditorContent } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
|
|
@ -22,11 +23,25 @@ import { useMailStore } from '../../stores/mail'
|
|||
import Button from '../ui/Button.vue'
|
||||
import Input from '../ui/Input.vue'
|
||||
|
||||
defineProps({ panelMode: { type: Boolean, default: false } })
|
||||
const props = defineProps({ panelMode: { type: Boolean, default: false } })
|
||||
|
||||
const store = useMailStore()
|
||||
const { isMobile } = useBreakpoint()
|
||||
const effectivePanelMode = computed(() => props.panelMode || isMobile.value)
|
||||
|
||||
const to = ref('')
|
||||
const _EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
const toTags = ref<string[]>([])
|
||||
const toInput = ref('')
|
||||
const ccTags = ref<string[]>([])
|
||||
const ccInput = ref('')
|
||||
const bccTags = ref<string[]>([])
|
||||
const bccInput = ref('')
|
||||
const showCc = ref(false)
|
||||
const showBcc = ref(false)
|
||||
|
||||
const fromAddress = ref('')
|
||||
const aliases = ref<string[]>([])
|
||||
const subject = ref('')
|
||||
const attachments = ref<File[]>([])
|
||||
const sending = ref(false)
|
||||
|
|
@ -65,8 +80,24 @@ const editor = useEditor({
|
|||
},
|
||||
})
|
||||
|
||||
const loadAliases = async () => {
|
||||
if (!store.currentMailbox) return
|
||||
try {
|
||||
const res = await mailApi.getAliases(store.currentMailbox)
|
||||
aliases.value = (res.data.aliases || []).map((a: any) => a.address)
|
||||
} catch { aliases.value = [] }
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
to.value = ''
|
||||
toTags.value = []
|
||||
toInput.value = ''
|
||||
ccTags.value = []
|
||||
ccInput.value = ''
|
||||
bccTags.value = []
|
||||
bccInput.value = ''
|
||||
showCc.value = false
|
||||
showBcc.value = false
|
||||
fromAddress.value = store.currentMailbox || ''
|
||||
subject.value = ''
|
||||
attachments.value = []
|
||||
error.value = ''
|
||||
|
|
@ -78,17 +109,67 @@ const reset = () => {
|
|||
store.composeDefaults = null
|
||||
}
|
||||
|
||||
const addTag = (tags: Ref<string[]>, input: Ref<string>) => {
|
||||
const val = input.value.trim().replace(/[,;]+$/, '')
|
||||
if (val && _EMAIL_RE.test(val) && !tags.value.includes(val)) {
|
||||
tags.value.push(val)
|
||||
}
|
||||
input.value = ''
|
||||
}
|
||||
|
||||
const makeTagHandlers = (tags: Ref<string[]>, input: Ref<string>) => ({
|
||||
onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter' || e.key === ',' || e.key === 'Tab') {
|
||||
e.preventDefault()
|
||||
addTag(tags, input)
|
||||
} else if (e.key === 'Backspace' && !input.value && tags.value.length) {
|
||||
tags.value.pop()
|
||||
}
|
||||
},
|
||||
onBlur() { addTag(tags, input) },
|
||||
onPaste(e: ClipboardEvent) {
|
||||
e.preventDefault()
|
||||
const text = e.clipboardData?.getData('text') || ''
|
||||
for (const addr of text.split(/[,;\s]+/)) {
|
||||
const trimmed = addr.trim()
|
||||
if (trimmed && _EMAIL_RE.test(trimmed) && !tags.value.includes(trimmed)) {
|
||||
tags.value.push(trimmed)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const toHandlers = makeTagHandlers(toTags, toInput)
|
||||
const ccHandlers = makeTagHandlers(ccTags, ccInput)
|
||||
const bccHandlers = makeTagHandlers(bccTags, bccInput)
|
||||
|
||||
onMounted(loadAliases)
|
||||
|
||||
watch(() => store.isComposeOpen, async (open) => {
|
||||
if (open && store.composeDefaults) {
|
||||
to.value = store.composeDefaults.to || ''
|
||||
subject.value = store.composeDefaults.subject || ''
|
||||
quotedHtml.value = store.composeDefaults.quotedHtml || ''
|
||||
if (store.composeDefaults.draftId) {
|
||||
draftId.value = store.composeDefaults.draftId
|
||||
if (open) {
|
||||
await loadAliases()
|
||||
if (store.composeDefaults) {
|
||||
const rawTo = store.composeDefaults.to || ''
|
||||
if (rawTo) {
|
||||
for (const addr of rawTo.split(',').map((s: string) => s.trim()).filter(Boolean)) {
|
||||
if (_EMAIL_RE.test(addr) && !toTags.value.includes(addr)) toTags.value.push(addr)
|
||||
}
|
||||
}
|
||||
subject.value = store.composeDefaults.subject || ''
|
||||
quotedHtml.value = store.composeDefaults.quotedHtml || ''
|
||||
if (store.composeDefaults.draftId) {
|
||||
draftId.value = store.composeDefaults.draftId
|
||||
}
|
||||
const requestedFrom = store.composeDefaults.from
|
||||
fromAddress.value = (requestedFrom && aliases.value.includes(requestedFrom))
|
||||
? requestedFrom
|
||||
: (store.currentMailbox || '')
|
||||
} else {
|
||||
fromAddress.value = store.currentMailbox || ''
|
||||
}
|
||||
minimized.value = false
|
||||
await nextTick()
|
||||
if (store.composeDefaults.body) {
|
||||
if (store.composeDefaults?.body) {
|
||||
editor.value?.commands.setContent(store.composeDefaults.body)
|
||||
} else {
|
||||
editor.value?.commands.clearContent()
|
||||
|
|
@ -98,24 +179,6 @@ watch(() => store.isComposeOpen, async (open) => {
|
|||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(to, (val) => {
|
||||
if (store.composeDefaults !== null) {
|
||||
store.composeDefaults = { ...store.composeDefaults, to: val }
|
||||
}
|
||||
})
|
||||
|
||||
watch(subject, (val) => {
|
||||
if (store.composeDefaults !== null) {
|
||||
store.composeDefaults = { ...store.composeDefaults, subject: val }
|
||||
}
|
||||
})
|
||||
|
||||
watch(quotedHtml, (val) => {
|
||||
if (store.composeDefaults !== null) {
|
||||
store.composeDefaults = { ...store.composeDefaults, quotedHtml: val }
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => editor.value?.destroy())
|
||||
|
||||
const close = () => {
|
||||
|
|
@ -167,24 +230,44 @@ const formatBytes = (bytes: number) => {
|
|||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
|
||||
const setLink = () => {
|
||||
const prev = editor.value?.getAttributes('link').href || ''
|
||||
const url = window.prompt('Enter URL', prev)
|
||||
if (url === null) return
|
||||
if (url === '') {
|
||||
const showLinkPopover = ref(false)
|
||||
const linkInput = ref('')
|
||||
|
||||
const openLinkPopover = () => {
|
||||
linkInput.value = editor.value?.getAttributes('link').href || ''
|
||||
showLinkPopover.value = true
|
||||
nextTick(() => {
|
||||
const el = document.getElementById('compose-link-input')
|
||||
el?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const applyLink = () => {
|
||||
const url = linkInput.value.trim()
|
||||
if (!url) {
|
||||
editor.value?.chain().focus().unsetLink().run()
|
||||
} else {
|
||||
editor.value?.chain().focus().setLink({ href: url }).run()
|
||||
const href = url.startsWith('http') ? url : `https://${url}`
|
||||
editor.value?.chain().focus().setLink({ href }).run()
|
||||
}
|
||||
showLinkPopover.value = false
|
||||
}
|
||||
|
||||
const onLinkKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') { e.preventDefault(); applyLink() }
|
||||
if (e.key === 'Escape') { showLinkPopover.value = false }
|
||||
}
|
||||
|
||||
const saveDraft = async () => {
|
||||
if (!store.currentMailbox || !editor.value) return
|
||||
toHandlers.onBlur()
|
||||
savingDraft.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const payload = {
|
||||
to: to.value ? to.value.split(',').map((s: string) => s.trim()).filter(Boolean) : [],
|
||||
to: toTags.value,
|
||||
cc: ccTags.value,
|
||||
bcc: bccTags.value,
|
||||
subject: subject.value,
|
||||
html_body: editor.value.getHTML() + (quotedHtml.value || ''),
|
||||
text_body: editor.value.getText(),
|
||||
|
|
@ -209,6 +292,12 @@ const saveDraft = async () => {
|
|||
const send = async () => {
|
||||
if (!store.currentMailbox || !editor.value) return
|
||||
|
||||
toHandlers.onBlur()
|
||||
if (!toTags.value.length) {
|
||||
error.value = 'Please add at least one recipient.'
|
||||
return
|
||||
}
|
||||
|
||||
const totalSize = attachments.value.reduce((sum, f) => sum + f.size, 0)
|
||||
if (totalSize > MAX_ATTACHMENT_BYTES) {
|
||||
error.value = `Attachments exceed 10 MB limit (${formatBytes(totalSize)} total).`
|
||||
|
|
@ -221,10 +310,15 @@ const send = async () => {
|
|||
const html = editor.value.getHTML() + (quotedHtml.value || '')
|
||||
const text = editor.value.getText()
|
||||
const formData = new FormData()
|
||||
formData.append('to', to.value)
|
||||
for (const addr of toTags.value) formData.append('to', addr)
|
||||
for (const addr of ccTags.value) formData.append('cc', addr)
|
||||
for (const addr of bccTags.value) formData.append('bcc', addr)
|
||||
formData.append('subject', subject.value)
|
||||
formData.append('html', html)
|
||||
formData.append('text', text)
|
||||
if (fromAddress.value && fromAddress.value !== store.currentMailbox) {
|
||||
formData.append('from_address', fromAddress.value)
|
||||
}
|
||||
for (const file of attachments.value) {
|
||||
formData.append('attachments', file)
|
||||
}
|
||||
|
|
@ -266,18 +360,55 @@ const setHighlight = (e: Event) => {
|
|||
const target = e.target as HTMLInputElement
|
||||
editor.value?.chain().focus().setHighlight({ color: target.value }).run()
|
||||
}
|
||||
|
||||
const showEmojiPicker = ref(false)
|
||||
const emojiPickerContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const openEmojiPicker = async () => {
|
||||
showEmojiPicker.value = !showEmojiPicker.value
|
||||
if (!showEmojiPicker.value) return
|
||||
await nextTick()
|
||||
if (!emojiPickerContainer.value) return
|
||||
emojiPickerContainer.value.innerHTML = ''
|
||||
const { Picker } = await import('emoji-mart')
|
||||
const data = (await import('@emoji-mart/data')).default
|
||||
new Picker({
|
||||
data,
|
||||
onEmojiSelect: (emoji: any) => {
|
||||
editor.value?.chain().focus().insertContent(emoji.native).run()
|
||||
showEmojiPicker.value = false
|
||||
},
|
||||
parent: emojiPickerContainer.value,
|
||||
theme: document.documentElement.classList.contains('dark') ? 'dark' : 'light',
|
||||
})
|
||||
}
|
||||
|
||||
const onEmojiClickOutside = (e: MouseEvent) => {
|
||||
if (!emojiPickerContainer.value) return
|
||||
const wrapper = emojiPickerContainer.value.closest('.emoji-picker-wrapper')
|
||||
if (wrapper && !wrapper.contains(e.target as Node)) {
|
||||
showEmojiPicker.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(showEmojiPicker, (val) => {
|
||||
if (val) document.addEventListener('mousedown', onEmojiClickOutside)
|
||||
else document.removeEventListener('mousedown', onEmojiClickOutside)
|
||||
})
|
||||
|
||||
onUnmounted(() => document.removeEventListener('mousedown', onEmojiClickOutside))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="store.isComposeOpen && (panelMode || !store.isComposeFullView)"
|
||||
:class="panelMode
|
||||
v-if="store.isComposeOpen && (effectivePanelMode || !store.isComposeFullView)"
|
||||
:class="effectivePanelMode
|
||||
? 'flex flex-col h-full w-full bg-background'
|
||||
: 'fixed bottom-0 right-6 z-50 flex flex-col rounded-t-xl shadow-2xl border border-border bg-background'"
|
||||
:style="!panelMode ? (minimized ? 'width:320px' : 'width:560px') : ''"
|
||||
:style="!effectivePanelMode ? (minimized ? 'width:320px' : 'width:620px') : ''"
|
||||
>
|
||||
<!-- Panel mode title bar -->
|
||||
<div v-if="panelMode" class="h-[52px] flex items-center gap-2 px-4 border-b border-border flex-shrink-0">
|
||||
<div v-if="effectivePanelMode" class="h-[52px] flex items-center gap-2 px-4 border-b border-border flex-shrink-0">
|
||||
<span class="flex-1 text-base font-semibold truncate">{{ subject || 'New Message' }}</span>
|
||||
<button type="button" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors" title="Pop out" @click="toggleFullView">
|
||||
<Minimize2 class="size-4" />
|
||||
|
|
@ -288,7 +419,7 @@ const setHighlight = (e: Event) => {
|
|||
</div>
|
||||
|
||||
<!-- Popup mode title bar -->
|
||||
<div v-else class="flex items-center gap-2 rounded-t-xl bg-primary px-4 py-2.5 cursor-pointer select-none" @click="toggleMinimize">
|
||||
<div v-else-if="!effectivePanelMode" class="flex items-center gap-2 rounded-t-xl bg-primary px-4 py-2.5 cursor-pointer select-none" @click="toggleMinimize">
|
||||
<span class="flex-1 text-sm font-semibold text-primary-foreground truncate">{{ subject || 'New Message' }}</span>
|
||||
<button type="button" class="rounded p-0.5 text-primary-foreground/70 hover:text-primary-foreground hover:bg-white/10 transition-colors" title="Full view" @click.stop="toggleFullView">
|
||||
<Maximize2 class="size-4" />
|
||||
|
|
@ -301,14 +432,94 @@ const setHighlight = (e: Event) => {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Body — always visible in panel mode, hidden when minimized in popup mode -->
|
||||
<!-- Body -->
|
||||
<div
|
||||
v-show="panelMode || !minimized"
|
||||
:class="panelMode ? 'flex flex-col flex-1 overflow-hidden' : 'flex flex-col flex-1 overflow-hidden max-h-[80vh]'"
|
||||
v-show="effectivePanelMode || !minimized"
|
||||
:class="effectivePanelMode ? 'flex flex-col flex-1 overflow-hidden' : 'flex flex-col flex-1 overflow-hidden max-h-[80vh]'"
|
||||
>
|
||||
<!-- Fields -->
|
||||
<div class="flex flex-col border-b border-border flex-shrink-0">
|
||||
<input v-model="to" placeholder="To" class="w-full border-b border-border px-4 py-2 text-sm bg-background text-foreground placeholder:text-muted-foreground focus:outline-none" />
|
||||
|
||||
<!-- To row -->
|
||||
<div class="flex items-start border-b border-border min-h-[36px]">
|
||||
<span class="px-4 py-2 text-sm text-muted-foreground shrink-0 leading-5">To</span>
|
||||
<div class="flex flex-wrap items-center gap-1 flex-1 py-1.5 pr-2 min-w-0">
|
||||
<span
|
||||
v-for="(tag, i) in toTags" :key="tag"
|
||||
class="flex items-center gap-1 bg-muted rounded-full px-2 py-0.5 text-xs text-foreground border border-border"
|
||||
>
|
||||
{{ tag }}
|
||||
<button type="button" @click="toTags.splice(i, 1)" class="hover:text-destructive leading-none"><X :size="10" /></button>
|
||||
</span>
|
||||
<input
|
||||
v-model="toInput"
|
||||
placeholder="Add recipient…"
|
||||
class="flex-1 min-w-[120px] bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none py-0.5"
|
||||
@keydown="toHandlers.onKeydown"
|
||||
@blur="toHandlers.onBlur"
|
||||
@paste="toHandlers.onPaste"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 px-3 py-2 shrink-0">
|
||||
<button v-if="!showCc" type="button" class="text-xs text-muted-foreground hover:text-foreground transition-colors" @click="showCc = true">Cc</button>
|
||||
<button v-if="!showBcc" type="button" class="text-xs text-muted-foreground hover:text-foreground transition-colors" @click="showBcc = true">Bcc</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cc row -->
|
||||
<div v-if="showCc" class="flex items-start border-b border-border min-h-[36px]">
|
||||
<span class="px-4 py-2 text-sm text-muted-foreground shrink-0 leading-5">Cc</span>
|
||||
<div class="flex flex-wrap items-center gap-1 flex-1 py-1.5 pr-2 min-w-0">
|
||||
<span
|
||||
v-for="(tag, i) in ccTags" :key="tag"
|
||||
class="flex items-center gap-1 bg-muted rounded-full px-2 py-0.5 text-xs text-foreground border border-border"
|
||||
>
|
||||
{{ tag }}
|
||||
<button type="button" @click="ccTags.splice(i, 1)" class="hover:text-destructive leading-none"><X :size="10" /></button>
|
||||
</span>
|
||||
<input
|
||||
v-model="ccInput"
|
||||
placeholder="Add Cc…"
|
||||
class="flex-1 min-w-[120px] bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none py-0.5"
|
||||
@keydown="ccHandlers.onKeydown"
|
||||
@blur="ccHandlers.onBlur"
|
||||
@paste="ccHandlers.onPaste"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bcc row -->
|
||||
<div v-if="showBcc" class="flex items-start border-b border-border min-h-[36px]">
|
||||
<span class="px-4 py-2 text-sm text-muted-foreground shrink-0 leading-5">Bcc</span>
|
||||
<div class="flex flex-wrap items-center gap-1 flex-1 py-1.5 pr-2 min-w-0">
|
||||
<span
|
||||
v-for="(tag, i) in bccTags" :key="tag"
|
||||
class="flex items-center gap-1 bg-muted rounded-full px-2 py-0.5 text-xs text-foreground border border-border"
|
||||
>
|
||||
{{ tag }}
|
||||
<button type="button" @click="bccTags.splice(i, 1)" class="hover:text-destructive leading-none"><X :size="10" /></button>
|
||||
</span>
|
||||
<input
|
||||
v-model="bccInput"
|
||||
placeholder="Add Bcc…"
|
||||
class="flex-1 min-w-[120px] bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none py-0.5"
|
||||
@keydown="bccHandlers.onKeydown"
|
||||
@blur="bccHandlers.onBlur"
|
||||
@paste="bccHandlers.onPaste"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- From row (aliases) -->
|
||||
<div v-if="aliases.length" class="flex items-center border-b border-border">
|
||||
<span class="px-4 py-2 text-sm text-muted-foreground shrink-0">From</span>
|
||||
<select v-model="fromAddress" class="flex-1 px-2 py-2 text-sm bg-background text-foreground focus:outline-none">
|
||||
<option :value="store.currentMailbox">{{ store.currentMailbox }}</option>
|
||||
<option v-for="alias in aliases" :key="alias" :value="alias">{{ alias }} (alias)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Subject -->
|
||||
<input v-model="subject" placeholder="Subject" class="w-full px-4 py-2 text-sm bg-background text-foreground placeholder:text-muted-foreground focus:outline-none" />
|
||||
</div>
|
||||
|
||||
|
|
@ -330,7 +541,7 @@ const setHighlight = (e: Event) => {
|
|||
|
||||
<div v-if="error" class="px-4 py-1 text-xs text-red-500 flex-shrink-0">{{ error }}</div>
|
||||
|
||||
<!-- Formatting Toolbar (Collapsible) -->
|
||||
<!-- Formatting Toolbar -->
|
||||
<div v-if="showFormatting" class="flex flex-wrap items-center gap-1 border-t border-border bg-muted/30 px-3 py-1.5 flex-shrink-0">
|
||||
<select @change="setFont" class="text-xs bg-transparent border-none focus:ring-0 text-foreground cursor-pointer mr-1 max-w-[100px]">
|
||||
<option value="">Default Font</option>
|
||||
|
|
@ -364,12 +575,18 @@ const setHighlight = (e: Event) => {
|
|||
<!-- Bottom Action Bar -->
|
||||
<div class="flex items-center justify-between gap-2 border-t border-border px-4 py-2.5 flex-shrink-0 bg-background">
|
||||
<div class="flex items-center gap-1">
|
||||
<Button as="button" type="button" size="sm" class="rounded-full px-5 font-semibold tracking-wide" @click.prevent="send" :disabled="sending || !to">
|
||||
<Button as="button" type="button" size="sm" class="rounded-full px-5 font-semibold tracking-wide" @click.prevent="send" :disabled="sending || (!toTags.length && !toInput)">
|
||||
{{ sending ? 'Sending…' : 'Send' }}
|
||||
</Button>
|
||||
<button type="button" class="ml-1 rounded p-1.5 transition-colors" :class="savedDraft ? 'text-green-500' : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'" :disabled="savingDraft" title="Save draft" @click="saveDraft">
|
||||
<BookmarkCheck class="size-4" />
|
||||
</button>
|
||||
<div class="relative emoji-picker-wrapper">
|
||||
<button type="button" class="rounded p-1.5 transition-colors" :class="showEmojiPicker ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'" @click="openEmojiPicker" title="Insert emoji">
|
||||
<Smile class="size-4" />
|
||||
</button>
|
||||
<div v-if="showEmojiPicker" class="absolute bottom-10 left-0 z-50 shadow-xl rounded-xl overflow-hidden" ref="emojiPickerContainer" />
|
||||
</div>
|
||||
<button type="button" class="ml-1 rounded p-1.5 hover:bg-accent transition-colors" :class="showFormatting ? 'bg-accent text-accent-foreground' : 'text-muted-foreground'" @click="showFormatting = !showFormatting" title="Formatting options">
|
||||
<Type class="size-4" />
|
||||
</button>
|
||||
|
|
@ -377,9 +594,26 @@ const setHighlight = (e: Event) => {
|
|||
<Paperclip class="size-4" />
|
||||
<input type="file" multiple class="hidden" @change="onFileChange" />
|
||||
</label>
|
||||
<button type="button" class="rounded p-1.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors" @click="setLink" title="Insert link">
|
||||
<Link2 class="size-4" />
|
||||
</button>
|
||||
<div class="relative">
|
||||
<button type="button" class="rounded p-1.5 transition-colors" :class="showLinkPopover ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'" @click="openLinkPopover" title="Insert link">
|
||||
<Link2 class="size-4" />
|
||||
</button>
|
||||
<div v-if="showLinkPopover" class="absolute bottom-10 left-0 z-50 w-72 rounded-lg border border-border bg-background shadow-xl p-3 flex flex-col gap-2">
|
||||
<label class="text-xs font-medium text-muted-foreground">Insert link</label>
|
||||
<input
|
||||
id="compose-link-input"
|
||||
v-model="linkInput"
|
||||
type="url"
|
||||
placeholder="https://example.com"
|
||||
class="w-full rounded-md border border-border bg-background px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
@keydown="onLinkKeydown"
|
||||
/>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<button type="button" class="rounded-md px-3 py-1 text-xs text-muted-foreground hover:bg-accent transition-colors" @click="showLinkPopover = false">Cancel</button>
|
||||
<button type="button" class="rounded-md px-3 py-1 text-xs bg-primary text-primary-foreground hover:bg-primary/90 transition-colors" @click="applyLink">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="rounded p-1.5 text-muted-foreground hover:bg-accent hover:text-destructive transition-colors" :title="draftId ? 'Delete draft' : 'Discard'" @click="discardDraft">
|
||||
<Trash2 class="size-4" />
|
||||
|
|
@ -390,7 +624,6 @@ const setHighlight = (e: Event) => {
|
|||
</template>
|
||||
|
||||
<style>
|
||||
/* Tiptap editor inside compose */
|
||||
.compose-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -433,7 +666,6 @@ const setHighlight = (e: Event) => {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Pop-up animation */
|
||||
.compose-pop-enter-active { transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.18s ease; }
|
||||
.compose-pop-leave-active { transition: transform 0.14s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.14s ease; }
|
||||
.compose-pop-enter-from { transform: translateY(24px) scale(0.95); opacity: 0; }
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -48,8 +48,8 @@ const confirmNewFolder = async () => {
|
|||
store.folders = res.data;
|
||||
showNewFolder.value = false;
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to create folder', e);
|
||||
catch {
|
||||
store.showToast('Failed to create folder');
|
||||
}
|
||||
finally {
|
||||
creatingFolder.value = false;
|
||||
|
|
@ -69,8 +69,8 @@ const deleteFolder = async (f) => {
|
|||
store.currentFolder = store.folders[0]?.name || '';
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to delete folder', e);
|
||||
catch {
|
||||
store.showToast('Failed to delete folder');
|
||||
}
|
||||
};
|
||||
// ── Folder rename / colour edit ──────────────────────────────────────
|
||||
|
|
@ -100,8 +100,8 @@ const confirmEdit = async () => {
|
|||
}
|
||||
editingFolder.value = null;
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to rename folder', e);
|
||||
catch {
|
||||
store.showToast('Failed to rename folder');
|
||||
}
|
||||
};
|
||||
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
|
||||
|
|
@ -182,17 +182,19 @@ for (const [f] of __VLS_getVForSourceType((__VLS_ctx.store.folders))) {
|
|||
}, ...__VLS_functionalComponentArgsRest(__VLS_17));
|
||||
__VLS_19.slots.default;
|
||||
(f.name);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "ml-auto text-muted-foreground flex gap-1" },
|
||||
});
|
||||
if (f.unread_count) {
|
||||
if (f.total_count > 0) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "font-bold" },
|
||||
...{ class: "ml-auto text-muted-foreground flex gap-1" },
|
||||
});
|
||||
(f.unread_count);
|
||||
if (f.unread_count) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "font-bold" },
|
||||
});
|
||||
(f.unread_count);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(f.total_count);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(f.total_count || 0);
|
||||
var __VLS_19;
|
||||
var __VLS_15;
|
||||
var __VLS_3;
|
||||
|
|
@ -279,17 +281,19 @@ for (const [f] of __VLS_getVForSourceType((__VLS_ctx.store.folders))) {
|
|||
...{ class: "truncate" },
|
||||
});
|
||||
(f.name);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: (__VLS_ctx.cn('ml-auto text-xs flex-shrink-0 flex gap-1', __VLS_ctx.store.currentFolder === f.name ? 'text-primary-foreground' : 'text-muted-foreground')) },
|
||||
});
|
||||
if (f.unread_count) {
|
||||
if (f.total_count > 0) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "font-bold" },
|
||||
...{ class: (__VLS_ctx.cn('ml-auto text-xs flex-shrink-0 flex gap-1', __VLS_ctx.store.currentFolder === f.name ? 'text-primary-foreground' : 'text-muted-foreground')) },
|
||||
});
|
||||
(f.unread_count);
|
||||
if (f.unread_count) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "font-bold" },
|
||||
});
|
||||
(f.unread_count);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(f.total_count);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(f.total_count || 0);
|
||||
if (!f.system_folder) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: (__VLS_ctx.cn('absolute right-1 flex gap-0.5 opacity-0 group-hover/row:opacity-100 transition-opacity rounded px-0.5', __VLS_ctx.store.currentFolder === f.name ? 'bg-primary' : 'bg-accent')) },
|
||||
|
|
@ -372,6 +376,16 @@ if (__VLS_ctx.showNewFolder && !__VLS_ctx.isCollapsed) {
|
|||
...{ style: (`background:${c}; border-color:${__VLS_ctx.newFolderColor === c ? '#000' : 'transparent'}`) },
|
||||
});
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.showNewFolder && !__VLS_ctx.isCollapsed))
|
||||
return;
|
||||
__VLS_ctx.newFolderColor = '';
|
||||
} },
|
||||
...{ class: "h-5 w-5 rounded-full border-2 text-xs flex items-center justify-center text-muted-foreground hover:bg-accent" },
|
||||
...{ style: (`border-color:${!__VLS_ctx.newFolderColor ? '#888' : 'transparent'}`) },
|
||||
title: "No colour",
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex gap-1 justify-end" },
|
||||
});
|
||||
|
|
@ -531,6 +545,16 @@ if (!__VLS_ctx.isCollapsed) {
|
|||
/** @type {__VLS_StyleScopedClasses['border-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-transform']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:scale-110']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-accent']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-end']} */ ;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -3,10 +3,9 @@ import {
|
|||
SplitterGroup, SplitterPanel, SplitterResizeHandle,
|
||||
TooltipProvider, TooltipRoot, TooltipTrigger, TooltipContent, TooltipPortal,
|
||||
} from 'radix-vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { PenSquare, Sun, Moon, LogOut, Settings, Columns2, Maximize2 } from 'lucide-vue-next'
|
||||
import { defineAsyncComponent, ref, watch, computed } from 'vue'
|
||||
import { PenSquare, Sun, Moon, LogOut, Settings, Columns2, Maximize2, ChevronLeft, Menu } from 'lucide-vue-next'
|
||||
import { cn } from '../../lib/utils'
|
||||
import Separator from '../ui/Separator.vue'
|
||||
import MailboxSelector from './MailboxSelector.vue'
|
||||
import FolderNav from './FolderNav.vue'
|
||||
import MessageList from './MessageList.vue'
|
||||
|
|
@ -14,11 +13,13 @@ import MessageDisplay from './MessageDisplay.vue'
|
|||
import ComposeDialog from './ComposeDialog.vue'
|
||||
import { useMailStore } from '../../stores/mail'
|
||||
import { useAuth } from '../../composables/useAuth'
|
||||
import { useBreakpoint } from '../../composables/useBreakpoint'
|
||||
|
||||
const SettingsDialog = defineAsyncComponent(() => import('./SettingsDialog.vue'))
|
||||
|
||||
const store = useMailStore()
|
||||
const { logout } = useAuth()
|
||||
const { isMobile } = useBreakpoint()
|
||||
|
||||
const onCollapse = () => { store.isCollapsed = true }
|
||||
const onExpand = () => { store.isCollapsed = false }
|
||||
|
|
@ -27,11 +28,134 @@ const compose = () => {
|
|||
store.composeDefaults = null
|
||||
store.isComposeOpen = true
|
||||
}
|
||||
|
||||
// ── Mobile navigation stack ──────────────────────────────────────────
|
||||
type MobilePanel = 'folders' | 'list' | 'detail'
|
||||
const mobilePanel = ref<MobilePanel>('list')
|
||||
|
||||
watch(() => store.currentFolder, () => {
|
||||
if (isMobile.value) mobilePanel.value = 'list'
|
||||
})
|
||||
|
||||
watch(() => store.currentMessage, (msg) => {
|
||||
if (isMobile.value && msg) mobilePanel.value = 'detail'
|
||||
})
|
||||
|
||||
const goBack = () => {
|
||||
if (mobilePanel.value === 'detail') {
|
||||
store.currentMessage = null
|
||||
mobilePanel.value = 'list'
|
||||
} else if (mobilePanel.value === 'list') {
|
||||
mobilePanel.value = 'folders'
|
||||
}
|
||||
}
|
||||
|
||||
const mobileTitle = computed(() => {
|
||||
if (mobilePanel.value === 'folders') return store.currentMailbox || 'Folders'
|
||||
if (mobilePanel.value === 'list') return store.currentFolder || 'Inbox'
|
||||
return store.currentMessage?.subject || 'Message'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipProvider :delay-duration="0">
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
MOBILE LAYOUT (< 768px)
|
||||
══════════════════════════════════════════════════════════ -->
|
||||
<div v-if="isMobile" class="flex flex-col h-[100dvh] w-screen bg-background overflow-hidden">
|
||||
|
||||
<!-- Top bar -->
|
||||
<div class="h-14 flex items-center gap-2 px-3 border-b border-border flex-shrink-0 bg-background">
|
||||
<button
|
||||
v-if="mobilePanel !== 'folders'"
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-accent transition-colors flex-shrink-0"
|
||||
@click="goBack"
|
||||
>
|
||||
<ChevronLeft class="size-5" />
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-accent transition-colors flex-shrink-0"
|
||||
@click="store.isSettingsOpen = true"
|
||||
>
|
||||
<Settings class="size-4" />
|
||||
</button>
|
||||
|
||||
<span class="flex-1 text-base font-semibold truncate">{{ mobileTitle }}</span>
|
||||
|
||||
<button
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-accent transition-colors flex-shrink-0"
|
||||
@click="store.toggleTheme()"
|
||||
>
|
||||
<Sun v-if="store.isDark" class="size-4" />
|
||||
<Moon v-else class="size-4" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-accent transition-colors flex-shrink-0"
|
||||
@click="logout"
|
||||
>
|
||||
<LogOut class="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Panel content -->
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
|
||||
<!-- Folders panel -->
|
||||
<div v-if="mobilePanel === 'folders'" class="h-full flex flex-col overflow-y-auto">
|
||||
<div class="px-3 py-3 border-b border-border">
|
||||
<MailboxSelector :is-collapsed="false" />
|
||||
</div>
|
||||
<FolderNav :is-collapsed="false" />
|
||||
</div>
|
||||
|
||||
<!-- Message list panel -->
|
||||
<div v-else-if="mobilePanel === 'list'" class="h-full flex flex-col overflow-hidden">
|
||||
<MessageList />
|
||||
</div>
|
||||
|
||||
<!-- Message detail panel -->
|
||||
<div v-else-if="mobilePanel === 'detail'" class="h-full flex flex-col overflow-hidden">
|
||||
<MessageDisplay :message="store.currentMessage ?? undefined" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom nav bar -->
|
||||
<div class="h-16 flex items-center justify-around border-t border-border flex-shrink-0 bg-background pb-safe">
|
||||
<button
|
||||
class="flex flex-col items-center gap-0.5 px-4 py-2 rounded-lg transition-colors"
|
||||
:class="mobilePanel === 'folders' ? 'text-primary' : 'text-muted-foreground hover:text-foreground'"
|
||||
@click="mobilePanel = 'folders'"
|
||||
>
|
||||
<Menu class="size-5" />
|
||||
<span class="text-[10px] font-medium">Folders</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex items-center justify-center h-12 w-12 rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-colors"
|
||||
@click="compose"
|
||||
>
|
||||
<PenSquare class="size-5" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex flex-col items-center gap-0.5 px-4 py-2 rounded-lg transition-colors"
|
||||
:class="mobilePanel === 'list' ? 'text-primary' : 'text-muted-foreground hover:text-foreground'"
|
||||
@click="mobilePanel = 'list'"
|
||||
>
|
||||
<Columns2 class="size-5" />
|
||||
<span class="text-[10px] font-medium">Mail</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
DESKTOP LAYOUT (≥ 768px) — unchanged
|
||||
══════════════════════════════════════════════════════════ -->
|
||||
<SplitterGroup
|
||||
v-else
|
||||
id="mail-layout"
|
||||
direction="horizontal"
|
||||
class="h-screen w-screen items-stretch"
|
||||
|
|
@ -50,7 +174,6 @@ const compose = () => {
|
|||
@collapse="onCollapse"
|
||||
@expand="onExpand"
|
||||
>
|
||||
<!-- ── Single header row (same height as middle + right panel headers) ── -->
|
||||
<div
|
||||
:class="cn(
|
||||
'h-[52px] flex items-center gap-1 px-2 flex-shrink-0',
|
||||
|
|
@ -58,11 +181,9 @@ const compose = () => {
|
|||
)"
|
||||
>
|
||||
<template v-if="!store.isCollapsed">
|
||||
<!-- Mailbox selector fills available space -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<MailboxSelector :is-collapsed="false" />
|
||||
</div>
|
||||
<!-- Compose -->
|
||||
<TooltipRoot :delay-duration="0">
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
|
|
@ -74,7 +195,6 @@ const compose = () => {
|
|||
</button>
|
||||
</TooltipTrigger>
|
||||
</TooltipRoot>
|
||||
<!-- View Mode toggle -->
|
||||
<TooltipRoot :delay-duration="0">
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
|
|
@ -91,7 +211,6 @@ const compose = () => {
|
|||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</TooltipRoot>
|
||||
<!-- Theme toggle -->
|
||||
<TooltipRoot :delay-duration="0">
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
|
|
@ -108,7 +227,6 @@ const compose = () => {
|
|||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</TooltipRoot>
|
||||
<!-- Settings -->
|
||||
<TooltipRoot :delay-duration="0">
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
|
|
@ -124,7 +242,6 @@ const compose = () => {
|
|||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</TooltipRoot>
|
||||
<!-- Logout -->
|
||||
<TooltipRoot :delay-duration="0">
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
|
|
@ -142,7 +259,6 @@ const compose = () => {
|
|||
</TooltipRoot>
|
||||
</template>
|
||||
|
||||
<!-- Collapsed: stacked icon buttons -->
|
||||
<template v-else>
|
||||
<TooltipRoot :delay-duration="0">
|
||||
<TooltipTrigger as-child>
|
||||
|
|
@ -185,7 +301,6 @@ const compose = () => {
|
|||
<TooltipContent side="right" class="z-50 rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md">Logout</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</TooltipRoot>
|
||||
<!-- Collapsed mailbox icon below -->
|
||||
<MailboxSelector :is-collapsed="true" />
|
||||
</template>
|
||||
</div>
|
||||
|
|
@ -242,7 +357,15 @@ const compose = () => {
|
|||
</template>
|
||||
</SplitterGroup>
|
||||
|
||||
<ComposeDialog />
|
||||
<!-- Floating compose (desktop only) + settings dialog -->
|
||||
<template v-if="!isMobile">
|
||||
<ComposeDialog />
|
||||
</template>
|
||||
<Teleport v-else to="body">
|
||||
<div v-if="store.isComposeOpen" class="fixed inset-0 z-50 flex flex-col bg-background">
|
||||
<ComposeDialog :panel-mode="true" />
|
||||
</div>
|
||||
</Teleport>
|
||||
<SettingsDialog />
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -81,6 +81,15 @@ watch(emailIframe, (el) => {
|
|||
|
||||
onUnmounted(() => resizeObserver?.disconnect())
|
||||
|
||||
const parseAddrs = (raw: string | null | undefined) => {
|
||||
let addrs: string[] = []
|
||||
try { addrs = JSON.parse(raw || '[]') } catch { addrs = [] }
|
||||
return addrs.map((a: string) => { const m = a.match(/<([^>]+)>/); return m ? m[1] : a }).join(', ')
|
||||
}
|
||||
const toDisplay = computed(() => parseAddrs(props.message?.to_addresses))
|
||||
const ccDisplay = computed(() => parseAddrs(props.message?.cc_addresses))
|
||||
const bccDisplay = computed(() => parseAddrs(props.message?.bcc_addresses))
|
||||
|
||||
const displayTimestamp = computed(() => {
|
||||
const ts = props.message?.received_at || props.message?.sent_at
|
||||
return ts ? format(new Date(ts), 'PPpp') : ''
|
||||
|
|
@ -104,6 +113,7 @@ const replyTo = () => {
|
|||
if (!props.message) return
|
||||
store.composeDefaults = {
|
||||
to: props.message.from_address,
|
||||
from: props.message.received_via_alias || undefined,
|
||||
subject: props.message.subject?.startsWith('Re:')
|
||||
? props.message.subject
|
||||
: `Re: ${props.message.subject || ''}`,
|
||||
|
|
@ -126,6 +136,7 @@ const replyAll = () => {
|
|||
].filter((a: string) => a && a !== store.currentMailbox)
|
||||
store.composeDefaults = {
|
||||
to: allAddresses.join(', '),
|
||||
from: props.message.received_via_alias || undefined,
|
||||
subject: props.message.subject?.startsWith('Re:')
|
||||
? props.message.subject
|
||||
: `Re: ${props.message.subject || ''}`,
|
||||
|
|
@ -495,10 +506,16 @@ const sendInlineReply = async () => {
|
|||
<Avatar :initials="message.from_name?.[0] || message.from_address?.[0] || '?'" />
|
||||
<div class="grid gap-1">
|
||||
<div class="font-semibold">{{ message.from_name || message.from_address }}</div>
|
||||
<div class="line-clamp-1 text-xs">{{ message.subject }}</div>
|
||||
<div class="line-clamp-1 text-xs">
|
||||
<span class="font-medium">Reply-To:</span> {{ message.from_address }}
|
||||
<div v-if="toDisplay" class="line-clamp-1 text-xs">
|
||||
<span class="font-medium">To:</span> {{ toDisplay }}
|
||||
</div>
|
||||
<div v-if="ccDisplay" class="line-clamp-1 text-xs">
|
||||
<span class="font-medium">Cc:</span> {{ ccDisplay }}
|
||||
</div>
|
||||
<div v-if="bccDisplay" class="line-clamp-1 text-xs">
|
||||
<span class="font-medium">Bcc:</span> {{ bccDisplay }}
|
||||
</div>
|
||||
<div class="line-clamp-1 text-xs">{{ message.subject }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="displayTimestamp" class="ml-auto text-xs text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -65,6 +65,19 @@ watch(emailIframe, (el) => {
|
|||
resizeObserver.observe(el.parentElement);
|
||||
});
|
||||
onUnmounted(() => resizeObserver?.disconnect());
|
||||
const parseAddrs = (raw) => {
|
||||
let addrs = [];
|
||||
try {
|
||||
addrs = JSON.parse(raw || '[]');
|
||||
}
|
||||
catch {
|
||||
addrs = [];
|
||||
}
|
||||
return addrs.map((a) => { const m = a.match(/<([^>]+)>/); return m ? m[1] : a; }).join(', ');
|
||||
};
|
||||
const toDisplay = computed(() => parseAddrs(props.message?.to_addresses));
|
||||
const ccDisplay = computed(() => parseAddrs(props.message?.cc_addresses));
|
||||
const bccDisplay = computed(() => parseAddrs(props.message?.bcc_addresses));
|
||||
const displayTimestamp = computed(() => {
|
||||
const ts = props.message?.received_at || props.message?.sent_at;
|
||||
return ts ? format(new Date(ts), 'PPpp') : '';
|
||||
|
|
@ -85,6 +98,7 @@ const replyTo = () => {
|
|||
return;
|
||||
store.composeDefaults = {
|
||||
to: props.message.from_address,
|
||||
from: props.message.received_via_alias || undefined,
|
||||
subject: props.message.subject?.startsWith('Re:')
|
||||
? props.message.subject
|
||||
: `Re: ${props.message.subject || ''}`,
|
||||
|
|
@ -96,13 +110,28 @@ const replyTo = () => {
|
|||
const replyAll = () => {
|
||||
if (!props.message)
|
||||
return;
|
||||
let toList = [];
|
||||
let ccList = [];
|
||||
try {
|
||||
toList = JSON.parse(props.message.to_addresses || '[]');
|
||||
}
|
||||
catch {
|
||||
toList = [];
|
||||
}
|
||||
try {
|
||||
ccList = JSON.parse(props.message.cc_addresses || '[]');
|
||||
}
|
||||
catch {
|
||||
ccList = [];
|
||||
}
|
||||
const allAddresses = [
|
||||
props.message.from_address,
|
||||
...(JSON.parse(props.message.to_addresses || '[]')),
|
||||
...(JSON.parse(props.message.cc_addresses || '[]')),
|
||||
...toList,
|
||||
...ccList,
|
||||
].filter((a) => a && a !== store.currentMailbox);
|
||||
store.composeDefaults = {
|
||||
to: allAddresses.join(', '),
|
||||
from: props.message.received_via_alias || undefined,
|
||||
subject: props.message.subject?.startsWith('Re:')
|
||||
? props.message.subject
|
||||
: `Re: ${props.message.subject || ''}`,
|
||||
|
|
@ -134,9 +163,11 @@ const trash = async () => {
|
|||
await mailApi.deleteMessage(store.currentMailbox, props.message.id);
|
||||
store.messages = store.messages.filter((m) => m.id !== props.message.id);
|
||||
store.currentMessage = null;
|
||||
const fRes = await mailApi.getFolders(store.currentMailbox);
|
||||
store.folders = fRes.data;
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to trash message', e);
|
||||
catch {
|
||||
store.showToast('Failed to move message to trash');
|
||||
}
|
||||
};
|
||||
const markUnread = async () => {
|
||||
|
|
@ -147,10 +178,28 @@ const markUnread = async () => {
|
|||
const idx = store.messages.findIndex((m) => m.id === props.message.id);
|
||||
if (idx !== -1)
|
||||
store.messages[idx] = { ...store.messages[idx], is_read: 0 };
|
||||
store.currentMessage = null;
|
||||
store.currentMessage = { ...store.currentMessage, is_read: 0 };
|
||||
const fRes = await mailApi.getFolders(store.currentMailbox);
|
||||
store.folders = fRes.data;
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to mark unread', e);
|
||||
catch {
|
||||
store.showToast('Failed to mark as unread');
|
||||
}
|
||||
};
|
||||
const markRead = async () => {
|
||||
if (!props.message || !store.currentMailbox)
|
||||
return;
|
||||
try {
|
||||
await mailApi.updateMessage(store.currentMailbox, props.message.id, { is_read: true });
|
||||
const idx = store.messages.findIndex((m) => m.id === props.message.id);
|
||||
if (idx !== -1)
|
||||
store.messages[idx] = { ...store.messages[idx], is_read: 1 };
|
||||
store.currentMessage = { ...store.currentMessage, is_read: 1 };
|
||||
const fRes = await mailApi.getFolders(store.currentMailbox);
|
||||
store.folders = fRes.data;
|
||||
}
|
||||
catch {
|
||||
store.showToast('Failed to mark as read');
|
||||
}
|
||||
};
|
||||
const toggleStar = async () => {
|
||||
|
|
@ -165,8 +214,8 @@ const toggleStar = async () => {
|
|||
if (store.currentMessage)
|
||||
store.currentMessage = { ...store.currentMessage, is_starred: newVal };
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to toggle star', e);
|
||||
catch {
|
||||
store.showToast('Failed to update star');
|
||||
}
|
||||
};
|
||||
const moveToFolder = async (targetFolder) => {
|
||||
|
|
@ -179,17 +228,26 @@ const moveToFolder = async (targetFolder) => {
|
|||
});
|
||||
store.messages = store.messages.filter((m) => m.id !== props.message.id);
|
||||
store.currentMessage = null;
|
||||
const fRes = await mailApi.getFolders(store.currentMailbox);
|
||||
store.folders = fRes.data;
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to move message', e);
|
||||
catch {
|
||||
store.showToast('Failed to move message');
|
||||
}
|
||||
};
|
||||
const escapeHtml = (str) => str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const printMessage = () => {
|
||||
if (!props.message)
|
||||
return;
|
||||
const from = props.message.from_name ? `${props.message.from_name} <${props.message.from_address}>` : props.message.from_address;
|
||||
const toRaw = JSON.parse(props.message.to_addresses || '[]');
|
||||
const to = Array.isArray(toRaw) ? toRaw.join(', ') : toRaw;
|
||||
let toRaw = [];
|
||||
try {
|
||||
toRaw = JSON.parse(props.message.to_addresses || '[]');
|
||||
}
|
||||
catch {
|
||||
toRaw = [];
|
||||
}
|
||||
const to = Array.isArray(toRaw) ? toRaw.join(', ') : String(toRaw);
|
||||
const date = displayTimestamp.value;
|
||||
const subject = props.message.subject || '(No Subject)';
|
||||
let content = '';
|
||||
|
|
@ -225,9 +283,9 @@ const printMessage = () => {
|
|||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1 class="subject">${subject}</h1>
|
||||
<div class="meta"><div class="label">From:</div><div class="val">${from.replace(/</g, '<').replace(/>/g, '>')}</div></div>
|
||||
<div class="meta"><div class="label">To:</div><div class="val">${to.replace(/</g, '<').replace(/>/g, '>')}</div></div>
|
||||
<h1 class="subject">${escapeHtml(subject)}</h1>
|
||||
<div class="meta"><div class="label">From:</div><div class="val">${escapeHtml(from)}</div></div>
|
||||
<div class="meta"><div class="label">To:</div><div class="val">${escapeHtml(to)}</div></div>
|
||||
<div class="meta"><div class="label">Date:</div><div class="val">${date}</div></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
|
|
@ -248,6 +306,8 @@ const printMessage = () => {
|
|||
const sendInlineReply = async () => {
|
||||
if (!props.message || !store.currentMailbox || !replyText.value.trim())
|
||||
return;
|
||||
if (!props.message.from_address)
|
||||
return;
|
||||
sendingReply.value = true;
|
||||
try {
|
||||
await mailApi.sendMessage(store.currentMailbox, {
|
||||
|
|
@ -261,8 +321,8 @@ const sendInlineReply = async () => {
|
|||
});
|
||||
replyText.value = '';
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to send reply', e);
|
||||
catch {
|
||||
store.showToast('Failed to send reply');
|
||||
}
|
||||
finally {
|
||||
sendingReply.value = false;
|
||||
|
|
@ -882,137 +942,169 @@ const __VLS_223 = __VLS_222({
|
|||
...{ class: "z-50 min-w-[160px] rounded-md border bg-popover p-1 text-popover-foreground shadow-md" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_222));
|
||||
__VLS_224.slots.default;
|
||||
const __VLS_225 = {}.DropdownMenuItem;
|
||||
if (props.message?.is_read) {
|
||||
const __VLS_225 = {}.DropdownMenuItem;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuItem, typeof __VLS_components.DropdownMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_226 = __VLS_asFunctionalComponent(__VLS_225, new __VLS_225({
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}));
|
||||
const __VLS_227 = __VLS_226({
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_226));
|
||||
let __VLS_229;
|
||||
let __VLS_230;
|
||||
let __VLS_231;
|
||||
const __VLS_232 = {
|
||||
onClick: (__VLS_ctx.markUnread)
|
||||
};
|
||||
__VLS_228.slots.default;
|
||||
const __VLS_233 = {}.MailOpen;
|
||||
/** @type {[typeof __VLS_components.MailOpen, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_234 = __VLS_asFunctionalComponent(__VLS_233, new __VLS_233({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}));
|
||||
const __VLS_235 = __VLS_234({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_234));
|
||||
var __VLS_228;
|
||||
}
|
||||
else {
|
||||
const __VLS_237 = {}.DropdownMenuItem;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuItem, typeof __VLS_components.DropdownMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_238 = __VLS_asFunctionalComponent(__VLS_237, new __VLS_237({
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}));
|
||||
const __VLS_239 = __VLS_238({
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_238));
|
||||
let __VLS_241;
|
||||
let __VLS_242;
|
||||
let __VLS_243;
|
||||
const __VLS_244 = {
|
||||
onClick: (__VLS_ctx.markRead)
|
||||
};
|
||||
__VLS_240.slots.default;
|
||||
const __VLS_245 = {}.MailOpen;
|
||||
/** @type {[typeof __VLS_components.MailOpen, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_246 = __VLS_asFunctionalComponent(__VLS_245, new __VLS_245({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}));
|
||||
const __VLS_247 = __VLS_246({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_246));
|
||||
var __VLS_240;
|
||||
}
|
||||
const __VLS_249 = {}.DropdownMenuItem;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuItem, typeof __VLS_components.DropdownMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_226 = __VLS_asFunctionalComponent(__VLS_225, new __VLS_225({
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}));
|
||||
const __VLS_227 = __VLS_226({
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_226));
|
||||
let __VLS_229;
|
||||
let __VLS_230;
|
||||
let __VLS_231;
|
||||
const __VLS_232 = {
|
||||
onClick: (__VLS_ctx.markUnread)
|
||||
};
|
||||
__VLS_228.slots.default;
|
||||
const __VLS_233 = {}.MailOpen;
|
||||
/** @type {[typeof __VLS_components.MailOpen, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_234 = __VLS_asFunctionalComponent(__VLS_233, new __VLS_233({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}));
|
||||
const __VLS_235 = __VLS_234({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_234));
|
||||
var __VLS_228;
|
||||
const __VLS_237 = {}.DropdownMenuItem;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuItem, typeof __VLS_components.DropdownMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_238 = __VLS_asFunctionalComponent(__VLS_237, new __VLS_237({
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}));
|
||||
const __VLS_239 = __VLS_238({
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_238));
|
||||
let __VLS_241;
|
||||
let __VLS_242;
|
||||
let __VLS_243;
|
||||
const __VLS_244 = {
|
||||
onClick: (__VLS_ctx.toggleStar)
|
||||
};
|
||||
__VLS_240.slots.default;
|
||||
const __VLS_245 = {}.Star;
|
||||
/** @type {[typeof __VLS_components.Star, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_246 = __VLS_asFunctionalComponent(__VLS_245, new __VLS_245({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}));
|
||||
const __VLS_247 = __VLS_246({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_246));
|
||||
(__VLS_ctx.message?.is_starred ? 'Unstar' : 'Star');
|
||||
var __VLS_240;
|
||||
const __VLS_249 = {}.DropdownMenuSeparator;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuSeparator, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_250 = __VLS_asFunctionalComponent(__VLS_249, new __VLS_249({
|
||||
...{ class: "my-1 h-px bg-border" },
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}));
|
||||
const __VLS_251 = __VLS_250({
|
||||
...{ class: "my-1 h-px bg-border" },
|
||||
...{ 'onClick': {} },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_250));
|
||||
const __VLS_253 = {}.DropdownMenuSub;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuSub, typeof __VLS_components.DropdownMenuSub, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_254 = __VLS_asFunctionalComponent(__VLS_253, new __VLS_253({}));
|
||||
const __VLS_255 = __VLS_254({}, ...__VLS_functionalComponentArgsRest(__VLS_254));
|
||||
__VLS_256.slots.default;
|
||||
const __VLS_257 = {}.DropdownMenuSubTrigger;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuSubTrigger, typeof __VLS_components.DropdownMenuSubTrigger, ]} */ ;
|
||||
let __VLS_253;
|
||||
let __VLS_254;
|
||||
let __VLS_255;
|
||||
const __VLS_256 = {
|
||||
onClick: (__VLS_ctx.toggleStar)
|
||||
};
|
||||
__VLS_252.slots.default;
|
||||
const __VLS_257 = {}.Star;
|
||||
/** @type {[typeof __VLS_components.Star, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_258 = __VLS_asFunctionalComponent(__VLS_257, new __VLS_257({
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
...{ class: "mr-2 size-4" },
|
||||
}));
|
||||
const __VLS_259 = __VLS_258({
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
...{ class: "mr-2 size-4" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_258));
|
||||
__VLS_260.slots.default;
|
||||
const __VLS_261 = {}.FolderInput;
|
||||
/** @type {[typeof __VLS_components.FolderInput, ]} */ ;
|
||||
(__VLS_ctx.message?.is_starred ? 'Unstar' : 'Star');
|
||||
var __VLS_252;
|
||||
const __VLS_261 = {}.DropdownMenuSeparator;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuSeparator, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_262 = __VLS_asFunctionalComponent(__VLS_261, new __VLS_261({
|
||||
...{ class: "mr-2 size-4" },
|
||||
...{ class: "my-1 h-px bg-border" },
|
||||
}));
|
||||
const __VLS_263 = __VLS_262({
|
||||
...{ class: "mr-2 size-4" },
|
||||
...{ class: "my-1 h-px bg-border" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_262));
|
||||
var __VLS_260;
|
||||
const __VLS_265 = {}.DropdownMenuPortal;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuPortal, typeof __VLS_components.DropdownMenuPortal, ]} */ ;
|
||||
const __VLS_265 = {}.DropdownMenuSub;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuSub, typeof __VLS_components.DropdownMenuSub, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_266 = __VLS_asFunctionalComponent(__VLS_265, new __VLS_265({}));
|
||||
const __VLS_267 = __VLS_266({}, ...__VLS_functionalComponentArgsRest(__VLS_266));
|
||||
__VLS_268.slots.default;
|
||||
const __VLS_269 = {}.DropdownMenuSubContent;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuSubContent, typeof __VLS_components.DropdownMenuSubContent, ]} */ ;
|
||||
const __VLS_269 = {}.DropdownMenuSubTrigger;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuSubTrigger, typeof __VLS_components.DropdownMenuSubTrigger, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_270 = __VLS_asFunctionalComponent(__VLS_269, new __VLS_269({
|
||||
...{ class: "z-50 min-w-[140px] rounded-md border bg-popover p-1 text-popover-foreground shadow-md" },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}));
|
||||
const __VLS_271 = __VLS_270({
|
||||
...{ class: "z-50 min-w-[140px] rounded-md border bg-popover p-1 text-popover-foreground shadow-md" },
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_270));
|
||||
__VLS_272.slots.default;
|
||||
const __VLS_273 = {}.FolderInput;
|
||||
/** @type {[typeof __VLS_components.FolderInput, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_274 = __VLS_asFunctionalComponent(__VLS_273, new __VLS_273({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}));
|
||||
const __VLS_275 = __VLS_274({
|
||||
...{ class: "mr-2 size-4" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_274));
|
||||
var __VLS_272;
|
||||
const __VLS_277 = {}.DropdownMenuPortal;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuPortal, typeof __VLS_components.DropdownMenuPortal, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_278 = __VLS_asFunctionalComponent(__VLS_277, new __VLS_277({}));
|
||||
const __VLS_279 = __VLS_278({}, ...__VLS_functionalComponentArgsRest(__VLS_278));
|
||||
__VLS_280.slots.default;
|
||||
const __VLS_281 = {}.DropdownMenuSubContent;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuSubContent, typeof __VLS_components.DropdownMenuSubContent, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_282 = __VLS_asFunctionalComponent(__VLS_281, new __VLS_281({
|
||||
...{ class: "z-50 min-w-[140px] rounded-md border bg-popover p-1 text-popover-foreground shadow-md" },
|
||||
}));
|
||||
const __VLS_283 = __VLS_282({
|
||||
...{ class: "z-50 min-w-[140px] rounded-md border bg-popover p-1 text-popover-foreground shadow-md" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_282));
|
||||
__VLS_284.slots.default;
|
||||
for (const [f] of __VLS_getVForSourceType((__VLS_ctx.otherFolders))) {
|
||||
const __VLS_273 = {}.DropdownMenuItem;
|
||||
const __VLS_285 = {}.DropdownMenuItem;
|
||||
/** @type {[typeof __VLS_components.DropdownMenuItem, typeof __VLS_components.DropdownMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_274 = __VLS_asFunctionalComponent(__VLS_273, new __VLS_273({
|
||||
const __VLS_286 = __VLS_asFunctionalComponent(__VLS_285, new __VLS_285({
|
||||
...{ 'onClick': {} },
|
||||
key: (f.id),
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}));
|
||||
const __VLS_275 = __VLS_274({
|
||||
const __VLS_287 = __VLS_286({
|
||||
...{ 'onClick': {} },
|
||||
key: (f.id),
|
||||
...{ class: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_274));
|
||||
let __VLS_277;
|
||||
let __VLS_278;
|
||||
let __VLS_279;
|
||||
const __VLS_280 = {
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_286));
|
||||
let __VLS_289;
|
||||
let __VLS_290;
|
||||
let __VLS_291;
|
||||
const __VLS_292 = {
|
||||
onClick: (...[$event]) => {
|
||||
__VLS_ctx.moveToFolder(f);
|
||||
}
|
||||
};
|
||||
__VLS_276.slots.default;
|
||||
__VLS_288.slots.default;
|
||||
if (f.color) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span)({
|
||||
...{ class: "mr-2 inline-block h-2 w-2 rounded-full flex-shrink-0" },
|
||||
|
|
@ -1020,11 +1112,11 @@ for (const [f] of __VLS_getVForSourceType((__VLS_ctx.otherFolders))) {
|
|||
});
|
||||
}
|
||||
(f.name);
|
||||
var __VLS_276;
|
||||
var __VLS_288;
|
||||
}
|
||||
var __VLS_272;
|
||||
var __VLS_284;
|
||||
var __VLS_280;
|
||||
var __VLS_268;
|
||||
var __VLS_256;
|
||||
var __VLS_224;
|
||||
var __VLS_220;
|
||||
var __VLS_205;
|
||||
|
|
@ -1052,12 +1144,12 @@ if (__VLS_ctx.message) {
|
|||
});
|
||||
/** @type {[typeof Avatar, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_281 = __VLS_asFunctionalComponent(Avatar, new Avatar({
|
||||
const __VLS_293 = __VLS_asFunctionalComponent(Avatar, new Avatar({
|
||||
initials: (__VLS_ctx.message.from_name?.[0] || __VLS_ctx.message.from_address?.[0] || '?'),
|
||||
}));
|
||||
const __VLS_282 = __VLS_281({
|
||||
const __VLS_294 = __VLS_293({
|
||||
initials: (__VLS_ctx.message.from_name?.[0] || __VLS_ctx.message.from_address?.[0] || '?'),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_281));
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_293));
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "grid gap-1" },
|
||||
});
|
||||
|
|
@ -1065,17 +1157,37 @@ if (__VLS_ctx.message) {
|
|||
...{ class: "font-semibold" },
|
||||
});
|
||||
(__VLS_ctx.message.from_name || __VLS_ctx.message.from_address);
|
||||
if (__VLS_ctx.toDisplay) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "line-clamp-1 text-xs" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "font-medium" },
|
||||
});
|
||||
(__VLS_ctx.toDisplay);
|
||||
}
|
||||
if (__VLS_ctx.ccDisplay) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "line-clamp-1 text-xs" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "font-medium" },
|
||||
});
|
||||
(__VLS_ctx.ccDisplay);
|
||||
}
|
||||
if (__VLS_ctx.bccDisplay) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "line-clamp-1 text-xs" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "font-medium" },
|
||||
});
|
||||
(__VLS_ctx.bccDisplay);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "line-clamp-1 text-xs" },
|
||||
});
|
||||
(__VLS_ctx.message.subject);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "line-clamp-1 text-xs" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "font-medium" },
|
||||
});
|
||||
(__VLS_ctx.message.from_address);
|
||||
if (__VLS_ctx.displayTimestamp) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "ml-auto text-xs text-muted-foreground" },
|
||||
|
|
@ -1084,8 +1196,8 @@ if (__VLS_ctx.message) {
|
|||
}
|
||||
/** @type {[typeof Separator, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_284 = __VLS_asFunctionalComponent(Separator, new Separator({}));
|
||||
const __VLS_285 = __VLS_284({}, ...__VLS_functionalComponentArgsRest(__VLS_284));
|
||||
const __VLS_296 = __VLS_asFunctionalComponent(Separator, new Separator({}));
|
||||
const __VLS_297 = __VLS_296({}, ...__VLS_functionalComponentArgsRest(__VLS_296));
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex-1 overflow-y-auto" },
|
||||
});
|
||||
|
|
@ -1109,20 +1221,20 @@ if (__VLS_ctx.message) {
|
|||
}
|
||||
/** @type {[typeof AttachmentBar, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_287 = __VLS_asFunctionalComponent(AttachmentBar, new AttachmentBar({
|
||||
const __VLS_299 = __VLS_asFunctionalComponent(AttachmentBar, new AttachmentBar({
|
||||
attachments: (__VLS_ctx.message.attachments),
|
||||
}));
|
||||
const __VLS_288 = __VLS_287({
|
||||
const __VLS_300 = __VLS_299({
|
||||
attachments: (__VLS_ctx.message.attachments),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_287));
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_299));
|
||||
/** @type {[typeof Separator, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_290 = __VLS_asFunctionalComponent(Separator, new Separator({
|
||||
const __VLS_302 = __VLS_asFunctionalComponent(Separator, new Separator({
|
||||
...{ class: "mt-auto print-hide" },
|
||||
}));
|
||||
const __VLS_291 = __VLS_290({
|
||||
const __VLS_303 = __VLS_302({
|
||||
...{ class: "mt-auto print-hide" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_290));
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_302));
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-4 print-hide" },
|
||||
});
|
||||
|
|
@ -1134,36 +1246,36 @@ if (__VLS_ctx.message) {
|
|||
});
|
||||
/** @type {[typeof Textarea, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_293 = __VLS_asFunctionalComponent(Textarea, new Textarea({
|
||||
const __VLS_305 = __VLS_asFunctionalComponent(Textarea, new Textarea({
|
||||
modelValue: (__VLS_ctx.replyText),
|
||||
...{ class: "p-4 min-h-[100px]" },
|
||||
placeholder: (`Reply ${__VLS_ctx.message.from_name || __VLS_ctx.message.from_address}...`),
|
||||
}));
|
||||
const __VLS_294 = __VLS_293({
|
||||
const __VLS_306 = __VLS_305({
|
||||
modelValue: (__VLS_ctx.replyText),
|
||||
...{ class: "p-4 min-h-[100px]" },
|
||||
placeholder: (`Reply ${__VLS_ctx.message.from_name || __VLS_ctx.message.from_address}...`),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_293));
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_305));
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center" },
|
||||
});
|
||||
/** @type {[typeof Button, typeof Button, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_296 = __VLS_asFunctionalComponent(Button, new Button({
|
||||
const __VLS_308 = __VLS_asFunctionalComponent(Button, new Button({
|
||||
type: "submit",
|
||||
size: "sm",
|
||||
...{ class: "ml-auto" },
|
||||
disabled: (__VLS_ctx.sendingReply || !__VLS_ctx.replyText.trim()),
|
||||
}));
|
||||
const __VLS_297 = __VLS_296({
|
||||
const __VLS_309 = __VLS_308({
|
||||
type: "submit",
|
||||
size: "sm",
|
||||
...{ class: "ml-auto" },
|
||||
disabled: (__VLS_ctx.sendingReply || !__VLS_ctx.replyText.trim()),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_296));
|
||||
__VLS_298.slots.default;
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_308));
|
||||
__VLS_310.slots.default;
|
||||
(__VLS_ctx.sendingReply ? 'Sending...' : 'Send');
|
||||
var __VLS_298;
|
||||
var __VLS_310;
|
||||
}
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
|
|
@ -1298,6 +1410,19 @@ else {
|
|||
/** @type {__VLS_StyleScopedClasses['hover:bg-accent']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mr-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['size-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['relative']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['select-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-1.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-accent']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mr-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['size-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['my-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-px']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-border']} */ ;
|
||||
|
|
@ -1363,9 +1488,15 @@ else {
|
|||
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['line-clamp-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['line-clamp-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['line-clamp-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['line-clamp-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['ml-auto']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
|
|
@ -1434,6 +1565,9 @@ const __VLS_self = (await import('vue')).defineComponent({
|
|||
emailIframe: emailIframe,
|
||||
safeHtml: safeHtml,
|
||||
resizeIframe: resizeIframe,
|
||||
toDisplay: toDisplay,
|
||||
ccDisplay: ccDisplay,
|
||||
bccDisplay: bccDisplay,
|
||||
displayTimestamp: displayTimestamp,
|
||||
otherFolders: otherFolders,
|
||||
replyTo: replyTo,
|
||||
|
|
@ -1442,6 +1576,7 @@ const __VLS_self = (await import('vue')).defineComponent({
|
|||
backToList: backToList,
|
||||
trash: trash,
|
||||
markUnread: markUnread,
|
||||
markRead: markRead,
|
||||
toggleStar: toggleStar,
|
||||
moveToFolder: moveToFolder,
|
||||
printMessage: printMessage,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,31 +1,21 @@
|
|||
/// <reference types="../../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
|
||||
import { ref, computed } from 'vue';
|
||||
import { Search, ArrowDownUp, Trash2 } from 'lucide-vue-next';
|
||||
import { ArrowDownUp, Trash2 } from 'lucide-vue-next';
|
||||
import { TabsRoot, TabsList, TabsTrigger, TabsContent, } from 'radix-vue';
|
||||
import { ScrollAreaRoot, ScrollAreaViewport, ScrollAreaScrollbar, ScrollAreaThumb, } from 'radix-vue';
|
||||
import { useMailStore } from '../../stores/mail';
|
||||
import { mailApi } from '../../api/mail';
|
||||
import MessageListItem from './MessageListItem.vue';
|
||||
import Input from '../ui/Input.vue';
|
||||
import SearchBar from './SearchBar.vue';
|
||||
import Dialog from '../ui/Dialog.vue';
|
||||
import Button from '../ui/Button.vue';
|
||||
const store = useMailStore();
|
||||
const searchValue = ref('');
|
||||
const showTrashConfirm = ref(false);
|
||||
const folderColor = computed(() => store.currentFolderObj?.color || '');
|
||||
const filteredMessages = computed(() => {
|
||||
const q = searchValue.value.trim().toLowerCase();
|
||||
if (!q)
|
||||
return store.messages;
|
||||
return store.messages.filter((m) => (m.from_name || '').toLowerCase().includes(q) ||
|
||||
(m.from_address || '').toLowerCase().includes(q) ||
|
||||
(m.subject || '').toLowerCase().includes(q) ||
|
||||
(m.text_body || '').toLowerCase().includes(q));
|
||||
});
|
||||
const unreadMessages = computed(() => filteredMessages.value.filter((m) => !m.is_read));
|
||||
const starredMessages = computed(() => filteredMessages.value.filter((m) => m.is_starred));
|
||||
const unreadMessages = computed(() => store.messages.filter((m) => !m.is_read));
|
||||
const starredMessages = computed(() => store.messages.filter((m) => m.is_starred));
|
||||
const displayMessages = computed(() => {
|
||||
let msgs = filteredMessages.value;
|
||||
let msgs = store.messages;
|
||||
if (store.activeTab === 'unread')
|
||||
msgs = unreadMessages.value;
|
||||
else if (store.activeTab === 'starred')
|
||||
|
|
@ -68,17 +58,20 @@ const emptyTrash = () => {
|
|||
}
|
||||
};
|
||||
const performEmptyTrash = async () => {
|
||||
if (store.currentFolderObj) {
|
||||
try {
|
||||
await mailApi.emptyFolder(store.currentMailbox, store.currentFolderObj.id);
|
||||
store.messages = [];
|
||||
store.currentMessage = null;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
finally {
|
||||
showTrashConfirm.value = false;
|
||||
}
|
||||
if (!store.currentFolderObj)
|
||||
return;
|
||||
try {
|
||||
await mailApi.emptyFolder(store.currentMailbox, store.currentFolderObj.id);
|
||||
store.messages = [];
|
||||
store.currentMessage = null;
|
||||
const fRes = await mailApi.getFolders(store.currentMailbox);
|
||||
store.folders = fRes.data;
|
||||
}
|
||||
catch {
|
||||
store.showToast('Failed to empty trash');
|
||||
}
|
||||
finally {
|
||||
showTrashConfirm.value = false;
|
||||
}
|
||||
};
|
||||
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
|
||||
|
|
@ -195,44 +188,34 @@ var __VLS_15;
|
|||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "relative" },
|
||||
});
|
||||
const __VLS_28 = {}.Search;
|
||||
/** @type {[typeof __VLS_components.Search, ]} */ ;
|
||||
/** @type {[typeof SearchBar, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_29 = __VLS_asFunctionalComponent(__VLS_28, new __VLS_28({
|
||||
...{ class: "absolute left-2 top-2.5 size-4 text-muted-foreground" },
|
||||
}));
|
||||
const __VLS_30 = __VLS_29({
|
||||
...{ class: "absolute left-2 top-2.5 size-4 text-muted-foreground" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_29));
|
||||
/** @type {[typeof Input, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_32 = __VLS_asFunctionalComponent(Input, new Input({
|
||||
modelValue: (__VLS_ctx.searchValue),
|
||||
placeholder: "Search",
|
||||
...{ class: "pl-8" },
|
||||
}));
|
||||
const __VLS_33 = __VLS_32({
|
||||
modelValue: (__VLS_ctx.searchValue),
|
||||
placeholder: "Search",
|
||||
...{ class: "pl-8" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_32));
|
||||
const __VLS_35 = {}.TabsContent;
|
||||
const __VLS_28 = __VLS_asFunctionalComponent(SearchBar, new SearchBar({}));
|
||||
const __VLS_29 = __VLS_28({}, ...__VLS_functionalComponentArgsRest(__VLS_28));
|
||||
const __VLS_31 = {}.TabsContent;
|
||||
/** @type {[typeof __VLS_components.TabsContent, typeof __VLS_components.TabsContent, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_36 = __VLS_asFunctionalComponent(__VLS_35, new __VLS_35({
|
||||
const __VLS_32 = __VLS_asFunctionalComponent(__VLS_31, new __VLS_31({
|
||||
value: "all",
|
||||
...{ class: "m-0 flex-1 overflow-hidden" },
|
||||
}));
|
||||
const __VLS_37 = __VLS_36({
|
||||
const __VLS_33 = __VLS_32({
|
||||
value: "all",
|
||||
...{ class: "m-0 flex-1 overflow-hidden" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_32));
|
||||
__VLS_34.slots.default;
|
||||
const __VLS_35 = {}.ScrollAreaRoot;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaRoot, typeof __VLS_components.ScrollAreaRoot, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_36 = __VLS_asFunctionalComponent(__VLS_35, new __VLS_35({
|
||||
...{ class: "h-full" },
|
||||
}));
|
||||
const __VLS_37 = __VLS_36({
|
||||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_36));
|
||||
__VLS_38.slots.default;
|
||||
const __VLS_39 = {}.ScrollAreaRoot;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaRoot, typeof __VLS_components.ScrollAreaRoot, ]} */ ;
|
||||
const __VLS_39 = {}.ScrollAreaViewport;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaViewport, typeof __VLS_components.ScrollAreaViewport, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_40 = __VLS_asFunctionalComponent(__VLS_39, new __VLS_39({
|
||||
...{ class: "h-full" },
|
||||
|
|
@ -241,103 +224,129 @@ const __VLS_41 = __VLS_40({
|
|||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_40));
|
||||
__VLS_42.slots.default;
|
||||
const __VLS_43 = {}.ScrollAreaViewport;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaViewport, typeof __VLS_components.ScrollAreaViewport, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_44 = __VLS_asFunctionalComponent(__VLS_43, new __VLS_43({
|
||||
...{ class: "h-full" },
|
||||
}));
|
||||
const __VLS_45 = __VLS_44({
|
||||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_44));
|
||||
__VLS_46.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex flex-col gap-2 p-4 pt-0" },
|
||||
});
|
||||
const __VLS_47 = {}.TransitionGroup;
|
||||
/** @type {[typeof __VLS_components.TransitionGroup, typeof __VLS_components.TransitionGroup, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_48 = __VLS_asFunctionalComponent(__VLS_47, new __VLS_47({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}));
|
||||
const __VLS_49 = __VLS_48({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_48));
|
||||
__VLS_50.slots.default;
|
||||
for (const [msg] of __VLS_getVForSourceType((__VLS_ctx.displayMessages))) {
|
||||
/** @type {[typeof MessageListItem, ]} */ ;
|
||||
if (__VLS_ctx.store.messagesLoading) {
|
||||
for (const [n] of __VLS_getVForSourceType((6))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div)({
|
||||
key: (n),
|
||||
...{ class: "h-16 rounded-lg bg-muted animate-pulse" },
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
const __VLS_43 = {}.TransitionGroup;
|
||||
/** @type {[typeof __VLS_components.TransitionGroup, typeof __VLS_components.TransitionGroup, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_51 = __VLS_asFunctionalComponent(MessageListItem, new MessageListItem({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
const __VLS_44 = __VLS_asFunctionalComponent(__VLS_43, new __VLS_43({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}));
|
||||
const __VLS_52 = __VLS_51({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_51));
|
||||
let __VLS_54;
|
||||
let __VLS_55;
|
||||
let __VLS_56;
|
||||
const __VLS_57 = {
|
||||
onClick: (...[$event]) => {
|
||||
__VLS_ctx.selectMessage(msg);
|
||||
}
|
||||
};
|
||||
var __VLS_53;
|
||||
const __VLS_45 = __VLS_44({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_44));
|
||||
__VLS_46.slots.default;
|
||||
for (const [msg] of __VLS_getVForSourceType((__VLS_ctx.displayMessages))) {
|
||||
/** @type {[typeof MessageListItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_47 = __VLS_asFunctionalComponent(MessageListItem, new MessageListItem({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}));
|
||||
const __VLS_48 = __VLS_47({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_47));
|
||||
let __VLS_50;
|
||||
let __VLS_51;
|
||||
let __VLS_52;
|
||||
const __VLS_53 = {
|
||||
onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
__VLS_ctx.selectMessage(msg);
|
||||
}
|
||||
};
|
||||
var __VLS_49;
|
||||
}
|
||||
var __VLS_46;
|
||||
if (__VLS_ctx.displayMessages.length === 0) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-8 text-center text-muted-foreground" },
|
||||
});
|
||||
}
|
||||
if (__VLS_ctx.store.hasMoreMessages && !__VLS_ctx.store.messagesLoading) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
if (!(__VLS_ctx.store.hasMoreMessages && !__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
__VLS_ctx.store.loadMore();
|
||||
} },
|
||||
...{ class: "w-full py-2 text-sm text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50" },
|
||||
disabled: (__VLS_ctx.store.isFetchingNextPage),
|
||||
});
|
||||
(__VLS_ctx.store.isFetchingNextPage ? 'Loading…' : 'Load more');
|
||||
}
|
||||
}
|
||||
var __VLS_50;
|
||||
if (__VLS_ctx.displayMessages.length === 0) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-8 text-center text-muted-foreground" },
|
||||
});
|
||||
}
|
||||
var __VLS_46;
|
||||
const __VLS_58 = {}.ScrollAreaScrollbar;
|
||||
var __VLS_42;
|
||||
const __VLS_54 = {}.ScrollAreaScrollbar;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaScrollbar, typeof __VLS_components.ScrollAreaScrollbar, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_59 = __VLS_asFunctionalComponent(__VLS_58, new __VLS_58({
|
||||
const __VLS_55 = __VLS_asFunctionalComponent(__VLS_54, new __VLS_54({
|
||||
orientation: "vertical",
|
||||
...{ class: "flex touch-none select-none bg-transparent p-0.5 transition-colors w-2.5" },
|
||||
}));
|
||||
const __VLS_60 = __VLS_59({
|
||||
const __VLS_56 = __VLS_55({
|
||||
orientation: "vertical",
|
||||
...{ class: "flex touch-none select-none bg-transparent p-0.5 transition-colors w-2.5" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_59));
|
||||
__VLS_61.slots.default;
|
||||
const __VLS_62 = {}.ScrollAreaThumb;
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_55));
|
||||
__VLS_57.slots.default;
|
||||
const __VLS_58 = {}.ScrollAreaThumb;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaThumb, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_63 = __VLS_asFunctionalComponent(__VLS_62, new __VLS_62({
|
||||
const __VLS_59 = __VLS_asFunctionalComponent(__VLS_58, new __VLS_58({
|
||||
...{ class: "relative flex-1 rounded-full bg-border" },
|
||||
}));
|
||||
const __VLS_64 = __VLS_63({
|
||||
const __VLS_60 = __VLS_59({
|
||||
...{ class: "relative flex-1 rounded-full bg-border" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_63));
|
||||
var __VLS_61;
|
||||
var __VLS_42;
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_59));
|
||||
var __VLS_57;
|
||||
var __VLS_38;
|
||||
const __VLS_66 = {}.TabsContent;
|
||||
var __VLS_34;
|
||||
const __VLS_62 = {}.TabsContent;
|
||||
/** @type {[typeof __VLS_components.TabsContent, typeof __VLS_components.TabsContent, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_67 = __VLS_asFunctionalComponent(__VLS_66, new __VLS_66({
|
||||
const __VLS_63 = __VLS_asFunctionalComponent(__VLS_62, new __VLS_62({
|
||||
value: "unread",
|
||||
...{ class: "m-0 flex-1 overflow-hidden" },
|
||||
}));
|
||||
const __VLS_68 = __VLS_67({
|
||||
const __VLS_64 = __VLS_63({
|
||||
value: "unread",
|
||||
...{ class: "m-0 flex-1 overflow-hidden" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_63));
|
||||
__VLS_65.slots.default;
|
||||
const __VLS_66 = {}.ScrollAreaRoot;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaRoot, typeof __VLS_components.ScrollAreaRoot, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_67 = __VLS_asFunctionalComponent(__VLS_66, new __VLS_66({
|
||||
...{ class: "h-full" },
|
||||
}));
|
||||
const __VLS_68 = __VLS_67({
|
||||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_67));
|
||||
__VLS_69.slots.default;
|
||||
const __VLS_70 = {}.ScrollAreaRoot;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaRoot, typeof __VLS_components.ScrollAreaRoot, ]} */ ;
|
||||
const __VLS_70 = {}.ScrollAreaViewport;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaViewport, typeof __VLS_components.ScrollAreaViewport, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_71 = __VLS_asFunctionalComponent(__VLS_70, new __VLS_70({
|
||||
...{ class: "h-full" },
|
||||
|
|
@ -346,103 +355,129 @@ const __VLS_72 = __VLS_71({
|
|||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_71));
|
||||
__VLS_73.slots.default;
|
||||
const __VLS_74 = {}.ScrollAreaViewport;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaViewport, typeof __VLS_components.ScrollAreaViewport, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_75 = __VLS_asFunctionalComponent(__VLS_74, new __VLS_74({
|
||||
...{ class: "h-full" },
|
||||
}));
|
||||
const __VLS_76 = __VLS_75({
|
||||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_75));
|
||||
__VLS_77.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex flex-col gap-2 p-4 pt-0" },
|
||||
});
|
||||
const __VLS_78 = {}.TransitionGroup;
|
||||
/** @type {[typeof __VLS_components.TransitionGroup, typeof __VLS_components.TransitionGroup, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_79 = __VLS_asFunctionalComponent(__VLS_78, new __VLS_78({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}));
|
||||
const __VLS_80 = __VLS_79({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_79));
|
||||
__VLS_81.slots.default;
|
||||
for (const [msg] of __VLS_getVForSourceType((__VLS_ctx.displayMessages))) {
|
||||
/** @type {[typeof MessageListItem, ]} */ ;
|
||||
if (__VLS_ctx.store.messagesLoading) {
|
||||
for (const [n] of __VLS_getVForSourceType((6))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div)({
|
||||
key: (n),
|
||||
...{ class: "h-16 rounded-lg bg-muted animate-pulse" },
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
const __VLS_74 = {}.TransitionGroup;
|
||||
/** @type {[typeof __VLS_components.TransitionGroup, typeof __VLS_components.TransitionGroup, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_82 = __VLS_asFunctionalComponent(MessageListItem, new MessageListItem({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
const __VLS_75 = __VLS_asFunctionalComponent(__VLS_74, new __VLS_74({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}));
|
||||
const __VLS_83 = __VLS_82({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_82));
|
||||
let __VLS_85;
|
||||
let __VLS_86;
|
||||
let __VLS_87;
|
||||
const __VLS_88 = {
|
||||
onClick: (...[$event]) => {
|
||||
__VLS_ctx.selectMessage(msg);
|
||||
}
|
||||
};
|
||||
var __VLS_84;
|
||||
const __VLS_76 = __VLS_75({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_75));
|
||||
__VLS_77.slots.default;
|
||||
for (const [msg] of __VLS_getVForSourceType((__VLS_ctx.displayMessages))) {
|
||||
/** @type {[typeof MessageListItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_78 = __VLS_asFunctionalComponent(MessageListItem, new MessageListItem({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}));
|
||||
const __VLS_79 = __VLS_78({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_78));
|
||||
let __VLS_81;
|
||||
let __VLS_82;
|
||||
let __VLS_83;
|
||||
const __VLS_84 = {
|
||||
onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
__VLS_ctx.selectMessage(msg);
|
||||
}
|
||||
};
|
||||
var __VLS_80;
|
||||
}
|
||||
var __VLS_77;
|
||||
if (__VLS_ctx.displayMessages.length === 0) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-8 text-center text-muted-foreground" },
|
||||
});
|
||||
}
|
||||
if (__VLS_ctx.store.hasMoreMessages && !__VLS_ctx.store.messagesLoading) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
if (!(__VLS_ctx.store.hasMoreMessages && !__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
__VLS_ctx.store.loadMore();
|
||||
} },
|
||||
...{ class: "w-full py-2 text-sm text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50" },
|
||||
disabled: (__VLS_ctx.store.isFetchingNextPage),
|
||||
});
|
||||
(__VLS_ctx.store.isFetchingNextPage ? 'Loading…' : 'Load more');
|
||||
}
|
||||
}
|
||||
var __VLS_81;
|
||||
if (__VLS_ctx.displayMessages.length === 0) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-8 text-center text-muted-foreground" },
|
||||
});
|
||||
}
|
||||
var __VLS_77;
|
||||
const __VLS_89 = {}.ScrollAreaScrollbar;
|
||||
var __VLS_73;
|
||||
const __VLS_85 = {}.ScrollAreaScrollbar;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaScrollbar, typeof __VLS_components.ScrollAreaScrollbar, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_90 = __VLS_asFunctionalComponent(__VLS_89, new __VLS_89({
|
||||
const __VLS_86 = __VLS_asFunctionalComponent(__VLS_85, new __VLS_85({
|
||||
orientation: "vertical",
|
||||
...{ class: "flex touch-none select-none bg-transparent p-0.5 transition-colors w-2.5" },
|
||||
}));
|
||||
const __VLS_91 = __VLS_90({
|
||||
const __VLS_87 = __VLS_86({
|
||||
orientation: "vertical",
|
||||
...{ class: "flex touch-none select-none bg-transparent p-0.5 transition-colors w-2.5" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_90));
|
||||
__VLS_92.slots.default;
|
||||
const __VLS_93 = {}.ScrollAreaThumb;
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_86));
|
||||
__VLS_88.slots.default;
|
||||
const __VLS_89 = {}.ScrollAreaThumb;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaThumb, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_94 = __VLS_asFunctionalComponent(__VLS_93, new __VLS_93({
|
||||
const __VLS_90 = __VLS_asFunctionalComponent(__VLS_89, new __VLS_89({
|
||||
...{ class: "relative flex-1 rounded-full bg-border" },
|
||||
}));
|
||||
const __VLS_95 = __VLS_94({
|
||||
const __VLS_91 = __VLS_90({
|
||||
...{ class: "relative flex-1 rounded-full bg-border" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_94));
|
||||
var __VLS_92;
|
||||
var __VLS_73;
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_90));
|
||||
var __VLS_88;
|
||||
var __VLS_69;
|
||||
const __VLS_97 = {}.TabsContent;
|
||||
var __VLS_65;
|
||||
const __VLS_93 = {}.TabsContent;
|
||||
/** @type {[typeof __VLS_components.TabsContent, typeof __VLS_components.TabsContent, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_98 = __VLS_asFunctionalComponent(__VLS_97, new __VLS_97({
|
||||
const __VLS_94 = __VLS_asFunctionalComponent(__VLS_93, new __VLS_93({
|
||||
value: "starred",
|
||||
...{ class: "m-0 flex-1 overflow-hidden" },
|
||||
}));
|
||||
const __VLS_99 = __VLS_98({
|
||||
const __VLS_95 = __VLS_94({
|
||||
value: "starred",
|
||||
...{ class: "m-0 flex-1 overflow-hidden" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_94));
|
||||
__VLS_96.slots.default;
|
||||
const __VLS_97 = {}.ScrollAreaRoot;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaRoot, typeof __VLS_components.ScrollAreaRoot, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_98 = __VLS_asFunctionalComponent(__VLS_97, new __VLS_97({
|
||||
...{ class: "h-full" },
|
||||
}));
|
||||
const __VLS_99 = __VLS_98({
|
||||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_98));
|
||||
__VLS_100.slots.default;
|
||||
const __VLS_101 = {}.ScrollAreaRoot;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaRoot, typeof __VLS_components.ScrollAreaRoot, ]} */ ;
|
||||
const __VLS_101 = {}.ScrollAreaViewport;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaViewport, typeof __VLS_components.ScrollAreaViewport, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_102 = __VLS_asFunctionalComponent(__VLS_101, new __VLS_101({
|
||||
...{ class: "h-full" },
|
||||
|
|
@ -451,99 +486,115 @@ const __VLS_103 = __VLS_102({
|
|||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_102));
|
||||
__VLS_104.slots.default;
|
||||
const __VLS_105 = {}.ScrollAreaViewport;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaViewport, typeof __VLS_components.ScrollAreaViewport, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_106 = __VLS_asFunctionalComponent(__VLS_105, new __VLS_105({
|
||||
...{ class: "h-full" },
|
||||
}));
|
||||
const __VLS_107 = __VLS_106({
|
||||
...{ class: "h-full" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_106));
|
||||
__VLS_108.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex flex-col gap-2 p-4 pt-0" },
|
||||
});
|
||||
const __VLS_109 = {}.TransitionGroup;
|
||||
/** @type {[typeof __VLS_components.TransitionGroup, typeof __VLS_components.TransitionGroup, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_110 = __VLS_asFunctionalComponent(__VLS_109, new __VLS_109({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}));
|
||||
const __VLS_111 = __VLS_110({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_110));
|
||||
__VLS_112.slots.default;
|
||||
for (const [msg] of __VLS_getVForSourceType((__VLS_ctx.displayMessages))) {
|
||||
/** @type {[typeof MessageListItem, ]} */ ;
|
||||
if (__VLS_ctx.store.messagesLoading) {
|
||||
for (const [n] of __VLS_getVForSourceType((6))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div)({
|
||||
key: (n),
|
||||
...{ class: "h-16 rounded-lg bg-muted animate-pulse" },
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
const __VLS_105 = {}.TransitionGroup;
|
||||
/** @type {[typeof __VLS_components.TransitionGroup, typeof __VLS_components.TransitionGroup, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_113 = __VLS_asFunctionalComponent(MessageListItem, new MessageListItem({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
const __VLS_106 = __VLS_asFunctionalComponent(__VLS_105, new __VLS_105({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}));
|
||||
const __VLS_114 = __VLS_113({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_113));
|
||||
let __VLS_116;
|
||||
let __VLS_117;
|
||||
let __VLS_118;
|
||||
const __VLS_119 = {
|
||||
onClick: (...[$event]) => {
|
||||
__VLS_ctx.selectMessage(msg);
|
||||
}
|
||||
};
|
||||
var __VLS_115;
|
||||
const __VLS_107 = __VLS_106({
|
||||
name: "list",
|
||||
appear: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_106));
|
||||
__VLS_108.slots.default;
|
||||
for (const [msg] of __VLS_getVForSourceType((__VLS_ctx.displayMessages))) {
|
||||
/** @type {[typeof MessageListItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_109 = __VLS_asFunctionalComponent(MessageListItem, new MessageListItem({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}));
|
||||
const __VLS_110 = __VLS_109({
|
||||
...{ 'onClick': {} },
|
||||
key: (msg.id),
|
||||
message: (msg),
|
||||
selected: (__VLS_ctx.store.currentMessage?.id === msg.id),
|
||||
folderColor: (__VLS_ctx.folderColor),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_109));
|
||||
let __VLS_112;
|
||||
let __VLS_113;
|
||||
let __VLS_114;
|
||||
const __VLS_115 = {
|
||||
onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
__VLS_ctx.selectMessage(msg);
|
||||
}
|
||||
};
|
||||
var __VLS_111;
|
||||
}
|
||||
var __VLS_108;
|
||||
if (__VLS_ctx.displayMessages.length === 0) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-8 text-center text-muted-foreground" },
|
||||
});
|
||||
}
|
||||
if (__VLS_ctx.store.hasMoreMessages && !__VLS_ctx.store.messagesLoading) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
if (!(__VLS_ctx.store.hasMoreMessages && !__VLS_ctx.store.messagesLoading))
|
||||
return;
|
||||
__VLS_ctx.store.loadMore();
|
||||
} },
|
||||
...{ class: "w-full py-2 text-sm text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50" },
|
||||
disabled: (__VLS_ctx.store.isFetchingNextPage),
|
||||
});
|
||||
(__VLS_ctx.store.isFetchingNextPage ? 'Loading…' : 'Load more');
|
||||
}
|
||||
}
|
||||
var __VLS_112;
|
||||
if (__VLS_ctx.displayMessages.length === 0) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-8 text-center text-muted-foreground" },
|
||||
});
|
||||
}
|
||||
var __VLS_108;
|
||||
const __VLS_120 = {}.ScrollAreaScrollbar;
|
||||
var __VLS_104;
|
||||
const __VLS_116 = {}.ScrollAreaScrollbar;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaScrollbar, typeof __VLS_components.ScrollAreaScrollbar, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_121 = __VLS_asFunctionalComponent(__VLS_120, new __VLS_120({
|
||||
const __VLS_117 = __VLS_asFunctionalComponent(__VLS_116, new __VLS_116({
|
||||
orientation: "vertical",
|
||||
...{ class: "flex touch-none select-none bg-transparent p-0.5 transition-colors w-2.5" },
|
||||
}));
|
||||
const __VLS_122 = __VLS_121({
|
||||
const __VLS_118 = __VLS_117({
|
||||
orientation: "vertical",
|
||||
...{ class: "flex touch-none select-none bg-transparent p-0.5 transition-colors w-2.5" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_121));
|
||||
__VLS_123.slots.default;
|
||||
const __VLS_124 = {}.ScrollAreaThumb;
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_117));
|
||||
__VLS_119.slots.default;
|
||||
const __VLS_120 = {}.ScrollAreaThumb;
|
||||
/** @type {[typeof __VLS_components.ScrollAreaThumb, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_125 = __VLS_asFunctionalComponent(__VLS_124, new __VLS_124({
|
||||
const __VLS_121 = __VLS_asFunctionalComponent(__VLS_120, new __VLS_120({
|
||||
...{ class: "relative flex-1 rounded-full bg-border" },
|
||||
}));
|
||||
const __VLS_126 = __VLS_125({
|
||||
const __VLS_122 = __VLS_121({
|
||||
...{ class: "relative flex-1 rounded-full bg-border" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_125));
|
||||
var __VLS_123;
|
||||
var __VLS_104;
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_121));
|
||||
var __VLS_119;
|
||||
var __VLS_100;
|
||||
var __VLS_96;
|
||||
var __VLS_3;
|
||||
/** @type {[typeof Dialog, typeof Dialog, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_128 = __VLS_asFunctionalComponent(Dialog, new Dialog({
|
||||
const __VLS_124 = __VLS_asFunctionalComponent(Dialog, new Dialog({
|
||||
open: (__VLS_ctx.showTrashConfirm),
|
||||
}));
|
||||
const __VLS_129 = __VLS_128({
|
||||
const __VLS_125 = __VLS_124({
|
||||
open: (__VLS_ctx.showTrashConfirm),
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_128));
|
||||
__VLS_130.slots.default;
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_124));
|
||||
__VLS_126.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "space-y-4" },
|
||||
});
|
||||
|
|
@ -558,43 +609,43 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.d
|
|||
});
|
||||
/** @type {[typeof Button, typeof Button, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_131 = __VLS_asFunctionalComponent(Button, new Button({
|
||||
const __VLS_127 = __VLS_asFunctionalComponent(Button, new Button({
|
||||
...{ 'onClick': {} },
|
||||
variant: "outline",
|
||||
}));
|
||||
const __VLS_132 = __VLS_131({
|
||||
const __VLS_128 = __VLS_127({
|
||||
...{ 'onClick': {} },
|
||||
variant: "outline",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_131));
|
||||
let __VLS_134;
|
||||
let __VLS_135;
|
||||
let __VLS_136;
|
||||
const __VLS_137 = {
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_127));
|
||||
let __VLS_130;
|
||||
let __VLS_131;
|
||||
let __VLS_132;
|
||||
const __VLS_133 = {
|
||||
onClick: (...[$event]) => {
|
||||
__VLS_ctx.showTrashConfirm = false;
|
||||
}
|
||||
};
|
||||
__VLS_133.slots.default;
|
||||
var __VLS_133;
|
||||
__VLS_129.slots.default;
|
||||
var __VLS_129;
|
||||
/** @type {[typeof Button, typeof Button, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_138 = __VLS_asFunctionalComponent(Button, new Button({
|
||||
const __VLS_134 = __VLS_asFunctionalComponent(Button, new Button({
|
||||
...{ 'onClick': {} },
|
||||
variant: "destructive",
|
||||
}));
|
||||
const __VLS_139 = __VLS_138({
|
||||
const __VLS_135 = __VLS_134({
|
||||
...{ 'onClick': {} },
|
||||
variant: "destructive",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_138));
|
||||
let __VLS_141;
|
||||
let __VLS_142;
|
||||
let __VLS_143;
|
||||
const __VLS_144 = {
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_134));
|
||||
let __VLS_137;
|
||||
let __VLS_138;
|
||||
let __VLS_139;
|
||||
const __VLS_140 = {
|
||||
onClick: (__VLS_ctx.performEmptyTrash)
|
||||
};
|
||||
__VLS_140.slots.default;
|
||||
var __VLS_140;
|
||||
var __VLS_130;
|
||||
__VLS_136.slots.default;
|
||||
var __VLS_136;
|
||||
var __VLS_126;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
|
||||
|
|
@ -697,13 +748,6 @@ var __VLS_130;
|
|||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['backdrop-blur']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['supports-[backdrop-filter]:bg-background/60']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['relative']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['left-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['top-2.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['size-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['pl-8']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['m-0']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['overflow-hidden']} */ ;
|
||||
|
|
@ -714,9 +758,20 @@ var __VLS_130;
|
|||
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['pt-0']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-16']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['animate-pulse']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-8']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:text-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['disabled:opacity-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['touch-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['select-none']} */ ;
|
||||
|
|
@ -738,9 +793,20 @@ var __VLS_130;
|
|||
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['pt-0']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-16']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['animate-pulse']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-8']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:text-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['disabled:opacity-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['touch-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['select-none']} */ ;
|
||||
|
|
@ -762,9 +828,20 @@ var __VLS_130;
|
|||
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['pt-0']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-16']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['animate-pulse']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-8']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:text-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['disabled:opacity-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['touch-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['select-none']} */ ;
|
||||
|
|
@ -791,7 +868,6 @@ var __VLS_dollars;
|
|||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
Search: Search,
|
||||
ArrowDownUp: ArrowDownUp,
|
||||
Trash2: Trash2,
|
||||
TabsRoot: TabsRoot,
|
||||
|
|
@ -803,11 +879,10 @@ const __VLS_self = (await import('vue')).defineComponent({
|
|||
ScrollAreaScrollbar: ScrollAreaScrollbar,
|
||||
ScrollAreaThumb: ScrollAreaThumb,
|
||||
MessageListItem: MessageListItem,
|
||||
Input: Input,
|
||||
SearchBar: SearchBar,
|
||||
Dialog: Dialog,
|
||||
Button: Button,
|
||||
store: store,
|
||||
searchValue: searchValue,
|
||||
showTrashConfirm: showTrashConfirm,
|
||||
folderColor: folderColor,
|
||||
displayMessages: displayMessages,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -5,6 +5,7 @@ import { Paperclip, Star } from 'lucide-vue-next'
|
|||
import { TooltipRoot, TooltipTrigger, TooltipContent, TooltipPortal } from 'radix-vue'
|
||||
import { cn } from '../../lib/utils'
|
||||
import Badge from '../ui/Badge.vue'
|
||||
import { useMailStore } from '../../stores/mail'
|
||||
|
||||
const props = defineProps({
|
||||
message: { type: Object, required: true },
|
||||
|
|
@ -12,6 +13,8 @@ const props = defineProps({
|
|||
folderColor: { type: String, default: '' },
|
||||
})
|
||||
|
||||
const store = useMailStore()
|
||||
|
||||
const timestamp = computed(() => props.message.received_at || props.message.sent_at)
|
||||
const relativeTime = computed(() =>
|
||||
timestamp.value ? formatDistanceToNow(new Date(timestamp.value), { addSuffix: true }) : ''
|
||||
|
|
@ -19,6 +22,22 @@ const relativeTime = computed(() =>
|
|||
const exactTime = computed(() =>
|
||||
timestamp.value ? format(new Date(timestamp.value), 'PPpp') : ''
|
||||
)
|
||||
|
||||
const isSentOrDrafts = computed(() => {
|
||||
const name = store.currentFolderObj?.name?.toLowerCase() ?? ''
|
||||
return name === 'sent' || name === 'drafts'
|
||||
})
|
||||
|
||||
const recipientLabel = computed(() => {
|
||||
if (!isSentOrDrafts.value) return null
|
||||
let addrs: string[] = []
|
||||
try { addrs = JSON.parse(props.message.to_addresses || '[]') } catch { addrs = [] }
|
||||
if (!addrs.length) return null
|
||||
return 'To: ' + addrs.map((a: string) => {
|
||||
const m = a.match(/<([^>]+)>/)
|
||||
return m ? m[1] : a
|
||||
}).join(', ')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -32,7 +51,7 @@ const exactTime = computed(() =>
|
|||
<div class="flex w-full flex-col gap-1">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="font-semibold">{{ message.from_name || message.from_address }}</div>
|
||||
<div class="font-semibold">{{ recipientLabel ?? (message.from_name || message.from_address) }}</div>
|
||||
<span v-if="!message.is_read" class="flex h-2 w-2 rounded-full bg-primary" />
|
||||
<Star v-if="message.is_starred" class="size-3 fill-yellow-400 text-yellow-400" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,14 +5,37 @@ import { Paperclip, Star } from 'lucide-vue-next';
|
|||
import { TooltipRoot, TooltipTrigger, TooltipContent, TooltipPortal } from 'radix-vue';
|
||||
import { cn } from '../../lib/utils';
|
||||
import Badge from '../ui/Badge.vue';
|
||||
import { useMailStore } from '../../stores/mail';
|
||||
const props = defineProps({
|
||||
message: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
folderColor: { type: String, default: '' },
|
||||
});
|
||||
const store = useMailStore();
|
||||
const timestamp = computed(() => props.message.received_at || props.message.sent_at);
|
||||
const relativeTime = computed(() => timestamp.value ? formatDistanceToNow(new Date(timestamp.value), { addSuffix: true }) : '');
|
||||
const exactTime = computed(() => timestamp.value ? format(new Date(timestamp.value), 'PPpp') : '');
|
||||
const isSentOrDrafts = computed(() => {
|
||||
const name = store.currentFolderObj?.name?.toLowerCase() ?? '';
|
||||
return name === 'sent' || name === 'drafts';
|
||||
});
|
||||
const recipientLabel = computed(() => {
|
||||
if (!isSentOrDrafts.value)
|
||||
return null;
|
||||
let addrs = [];
|
||||
try {
|
||||
addrs = JSON.parse(props.message.to_addresses || '[]');
|
||||
}
|
||||
catch {
|
||||
addrs = [];
|
||||
}
|
||||
if (!addrs.length)
|
||||
return null;
|
||||
return 'To: ' + addrs.map((a) => {
|
||||
const m = a.match(/<([^>]+)>/);
|
||||
return m ? m[1] : a;
|
||||
}).join(', ');
|
||||
});
|
||||
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
|
||||
const __VLS_ctx = {};
|
||||
let __VLS_components;
|
||||
|
|
@ -33,7 +56,7 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.d
|
|||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "font-semibold" },
|
||||
});
|
||||
(__VLS_ctx.message.from_name || __VLS_ctx.message.from_address);
|
||||
(__VLS_ctx.recipientLabel ?? (__VLS_ctx.message.from_name || __VLS_ctx.message.from_address));
|
||||
if (!__VLS_ctx.message.is_read) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span)({
|
||||
...{ class: "flex h-2 w-2 rounded-full bg-primary" },
|
||||
|
|
@ -183,6 +206,7 @@ const __VLS_self = (await import('vue')).defineComponent({
|
|||
timestamp: timestamp,
|
||||
relativeTime: relativeTime,
|
||||
exactTime: exactTime,
|
||||
recipientLabel: recipientLabel,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@ const store = useMailStore()
|
|||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
||||
@click.self="store.isSettingsOpen = false"
|
||||
>
|
||||
<div class="relative w-full max-w-3xl rounded-xl border bg-background shadow-xl mx-4 max-h-[90vh] overflow-y-auto">
|
||||
<div class="relative w-full max-w-5xl rounded-xl border bg-background shadow-xl mx-4 max-h-[90vh] overflow-y-auto">
|
||||
<button
|
||||
class="absolute right-4 top-4 inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||
@click="store.isSettingsOpen = false"
|
||||
|
|
|
|||
115
webmail/src/components/mail/SettingsDialog.vue.js
Normal file
115
webmail/src/components/mail/SettingsDialog.vue.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/// <reference types="../../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
|
||||
import { X } from 'lucide-vue-next';
|
||||
import { useMailStore } from '../../stores/mail';
|
||||
import SettingsContent from '../settings/SettingsContent.vue';
|
||||
const store = useMailStore();
|
||||
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
|
||||
const __VLS_ctx = {};
|
||||
let __VLS_components;
|
||||
let __VLS_directives;
|
||||
// CSS variable injection
|
||||
// CSS variable injection end
|
||||
const __VLS_0 = {}.Teleport;
|
||||
/** @type {[typeof __VLS_components.Teleport, typeof __VLS_components.Teleport, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_1 = __VLS_asFunctionalComponent(__VLS_0, new __VLS_0({
|
||||
to: "body",
|
||||
}));
|
||||
const __VLS_2 = __VLS_1({
|
||||
to: "body",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
|
||||
__VLS_3.slots.default;
|
||||
const __VLS_4 = {}.Transition;
|
||||
/** @type {[typeof __VLS_components.Transition, typeof __VLS_components.Transition, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_5 = __VLS_asFunctionalComponent(__VLS_4, new __VLS_4({
|
||||
name: "fade",
|
||||
}));
|
||||
const __VLS_6 = __VLS_5({
|
||||
name: "fade",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_5));
|
||||
__VLS_7.slots.default;
|
||||
if (__VLS_ctx.store.isSettingsOpen) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.store.isSettingsOpen))
|
||||
return;
|
||||
__VLS_ctx.store.isSettingsOpen = false;
|
||||
} },
|
||||
...{ class: "fixed inset-0 z-50 flex items-center justify-center bg-black/50" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "relative w-full max-w-5xl rounded-xl border bg-background shadow-xl mx-4 max-h-[90vh] overflow-y-auto" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.store.isSettingsOpen))
|
||||
return;
|
||||
__VLS_ctx.store.isSettingsOpen = false;
|
||||
} },
|
||||
...{ class: "absolute right-4 top-4 inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors" },
|
||||
});
|
||||
const __VLS_8 = {}.X;
|
||||
/** @type {[typeof __VLS_components.X, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_9 = __VLS_asFunctionalComponent(__VLS_8, new __VLS_8({
|
||||
...{ class: "size-4" },
|
||||
}));
|
||||
const __VLS_10 = __VLS_9({
|
||||
...{ class: "size-4" },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_9));
|
||||
/** @type {[typeof SettingsContent, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_12 = __VLS_asFunctionalComponent(SettingsContent, new SettingsContent({}));
|
||||
const __VLS_13 = __VLS_12({}, ...__VLS_functionalComponentArgsRest(__VLS_12));
|
||||
}
|
||||
var __VLS_7;
|
||||
var __VLS_3;
|
||||
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['z-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-black/50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['relative']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-5xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-background']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['shadow-xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mx-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-h-[90vh]']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['overflow-y-auto']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['right-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['top-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-7']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-7']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-accent']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:text-accent-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['size-4']} */ ;
|
||||
var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
X: X,
|
||||
SettingsContent: SettingsContent,
|
||||
store: store,
|
||||
};
|
||||
},
|
||||
});
|
||||
export default (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
; /* PartiallyEnd: #4569/main.vue */
|
||||
//# sourceMappingURL=SettingsDialog.vue.js.map
|
||||
1
webmail/src/components/mail/SettingsDialog.vue.js.map
Normal file
1
webmail/src/components/mail/SettingsDialog.vue.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"SettingsDialog.vue.js","sourceRoot":"","sources":["SettingsDialog.vue"],"names":[],"mappings":"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"}
|
||||
|
|
@ -6,6 +6,7 @@ import { usePushSubscription } from '@/composables/usePushSubscription'
|
|||
import { useMailStore } from '@/stores/mail'
|
||||
import { mailApi } from '@/api/mail'
|
||||
import { authApi } from '@/api/auth'
|
||||
import type { Alias } from '@/types/mail'
|
||||
|
||||
const { canInstall, promptInstall } = useInstallPrompt()
|
||||
const notificationsStore = useNotificationsStore()
|
||||
|
|
@ -167,6 +168,98 @@ async function deleteAutoResponder() {
|
|||
arDeleteLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const alAliases = ref<Alias[]>([])
|
||||
const alLoading = ref(false)
|
||||
const alError = ref('')
|
||||
const alSuccess = ref('')
|
||||
const alNewAddress = ref('')
|
||||
const alNewLabel = ref('')
|
||||
const alNewDescription = ref('')
|
||||
const alNewExpires = ref('')
|
||||
const alNeverExpire = ref(true)
|
||||
const alCreateLoading = ref(false)
|
||||
const alGenerating = ref(false)
|
||||
|
||||
async function loadAliases(address: string) {
|
||||
alLoading.value = true
|
||||
alError.value = ''
|
||||
try {
|
||||
const res = await mailApi.getAllAliases(address)
|
||||
alAliases.value = res.data.aliases || []
|
||||
} catch {
|
||||
alError.value = 'Failed to load aliases.'
|
||||
} finally {
|
||||
alLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => mailStore.currentMailbox,
|
||||
(address) => { if (address) loadAliases(address) },
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
async function generateAlias() {
|
||||
if (!mailStore.currentMailbox) return
|
||||
const domain = mailStore.currentMailbox.split('@')[1]
|
||||
if (!domain) return
|
||||
alGenerating.value = true
|
||||
try {
|
||||
const res = await mailApi.generateAlias(mailStore.currentMailbox, domain)
|
||||
alNewAddress.value = res.data.suggestion?.split('@')[0] || ''
|
||||
} catch { /* ignore */ } finally {
|
||||
alGenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createAlias() {
|
||||
if (!mailStore.currentMailbox || !alNewAddress.value) return
|
||||
const domain = mailStore.currentMailbox.split('@')[1]
|
||||
alError.value = ''
|
||||
alSuccess.value = ''
|
||||
alCreateLoading.value = true
|
||||
try {
|
||||
await mailApi.createAlias({
|
||||
address: `${alNewAddress.value}@${domain}`,
|
||||
mailbox_address: mailStore.currentMailbox,
|
||||
label: alNewLabel.value || undefined,
|
||||
description: alNewDescription.value || undefined,
|
||||
expires_at: alNeverExpire.value ? null : (alNewExpires.value || null),
|
||||
})
|
||||
alNewAddress.value = ''
|
||||
alNewLabel.value = ''
|
||||
alNewDescription.value = ''
|
||||
alNewExpires.value = ''
|
||||
alNeverExpire.value = true
|
||||
alSuccess.value = 'Alias created.'
|
||||
await loadAliases(mailStore.currentMailbox)
|
||||
} catch (e: any) {
|
||||
alError.value = e?.response?.data?.error || 'Failed to create alias.'
|
||||
} finally {
|
||||
alCreateLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleAlias(alias: Alias) {
|
||||
try {
|
||||
await mailApi.updateAlias(alias.address, { is_active: !alias.is_active })
|
||||
alias.is_active = !alias.is_active
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function deleteAlias(alias: Alias) {
|
||||
if (!confirm(`Delete alias ${alias.address}? This cannot be undone.`)) return
|
||||
try {
|
||||
await mailApi.deleteAlias(alias.address)
|
||||
alAliases.value = alAliases.value.filter(a => a.address !== alias.address)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function formatDate(iso: string | null) {
|
||||
if (!iso) return '—'
|
||||
return new Date(iso).toLocaleDateString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -410,6 +503,111 @@ async function deleteAutoResponder() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="mailStore.currentMailbox" class="mb-4 rounded-lg border p-4 space-y-3">
|
||||
<div>
|
||||
<h2 class="text-sm font-medium">Email Aliases</h2>
|
||||
<p class="text-sm text-muted-foreground mt-0.5">
|
||||
Create aliases that forward mail to your mailbox while keeping your real address private.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="alLoading" class="text-sm text-muted-foreground">Loading…</div>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="alAliases.length" class="divide-y divide-border rounded-md border overflow-hidden">
|
||||
<div
|
||||
v-for="alias in alAliases"
|
||||
:key="alias.address"
|
||||
class="flex items-center gap-3 px-3 py-2.5 bg-background text-sm"
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-mono text-xs truncate">{{ alias.address }}</div>
|
||||
<div class="flex items-center gap-2 mt-0.5">
|
||||
<span v-if="alias.label" class="inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">{{ alias.label }}</span>
|
||||
<span v-if="alias.expires_at" class="text-xs text-muted-foreground">Expires {{ formatDate(alias.expires_at) }}</span>
|
||||
<span class="text-xs text-muted-foreground">{{ alias.use_count }} received</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200"
|
||||
:class="alias.is_active ? 'bg-primary' : 'bg-muted'"
|
||||
role="switch"
|
||||
:aria-checked="alias.is_active"
|
||||
@click="toggleAlias(alias)"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow ring-0 transition-transform duration-200"
|
||||
:class="alias.is_active ? 'translate-x-4' : 'translate-x-0'"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="text-muted-foreground hover:text-destructive transition-colors text-xs"
|
||||
@click="deleteAlias(alias)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="text-sm text-muted-foreground">No aliases yet.</p>
|
||||
|
||||
<div class="space-y-2 pt-1">
|
||||
<label class="text-sm font-medium">Create alias</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="alNewAddress"
|
||||
type="text"
|
||||
placeholder="local-part"
|
||||
class="flex-1 rounded-md border bg-background px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
<span class="text-sm text-muted-foreground shrink-0">@{{ mailStore.currentMailbox.split('@')[1] }}</span>
|
||||
<button
|
||||
:disabled="alGenerating"
|
||||
class="inline-flex items-center justify-center rounded-md border px-3 py-2 text-sm font-medium hover:bg-accent transition-colors disabled:opacity-50 shrink-0"
|
||||
@click="generateAlias"
|
||||
>
|
||||
{{ alGenerating ? '…' : 'Generate' }}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model="alNewLabel"
|
||||
type="text"
|
||||
placeholder="Label (optional, e.g. shopping)"
|
||||
class="w-full rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
<input
|
||||
v-model="alNewDescription"
|
||||
type="text"
|
||||
placeholder="Description (optional)"
|
||||
maxlength="200"
|
||||
class="w-full rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<input v-model="alNeverExpire" type="checkbox" class="rounded border-border" />
|
||||
Never expire
|
||||
</label>
|
||||
<input
|
||||
v-if="!alNeverExpire"
|
||||
v-model="alNewExpires"
|
||||
type="date"
|
||||
class="rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="alError" class="text-xs text-destructive">{{ alError }}</p>
|
||||
<p v-if="alSuccess" class="text-xs text-green-600">{{ alSuccess }}</p>
|
||||
|
||||
<button
|
||||
:disabled="alCreateLoading || !alNewAddress"
|
||||
class="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50"
|
||||
@click="createAlias"
|
||||
>
|
||||
{{ alCreateLoading ? 'Creating…' : 'Create Alias' }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-muted-foreground">Additional settings are managed in DockFlare Master.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
1308
webmail/src/components/settings/SettingsContent.vue.js
Normal file
1308
webmail/src/components/settings/SettingsContent.vue.js
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
15
webmail/src/composables/useBreakpoint.js
Normal file
15
webmail/src/composables/useBreakpoint.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
const isMobile = ref(false);
|
||||
export function useBreakpoint() {
|
||||
const mq = window.matchMedia('(max-width: 767px)');
|
||||
const update = (e) => {
|
||||
isMobile.value = e.matches;
|
||||
};
|
||||
onMounted(() => {
|
||||
isMobile.value = mq.matches;
|
||||
mq.addEventListener('change', update);
|
||||
});
|
||||
onUnmounted(() => mq.removeEventListener('change', update));
|
||||
return { isMobile };
|
||||
}
|
||||
//# sourceMappingURL=useBreakpoint.js.map
|
||||
1
webmail/src/composables/useBreakpoint.js.map
Normal file
1
webmail/src/composables/useBreakpoint.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"useBreakpoint.js","sourceRoot":"","sources":["useBreakpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AAEjD,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAAA;AAE3B,MAAM,UAAU,aAAa;IAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAA;IAElD,MAAM,MAAM,GAAG,CAAC,CAAuC,EAAE,EAAE;QACzD,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAA;IAC5B,CAAC,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAC,OAAO,CAAA;QAC3B,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAA;IAE3D,OAAO,EAAE,QAAQ,EAAE,CAAA;AACrB,CAAC"}
|
||||
20
webmail/src/composables/useBreakpoint.ts
Normal file
20
webmail/src/composables/useBreakpoint.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const isMobile = ref(false)
|
||||
|
||||
export function useBreakpoint() {
|
||||
const mq = window.matchMedia('(max-width: 767px)')
|
||||
|
||||
const update = (e: MediaQueryListEvent | MediaQueryList) => {
|
||||
isMobile.value = e.matches
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
isMobile.value = mq.matches
|
||||
mq.addEventListener('change', update)
|
||||
})
|
||||
|
||||
onUnmounted(() => mq.removeEventListener('change', update))
|
||||
|
||||
return { isMobile }
|
||||
}
|
||||
|
|
@ -1,18 +1,41 @@
|
|||
import { onUnmounted, ref, watch } from 'vue';
|
||||
import { mailApi } from '@/api/mail';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useMailStore } from '@/stores/mail';
|
||||
function updateBadge(count) {
|
||||
if (!('setAppBadge' in navigator))
|
||||
return;
|
||||
if (count > 0) {
|
||||
navigator.setAppBadge(count).catch(() => { });
|
||||
}
|
||||
else {
|
||||
navigator.clearAppBadge().catch(() => { });
|
||||
}
|
||||
}
|
||||
async function getPushIntervalMs() {
|
||||
try {
|
||||
if ('serviceWorker' in navigator && 'PushManager' in window) {
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
const sub = await reg.pushManager.getSubscription();
|
||||
if (sub)
|
||||
return 300_000;
|
||||
}
|
||||
}
|
||||
catch { /* SW not available */ }
|
||||
return 30_000 + Math.random() * 20_000 - 10_000;
|
||||
}
|
||||
export function useMailPolling() {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
const mailStore = useMailStore();
|
||||
const lastSeen = ref({});
|
||||
const initialized = ref(false);
|
||||
let intervalId = null;
|
||||
const poll = async () => {
|
||||
if (mailStore.mailboxes.length === 0)
|
||||
return;
|
||||
try {
|
||||
const res = await mailApi.getMailboxStatus();
|
||||
const statuses = res.data;
|
||||
const totalUnread = statuses.reduce((sum, s) => sum + s.unread_count, 0);
|
||||
updateBadge(totalUnread);
|
||||
if (!initialized.value) {
|
||||
for (const s of statuses) {
|
||||
lastSeen.value[s.address] = s.latest_received_at;
|
||||
|
|
@ -20,14 +43,36 @@ export function useMailPolling() {
|
|||
initialized.value = true;
|
||||
return;
|
||||
}
|
||||
if (!notificationsStore.isGranted)
|
||||
return;
|
||||
for (const s of statuses) {
|
||||
const prev = lastSeen.value[s.address];
|
||||
if (s.latest_received_at &&
|
||||
(prev === undefined || prev === null || s.latest_received_at > prev)) {
|
||||
lastSeen.value[s.address] = s.latest_received_at;
|
||||
fireNotification(s.address, s.unread_count);
|
||||
if (s.address === mailStore.currentMailbox && mailStore.currentFolder) {
|
||||
try {
|
||||
const mRes = await mailApi.getMessages(s.address, {
|
||||
folder: mailStore.currentFolder,
|
||||
order: mailStore.sortOrder,
|
||||
page: 1,
|
||||
per_page: 50,
|
||||
});
|
||||
const payload = mRes.data;
|
||||
const items = Array.isArray(payload) ? payload : payload.items || [];
|
||||
mailStore.messages = items;
|
||||
mailStore.totalMessages = payload.total ?? items.length;
|
||||
mailStore.messagesPage = 1;
|
||||
mailStore.hasMoreMessages = items.length === 50;
|
||||
}
|
||||
catch { /* network error — skip */ }
|
||||
try {
|
||||
const fRes = await mailApi.getFolders(s.address);
|
||||
mailStore.folders = fRes.data;
|
||||
}
|
||||
catch { /* network error — skip */ }
|
||||
}
|
||||
if (Notification.permission === 'granted') {
|
||||
fireNotification(s.address, s.unread_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,11 +93,34 @@ export function useMailPolling() {
|
|||
n.close();
|
||||
};
|
||||
};
|
||||
const startInterval = async () => {
|
||||
if (intervalId)
|
||||
clearInterval(intervalId);
|
||||
const ms = await getPushIntervalMs();
|
||||
intervalId = setInterval(poll, ms);
|
||||
};
|
||||
const onVisibilityChange = () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
poll();
|
||||
startInterval();
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||
watch(() => mailStore.mailboxes, (boxes) => {
|
||||
if (boxes.length > 0 && !initialized.value)
|
||||
poll();
|
||||
}, { immediate: true });
|
||||
const interval = setInterval(poll, 60_000);
|
||||
onUnmounted(() => clearInterval(interval));
|
||||
startInterval();
|
||||
onUnmounted(() => {
|
||||
if (intervalId)
|
||||
clearInterval(intervalId);
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=useMailPolling.js.map
|
||||
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"useMailPolling.js","sourceRoot":"","sources":["useMailPolling.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"useMailPolling.js","sourceRoot":"","sources":["useMailPolling.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { ref } from 'vue';
|
||||
import apiClient from '@/api/client';
|
||||
import { useMailStore } from '@/stores/mail';
|
||||
const isSupported = typeof window !== 'undefined' && 'serviceWorker' in navigator && 'PushManager' in window;
|
||||
function urlBase64ToUint8Array(base64) {
|
||||
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
|
||||
|
|
@ -12,6 +13,8 @@ function urlBase64ToUint8Array(base64) {
|
|||
export function usePushSubscription() {
|
||||
const isSubscribed = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
const mailStore = useMailStore();
|
||||
const checkSubscription = async () => {
|
||||
if (!isSupported)
|
||||
return;
|
||||
|
|
@ -19,10 +22,11 @@ export function usePushSubscription() {
|
|||
const sub = await reg.pushManager.getSubscription();
|
||||
isSubscribed.value = !!sub;
|
||||
};
|
||||
const subscribe = async (mailboxAddress) => {
|
||||
const subscribe = async () => {
|
||||
if (!isSupported)
|
||||
return;
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const { data } = await apiClient.get('/notifications/vapid-key');
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
|
|
@ -31,13 +35,28 @@ export function usePushSubscription() {
|
|||
applicationServerKey: urlBase64ToUint8Array(data.public_key),
|
||||
});
|
||||
const subJson = sub.toJSON();
|
||||
await apiClient.post('/notifications/subscribe', {
|
||||
endpoint: subJson.endpoint,
|
||||
keys: subJson.keys,
|
||||
mailbox_address: mailboxAddress,
|
||||
});
|
||||
let addresses = mailStore.mailboxes.map((m) => m.address);
|
||||
if (addresses.length === 0) {
|
||||
const statusRes = await apiClient.get('/mailboxes/status');
|
||||
addresses = statusRes.data.map((s) => s.address);
|
||||
}
|
||||
if (addresses.length === 0) {
|
||||
error.value = 'No mailboxes found';
|
||||
return;
|
||||
}
|
||||
for (const address of addresses) {
|
||||
await apiClient.post('/notifications/subscribe', {
|
||||
endpoint: subJson.endpoint,
|
||||
keys: subJson.keys,
|
||||
mailbox_address: address,
|
||||
});
|
||||
}
|
||||
isSubscribed.value = true;
|
||||
}
|
||||
catch (err) {
|
||||
console.error('Push subscribe failed:', err);
|
||||
error.value = err?.message ?? 'Subscription failed';
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
|
@ -46,6 +65,7 @@ export function usePushSubscription() {
|
|||
if (!isSupported)
|
||||
return;
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
const sub = await reg.pushManager.getSubscription();
|
||||
|
|
@ -57,11 +77,15 @@ export function usePushSubscription() {
|
|||
}
|
||||
isSubscribed.value = false;
|
||||
}
|
||||
catch (err) {
|
||||
console.error('Push unsubscribe failed:', err);
|
||||
error.value = err?.message ?? 'Unsubscribe failed';
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
checkSubscription();
|
||||
return { isSubscribed, isLoading, isSupported, subscribe, unsubscribe };
|
||||
return { isSubscribed, isLoading, isSupported, error, subscribe, unsubscribe };
|
||||
}
|
||||
//# sourceMappingURL=usePushSubscription.js.map
|
||||
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"usePushSubscription.js","sourceRoot":"","sources":["usePushSubscription.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"usePushSubscription.js","sourceRoot":"","sources":["usePushSubscription.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { setIDBItem, removeIDBItem } from '@/lib/idb';
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const token = ref(localStorage.getItem('jwt_token') || '');
|
||||
const isAuthenticated = computed(() => {
|
||||
|
|
@ -16,6 +17,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
const setToken = (newToken) => {
|
||||
token.value = newToken;
|
||||
localStorage.setItem('jwt_token', newToken);
|
||||
setIDBItem('jwt_token', newToken).catch(() => { });
|
||||
};
|
||||
const logout = async () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
|
|
@ -38,6 +40,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
}
|
||||
token.value = '';
|
||||
localStorage.removeItem('jwt_token');
|
||||
removeIDBItem('jwt_token').catch(() => { });
|
||||
};
|
||||
const decodeToken = () => {
|
||||
if (!token.value)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACnC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAA;AAEnC,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE;IACnD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAA;IAE1D,MAAM,eAAe,GAAG,QAAQ,CAAC,GAAG,EAAE;QACpC,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,OAAO,KAAK,CAAA;QAC9B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC3D,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACzE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAE,EAAE;QACpC,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAA;QACtB,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAA;IAC7C,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,KAAK,IAAI,EAAE;QACxB,IAAI,eAAe,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,aAAa,CAAC,KAAK,CAAA;gBAC/C,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,eAAe,EAAE,CAAA;gBACnD,IAAI,GAAG,EAAE,CAAC;oBACR,MAAM,KAAK,CAAC,iCAAiC,EAAE;wBAC7C,MAAM,EAAE,QAAQ;wBAChB,OAAO,EAAE;4BACP,cAAc,EAAE,kBAAkB;4BAClC,eAAe,EAAE,UAAU,KAAK,CAAC,KAAK,EAAE;yBACzC;wBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;qBACjD,CAAC,CAAA;oBACF,MAAM,GAAG,CAAC,WAAW,EAAE,CAAA;gBACzB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC,CAAC,gCAAgC,CAAC,CAAC;QAC9C,CAAC;QACD,KAAK,CAAC,KAAK,GAAG,EAAE,CAAA;QAChB,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;IACtC,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QAC7B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;YACzC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC,CAAA;IAED,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;AAClE,CAAC,CAAC,CAAA"}
|
||||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -1,14 +1,20 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, shallowRef, computed } from 'vue';
|
||||
export const useMailStore = defineStore('mail', () => {
|
||||
const mailboxes = ref([]);
|
||||
const currentMailbox = ref('');
|
||||
const folders = ref([]);
|
||||
const currentFolder = ref('');
|
||||
const messages = ref([]);
|
||||
const messages = shallowRef([]);
|
||||
const totalMessages = ref(0);
|
||||
const hasMoreMessages = ref(false);
|
||||
const messagesPage = ref(1);
|
||||
const isFetchingNextPage = ref(false);
|
||||
const currentMessage = ref(null);
|
||||
const messagesLoading = ref(false);
|
||||
const isComposeOpen = ref(false);
|
||||
const isComposeFullView = ref(false);
|
||||
const isSettingsOpen = ref(false);
|
||||
const composeDefaults = ref(null);
|
||||
const composeBody = ref('');
|
||||
const activeTab = ref('all');
|
||||
|
|
@ -16,6 +22,22 @@ export const useMailStore = defineStore('mail', () => {
|
|||
const sortOrder = ref('desc');
|
||||
const isDark = ref(localStorage.getItem('theme') === 'dark');
|
||||
const viewMode = ref(localStorage.getItem('viewMode') || 'split');
|
||||
const toast = ref(null);
|
||||
let toastTimer = null;
|
||||
let _loadMore = null;
|
||||
function showToast(message, type = 'error') {
|
||||
if (toastTimer)
|
||||
clearTimeout(toastTimer);
|
||||
toast.value = { message, type };
|
||||
toastTimer = setTimeout(() => { toast.value = null; }, 4000);
|
||||
}
|
||||
function registerLoadMore(fn) {
|
||||
_loadMore = fn;
|
||||
}
|
||||
function loadMore() {
|
||||
if (_loadMore)
|
||||
_loadMore();
|
||||
}
|
||||
const unreadMessages = computed(() => messages.value.filter((m) => !m.is_read));
|
||||
const starredMessages = computed(() => messages.value.filter((m) => m.is_starred));
|
||||
const currentFolderObj = computed(() => folders.value.find((f) => f.name === currentFolder.value) || null);
|
||||
|
|
@ -37,12 +59,15 @@ export const useMailStore = defineStore('mail', () => {
|
|||
return {
|
||||
mailboxes, currentMailbox,
|
||||
folders, currentFolder, currentFolderObj,
|
||||
messages, currentMessage,
|
||||
isComposeOpen, isComposeFullView, composeDefaults, composeBody,
|
||||
messages, totalMessages, hasMoreMessages, messagesPage, isFetchingNextPage,
|
||||
currentMessage, messagesLoading,
|
||||
isComposeOpen, isComposeFullView, isSettingsOpen, composeDefaults, composeBody,
|
||||
activeTab, isCollapsed,
|
||||
sortOrder, isDark, toggleTheme,
|
||||
viewMode, toggleViewMode,
|
||||
unreadMessages, starredMessages,
|
||||
toast, showToast,
|
||||
registerLoadMore, loadMore,
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=mail.js.map
|
||||
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"mail.js","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"mail.js","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":"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"}
|
||||
2
webmail/src/types/mail.js
Normal file
2
webmail/src/types/mail.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export {};
|
||||
//# sourceMappingURL=mail.js.map
|
||||
1
webmail/src/types/mail.js.map
Normal file
1
webmail/src/types/mail.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"mail.js","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":""}
|
||||
|
|
@ -28,6 +28,7 @@ export interface Message {
|
|||
from_address: string
|
||||
to_addresses: string
|
||||
cc_addresses: string
|
||||
bcc_addresses: string
|
||||
subject: string | null
|
||||
text_body: string | null
|
||||
html_body: string | null
|
||||
|
|
@ -36,9 +37,23 @@ export interface Message {
|
|||
is_read: 0 | 1
|
||||
is_starred: 0 | 1
|
||||
is_draft: boolean
|
||||
received_via_alias: string | null
|
||||
attachments?: Attachment[]
|
||||
}
|
||||
|
||||
export interface Alias {
|
||||
address: string
|
||||
mailbox_address: string
|
||||
domain: string
|
||||
label: string | null
|
||||
description: string | null
|
||||
is_active: boolean
|
||||
expires_at: string | null
|
||||
created_at: string
|
||||
use_count: number
|
||||
last_use_at: string | null
|
||||
}
|
||||
|
||||
export interface Toast {
|
||||
message: string
|
||||
type: 'error' | 'success' | 'info'
|
||||
|
|
@ -46,6 +61,7 @@ export interface Toast {
|
|||
|
||||
export interface ComposeDefaults {
|
||||
to?: string
|
||||
from?: string
|
||||
subject?: string
|
||||
body?: string
|
||||
quotedHtml?: string
|
||||
|
|
|
|||
|
|
@ -1,26 +1,65 @@
|
|||
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
|
||||
import { onMounted, watch } from 'vue';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useMail } from '../composables/useMail';
|
||||
import { useMailPolling } from '../composables/useMailPolling';
|
||||
import { useNotificationsStore } from '../stores/notifications';
|
||||
import { mailApi } from '../api/mail';
|
||||
import MailLayout from '../components/mail/MailLayout.vue';
|
||||
const route = useRoute();
|
||||
const { store, loadMailboxes } = useMail();
|
||||
const mailStore = store;
|
||||
const notificationsStore = useNotificationsStore();
|
||||
useMailPolling();
|
||||
const loadMessages = async (addr, folder) => {
|
||||
const showNotifPrompt = ref(false);
|
||||
let mailboxLoadSeq = 0;
|
||||
const loadMessages = async (addr, folder, page = 1) => {
|
||||
if (!addr || !folder)
|
||||
return;
|
||||
try {
|
||||
const mRes = await mailApi.getMessages(addr, { folder, order: store.sortOrder });
|
||||
const payload = mRes.data;
|
||||
store.messages = Array.isArray(payload) ? payload : payload.items || [];
|
||||
store.currentMessage = null;
|
||||
if (page === 1) {
|
||||
store.messagesLoading = true;
|
||||
store.messages = [];
|
||||
store.messagesPage = 1;
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to load messages', e);
|
||||
else {
|
||||
store.isFetchingNextPage = true;
|
||||
}
|
||||
try {
|
||||
const mRes = await mailApi.getMessages(addr, { folder, order: store.sortOrder, page, per_page: 50 });
|
||||
const payload = mRes.data;
|
||||
const items = Array.isArray(payload) ? payload : payload.items || [];
|
||||
store.messages = page === 1 ? items : [...store.messages, ...items];
|
||||
store.totalMessages = payload.total ?? items.length;
|
||||
store.messagesPage = page;
|
||||
store.hasMoreMessages = items.length === 50;
|
||||
if (page === 1)
|
||||
store.currentMessage = null;
|
||||
}
|
||||
catch {
|
||||
store.showToast('Failed to load messages');
|
||||
}
|
||||
finally {
|
||||
store.messagesLoading = false;
|
||||
store.isFetchingNextPage = false;
|
||||
}
|
||||
};
|
||||
store.registerLoadMore(() => {
|
||||
if (store.hasMoreMessages && !store.isFetchingNextPage) {
|
||||
loadMessages(store.currentMailbox, store.currentFolder, store.messagesPage + 1);
|
||||
}
|
||||
});
|
||||
async function enableNotifications() {
|
||||
await notificationsStore.requestPermission();
|
||||
showNotifPrompt.value = false;
|
||||
localStorage.setItem('notif_prompted', '1');
|
||||
if (notificationsStore.isGranted) {
|
||||
mailStore.isSettingsOpen = true;
|
||||
}
|
||||
}
|
||||
function dismissPrompt() {
|
||||
showNotifPrompt.value = false;
|
||||
localStorage.setItem('notif_prompted', '1');
|
||||
}
|
||||
onMounted(async () => {
|
||||
await loadMailboxes();
|
||||
const mailboxParam = route.query.mailbox;
|
||||
|
|
@ -29,27 +68,46 @@ onMounted(async () => {
|
|||
if (found)
|
||||
store.currentMailbox = mailboxParam;
|
||||
}
|
||||
if (Notification.permission === 'default' && !localStorage.getItem('notif_prompted')) {
|
||||
showNotifPrompt.value = true;
|
||||
}
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.addEventListener('message', (ev) => {
|
||||
if (ev.data?.type === 'NOTIFICATION_CLICK' && ev.data.mailbox) {
|
||||
store.currentMailbox = ev.data.mailbox;
|
||||
}
|
||||
if (ev.data?.type === 'SET_BADGE') {
|
||||
const count = ev.data.count ?? 0;
|
||||
if ('setAppBadge' in navigator) {
|
||||
if (count > 0) {
|
||||
navigator.setAppBadge(count).catch(() => { });
|
||||
}
|
||||
else {
|
||||
navigator.clearAppBadge().catch(() => { });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
watch(() => store.currentMailbox, async (addr) => {
|
||||
if (!addr)
|
||||
return;
|
||||
const seq = ++mailboxLoadSeq;
|
||||
try {
|
||||
const fRes = await mailApi.getFolders(addr);
|
||||
if (seq !== mailboxLoadSeq)
|
||||
return;
|
||||
store.folders = fRes.data;
|
||||
if (store.folders.length > 0) {
|
||||
const inbox = store.folders.find((f) => f.name.toLowerCase() === 'inbox');
|
||||
store.currentFolder = inbox ? inbox.name : store.folders[0].name;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to load folders', e);
|
||||
catch {
|
||||
if (seq !== mailboxLoadSeq)
|
||||
return;
|
||||
store.showToast('Failed to load folders');
|
||||
}
|
||||
});
|
||||
watch(() => [store.currentMailbox, store.currentFolder], ([addr, folder]) => {
|
||||
|
|
@ -58,44 +116,154 @@ watch(() => [store.currentMailbox, store.currentFolder], ([addr, folder]) => {
|
|||
watch(() => store.sortOrder, () => {
|
||||
loadMessages(store.currentMailbox, store.currentFolder);
|
||||
});
|
||||
let openedMessageId = null;
|
||||
watch(() => store.currentMessage, async (msg) => {
|
||||
if (!msg || msg.attachments !== undefined)
|
||||
if (!msg)
|
||||
return;
|
||||
try {
|
||||
const res = await mailApi.getMessage(store.currentMailbox, msg.id);
|
||||
const fullMsg = res.data;
|
||||
store.currentMessage = fullMsg;
|
||||
const idx = store.messages.findIndex((m) => m.id === msg.id);
|
||||
if (idx !== -1) {
|
||||
store.messages[idx] = fullMsg;
|
||||
let fullMsg = msg;
|
||||
const isUserOpen = msg.attachments === undefined || msg.id !== openedMessageId;
|
||||
if (msg.attachments === undefined) {
|
||||
const res = await mailApi.getMessage(store.currentMailbox, msg.id);
|
||||
fullMsg = res.data;
|
||||
store.currentMessage = fullMsg;
|
||||
if (idx !== -1)
|
||||
store.messages[idx] = fullMsg;
|
||||
}
|
||||
if (!fullMsg.is_read) {
|
||||
if (!fullMsg.is_read && isUserOpen) {
|
||||
openedMessageId = msg.id;
|
||||
await mailApi.updateMessage(store.currentMailbox, msg.id, { is_read: true });
|
||||
if (idx !== -1) {
|
||||
store.messages[idx] = { ...store.messages[idx], is_read: 1 };
|
||||
}
|
||||
store.currentMessage = { ...store.currentMessage, is_read: 1 };
|
||||
const fRes = await mailApi.getFolders(store.currentMailbox);
|
||||
store.folders = fRes.data;
|
||||
}
|
||||
else {
|
||||
openedMessageId = msg.id;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to load message', e);
|
||||
catch {
|
||||
store.showToast('Failed to load message');
|
||||
}
|
||||
});
|
||||
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
|
||||
const __VLS_ctx = {};
|
||||
let __VLS_components;
|
||||
let __VLS_directives;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "relative h-full" },
|
||||
});
|
||||
/** @type {[typeof MailLayout, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_0 = __VLS_asFunctionalComponent(MailLayout, new MailLayout({}));
|
||||
const __VLS_1 = __VLS_0({}, ...__VLS_functionalComponentArgsRest(__VLS_0));
|
||||
var __VLS_3 = {};
|
||||
var __VLS_2;
|
||||
const __VLS_3 = {}.Transition;
|
||||
/** @type {[typeof __VLS_components.Transition, typeof __VLS_components.Transition, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_4 = __VLS_asFunctionalComponent(__VLS_3, new __VLS_3({
|
||||
name: "slide-up",
|
||||
}));
|
||||
const __VLS_5 = __VLS_4({
|
||||
name: "slide-up",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_4));
|
||||
__VLS_6.slots.default;
|
||||
if (__VLS_ctx.showNotifPrompt) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "fixed bottom-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-4 rounded-xl border bg-background shadow-lg px-5 py-3.5 text-sm" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-muted-foreground" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.enableNotifications) },
|
||||
...{ class: "inline-flex items-center justify-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.dismissPrompt) },
|
||||
...{ class: "text-muted-foreground hover:text-foreground transition-colors" },
|
||||
});
|
||||
}
|
||||
var __VLS_6;
|
||||
const __VLS_7 = {}.Transition;
|
||||
/** @type {[typeof __VLS_components.Transition, typeof __VLS_components.Transition, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_8 = __VLS_asFunctionalComponent(__VLS_7, new __VLS_7({
|
||||
name: "slide-up",
|
||||
}));
|
||||
const __VLS_9 = __VLS_8({
|
||||
name: "slide-up",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_8));
|
||||
__VLS_10.slots.default;
|
||||
if (__VLS_ctx.store.toast) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "fixed bottom-4 right-4 z-50 flex items-center gap-3 rounded-xl border px-5 py-3.5 text-sm shadow-lg" },
|
||||
...{ class: (__VLS_ctx.store.toast.type === 'error'
|
||||
? 'bg-destructive text-destructive-foreground border-destructive'
|
||||
: __VLS_ctx.store.toast.type === 'success'
|
||||
? 'bg-green-600 text-white border-green-700'
|
||||
: 'bg-background text-foreground border-border') },
|
||||
});
|
||||
(__VLS_ctx.store.toast.message);
|
||||
}
|
||||
var __VLS_10;
|
||||
/** @type {__VLS_StyleScopedClasses['relative']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bottom-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['left-1/2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['-translate-x-1/2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['z-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-background']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['shadow-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-3.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-primary']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-1.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-primary-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-primary/90']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:text-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bottom-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['right-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['z-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-3.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['shadow-lg']} */ ;
|
||||
var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
MailLayout: MailLayout,
|
||||
store: store,
|
||||
showNotifPrompt: showNotifPrompt,
|
||||
enableNotifications: enableNotifications,
|
||||
dismissPrompt: dismissPrompt,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,12 +1,5 @@
|
|||
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
|
||||
import { useInstallPrompt } from '@/composables/useInstallPrompt';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { usePushSubscription } from '@/composables/usePushSubscription';
|
||||
import { useMailStore } from '@/stores/mail';
|
||||
const { canInstall, promptInstall } = useInstallPrompt();
|
||||
const notificationsStore = useNotificationsStore();
|
||||
const push = usePushSubscription();
|
||||
const mailStore = useMailStore();
|
||||
import SettingsContent from '@/components/settings/SettingsContent.vue';
|
||||
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
|
||||
const __VLS_ctx = {};
|
||||
let __VLS_components;
|
||||
|
|
@ -14,197 +7,17 @@ let __VLS_directives;
|
|||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-8 max-w-2xl" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
|
||||
...{ class: "text-2xl font-semibold mb-6" },
|
||||
});
|
||||
if (__VLS_ctx.canInstall) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "mb-4 rounded-lg border p-4 space-y-3" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
|
||||
...{ class: "text-sm font-medium" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-sm text-muted-foreground mt-0.5" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.promptInstall) },
|
||||
...{ class: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors" },
|
||||
});
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "mb-4 rounded-lg border p-4 space-y-3" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
|
||||
...{ class: "text-sm font-medium" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-sm text-muted-foreground mt-0.5" },
|
||||
});
|
||||
if (__VLS_ctx.notificationsStore.isDenied) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-sm text-muted-foreground" },
|
||||
});
|
||||
}
|
||||
else if (!__VLS_ctx.notificationsStore.isGranted) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.notificationsStore.requestPermission) },
|
||||
...{ class: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors" },
|
||||
});
|
||||
}
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-sm text-muted-foreground" },
|
||||
});
|
||||
if (__VLS_ctx.push.isSupported && __VLS_ctx.mailStore.currentMailbox) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center gap-3" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-sm" },
|
||||
});
|
||||
(__VLS_ctx.mailStore.currentMailbox);
|
||||
if (!__VLS_ctx.push.isSubscribed.value) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.notificationsStore.isDenied))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.notificationsStore.isGranted))
|
||||
return;
|
||||
if (!(__VLS_ctx.push.isSupported && __VLS_ctx.mailStore.currentMailbox))
|
||||
return;
|
||||
if (!(!__VLS_ctx.push.isSubscribed.value))
|
||||
return;
|
||||
__VLS_ctx.push.subscribe(__VLS_ctx.mailStore.currentMailbox);
|
||||
} },
|
||||
disabled: (__VLS_ctx.push.isLoading.value),
|
||||
...{ class: "inline-flex items-center justify-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50" },
|
||||
});
|
||||
}
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.notificationsStore.isDenied))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.notificationsStore.isGranted))
|
||||
return;
|
||||
if (!(__VLS_ctx.push.isSupported && __VLS_ctx.mailStore.currentMailbox))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.push.isSubscribed.value))
|
||||
return;
|
||||
__VLS_ctx.push.unsubscribe();
|
||||
} },
|
||||
disabled: (__VLS_ctx.push.isLoading.value),
|
||||
...{ class: "inline-flex items-center justify-center rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-muted transition-colors disabled:opacity-50" },
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (!__VLS_ctx.push.isSupported) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-sm text-muted-foreground" },
|
||||
});
|
||||
}
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-sm text-muted-foreground" },
|
||||
});
|
||||
/** @type {[typeof SettingsContent, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_0 = __VLS_asFunctionalComponent(SettingsContent, new SettingsContent({}));
|
||||
const __VLS_1 = __VLS_0({}, ...__VLS_functionalComponentArgsRest(__VLS_0));
|
||||
/** @type {__VLS_StyleScopedClasses['p-8']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['space-y-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mt-0.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-primary']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-primary-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-primary/90']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['space-y-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mt-0.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-primary']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-primary-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-primary/90']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-primary']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-1.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-primary-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-primary/90']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['disabled:opacity-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-1.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['disabled:opacity-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-muted-foreground']} */ ;
|
||||
var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
canInstall: canInstall,
|
||||
promptInstall: promptInstall,
|
||||
notificationsStore: notificationsStore,
|
||||
push: push,
|
||||
mailStore: mailStore,
|
||||
SettingsContent: SettingsContent,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue