source: Klonkt/src/assets/js/mod/admin-site-edit.js@ 746d98a

main
Last change on this file since 746d98a was 746d98a, checked in by Robin <roboburr@…>, 6 hours ago

Wardmodus: een besloten Klonkt met alleen het profiel openbaar

Robin, 30-9: "Ward Profile mode gaat actief wanneer ward gekoppeld is, dus
wanneer de persoon guardian(s) heeft. Een besloten Klonkt met enkel het profiel
van de ward publiek. Wij denken dat dit het beste valt bij emancipation om de
functie weer naar normaal Klonkt te brengen."

AFGELEID, NIET OPGESLAGEN. Heeft de site geaccepteerde guardians, dan staat de
modus aan; is de set leeg, dan uit. listGuardians telt alleen geaccepteerde
guardianships, dus een voorstel zet niets dicht, en de set leegmaken is
emancipatie (FEP-633c 3.4) -- precies waar het ontwerp de weg terug legt. Een
schakelaar zou een tweede waarheid naast de guardianship zijn.

STANDAARD DICHT (middleware/ward-profile.js, in server.js direct na de
siteresolver en de schrijfpoort). Een bezoeker ziet op / het profiel en verder
niets: een pagina gaat terug naar het profiel, al het andere is 404, een POST
bereikt zijn handler niet, en een htmx-navigatie krijgt HX-Redirect in plaats
van een brokstuk. Wat niet op de lijst staat komt er niet langs, zodat een
nieuwe route niet vanzelf een kind openzet.

Open blijft: inloggen, account, beheer, het guardianpaneel, /lang, de bestanden
van de pagina zelf, en /audio/stream -- die bewaakt elk bestand zelf, en een
track die de ward op de fediverse opende moet daar speelbaar blijven. De
federatie (ActivityPub, WebFinger, OAuth voor Shaer, /media, /assets, /og) hangt
al VOOR de poort; de paden staan toch op de lijst, voor als de volgorde ooit
omgaat, want dan zou een ward stil van de fediverse verdwijnen.

Wie alles ziet: de ward zelf, en god, kijker en sitebeheer. Een andere
ingelogde gebruiker is gewoon een bezoeker.

DE PROFIELPAGINA (pages/ward-profile.ejs) mag verzorgd, want dit is alles wat
een bezoeker ziet: foto met een ring in de accentkleur, de naam groot, de
handle op een pil, de bio, de links als brede knoppen in hun merkkleur, een
volgknop die de bestaande fediverse-dialoog opent (een volgverzoek aan een ward
gaat eerst langs de guardians), en een neutrale regel dat berichten alleen
voor volgers zijn. Nergens "kind" of "ward": dat de account guardians heeft
staat al openbaar op de fediverse. Dezelfde bronnen als de profielkop, zodat
het profiel op geen twee plekken iets anders kan zeggen. De chrome biedt een
bezoeker niets aan wat dicht is: geen zoekknop, geen archief, geen
Lezen/Grid-schakelaar.

