fix: preserve multi-file skill review desk
This commit is contained in:
+12
-7
@@ -1,6 +1,7 @@
|
||||
import { catalog } from './catalog.js';
|
||||
import { files } from './files.js';
|
||||
|
||||
const state = { selected: catalog[0], query: '', preview: 'original', source: '' };
|
||||
const state = { selected: catalog[0], query: '', preview: 'original', source: '', file: null };
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const escape = (value) => value.replace(/[&<>"']/g, (character) => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' })[character]);
|
||||
const redact = (value) => value.replace(/(NDO_PASS[^\n=]*[=:]\s*["']?)[^\n"']+/gi, '$1[REDACTED]').replace(/(password["']?\s*[:=]\s*["']?)[^\n"']+/gi, '$1[REDACTED]').replace(/\bsysadm@netcracker\.com\b/gi, '[REDACTED SERVICE ACCOUNT]');
|
||||
@@ -16,6 +17,7 @@ function syncUrl() {
|
||||
url.searchParams.set('author', state.selected.author);
|
||||
url.searchParams.set('skill', state.selected.id);
|
||||
url.searchParams.set('view', state.preview);
|
||||
if (state.file && state.file.name !== 'SKILL.md') url.searchParams.set('file', state.file.name); else url.searchParams.delete('file');
|
||||
history.replaceState({}, '', url);
|
||||
}
|
||||
function selectFromUrl() {
|
||||
@@ -28,30 +30,33 @@ function selectFromUrl() {
|
||||
state.selected = byId || byAuthor?.[0] || catalog[0];
|
||||
state.query = byAuthor ? state.selected.author.toLowerCase() : '';
|
||||
state.preview = view === 'improved' ? 'improved' : 'original';
|
||||
const available = files[state.selected.id] || [{ name:'SKILL.md', path:state.selected.path, kind:'skill' }];
|
||||
state.file = available.find((item) => item.name === params.get('file')) || available[0];
|
||||
$('#skill-filter').value = byAuthor ? state.selected.author : '';
|
||||
}
|
||||
function renderList() {
|
||||
const items = visible();
|
||||
$('#count').textContent = `${items.length} of ${catalog.length} reviewed`;
|
||||
$('#skill-list').innerHTML = items.map((item) => `<button role="option" aria-selected="${item.id === state.selected.id}" class="${item.id === state.selected.id ? 'active' : ''}" data-id="${item.id}"><span>${escape(item.author)}</span><strong>${escape(item.title)}</strong><small>${escape(item.status)}</small></button>`).join('');
|
||||
$('#skill-list').querySelectorAll('button').forEach((button) => button.addEventListener('click', () => { state.selected = catalog.find((item) => item.id === button.dataset.id); state.preview = 'original'; state.source = ''; syncUrl(); renderList(); renderDetail(); }));
|
||||
$('#skill-list').querySelectorAll('button').forEach((button) => button.addEventListener('click', () => { state.selected = catalog.find((item) => item.id === button.dataset.id); state.file = (files[state.selected.id] || [{ name:'SKILL.md', path:state.selected.path, kind:'skill' }])[0]; state.preview = 'original'; state.source = ''; syncUrl(); renderList(); renderDetail(); }));
|
||||
}
|
||||
async function original(entry) {
|
||||
if (state.source) return state.source;
|
||||
try { state.source = redact(await (await fetch(entry.path)).text()); } catch { state.source = '# Original preview unavailable\n\nServe this site from the repository root to load the submitted source.'; }
|
||||
try { state.source = redact(await (await fetch(state.file.path)).text()); } catch { state.source = '# Original preview unavailable\n\nServe this site from the repository root to load the submitted source.'; }
|
||||
renderDetail();
|
||||
return state.source;
|
||||
}
|
||||
function renderDetail() {
|
||||
const entry = state.selected; const markdown = state.preview === 'original' ? (state.source || 'Loading original Markdown…') : entry.improved;
|
||||
const entry = state.selected; const available = files[entry.id] || [{ name:'SKILL.md', path:entry.path, kind:'skill' }]; const markdown = state.preview === 'original' ? (state.source || 'Loading original Markdown…') : entry.improved;
|
||||
$('#detail').innerHTML = `<header><div><span class="status">${escape(entry.status)}</span><h2>${escape(entry.title)}</h2><p>Submitted by <a class="author-link" href="?author=${encodeURIComponent(entry.author)}">${escape(entry.author)}</a> · <a class="share-link" href="?author=${encodeURIComponent(entry.author)}&skill=${encodeURIComponent(entry.id)}&view=${state.preview}">share review ↗</a></p></div><div class="switch" role="group" aria-label="Preview version"><button class="${state.preview === 'original' ? 'active' : ''}" data-preview="original">Original</button><button class="${state.preview === 'improved' ? 'active' : ''}" data-preview="improved">Improved draft</button></div></header>
|
||||
<div class="purpose"><span>THE JOB</span><p>${escape(entry.focus)}</p></div>
|
||||
<div class="review-grid"><section><span>WHAT'S ALREADY WORKING</span><ul>${entry.wins.map((item) => `<li>${escape(item)}</li>`).join('')}</ul></section><section><span>HIGHEST-VALUE IMPROVEMENTS</span><ul>${entry.improve.map((item) => `<li>${escape(item)}</li>`).join('')}</ul></section></div>
|
||||
<aside class="extras"><span>GOOD NEXT ADDITION</span>${escape(entry.extras)}</aside>
|
||||
<section class="preview"><header><span>${state.preview === 'original' ? 'ORIGINAL / SAFETY-REDACTED WHERE NEEDED' : 'IMPROVED DRAFT / READY TO ADAPT'}</span><div><button data-copy>Copy</button><button data-download>Download</button></div></header><pre><code>${escape(markdown)}</code></pre></section>`;
|
||||
<aside class="extras"><span>GOOD NEXT ADDITION</span><p>${escape(entry.extras)}</p></aside>
|
||||
<section class="preview"><header><span>${state.preview === 'original' ? 'ORIGINAL / SAFETY-REDACTED WHERE NEEDED' : 'IMPROVED DRAFT / READY TO ADAPT'}</span><div><button data-copy>Copy</button><button data-download>Download</button></div></header><nav class="file-tabs" aria-label="Skill package files">${available.map((item) => `<button class="${item.name === state.file.name ? 'active' : ''}" data-file="${escape(item.name)}"><span>${escape(item.kind)}</span>${escape(item.name)}</button>`).join('')}</nav><pre><code>${escape(markdown)}</code></pre></section>`;
|
||||
$('#detail').querySelectorAll('[data-file]').forEach((button) => button.addEventListener('click', () => { state.file = available.find((item) => item.name === button.dataset.file) || available[0]; state.source = ''; state.preview = 'original'; syncUrl(); renderDetail(); }));
|
||||
$('#detail').querySelectorAll('[data-preview]').forEach((button) => button.addEventListener('click', () => { state.preview = button.dataset.preview; state.source = state.preview === 'original' ? state.source : ''; syncUrl(); renderDetail(); if (state.preview === 'original') original(entry); }));
|
||||
$('[data-copy]').addEventListener('click', async () => { const content = state.preview === 'original' ? await original(entry) : entry.improved; await copy(content); $('[data-copy]').textContent = 'Copied'; });
|
||||
$('[data-download]').addEventListener('click', async () => download(`${entry.id}-${state.preview}.md`, state.preview === 'original' ? await original(entry) : entry.improved));
|
||||
$('[data-download]').addEventListener('click', async () => download(`${entry.id}-${state.file.name.replaceAll('/', '-')}-${state.preview}.md`, state.preview === 'original' ? await original(entry) : entry.improved));
|
||||
if (state.preview === 'original' && !state.source) original(entry);
|
||||
}
|
||||
$('#skill-filter').addEventListener('input', (event) => { state.query = event.target.value.toLowerCase().trim(); renderList(); });
|
||||
|
||||
Reference in New Issue
Block a user