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>
| M | crates/ | 22 | +++++++++++- |
| M | docs/ | 6 | ++- |
| M | web/ | 57 | +++++++++++++++++++----------- |
| M | web/ | 11 | ++++++ |
@@ -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"] {@@ -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:@@ -22,6 +22,10 @@ const esc = (value) =>
2222 String(value).replace(/[&<>"']/g, (c) =>2323 ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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>';@@ -74,6 +74,17 @@ main:has(.sequence) :is(pre, code, #install-command) {
7474 user-select: text;7575 }7676 77+/* Dashboard labels and controls are interface, not document copy. Keep the78+ * values someone may reasonably copy selectable: form input, commands, link79+ * destinations, and errors worth carrying into a bug report. */80+main:has(#private-only) {81+ user-select: none;82+}83+84+main:has(#private-only) :is(input, textarea, pre, code, [data-selectable], .error) {85+ user-select: text;86+}87+7788 .brand {7889 display: inline-flex;7990 gap: calc(var(--pico-spacing) * 0.4);