add footer
This commit is contained in:
@ -13,4 +13,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Password sign-in through `POST /api/login` and optional connect-with-token from Admin ? Tokens. MFA accounts continue in-app (`api/login/mfa/{loginCode}`) until a token is issued. `loginCode` stays in the main process; a pasted Admin token skips MFA.
|
||||
- Session stored in `userData`, encrypted with `safeStorage` when the OS allows it. The renderer never receives the token.
|
||||
- Logged-in chrome matches TTP: navy header, Font Awesome 6.7.1 / Bootstrap 5.3, centered full-width search, notifications and messages dropdowns, avatar account menu. Profile settings (avatar, name, gender, newsletter, timezone, date/time, page size, dark mode) live in-app and save through `POST /api/profile/update`. Email, password, and phone open the connected site.
|
||||
- After sign-in, Capsule loads `GET /api/profile` plus notifications and messages. Search, contact, and bug reports use the matching user-token endpoints. Disabled plugins show an unavailable note instead of demo data.
|
||||
- After sign-in, Capsule loads `GET /api/profile` plus notifications and messages. Search uses the matching user-token endpoint. Contact and bug reports live on footer pages (`#/contact`, `#/bugreport`) instead of the dashboard. Disabled plugins show an unavailable note instead of demo data.
|
||||
- Footer chrome matches TTP copy and socials. The upper band keeps a dark-mode toggle, Privacy Policy, Terms of Service, Contact, and Report a Bug. There is no subscribe box.
|
||||
|
||||
@ -29,7 +29,7 @@ All HTTP runs in the **main process**. The renderer never sees the token and nev
|
||||
| Search | `GET /api/search` | Header search. `q`, `resource`, `page`. |
|
||||
| Profile save | `POST /api/profile/update` | Name, avatar, prefs. |
|
||||
| Mail / notices | `POST /api/messages/?`, `POST /api/notifications/?` | View, reply, create, read, delete. |
|
||||
| Contact / bugs | `POST /api/contact`, `POST /api/bugreport` | Dashboard forms when those plugins are enabled. |
|
||||
| Contact / bugs | `POST /api/contact`, `POST /api/bugreport` | Footer pages when those plugins are enabled. |
|
||||
| Existing token | Admin ? Tokens | Personal or app token. A user token hydrates the workspace; an app token can connect but cannot call the user API. |
|
||||
|
||||
The token is stored under Electron `userData` (`session.json`). `safeStorage` encrypts it when the OS keychain is available. MFA `loginCode` is not stored.
|
||||
@ -44,7 +44,7 @@ App-facing pairing notes live with the PHP app: `repos/ttp/docs/capsule.md`.
|
||||
| `src/preload/` | `window.capsule` bridge |
|
||||
| `src/renderer/` | Login, MFA, TTP-styled chrome, and live API views |
|
||||
|
||||
The logged-in header follows the public TTP shell (`text-bg-dark`, FA 6.7.1, Bootstrap 5.3). Search stays visible and centered. Account is a top-right dropdown like the site. Notifications and messages are the same bell / envelope menus. Profile edit covers User CP preferences except email, password, and phone ? those open `{site}/usercp/?`. Lists load from the site API after sign-in.
|
||||
The logged-in header follows the public TTP shell (`text-bg-dark`, FA 6.7.1, Bootstrap 5.3). Search stays visible and centered. Account is a top-right dropdown like the site. Notifications and messages are the same bell / envelope menus. Profile edit covers User CP preferences except email, password, and phone ? those open `{site}/usercp/?`. Lists load from the site API after sign-in. The footer matches TTP copyright and social icons. Above that: dark-mode toggle, Privacy Policy, Terms of Service, Contact, and Report a Bug. No subscribe box. Privacy and terms open the connected site; contact and bug reports stay in-app.
|
||||
|
||||
## Remote
|
||||
|
||||
|
||||
@ -344,59 +344,171 @@
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="col-12 col-lg-10 mx-auto p-4 rounded shadow-sm context-main-bg">
|
||||
<h1 class="h3">Dashboard</h1>
|
||||
<h1 class="h3">Workspace</h1>
|
||||
<p class="text-muted" id="home-note">
|
||||
Search, notifications, messages, and profile are here. Site API wiring is the next
|
||||
step.
|
||||
</p>
|
||||
<p class="text-muted" id="home-note"></p>
|
||||
<p class="mb-0">
|
||||
Connected to <a id="home-site" href="#" target="_blank" rel="noreferrer"></a>
|
||||
as <strong id="home-username"></strong>.
|
||||
</p>
|
||||
<p id="home-plugins" class="small text-muted mb-4"></p>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6" id="contact-panel" hidden>
|
||||
<h2 class="h5">Contact</h2>
|
||||
<form id="contact-form">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="contact-name">Name</label>
|
||||
<input id="contact-name" class="form-control" name="name" type="text" required />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="contact-email">Email</label>
|
||||
<input id="contact-email" class="form-control" name="email" type="email" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="contact-entry">Message</label>
|
||||
<textarea id="contact-entry" class="form-control" name="entry" rows="3" required></textarea>
|
||||
</div>
|
||||
<p id="contact-status" class="small" hidden></p>
|
||||
<button class="btn btn-primary" type="submit">Send</button>
|
||||
</form>
|
||||
<p id="home-plugins" class="small text-muted mb-0"></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="view-contact" class="view" hidden>
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="context-main-bg container py-2 my-2 py-lg-4 my-lg-4">
|
||||
<h1 class="h2 text-center mb-4">Contact Us</h1>
|
||||
<p id="contact-unavailable" class="text-muted text-center" hidden></p>
|
||||
<div id="contact-body">
|
||||
<div class="col-12 col-lg-6 offset-lg-3">
|
||||
<p class="text-center text-lg-start">
|
||||
Here at <strong id="contact-sitename">this site</strong>, we highly value your
|
||||
feedback. We constantly strive to provide our users with the highest level of
|
||||
quality in everything we do.
|
||||
</p>
|
||||
<p class="text-center text-lg-start">
|
||||
If you would like to provide any suggestions or comments on our service, we ask
|
||||
that you please fill out the quick form below and let us know what's on your mind.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6" id="bugreport-panel" hidden>
|
||||
<h2 class="h5">Bug report</h2>
|
||||
<form id="bugreport-form">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="bug-url">Page URL</label>
|
||||
<input id="bug-url" class="form-control" name="url" type="url" required />
|
||||
<form id="contact-form">
|
||||
<div class="mb-3 row">
|
||||
<label for="contact-name" class="col-lg-3 col-form-label text-lg-end">Name:</label>
|
||||
<div class="col-lg-6">
|
||||
<input
|
||||
id="contact-name"
|
||||
class="form-control"
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
autocomplete="name"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="bug-ourl">Expected URL <span class="text-muted">(optional)</span></label>
|
||||
<input id="bug-ourl" class="form-control" name="ourl" type="url" />
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label for="contact-email" class="col-lg-3 col-form-label text-lg-end">
|
||||
E-mail: (optional)
|
||||
</label>
|
||||
<div class="col-lg-6">
|
||||
<input
|
||||
id="contact-email"
|
||||
class="form-control"
|
||||
name="email"
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="bug-repeat" />
|
||||
<label class="form-check-label" for="bug-repeat">This happens every time</label>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label for="contact-entry" class="col-lg-3 col-form-label text-lg-end">Feedback:</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea
|
||||
id="contact-entry"
|
||||
class="form-control"
|
||||
name="entry"
|
||||
rows="6"
|
||||
maxlength="2000"
|
||||
required
|
||||
></textarea>
|
||||
<small class="form-text text-muted">Max: 2000 characters</small>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="bug-entry">What happened</label>
|
||||
<textarea id="bug-entry" class="form-control" name="entry" rows="3" required></textarea>
|
||||
</div>
|
||||
<p id="contact-status" class="small text-center" hidden></p>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-primary btn-lg" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="view-bugreport" class="view" hidden>
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="col-12 col-sm-10 col-lg-8 mx-auto p-4 rounded shadow-sm context-main-bg">
|
||||
<h1 class="h2 text-center mb-4">Report a Bug</h1>
|
||||
<hr />
|
||||
<p id="bug-unavailable" class="text-muted text-center" hidden></p>
|
||||
<div id="bug-body">
|
||||
<p class="text-center text-sm-start">
|
||||
Thank you for visiting our bug reporting page. We value our users' input highly and
|
||||
in an effort to better serve your needs, please fill out the form below to help us
|
||||
address this issue.
|
||||
</p>
|
||||
<p class="text-center text-sm-start">
|
||||
We read each and every bug report submitted, and by submitting this form you allow
|
||||
us to send you a follow-up email.
|
||||
</p>
|
||||
<form id="bugreport-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="bug-url">Page you were trying to reach:</label>
|
||||
<input
|
||||
id="bug-url"
|
||||
class="form-control"
|
||||
name="url"
|
||||
type="url"
|
||||
aria-describedby="bug-url-help"
|
||||
required
|
||||
/>
|
||||
<small id="bug-url-help" class="form-text text-muted">
|
||||
This is the URL of the page you actually received the error.
|
||||
</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="bug-ourl">Page you were on:</label>
|
||||
<input
|
||||
id="bug-ourl"
|
||||
class="form-control"
|
||||
name="ourl"
|
||||
type="url"
|
||||
aria-describedby="bug-ourl-help"
|
||||
/>
|
||||
<small id="bug-ourl-help" class="form-text text-muted">
|
||||
This is the URL of the page you were on before you received the error.
|
||||
</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<p class="form-label">*Has this happened more than once?</p>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="repeat"
|
||||
id="bug-repeat-no"
|
||||
value="false"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="bug-repeat-no">No</label>
|
||||
</div>
|
||||
<p id="bug-status" class="small" hidden></p>
|
||||
<button class="btn btn-primary" type="submit">Send report</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="repeat"
|
||||
id="bug-repeat-yes"
|
||||
value="true"
|
||||
/>
|
||||
<label class="form-check-label" for="bug-repeat-yes">Yes</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="bug-entry">Describe the error you received:</label>
|
||||
<textarea
|
||||
id="bug-entry"
|
||||
class="form-control"
|
||||
name="entry"
|
||||
rows="6"
|
||||
maxlength="2000"
|
||||
aria-describedby="bug-entry-help"
|
||||
required
|
||||
></textarea>
|
||||
<small id="bug-entry-help" class="form-text text-muted">(max: 2000 characters)</small>
|
||||
</div>
|
||||
<p id="bug-status" class="small text-center" hidden></p>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-primary btn-lg" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -620,6 +732,116 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="container mt-auto">
|
||||
<footer class="pt-4">
|
||||
<div class="text-center border-top context-main-border">
|
||||
<button
|
||||
class="d-md-none my-3 btn btn-lg context-main context-main-border"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#footerMenu"
|
||||
aria-controls="footerMenu"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle footer navigation"
|
||||
>
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="collapse d-md-block my-4" id="footerMenu">
|
||||
<div class="row">
|
||||
<div id="footer-contact-col" class="col-12 col-sm-6 col-md-3 col-lg-2 mb-3 text-center">
|
||||
<h2 class="h5">Contact Us</h2>
|
||||
<ul class="nav flex-column">
|
||||
<li id="footer-link-contact" class="nav-item mb-2">
|
||||
<a href="#/contact" class="nav-link p-0 text-muted">Contact</a>
|
||||
</li>
|
||||
<li id="footer-link-bug" class="nav-item mb-2">
|
||||
<a href="#/bugreport" class="nav-link p-0 text-muted">Report a Bug</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-3 col-lg-2 mb-3 text-center">
|
||||
<h2 class="h5">More Info</h2>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item mb-2">
|
||||
<a
|
||||
id="footer-privacy"
|
||||
href="#"
|
||||
class="nav-link p-0 text-muted"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
data-footer-path="home/privacy"
|
||||
>Privacy Policy</a>
|
||||
</li>
|
||||
<li class="nav-item mb-2">
|
||||
<a
|
||||
id="footer-terms"
|
||||
href="#"
|
||||
class="nav-link p-0 text-muted"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
data-footer-path="home/terms"
|
||||
>Terms of Service</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-3 col-lg-2 mb-3 text-center">
|
||||
<h2 class="h5">Dark Mode</h2>
|
||||
<div class="material-switch px-4 mt-2">
|
||||
<input name="dark-mode-toggle" type="checkbox" id="dark-mode-toggle" class="form-check-input" />
|
||||
<label for="dark-mode-toggle" class="label-default"><span class="visually-hidden">Dark mode</span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column flex-md-row justify-content-md-between py-3 border-top context-main-border">
|
||||
<div class="d-flex justify-content-center justify-content-md-start text-center text-md-start">
|
||||
<span>
|
||||
© <span id="footer-year"></span>
|
||||
<span id="footer-sitename">Capsule</span>, Powered by
|
||||
<a
|
||||
href="https://thetempusproject.com"
|
||||
class="text-decoration-none"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>The Tempus Project</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center justify-content-md-end mt-3 mt-md-0">
|
||||
<ul class="list-unstyled d-flex mb-0">
|
||||
<li class="ms-3">
|
||||
<a class="context-main" href="#" target="_blank" rel="noreferrer" data-footer-path="fb" aria-label="Facebook">
|
||||
<span class="fa-brands fa-fw fa-facebook" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="ms-3">
|
||||
<a class="context-main" href="#" target="_blank" rel="noreferrer" data-footer-path="twitter" aria-label="X (Twitter)">
|
||||
<span class="fa-brands fa-fw fa-twitter" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="ms-3">
|
||||
<a class="context-main" href="#" target="_blank" rel="noreferrer" data-footer-path="in" aria-label="LinkedIn">
|
||||
<span class="fa-brands fa-fw fa-linkedin" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="ms-3">
|
||||
<a class="context-main" href="#" target="_blank" rel="noreferrer" data-footer-path="youtube" aria-label="YouTube">
|
||||
<span class="fa-brands fa-fw fa-youtube" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="ms-3">
|
||||
<a class="context-main" href="#" target="_blank" rel="noreferrer" data-footer-path="git" aria-label="GitHub">
|
||||
<span class="fa-brands fa-fw fa-github" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
||||
crossorigin="anonymous"
|
||||
|
||||
@ -22,7 +22,9 @@ const views = {
|
||||
notifications: document.getElementById('view-notifications'),
|
||||
messages: document.getElementById('view-messages'),
|
||||
profile: document.getElementById('view-profile'),
|
||||
settings: document.getElementById('view-settings')
|
||||
settings: document.getElementById('view-settings'),
|
||||
contact: document.getElementById('view-contact'),
|
||||
bugreport: document.getElementById('view-bugreport')
|
||||
}
|
||||
|
||||
const mainPages = {
|
||||
@ -124,10 +126,28 @@ function hasPlugin(name) {
|
||||
* @return {string} - absolute URL, or #
|
||||
*/
|
||||
function siteHref(path) {
|
||||
if (!session?.siteUrl) {
|
||||
const root = session?.siteUrl || session?.lastSiteUrl
|
||||
if (!root) {
|
||||
return '#'
|
||||
}
|
||||
return `${session.siteUrl.replace(/\/+$/, '')}/${String(path).replace(/^\/+/, '')}`
|
||||
return `${root.replace(/\/+$/, '')}/${String(path).replace(/^\/+/, '')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Name for copyright and contact copy.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
function displaySiteName() {
|
||||
const root = session?.siteUrl || session?.lastSiteUrl
|
||||
if (!root) {
|
||||
return 'Capsule'
|
||||
}
|
||||
try {
|
||||
return new URL(root).hostname.replace(/^www\./i, '')
|
||||
} catch {
|
||||
return 'Capsule'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -366,6 +386,36 @@ function renderChrome() {
|
||||
)
|
||||
.join('')
|
||||
)
|
||||
|
||||
renderFooter()
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright, socials, legal links, and contact/bug items.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function renderFooter() {
|
||||
document.getElementById('footer-year').textContent = String(new Date().getFullYear())
|
||||
const sitename = displaySiteName()
|
||||
document.getElementById('footer-sitename').textContent = sitename
|
||||
document.getElementById('contact-sitename').textContent = sitename
|
||||
|
||||
document.querySelectorAll('[data-footer-path]').forEach((link) => {
|
||||
const href = siteHref(link.dataset.footerPath)
|
||||
link.href = href
|
||||
if (href === '#') {
|
||||
link.removeAttribute('target')
|
||||
} else {
|
||||
link.setAttribute('target', '_blank')
|
||||
}
|
||||
})
|
||||
|
||||
const showContact = Boolean(session?.connected) && (session.preview || hasPlugin('contact'))
|
||||
const showBug = Boolean(session?.connected) && (session.preview || hasPlugin('bugreport'))
|
||||
document.getElementById('footer-link-contact').hidden = !showContact
|
||||
document.getElementById('footer-link-bug').hidden = !showBug
|
||||
document.getElementById('footer-contact-col').hidden = !showContact && !showBug
|
||||
}
|
||||
|
||||
/**
|
||||
@ -439,15 +489,21 @@ function renderPages() {
|
||||
? `Enabled plugins: ${plugins.join(', ')}`
|
||||
: 'Plugin list will show after profile loads.'
|
||||
|
||||
const contactPanel = document.getElementById('contact-panel')
|
||||
const bugPanel = document.getElementById('bugreport-panel')
|
||||
contactPanel.hidden = session.preview || !hasPlugin('contact')
|
||||
bugPanel.hidden = session.preview || !hasPlugin('bugreport')
|
||||
if (!contactPanel.hidden && profile.name && !document.getElementById('contact-name').value) {
|
||||
const contactOk = session.preview || hasPlugin('contact')
|
||||
const bugOk = session.preview || hasPlugin('bugreport')
|
||||
document.getElementById('contact-body').hidden = !contactOk
|
||||
document.getElementById('bug-body').hidden = !bugOk
|
||||
const contactUnavailable = document.getElementById('contact-unavailable')
|
||||
const bugUnavailable = document.getElementById('bug-unavailable')
|
||||
contactUnavailable.hidden = contactOk
|
||||
bugUnavailable.hidden = bugOk
|
||||
contactUnavailable.textContent = contactOk ? '' : 'Contact is not available on this site.'
|
||||
bugUnavailable.textContent = bugOk ? '' : 'Bug reports are not available on this site.'
|
||||
if (contactOk && profile.name && !document.getElementById('contact-name').value) {
|
||||
document.getElementById('contact-name').value = profile.name || profile.username || ''
|
||||
document.getElementById('contact-email').value = profile.email || ''
|
||||
}
|
||||
if (!bugPanel.hidden && session.siteUrl && !document.getElementById('bug-url').value) {
|
||||
if (bugOk && session.siteUrl && !document.getElementById('bug-url').value) {
|
||||
document.getElementById('bug-url').value = session.siteUrl
|
||||
}
|
||||
|
||||
@ -602,7 +658,44 @@ function renderThread() {
|
||||
* @return {void}
|
||||
*/
|
||||
function applyTheme(dark) {
|
||||
document.documentElement.setAttribute('data-bs-theme', dark ? 'dark' : 'light')
|
||||
const on = Boolean(dark)
|
||||
document.documentElement.setAttribute('data-bs-theme', on ? 'dark' : 'light')
|
||||
const footerToggle = document.getElementById('dark-mode-toggle')
|
||||
const settingsToggle = document.getElementById('settings-dark')
|
||||
if (footerToggle) {
|
||||
footerToggle.checked = on
|
||||
}
|
||||
if (settingsToggle) {
|
||||
settingsToggle.checked = on
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply dark mode and save it on a live session.
|
||||
*
|
||||
* @param {boolean} dark - dark-mode preference
|
||||
* @param {boolean} persist - POST api/profile/update when signed in
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function setDarkMode(dark, persist) {
|
||||
profile.darkMode = Boolean(dark)
|
||||
applyTheme(profile.darkMode)
|
||||
if (!persist || session?.preview || !window.capsule || !session?.connected) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = await runApi(() =>
|
||||
window.capsule.updateProfile({
|
||||
fields: { darkMode: profile.darkMode ? '1' : '0' }
|
||||
})
|
||||
)
|
||||
if (data) {
|
||||
applyProfile(data)
|
||||
applyTheme(profile.darkMode)
|
||||
}
|
||||
} catch {
|
||||
applyTheme(profile.darkMode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -703,6 +796,14 @@ function route() {
|
||||
showView('settings')
|
||||
return
|
||||
}
|
||||
if (path === 'contact') {
|
||||
showView('contact')
|
||||
return
|
||||
}
|
||||
if (path === 'bugreport') {
|
||||
showView('bugreport')
|
||||
return
|
||||
}
|
||||
if (path === 'search') {
|
||||
showView('search')
|
||||
const params = new URLSearchParams(query || '')
|
||||
@ -1096,6 +1197,11 @@ document.getElementById('contact-form').addEventListener('submit', async (event)
|
||||
const button = event.target.querySelector('button[type="submit"]')
|
||||
button.disabled = true
|
||||
setMessage(status, '')
|
||||
if (session?.preview || !window.capsule) {
|
||||
setMessage(status, 'Saved in preview only.', 'text-muted')
|
||||
button.disabled = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
await runApi(() =>
|
||||
window.capsule.contact({
|
||||
@ -1119,12 +1225,17 @@ document.getElementById('bugreport-form').addEventListener('submit', async (even
|
||||
const button = event.target.querySelector('button[type="submit"]')
|
||||
button.disabled = true
|
||||
setMessage(status, '')
|
||||
if (session?.preview || !window.capsule) {
|
||||
setMessage(status, 'Saved in preview only.', 'text-muted')
|
||||
button.disabled = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
await runApi(() =>
|
||||
window.capsule.bugreport({
|
||||
url: document.getElementById('bug-url').value.trim(),
|
||||
ourl: document.getElementById('bug-ourl').value.trim(),
|
||||
repeat: document.getElementById('bug-repeat').checked,
|
||||
repeat: document.getElementById('bug-repeat-yes').checked,
|
||||
entry: document.getElementById('bug-entry').value
|
||||
})
|
||||
)
|
||||
@ -1203,7 +1314,11 @@ document.getElementById('settings-avatar-file').addEventListener('change', async
|
||||
})
|
||||
|
||||
document.getElementById('settings-dark').addEventListener('change', (event) => {
|
||||
applyTheme(event.target.checked)
|
||||
setDarkMode(event.target.checked, true)
|
||||
})
|
||||
|
||||
document.getElementById('dark-mode-toggle').addEventListener('change', (event) => {
|
||||
setDarkMode(event.target.checked, true)
|
||||
})
|
||||
|
||||
window.addEventListener('hashchange', route)
|
||||
|
||||
@ -74,14 +74,92 @@ body {
|
||||
font-family: var(--ttp-font);
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#main-content {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.context-main-bg {
|
||||
background: var(--ttp-surface);
|
||||
color: var(--ttp-text);
|
||||
}
|
||||
|
||||
.context-main {
|
||||
color: var(--ttp-text);
|
||||
}
|
||||
|
||||
.context-main-border {
|
||||
border-color: var(--ttp-border) !important;
|
||||
}
|
||||
|
||||
footer {
|
||||
flex-shrink: 0;
|
||||
color: var(--ttp-text-muted);
|
||||
font-size: 0.925rem;
|
||||
}
|
||||
|
||||
footer a.context-main {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dark-mode footer switch.
|
||||
*/
|
||||
.material-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.material-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.material-switch .label-default {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--switch-off-bg, var(--ttp-surface-muted));
|
||||
border-radius: 25px;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.material-switch .label-default::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--switch-slider-bg, #fff);
|
||||
bottom: 2.5px;
|
||||
left: 5px;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
box-shadow: 0 2px 4px #00000033;
|
||||
}
|
||||
|
||||
.material-switch input:checked + .label-default {
|
||||
background-color: var(--switch-on-bg, var(--ttp-primary));
|
||||
}
|
||||
|
||||
.material-switch input:checked + .label-default::before {
|
||||
transform: translateX(25px);
|
||||
}
|
||||
|
||||
header.text-bg-dark {
|
||||
background-color: var(--ttp-chrome) !important;
|
||||
box-shadow: 0 1px 0 rgba(63, 169, 245, 0.42), 0 10px 28px rgba(12, 25, 41, 0.18);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.capsule-header {
|
||||
|
||||
Reference in New Issue
Block a user