add ability to include custom css & js in web interface (webmail, webaccount, webadmin), and use css variables in webmail for easier customization

if files {webmail,webaccount,webadmin}.{css,js} exist in the configdir (where
the mox.conf file lives), their contents are included in the web apps.

the webmail now uses css variables, mostly for colors. so you can write a
custom webmail.css that changes the variables, e.g.:

	:root {
		--color: blue
	}

you can also look at css class names and override their styles.

in the future, we may want to make some css variables configurable in the
per-user settings in the webmail. should reduce the number of variables first.

any custom javascript is loaded first. if it defines a global function
"moxBeforeDisplay", that is called each time a page loads (after
authentication) with the DOM element of the page content as parameter. the
webmail is a single persistent page. this can be used to make some changes to
the DOM, e.g. inserting some elements. we'll have to see how well this works in
practice. perhaps some patterns emerge (e.g. adding a logo), and we can make
those use-cases easier to achieve.

helps partially with issue #114, and based on questions from laura-lilly on
matrix.
This commit is contained in:
Mechiel Lukkien
2024-11-29 10:17:07 +01:00
parent 9e8c8ca583
commit 96d86ad6f1
20 changed files with 838 additions and 418 deletions

View File

@ -5,6 +5,8 @@ declare let page: HTMLElement
declare let moxversion: string
declare let moxgoos: string
declare let moxgoarch: string
// From customization script.
declare let moxBeforeDisplay: (webmailroot: HTMLElement) => void
const login = async (reason: string) => {
return new Promise<string>((resolve: (v: string) => void, _) => {
@ -737,7 +739,7 @@ const index = async () => {
onchange()
}
dom._kids(page,
const root = dom.div(
crumbs('Mox Account'),
dom.div(
'Default domain: ',
@ -1390,36 +1392,40 @@ const index = async () => {
footer,
)
// Try to show the progress of an earlier import session. The user may have just
// refreshed the browser.
let importToken: string
try {
importToken = window.sessionStorage.getItem('ImportToken') || ''
} catch (err) {
console.log('looking up ImportToken in session storage', {err})
return
}
if (!importToken) {
return
}
importFieldset.disabled = true
dom._kids(importProgress,
dom.div(
dom.div('Reconnecting to import...'),
),
)
importProgress.style.display = ''
importTrack(importToken)
.catch(() => {
if (window.confirm('Error reconnecting to import. Remove this import session?')) {
window.sessionStorage.removeItem('ImportToken')
dom._kids(importProgress)
importProgress.style.display = 'none'
;(async () => {
// Try to show the progress of an earlier import session. The user may have just
// refreshed the browser.
let importToken: string
try {
importToken = window.sessionStorage.getItem('ImportToken') || ''
} catch (err) {
console.log('looking up ImportToken in session storage', {err})
return
}
})
.finally(() => {
importFieldset.disabled = false
})
if (!importToken) {
return
}
importFieldset.disabled = true
dom._kids(importProgress,
dom.div(
dom.div('Reconnecting to import...'),
),
)
importProgress.style.display = ''
importTrack(importToken)
.catch(() => {
if (window.confirm('Error reconnecting to import. Remove this import session?')) {
window.sessionStorage.removeItem('ImportToken')
dom._kids(importProgress)
importProgress.style.display = 'none'
}
})
.finally(() => {
importFieldset.disabled = false
})
})()
return root
}
const destination = async (name: string) => {
@ -1552,7 +1558,7 @@ const destination = async (name: string) => {
const addresses = [name, ...Object.keys(acc.Destinations || {}).filter(a => !a.startsWith('@') && a !== name)]
dom._kids(page,
return dom.div(
crumbs(
crumblink('Mox Account', '#'),
'Destination ' + name,
@ -1664,13 +1670,18 @@ const init = async () => {
const t = h.split('/')
page.classList.add('loading')
try {
let root: HTMLElement
if (h === '') {
await index()
root = await index()
} else if (t[0] === 'destinations' && t.length === 2) {
await destination(t[1])
root = await destination(t[1])
} else {
dom._kids(page, 'page not found')
root = dom.div('page not found')
}
if ((window as any).moxBeforeDisplay) {
moxBeforeDisplay(root)
}
dom._kids(page, root)
} catch (err) {
console.log({err})
window.alert('Error: ' + errmsg(err))