initial commit

This commit is contained in:
Moon.eth
2026-07-29 09:16:38 +09:00
commit 586b599d4c
67 changed files with 9906 additions and 0 deletions
+216
View File
@@ -0,0 +1,216 @@
<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 { session } from '$lib/stores/session.svelte'
import { theme, 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'
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'],
],
},
{
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'],
],
},
{
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'],
['.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">Everything here is stored in this browser only.</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="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="Your CSS" variant="band">
<p>
This is applied to every page you view in plspace. 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">
A profile can publish a stylesheet by putting CSS in a profile field named
{#each CSS_FIELD_NAMES as name, index (name)}<code>{name}</code>{#if index < CSS_FIELD_NAMES.length - 1}, {/if}{/each}.
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="Publish your own layout" variant="band">
<p>
Add a profile field on <strong>{domain || 'your server'}</strong> named <code>css</code> and
paste a stylesheet into its value. Anyone viewing your profile in plspace sees it. Since
it's an ordinary profile field, it survives elsewhere too &mdash; other clients just show it
as text.
</p>
{#if session.signedIn}
<p>
<a
class="button"
href={`https://${session.host}/settings/profile`}
target="_blank"
rel="noopener noreferrer"
>
Edit your profile on {domain}
</a>
</p>
{/if}
</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>