bsc

Comprehensive codebase and cou...
Log | Files | Refs | Activity | README | LICENSE

commit cf6fd5bcd3e14772a19346162e2aa62049b9e6b0
parent 36ac1f57f697329d357b10345db342d86e2ebfb8
Author: Amit Dutta <mail@amit.is-a.dev>
Date:   Sat, 29 Aug 2026 19:19:23 +0530

Refactor snapshot manager to include file handling
Diffstat:
Mdocs/.vitepress/theme/components/SnapshotManager.vue | 168++++++++-----------------------------------------------------------------------
1 file changed, 16 insertions(+), 152 deletions(-)

diff --git a/docs/.vitepress/theme/components/SnapshotManager.vue b/docs/.vitepress/theme/components/SnapshotManager.vue @@ -1,15 +1,9 @@ <script setup> import { ref, computed, onMounted } from 'vue' -import { listSnapshots, putSnapshot, deleteSnapshotMeta } from '../lib/snapshotDb' +import { listSnapshots, putSnapshot, putSnapshotData, deleteSnapshotMeta, deleteSnapshotData } from '../lib/snapshotDb' const snapshots = ref([]) const isCapturing = ref(false) -const progress = ref({ done: 0, total: 0, phaseLabel: '', pagesTotal: 0, assetsTotal: 0 }) - -const progressPct = computed(() => { - if (!progress.value.total) return 0 - return Math.min(100, Math.round((progress.value.done / progress.value.total) * 100)) -}) const error = ref('') onMounted(refresh) @@ -30,129 +24,43 @@ function fmtDate(ts) { return new Date(ts).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }) } -async function getPagePaths() { - const res = await fetch('/sitemap.xml') - if (!res.ok) throw new Error('Could not read /sitemap.xml') - const xml = await res.text() - const locs = [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]) - return locs.map((loc) => { - try { - return new URL(loc).pathname - } catch { - return null - } - }).filter(Boolean) -} - -function extractAssetUrls(html) { - const urls = new Set() - const re = /(?:src|href)="([^"]+)"/g - let m - while ((m = re.exec(html))) { - const u = m[1] - if (u.startsWith('/') && !u.startsWith('//')) urls.add(u) - } - return urls -} - -// Runs `worker` over `items` with up to `concurrency` in flight at once, -// instead of one-at-a-time — this is what makes capture fast, and it's what -// lets the progress bar move continuously instead of stalling. -async function runPool(items, concurrency, worker) { - let i = 0 - async function runNext() { - while (i < items.length) { - const idx = i++ - await worker(items[idx], idx) - } - } - const workers = Array.from({ length: Math.min(concurrency, items.length) }, runNext) - await Promise.all(workers) -} - -const CONCURRENCY = 12 - async function takeSnapshot() { error.value = '' isCapturing.value = true const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}` - const cacheName = `snapshot-${id}` - let sizeBytes = 0 - const assetUrls = new Set() try { - const pagePaths = await getPagePaths() - // "total" spans pages + assets so the bar reflects overall progress, not - // just the current phase resetting to 0. - progress.value = { done: 0, total: pagePaths.length, phaseLabel: 'Fetching pages…', pagesTotal: pagePaths.length, assetsTotal: 0 } - - const cache = await caches.open(cacheName) - - await runPool(pagePaths, CONCURRENCY, async (path) => { - try { - const res = await fetch(path, { cache: 'no-store' }) - if (res && res.ok) { - const html = await res.clone().text() - sizeBytes += html.length - extractAssetUrls(html).forEach((u) => assetUrls.add(u)) - await cache.put(path, res) - } - } catch (_) { - /* skip page that failed, continue capturing the rest */ - } - progress.value.done++ - }) - - const assetList = [...assetUrls] - progress.value = { - done: pagePaths.length, - total: pagePaths.length + assetList.length, - phaseLabel: 'Fetching assets…', - pagesTotal: pagePaths.length, - assetsTotal: assetList.length, - } + const res = await fetch('/content.json', { cache: 'no-store' }) + if (!res.ok) throw new Error(`Could not download content.json (${res.status})`) + const files = await res.json() + if (!Array.isArray(files)) throw new Error('content.json was not the expected format') - await runPool(assetList, CONCURRENCY, async (url) => { - try { - const res = await fetch(url, { cache: 'no-store' }) - if (res && res.ok) { - const len = res.headers.get('content-length') - sizeBytes += len ? Number(len) : 0 - await cache.put(url, res) - } - } catch (_) { - /* skip */ - } - progress.value.done++ - }) + const sizeBytes = files.reduce((sum, f) => sum + (f.size || 0), 0) + await putSnapshotData(id, files) await putSnapshot({ id, name: `Snapshot – ${new Date().toLocaleDateString()}`, createdAt: Date.now(), - pageCount: pagePaths.length, - assetCount: assetList.length, + fileCount: files.length, sizeBytes, }) await refresh() } catch (e) { error.value = e?.message || 'Snapshot failed.' - // best-effort cleanup of a partial cache - try { await caches.delete(cacheName) } catch (_) {} } finally { isCapturing.value = false - progress.value = { done: 0, total: 0, phaseLabel: '', pagesTotal: 0, assetsTotal: 0 } } } function viewSnapshot(id) { - window.open(`/snapshots/view/${id}/`, '_blank', 'noopener') + window.open(`/snapshots/view/?id=${encodeURIComponent(id)}`, '_blank', 'noopener') } async function removeSnapshot(id) { if (!confirm('Delete this snapshot? This cannot be undone.')) return - await caches.delete(`snapshot-${id}`) + await deleteSnapshotData(id) await deleteSnapshotMeta(id) await refresh() } @@ -168,7 +76,7 @@ async function renameSnapshot(snap) { <template> <div class="snapshot-manager"> <p class="intro"> - Snapshots save a fully offline, point-in-time copy of the site into your browser + Snapshots save a full offline copy of every file's content into your browser (separate from normal caching — the live site keeps auto-updating as usual). Take one before you go offline, and come back to it any time from this page. </p> @@ -178,20 +86,11 @@ async function renameSnapshot(snap) { class="capture-btn" @click="takeSnapshot" > - 📸 Take snapshot now + Take snapshot now </button> - <div v-else class="capture-btn capture-progress" role="progressbar" - :aria-valuenow="progressPct" aria-valuemin="0" aria-valuemax="100"> - <div class="capture-progress-fill" :style="{ width: progressPct + '%' }"></div> - <!-- Two copies of the same label, each clipped to one side of the fill, - so the text stays readable no matter how far the bar has progressed. --> - <div class="capture-progress-label capture-progress-label-track"> - {{ progress.phaseLabel }} {{ progress.done }}/{{ progress.total }} ({{ progressPct }}%) - </div> - <div class="capture-progress-label capture-progress-label-fill" :style="{ clipPath: `inset(0 ${100 - progressPct}% 0 0)` }"> - {{ progress.phaseLabel }} {{ progress.done }}/{{ progress.total }} ({{ progressPct }}%) - </div> + <div v-else class="capture-btn capture-progress"> + Downloading content… </div> <p v-if="error" class="error">{{ error }}</p> @@ -201,7 +100,7 @@ async function renameSnapshot(snap) { <div class="snapshot-info"> <div class="snapshot-name">{{ s.name }}</div> <div class="snapshot-meta"> - {{ fmtDate(s.createdAt) }} · {{ s.pageCount }} pages · {{ s.assetCount }} assets · {{ fmtBytes(s.sizeBytes) }} + {{ fmtDate(s.createdAt) }} · {{ s.fileCount }} files · {{ fmtBytes(s.sizeBytes) }} </div> </div> <div class="snapshot-actions"> @@ -240,43 +139,8 @@ async function renameSnapshot(snap) { justify-content: center; } .capture-progress { - position: relative; - overflow: hidden; cursor: default; - justify-content: flex-start; - padding: 0; - background: var(--vp-c-bg-soft); - border: 1px solid var(--vp-c-brand-1); -} -.capture-progress-fill { - position: absolute; - inset: 0 auto 0 0; - background: var(--vp-c-brand-1); - transition: width 0.25s ease; -} -.capture-progress-label { - position: absolute; - inset: 0; - z-index: 1; - width: 100%; - display: flex; - align-items: center; - justify-content: center; - padding: 0 12px; - font-size: 12.5px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - pointer-events: none; -} -/* Text over the unfilled track: use the normal (theme-adaptive) text color. */ -.capture-progress-label-track { - color: var(--vp-c-text-1); -} -/* Text over the colored fill: always white, revealed only where the fill covers it. */ -.capture-progress-label-fill { - color: #fff; + opacity: 0.8; } .error { color: var(--vp-c-danger-1, #d33);
© notamitgamer • Site Built: 2026-09-05 01:53:16 UTC • git-mirror commit: c170d72 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror