contact messages: label is the enable switch; single-line settings row

Data.contact_enabled is gone: an empty contact label disables messaging
(no buttons, no visitor icon, sends rejected). The settings row is one
line — flag selector, label, email, subject as small boxes with tooltip
instructions — and saves re-render the page in place so button/dialog
strings update without a reload; the open dialog also re-reads its
strings on page swaps. The built-in Contact us default survives only as
the inert preview placeholder.
This commit is contained in:
LeoVasanko committed 2026-09-28 15:43:01 +00:00
1 parent 59fe426e5e
commit 8d577dc56e
8 files changed
+119 -114

No files matched your search

+52 -60
View File
@@ -62,15 +62,14 @@ const brand = ref('')
const theme = ref('')
// --- Messaging (contact) ---------------------------------------------------
// Site-wide contact settings: the enabled toggle (combined with the
// section header) and mailto address are global; the label (the
// {contact:} buttons' default and the visitor dialog's title) and the
// email subject are per-language strings — the LangSelect (the article
// editor's flag dropdown, on the shared shell-wide editorLang) switches
// the two text fields between the original and a translation override
// (stored in the chunk-keyed trans store; auto translators fill the same
// store, so this is the manual-override UI).
const contactEnabled = ref(true)
// One-line contact settings. The label (the {contact:} buttons' label and
// the visitor dialog's title) doubles as the on/off switch: empty =
// disabled. The label and email subject are per-language strings — the
// LangSelect (the article editor's flag dropdown, on the shared shell-wide
// editorLang) switches the two fields between the original and a
// translation override (stored in the chunk-keyed trans store; auto
// translators fill the same store, so this is the manual-override UI).
// The email is global; its reveal after the PoW solve is server-side.
const contactEmail = ref('')
const contactLabel = ref('')
const contactSubject = ref('')
@@ -135,10 +134,18 @@ async function saveContact(tag = lang.value, label = '', subject = '') {
}),
})
saveError.value = res.ok ? '' : '⚠️ changes could not be saved'
if (res.ok) dropPageCache()
if (res.ok) rerender()
return
}
saveSettings()
if (await saveSettings()) rerender()
}
// The rendered {contact:} buttons are server HTML: after a save the page
// re-renders (like a theme change), updating buttons and dialog strings
// without a reload.
async function rerender() {
dropPageCache()
loadPlain(path.value)
}
// Theme options come from the backend (theme folders on disk, see GET
// /_api/settings), so added themes need no frontend changes.
@@ -171,7 +178,6 @@ async function loadSettings() {
]
transition.value = s.transition || 'cube'
transitionOptions.value = s.transitions || []
contactEnabled.value = s.contact_enabled !== false
contactEmail.value = s.contact_email || ''
contactLabelOrig.value = s.contact_label || ''
contactSubjectOrig.value = s.contact_subject || ''
@@ -348,7 +354,6 @@ async function saveSettings(opts = {}) {
custom_css: customCss.value,
brand_html: brandHtml.value,
transition: transition.value,
contact_enabled: contactEnabled.value,
contact_email: contactEmail.value,
// The originals: while a translation override is being edited the
// input refs hold the override, not the original.
@@ -363,6 +368,7 @@ async function saveSettings(opts = {}) {
} else {
saveError.value = '⚠️ changes could not be saved'
}
return res.ok
}
async function onThemeChange() {
@@ -758,51 +764,34 @@ onUnmounted(() => {
</section>
<section class="block">
<div class="block-head">
<div class="block-head msg-head">
<LangSelect
v-model="lang"
:options="contactLangOptions"
title="Language of the label/subject strings (auto translators fill these too; this edits the manual override)"
/>
<label
class="msg-toggle"
title="Visitor messaging ({contact:} buttons, corner icon)"
>
<input v-model="contactEnabled" type="checkbox" @change="saveSettings()" />
<span class="field-label">messaging</span>
</label>
</div>
<div class="field-grid">
<label class="field">
<span class="field-label">email</span>
<input
v-model="contactEmail"
class="text-input"
title="Contact email — a mailto link in the visitor dialog (empty = no link)"
:disabled="!!translating"
@input="debounce('contact-email', () => saveSettings())"
/>
</label>
<label class="field">
<span class="field-label">label</span>
<input
v-model="contactLabel"
class="text-input"
title="Default {contact:} button label and visitor dialog title (empty = built-in default)"
:placeholder="translating ? contactLabelOrig : 'Contact us'"
@input="onContactInput"
/>
</label>
<label class="field">
<span class="field-label">subject</span>
<input
v-model="contactSubject"
class="text-input"
title="Subject for the mailto link (empty = the label)"
:placeholder="translating ? contactSubjectOrig : ''"
@input="onContactInput"
/>
</label>
<input
v-model="contactLabel"
class="text-input"
:placeholder="translating ? contactLabelOrig : 'Contact us'"
title="Messaging: the {contact:} button label and visitor dialog title — empty disables messaging"
@input="onContactInput"
/>
<input
v-model="contactEmail"
class="text-input"
placeholder="email"
title="Contact email — a mailto link in the visitor dialog, revealed only after the proof of work is solved (empty = no link)"
:disabled="!!translating"
@input="debounce('contact-email', () => saveSettings())"
/>
<input
v-model="contactSubject"
class="text-input"
:placeholder="translating ? contactSubjectOrig : 'subject'"
title="Subject for the mailto link (empty = the label)"
@input="onContactInput"
/>
</div>
</section>
@@ -936,13 +925,16 @@ onUnmounted(() => {
font-size: 1rem;
}
/* The messaging block's single-line header: the article editor's flag
selector, then the enabled toggle fused with the section label. */
.msg-toggle {
display: flex;
align-items: center;
gap: 0.4rem;
cursor: pointer;
/* The messaging block: one line — the article editor's flag selector,
then small text boxes (label = the enable switch, email, subject). */
.msg-head {
flex-wrap: wrap;
}
.msg-head .text-input {
flex: 1 1 7rem;
min-width: 5rem;
font-size: 0.85rem;
}
.text-input {
+5
View File
@@ -54,6 +54,11 @@ function readContactStrings() {
// PoW is solved and stays valid for the session.
}
// Page swaps (fetch navigation, editor re-renders after settings saves)
// carry fresh per-language config: keep the dialog strings in sync
// without reopening.
addEventListener('pagerite:page-fetched', readContactStrings)
// The panel is main content, not a sidebar: while open it holds paskia's
// shared blur backdrop (refcounted, z-index 1099; the overlay itself sits
// at 1100, below the auth iframe's 9999).