feat(mail): return exact messages from Quick Access
This commit is contained in:
@@ -436,7 +436,9 @@ manifest = ModuleManifest(
|
||||
full_page_path="/mail",
|
||||
required_any=("mail:mailbox:read",),
|
||||
order=10,
|
||||
modes=("browse", "compose"),
|
||||
modes=("browse", "compose", "select"),
|
||||
returned_reference_kinds=("mail.message",),
|
||||
help_context_id="mail.quick_access.messages",
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -621,6 +623,10 @@ manifest = ModuleManifest(
|
||||
"appears inside the shared Messages drawer alongside independent Postbox and future chat contributions. "
|
||||
"Recent entries open the exact authorized profile, folder, and message; Drafts resolves the configured or "
|
||||
"provider-detected Drafts folder, and the full Mail page keeps the versioned Quick Access return context. "
|
||||
"When an active Case requests a selection, choosing a recent message returns only its tenant-bound profile, "
|
||||
"folder, UID version, label, and owner route through the shared result contract. Cases discards the label and "
|
||||
"does not receive headers, participants, preview, body, attachments, credentials, or an access decision; Mail "
|
||||
"reauthorizes the mailbox list now and the exact message again whenever its owner route is opened. "
|
||||
"Compose deliberately launches the user's configured mail application because GovOPlaN Mail's mailbox is "
|
||||
"read-only; it neither selects a GovOPlaN transport profile nor claims a GovOPlaN delivery. The shared drawer "
|
||||
"does not merge channel state, credentials, custody, delivery semantics, or authorization."
|
||||
@@ -639,6 +645,10 @@ manifest = ModuleManifest(
|
||||
"Aktuelle Einträge öffnen das genaue berechtigte Profil, den Ordner und die Nachricht; Entwürfe verwendet "
|
||||
"den konfigurierten oder vom Anbieter erkannten Entwurfsordner. Die vollständige Mail-Seite erhält den "
|
||||
"versionierten Rücksprungkontext. Verfassen öffnet bewusst die konfigurierte Mail-Anwendung des Benutzers, "
|
||||
"Bei einem aktiven Vorgang gibt die Auswahl einer aktuellen Nachricht nur den mandantengebundenen Profil-, "
|
||||
"Ordner- und UID-Verweis sowie die Eigentümerroute über den gemeinsamen Ergebnisvertrag zurück. Vorgänge "
|
||||
"übernehmen weder Kopfzeilen, Beteiligte, Vorschau, Inhalt, Anlagen, Zugangsdaten noch eine Zugriffsentscheidung; "
|
||||
"Mail prüft den Zugriff beim Auflisten und beim späteren Öffnen erneut. "
|
||||
"da das GovOPlaN-Mail-Postfach nur lesend arbeitet; dabei wird weder ein GovOPlaN-Transportprofil gewählt "
|
||||
"noch eine GovOPlaN-Zustellung behauptet. Die gemeinsame Darstellung führt weder Kanalzustand noch "
|
||||
"Zugangsdaten, Verwahrung, Zustelllogik oder Berechtigungen zusammen."
|
||||
|
||||
@@ -9,6 +9,15 @@ from govoplan_mail.backend.manifest import _mail_retirement_provider, get_manife
|
||||
|
||||
|
||||
class MailManifestTests(unittest.TestCase):
|
||||
def test_mail_quick_access_can_return_exact_message_references(self) -> None:
|
||||
frontend = get_manifest().frontend
|
||||
self.assertIsNotNone(frontend)
|
||||
tool = next(
|
||||
item for item in frontend.quick_access_tools if item.id == "mail.messages" # type: ignore[union-attr]
|
||||
)
|
||||
self.assertIn("select", tool.modes)
|
||||
self.assertEqual(("mail.message",), tool.returned_reference_kinds)
|
||||
|
||||
def test_manifest_declares_optional_addresses_lookup(self) -> None:
|
||||
manifest = get_manifest()
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/mail-webui",
|
||||
"version": "0.1.18",
|
||||
"version": "0.1.19",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { ExternalLink, FilePenLine, Mail, Pencil } from "lucide-react";
|
||||
import { ExternalLink, FilePenLine, Mail, Pencil, X } from "lucide-react";
|
||||
import { Link } from "react-router";
|
||||
import {
|
||||
DashboardWidgetList,
|
||||
Button,
|
||||
DismissibleAlert,
|
||||
EmailAddressInput,
|
||||
LoadingFrame,
|
||||
@@ -34,10 +35,16 @@ type MailQuickAccessData = {
|
||||
|
||||
type Props = Pick<
|
||||
QuickAccessToolRenderContext,
|
||||
"settings" | "launchContext" | "close"
|
||||
"settings" | "launchContext" | "complete" | "cancel" | "close"
|
||||
>;
|
||||
|
||||
export default function MailQuickAccess({ settings, launchContext, close }: Props) {
|
||||
export default function MailQuickAccess({
|
||||
settings,
|
||||
launchContext,
|
||||
complete,
|
||||
cancel,
|
||||
close
|
||||
}: Props) {
|
||||
const [composing, setComposing] = useState(false);
|
||||
const [recipients, setRecipients] = useState<MailboxAddress[]>([]);
|
||||
const [suggestions, setSuggestions] = useState<MailboxAddress[]>([]);
|
||||
@@ -60,6 +67,8 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
||||
};
|
||||
}, [settings]);
|
||||
const { data, loading, error } = useDashboardWidgetData(load, 0);
|
||||
const selectingForCase = launchContext.activeObject?.ownerModule === "cases"
|
||||
&& launchContext.activeObject.kind === "case";
|
||||
|
||||
const lookupRecipients = useCallback(async (query: string) => {
|
||||
const request = ++lookupRequestRef.current;
|
||||
@@ -85,6 +94,12 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
||||
return (
|
||||
<LoadingFrame loading={loading} label="i18n:govoplan-mail.loading_messages.4294022c">
|
||||
{error ? <DismissibleAlert tone="warning" resetKey={error}>{error}</DismissibleAlert> : null}
|
||||
{selectingForCase ? (
|
||||
<p className="muted small-note">
|
||||
Select an authorized exact message for {launchContext.activeObject?.label}.
|
||||
Mail content remains in Mail and access is checked again when opened.
|
||||
</p>
|
||||
) : null}
|
||||
<DashboardWidgetList
|
||||
emptyText={data?.available ? "i18n:govoplan-mail.no_messages_in_this_folder.5c7fa25d" : "i18n:govoplan-mail.select_an_imap_profile.5445648c"}
|
||||
items={(data?.messages ?? []).map((message) => ({
|
||||
@@ -93,12 +108,27 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
||||
detail: message.from_header || data?.profileName,
|
||||
meta: formatMessageDate(message.date),
|
||||
leading: <Mail size={17} aria-hidden="true" />,
|
||||
to: mailboxMessageLaunchPath(data!.profileId!, message),
|
||||
state: quickAccessLaunchState(launchContext),
|
||||
onClick: close
|
||||
to: selectingForCase
|
||||
? undefined
|
||||
: mailboxMessageLaunchPath(data!.profileId!, message),
|
||||
state: selectingForCase ? undefined : quickAccessLaunchState(launchContext),
|
||||
onClick: selectingForCase ? () => complete({
|
||||
contractVersion: "1",
|
||||
outcome: "completed",
|
||||
action: "selected",
|
||||
reference: {
|
||||
ownerModule: "mail",
|
||||
kind: "message",
|
||||
objectId: `${data!.profileId!}:${message.folder}:${message.uid}`,
|
||||
tenantId: launchContext.tenantId,
|
||||
label: message.subject || "Mail message",
|
||||
version: message.uid,
|
||||
path: mailboxMessageLaunchPath(data!.profileId!, message)
|
||||
}
|
||||
}) : close
|
||||
}))}
|
||||
/>
|
||||
{composing ? (
|
||||
{!selectingForCase && composing ? (
|
||||
<div className="mail-quick-compose" aria-label="i18n:govoplan-mail.compose">
|
||||
<label>i18n:govoplan-mail.recipients</label>
|
||||
<EmailAddressInput
|
||||
@@ -125,7 +155,13 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{selectingForCase ? (
|
||||
<div className="dashboard-contribution-footer">
|
||||
<Button onClick={() => cancel("user")}>
|
||||
<X size={15} aria-hidden="true" /> Cancel selection
|
||||
</Button>
|
||||
</div>
|
||||
) : <div className="dashboard-contribution-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={() => setComposing((current) => !current)} aria-expanded={composing}>
|
||||
<Pencil size={15} aria-hidden="true" /> i18n:govoplan-mail.compose
|
||||
</button>
|
||||
@@ -147,7 +183,7 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
||||
>
|
||||
<ExternalLink size={15} aria-hidden="true" /> i18n:govoplan-mail.open_mail
|
||||
</Link>
|
||||
</div>
|
||||
</div>}
|
||||
</LoadingFrame>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user