clone

one developer profile for links and private tcp services, reached peer to peer over iroh with no open ports

feat: fold dashboard controls

Dami committed 5ae8f8d
Add page titles and selective dashboard text selection. Allow safe shadow removal and themed open accordion summaries.
commit
5ae8f8dc949c352b51386233a1405d5b676c5422
parent
2a6f38107ecd20c8a23e4159adbe20bfd4d0f3ae
author
Dami <dami.etoile@gmail.com>
4 files changed, +73 -23
Mcrates/devsite-server/src/theme.rs22+++++++++++-
Mdocs/profile-template.md6++-
Mweb/app.js57+++++++++++++++++++-----------
Mweb/site.css11++++++
M crates/devsite-server/src/theme.rs +21 -1
@@ -94,6 +94,7 @@ const PROPERTIES: &[(&str, Kind)] = &[
9494 ("--pico-mark-background-color", Kind::Color),9595 ("--pico-mark-color", Kind::Color),9696 ("--pico-blockquote-border-color", Kind::Color),97+ ("--pico-accordion-open-summary-color", Kind::Color),9798 // `ins` and `del` also colour the site's own confirmations and refusals.9899 ("--pico-ins-color", Kind::Color),99100 ("--pico-del-color", Kind::Color),
@@ -101,6 +102,10 @@ const PROPERTIES: &[(&str, Kind)] = &[
101102 ("--pico-form-element-background-color", Kind::Color),102103 ("--pico-form-element-border-color", Kind::Color),103104 ("--pico-form-element-color", Kind::Color),105+ // -- effects -------------------------------------------------------------106+ // A profile may remove Pico's global shadow, but may not supply arbitrary107+ // shadow syntax that could visually escape the component it belongs to.108+ ("--pico-box-shadow", Kind::Keyword(&["unset"])),104109 // -- metrics -------------------------------------------------------------105110 ("--pico-border-radius", Kind::Length),106111 ("--pico-border-width", Kind::Length),
@@ -588,16 +593,31 @@ mod tests {
588593 --pico-background-color: light-dark(#fefae0, #283618);589594 --pico-color: light-dark(#283618, #fefae0);590595 --pico-primary: light-dark(#bc6c25, #dda15e);596+ --pico-accordion-open-summary-color: light-dark(#606c38, #dda15e);597+ --pico-box-shadow: unset;591598 ";592599 assert_eq!(593600 css(source).unwrap(),594601 "--devsite-scheme: auto;\n\595602 --pico-background-color: light-dark(#fefae0, #283618);\n\596603 --pico-color: light-dark(#283618, #fefae0);\n\597- --pico-primary: light-dark(#bc6c25, #dda15e);\n"604+ --pico-primary: light-dark(#bc6c25, #dda15e);\n\605+ --pico-accordion-open-summary-color: light-dark(#606c38, #dda15e);\n\606+ --pico-box-shadow: unset;\n"598607 );599608 }600609 610+ #[test]611+ fn box_shadow_can_only_be_removed() {612+ assert!(parse("--pico-box-shadow: unset;").is_ok());613+ for value in ["none", "0 0 1rem red", "var(--shadow)"] {614+ assert!(615+ parse(&format!("--pico-box-shadow: {value};")).is_err(),616+ "{value} should be refused"617+ );618+ }619+ }620+601621 #[test]602622 fn color_pairs_are_accepted_with_forced_and_automatic_schemes() {603623 for scheme in ["light", "dark", "auto"] {
M docs/profile-template.md +4 -2
@@ -149,8 +149,9 @@ the failure mode the list exists to prevent.
149149 | Surfaces | `--pico-background-color`, `--pico-color`, `--pico-muted-color`, `--pico-muted-border-color`, `--pico-border-color`, `--pico-text-selection-color` | colour |150150 | Accents | `--pico-primary` and its `-background`, `-hover`, `-hover-background`, `-inverse`, `-underline`, `-focus`; `--pico-secondary` and its `-background`, `-hover`, `-inverse`; `--pico-contrast` and its `-background`, `-inverse` | colour |151151 | Headings | `--pico-h1-color` … `--pico-h6-color` | colour |152-| Blocks | `--pico-card-background-color`, `--pico-card-border-color`, `--pico-card-sectioning-background-color`, `--pico-code-background-color`, `--pico-code-color`, `--pico-mark-background-color`, `--pico-mark-color`, `--pico-blockquote-border-color`, `--pico-ins-color`, `--pico-del-color` | colour |152+| Blocks | `--pico-card-background-color`, `--pico-card-border-color`, `--pico-card-sectioning-background-color`, `--pico-code-background-color`, `--pico-code-color`, `--pico-mark-background-color`, `--pico-mark-color`, `--pico-blockquote-border-color`, `--pico-accordion-open-summary-color`, `--pico-ins-color`, `--pico-del-color` | colour |153153 | Form elements | `--pico-form-element-background-color`, `--pico-form-element-border-color`, `--pico-form-element-color` | colour |154+| Effects | `--pico-box-shadow` | `unset` |154155 | Metrics | `--pico-border-radius`, `--pico-border-width`, `--pico-outline-width`, `--pico-spacing`, `--pico-block-spacing-vertical`, `--pico-block-spacing-horizontal`, `--pico-typography-spacing-vertical`, `--pico-form-element-spacing-vertical`, `--pico-form-element-spacing-horizontal`, `--pico-nav-element-spacing-vertical`, `--pico-nav-element-spacing-horizontal`, `--pico-text-underline-offset`, `--pico-font-size` | length |155156 | Type | `--pico-line-height` | number |156157 | | `--pico-font-weight` | `400` or `700` |
@@ -225,8 +226,9 @@ devsite theme clear
225226 --pico-color: light-dark(#283618, #fefae0);226227 --pico-primary: light-dark(#bc6c25, #dda15e);227228 --pico-primary-hover: light-dark(#606c38, #fefae0);229+--pico-accordion-open-summary-color: light-dark(#606c38, #dda15e);230+--pico-box-shadow: unset;228231 --pico-border-radius: 0.5rem;229---pico-font-weight: 400;230232 ```231233 232234 Rejections are specific and name the offending declaration:
M web/app.js +37 -20
@@ -22,6 +22,10 @@ const esc = (value) =>
2222 String(value).replace(/[&<>"']/g, (c) =>2323 ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));2424 25+function setPageTitle(suffix = '') {26+ document.title = suffix ? `dev.site - ${suffix}` : 'dev.site';27+}28+2529 async function api(path, options = {}) {2630 const response = await fetch(path, {2731 credentials: 'same-origin',
@@ -319,6 +323,7 @@ function folder(name, items) {
319323 }320324 321325 function renderProfile(profile) {326+ setPageTitle(`@${profile.handle}`);322327 applyTheme(profile.handle, profile.theme);323328 main.innerHTML = '';324329
@@ -449,6 +454,7 @@ function openService(item) {
449454 // -- setup views ---------------------------------------------------------------450455 451456 function renderSignedOut() {457+ setPageTitle('home');452458 clearTheme();453459 document.querySelector('.brand strong').hidden = true;454460 const platform = navigator.userAgentData?.platform || navigator.platform || '';
@@ -558,6 +564,7 @@ function renderSignedOut() {
558564 }559565 560566 function renderClaimHandle() {567+ setPageTitle('dashboard');561568 clearTheme();562569 main.innerHTML = `563570 <article>
@@ -638,11 +645,13 @@ function resourceControl(resource) {
638645 639646 function incomingShareControl(share) {640647 const item = document.createElement('li');641- const destination = share.url ? ` - ${share.url}` : '';648+ const destination = share.url649+ ? ` - <span data-selectable>${esc(share.url)}</span>`650+ : '';642651 item.innerHTML = `643652 <div>644653 <strong>${esc(share.name)}</strong>645- <small>from @${esc(share.owner_handle)} - ${esc(share.kind)}${esc(destination)}</small>654+ <small>from @${esc(share.owner_handle)} - ${esc(share.kind)}${destination}</small>646655 </div>647656 <div class="dashboard-actions"></div>`;648657 const actions = item.querySelector('.dashboard-actions');
@@ -745,6 +754,7 @@ async function signOut(button) {
745754 }746755 747756 async function showDashboard(newCredential = null) {757+ setPageTitle('dashboard');748758 clearTheme();749759 main.setAttribute('aria-busy', 'true');750760 const [resourceListing, incomingListing, credentialListing, settings] = await Promise.all([
@@ -774,30 +784,36 @@ async function showDashboard(newCredential = null) {
774784 </article>775785 776786 <article>777- <header><strong>Incoming shares</strong></header>778- <p>Nothing is added to your profile until you accept it. Link destinations are shown779- as text so you can inspect them without opening them.</p>780- <ul class="dashboard-list" id="incoming-shares"></ul>787+ <details>788+ <summary><strong>Incoming shares</strong></summary>789+ <p>Nothing is added to your profile until you accept it. Link destinations are shown790+ as text so you can inspect them without opening them.</p>791+ <ul class="dashboard-list" id="incoming-shares"></ul>792+ </details>781793 </article>782794 783795 <article>784- <header><strong>Shared by you</strong></header>785- <ul class="dashboard-list" id="dashboard-resources"></ul>796+ <details>797+ <summary><strong>Shared by you</strong></summary>798+ <ul class="dashboard-list" id="dashboard-resources"></ul>799+ </details>786800 </article>787801 788802 <article>789- <header><strong>Machine credentials</strong></header>790- <p>Each credential remains valid until you revoke it. Its secret is shown once.</p>791- <form id="credential-form">792- <fieldset role="group">793- <input id="credential-name" name="name" maxlength="60"794- placeholder="This MacBook" aria-label="Machine name" required>795- <button type="submit">Create</button>796- </fieldset>797- <small id="credential-note"></small>798- </form>799- <div id="new-machine-token"></div>800- <ul class="dashboard-list" id="machine-credentials"></ul>803+ <details${newCredential ? ' open' : ''}>804+ <summary><strong>Machine credentials</strong></summary>805+ <p>Each credential remains valid until you revoke it. Its secret is shown once.</p>806+ <form id="credential-form">807+ <fieldset role="group">808+ <input id="credential-name" name="name" maxlength="60"809+ placeholder="This MacBook" aria-label="Machine name" required>810+ <button type="submit">Create</button>811+ </fieldset>812+ <small id="credential-note"></small>813+ </form>814+ <div id="new-machine-token"></div>815+ <ul class="dashboard-list" id="machine-credentials"></ul>816+ </details>801817 </article>802818 803819 <article>
@@ -882,6 +898,7 @@ async function showDashboard(newCredential = null) {
882898 883899 async function route() {884900 const path = location.pathname;901+ setPageTitle();885902 886903 if (path === '/auth/callback') {887904 main.innerHTML = '<article aria-busy="true">Signing you in…</article>';
M web/site.css +11 -0