feat(mail): return exact messages from Quick Access
Module Package Release / publish-packages (push) Successful in 12s
Module Package Release / publish-packages (push) Successful in 12s
This commit is contained in:
@@ -436,7 +436,9 @@ manifest = ModuleManifest(
|
|||||||
full_page_path="/mail",
|
full_page_path="/mail",
|
||||||
required_any=("mail:mailbox:read",),
|
required_any=("mail:mailbox:read",),
|
||||||
order=10,
|
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. "
|
"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 "
|
"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. "
|
"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 "
|
"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 "
|
"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."
|
"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 "
|
"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 "
|
"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, "
|
"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 "
|
"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 "
|
"noch eine GovOPlaN-Zustellung behauptet. Die gemeinsame Darstellung führt weder Kanalzustand noch "
|
||||||
"Zugangsdaten, Verwahrung, Zustelllogik oder Berechtigungen zusammen."
|
"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):
|
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:
|
def test_manifest_declares_optional_addresses_lookup(self) -> None:
|
||||||
manifest = get_manifest()
|
manifest = get_manifest()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useCallback, useRef, useState } from "react";
|
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 { Link } from "react-router";
|
||||||
import {
|
import {
|
||||||
DashboardWidgetList,
|
DashboardWidgetList,
|
||||||
|
Button,
|
||||||
DismissibleAlert,
|
DismissibleAlert,
|
||||||
EmailAddressInput,
|
EmailAddressInput,
|
||||||
LoadingFrame,
|
LoadingFrame,
|
||||||
@@ -34,10 +35,16 @@ type MailQuickAccessData = {
|
|||||||
|
|
||||||
type Props = Pick<
|
type Props = Pick<
|
||||||
QuickAccessToolRenderContext,
|
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 [composing, setComposing] = useState(false);
|
||||||
const [recipients, setRecipients] = useState<MailboxAddress[]>([]);
|
const [recipients, setRecipients] = useState<MailboxAddress[]>([]);
|
||||||
const [suggestions, setSuggestions] = useState<MailboxAddress[]>([]);
|
const [suggestions, setSuggestions] = useState<MailboxAddress[]>([]);
|
||||||
@@ -60,6 +67,8 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
|||||||
};
|
};
|
||||||
}, [settings]);
|
}, [settings]);
|
||||||
const { data, loading, error } = useDashboardWidgetData(load, 0);
|
const { data, loading, error } = useDashboardWidgetData(load, 0);
|
||||||
|
const selectingForCase = launchContext.activeObject?.ownerModule === "cases"
|
||||||
|
&& launchContext.activeObject.kind === "case";
|
||||||
|
|
||||||
const lookupRecipients = useCallback(async (query: string) => {
|
const lookupRecipients = useCallback(async (query: string) => {
|
||||||
const request = ++lookupRequestRef.current;
|
const request = ++lookupRequestRef.current;
|
||||||
@@ -85,6 +94,12 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
|||||||
return (
|
return (
|
||||||
<LoadingFrame loading={loading} label="i18n:govoplan-mail.loading_messages.4294022c">
|
<LoadingFrame loading={loading} label="i18n:govoplan-mail.loading_messages.4294022c">
|
||||||
{error ? <DismissibleAlert tone="warning" resetKey={error}>{error}</DismissibleAlert> : null}
|
{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
|
<DashboardWidgetList
|
||||||
emptyText={data?.available ? "i18n:govoplan-mail.no_messages_in_this_folder.5c7fa25d" : "i18n:govoplan-mail.select_an_imap_profile.5445648c"}
|
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) => ({
|
items={(data?.messages ?? []).map((message) => ({
|
||||||
@@ -93,12 +108,27 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
|||||||
detail: message.from_header || data?.profileName,
|
detail: message.from_header || data?.profileName,
|
||||||
meta: formatMessageDate(message.date),
|
meta: formatMessageDate(message.date),
|
||||||
leading: <Mail size={17} aria-hidden="true" />,
|
leading: <Mail size={17} aria-hidden="true" />,
|
||||||
to: mailboxMessageLaunchPath(data!.profileId!, message),
|
to: selectingForCase
|
||||||
state: quickAccessLaunchState(launchContext),
|
? undefined
|
||||||
onClick: close
|
: 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">
|
<div className="mail-quick-compose" aria-label="i18n:govoplan-mail.compose">
|
||||||
<label>i18n:govoplan-mail.recipients</label>
|
<label>i18n:govoplan-mail.recipients</label>
|
||||||
<EmailAddressInput
|
<EmailAddressInput
|
||||||
@@ -125,7 +155,13 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="dashboard-contribution-footer">
|
{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}>
|
<button type="button" className="btn btn-secondary" onClick={() => setComposing((current) => !current)} aria-expanded={composing}>
|
||||||
<Pencil size={15} aria-hidden="true" /> i18n:govoplan-mail.compose
|
<Pencil size={15} aria-hidden="true" /> i18n:govoplan-mail.compose
|
||||||
</button>
|
</button>
|
||||||
@@ -147,7 +183,7 @@ export default function MailQuickAccess({ settings, launchContext, close }: Prop
|
|||||||
>
|
>
|
||||||
<ExternalLink size={15} aria-hidden="true" /> i18n:govoplan-mail.open_mail
|
<ExternalLink size={15} aria-hidden="true" /> i18n:govoplan-mail.open_mail
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>}
|
||||||
</LoadingFrame>
|
</LoadingFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user