domma-cms 1.6.37 → 1.6.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/package.json +2 -2
- package/plugins/_lib/admin/mail/mail.css +1 -1
- package/plugins/_lib/admin/mail/oauth-panel.js +303 -0
- package/plugins/_lib/admin/mail/reader-view.js +126 -13
- package/plugins/_lib/mail/accounts.js +548 -225
- package/plugins/_lib/mail/imapPool.js +63 -13
- package/plugins/_lib/mail/oauth.js +453 -0
- package/plugins/_lib/mail/oauthRoutes.js +294 -0
- package/plugins/_lib/mail/readRoutes.js +23 -2
- package/plugins/_lib/mail/scheduler.js +30 -6
- package/plugins/blog/CLAUDE.md +11 -1
- package/plugins/blog/lib/cache.js +43 -0
- package/plugins/blog/lib/page.js +28 -9
- package/plugins/blog/plugin.js +14 -13
- package/plugins/blog/plugin.json +1 -1
- package/plugins/blog/plugin.public.js +1 -1
- package/plugins/blog/tests/cache.test.js +65 -0
- package/plugins/booking/CLAUDE.md +14 -2
- package/plugins/booking/admin/lib/diary-layout.js +18 -0
- package/plugins/booking/admin/views/diary.js +153 -152
- package/plugins/booking/admin/views/people.js +64 -66
- package/plugins/booking/admin/views/settings.js +31 -32
- package/plugins/booking/plugin.json +1 -1
- package/plugins/booking/tests/admin-diary.test.js +9 -1
- package/plugins/feedback/CLAUDE.md +62 -0
- package/plugins/feedback/admin/css/index.css +16 -1
- package/plugins/feedback/admin/lib/kit.js +106 -0
- package/plugins/feedback/admin/lib/settings.js +107 -0
- package/plugins/feedback/admin/views/feedback.js +4 -1
- package/plugins/feedback/admin/views/inbox.js +11 -2
- package/plugins/feedback/admin/views/send.js +74 -7
- package/plugins/feedback/docs/guide.md +9 -9
- package/plugins/feedback/lib/attachments.js +133 -0
- package/plugins/feedback/lib/cache.js +43 -0
- package/plugins/feedback/lib/receiver.js +36 -6
- package/plugins/feedback/lib/sender.js +89 -6
- package/plugins/feedback/lib/shape.js +111 -6
- package/plugins/feedback/plugin.js +173 -17
- package/plugins/feedback/plugin.json +2 -2
- package/plugins/feedback/tests/api.test.js +125 -3
- package/plugins/feedback/tests/settings.test.js +59 -0
- package/plugins/feedback/tests/shape.test.js +87 -1
- package/plugins/free-tier.lock.json +74 -62
- package/plugins/mail-reader/CLAUDE.md +15 -4
- package/plugins/mail-reader/plugin.json +1 -1
- package/plugins/shopping-cart/CLAUDE.md +35 -2
- package/plugins/shopping-cart/admin/css/index.css +3 -0
- package/plugins/shopping-cart/admin/views/orders.js +75 -23
- package/plugins/shopping-cart/admin/views/shop.js +1 -1
- package/plugins/shopping-cart/docs/guide.md +7 -3
- package/plugins/shopping-cart/lib/payments.js +15 -2
- package/plugins/shopping-cart/lib/refunds.js +51 -0
- package/plugins/shopping-cart/lib/sales.js +15 -8
- package/plugins/shopping-cart/lib/shop.js +38 -3
- package/plugins/shopping-cart/plugin.js +47 -19
- package/plugins/shopping-cart/plugin.json +2 -2
- package/plugins/shopping-cart/tests/api.test.js +52 -0
- package/plugins/shopping-cart/tests/commission.test.js +1 -1
- package/plugins/shopping-cart/tests/pure.test.js +16 -0
- package/plugins/shopping-cart/tests/refunds.test.js +42 -0
- package/plugins/support/CLAUDE.md +58 -0
- package/plugins/support/admin/css/index.css +16 -1
- package/plugins/support/admin/lib/kit.js +192 -8
- package/plugins/support/admin/lib/settings.js +123 -0
- package/plugins/support/admin/views/desk.js +55 -12
- package/plugins/support/admin/views/inbox.js +17 -4
- package/plugins/support/admin/views/support.js +4 -1
- package/plugins/support/docs/guide.md +7 -9
- package/plugins/support/lib/attachments.js +133 -0
- package/plugins/support/lib/cache.js +43 -0
- package/plugins/support/lib/receiver.js +45 -10
- package/plugins/support/lib/sender.js +96 -10
- package/plugins/support/lib/shape.js +153 -12
- package/plugins/support/plugin.js +159 -29
- package/plugins/support/plugin.json +2 -2
- package/plugins/support/tests/api.test.js +125 -3
- package/plugins/support/tests/settings.test.js +68 -0
- package/plugins/support/tests/shape.test.js +76 -2
- package/server/routes/outreach-public.js +2 -2
- package/server/services/request-log.js +3 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Domma Tools CSS v0.51.
|
|
2
|
+
* Domma Tools CSS v0.51.13
|
|
3
3
|
* Dynamic Object Manipulation & Modeling API
|
|
4
4
|
* (c) 2026 Darryl Waterhouse & DCBW-IT
|
|
5
|
-
* Built: 2026-10-
|
|
6
|
-
* Commit:
|
|
5
|
+
* Built: 2026-10-13T15:14:47.576Z
|
|
6
|
+
* Commit: 85d3945
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/*!
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Domma Tools v0.51.
|
|
2
|
+
* Domma Tools v0.51.13
|
|
3
3
|
* Developer tools: Theme Roller & Page Roller
|
|
4
4
|
* (c) 2026 Darryl Waterhouse & DCBW-IT
|
|
5
|
-
* Built: 2026-10-
|
|
6
|
-
* Commit:
|
|
5
|
+
* Built: 2026-10-13T15:14:31.868Z
|
|
6
|
+
* Commit: 85d3945
|
|
7
7
|
*
|
|
8
8
|
* Requires: domma.min.js
|
|
9
9
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.39",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"@fastify/rate-limit": "^10.3.0",
|
|
85
85
|
"@fastify/static": "9.1.1",
|
|
86
86
|
"bcryptjs": "^3.0.3",
|
|
87
|
-
"domma-js": "0.51.
|
|
87
|
+
"domma-js": "0.51.13",
|
|
88
88
|
"dotenv": "^17.2.3",
|
|
89
89
|
"fastify": "5.8.5",
|
|
90
90
|
"gray-matter": "^4.0.3",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
#view-container.mail-fullbleed{max-width:none}.mail-layout [hidden],.dm-slideover [hidden]{display:none!important}.mail-reader-summary{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}.mail-header-toggle,.mail-extras-toggle{padding:0!important;font-size:.78rem;opacity:.75;text-decoration:underline}.mail-header-toggle:hover,.mail-extras-toggle:hover{opacity:1}.mail-header-details{display:grid;grid-template-columns:max-content 1fr;gap:.15rem .75rem;margin:.5rem 0 0;font-size:.8rem}.mail-header-details dt{opacity:.65}.mail-header-details dd{margin:0;overflow-wrap:anywhere}.mail-layout{display:grid;grid-template-columns:var(--mail-col-1, 190px) auto var(--mail-col-2, 340px) auto 1fr;height:calc(100vh - 140px);min-height:320px;border:1px solid var(--dm-border, #e2e8f0);border-radius:var(--dm-radius-md, 8px);overflow:hidden;background:var(--dm-surface, #fff)}.mail-folders-pane,.mail-list-pane{display:flex;flex-direction:column;border-right:1px solid var(--dm-border, #e2e8f0);min-width:0;min-height:0}.mail-reader-pane{display:flex;flex-direction:column;min-width:0;min-height:0}.mail-pane-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.5rem .75rem;font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted, #64748b);border-bottom:1px solid var(--dm-border, #e2e8f0);flex-shrink:0}.mail-folders-list,.mail-list{overflow-y:auto;flex:1;min-height:0}.mail-search{display:flex;align-items:center;gap:.4rem;padding:.4rem .6rem;border-bottom:1px solid var(--dm-border, #e2e8f0);color:var(--dm-text-muted, #64748b);flex-shrink:0}.mail-search input{flex:1;min-width:0;font-size:.85rem;padding:.25rem .4rem}.mail-search-note{padding:.3rem .6rem;font-size:.74rem;color:var(--dm-text-muted, #64748b);border-bottom:1px solid var(--dm-border, #e2e8f0);flex-shrink:0}.mail-slideover-resizer{padding:0;position:absolute;left:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.mail-slideover-resizer:hover,.is-resizing>.mail-slideover-resizer{background:var(--dm-primary, #2563eb);opacity:.7}.is-resizing{user-select:none;transition:none!important}.mail-selection{display:flex;align-items:center;gap:.35rem;flex-wrap:wrap;padding:.35rem .6rem;font-size:.78rem;background:var(--dm-primary, #2563eb);color:var(--dm-primary-text, #fff);flex-shrink:0}.mail-selection span{flex:1;min-width:0}.mail-selection .btn{color:var(--dm-primary-text, #fff);border:1px solid currentColor;background:transparent}.mail-selection .btn:hover{background:color-mix(in srgb,var(--dm-primary-text, #fff) 15%,transparent)}.mail-item.is-selected{background:var(--dm-active-bg, rgba(15, 23, 42, .07));box-shadow:inset 3px 0 0 var(--dm-primary, #2563eb)}.mail-undo{display:flex;align-items:center;gap:.5rem;padding:.4rem .6rem;font-size:.78rem;background:var(--dm-active-bg, rgba(15, 23, 42, .07));border-bottom:1px solid var(--dm-border, #e2e8f0);flex-shrink:0}.mail-undo span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-splitter{width:5px;cursor:col-resize;background:var(--dm-border, #e2e8f0);flex-shrink:0;background-clip:content-box;border-left:2px solid transparent;border-right:2px solid transparent;box-sizing:content-box}.mail-splitter:hover,.mail-layout.is-resizing .mail-splitter{background-color:var(--dm-primary, #2563eb)}.mail-layout.is-resizing{user-select:none}.mail-layout.is-resizing .mail-folders-pane,.mail-layout.is-resizing .mail-list-pane,.mail-layout.is-resizing .mail-reader-pane,.mail-layout.is-resizing iframe{pointer-events:none}.mail-unread-filter{display:flex;align-items:center;gap:.3rem;font-size:.7rem;text-transform:none;letter-spacing:0;cursor:pointer;margin-left:auto;margin-right:.25rem}.mail-unread-pill{flex-shrink:0;min-width:1.15rem;padding:0 .3rem;border-radius:999px;background:var(--dm-primary, #2563eb);color:var(--dm-primary-text, #fff);font-size:.66rem;font-weight:700;line-height:1.35;text-align:center}.mail-folder.is-context .mail-folder-name{opacity:.6}.mail-account{display:flex;align-items:center;gap:.4rem;width:100%;padding:.45rem .6rem;border:none;background:var(--dm-surface, #fff);cursor:pointer;text-align:left;font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--dm-text-muted, #64748b)}.mail-account:hover{background:var(--dm-hover-bg, rgba(15, 23, 42, .04))}.mail-account.is-active{color:var(--dm-text, #1e293b);position:sticky;top:0;z-index:1;background:var(--dm-surface, #fff)}.mail-account-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-folder{display:flex;align-items:center;gap:.5rem;padding:.45rem .75rem;font-size:.875rem;cursor:pointer;border:none;background:none;width:100%;text-align:left;color:var(--dm-text, #1e293b)}.mail-folder:hover{background:var(--dm-hover-bg, rgba(15, 23, 42, .04))}.mail-folder.is-active{background:var(--dm-active-bg, rgba(15, 23, 42, .07));color:var(--dm-primary, #2563eb);font-weight:600}.mail-folder.is-unselectable{opacity:.6;cursor:default}.mail-folder.is-unselectable:hover{background:none}.mail-folder-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item{display:block;width:100%;text-align:left;padding:.55rem .75rem;border:none;border-bottom:1px solid var(--dm-border, #e2e8f0);background:none;cursor:pointer;color:var(--dm-text, #1e293b)}.mail-item:hover{background:var(--dm-hover-bg, rgba(15, 23, 42, .04))}.mail-item.is-active{background:var(--dm-active-bg, rgba(15, 23, 42, .07))}.mail-item-top{display:flex;justify-content:space-between;gap:.5rem;align-items:baseline}.mail-item-from{font-size:.85rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item.is-unread .mail-item-from,.mail-item.is-unread .mail-item-subject{font-weight:700}.mail-item-date{font-size:.72rem;color:var(--dm-text-muted, #64748b);flex-shrink:0}.mail-item-subject{font-size:.82rem;margin-top:.1rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item-meta{display:flex;align-items:center;gap:.35rem;margin-top:.15rem;color:var(--dm-text-muted, #64748b)}.mail-pager{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.4rem .6rem;border-top:1px solid var(--dm-border, #e2e8f0);font-size:.75rem;color:var(--dm-text-muted, #64748b);flex-shrink:0}.mail-reader-head{padding:.75rem 1rem;border-bottom:1px solid var(--dm-border, #e2e8f0);flex-shrink:0}.mail-reader-head:empty{display:none}.mail-reader-subject{font-size:1.05rem;font-weight:600;margin:0 0 .3rem;color:var(--dm-text, #1e293b)}.mail-reader-actions{display:flex;gap:.25rem;margin:.1rem 0 .35rem;flex-wrap:wrap}.mail-reader-meta{font-size:.8rem;color:var(--dm-text-muted, #64748b)}.mail-reader-body{flex:1;position:relative;overflow:hidden;display:flex;flex-direction:column;min-height:0;background:var(--dm-surface, #fff)}.mail-reader-body iframe{width:100%;flex:1;border:none;display:block;background:#fff}.mail-notice{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;padding:.4rem .75rem;font-size:.78rem;background:var(--dm-warning, #8a6d1f);color:var(--dm-warning-text, #fff);border-bottom:1px solid var(--dm-border, #e2e8f0)}.mail-notice .btn{color:var(--dm-warning-text, #fff);border:1px solid currentColor;background:transparent;opacity:.95}.mail-notice .btn:hover{background:color-mix(in srgb,var(--dm-warning-text, #fff) 15%,transparent);opacity:1}.mail-attachments{display:flex;flex-wrap:wrap;gap:.4rem;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border, #e2e8f0);background:var(--dm-background-alt, rgba(15, 23, 42, .03))}.mail-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;height:100%;color:var(--dm-text-muted, #94a3b8);text-align:center;padding:1rem}.mail-empty p{margin:0;font-size:.875rem}@media(max-width:900px){.mail-layout{grid-template-columns:1fr;height:auto!important}.mail-splitter{display:none}.mail-folders-pane,.mail-list-pane{border-right:none;border-bottom:1px solid var(--dm-border, #e2e8f0)}.mail-folders-list,.mail-list{max-height:240px}.mail-reader-body{min-height:60vh}}.mail-box-row{display:flex;align-items:center;gap:.5rem;padding:.5rem .6rem;border:1px solid var(--dm-border, #e2e8f0);border-radius:var(--dm-radius-md, 6px)}.mail-default-badge{margin-left:.4rem;font-size:.7rem;font-weight:500;vertical-align:middle}.mail-item.is-dragging{opacity:.45}.mail-folder.is-drop-target{background:var(--dm-primary, #2563eb);color:var(--dm-primary-text, #fff);outline:2px solid var(--dm-primary, #2563eb);outline-offset:-2px}.mail-folder.is-drop-target .mail-folder-name,.mail-folder.is-drop-target .mail-unread-pill{color:inherit}.mail-item.is-dragging *{pointer-events:none}.mail-item{position:relative}.mail-item-tick{position:absolute;left:.45rem;top:.55rem;display:inline-flex;opacity:0;color:var(--dm-text-muted, #64748b);transition:opacity .12s ease;padding:2px;margin:-2px;border-radius:var(--dm-radius-sm, 3px)}.mail-item:hover .mail-item-tick,.mail-item:focus-visible .mail-item-tick,.mail-list.is-selecting .mail-item-tick,.mail-item-tick[aria-checked=true]{opacity:1}.mail-item-tick[aria-checked=true]{color:var(--dm-primary, #2563eb)}.mail-item-tick:hover{color:var(--dm-primary, #2563eb);background:var(--dm-hover-bg)}.mail-item-top,.mail-item-subject,.mail-item-meta{padding-left:1.5rem}.mail-select-all{display:inline-flex;align-items:center;margin-right:.1rem;cursor:pointer}.mail-select-all input{cursor:pointer;margin:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mail-item-meta>span{display:inline-flex}.mail-item-meta .is-replied{color:var(--dm-success)}.mail-item-meta .is-forwarded{color:var(--dm-info, var(--dm-primary))}.mail-item-meta .is-high{color:var(--dm-danger, #dc2626)}.mail-item-meta .is-low{color:var(--dm-text-muted, #64748b);opacity:.7}.mail-folder.is-hidden-folder .mail-folder-name{opacity:.5;font-style:italic}.mail-thread-chip{display:inline-flex;align-items:center;margin-left:.4rem;padding:0 .35rem;border-radius:999px;font-size:.68rem;font-weight:700;line-height:1.5;color:var(--dm-text-muted, #64748b);background:var(--dm-active-bg, rgba(15, 23, 42, .07));cursor:pointer}.mail-thread-chip:hover{color:var(--dm-primary, #2563eb)}.mail-thread-chip.is-unread{background:var(--dm-primary, #2563eb);color:var(--dm-primary-text, #fff)}.mail-item.is-in-thread{padding-left:1.6rem;box-shadow:inset 3px 0 0 var(--dm-border, #e2e8f0)}.mail-item.is-in-thread .mail-item-from{font-size:.8rem}.mail-pane-head .btn.is-on{color:var(--dm-primary, #2563eb);background:var(--dm-active-bg, rgba(15, 23, 42, .07))}#mail-outbox-btn{font-size:.72rem;font-weight:700}#mail-outbox-btn.is-failed{color:var(--dm-danger, #dc2626)}.mail-attachment{display:inline-flex;align-items:center;border:1px solid var(--dm-border, #e2e8f0);border-radius:var(--dm-radius-md, 6px);overflow:hidden}.mail-attachment .btn{border:none;border-radius:0}.mail-attachment-save{padding-left:.4rem!important;padding-right:.4rem!important;border-left:1px solid var(--dm-border, #e2e8f0)!important;opacity:.7}.mail-attachment-save:hover{opacity:1;color:var(--dm-primary, #2563eb)}.mail-section-heading{margin:1rem 0 .1rem;font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted, #64748b)}.mail-section-heading:first-child{margin-top:.25rem}.mail-section-form .domma-form{margin:0}.mail-section-form .domma-form-field:last-child{margin-bottom:0}.mail-item-card{margin:0}.mail-item-card .card-header{padding:.5rem .6rem}.mail-item-card .card-body{padding:.6rem}.mail-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.mail-status{min-height:1.2em}.mail-empty-line{margin:.25rem 0}.mail-editor-fallback{min-height:8rem;font-family:inherit;resize:vertical}.mail-clause{display:flex;gap:.3rem;align-items:center;flex-wrap:wrap}.mail-clause .form-select,.mail-clause .form-input{width:auto;flex:0 1 auto}.mail-clause .mail-clause-value{flex:1 1 8rem;min-width:8rem}.mail-section-list{display:flex;flex-direction:column;gap:.5rem}.mail-scroll-sm{max-height:20vh;overflow-y:auto}.mail-scroll-md{max-height:46vh;overflow-y:auto}.mail-section-form .domma-form-field:has(select) .form-label .text-danger{display:none}.mail-compose-form{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.6rem;height:100%}.mail-compose-body{display:flex;flex-direction:column;flex:1;min-height:0}.mail-compose-editor{flex:1;min-height:0}.mail-compose-textarea{flex:1;min-height:34vh;font-family:inherit;resize:vertical}.mail-attach-list{display:flex;flex-direction:column;gap:.2rem}.mail-template-row{margin:-.15rem 0 .35rem}.mail-template-picker{max-width:16rem}.mail-suggest-row{display:flex;flex-direction:column;line-height:1.25}.mail-suggest-detail{opacity:.7}.mail-rule-off{opacity:.55}.mail-panel{padding:.25rem 0 .5rem}.mail-panel-stack{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:1rem}.mail-form-stack{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem}.mail-dialog-stack{display:flex;flex-direction:column;gap:.5rem;padding:.25rem 0}.mail-hint{margin-top:.2rem}.mail-box-sub{overflow:hidden;text-overflow:ellipsis}.mail-add-btn{align-self:flex-start;margin-top:.5rem}.mail-none{margin:0;padding:.5rem .6rem}.mail-kv{padding:.5rem 0;display:flex;flex-direction:column;gap:.35rem}.mail-kv-row{display:flex;gap:.75rem}.mail-kv-key{min-width:8.5rem;flex-shrink:0}.mail-toggle-row{display:flex;align-items:center;gap:.5rem;margin-bottom:.75rem}.mail-sender-list{display:flex;flex-direction:column;gap:.2rem;max-height:40vh;overflow-y:auto}.mail-folder-pick{display:flex;align-items:center;gap:.5rem;padding:.3rem .4rem;border-radius:var(--dm-radius-md, 6px)}.mail-diag-log{width:100%;height:30vh;margin-top:.5rem;font-family:monospace;font-size:.72rem}.mail-facts{display:flex;flex-direction:column;gap:.2rem;margin-bottom:.75rem}.mail-item-more{position:absolute;right:.4rem;bottom:.35rem;display:inline-flex;align-items:center;justify-content:center;min-width:1.4rem;height:1.4rem;border-radius:var(--dm-radius-sm, 3px);font-size:1rem;line-height:1;color:var(--dm-text-muted, #64748b);background:inherit;opacity:0;transition:opacity .12s ease}.mail-item:hover .mail-item-more,.mail-item:focus-visible .mail-item-more,.mail-item.is-active .mail-item-more{opacity:1}.mail-item-more:hover{color:var(--dm-primary, #2563eb)}.mail-section-intro{display:flex;align-items:center;gap:.35rem;margin:0 0 .75rem;padding-bottom:.5rem;border-bottom:1px solid var(--dm-border, #e2e8f0)}.mail-pager-middle{display:flex;align-items:center;gap:.5rem;min-width:0}.mail-page-size{font:inherit;color:inherit;background:transparent;border:1px solid var(--dm-border, #e2e8f0);border-radius:4px;padding:.1rem .25rem}
|
|
1
|
+
#view-container.mail-fullbleed{max-width:none}.mail-layout [hidden],.dm-slideover [hidden]{display:none!important}.mail-reader-summary{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}.mail-header-toggle,.mail-extras-toggle{padding:0!important;font-size:.78rem;opacity:.75;text-decoration:underline}.mail-header-toggle:hover,.mail-extras-toggle:hover{opacity:1}.mail-header-details{display:grid;grid-template-columns:max-content 1fr;gap:.15rem .75rem;margin:.5rem 0 0;font-size:.8rem}.mail-header-details dt{opacity:.65}.mail-header-details dd{margin:0;overflow-wrap:anywhere}.mail-layout{display:grid;grid-template-columns:var(--mail-col-1, 190px) auto var(--mail-col-2, 340px) auto 1fr;height:calc(100vh - 140px);min-height:320px;border:1px solid var(--dm-border, #e2e8f0);border-radius:var(--dm-radius-md, 8px);overflow:hidden;background:var(--dm-surface, #fff)}.mail-folders-pane,.mail-list-pane{display:flex;flex-direction:column;border-right:1px solid var(--dm-border, #e2e8f0);min-width:0;min-height:0}.mail-reader-pane{display:flex;flex-direction:column;min-width:0;min-height:0}.mail-pane-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.5rem .75rem;font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted, #64748b);border-bottom:1px solid var(--dm-border, #e2e8f0);flex-shrink:0}.mail-folders-list,.mail-list{overflow-y:auto;flex:1;min-height:0}.mail-search{display:flex;align-items:center;gap:.4rem;padding:.4rem .6rem;border-bottom:1px solid var(--dm-border, #e2e8f0);color:var(--dm-text-muted, #64748b);flex-shrink:0}.mail-search input{flex:1;min-width:0;font-size:.85rem;padding:.25rem .4rem}.mail-search-note{padding:.3rem .6rem;font-size:.74rem;color:var(--dm-text-muted, #64748b);border-bottom:1px solid var(--dm-border, #e2e8f0);flex-shrink:0}.mail-slideover-resizer{padding:0;position:absolute;left:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.mail-slideover-resizer:hover,.is-resizing>.mail-slideover-resizer{background:var(--dm-primary, #2563eb);opacity:.7}.is-resizing{user-select:none;transition:none!important}.mail-selection{display:flex;align-items:center;gap:.35rem;flex-wrap:wrap;padding:.35rem .6rem;font-size:.78rem;background:var(--dm-primary, #2563eb);color:var(--dm-primary-text, #fff);flex-shrink:0}.mail-selection span{flex:1;min-width:0}.mail-selection .btn{color:var(--dm-primary-text, #fff);border:1px solid currentColor;background:transparent}.mail-selection .btn:hover{background:color-mix(in srgb,var(--dm-primary-text, #fff) 15%,transparent)}.mail-item.is-selected{background:var(--dm-active-bg, rgba(15, 23, 42, .07));box-shadow:inset 3px 0 0 var(--dm-primary, #2563eb)}.mail-undo{display:flex;align-items:center;gap:.5rem;padding:.4rem .6rem;font-size:.78rem;background:var(--dm-active-bg, rgba(15, 23, 42, .07));border-bottom:1px solid var(--dm-border, #e2e8f0);flex-shrink:0}.mail-undo span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-splitter{width:5px;cursor:col-resize;background:var(--dm-border, #e2e8f0);flex-shrink:0;background-clip:content-box;border-left:2px solid transparent;border-right:2px solid transparent;box-sizing:content-box}.mail-splitter:hover,.mail-layout.is-resizing .mail-splitter{background-color:var(--dm-primary, #2563eb)}.mail-layout.is-resizing{user-select:none}.mail-layout.is-resizing .mail-folders-pane,.mail-layout.is-resizing .mail-list-pane,.mail-layout.is-resizing .mail-reader-pane,.mail-layout.is-resizing iframe{pointer-events:none}.mail-unread-filter{display:flex;align-items:center;gap:.3rem;font-size:.7rem;text-transform:none;letter-spacing:0;cursor:pointer;margin-left:auto;margin-right:.25rem}.mail-unread-pill{flex-shrink:0;min-width:1.15rem;padding:0 .3rem;border-radius:999px;background:var(--dm-primary, #2563eb);color:var(--dm-primary-text, #fff);font-size:.66rem;font-weight:700;line-height:1.35;text-align:center}.mail-folder.is-context .mail-folder-name{opacity:.6}.mail-account{display:flex;align-items:center;gap:.4rem;width:100%;padding:.45rem .6rem;border:none;background:var(--dm-surface, #fff);cursor:pointer;text-align:left;font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--dm-text-muted, #64748b)}.mail-account:hover{background:var(--dm-hover-bg, rgba(15, 23, 42, .04))}.mail-account.is-active{color:var(--dm-text, #1e293b);position:sticky;top:0;z-index:1;background:var(--dm-surface, #fff)}.mail-account-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-folder{display:flex;align-items:center;gap:.5rem;padding:.45rem .75rem;font-size:.875rem;cursor:pointer;border:none;background:none;width:100%;text-align:left;color:var(--dm-text, #1e293b)}.mail-folder:hover{background:var(--dm-hover-bg, rgba(15, 23, 42, .04))}.mail-folder.is-active{background:var(--dm-active-bg, rgba(15, 23, 42, .07));color:var(--dm-primary, #2563eb);font-weight:600}.mail-folder.is-unselectable{opacity:.6;cursor:default}.mail-folder.is-unselectable:hover{background:none}.mail-folder-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item{display:block;width:100%;text-align:left;padding:.55rem .75rem;border:none;border-bottom:1px solid var(--dm-border, #e2e8f0);background:none;cursor:pointer;color:var(--dm-text, #1e293b)}.mail-item:hover{background:var(--dm-hover-bg, rgba(15, 23, 42, .04))}.mail-item.is-active{background:var(--dm-active-bg, rgba(15, 23, 42, .07))}.mail-item-top{display:flex;justify-content:space-between;gap:.5rem;align-items:baseline}.mail-item-from{font-size:.85rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item.is-unread .mail-item-from,.mail-item.is-unread .mail-item-subject{font-weight:700}.mail-item-date{font-size:.72rem;color:var(--dm-text-muted, #64748b);flex-shrink:0}.mail-item-subject{font-size:.82rem;margin-top:.1rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item-meta{display:flex;align-items:center;gap:.35rem;margin-top:.15rem;color:var(--dm-text-muted, #64748b)}.mail-pager{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.4rem .6rem;border-top:1px solid var(--dm-border, #e2e8f0);font-size:.75rem;color:var(--dm-text-muted, #64748b);flex-shrink:0}.mail-reader-head{padding:.75rem 1rem;border-bottom:1px solid var(--dm-border, #e2e8f0);flex-shrink:0}.mail-reader-head:empty{display:none}.mail-reader-subject{font-size:1.05rem;font-weight:600;margin:0 0 .3rem;color:var(--dm-text, #1e293b)}.mail-reader-actions{display:flex;gap:.25rem;margin:.1rem 0 .35rem;flex-wrap:wrap}.mail-reader-meta{font-size:.8rem;color:var(--dm-text-muted, #64748b)}.mail-reader-body{flex:1;position:relative;overflow:hidden;display:flex;flex-direction:column;min-height:0;background:var(--dm-surface, #fff)}.mail-reader-body iframe{width:100%;flex:1;border:none;display:block;background:#fff}.mail-notice{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;padding:.4rem .75rem;font-size:.78rem;background:var(--dm-warning, #8a6d1f);color:var(--dm-warning-text, #fff);border-bottom:1px solid var(--dm-border, #e2e8f0)}.mail-notice .btn{color:var(--dm-warning-text, #fff);border:1px solid currentColor;background:transparent;opacity:.95}.mail-notice .btn:hover{background:color-mix(in srgb,var(--dm-warning-text, #fff) 15%,transparent);opacity:1}.mail-attachments{display:flex;flex-wrap:wrap;gap:.4rem;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border, #e2e8f0);background:var(--dm-background-alt, rgba(15, 23, 42, .03))}.mail-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;height:100%;color:var(--dm-text-muted, #94a3b8);text-align:center;padding:1rem}.mail-empty p{margin:0;font-size:.875rem}@media(max-width:900px){.mail-layout{grid-template-columns:1fr;height:auto!important}.mail-splitter{display:none}.mail-folders-pane,.mail-list-pane{border-right:none;border-bottom:1px solid var(--dm-border, #e2e8f0)}.mail-folders-list,.mail-list{max-height:240px}.mail-reader-body{min-height:60vh}}.mail-box-row{display:flex;align-items:center;gap:.5rem;padding:.5rem .6rem;border:1px solid var(--dm-border, #e2e8f0);border-radius:var(--dm-radius-md, 6px)}.mail-default-badge{margin-left:.4rem;font-size:.7rem;font-weight:500;vertical-align:middle}.mail-item.is-dragging{opacity:.45}.mail-folder.is-drop-target{background:var(--dm-primary, #2563eb);color:var(--dm-primary-text, #fff);outline:2px solid var(--dm-primary, #2563eb);outline-offset:-2px}.mail-folder.is-drop-target .mail-folder-name,.mail-folder.is-drop-target .mail-unread-pill{color:inherit}.mail-item.is-dragging *{pointer-events:none}.mail-item{position:relative}.mail-item-tick{position:absolute;left:.45rem;top:.55rem;display:inline-flex;opacity:0;color:var(--dm-text-muted, #64748b);transition:opacity .12s ease;padding:2px;margin:-2px;border-radius:var(--dm-radius-sm, 3px)}.mail-item:hover .mail-item-tick,.mail-item:focus-visible .mail-item-tick,.mail-list.is-selecting .mail-item-tick,.mail-item-tick[aria-checked=true]{opacity:1}.mail-item-tick[aria-checked=true]{color:var(--dm-primary, #2563eb)}.mail-item-tick:hover{color:var(--dm-primary, #2563eb);background:var(--dm-hover-bg)}.mail-item-top,.mail-item-subject,.mail-item-meta{padding-left:1.5rem}.mail-select-all{display:inline-flex;align-items:center;margin-right:.1rem;cursor:pointer}.mail-select-all input{cursor:pointer;margin:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mail-item-meta>span{display:inline-flex}.mail-item-meta .is-replied{color:var(--dm-success)}.mail-item-meta .is-forwarded{color:var(--dm-info, var(--dm-primary))}.mail-item-meta .is-high{color:var(--dm-danger, #dc2626)}.mail-item-meta .is-low{color:var(--dm-text-muted, #64748b);opacity:.7}.mail-folder.is-hidden-folder .mail-folder-name{opacity:.5;font-style:italic}.mail-thread-chip{display:inline-flex;align-items:center;margin-left:.4rem;padding:0 .35rem;border-radius:999px;font-size:.68rem;font-weight:700;line-height:1.5;color:var(--dm-text-muted, #64748b);background:var(--dm-active-bg, rgba(15, 23, 42, .07));cursor:pointer}.mail-thread-chip:hover{color:var(--dm-primary, #2563eb)}.mail-thread-chip.is-unread{background:var(--dm-primary, #2563eb);color:var(--dm-primary-text, #fff)}.mail-item.is-in-thread{padding-left:1.6rem;box-shadow:inset 3px 0 0 var(--dm-border, #e2e8f0)}.mail-item.is-in-thread .mail-item-from{font-size:.8rem}.mail-pane-head .btn.is-on{color:var(--dm-primary, #2563eb);background:var(--dm-active-bg, rgba(15, 23, 42, .07))}#mail-outbox-btn{font-size:.72rem;font-weight:700}#mail-outbox-btn.is-failed{color:var(--dm-danger, #dc2626)}.mail-attachment{display:inline-flex;align-items:center;border:1px solid var(--dm-border, #e2e8f0);border-radius:var(--dm-radius-md, 6px);overflow:hidden}.mail-attachment .btn{border:none;border-radius:0}.mail-attachment-save{padding-left:.4rem!important;padding-right:.4rem!important;border-left:1px solid var(--dm-border, #e2e8f0)!important;opacity:.7}.mail-attachment-save:hover{opacity:1;color:var(--dm-primary, #2563eb)}.mail-section-heading{margin:1rem 0 .1rem;font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted, #64748b)}.mail-section-heading:first-child{margin-top:.25rem}.mail-section-form .domma-form{margin:0}.mail-section-form .domma-form-field:last-child{margin-bottom:0}.mail-item-card{margin:0}.mail-item-card .card-header{padding:.5rem .6rem}.mail-item-card .card-body{padding:.6rem}.mail-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.mail-status{min-height:1.2em}.mail-empty-line{margin:.25rem 0}.mail-editor-fallback{min-height:8rem;font-family:inherit;resize:vertical}.mail-clause{display:flex;gap:.3rem;align-items:center;flex-wrap:wrap}.mail-clause .form-select,.mail-clause .form-input{width:auto;flex:0 1 auto}.mail-clause .mail-clause-value{flex:1 1 8rem;min-width:8rem}.mail-section-list{display:flex;flex-direction:column;gap:.5rem}.mail-scroll-sm{max-height:20vh;overflow-y:auto}.mail-scroll-md{max-height:46vh;overflow-y:auto}.mail-section-form .domma-form-field:has(select) .form-label .text-danger{display:none}.mail-compose-form{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.6rem;height:100%}.mail-compose-body{display:flex;flex-direction:column;flex:1;min-height:0}.mail-compose-editor{flex:1;min-height:0}.mail-compose-textarea{flex:1;min-height:34vh;font-family:inherit;resize:vertical}.mail-attach-list{display:flex;flex-direction:column;gap:.2rem}.mail-template-row{margin:-.15rem 0 .35rem}.mail-template-picker{max-width:16rem}.mail-suggest-row{display:flex;flex-direction:column;line-height:1.25}.mail-suggest-detail{opacity:.7}.mail-rule-off{opacity:.55}.mail-panel{padding:.25rem 0 .5rem}.mail-panel-stack{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:1rem}.mail-form-stack{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem}.mail-dialog-stack{display:flex;flex-direction:column;gap:.5rem;padding:.25rem 0}.mail-hint{margin-top:.2rem}.mail-box-sub{overflow:hidden;text-overflow:ellipsis}.mail-add-btn{align-self:flex-start;margin-top:.5rem}.mail-none{margin:0;padding:.5rem .6rem}.mail-kv{padding:.5rem 0;display:flex;flex-direction:column;gap:.35rem}.mail-kv-row{display:flex;gap:.75rem}.mail-kv-key{min-width:8.5rem;flex-shrink:0}.mail-toggle-row{display:flex;align-items:center;gap:.5rem;margin-bottom:.75rem}.mail-sender-list{display:flex;flex-direction:column;gap:.2rem;max-height:40vh;overflow-y:auto}.mail-folder-pick{display:flex;align-items:center;gap:.5rem;padding:.3rem .4rem;border-radius:var(--dm-radius-md, 6px)}.mail-diag-log{width:100%;height:30vh;margin-top:.5rem;font-family:monospace;font-size:.72rem}.mail-facts{display:flex;flex-direction:column;gap:.2rem;margin-bottom:.75rem}.mail-item-more{position:absolute;right:.4rem;bottom:.35rem;display:inline-flex;align-items:center;justify-content:center;min-width:1.4rem;height:1.4rem;border-radius:var(--dm-radius-sm, 3px);font-size:1rem;line-height:1;color:var(--dm-text-muted, #64748b);background:inherit;opacity:0;transition:opacity .12s ease}.mail-item:hover .mail-item-more,.mail-item:focus-visible .mail-item-more,.mail-item.is-active .mail-item-more{opacity:1}.mail-item-more:hover{color:var(--dm-primary, #2563eb)}.mail-section-intro{display:flex;align-items:center;gap:.35rem;margin:0 0 .75rem;padding-bottom:.5rem;border-bottom:1px solid var(--dm-border, #e2e8f0)}.mail-pager-middle{display:flex;align-items:center;gap:.5rem;min-width:0}.mail-page-size{font:inherit;color:inherit;background:transparent;border:1px solid var(--dm-border, #e2e8f0);border-radius:4px;padding:.1rem .25rem}.mail-auth-choice{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;padding-bottom:.5rem;border-bottom:1px solid var(--dm-border)}.mail-oauth{display:flex;flex-direction:column;gap:.75rem}.mail-oauth-warn{margin:0;padding:.5rem .7rem;border:1px solid var(--dm-warning);border-radius:var(--dm-radius-md);color:var(--dm-warning-text)}.mail-oauth-state{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.mail-oauth-chip{margin-left:.4rem;border:1px solid var(--dm-border);background:var(--dm-surface);color:var(--dm-text-muted)}.mail-oauth-state .mail-oauth-chip{margin-left:0}.mail-oauth-chip--ok{border-color:var(--dm-success);color:var(--dm-success)}.mail-oauth-chip--bad{border-color:var(--dm-danger);color:var(--dm-danger)}.mail-oauth-guide{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);padding:.5rem .75rem;background:var(--dm-background-alt)}.mail-oauth-guide>summary{cursor:pointer;font-weight:600}.mail-oauth-guide-body ol{padding-left:1.2rem;margin:.5rem 0 0}.mail-oauth-guide-body li{margin-bottom:.45rem}.mail-oauth-code{word-break:break-all;user-select:all}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Sign in with Microsoft / Google" for a mailbox - the part of the mailbox
|
|
3
|
+
* form that replaces the password with a provider sign-in (OAuth2/XOAUTH2).
|
|
4
|
+
*
|
|
5
|
+
* The customer registers their OWN app with the provider, so the panel
|
|
6
|
+
* carries the step-by-step guide for each, with this site's exact redirect
|
|
7
|
+
* URI and a Copy button (the Docs Pro cloud-copies design). Connect opens the
|
|
8
|
+
* provider in a popup; the server's callback page tells this window it is
|
|
9
|
+
* done, and when it cannot (an admin opened on another address than the
|
|
10
|
+
* site's) the closed popup is noticed instead.
|
|
11
|
+
*
|
|
12
|
+
* Shown only when the edition offers it (`features.oauth`) AND the server
|
|
13
|
+
* answers `GET /oauth/status` - an engine without the routes says 404 and the
|
|
14
|
+
* form stays password-only.
|
|
15
|
+
*
|
|
16
|
+
* @module _lib/admin/mail/oauth-panel
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
20
|
+
|
|
21
|
+
/** What a stored sign-in's status says, and how it looks. Pure. */
|
|
22
|
+
export function describeSignIn(box) {
|
|
23
|
+
const o = box?.oauth;
|
|
24
|
+
if (box?.auth !== 'oauth' || !o) return null;
|
|
25
|
+
const who = o.provider === 'google' ? 'Google' : 'Microsoft';
|
|
26
|
+
if (o.status === 'ok') return {tone: 'ok', chip: 'Connected', text: `Signed in with ${who}.`};
|
|
27
|
+
if (o.status === 'disconnected') return {tone: 'quiet', chip: 'Disconnected', text: o.error || 'Disconnected. Reconnect to use this mailbox again.'};
|
|
28
|
+
return {tone: 'bad', chip: 'Needs reconnecting', text: o.error || `The ${who} sign-in has expired. Reconnect to sign in again.`};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The setup guide for each provider, written from the vendors' documentation
|
|
33
|
+
* (2026-10). `uri` is this site's exact return address.
|
|
34
|
+
*
|
|
35
|
+
* @param {'microsoft'|'google'} provider
|
|
36
|
+
* @param {string} uri
|
|
37
|
+
* @returns {string} HTML
|
|
38
|
+
*/
|
|
39
|
+
export function providerGuide(provider, uri) {
|
|
40
|
+
const code = `<code class="mail-oauth-code">${esc(uri)}</code> <button type="button" class="btn btn-xs btn-ghost" data-act="copy-uri">Copy</button>`;
|
|
41
|
+
if (provider === 'microsoft') {
|
|
42
|
+
return `<p>You register your own small app with Microsoft, so your mail is read with your sign-in and nobody else's app is involved. Works for Microsoft 365 (work or school) and Outlook.com.</p><ol>
|
|
43
|
+
<li>Go to <a href="https://entra.microsoft.com/" target="_blank" rel="noopener">entra.microsoft.com</a> (or the Azure portal) > <strong>App registrations > New registration</strong>.</li>
|
|
44
|
+
<li>Name it (for example "Domma Mail"). Supported account types: <strong>Accounts in any organizational directory and personal Microsoft accounts</strong> - or <strong>this organizational directory only</strong> for one company's mailboxes (then put its Directory (tenant) ID in Tenant below). Redirect URI: platform <strong>Web</strong>, exactly:<br>${code}</li>
|
|
45
|
+
<li>Copy the <strong>Application (client) ID</strong> from the Overview.</li>
|
|
46
|
+
<li><strong>Certificates & secrets > New client secret</strong>. Copy its <strong>Value</strong> (not the Secret ID) straight away - it is shown once. Note when it expires: on that day the mailbox needs reconnecting with a new secret.</li>
|
|
47
|
+
<li><strong>API permissions > Add a permission > Microsoft Graph > Delegated</strong>: tick <code>IMAP.AccessAsUser.All</code>, <code>SMTP.Send</code>, <code>offline_access</code>, <code>openid</code> and <code>email</code>.</li>
|
|
48
|
+
<li><strong>Microsoft 365:</strong> the mailbox needs <strong>IMAP</strong> and <strong>Authenticated SMTP</strong> switched on (Microsoft 365 admin center > Users > the user > Mail > Manage email apps). Your organisation may also have to consent to the app. <strong>Outlook.com:</strong> turn on IMAP under Settings > Mail > Forwarding and IMAP.</li>
|
|
49
|
+
<li>Fill in the fields here and press <strong>Connect</strong>, then sign in as the mailbox. For a Microsoft 365 <em>shared</em> mailbox, type its address in Email address and sign in as someone with Full Access to it.</li></ol>`;
|
|
50
|
+
}
|
|
51
|
+
return `<p>You make your own small Google app, so your mail is read with your sign-in and nobody else's app is involved. Works for Gmail and Google Workspace.</p><ol>
|
|
52
|
+
<li>Go to <a href="https://console.cloud.google.com/" target="_blank" rel="noopener">console.cloud.google.com</a> and create a project (or choose one). Under <strong>APIs & Services > Library</strong> enable the <strong>Gmail API</strong>.</li>
|
|
53
|
+
<li>Set up the <strong>OAuth consent screen</strong> (newer consoles: <strong>Google Auth Platform</strong>): an app name such as "Domma Mail" and your email. Audience: <strong>Internal</strong> on Google Workspace - no review and no time limit. For a personal Gmail account it has to be <strong>External</strong>.</li>
|
|
54
|
+
<li><strong>Data access</strong>: add the scope <code>https://mail.google.com/</code>. Google calls it <em>restricted</em>: an External app used by the public needs Google's verification and a security assessment. For your own mailboxes (under 100 users) you can skip that and click through Google's "unverified app" warning (Advanced > Go to the app).</li>
|
|
55
|
+
<li><strong>Publish the app</strong> (Audience > Publish app, <em>In production</em>). While an External app says <em>Testing</em>, Google ends the sign-in after <strong>7 days</strong> and the mailbox needs reconnecting every week.</li>
|
|
56
|
+
<li><strong>Clients > Create client</strong> (or Credentials > OAuth client ID): type <strong>Web application</strong>. Under <strong>Authorised redirect URIs</strong> add exactly:<br>${code}</li>
|
|
57
|
+
<li>Copy the <strong>Client ID</strong> and <strong>Client secret</strong> into the fields here and press <strong>Connect</strong>, then sign in as the mailbox. On Google Workspace an administrator may have to allow the app (Admin console > Security > API controls).</li></ol>`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const TONE = {ok: 'text-success', bad: 'text-danger', quiet: 'text-muted'};
|
|
61
|
+
|
|
62
|
+
const TEMPLATE = `
|
|
63
|
+
<div class="mail-oauth">
|
|
64
|
+
<p class="mail-oauth-warn text-sm" data-bind-hidden="https.value">This site's address starts with <strong>http://</strong>. Microsoft and Google only return to <strong>https://</strong> addresses: set the Site URL (with https) in Settings > General first.</p>
|
|
65
|
+
<div class="mail-oauth-state" data-bind-hidden="!hasState.value">
|
|
66
|
+
<span data-bind-class="'badge mail-oauth-chip mail-oauth-chip--' + stateTone.value" data-bind-text="stateChip.value"></span>
|
|
67
|
+
<span class="text-sm" data-bind-text="stateText.value"></span>
|
|
68
|
+
</div>
|
|
69
|
+
<details class="mail-oauth-guide">
|
|
70
|
+
<summary>How to set this up <span data-help-title="Your own app" data-help="Each site signs in with an app YOU register with Microsoft or Google - so nobody else's app stands between you and your mail, and you can withdraw it at any time from the provider's side."></span></summary>
|
|
71
|
+
<div class="mail-oauth-guide-body text-sm"></div>
|
|
72
|
+
</details>
|
|
73
|
+
<div class="mail-form-stack">
|
|
74
|
+
<div>
|
|
75
|
+
<label class="form-label" for="mail-oauth-label">Name</label>
|
|
76
|
+
<input id="mail-oauth-label" class="form-input" maxlength="80" data-model="label.value" placeholder="What to call this mailbox">
|
|
77
|
+
</div>
|
|
78
|
+
<div>
|
|
79
|
+
<label class="form-label" for="mail-oauth-user">Email address <span data-help="Leave blank to use the account you sign in as. Type an address only for a Microsoft 365 shared mailbox you have Full Access to."></span></label>
|
|
80
|
+
<input id="mail-oauth-user" class="form-input" type="email" maxlength="254" data-model="username.value" data-bind-disabled="locked.value" placeholder="Optional - the account you sign in as">
|
|
81
|
+
</div>
|
|
82
|
+
<div>
|
|
83
|
+
<label class="form-label" for="mail-oauth-client" data-bind-text="clientIdLabel.value">Client ID</label>
|
|
84
|
+
<input id="mail-oauth-client" class="form-input" maxlength="200" autocomplete="off" spellcheck="false" data-model="clientId.value">
|
|
85
|
+
</div>
|
|
86
|
+
<div>
|
|
87
|
+
<label class="form-label" for="mail-oauth-secret">Client secret <span data-help="Stored sealed on this server and never sent back to the browser. Microsoft secrets expire (you chose when): on that day the mailbox needs reconnecting with a new one."></span></label>
|
|
88
|
+
<input id="mail-oauth-secret" class="form-input" type="password" maxlength="2000" autocomplete="new-password" data-model="clientSecret.value" data-bind-placeholder="secretPlaceholder.value">
|
|
89
|
+
</div>
|
|
90
|
+
<div data-bind-hidden="!isMicrosoft.value">
|
|
91
|
+
<label class="form-label" for="mail-oauth-tenant">Tenant <span data-help="Leave as common unless the app is registered for your organisation only - then its Directory (tenant) ID."></span></label>
|
|
92
|
+
<input id="mail-oauth-tenant" class="form-input" maxlength="100" spellcheck="false" data-model="tenant.value" placeholder="common">
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div data-bind-class="'text-sm mail-status ' + statusClass.value" data-bind-text="status.value"></div>
|
|
96
|
+
<div class="flex justify-between gap-2 mt-2">
|
|
97
|
+
<div class="flex gap-2">
|
|
98
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="test" data-bind-hidden="!canTest.value" data-bind-disabled="busy.value">Test</button>
|
|
99
|
+
<button type="button" class="btn btn-sm btn-ghost text-danger" data-on-click="disconnect" data-bind-hidden="!canDisconnect.value" data-bind-disabled="busy.value">Disconnect</button>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="flex gap-2">
|
|
102
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="cancel">Cancel</button>
|
|
103
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="saveName" data-bind-hidden="!isSaved.value" data-bind-disabled="busy.value">Save name</button>
|
|
104
|
+
<button type="button" class="btn btn-sm btn-primary" data-on-click="connect" data-bind-disabled="busy.value" data-bind-text="connectLabel.value">Connect</button>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>`;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Mount the sign-in panel.
|
|
111
|
+
*
|
|
112
|
+
* @param {HTMLElement} host
|
|
113
|
+
* @param {{provider: 'microsoft'|'google', box: object|null, base: string, api: Function,
|
|
114
|
+
* status: {redirectUri: string, https: boolean}, helpPopovers: Function,
|
|
115
|
+
* onConnected: (accountId: string|null) => void, onChanged: () => void, onCancel: () => void}} o
|
|
116
|
+
* @returns {{dispose: () => void, dirty: () => boolean, setProvider: (p: string) => void}}
|
|
117
|
+
*/
|
|
118
|
+
export function mountSignIn(host, {provider: first, box, base, api, status, helpPopovers, onConnected, onChanged, onCancel}) {
|
|
119
|
+
const signedIn = box?.auth === 'oauth' ? box.oauth : null;
|
|
120
|
+
const provider = M.observable(signedIn?.provider ?? first);
|
|
121
|
+
const label = M.observable(box?.label ?? '');
|
|
122
|
+
const username = M.observable('');
|
|
123
|
+
const clientId = M.observable(signedIn?.clientId ?? '');
|
|
124
|
+
const clientSecret = M.observable('');
|
|
125
|
+
const tenant = M.observable(signedIn?.tenant ?? 'common');
|
|
126
|
+
const busy = M.observable(false);
|
|
127
|
+
const said = M.observable('');
|
|
128
|
+
const tone = M.observable('quiet');
|
|
129
|
+
const current = M.observable(box);
|
|
130
|
+
const say = (text, t = 'quiet') => { said.value = text ?? ''; tone.value = t; };
|
|
131
|
+
|
|
132
|
+
const opened = JSON.stringify({l: label.peek(), u: username.peek(), c: clientId.peek(), t: tenant.peek()});
|
|
133
|
+
|
|
134
|
+
const holder = document.createElement('div');
|
|
135
|
+
holder.innerHTML = TEMPLATE.trim();
|
|
136
|
+
const root = holder.firstElementChild;
|
|
137
|
+
const guideBody = root.querySelector('.mail-oauth-guide-body');
|
|
138
|
+
const guide = root.querySelector('.mail-oauth-guide');
|
|
139
|
+
// Open for a first setup, folded once there is a sign-in to look at.
|
|
140
|
+
if (!signedIn) guide.open = true;
|
|
141
|
+
host.appendChild(root);
|
|
142
|
+
// Help markers before binding (they stay where they are), then bind.
|
|
143
|
+
// (Computeds below read observables only - never another computed.)
|
|
144
|
+
const stopHelp = helpPopovers(root, {position: 'bottom'});
|
|
145
|
+
|
|
146
|
+
const stopGuide = M.effect(() => { guideBody.innerHTML = providerGuide(provider.value, status.redirectUri); });
|
|
147
|
+
root.addEventListener('click', (event) => {
|
|
148
|
+
if (!event.target.closest('[data-act="copy-uri"]')) return;
|
|
149
|
+
navigator.clipboard?.writeText(status.redirectUri)
|
|
150
|
+
.then(() => E.toast('Copied', {type: 'success'}))
|
|
151
|
+
.catch(() => E.toast('Copy it by hand - the browser refused.', {type: 'warning'}));
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
let popup = null;
|
|
155
|
+
let watch = null;
|
|
156
|
+
const onMessage = (event) => {
|
|
157
|
+
// Our window, our page, our origin: anything else posting this shape is ignored.
|
|
158
|
+
if (!popup || event.source !== popup || event.origin !== location.origin || event.data?.type !== 'domma-mail-oauth') return;
|
|
159
|
+
finish(event.data.ok, event.data.message, event.data.accountId ?? null);
|
|
160
|
+
};
|
|
161
|
+
window.addEventListener('message', onMessage);
|
|
162
|
+
|
|
163
|
+
function stopWatching() {
|
|
164
|
+
if (watch) clearInterval(watch);
|
|
165
|
+
watch = null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function finish(ok, message, accountId) {
|
|
169
|
+
stopWatching();
|
|
170
|
+
popup = null;
|
|
171
|
+
busy.value = false;
|
|
172
|
+
if (ok) {
|
|
173
|
+
say(message || 'Connected.', 'ok');
|
|
174
|
+
clientSecret.value = '';
|
|
175
|
+
onConnected(accountId);
|
|
176
|
+
} else {
|
|
177
|
+
say(message || 'The sign-in was not completed.', 'bad');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function connect() {
|
|
182
|
+
// The window must open inside the click itself, or the browser blocks it.
|
|
183
|
+
popup = window.open('about:blank', 'domma-mail-oauth', 'width=560,height=720');
|
|
184
|
+
if (!popup) {
|
|
185
|
+
say('The browser blocked the sign-in window. Allow pop-ups for this site and try again.', 'bad');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
busy.value = true;
|
|
189
|
+
say('Waiting for you to sign in…');
|
|
190
|
+
try {
|
|
191
|
+
const r = await api(`${base}/oauth/start`, 'POST', {
|
|
192
|
+
accountId: box?.id ?? null,
|
|
193
|
+
provider: provider.peek(),
|
|
194
|
+
clientId: clientId.peek().trim(),
|
|
195
|
+
clientSecret: clientSecret.peek(),
|
|
196
|
+
tenant: tenant.peek().trim(),
|
|
197
|
+
label: label.peek().trim(),
|
|
198
|
+
username: username.peek().trim()
|
|
199
|
+
});
|
|
200
|
+
popup.location.href = r.url;
|
|
201
|
+
// The callback page posts back when it can; if the admin is open on
|
|
202
|
+
// another address than the site's, the message cannot arrive - so a
|
|
203
|
+
// closed window also counts as "done, go and look".
|
|
204
|
+
watch = setInterval(() => {
|
|
205
|
+
if (popup && popup.closed) finish(null, null, null);
|
|
206
|
+
}, 1000);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
try { popup.close(); } catch { /* gone */ }
|
|
209
|
+
popup = null;
|
|
210
|
+
busy.value = false;
|
|
211
|
+
say(err.message, 'bad');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const bindings = M.applyBindings({
|
|
216
|
+
label, username, clientId, clientSecret, tenant, busy,
|
|
217
|
+
https: M.computed(() => status.https !== false),
|
|
218
|
+
isMicrosoft: M.computed(() => provider.value === 'microsoft'),
|
|
219
|
+
clientIdLabel: M.computed(() => (provider.value === 'microsoft' ? 'Application (client) ID' : 'Client ID')),
|
|
220
|
+
secretPlaceholder: M.computed(() => (signedIn?.hasClientSecret && signedIn.provider === provider.value
|
|
221
|
+
? 'Saved - leave blank to keep it' : (provider.value === 'microsoft' ? 'The secret\'s Value' : 'Client secret'))),
|
|
222
|
+
locked: M.computed(() => Boolean(signedIn)),
|
|
223
|
+
hasState: M.computed(() => Boolean(describeSignIn(current.value))),
|
|
224
|
+
stateChip: M.computed(() => describeSignIn(current.value)?.chip ?? ''),
|
|
225
|
+
stateText: M.computed(() => describeSignIn(current.value)?.text ?? ''),
|
|
226
|
+
stateTone: M.computed(() => describeSignIn(current.value)?.tone ?? 'quiet'),
|
|
227
|
+
isSaved: M.computed(() => Boolean(signedIn)),
|
|
228
|
+
canTest: M.computed(() => current.value?.auth === 'oauth' && current.value.oauth?.status === 'ok'),
|
|
229
|
+
canDisconnect: M.computed(() => current.value?.auth === 'oauth' && current.value.oauth?.status !== 'disconnected'),
|
|
230
|
+
connectLabel: M.computed(() => (signedIn ? 'Reconnect' : `Connect with ${provider.value === 'microsoft' ? 'Microsoft' : 'Google'}`)),
|
|
231
|
+
status: said,
|
|
232
|
+
statusClass: M.computed(() => TONE[tone.value] ?? TONE.quiet)
|
|
233
|
+
}, root, {
|
|
234
|
+
methods: {
|
|
235
|
+
connect: () => { connect(); },
|
|
236
|
+
cancel: () => onCancel(),
|
|
237
|
+
async test() {
|
|
238
|
+
busy.value = true;
|
|
239
|
+
say('Signing in to both servers…');
|
|
240
|
+
try {
|
|
241
|
+
const r = await api(`${base}/oauth/${encodeURIComponent(box.id)}/test`, 'POST', {});
|
|
242
|
+
say(r.ok ? 'Reading and sending both signed in.'
|
|
243
|
+
: [r.imap?.ok ? '' : `Reading: ${r.imap?.error}`, r.smtp?.ok ? '' : `Sending: ${r.smtp?.error}`].filter(Boolean).join(' '),
|
|
244
|
+
r.ok ? 'ok' : 'bad');
|
|
245
|
+
} catch (err) {
|
|
246
|
+
say(err.message, 'bad');
|
|
247
|
+
} finally {
|
|
248
|
+
busy.value = false;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
async disconnect() {
|
|
252
|
+
const who = current.peek()?.oauth?.provider === 'google' ? 'Google' : 'Microsoft';
|
|
253
|
+
const note = who === 'Google'
|
|
254
|
+
? 'The sign-in is handed back to Google and forgotten here.'
|
|
255
|
+
: 'The sign-in is forgotten here. Microsoft gives apps no way to hand one back: to cut it off at Microsoft too, delete the app\'s client secret in Entra.';
|
|
256
|
+
if (!await E.confirm(`Disconnect "${esc(current.peek()?.label ?? '')}"? ${note} The mailbox and its settings stay; Reconnect to use it again.`)) return;
|
|
257
|
+
busy.value = true;
|
|
258
|
+
try {
|
|
259
|
+
const r = await api(`${base}/oauth/${encodeURIComponent(box.id)}/disconnect`, 'POST', {});
|
|
260
|
+
current.value = r.account;
|
|
261
|
+
say(r.revoked ? 'Disconnected, and the sign-in was handed back.' : 'Disconnected.', 'quiet');
|
|
262
|
+
onChanged();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
say(err.message, 'bad');
|
|
265
|
+
} finally {
|
|
266
|
+
busy.value = false;
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
async saveName() {
|
|
270
|
+
busy.value = true;
|
|
271
|
+
try {
|
|
272
|
+
const r = await api(`${base}/accounts/${encodeURIComponent(box.id)}`, 'PUT', {label: label.peek().trim()});
|
|
273
|
+
current.value = r.account;
|
|
274
|
+
say('Saved.', 'ok');
|
|
275
|
+
onChanged();
|
|
276
|
+
} catch (err) {
|
|
277
|
+
say(err.message, 'bad');
|
|
278
|
+
} finally {
|
|
279
|
+
busy.value = false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
Domma.icons?.scan?.(root);
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
setProvider(p) {
|
|
288
|
+
if (signedIn) return;
|
|
289
|
+
provider.value = p;
|
|
290
|
+
say('');
|
|
291
|
+
},
|
|
292
|
+
dirty: () => clientSecret.peek() !== ''
|
|
293
|
+
|| JSON.stringify({l: label.peek(), u: username.peek(), c: clientId.peek(), t: tenant.peek()}) !== opened,
|
|
294
|
+
dispose() {
|
|
295
|
+
stopWatching();
|
|
296
|
+
window.removeEventListener('message', onMessage);
|
|
297
|
+
try { if (popup && !popup.closed) popup.close(); } catch { /* gone */ }
|
|
298
|
+
try { stopGuide?.dispose?.(); } catch { /* gone */ }
|
|
299
|
+
try { bindings?.dispose?.(); } catch { /* gone */ }
|
|
300
|
+
try { stopHelp?.(); } catch { /* gone */ }
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
@@ -50,6 +50,9 @@ const {fillMessageAction, loadMessageActions, messageFacts} = await import(versi
|
|
|
50
50
|
const {isPlainKey, isTyping, resolveKey, SHORTCUTS} = await import(versioned('keyboard.js'));
|
|
51
51
|
const {menuPoint, quoteSelection, readBridgeMessage, selectionItems} = await import(versioned('body-menu.js'));
|
|
52
52
|
const {describeWhen, quickTimes} = await import(versioned('scheduling.js'));
|
|
53
|
+
// Sign in with Microsoft / Google (Email Pro). Optional: an older copy of this
|
|
54
|
+
// directory without it must not take the reader down.
|
|
55
|
+
const {describeSignIn, mountSignIn} = await import(versioned('oauth-panel.js')).catch(() => ({}));
|
|
53
56
|
|
|
54
57
|
/**
|
|
55
58
|
* Call a plugin route, returning parsed JSON.
|
|
@@ -975,7 +978,17 @@ return {
|
|
|
975
978
|
}
|
|
976
979
|
const sub = document.createElement('div');
|
|
977
980
|
sub.className = 'text-xs text-muted mail-box-sub';
|
|
978
|
-
|
|
981
|
+
const signIn = describeSignIn?.(box) ?? null;
|
|
982
|
+
sub.textContent = signIn
|
|
983
|
+
? `${box.username} \u00b7 ${box.oauth.provider === 'google' ? 'Google' : 'Microsoft'} sign-in`
|
|
984
|
+
: `${box.username} \u00b7 ${box.host}:${box.port}${box.secure ? '' : ' (no TLS)'}`;
|
|
985
|
+
if (signIn && signIn.tone !== 'ok') {
|
|
986
|
+
const chip = document.createElement('span');
|
|
987
|
+
chip.className = `badge mail-oauth-chip mail-oauth-chip--${signIn.tone}`;
|
|
988
|
+
chip.textContent = signIn.chip;
|
|
989
|
+
chip.title = signIn.text;
|
|
990
|
+
name.appendChild(chip);
|
|
991
|
+
}
|
|
979
992
|
info.appendChild(name);
|
|
980
993
|
info.appendChild(sub);
|
|
981
994
|
row.appendChild(info);
|
|
@@ -1060,13 +1073,19 @@ return {
|
|
|
1060
1073
|
|
|
1061
1074
|
const form = document.createElement('div');
|
|
1062
1075
|
form.className = 'mail-form-stack';
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1076
|
+
// The password form lives in its own wrapper so "Sign in with
|
|
1077
|
+
// Microsoft / Google" can stand in for it (Email Pro).
|
|
1078
|
+
const pwWrap = document.createElement('div');
|
|
1079
|
+
pwWrap.className = 'mail-form-stack';
|
|
1080
|
+
form.appendChild(pwWrap);
|
|
1081
|
+
const signedIn = box?.auth === 'oauth';
|
|
1082
|
+
|
|
1083
|
+
const labelInput = field(pwWrap, 'label', 'Name', 'text', box?.label ?? '', 'What to call this mailbox.');
|
|
1084
|
+
const hostInput = field(pwWrap, 'host', 'IMAP server', 'text', box?.host ?? '', 'e.g. mail.example.com');
|
|
1085
|
+
const portInput = field(pwWrap, 'port', 'Port', 'number', String(box?.port ?? 993));
|
|
1086
|
+
const userInput = field(pwWrap, 'username', 'Username', 'text', box?.username ?? '');
|
|
1068
1087
|
const passInput = field(
|
|
1069
|
-
|
|
1088
|
+
pwWrap, 'password', 'Password', 'password', '',
|
|
1070
1089
|
box?.hasPassword ? 'Leave blank to keep the saved password.' : ''
|
|
1071
1090
|
);
|
|
1072
1091
|
|
|
@@ -1077,7 +1096,7 @@ return {
|
|
|
1077
1096
|
secureInput.checked = box ? box.secure !== false : true;
|
|
1078
1097
|
secureWrap.appendChild(secureInput);
|
|
1079
1098
|
secureWrap.appendChild(document.createTextNode('Use TLS (port 993)'));
|
|
1080
|
-
|
|
1099
|
+
pwWrap.appendChild(secureWrap);
|
|
1081
1100
|
|
|
1082
1101
|
// Keep the port in step with the TLS choice until the user sets one
|
|
1083
1102
|
// themselves - 993/143 is the only pair that matters here.
|
|
@@ -1109,7 +1128,7 @@ return {
|
|
|
1109
1128
|
status.className = 'text-sm mail-status '
|
|
1110
1129
|
+ ({ok: 'text-success', bad: 'text-danger'}[tone] ?? 'text-muted');
|
|
1111
1130
|
};
|
|
1112
|
-
|
|
1131
|
+
pwWrap.appendChild(status);
|
|
1113
1132
|
|
|
1114
1133
|
const actions = document.createElement('div');
|
|
1115
1134
|
actions.className = 'flex justify-between gap-2 mt-2';
|
|
@@ -1131,17 +1150,111 @@ return {
|
|
|
1131
1150
|
|
|
1132
1151
|
actions.appendChild(testBtn);
|
|
1133
1152
|
actions.appendChild(right);
|
|
1134
|
-
|
|
1153
|
+
pwWrap.appendChild(actions);
|
|
1135
1154
|
|
|
1136
1155
|
slideover.element.appendChild(form);
|
|
1137
1156
|
// What it opened with; a typed password always counts as a change.
|
|
1138
1157
|
const opened = JSON.stringify({...collect(), password: ''});
|
|
1158
|
+
let signInPanel = null;
|
|
1159
|
+
let stopChoiceHelp = null;
|
|
1160
|
+
let method = signedIn ? 'oauth' : 'password';
|
|
1139
1161
|
guardPanel(slideover,
|
|
1140
|
-
() =>
|
|
1162
|
+
() => (method === 'password'
|
|
1163
|
+
? passInput.value !== '' || JSON.stringify({...collect(), password: ''}) !== opened
|
|
1164
|
+
: Boolean(signInPanel?.dirty())),
|
|
1141
1165
|
box ? 'Discard your changes to this mailbox?' : 'Discard this mailbox? It has not been saved.',
|
|
1142
|
-
() => stopFormResize());
|
|
1166
|
+
() => { stopFormResize(); signInPanel?.dispose(); stopChoiceHelp?.(); });
|
|
1143
1167
|
slideover.open();
|
|
1144
|
-
|
|
1168
|
+
if (signedIn) pwWrap.hidden = true;
|
|
1169
|
+
else setTimeout(() => hostInput.focus(), 50);
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Offer "Sign in with Microsoft / Google" when this edition has it
|
|
1173
|
+
* and the server answers for it. A signed-in mailbox opens on its
|
|
1174
|
+
* sign-in; a password one can be moved onto one.
|
|
1175
|
+
*/
|
|
1176
|
+
(async () => {
|
|
1177
|
+
const oauth = features.oauth && mountSignIn
|
|
1178
|
+
? await api(`${BASE}/oauth/status`).catch(() => null)
|
|
1179
|
+
: null;
|
|
1180
|
+
if (!oauth?.available) {
|
|
1181
|
+
if (signedIn) {
|
|
1182
|
+
const note = document.createElement('p');
|
|
1183
|
+
note.className = 'text-sm text-muted';
|
|
1184
|
+
note.textContent = `This mailbox signs in with ${box.oauth?.provider === 'google' ? 'Google' : 'Microsoft'} instead of a password. `
|
|
1185
|
+
+ 'Its sign-in is managed in Email Pro.';
|
|
1186
|
+
form.prepend(note);
|
|
1187
|
+
}
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
const signInHost = document.createElement('div');
|
|
1192
|
+
const choices = [
|
|
1193
|
+
...(signedIn ? [] : [{id: 'password', text: 'Password'}]),
|
|
1194
|
+
{id: 'microsoft', text: 'Sign in with Microsoft'},
|
|
1195
|
+
{id: 'google', text: 'Sign in with Google'}
|
|
1196
|
+
].filter(c => !signedIn || c.id === box.oauth.provider);
|
|
1197
|
+
|
|
1198
|
+
const mount = (provider) => {
|
|
1199
|
+
if (signInPanel) {
|
|
1200
|
+
signInPanel.setProvider(provider);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
signInPanel = mountSignIn(signInHost, {
|
|
1204
|
+
provider, box, base: BASE, api, status: oauth, helpPopovers,
|
|
1205
|
+
onConnected: async (accountId) => {
|
|
1206
|
+
slideover.close(true);
|
|
1207
|
+
E.toast('Mailbox connected.', {type: 'success'});
|
|
1208
|
+
if (accountId) currentAccountId = accountId;
|
|
1209
|
+
await start();
|
|
1210
|
+
},
|
|
1211
|
+
onChanged: async () => {
|
|
1212
|
+
mailboxes = (await api(`${BASE}/accounts`)).accounts;
|
|
1213
|
+
renderFolders();
|
|
1214
|
+
},
|
|
1215
|
+
onCancel: () => slideover.close()
|
|
1216
|
+
});
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
if (!signedIn) {
|
|
1220
|
+
const bar = document.createElement('div');
|
|
1221
|
+
bar.className = 'mail-auth-choice';
|
|
1222
|
+
bar.setAttribute('role', 'radiogroup');
|
|
1223
|
+
bar.setAttribute('aria-label', 'How this mailbox signs in');
|
|
1224
|
+
const buttons = choices.map(choice => {
|
|
1225
|
+
const b = document.createElement('button');
|
|
1226
|
+
b.type = 'button';
|
|
1227
|
+
b.className = 'btn btn-sm ' + (choice.id === 'password' ? 'btn-primary' : 'btn-ghost');
|
|
1228
|
+
b.setAttribute('role', 'radio');
|
|
1229
|
+
b.setAttribute('aria-checked', String(choice.id === 'password'));
|
|
1230
|
+
b.textContent = choice.text;
|
|
1231
|
+
b.addEventListener('click', () => {
|
|
1232
|
+
method = choice.id === 'password' ? 'password' : 'oauth';
|
|
1233
|
+
for (const other of buttons) {
|
|
1234
|
+
const on = other === b;
|
|
1235
|
+
other.className = 'btn btn-sm ' + (on ? 'btn-primary' : 'btn-ghost');
|
|
1236
|
+
other.setAttribute('aria-checked', String(on));
|
|
1237
|
+
}
|
|
1238
|
+
pwWrap.hidden = method !== 'password';
|
|
1239
|
+
signInHost.hidden = method === 'password';
|
|
1240
|
+
if (method === 'oauth') mount(choice.id);
|
|
1241
|
+
});
|
|
1242
|
+
return b;
|
|
1243
|
+
});
|
|
1244
|
+
bar.append(...buttons);
|
|
1245
|
+
const help = document.createElement('span');
|
|
1246
|
+
help.setAttribute('data-help-title', 'Password or sign-in');
|
|
1247
|
+
help.setAttribute('data-help', 'Microsoft 365, Outlook.com and Gmail can be connected by signing in with them instead of typing a password - no app password needed. You register a small app of your own with the provider first; the form shows how.');
|
|
1248
|
+
bar.appendChild(help);
|
|
1249
|
+
form.prepend(bar);
|
|
1250
|
+
stopChoiceHelp = helpPopovers(bar, {position: 'bottom'});
|
|
1251
|
+
signInHost.hidden = true;
|
|
1252
|
+
form.appendChild(signInHost);
|
|
1253
|
+
} else {
|
|
1254
|
+
form.appendChild(signInHost);
|
|
1255
|
+
mount(box.oauth.provider);
|
|
1256
|
+
}
|
|
1257
|
+
})();
|
|
1145
1258
|
|
|
1146
1259
|
/**
|
|
1147
1260
|
* Collect the form into the shape the API expects.
|