DE PROFIELLINKS verhuizen van Appearance naar het account (/account#links),
bij naam, bio en foto. Twee vallen onderweg, allebei getoetst:

  • Appearance schreef profile_links altijd, uit de formuliervelden. Met de velden weg zou elke keer opslaan daar de links wissen. Die pagina schrijft ze niet meer.
  • ownedSite haalde profile_links niet op, dus de accountpagina toonde nul links en opslaan wiste ze. Gevonden bij het bekijken, niet door een toets; nu wel.

buildProfileLinks staat in PlatformIcons.js, en de rijen-knoppen in een eigen
module (mod/profile-links.js).

Getoetst: gewone Klonkt zonder guardians, een voorstel zet niets dicht,
gekoppeld alleen het profiel, pagina's terug, 404 voor wat geen pagina is, geen
handler voor een POST, HX-Redirect, wat open moet blijft open, ward en beheer
zien alles, een andere ingelogde is een bezoeker, emancipatie maakt het weer
gewoon, de links bij het account en Appearance wist ze niet. Tegenproef per
zaak. Volledige suite 1325 groen.

Nagekeken in de browser op 375 en 1280 pixels: het profiel past op een
telefoonscherm, knoppen van 55 pixels, geen overloop, de volgknop opent de
dialoog; de accountpagina toont de bestaande links en voegt er een toe.

Co-Authored-By: Claude Opus 5.5 <noreply@…>

  • Property mode set to 100644
File size: 4.6 KB
Line 
1// Site bewerken in beheer (pages/admin-site-edit.ejs) -- verplaatst uit inline script, shaer-bqr.
2//
3// Inline script in een pagina wordt door de CSP geweigerd zodra je die pagina via
4// een link BINNEN de site opent: de nonce rouleert per verzoek (shaer-0i6). Dit
5// bestand wordt door de bootstrap in shell.ejs geladen en heeft dat probleem niet.
6//
7// Alles hier hoort GEDELEGEERD te luisteren (op document, niet op een element dat
8// er nu staat) en tegen een tweede aanroep te kunnen.
9
10// Element-bedrading leeft zo lang als de pagina; de bootstrap roept init()
11// aan bij elke paginawissel waarop deze module actief is (shaer-5s1).
12export function init() { run(); }
13
14function run() {
15 (function(){
16 if (window.__themePreviewWired) return; window.__themePreviewWired = true;
17 var html = document.documentElement;
18 function applyAccent(hex){
19 if(!/^#[0-9a-fA-F]{6}$/.test(hex)) return;
20 var sa = document.getElementById('pcms-site-accent');
21 if(!sa){ sa = document.createElement('style'); sa.id = 'pcms-site-accent'; document.head.appendChild(sa); }
22 sa.textContent = ':root,[data-palette]{--accent:'+hex+';--accent-soft:color-mix(in srgb,'+hex+' 80%,white);--accent-tint:color-mix(in srgb,'+hex+' 12%,transparent);}';
23 }
24 document.addEventListener('change', function(e){
25 var t = e.target; if(!t || !t.name) return;
26 if(t.name === 'palette'){ html.setAttribute('data-palette', t.value); }
27 else if(t.name === 'accent'){ applyAccent(t.value); }
28 else if(t.name === 'theme_override'){
29 if(t.value === 'light' || t.value === 'dark'){ html.setAttribute('data-theme', t.value); }
30 else { var dk = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; html.setAttribute('data-theme', dk ? 'dark' : 'light'); }
31 }
32 });
33 })();
34
35
36// ── volgend blok ──
37
38(function() {
39 // Slug URL field: show the real host as a dimmed prefix (the slug itself is
40 // coloured via CSS). Hub → host/user/<slug>, otherwise host/<slug>.
41 var sh = document.getElementById('slug-host');
42 if (sh) sh.textContent = location.host + (sh.dataset.prefix || '/');
43})();
44// De profiellinks staan sinds 30-9 bij je profiel: zie mod/profile-links.js.
45
46// P63 — Profile photo picker: upload via /admin/sites/upload-photo,
47// then write the returned URL into the visible input. Live thumb preview.
48(function() {
49 var picker = document.getElementById('photo-picker');
50 if (!picker) return;
51 var thumb = document.getElementById('photo-thumb');
52 var preview = document.getElementById('photo-preview');
53 var urlEl = document.getElementById('photo-url');
54 var trigger = document.getElementById('photo-upload-trigger');
55 var clear = document.getElementById('photo-clear');
56 var field = document.getElementById('photo-upload-field');
57 var status = document.getElementById('photo-status');
58
59 function showPreview(url) {
60 if (url) {
61 preview.src = url;
62 preview.hidden = false;
63 thumb.removeAttribute('data-empty');
64 clear.hidden = false;
65 } else {
66 preview.src = '';
67 preview.hidden = true;
68 thumb.setAttribute('data-empty', '');
69 clear.hidden = true;
70 }
71 }
72
73 if (urlEl) {
74 urlEl.addEventListener('input', function() {
75 showPreview(urlEl.value.trim());
76 });
77 }
78
79 if (trigger && field) {
80 trigger.addEventListener('click', function() { field.click(); });
81 field.addEventListener('change', async function() {
82 var file = field.files && field.files[0];
83 if (!file) return;
84 status.classList.remove('is-error');
85 status.textContent = 'Uploaden…';
86 try {
87 var fd = new FormData();
88 fd.append('photo', file);
89 var r = await fetch('/admin/sites/upload-photo', {
90 method: 'POST',
91 body: fd,
92 credentials: 'same-origin',
93 });
94 var j = await r.json();
95 if (!r.ok || !j.ok) throw new Error(j.error || ('Upload mislukt (' + r.status + ')'));
96 urlEl.value = j.url;
97 showPreview(j.url);
98 status.textContent = 'Geüpload ✓';
99 setTimeout(function() { status.textContent = ''; }, 2000);
100 } catch (e) {
101 status.classList.add('is-error');
102 status.textContent = 'Mislukt: ' + e.message;
103 } finally {
104 field.value = '';
105 }
106 });
107 }
108
109 if (clear) {
110 clear.addEventListener('click', function() {
111 urlEl.value = '';
112 showPreview('');
113 status.textContent = '';
114 // Note: doesn't delete the file from disk — saving the form with empty
115 // URL leaves the file orphaned on the server. Acceptable for now.
116 });
117 }
118})();
119}
Note: See TracBrowser for help on using the repository browser.