- frontend/: Svelte 4 + TS + Vite app — omnibar (search-and-create), ranked diacritic-insensitive filtering, CodeMirror 6 markdown editor with 400ms autosave, marked+DOMPurify preview with [[wiki-links]], tag filter, undo toast, light/dark, narrow-screen stacked layout, IndexedDB store, pull-then- push sync engine with conflict handling, versioned cache-first service worker + manifest (installable PWA) - server/: tefterd — Go stdlib HTTP + modernc.org/sqlite, /api/v1 sync API (changes/batch/health/import), SHA-256 hashed bearer token, LWW-with- conflict-copies push rules, subcommands: init, token rotate, import simplenote, compact, backup (VACUUM INTO); embeds the frontend bundle - desktop/: Wails v2 shell — single instance, hide-to-tray (fyne systray), global Ctrl+Shift+Space hotkey, quit-on-close flag - Simplenote import (CLI + web upload), idempotent via source_id dedupe; verified against a real 242-note export - Makefile (frontend/server/server-all/desktop/test), GitHub release workflow, README with systemd/Caddy/backup docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { defineConfig, type Plugin } from 'vite';
|
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
import { createHash } from 'node:crypto';
|
|
|
|
const version = process.env.TEFTER_VERSION || 'dev';
|
|
|
|
// Emits sw.js at build time with the precache list of all emitted assets,
|
|
// versioned by a hash of the file names (which themselves carry content hashes).
|
|
function serviceWorker(): Plugin {
|
|
return {
|
|
name: 'tefter-sw',
|
|
apply: 'build',
|
|
generateBundle(_opts, bundle) {
|
|
const assets = Object.keys(bundle)
|
|
.filter((f) => !f.endsWith('.map'))
|
|
.map((f) => '/' + f);
|
|
assets.push('/', '/manifest.webmanifest', '/icon-192.png', '/icon-512.png');
|
|
const hash = createHash('sha256').update(assets.join(',') + version).digest('hex').slice(0, 12);
|
|
const sw = `// generated by vite.config.ts — do not edit
|
|
const CACHE = 'tefter-${'${'}HASH}';
|
|
const ASSETS = ${'${'}ASSETS};
|
|
|
|
self.addEventListener('install', (e) => {
|
|
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(ASSETS)).then(() => self.skipWaiting()));
|
|
});
|
|
|
|
self.addEventListener('activate', (e) => {
|
|
e.waitUntil(
|
|
caches.keys()
|
|
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
|
|
.then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', (e) => {
|
|
const url = new URL(e.request.url);
|
|
if (e.request.method !== 'GET' || url.origin !== location.origin) return;
|
|
if (url.pathname.startsWith('/api/')) return; // network-only; sync engine handles offline
|
|
e.respondWith(
|
|
caches.match(url.pathname === '/' || url.pathname === '/index.html' ? '/' : e.request).then(
|
|
(hit) => hit || fetch(e.request)
|
|
)
|
|
);
|
|
});
|
|
`
|
|
.replace('${HASH}', hash)
|
|
.replace('${ASSETS}', JSON.stringify(assets));
|
|
this.emitFile({ type: 'asset', fileName: 'sw.js', source: sw });
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins: [svelte(), serviceWorker()],
|
|
define: {
|
|
__TEFTER_VERSION__: JSON.stringify(version),
|
|
},
|
|
build: {
|
|
target: 'es2020',
|
|
sourcemap: false,
|
|
},
|
|
server: {
|
|
proxy: {
|
|
'/api': 'http://127.0.0.1:8420',
|
|
},
|
|
},
|
|
});
|