From d220af27b21dcf0d86d16c5b80585c6d5aba6b09 Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Thu, 13 Aug 2026 22:43:02 -0500 Subject: [PATCH] site: the open-profiles-in treatment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clients.js (the registry shared across the zzstoatzz sites — same localStorage key, so the preference follows the reader between projects), a gear popover for choosing where atproto identities open (bluesky, blacksky, witchsky, red dwarf, PDSls), and a post-render sweep that tags bsky.app profile links in rendered markdown so they honor the preference and stay fresh when it changes. authors keep writing plain bsky.app URLs. Co-Authored-By: Claude Fable 5 --- scripts/build-site.mjs | 4 +++ site/app.js | 44 +++++++++++++++++++++++++++++ site/clients.js | 63 ++++++++++++++++++++++++++++++++++++++++++ site/index.html | 5 ++++ site/style.css | 7 +++++ 5 files changed, 123 insertions(+) create mode 100644 site/clients.js diff --git a/scripts/build-site.mjs b/scripts/build-site.mjs index 5519410..83a4bbc 100644 --- a/scripts/build-site.mjs +++ b/scripts/build-site.mjs @@ -104,6 +104,10 @@ async function main() { 'src="./app.js"', `src="./app.js?v=${buildId}"`, ); + html = html.replaceAll( + 'src="./clients.js"', + `src="./clients.js?v=${buildId}"`, + ); html = html.replaceAll( 'href="./favicon.svg"', `href="./favicon.svg?v=${buildId}"`, diff --git a/site/app.js b/site/app.js index c179036..f6b35ad 100644 --- a/site/app.js +++ b/site/app.js @@ -280,6 +280,7 @@ async function main() { const html = globalThis.marked.parse(md); contentEl.innerHTML = html; globalThis.Prism?.highlightAllUnder(contentEl); + applyProfileClientPreference(contentEl); renderHeadingToc(); window.scrollTo(0, 0); @@ -307,3 +308,46 @@ async function main() { } main(); + +// atproto profile links honor the "open profiles in" preference (clients.js, +// shared across the zzstoatzz/zat.dev sites via the same localStorage key). +// Authors write plain bsky.app profile URLs in markdown; render tags them +// with data-profile-did so clients.js keeps them fresh on preference change. +function applyProfileClientPreference(root) { + if (!window.LeafletClients) return; + root.querySelectorAll('a[href^="https://bsky.app/profile/"]').forEach((a) => { + const id = a.href.slice("https://bsky.app/profile/".length).replace(/\/+$/, ""); + if (!id) return; + a.setAttribute("data-profile-did", id); + a.href = window.LeafletClients.profileUrlFor(id); + }); +} + +// settings popover: where atproto identities open +(function initClientSettings() { + const btn = document.querySelector(".client-settings-toggle"); + if (!btn || !window.LeafletClients) return; + const pop = document.getElementById("client-settings"); + function renderOptions() { + const current = window.LeafletClients.getPreferredClient().value; + pop.innerHTML = + '
open profiles in
' + + window.LeafletClients.CLIENTS.map( + (c) => + ``, + ).join(""); + } + btn.addEventListener("click", (e) => { + e.stopPropagation(); + renderOptions(); + pop.hidden = !pop.hidden; + }); + pop.addEventListener("change", (e) => { + if (e.target.name === "profile-client") window.LeafletClients.setPreferredClient(e.target.value); + }); + document.addEventListener("click", (e) => { + if (!pop.hidden && !pop.contains(e.target)) pop.hidden = true; + }); +})(); diff --git a/site/clients.js b/site/clients.js new file mode 100644 index 0000000..322093f --- /dev/null +++ b/site/clients.js @@ -0,0 +1,63 @@ +// shared: bluesky-compatible client registry + preferred-client preference. +// mirrors the shape used in ../@zzstoatzz.io/status and ../@chicago.at so +// users get consistent behavior across these atproto experiments. +// +// stored in localStorage under `leaflet.preferredClient`. default = bsky. +// `profileUrlFor(handle)` returns the resolved profile URL for the current +// preference; unknown values fall back to the first registered client. + +(function() { + 'use strict'; + + var STORAGE_KEY = 'leaflet.preferredClient'; + + var CLIENTS = [ + { value: 'bsky', label: 'Bluesky', profileUrl: function(h) { return 'https://bsky.app/profile/' + h; }, iconUrl: 'https://web-cdn.bsky.app/static/apple-touch-icon.png' }, + { value: 'blacksky', label: 'Blacksky', profileUrl: function(h) { return 'https://blacksky.community/profile/' + h; }, iconUrl: 'https://blacksky.community/static/apple-touch-icon.png' }, + { value: 'witchsky', label: 'Witchsky', profileUrl: function(h) { return 'https://witchsky.app/profile/' + h; }, iconUrl: 'https://witchsky.app/favicon.ico' }, + { value: 'reddwarf', label: 'Red Dwarf', profileUrl: function(h) { return 'https://reddwarf.app/profile/' + h; }, iconUrl: 'https://reddwarf.app/redstar.png' }, + { value: 'pdsls', label: 'PDSls', profileUrl: function(h) { return 'https://pdsls.dev/at/' + h; }, iconUrl: 'https://pdsls.dev/favicon.ico' }, + ]; + + function readPreferred() { + try { return localStorage.getItem(STORAGE_KEY); } catch (e) { return null; } + } + + function writePreferred(value) { + try { localStorage.setItem(STORAGE_KEY, value); } catch (e) {} + } + + function getPreferredClient() { + var v = readPreferred(); + return CLIENTS.find(function(c) { return c.value === v; }) || CLIENTS[0]; + } + + function setPreferredClient(value) { + if (CLIENTS.some(function(c) { return c.value === value; })) { + writePreferred(value); + window.dispatchEvent(new CustomEvent('leaflet:preferred-client-changed', { detail: value })); + } + } + + function profileUrlFor(handleOrDid) { + return getPreferredClient().profileUrl(handleOrDid); + } + + // Profile links bake the preferred-client URL into their href at render time. + // Rather than make every page listen for changes (easy to forget on a new + // surface), keep them fresh here: any anchor tagged `data-profile-did` gets + // its href rewritten app-wide when the preference changes. Render sites just + // need the attribute — no per-page wiring. + window.addEventListener('leaflet:preferred-client-changed', function() { + document.querySelectorAll('a[data-profile-did]').forEach(function(a) { + a.href = profileUrlFor(a.getAttribute('data-profile-did')); + }); + }); + + window.LeafletClients = { + CLIENTS: CLIENTS, + getPreferredClient: getPreferredClient, + setPreferredClient: setPreferredClient, + profileUrlFor: profileUrlFor, + }; +})(); diff --git a/site/index.html b/site/index.html index da5cdb5..4abf41a 100644 --- a/site/index.html +++ b/site/index.html @@ -37,6 +37,10 @@
devlog repo + +