Compare commits
10 Commits
8ea820aeaf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e6b9ba56b1 | |||
| 64d16dbbdf | |||
| 48c824981b | |||
| 5e024b0f29 | |||
| 384af7d5cd | |||
| def561da19 | |||
| b78af43122 | |||
| 5341988e8d | |||
| 6a934d829a | |||
| d42fc5160f |
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "BincioWiki",
|
||||||
|
"short_name": "BincioWiki",
|
||||||
|
"description": "Bincio personal wiki",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0d0d0d",
|
||||||
|
"theme_color": "#0d0d0d",
|
||||||
|
"icons": [
|
||||||
|
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
||||||
|
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -18,6 +18,9 @@ const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props;
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon-light.png" />
|
<link rel="icon" type="image/x-icon" href="/favicon-light.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
|
<meta name="theme-color" content="#0d0d0d" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
||||||
<!-- Font preloads -->
|
<!-- Font preloads -->
|
||||||
|
|||||||
@@ -19,6 +19,17 @@
|
|||||||
let dragOver = false;
|
let dragOver = false;
|
||||||
let fileInput: HTMLInputElement;
|
let fileInput: HTMLInputElement;
|
||||||
let heroFileInput: HTMLInputElement;
|
let heroFileInput: HTMLInputElement;
|
||||||
|
let textarea: HTMLTextAreaElement;
|
||||||
|
let colCount = 0;
|
||||||
|
|
||||||
|
function updateCol() {
|
||||||
|
if (!textarea) return;
|
||||||
|
const pos = textarea.selectionStart ?? 0;
|
||||||
|
const lineStart = textarea.value.lastIndexOf('\n', pos - 1) + 1;
|
||||||
|
colCount = pos - lineStart + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: colColor = colCount >= 80 ? '#f87171' : colCount >= 70 ? '#f59e0b' : 'var(--text-5)';
|
||||||
|
|
||||||
$: section = SECTIONS.find(s => s.slug === selectedSection) ?? null;
|
$: section = SECTIONS.find(s => s.slug === selectedSection) ?? null;
|
||||||
$: subs = section?.sub ?? [];
|
$: subs = section?.sub ?? [];
|
||||||
@@ -299,11 +310,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
|
bind:this={textarea}
|
||||||
bind:value={content}
|
bind:value={content}
|
||||||
class="flex-1 w-full p-4 bg-transparent font-mono text-sm resize-none outline-none"
|
class="flex-1 w-full p-4 bg-transparent font-mono text-sm resize-none outline-none"
|
||||||
style="color: var(--text-2)"
|
style="color: var(--text-2)"
|
||||||
placeholder="Scrivi in markdown…"
|
placeholder="Scrivi in markdown…"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
|
on:keyup={updateCol}
|
||||||
|
on:click={updateCol}
|
||||||
|
on:input={updateCol}
|
||||||
on:dragover|preventDefault={() => dragOver = true}
|
on:dragover|preventDefault={() => dragOver = true}
|
||||||
on:dragleave={() => dragOver = false}
|
on:dragleave={() => dragOver = false}
|
||||||
on:drop|preventDefault={e => { dragOver = false; if (e.dataTransfer?.files) uploadFiles(e.dataTransfer.files); }}
|
on:drop|preventDefault={e => { dragOver = false; if (e.dataTransfer?.files) uploadFiles(e.dataTransfer.files); }}
|
||||||
@@ -323,6 +338,8 @@
|
|||||||
<span class="text-zinc-600">oppure trascina un file nell'editor</span>
|
<span class="text-zinc-600">oppure trascina un file nell'editor</span>
|
||||||
<input bind:this={fileInput} type="file" accept="image/*" multiple class="hidden"
|
<input bind:this={fileInput} type="file" accept="image/*" multiple class="hidden"
|
||||||
on:change={e => e.currentTarget.files && uploadFiles(e.currentTarget.files)} />
|
on:change={e => e.currentTarget.files && uploadFiles(e.currentTarget.files)} />
|
||||||
|
<span class="ml-auto" style="color: var(--text-5)">~80 caratteri per riga per leggibilità</span>
|
||||||
|
<span style="color: {colColor}; font-variant-numeric: tabular-nums; min-width: 3.5rem; text-align: right">Col {colCount}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
+17
-4
@@ -8,6 +8,7 @@ interface Props {
|
|||||||
public?: boolean;
|
public?: boolean;
|
||||||
}
|
}
|
||||||
const { title = 'BincioWiki', description = 'La memoria collettiva del gruppo Bincio.', public: isPublic = false } = Astro.props;
|
const { title = 'BincioWiki', description = 'La memoria collettiva del gruppo Bincio.', public: isPublic = false } = Astro.props;
|
||||||
|
const activityUrl = import.meta.env.PUBLIC_ACTIVITY_URL ?? 'https://activity.bincio.org';
|
||||||
---
|
---
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-theme="dark">
|
<html lang="en" data-theme="dark">
|
||||||
@@ -113,7 +114,7 @@ const { title = 'BincioWiki', description = 'La memoria collettiva del gruppo Bi
|
|||||||
|
|
||||||
/* ── Base reset ──────────────────────────────────────────────────── */
|
/* ── Base reset ──────────────────────────────────────────────────── */
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
html { scroll-behavior: smooth; }
|
html { scroll-behavior: smooth; scroll-padding-top: 4rem; }
|
||||||
body { margin: 0; overflow-x: hidden; }
|
body { margin: 0; overflow-x: hidden; }
|
||||||
[data-auth-pending] { visibility: hidden; }
|
[data-auth-pending] { visibility: hidden; }
|
||||||
</style>
|
</style>
|
||||||
@@ -145,14 +146,17 @@ const { title = 'BincioWiki', description = 'La memoria collettiva del gruppo Bi
|
|||||||
<span id="nav-handle" class="hidden sm:inline text-xs text-zinc-500" style="display:none"></span>
|
<span id="nav-handle" class="hidden sm:inline text-xs text-zinc-500" style="display:none"></span>
|
||||||
<a id="nav-wikilog" href="/log/" class="hidden sm:inline text-xs text-zinc-500 hover:text-white transition-colors px-1" style="display:none">WikiLog</a>
|
<a id="nav-wikilog" href="/log/" class="hidden sm:inline text-xs text-zinc-500 hover:text-white transition-colors px-1" style="display:none">WikiLog</a>
|
||||||
<a id="nav-invites" href="/invites/" class="hidden sm:inline text-xs text-zinc-500 hover:text-white transition-colors px-1" style="display:none">Inviti</a>
|
<a id="nav-invites" href="/invites/" class="hidden sm:inline text-xs text-zinc-500 hover:text-white transition-colors px-1" style="display:none">Inviti</a>
|
||||||
|
{activityUrl && (
|
||||||
|
<a id="nav-activity" href={activityUrl} class="hidden sm:inline text-xs text-zinc-500 hover:text-white transition-colors px-1" style="display:none">Activity</a>
|
||||||
|
)}
|
||||||
<button id="nav-logout" class="hidden sm:inline text-xs text-zinc-500 hover:text-white transition-colors px-1" style="display:none">Log out</button>
|
<button id="nav-logout" class="hidden sm:inline text-xs text-zinc-500 hover:text-white transition-colors px-1" style="display:none">Log out</button>
|
||||||
<!-- Hamburger: mobile only, shown after auth -->
|
|
||||||
<button id="nav-hamburger" class="sm:hidden text-zinc-400 hover:text-white w-8 h-8 flex items-center justify-center rounded-md hover:bg-zinc-800 text-base" style="display:none" aria-label="Menu">☰</button>
|
|
||||||
<button
|
<button
|
||||||
id="theme-toggle"
|
id="theme-toggle"
|
||||||
class="text-zinc-400 hover:text-white transition-colors w-8 h-8 flex items-center justify-center rounded-md hover:bg-zinc-800 text-base"
|
class="text-zinc-400 hover:text-white transition-colors w-8 h-8 flex items-center justify-center rounded-md hover:bg-zinc-800 text-base"
|
||||||
aria-label="Toggle theme"
|
aria-label="Toggle theme"
|
||||||
>☀</button>
|
>☀</button>
|
||||||
|
<!-- Hamburger: mobile only, shown after auth -->
|
||||||
|
<button id="nav-hamburger" class="sm:hidden text-zinc-400 hover:text-white w-8 h-8 flex items-center justify-center rounded-md hover:bg-zinc-800 text-base" style="display:none" aria-label="Menu">☰</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Mobile dropdown (inside sticky nav so it scrolls with it) -->
|
<!-- Mobile dropdown (inside sticky nav so it scrolls with it) -->
|
||||||
@@ -161,6 +165,9 @@ const { title = 'BincioWiki', description = 'La memoria collettiva del gruppo Bi
|
|||||||
<span id="nav-handle-m" class="text-xs px-2 py-1.5" style="color: var(--text-5)"></span>
|
<span id="nav-handle-m" class="text-xs px-2 py-1.5" style="color: var(--text-5)"></span>
|
||||||
<a href="/log/" class="text-sm px-2 py-1.5 rounded hover:bg-zinc-800 transition-colors" style="color: var(--text-4)">WikiLog</a>
|
<a href="/log/" class="text-sm px-2 py-1.5 rounded hover:bg-zinc-800 transition-colors" style="color: var(--text-4)">WikiLog</a>
|
||||||
<a href="/invites/" class="text-sm px-2 py-1.5 rounded hover:bg-zinc-800 transition-colors" style="color: var(--text-4)">Inviti</a>
|
<a href="/invites/" class="text-sm px-2 py-1.5 rounded hover:bg-zinc-800 transition-colors" style="color: var(--text-4)">Inviti</a>
|
||||||
|
{activityUrl && (
|
||||||
|
<a id="nav-activity-m" href={activityUrl} class="text-sm px-2 py-1.5 rounded hover:bg-zinc-800 transition-colors" style="display:none; color: var(--text-4)">Activity</a>
|
||||||
|
)}
|
||||||
<button id="nav-logout-m" class="text-sm px-2 py-1.5 rounded hover:bg-zinc-800 transition-colors text-left" style="color: var(--text-4)">Log out</button>
|
<button id="nav-logout-m" class="text-sm px-2 py-1.5 rounded hover:bg-zinc-800 transition-colors text-left" style="color: var(--text-4)">Log out</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -199,6 +206,12 @@ const { title = 'BincioWiki', description = 'La memoria collettiva del gruppo Bi
|
|||||||
if (invitesEl) invitesEl.style.display = '';
|
if (invitesEl) invitesEl.style.display = '';
|
||||||
if (logoutEl) logoutEl.style.display = '';
|
if (logoutEl) logoutEl.style.display = '';
|
||||||
if (hamburgerEl) hamburgerEl.style.display = '';
|
if (hamburgerEl) hamburgerEl.style.display = '';
|
||||||
|
if (user.activity_access) {
|
||||||
|
const activityEl = document.getElementById('nav-activity');
|
||||||
|
const activityElM = document.getElementById('nav-activity-m');
|
||||||
|
if (activityEl) activityEl.style.display = '';
|
||||||
|
if (activityElM) activityElM.style.display = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => { document.body.removeAttribute('data-auth-pending'); });
|
.catch(() => { document.body.removeAttribute('data-auth-pending'); });
|
||||||
@@ -218,7 +231,7 @@ const { title = 'BincioWiki', description = 'La memoria collettiva del gruppo Bi
|
|||||||
});
|
});
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
if (navMenu && !navMenu.classList.contains('hidden') &&
|
if (navMenu && !navMenu.classList.contains('hidden') &&
|
||||||
!navMenu.contains(e.target as Node) && !hamburger?.contains(e.target as Node)) {
|
!navMenu.contains(e.target) && !hamburger?.contains(e.target)) {
|
||||||
navMenu.classList.add('hidden');
|
navMenu.classList.add('hidden');
|
||||||
if (hamburger) hamburger.textContent = '☰';
|
if (hamburger) hamburger.textContent = '☰';
|
||||||
}
|
}
|
||||||
|
|||||||
+138
-1
@@ -15,15 +15,33 @@ const { title, frontmatter, id } = Astro.props;
|
|||||||
<div class="flex items-start justify-between gap-4">
|
<div class="flex items-start justify-between gap-4">
|
||||||
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">{title}</h1>
|
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">{title}</h1>
|
||||||
{id && (
|
{id && (
|
||||||
|
<div class="flex items-center gap-2 mt-1 shrink-0">
|
||||||
|
<button
|
||||||
|
id="history-btn"
|
||||||
|
data-slug={id}
|
||||||
|
class="text-xs text-zinc-500 hover:text-white transition-colors px-2 py-1 rounded border border-zinc-700 hover:border-zinc-500"
|
||||||
|
>Cronologia</button>
|
||||||
<button
|
<button
|
||||||
data-slug={id}
|
data-slug={id}
|
||||||
class="shrink-0 text-xs text-zinc-500 hover:text-white transition-colors px-2 py-1 rounded border border-zinc-700 hover:border-zinc-500 mt-1"
|
class="text-xs text-zinc-500 hover:text-white transition-colors px-2 py-1 rounded border border-zinc-700 hover:border-zinc-500"
|
||||||
id="edit-entry-btn"
|
id="edit-entry-btn"
|
||||||
>Modifica</button>
|
>Modifica</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<BreadCrumbs current={frontmatter.fname} />
|
<BreadCrumbs current={frontmatter.fname} />
|
||||||
</div>
|
</div>
|
||||||
|
{id && (
|
||||||
|
<div id="page-history" class="hidden mb-6 rounded-lg overflow-hidden" style="background:var(--bg-card)">
|
||||||
|
<div class="px-3 py-2 border-b flex items-center justify-between" style="border-color:var(--border)">
|
||||||
|
<span class="text-sm font-medium" style="color:var(--text-3)">Cronologia modifiche</span>
|
||||||
|
<button id="history-close" class="text-xs hover:text-white transition-colors" style="color:var(--text-5)">✕</button>
|
||||||
|
</div>
|
||||||
|
<div id="history-list" class="p-2 space-y-1">
|
||||||
|
<p class="text-sm px-2 py-1" style="color:var(--text-4)">Caricamento…</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div class="prose-wiki">
|
<div class="prose-wiki">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
@@ -38,4 +56,123 @@ const { title, frontmatter, id } = Astro.props;
|
|||||||
const slug = (e.currentTarget as HTMLElement).dataset.slug;
|
const slug = (e.currentTarget as HTMLElement).dataset.slug;
|
||||||
window.dispatchEvent(new CustomEvent('open-editor', { detail: { slug, apiBase: '/pages' } }));
|
window.dispatchEvent(new CustomEvent('open-editor', { detail: { slug, apiBase: '/pages' } }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const historyBtn = document.getElementById('history-btn') as HTMLElement | null;
|
||||||
|
const historyPanel = document.getElementById('page-history') as HTMLElement | null;
|
||||||
|
const historyList = document.getElementById('history-list') as HTMLElement | null;
|
||||||
|
const historyClose = document.getElementById('history-close') as HTMLElement | null;
|
||||||
|
|
||||||
|
if (historyBtn && historyPanel && historyList) {
|
||||||
|
let loaded = false;
|
||||||
|
const slug = historyBtn.dataset.slug!;
|
||||||
|
|
||||||
|
function esc(s: string) {
|
||||||
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDiff(text: string): string {
|
||||||
|
return text.split('\n').map(line => {
|
||||||
|
if (line.startsWith('diff ') || line.startsWith('index ') || line.startsWith('new file') ||
|
||||||
|
line.startsWith('deleted file') || line.startsWith('similarity') || line.startsWith('rename')) {
|
||||||
|
return `<span style="color:var(--text-5)">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
if (line.startsWith('--- ') || line.startsWith('+++ ')) {
|
||||||
|
return `<span style="color:var(--text-3)">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
if (line.startsWith('@@')) {
|
||||||
|
return `<span style="color:var(--accent);opacity:0.8">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
if (line.startsWith('+')) {
|
||||||
|
return `<span style="color:#4ade80;background:rgba(74,222,128,0.07);display:block">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
if (line.startsWith('-')) {
|
||||||
|
return `<span style="color:#f87171;background:rgba(248,113,113,0.07);display:block">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
return `<span style="color:var(--text-4)">${esc(line)}</span>`;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
historyBtn.addEventListener('click', async () => {
|
||||||
|
if (!historyPanel.classList.contains('hidden')) {
|
||||||
|
historyPanel.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
historyPanel.classList.remove('hidden');
|
||||||
|
if (loaded) return;
|
||||||
|
loaded = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/history/${slug}`, { credentials: 'include' });
|
||||||
|
if (!r.ok) throw new Error(String(r.status));
|
||||||
|
const { log } = await r.json();
|
||||||
|
if (!log?.length) {
|
||||||
|
historyList.innerHTML = '<p class="text-sm px-2 py-1" style="color:var(--text-4)">Nessuna modifica registrata.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
historyList.innerHTML = log.map((e: any) => {
|
||||||
|
const [author, rest] = e.message.includes(': ') ? e.message.split(': ', 2) : ['', e.message];
|
||||||
|
return `
|
||||||
|
<div class="rounded overflow-hidden mb-1">
|
||||||
|
<button class="hist-row w-full flex items-center gap-3 px-2 py-1.5 text-sm text-left transition-colors hover:brightness-110 rounded" data-hash="${e.hash}" aria-expanded="false" style="background:var(--bg-elevated)">
|
||||||
|
<span class="font-mono text-xs shrink-0" style="color:var(--text-5)">${e.hash}</span>
|
||||||
|
<span class="shrink-0 font-medium text-xs" style="color:var(--accent)">${esc(author || e.author)}</span>
|
||||||
|
<span class="flex-1 min-w-0 truncate text-xs" style="color:var(--text-2)">${esc(rest || e.message)}</span>
|
||||||
|
<span class="shrink-0 text-xs" style="color:var(--text-5)">${e.date}</span>
|
||||||
|
<span class="hist-chevron shrink-0 text-xs transition-transform" style="color:var(--text-5)">▶</span>
|
||||||
|
</button>
|
||||||
|
<div class="hist-diff hidden" data-hash="${e.hash}">
|
||||||
|
<pre class="text-xs leading-5 px-3 pb-3 overflow-x-auto" style="font-family:'JetBrains Mono',monospace;border-top:1px solid var(--border);padding-top:0.75rem;margin:0;background:var(--bg-elevated)"></pre>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
historyList.addEventListener('click', async (ev) => {
|
||||||
|
const btn = (ev.target as Element).closest<HTMLButtonElement>('.hist-row');
|
||||||
|
if (!btn) return;
|
||||||
|
const hash = btn.dataset.hash!;
|
||||||
|
const diffDiv = historyList.querySelector<HTMLElement>(`.hist-diff[data-hash="${hash}"]`);
|
||||||
|
const pre = diffDiv?.querySelector('pre');
|
||||||
|
const chevron = btn.querySelector<HTMLElement>('.hist-chevron');
|
||||||
|
if (!diffDiv || !pre) return;
|
||||||
|
|
||||||
|
if (btn.getAttribute('aria-expanded') === 'true') {
|
||||||
|
diffDiv.classList.add('hidden');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
if (chevron) chevron.style.transform = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
btn.setAttribute('aria-expanded', 'true');
|
||||||
|
if (chevron) chevron.style.transform = 'rotate(90deg)';
|
||||||
|
diffDiv.classList.remove('hidden');
|
||||||
|
if (pre.dataset.loaded) return;
|
||||||
|
|
||||||
|
pre.textContent = 'Caricamento diff…';
|
||||||
|
pre.style.color = 'var(--text-5)';
|
||||||
|
try {
|
||||||
|
const diffUrl = `/api/diff/${hash}?file=pages/${encodeURIComponent(slug)}.md`;
|
||||||
|
const dr = await fetch(diffUrl, { credentials: 'include' });
|
||||||
|
if (!dr.ok) throw new Error(String(dr.status));
|
||||||
|
const { diff } = await dr.json();
|
||||||
|
pre.dataset.loaded = '1';
|
||||||
|
if (!diff.trim()) {
|
||||||
|
pre.textContent = '(nessuna modifica ai file)';
|
||||||
|
pre.style.color = 'var(--text-5)';
|
||||||
|
} else {
|
||||||
|
pre.innerHTML = renderDiff(diff);
|
||||||
|
pre.style.color = '';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
pre.textContent = 'Errore nel caricamento della diff.';
|
||||||
|
pre.style.color = '#f87171';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
historyList.innerHTML = '<p class="text-sm px-2 py-1" style="color:#f87171">Errore nel caricamento.</p>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
historyClose?.addEventListener('click', () => {
|
||||||
|
historyPanel.classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,29 +6,107 @@ import { SITE_TITLE } from '../../consts';
|
|||||||
<Base title={`WikiLog — ${SITE_TITLE}`} description="Ultime modifiche al wiki">
|
<Base title={`WikiLog — ${SITE_TITLE}`} description="Ultime modifiche al wiki">
|
||||||
<div class="max-w-3xl mx-auto">
|
<div class="max-w-3xl mx-auto">
|
||||||
<h1 class="text-3xl font-bold mb-8" style="color: var(--text-primary)">WikiLog</h1>
|
<h1 class="text-3xl font-bold mb-8" style="color: var(--text-primary)">WikiLog</h1>
|
||||||
<div id="log-list" class="space-y-2">
|
<div id="log-list" class="space-y-1">
|
||||||
<p class="text-sm" style="color: var(--text-4)">Caricamento…</p>
|
<p class="text-sm" style="color: var(--text-4)">Caricamento…</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Base>
|
</Base>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
function esc(s: string) {
|
||||||
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDiff(text: string): string {
|
||||||
|
return text.split('\n').map(line => {
|
||||||
|
if (line.startsWith('diff ') || line.startsWith('index ') || line.startsWith('new file') || line.startsWith('deleted file') || line.startsWith('similarity') || line.startsWith('rename')) {
|
||||||
|
return `<span style="color:var(--text-5)">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
if (line.startsWith('--- ') || line.startsWith('+++ ')) {
|
||||||
|
return `<span style="color:var(--text-3)">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
if (line.startsWith('@@')) {
|
||||||
|
return `<span style="color:var(--accent);opacity:0.8">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
if (line.startsWith('+')) {
|
||||||
|
return `<span style="color:#4ade80;background:rgba(74,222,128,0.07);display:block">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
if (line.startsWith('-')) {
|
||||||
|
return `<span style="color:#f87171;background:rgba(248,113,113,0.07);display:block">${esc(line)}</span>`;
|
||||||
|
}
|
||||||
|
return `<span style="color:var(--text-4)">${esc(line)}</span>`;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
fetch('/api/log', { credentials: 'include' })
|
fetch('/api/log', { credentials: 'include' })
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(({ log }) => {
|
.then(({ log }) => {
|
||||||
const el = document.getElementById('log-list');
|
const el = document.getElementById('log-list');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
if (!log?.length) { el.innerHTML = '<p class="text-sm" style="color:var(--text-4)">Nessuna modifica ancora.</p>'; return; }
|
if (!log?.length) {
|
||||||
|
el.innerHTML = '<p class="text-sm" style="color:var(--text-4)">Nessuna modifica ancora.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
el.innerHTML = log.map((e: any) => {
|
el.innerHTML = log.map((e: any) => {
|
||||||
const [author, rest] = e.message.includes(': ') ? e.message.split(': ', 2) : ['', e.message];
|
const [author, rest] = e.message.includes(': ') ? e.message.split(': ', 2) : ['', e.message];
|
||||||
return `
|
return `
|
||||||
<div class="flex items-baseline gap-3 px-3 py-2 rounded-lg text-sm" style="background:var(--bg-card)">
|
<div class="rounded-lg overflow-hidden" style="background:var(--bg-card)">
|
||||||
|
<button class="log-row w-full flex items-center gap-3 px-3 py-2 text-sm text-left transition-colors hover:brightness-110" data-hash="${e.hash}" aria-expanded="false">
|
||||||
<span class="font-mono text-xs shrink-0" style="color:var(--text-5)">${e.hash}</span>
|
<span class="font-mono text-xs shrink-0" style="color:var(--text-5)">${e.hash}</span>
|
||||||
<span class="shrink-0" style="color:var(--accent)">${author || e.author}</span>
|
<span class="shrink-0 font-medium" style="color:var(--accent)">${author || e.author}</span>
|
||||||
<span class="flex-1 truncate" style="color:var(--text-2)">${rest || e.message}</span>
|
<span class="flex-1 min-w-0 truncate" style="color:var(--text-2)">${rest || e.message}</span>
|
||||||
<span class="shrink-0 text-xs" style="color:var(--text-5)">${e.date}</span>
|
<span class="shrink-0 text-xs" style="color:var(--text-5)">${e.date}</span>
|
||||||
|
<span class="log-chevron shrink-0 text-xs transition-transform" style="color:var(--text-5)">▶</span>
|
||||||
|
</button>
|
||||||
|
<div class="log-diff hidden" data-hash="${e.hash}">
|
||||||
|
<pre class="text-xs leading-5 px-3 pb-3 overflow-x-auto" style="font-family:'JetBrains Mono',monospace;border-top:1px solid var(--border);padding-top:0.75rem;margin:0"></pre>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
el.addEventListener('click', async (ev) => {
|
||||||
|
const btn = (ev.target as Element).closest<HTMLButtonElement>('.log-row');
|
||||||
|
if (!btn) return;
|
||||||
|
const hash = btn.dataset.hash!;
|
||||||
|
const diffDiv = el.querySelector<HTMLElement>(`.log-diff[data-hash="${hash}"]`);
|
||||||
|
const pre = diffDiv?.querySelector('pre');
|
||||||
|
const chevron = btn.querySelector<HTMLElement>('.log-chevron');
|
||||||
|
if (!diffDiv || !pre) return;
|
||||||
|
|
||||||
|
const isOpen = btn.getAttribute('aria-expanded') === 'true';
|
||||||
|
if (isOpen) {
|
||||||
|
diffDiv.classList.add('hidden');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
if (chevron) chevron.style.transform = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.setAttribute('aria-expanded', 'true');
|
||||||
|
if (chevron) chevron.style.transform = 'rotate(90deg)';
|
||||||
|
diffDiv.classList.remove('hidden');
|
||||||
|
|
||||||
|
if (pre.dataset.loaded) return;
|
||||||
|
pre.textContent = 'Caricamento diff…';
|
||||||
|
pre.style.color = 'var(--text-5)';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/diff/${hash}`, { credentials: 'include' });
|
||||||
|
if (!r.ok) throw new Error(String(r.status));
|
||||||
|
const { diff } = await r.json();
|
||||||
|
pre.dataset.loaded = '1';
|
||||||
|
if (!diff.trim()) {
|
||||||
|
pre.textContent = '(nessuna modifica ai file)';
|
||||||
|
pre.style.color = 'var(--text-5)';
|
||||||
|
} else {
|
||||||
|
pre.innerHTML = renderDiff(diff);
|
||||||
|
pre.style.color = '';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
pre.textContent = 'Errore nel caricamento della diff.';
|
||||||
|
pre.style.color = '#f87171';
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
const el = document.getElementById('log-list');
|
const el = document.getElementById('log-list');
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
.prose-wiki a:hover { opacity: 0.8; }
|
.prose-wiki a:hover { opacity: 0.8; }
|
||||||
.prose-wiki ul,
|
.prose-wiki ul,
|
||||||
.prose-wiki ol { padding-left: 1.5em; margin-bottom: 1em; }
|
.prose-wiki ol { padding-left: 1.5em; margin-bottom: 1em; }
|
||||||
|
.prose-wiki ul { list-style-type: disc; }
|
||||||
|
.prose-wiki ol { list-style-type: decimal; }
|
||||||
.prose-wiki li { margin-bottom: 0.25em; }
|
.prose-wiki li { margin-bottom: 0.25em; }
|
||||||
.prose-wiki strong { color: var(--text-primary); font-weight: 600; }
|
.prose-wiki strong { color: var(--text-primary); font-weight: 600; }
|
||||||
.prose-wiki code {
|
.prose-wiki code {
|
||||||
|
|||||||
Reference in New Issue
Block a user