domma-cms 0.75.0 → 0.77.0
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/CLAUDE.md +1 -1
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +4 -4
- package/admin/js/lib/action-editor-arrange.js +1 -0
- package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
- package/admin/js/lib/api-tokens-arrange.js +3 -0
- package/admin/js/lib/block-editor-arrange.js +2 -0
- package/admin/js/lib/collection-editor-arrange.js +3 -0
- package/admin/js/lib/collection-entries-arrange.js +1 -0
- package/admin/js/lib/component-editor-arrange.js +27 -0
- package/admin/js/lib/context-menu-editor-arrange.js +1 -0
- package/admin/js/lib/dashboard-arrange.js +1 -0
- package/admin/js/lib/effects-arrange.js +3 -0
- package/admin/js/lib/form-editor-arrange.js +1 -0
- package/admin/js/lib/form-submissions-arrange.js +2 -0
- package/admin/js/lib/layouts-arrange.js +1 -0
- package/admin/js/lib/menu-editor-arrange.js +2 -0
- package/admin/js/lib/my-profile-arrange.js +1 -0
- package/admin/js/lib/notifications-arrange.js +1 -0
- package/admin/js/lib/page-editor-arrange.js +1 -0
- package/admin/js/lib/plugin-code-arrange.js +1 -0
- package/admin/js/lib/plugins-arrange.js +2 -0
- package/admin/js/lib/project-page-arrange.js +1 -0
- package/admin/js/lib/role-editor-arrange.js +3 -0
- package/admin/js/lib/roles-arrange.js +1 -0
- package/admin/js/lib/search-settings-arrange.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +2 -2
- package/admin/js/lib/site-settings-arrange.js +1 -0
- package/admin/js/lib/user-editor-arrange.js +1 -0
- package/admin/js/lib/users-arrange.js +1 -0
- package/admin/js/lib/view-editor-arrange.js +14 -0
- package/admin/js/templates/action-editor.html +223 -147
- package/admin/js/templates/api-endpoint-editor.html +172 -117
- package/admin/js/templates/api-tokens.html +83 -10
- package/admin/js/templates/block-editor.html +194 -203
- package/admin/js/templates/collection-editor.html +191 -113
- package/admin/js/templates/collection-entries.html +99 -36
- package/admin/js/templates/component-editor.html +128 -126
- package/admin/js/templates/context-menu-editor.html +208 -148
- package/admin/js/templates/dashboard.html +184 -22
- package/admin/js/templates/effects.html +155 -72
- package/admin/js/templates/form-editor.html +41 -26
- package/admin/js/templates/form-submissions.html +78 -26
- package/admin/js/templates/layouts.html +126 -43
- package/admin/js/templates/menu-editor.html +57 -17
- package/admin/js/templates/menu-locations.html +54 -11
- package/admin/js/templates/my-profile.html +100 -12
- package/admin/js/templates/notifications.html +70 -10
- package/admin/js/templates/page-editor.html +47 -26
- package/admin/js/templates/plugin-code.html +91 -73
- package/admin/js/templates/plugins.html +100 -28
- package/admin/js/templates/project-detail.html +83 -145
- package/admin/js/templates/project-settings.html +88 -53
- package/admin/js/templates/role-editor.html +158 -83
- package/admin/js/templates/roles.html +78 -7
- package/admin/js/templates/search.html +193 -216
- package/admin/js/templates/settings.html +404 -472
- package/admin/js/templates/user-editor.html +150 -14
- package/admin/js/templates/users.html +69 -9
- package/admin/js/templates/view-editor.html +203 -180
- package/admin/js/templates/view-preview.html +45 -45
- package/admin/js/views/action-editor.js +7 -1
- package/admin/js/views/api-endpoint-editor.js +2 -3
- package/admin/js/views/api-tokens.js +62 -8
- package/admin/js/views/block-editor.js +14 -8
- package/admin/js/views/collection-editor.js +64 -5
- package/admin/js/views/collection-entries.js +50 -1
- package/admin/js/views/component-editor.js +2 -28
- package/admin/js/views/context-menu-editor.js +54 -55
- package/admin/js/views/dashboard.js +7 -1
- package/admin/js/views/effects.js +31 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +11 -1
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/layouts.js +81 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +21 -43
- package/admin/js/views/menu-locations.js +16 -1
- package/admin/js/views/my-profile.js +1 -1
- package/admin/js/views/notifications.js +18 -1
- package/admin/js/views/page-editor.js +26 -26
- package/admin/js/views/plugin-code.js +23 -4
- package/admin/js/views/plugins.js +97 -39
- package/admin/js/views/project-detail.js +9 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/role-editor.js +11 -3
- package/admin/js/views/roles.js +49 -4
- package/admin/js/views/search.js +7 -1
- package/admin/js/views/settings.js +13 -3
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +13 -4
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/package.json +1 -1
- package/plugins/_lib/admin/mail/compose-window.js +97 -40
- package/plugins/_lib/admin/mail/contacts.js +4 -1
- package/plugins/_lib/admin/mail/diagnostics-section.js +4 -1
- package/plugins/_lib/admin/mail/folder-tree.js +1 -1
- package/plugins/_lib/admin/mail/image-senders-section.js +69 -90
- package/plugins/_lib/admin/mail/mail.css +1 -1
- package/plugins/_lib/admin/mail/mail.html +1 -1
- package/plugins/_lib/admin/mail/reader-view.js +303 -44
- package/plugins/_lib/admin/mail/when-closed.js +26 -0
- package/plugins/_lib/mail/readRoutes.js +43 -1
- package/plugins/_lib/mail/unreadBadge.js +42 -0
- package/plugins/free-tier.lock.json +5 -5
- package/plugins/mail-reader/CLAUDE.md +1 -0
- package/plugins/mail-reader/plugin.js +3 -1
- package/plugins/mail-reader/plugin.json +5 -4
- package/server/routes/api/api-tokens.js +56 -0
- package/server/routes/api/auth.js +7 -1
- package/server/routes/api/collections.js +13 -1
- package/server/routes/api/dashboard.js +32 -4
- package/server/routes/api/effects.js +68 -13
- package/server/routes/api/forms.js +104 -13
- package/server/routes/api/layouts.js +93 -2
- package/server/routes/api/notifications.js +140 -14
- package/server/routes/api/plugins.js +135 -4
- package/server/routes/api/roles.js +111 -0
- package/server/routes/api/search.js +38 -4
- package/server/routes/api/settings.js +52 -0
- package/server/routes/api/users.js +65 -3
- package/server/routes/api/views.js +55 -1
- package/server/server.js +12 -0
- package/server/services/components.js +9 -0
- package/server/services/plugins.js +7 -0
- package/server/services/roles.js +3 -0
- package/server/services/search.js +58 -0
- package/server/services/users.js +16 -2
- package/server/services/views.js +14 -0
- package/admin/js/templates/dashboard/activity-feed.html +0 -3
- package/admin/js/templates/dashboard/cache.html +0 -32
- package/admin/js/templates/dashboard/health-detail.html +0 -2
- package/admin/js/templates/dashboard/journeys.html +0 -17
- package/admin/js/templates/dashboard/kpi-strip.html +0 -34
- package/admin/js/templates/dashboard/spike-feed.html +0 -3
- package/admin/js/templates/dashboard/top-pages.html +0 -3
- package/admin/js/templates/dashboard/traffic-chart.html +0 -3
- package/admin/js/templates/plugin-marketplace.html +0 -29
- package/admin/js/views/block-editor-enhance.js +0 -1
- package/admin/js/views/dashboard/lib/escape.js +0 -1
- package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
- package/admin/js/views/dashboard/widgets/cache.js +0 -1
- package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
- package/admin/js/views/dashboard/widgets/journeys.js +0 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
- package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
- package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
- package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
- package/admin/js/views/plugin-marketplace.js +0 -1
|
@@ -72,6 +72,7 @@ import {applyIdentity, findIdentity, identityFrom, swapSignature} from './identi
|
|
|
72
72
|
import {describeWhen, quickTimes} from './scheduling.js';
|
|
73
73
|
import {applyTemplate, expandTemplate} from './templates.js';
|
|
74
74
|
import {makeResizable} from './resizable.js';
|
|
75
|
+
import {whenClosed} from './when-closed.js';
|
|
75
76
|
import {
|
|
76
77
|
collectGroups,
|
|
77
78
|
currentFragment,
|
|
@@ -509,16 +510,6 @@ export function openCompose({
|
|
|
509
510
|
}
|
|
510
511
|
form.appendChild(bodyWrap);
|
|
511
512
|
|
|
512
|
-
// What the window opened showing, read back out of the thing showing it.
|
|
513
|
-
//
|
|
514
|
-
// NOT `draft.body`: the editor is seeded from `draft.html` and derives its
|
|
515
|
-
// own text from that, so the two already differed on every HTML reply -
|
|
516
|
-
// which is why discarding an untouched reply asked whether to lose
|
|
517
|
-
// changes nobody had made. A signature makes that worse, since it means
|
|
518
|
-
// even a new message opens "changed". Comparing against what was actually
|
|
519
|
-
// put in front of the person is both the fix and the only baseline that
|
|
520
|
-
// stays true once anything else seeds the body.
|
|
521
|
-
const openedText = editor ? editor.getText() : bodyInput.value;
|
|
522
513
|
|
|
523
514
|
// Attachments
|
|
524
515
|
// Seeded from the draft, so reopening one keeps the files that were
|
|
@@ -653,7 +644,13 @@ export function openCompose({
|
|
|
653
644
|
const saveDraftBtn = document.createElement('button');
|
|
654
645
|
saveDraftBtn.className = 'btn btn-sm btn-ghost';
|
|
655
646
|
saveDraftBtn.textContent = 'Save draft';
|
|
656
|
-
|
|
647
|
+
/**
|
|
648
|
+
* Save this message to the mailbox's Drafts folder - the button, and what
|
|
649
|
+
* leaving the screen does with a message that has changed.
|
|
650
|
+
*
|
|
651
|
+
* @returns {Promise<object|null>} the server's answer, or null if it failed
|
|
652
|
+
*/
|
|
653
|
+
async function saveDraft() {
|
|
657
654
|
saveDraftBtn.disabled = true;
|
|
658
655
|
say('Saving\u2026');
|
|
659
656
|
try {
|
|
@@ -663,47 +660,56 @@ export function openCompose({
|
|
|
663
660
|
replaces: savedUid
|
|
664
661
|
});
|
|
665
662
|
savedUid = result.uid ?? savedUid;
|
|
663
|
+
// Saved is the new baseline: closing now loses nothing.
|
|
664
|
+
savedSnapshot = snapshot();
|
|
666
665
|
say(`Saved to ${result.savedTo}.`, 'ok');
|
|
667
666
|
// The Drafts folder now holds a different set of UIDs, so a list
|
|
668
667
|
// showing it is out of date whether or not the window closes.
|
|
669
668
|
onSaved(result);
|
|
669
|
+
return result;
|
|
670
670
|
} catch (err) {
|
|
671
671
|
say(err.message, 'bad');
|
|
672
|
+
return null;
|
|
672
673
|
} finally {
|
|
673
674
|
saveDraftBtn.disabled = false;
|
|
674
675
|
}
|
|
675
|
-
}
|
|
676
|
+
}
|
|
677
|
+
saveDraftBtn.addEventListener('click', () => { saveDraft(); });
|
|
676
678
|
|
|
677
679
|
const cancel = document.createElement('button');
|
|
678
680
|
cancel.className = 'btn btn-sm btn-ghost';
|
|
679
681
|
cancel.textContent = 'Discard';
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
// the only place that text exists until it is saved.
|
|
682
|
+
/** What the message says now - compared with the last saved (or opened) state. */
|
|
683
|
+
function snapshot() {
|
|
683
684
|
const current = editor ? editor.getText() : bodyInput.value;
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
685
|
+
return JSON.stringify([subjectInput.value.trim(), current.trim(), toInput.value.trim(),
|
|
686
|
+
ccInput.value.trim(), bccInput.value.trim(), attachments.length]);
|
|
687
|
+
}
|
|
688
|
+
let savedSnapshot = null; // set once the window is built (below)
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Whether closing now would lose something.
|
|
692
|
+
*
|
|
693
|
+
* Compared with the last saved state, or what the window opened with - so
|
|
694
|
+
* a reopened draft closed unchanged does not ask, and a reply or forward,
|
|
695
|
+
* which opens with `Re:` / `Fwd:` and a quote already in it, does not
|
|
696
|
+
* count as unsaved work until the reader actually changes something.
|
|
697
|
+
*/
|
|
698
|
+
function unsaved() {
|
|
699
|
+
return savedSnapshot !== null && snapshot() !== savedSnapshot;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Discard, the X, Escape and the backdrop all go through slideover.close,
|
|
703
|
+
// which asks (see the guard at the end) - so this button just closes.
|
|
704
|
+
cancel.addEventListener('click', () => slideover.close());
|
|
705
|
+
|
|
706
|
+
/** The question the close guard asks, for what would be lost. */
|
|
707
|
+
function discardQuestion() {
|
|
708
|
+
return savedUid
|
|
709
|
+
? 'Close without saving? The changes since you last saved this draft will be lost.'
|
|
710
|
+
: 'Discard this message? It has not been saved as a draft.';
|
|
711
|
+
}
|
|
712
|
+
|
|
707
713
|
|
|
708
714
|
/**
|
|
709
715
|
* The message as it currently stands.
|
|
@@ -855,7 +861,7 @@ export function openCompose({
|
|
|
855
861
|
replaces: savedUid
|
|
856
862
|
});
|
|
857
863
|
|
|
858
|
-
slideover.close();
|
|
864
|
+
slideover.close(true);
|
|
859
865
|
|
|
860
866
|
// Three outcomes, three different things to say. A message held
|
|
861
867
|
// for ten seconds has NOT been sent, and saying it has is how
|
|
@@ -905,10 +911,61 @@ export function openCompose({
|
|
|
905
911
|
}
|
|
906
912
|
}, 60);
|
|
907
913
|
|
|
914
|
+
// The baseline for "changed": what the window opened showing, read back out
|
|
915
|
+
// of the thing showing it (taken once the editor has been seeded).
|
|
916
|
+
//
|
|
917
|
+
// NOT `draft.body`: the editor is seeded from `draft.html` and derives its
|
|
918
|
+
// own text from that, so the two already differed on every HTML reply -
|
|
919
|
+
// which is why discarding an untouched reply asked whether to lose
|
|
920
|
+
// changes nobody had made. A signature makes that worse, since it means
|
|
921
|
+
// even a new message opens "changed". Comparing against what was actually
|
|
922
|
+
// put in front of the person is both the fix and the only baseline that
|
|
923
|
+
// stays true once anything else seeds the body.
|
|
924
|
+
setTimeout(() => { if (savedSnapshot === null) savedSnapshot = snapshot(); }, 0);
|
|
925
|
+
|
|
926
|
+
// Every way out asks when something would be lost - Discard, the X, Escape
|
|
927
|
+
// and the backdrop all call close(). Once closed it goes straight through:
|
|
928
|
+
// a later close() (the cleanup below, or the screen being left) must not ask again.
|
|
929
|
+
const rawClose = slideover.close.bind(slideover);
|
|
930
|
+
let closed = false;
|
|
931
|
+
let asking = false;
|
|
932
|
+
const closedListeners = [];
|
|
933
|
+
slideover.close = async (force) => {
|
|
934
|
+
if (!closed && force !== true && unsaved()) {
|
|
935
|
+
if (asking) return undefined;
|
|
936
|
+
asking = true;
|
|
937
|
+
// Two different losses, and saying the wrong one is worse than
|
|
938
|
+
// saying nothing: one has never been saved, the other has changes
|
|
939
|
+
// since it was.
|
|
940
|
+
const ok = await E.confirm(discardQuestion());
|
|
941
|
+
asking = false;
|
|
942
|
+
if (!ok) return undefined;
|
|
943
|
+
}
|
|
944
|
+
if (closed) return undefined;
|
|
945
|
+
closed = true;
|
|
946
|
+
return rawClose();
|
|
947
|
+
};
|
|
948
|
+
|
|
908
949
|
// The editor is a live component with its own listeners and autosave.
|
|
909
|
-
slideover
|
|
950
|
+
whenClosed(slideover, () => {
|
|
951
|
+
closed = true;
|
|
910
952
|
editor?.destroy?.();
|
|
911
953
|
stopResizing();
|
|
912
954
|
for (const suggester of suggesters) suggester.destroy?.();
|
|
955
|
+
closedListeners.splice(0).forEach(fn => { try { fn(); } catch { /* ignore */ } });
|
|
956
|
+
// Domma hides a closed slideover rather than removing it: every message
|
|
957
|
+
// written would otherwise leave its window behind in the page.
|
|
958
|
+
setTimeout(() => { try { slideover.element?.remove(); } catch { /* gone */ } }, 400);
|
|
913
959
|
});
|
|
960
|
+
|
|
961
|
+
return {
|
|
962
|
+
/** Whether closing would lose something the reader wrote. */
|
|
963
|
+
dirty: () => !closed && unsaved(),
|
|
964
|
+
/** Save to Drafts now (what leaving the screen does with a changed message). */
|
|
965
|
+
saveDraft,
|
|
966
|
+
/** Close; `true` skips the question. */
|
|
967
|
+
close: (force) => slideover.close(force),
|
|
968
|
+
/** Called once, when the window has closed however it closed. */
|
|
969
|
+
onClosed: (fn) => { if (closed) fn(); else closedListeners.push(fn); }
|
|
970
|
+
};
|
|
914
971
|
}
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
* not hold. A 404 here is indistinguishable from "not installed", which is
|
|
29
29
|
* exactly the behaviour wanted.
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
// Core since 0.67.0 (it was the Contacts plugin, at /api/plugins/contacts/contacts:
|
|
32
|
+
// that address has 404'd ever since, so "Add sender to contacts" and group
|
|
33
|
+
// expansion in compose silently vanished).
|
|
34
|
+
const CONTACTS_ENDPOINT = '/api/contacts/contacts';
|
|
32
35
|
|
|
33
36
|
/**
|
|
34
37
|
* Fetch the address book once.
|
|
@@ -129,5 +129,8 @@ export function createDiagnosticsSection(host) {
|
|
|
129
129
|
container.appendChild(actions);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
id: 'diagnostics', label: 'Diagnostics', render,
|
|
134
|
+
help: 'What this screen can see about itself - whether it runs the build the server is serving, the mailbox and folder, where the list came from, and recent errors. Copy diagnostics puts it on the clipboard for a report.'
|
|
135
|
+
};
|
|
133
136
|
}
|
|
@@ -245,7 +245,7 @@ export function folderMenuItems(roots, {current = null, onPick, label = null}) {
|
|
|
245
245
|
return {
|
|
246
246
|
label: node.name,
|
|
247
247
|
icon: iconForFolder(node),
|
|
248
|
-
submenu: [target(node), {type: '
|
|
248
|
+
submenu: [target(node), {type: 'divider'}, ...walk(node.children)]
|
|
249
249
|
};
|
|
250
250
|
});
|
|
251
251
|
}
|
|
@@ -11,23 +11,29 @@
|
|
|
11
11
|
* @module _lib/admin/mail/image-senders-section
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/** The status line's colour, by tone - classes, so a theme override reaches it. */
|
|
15
|
+
const TONE = {ok: 'text-success', bad: 'text-danger', quiet: 'text-muted'};
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* A class rather than `style.color`: an inline colour cannot read a `--dm-*`
|
|
18
|
-
* token that a theme override has redefined, so a status set that way is the
|
|
19
|
-
* same shade in every theme.
|
|
20
|
-
*
|
|
21
|
-
* @param {HTMLElement} el
|
|
22
|
-
* @param {string} text
|
|
23
|
-
* @param {'ok'|'bad'|'quiet'} [tone]
|
|
24
|
-
* @param {string} [base] - the layout classes the line always carries
|
|
25
|
-
* @returns {void}
|
|
18
|
+
* The section's markup. Its own DOM, never passed through `_.render`, so the
|
|
19
|
+
* row's `{{address}}` is the list's template and not eaten on the way in.
|
|
26
20
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
const TEMPLATE = `
|
|
22
|
+
<div class="mail-image-senders">
|
|
23
|
+
<p class="text-sm text-muted mb-3">Remote images are blocked by default, because loading one tells the sender that the message was opened and who opened it. Senders listed here are trusted, and their images load without asking.</p>
|
|
24
|
+
<label class="mail-toggle-row text-sm">
|
|
25
|
+
<input type="checkbox" class="mail-remember" data-bind-checked="remember.value">
|
|
26
|
+
Offer to remember senders
|
|
27
|
+
</label>
|
|
28
|
+
<p class="text-sm text-muted mail-none" data-bind-hidden="senders.value.length">No senders trusted yet.</p>
|
|
29
|
+
<div class="mail-sender-list" data-each="senders.value key=address">
|
|
30
|
+
<div class="mail-box-row" data-address="{{address}}">
|
|
31
|
+
<span class="flex-1 text-sm mail-ellipsis">{{address}}</span>
|
|
32
|
+
<button type="button" class="btn btn-sm btn-ghost text-danger mail-untrust" data-bind-disabled="busy">Stop trusting</button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div data-bind-class="'text-sm mail-status mt-2 ' + statusClass.value" data-bind-text="status.value"></div>
|
|
36
|
+
</div>`;
|
|
31
37
|
|
|
32
38
|
/**
|
|
33
39
|
* @param {HTMLElement} container
|
|
@@ -43,89 +49,62 @@ async function render(container, ctx) {
|
|
|
43
49
|
const params = new URLSearchParams({account: ctx.account.id});
|
|
44
50
|
const data = await ctx.api(`${ctx.base}/image-senders?${params}`);
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
toggle.type = 'checkbox';
|
|
55
|
-
toggle.checked = data.remember !== false;
|
|
56
|
-
toggleRow.appendChild(toggle);
|
|
57
|
-
toggleRow.appendChild(document.createTextNode('Offer to remember senders'));
|
|
58
|
-
container.appendChild(toggleRow);
|
|
59
|
-
|
|
60
|
-
const list = document.createElement('div');
|
|
61
|
-
list.className = 'mail-sender-list';
|
|
62
|
-
container.appendChild(list);
|
|
63
|
-
|
|
64
|
-
const status = document.createElement('div');
|
|
65
|
-
status.className = 'text-sm mail-status text-muted mt-2';
|
|
66
|
-
container.appendChild(status);
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @param {string[]} senders
|
|
70
|
-
* @returns {void}
|
|
71
|
-
*/
|
|
72
|
-
function paint(senders) {
|
|
73
|
-
list.textContent = '';
|
|
74
|
-
if (!senders.length) {
|
|
75
|
-
const empty = document.createElement('p');
|
|
76
|
-
empty.className = 'text-sm text-muted mail-none';
|
|
77
|
-
empty.textContent = 'No senders trusted yet.';
|
|
78
|
-
list.appendChild(empty);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
52
|
+
// ---- State ----
|
|
53
|
+
const busy = new Set();
|
|
54
|
+
const list = M.observable((data.senders ?? []).map(address => ({address, busy: false})));
|
|
55
|
+
const remember = M.observable(data.remember !== false);
|
|
56
|
+
const status = M.observable('');
|
|
57
|
+
const tone = M.observable('quiet');
|
|
58
|
+
const say = (text, t = 'quiet') => { status.value = text ?? ''; tone.value = t; };
|
|
59
|
+
const paint = (senders) => { list.value = senders.map(address => ({address, busy: busy.has(address)})); };
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
remove.addEventListener('click', async () => {
|
|
96
|
-
remove.disabled = true;
|
|
97
|
-
try {
|
|
98
|
-
const query = new URLSearchParams({account: ctx.account.id, address});
|
|
99
|
-
const result = await ctx.api(`${ctx.base}/image-senders?${query}`, 'DELETE');
|
|
100
|
-
paint(result.senders);
|
|
101
|
-
say(status, `Images from ${address} will be blocked again.`,
|
|
102
|
-
'quiet', 'text-sm mail-status mt-2');
|
|
103
|
-
} catch (err) {
|
|
104
|
-
say(status, err.message, 'bad', 'text-sm mail-status mt-2');
|
|
105
|
-
remove.disabled = false;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
row.appendChild(remove);
|
|
109
|
-
list.appendChild(row);
|
|
61
|
+
async function untrust(address) {
|
|
62
|
+
busy.add(address);
|
|
63
|
+
paint(list.peek().map(r => r.address));
|
|
64
|
+
try {
|
|
65
|
+
const query = new URLSearchParams({account: ctx.account.id, address});
|
|
66
|
+
const result = await ctx.api(`${ctx.base}/image-senders?${query}`, 'DELETE');
|
|
67
|
+
busy.delete(address);
|
|
68
|
+
paint(result.senders ?? []);
|
|
69
|
+
say(`Images from ${address} will be blocked again.`);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
busy.delete(address);
|
|
72
|
+
paint(list.peek().map(r => r.address));
|
|
73
|
+
say(err.message, 'bad');
|
|
110
74
|
}
|
|
111
75
|
}
|
|
112
76
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
toggle.addEventListener('change', async () => {
|
|
77
|
+
async function saveRemember(wanted) {
|
|
78
|
+
remember.value = wanted;
|
|
116
79
|
try {
|
|
117
|
-
const result = await ctx.api(`${ctx.base}/image-senders/remember?${params}`, 'PUT',
|
|
118
|
-
|
|
119
|
-
status.className = 'text-sm mail-status mt-2 text-muted';
|
|
120
|
-
status.textContent = result.remember
|
|
121
|
-
? 'Senders can be remembered.'
|
|
122
|
-
: 'Remembering is off. The list is kept but ignored.';
|
|
80
|
+
const result = await ctx.api(`${ctx.base}/image-senders/remember?${params}`, 'PUT', {remember: wanted});
|
|
81
|
+
say(result.remember ? 'Senders can be remembered.' : 'Remembering is off. The list is kept but ignored.');
|
|
123
82
|
} catch (err) {
|
|
124
|
-
|
|
125
|
-
|
|
83
|
+
remember.value = !wanted;
|
|
84
|
+
say(err.message, 'bad');
|
|
126
85
|
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const holder = document.createElement('div');
|
|
89
|
+
holder.innerHTML = TEMPLATE.trim();
|
|
90
|
+
const root = holder.firstElementChild;
|
|
91
|
+
container.appendChild(root);
|
|
92
|
+
M.applyBindings({
|
|
93
|
+
senders: list, remember, status,
|
|
94
|
+
statusClass: M.computed(() => TONE[tone.value] ?? TONE.quiet)
|
|
95
|
+
}, root);
|
|
96
|
+
root.querySelector('.mail-remember').addEventListener('change', (event) => saveRemember(event.target.checked));
|
|
97
|
+
|
|
98
|
+
// One listener for every row's button - the rows are re-rendered by the list.
|
|
99
|
+
root.addEventListener('click', (event) => {
|
|
100
|
+
const button = event.target.closest('.mail-untrust');
|
|
101
|
+
const row = button?.closest('[data-address]');
|
|
102
|
+
if (row && !button.disabled) untrust(row.dataset.address);
|
|
127
103
|
});
|
|
128
104
|
}
|
|
129
105
|
|
|
130
106
|
/** The section both editions show. */
|
|
131
|
-
export const imageSendersSection = {
|
|
107
|
+
export const imageSendersSection = {
|
|
108
|
+
id: 'images', label: 'Images', render,
|
|
109
|
+
help: 'Pictures in mail are blocked until you ask for them, because loading one tells the sender you opened it. Senders listed here are trusted: their pictures load straight away.'
|
|
110
|
+
};
|
|
@@ -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:#fff;flex-shrink:0}.mail-selection span{flex:1;min-width:0}.mail-selection .btn{color:#fff;border:1px solid currentColor;background:transparent}.mail-selection .btn:hover{background:#ffffff26}.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:#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:#ffffff26;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-item.is-dragging{opacity:.45}.mail-folder.is-drop-target{background:var(--dm-primary, #2563eb);color:#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-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:#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}
|
|
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-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-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)}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<span data-icon="clock" data-icon-size="14"></span><span id="mail-outbox-count"></span>
|
|
34
34
|
</button>
|
|
35
35
|
<button class="btn btn-sm btn-ghost" id="mail-threads-btn" title="Group messages into conversations" aria-pressed="false" hidden>
|
|
36
|
-
<span data-icon="
|
|
36
|
+
<span data-icon="message-square" data-icon-size="14"></span>
|
|
37
37
|
</button>
|
|
38
38
|
<button class="btn btn-sm btn-ghost" id="mail-compose-btn" title="New message" hidden>
|
|
39
39
|
<span data-icon="edit-3" data-icon-size="14"></span>
|