mirror of
https://git.shipoclu.com/moon/plspace.git
synced 2026-08-13 02:42:30 +00:00
222 lines
8.0 KiB
Svelte
222 lines
8.0 KiB
Svelte
<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 page’s 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 you’ve 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>
|