From 4e1073bd7ce848e4122ed4946ed7688eecb63961 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 31 May 2026 21:37:10 -0400 Subject: [PATCH] Linked Designs: Credits panel in metadata sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per LINKED_DESIGNS_FEATURE.md ยง10.1, surface each distinct contributor reachable via the host's linked layers as a clickable @npub entry in a "๐Ÿ”— Credits" panel in the metadata sidebar. Walks state.layers + every deck slide's layers, groups distinct linked refs by pubkey (so 5 copies of the same source count as 1 design from that creator), excludes self, and renders one entry per contributor sorted by descending design count. Each entry links to the /u/ profile page in a new tab. Pubkey is parsed straight from design.ref's address coord, so the panel populates without waiting for the resolver. Fieldset is hidden entirely when there are no linked layers โ€” non-collaborative designs stay clutter-free. Wired into render() so it stays current as the user adds, removes, pins, replaces, or unlinks layers. --- src/index.html | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/index.html b/src/index.html index 10575c9..da3c118 100644 --- a/src/index.html +++ b/src/index.html @@ -2955,6 +2955,12 @@

Title and category are required to save or publish.

+ +
Display options @@ -6681,6 +6687,73 @@ return { contributorTags, linkTags }; } + // Build the Credits panel that lives in the metadata sidebar + // (LINKED_DESIGNS_FEATURE.md ยง10.1). Walks state.layers + every + // deck slide's layers, groups distinct linked refs by pubkey, + // and renders one anchor per contributor with a click-through to + // the /u/ profile page. Excludes the current user โ€” they + // already authored the host, so self-attribution is noise. + // + // The contributor pubkey comes straight from the address coord + // in `design.ref`, so this can populate immediately without + // waiting for the resolver. Hidden when there are no linked + // layers so non-collaborative designs stay clutter-free. + function renderCreditsPanel() { + const fieldset = $('creditsFieldset'); + const list = $('creditsList'); + if (!fieldset || !list) return; + const allLayers = []; + if (Array.isArray(state.layers)) allLayers.push(...state.layers); + if (Array.isArray(state.deck?.slides)) { + for (const s of state.deck.slides) { + if (Array.isArray(s?.layers)) allLayers.push(...s.layers); + } + } + const myPubkey = state.community?.myPubkey || ''; + // pubkey โ†’ Set so multiple layers pointing at the same + // source count as one design. + const byPubkey = new Map(); + for (const l of allLayers) { + if (l?.type !== 'design' || l.design?.kind !== 'linked' || !l.design.ref) continue; + const coord = linkedDesigns._parseAddressCoord(l.design.ref); + if (!coord?.pubkey) continue; + if (coord.pubkey === myPubkey) continue; + let refs = byPubkey.get(coord.pubkey); + if (!refs) { refs = new Set(); byPubkey.set(coord.pubkey, refs); } + refs.add(l.design.ref); + } + if (byPubkey.size === 0) { + fieldset.style.display = 'none'; + list.innerHTML = ''; + return; + } + // Descending design count, then by pubkey for stable ordering. + const entries = [...byPubkey.entries()].sort( + (a, b) => b[1].size - a[1].size || a[0].localeCompare(b[0]) + ); + list.innerHTML = ''; + for (const [pk, refs] of entries) { + const li = document.createElement('li'); + li.style.cssText = 'margin:4px 0;line-height:1.35'; + const a = document.createElement('a'); + a.href = profileUrlForPubkey(pk); + a.target = '_blank'; + a.rel = 'noopener noreferrer'; + a.style.cssText = 'font-family:monospace;color:#1e40af;text-decoration:none'; + let np = ''; + try { np = encodeNpub(pk); } catch {} + a.textContent = '@' + (np ? np.slice(0, 12) + 'โ€ฆ' + np.slice(-6) : pk.slice(0, 10) + 'โ€ฆ'); + a.title = np || pk; + li.appendChild(a); + const count = document.createElement('span'); + count.style.cssText = 'color:#666;margin-left:4px'; + count.textContent = ` (${refs.size} design${refs.size === 1 ? '' : 's'})`; + li.appendChild(count); + list.appendChild(li); + } + fieldset.style.display = ''; + } + // Pre-publish hook. Linked layers carry render-only fields // (lastResolvedAt, lastResolvedSize, transient fallbackPreview) // that are session-local and should not ship in the published @@ -9744,6 +9817,7 @@ syncDesignerInputs(); updateBodyClasses(); renderDeckSorter(); + renderCreditsPanel(); if (state.templateMode === 'pixelart') ensureStandalonePixelEditor(); // After updateBodyClasses so the designer view is displayed/laid out โ€” // the disc image's cover-pan math needs the wrapper's real size.