Files
plspace/src/routes/Settings.svelte
T
2026-07-29 19:51:59 +09:00

222 lines
8.0 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
/**
* Settings — mostly the layout editor, which is the feature this whole app
* exists to have.
*
* Two things are editable: the CSS applied to *your* view of plspace (stored
* locally), and whether the CSS other people publish on their profiles is
* honoured when you visit them.
*/
import { useAppServices } from '$lib/app-services'
import { CSS_FIELD_NAMES, PROFILE_SCOPE } from '$lib/stores/theme.svelte'
import { PRESETS, EXAMPLE_CSS } from '$lib/themes'
import { instanceDomain } from '$lib/api/endpoints'
import { displayNameOf, profilePath } from '$lib/util/profile'
import Module from '$components/common/Module.svelte'
import PublicProfileEditor from '$components/profile/PublicProfileEditor.svelte'
import PublishedCssEditor from '$components/profile/PublishedCssEditor.svelte'
const { session, theme } = useAppServices()
let draft = $state(theme.viewerCss)
let saved = $state(false)
const domain = $derived(session.host ? instanceDomain(session.instance, session.host) : '')
/** Class hooks worth documenting, grouped the way pages are built. */
const HOOKS: Array<{ group: string; entries: Array<[string, string]> }> = [
{
group: 'Page skeletons',
entries: [
['.page', 'Every pages outer container'],
['.profile-page', 'The profile page — also the scope for published profile CSS'],
['.layout--split', 'Two-column pages (left rail + main)'],
['.layout--dashboard', 'The three-column home page'],
['.layout-column--left / --main / --right', 'The individual columns'],
],
},
{
group: 'Boxes',
entries: [
['.module', 'A bordered box'],
['.module-header', 'Its caption bar'],
['.module-body', 'Its contents'],
['.module--band', 'The peach-bar variant used in the main column'],
['.section-heading', '“About me:” style orange headings'],
['.notification-toast-stack, .notification-toast', 'Background notification toasts'],
],
},
{
group: 'Profile',
entries: [
['.profile-photo', 'The big photo'],
['.profile-headline', 'The quoted line beside it'],
['.profile-vitals', 'Gender / age / location / last active'],
['.interests-table, .interests-label, .interests-value', 'The Interests table'],
['.friend-grid, .friend-card, .friend-card-photo', 'Friend Space'],
['.pic-stream, .pic-card, .pic-card-image', 'Pics stream'],
],
},
{
group: 'Blog entries',
entries: [
['.blog-entry', 'One entry'],
['.blog-entry[data-mine="true"]', 'Entries you wrote'],
['.blog-entry[data-visibility="private"]', 'Friends-only entries'],
['.blog-entry[data-boosted="true"]', 'Reposts'],
['.youtube-attachment, .youtube-embed', 'YouTube embeds'],
['.blog-action[aria-pressed="true"]', 'Kudos/Repost buttons youve activated'],
['.comment[data-depth="2"]', 'Comments by nesting depth'],
],
},
]
function save(): void {
theme.setViewerCss(draft)
saved = true
setTimeout(() => (saved = false), 2000)
}
function applyPreset(css: string): void {
draft = css
theme.setViewerCss(css)
}
function reset(): void {
draft = ''
theme.setViewerCss('')
}
</script>
<div class="page settings-page">
<h1 class="page-title">Settings</h1>
<p class="page-subtitle">Edit your public profile and customise how plspace looks.</p>
<div class="layout--single">
<Module title="Your account">
{#if session.signedIn && session.me}
<p>
Signed in as
<a href={profilePath(session.me)}>{displayNameOf(session.me)}</a>
on <strong>{domain}</strong>.
</p>
<p class="field-row">
<button type="button" class="button" onclick={() => void session.logout()}>Sign out</button>
<button type="button" class="button" onclick={() => session.disconnect()}>
Forget this server
</button>
</p>
{:else if session.host}
<p>Browsing <strong>{domain}</strong> as a guest.</p>
<p class="field-row">
<a class="button button--primary" href="#/login">Sign in</a>
<button type="button" class="button" onclick={() => session.disconnect()}>
Choose a different server
</button>
</p>
{:else}
<p>Not connected to a server. <a href="#/login">Choose one</a>.</p>
{/if}
</Module>
<Module title="Your public profile" variant="band">
<PublicProfileEditor />
</Module>
<Module title="Pick a layout" variant="band">
<ul class="preset-list">
{#each PRESETS as preset (preset.id)}
<li class="preset">
<button type="button" class="button preset-button" onclick={() => applyPreset(preset.css)}>
{preset.name}
</button>
<span class="preset-description muted">{preset.description}</span>
</li>
{/each}
</ul>
</Module>
<Module title="CSS for this browser only" variant="band">
<p class="notice">
<strong>Private viewing setting:</strong>
this changes how plspace looks only for you, in this browser. It does not update your
profile, and nobody else can see it.
</p>
<p>
Overriding the custom properties in <code>styles/tokens.css</code> retints the entire app;
the class hooks below let you go further.
</p>
<label class="visually-hidden" for="viewer-css">Your CSS</label>
<textarea
id="viewer-css"
class="css-editor"
bind:value={draft}
rows="14"
spellcheck="false"
placeholder={EXAMPLE_CSS}
></textarea>
<div class="field-row">
<button type="button" class="button button--primary" onclick={save}>Save CSS</button>
<button type="button" class="button" onclick={reset}>Reset to default</button>
<button type="button" class="button" onclick={() => (draft = EXAMPLE_CSS)}>
Load the example
</button>
{#if saved}<span class="muted">Saved.</span>{/if}
</div>
<p class="field-hint">
<code>@import</code> and non-HTTPS <code>url()</code> values are stripped before your CSS is
applied.
</p>
</Module>
<Module title="Other people's layouts" variant="band">
<label class="checkbox-field">
<input
type="checkbox"
checked={theme.allowProfileCss}
onchange={(event) => theme.setAllowProfileCss(event.currentTarget.checked)}
/>
<span>
Show profile layouts published by the people I visit
</span>
</label>
<p class="field-hint">
plspace publishes layouts in ordered fields named <code>plspace:css1</code>,
<code>plspace:css2</code>, and so on. Legacy
{#each CSS_FIELD_NAMES as name, index (name)}<code>{name}</code>{#if index < CSS_FIELD_NAMES.length - 1}, {/if}{/each}
fields are still understood.
Their rules are rewritten to apply only inside <code>{PROFILE_SCOPE}</code>, so a profile
can restyle its own page but not the rest of plspace.
</p>
</Module>
<Module title="CSS published on your profile" variant="band">
<p>
This editor writes the layout into profile fields on
<strong>{domain || 'your server'}</strong>. Anyone who visits your profile with plspace
will receive it automatically.
</p>
<PublishedCssEditor />
</Module>
<Module title="Class reference" variant="band">
{#each HOOKS as section (section.group)}
<h3 class="section-heading">{section.group}</h3>
<table class="data-table hooks-table">
<tbody>
{#each section.entries as [selector, description] (selector)}
<tr>
<th class="data-table-label" scope="row"><code>{selector}</code></th>
<td class="data-table-value">{description}</td>
</tr>
{/each}
</tbody>
</table>
{/each}
</Module>
</div>
</div>