diff --git a/frontend/src/ContactOverlay.vue b/frontend/src/ContactOverlay.vue index bdc35b5..2ebf18f 100644 --- a/frontend/src/ContactOverlay.vue +++ b/frontend/src/ContactOverlay.vue @@ -20,10 +20,11 @@ const emit = defineEmits(['send', 'close']) const admin = computed(() => props.state.mode === 'admin') // The visitor dialog's title is the localized site contact label (the -// {contact:} buttons' default label); the mailto link carries the -// localized subject. +// {contact:} buttons' default label). The mailto href carries the +// server-encoded "Brand " recipient and the localized subject; +// the link text is the bare address. const mailto = computed( - () => `mailto:${props.state.email}?subject=${encodeURIComponent(props.state.subject)}` + () => `mailto:${props.state.mailtoTo}?subject=${encodeURIComponent(props.state.subject)}` ) // Escape closes, like the paskia dialogs whose backdrop we share. diff --git a/frontend/src/contact-main.js b/frontend/src/contact-main.js index 7c8b5d2..db0bfd4 100644 --- a/frontend/src/contact-main.js +++ b/frontend/src/contact-main.js @@ -31,6 +31,7 @@ const state = reactive({ // link with subject prefilled. label: 'Contact us', email: '', + mailtoTo: '', // RFC-encoded "Brand " recipient for the mailto href subject: '', }) @@ -145,6 +146,7 @@ function onPow(msg) { function onEmail(msg) { state.email = msg.email || '' + state.mailtoTo = msg.to || '' // RFC-encoded "Brand " recipient } // An upload hit the end of the PoW quota: re-register (the server pushes diff --git a/pagerite/messages.py b/pagerite/messages.py index 1595be4..554ca20 100644 --- a/pagerite/messages.py +++ b/pagerite/messages.py @@ -265,7 +265,17 @@ async def visitor_proven(ws: WebSocket, raw: dict) -> bytes | None: _quota[key] = POW_QUOTA await ws.send_json({"type": "pow", "quota": POW_QUOTA}) if data.contact_email: - await ws.send_json({"type": "contact-email", "email": data.contact_email}) + # The mailto recipient is "Brand " (RFC-encoded here; the + # link text is the bare address, the subject is the client's + # localized contact_subject). + recipient = ( + quote(f"{data.brand} <{data.contact_email}>", safe="@") + if data.brand.strip() + else quote(data.contact_email, safe="@") + ) + await ws.send_json( + {"type": "contact-email", "email": data.contact_email, "to": recipient} + ) return key