Fjern unpkg CDN-avhengigheit og legg til slett-buffer-knapp

Leaflet vendora:
- Fjerna CDN <link> for Leaflet CSS (allereie bundla via npm-import)
- Kartmarkør-ikon brukar bundla bilete i staden for unpkg URL-ar
- CSP stramma: unpkg ikkje lenger naudsynt i style-src/img-src

Slett buffer:
- «Slett lagra data»-knapp i om-dialogen
- Slettar localStorage, IndexedDB og tenestararbeidar-bufferar
- Lokalisert til en/nb/nn

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-03-23 15:10:30 +01:00
commit 4a95e0e23f
7 changed files with 62 additions and 8 deletions

View file

@ -6,14 +6,11 @@
<meta name="theme-color" content="#1A1A2E" />
<meta name="description" content="Find the nearest public shelter in Norway" />
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: https://*.tile.openstreetmap.org https://unpkg.com; connect-src 'self' https://*.tile.openstreetmap.org; font-src 'self'; worker-src 'self'" />
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.tile.openstreetmap.org; connect-src 'self' https://*.tile.openstreetmap.org; font-src 'self'; worker-src 'self'" />
<title>Tilfluktsrom</title>
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png" />
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin="anonymous" />
</head>
<body>
<div id="app">

View file

@ -71,4 +71,6 @@ export const en: Record<string, string> = {
about_open_source: 'Open source — kode.naiv.no/olemd/tilfluktsrom',
action_about: 'About',
action_close: 'Close',
action_clear_cache: 'Clear cached data',
cache_cleared: 'All cached data cleared',
};

View file

@ -66,4 +66,6 @@ export const nb: Record<string, string> = {
about_open_source: 'Åpen kildekode — kode.naiv.no/olemd/tilfluktsrom',
action_about: 'Om',
action_close: 'Lukk',
action_clear_cache: 'Slett lagrede data',
cache_cleared: 'Alle lagrede data slettet',
};

View file

@ -66,4 +66,6 @@ export const nn: Record<string, string> = {
about_open_source: 'Open kjeldekode — kode.naiv.no/olemd/tilfluktsrom',
action_about: 'Om',
action_close: 'Lukk',
action_clear_cache: 'Slett lagra data',
cache_cleared: 'Alle lagra data sletta',
};

View file

@ -458,6 +458,24 @@ html, body {
margin-bottom: 2px;
}
.about-clear-btn {
display: block;
margin: 12px 0 0;
padding: 8px 16px;
background: transparent;
border: 1px solid #90A4AE;
border-radius: 6px;
color: #90A4AE;
font-size: 13px;
cursor: pointer;
}
.about-clear-btn:disabled {
border-color: #4a6a5a;
color: #4a6a5a;
cursor: default;
}
.about-close-btn {
display: block;
margin: 16px auto 0;

View file

@ -1,5 +1,6 @@
/**
* About dialog: app info, privacy statement, data sources, copyright.
* Includes a "Clear cached data" button that removes all local storage.
* Opens as a modal overlay, same pattern as loading-overlay.
*/
@ -35,6 +36,13 @@ export function showAbout(): void {
content.appendChild(subheading(t('about_stored_title')));
content.appendChild(para(t('about_stored_body')));
// Clear cache button
const clearBtn = document.createElement('button');
clearBtn.className = 'about-clear-btn';
clearBtn.textContent = t('action_clear_cache');
clearBtn.addEventListener('click', () => clearAllData(clearBtn));
content.appendChild(clearBtn);
const footer = document.createElement('div');
footer.className = 'about-footer';
footer.appendChild(small(t('about_copyright')));
@ -63,6 +71,28 @@ export function hideAbout(): void {
previousFocus = null;
}
/** Clear all cached data: IndexedDB, localStorage, and service worker caches. */
async function clearAllData(btn: HTMLButtonElement): Promise<void> {
btn.disabled = true;
// Clear localStorage (map cache metadata)
localStorage.clear();
// Clear IndexedDB (shelter database)
const dbs = await indexedDB.databases?.() ?? [];
for (const db of dbs) {
if (db.name) indexedDB.deleteDatabase(db.name);
}
// Clear service worker caches (map tiles, precache)
const cacheNames = await caches.keys();
for (const name of cacheNames) {
await caches.delete(name);
}
btn.textContent = t('cache_cleared');
}
function heading(text: string): HTMLElement {
const el = document.createElement('h2');
el.textContent = text;

View file

@ -6,16 +6,19 @@
*/
import L from 'leaflet';
import markerIcon from 'leaflet/dist/images/marker-icon.png';
import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
import markerShadow from 'leaflet/dist/images/marker-shadow.png';
import type { Shelter, ShelterWithDistance, LatLon } from '../types';
import { t } from '../i18n/i18n';
// Fix Leaflet default icon paths (broken by bundlers)
// Fix Leaflet default icon paths (broken by bundlers) — use bundled assets
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
iconUrl: markerIcon,
iconRetinaUrl: markerIcon2x,
shadowUrl: markerShadow,
});
const DEFAULT_ZOOM = 14;