Compare commits
4 Commits
7d8fcce2e1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b1f2c4c875 | |||
| a32c0d0911 | |||
| 486e34baf3 | |||
| 91cb337c03 |
@@ -0,0 +1,15 @@
|
||||
node_modules
|
||||
dist
|
||||
.astro
|
||||
data
|
||||
.git
|
||||
.gitignore
|
||||
.env*
|
||||
.claude
|
||||
.vscode
|
||||
docs
|
||||
*.md
|
||||
!src/**/*.md
|
||||
!src/**/*.mdx
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
+1
-1
@@ -24,4 +24,4 @@ pnpm-debug.log*
|
||||
.idea/
|
||||
|
||||
# local data (view counter SQLite)
|
||||
data/
|
||||
/data/
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# ta1sph.info — Astro static site + small Node server for /api/* (view counter).
|
||||
# Dokploy: Build Type "Dockerfile", container port 4321, volume at /app/data.
|
||||
|
||||
# ---- build ----
|
||||
FROM node:24-slim AS build
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build && npm prune --omit=dev
|
||||
|
||||
# ---- runtime ----
|
||||
FROM node:24-slim AS runtime
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production \
|
||||
HOST=0.0.0.0 \
|
||||
PORT=4321 \
|
||||
DATA_DIR=/app/data
|
||||
COPY --from=build --chown=node:node /app/package.json ./
|
||||
COPY --from=build --chown=node:node /app/node_modules ./node_modules
|
||||
COPY --from=build --chown=node:node /app/dist ./dist
|
||||
COPY --from=build --chown=node:node /app/server.mjs ./
|
||||
# Named volume mounted here keeps the view-counter database across deploys.
|
||||
RUN mkdir -p /app/data && chown node:node /app/data
|
||||
VOLUME ["/app/data"]
|
||||
USER node
|
||||
EXPOSE 4321
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD node -e "fetch('http://127.0.0.1:4321/api/health/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
CMD ["node", "server.mjs"]
|
||||
+22
-9
@@ -1,22 +1,22 @@
|
||||
# PROJECT_MEMORY — ta1sph.info
|
||||
|
||||
> Claude'un bu proje için kalıcı hafızası. Her görev sonunda güncellenir. Kısa ve güncel tutulur.
|
||||
> Son güncelleme: 2026-10-16 · Güncelleyen: Claude (Siteyi Kur — Aşama 7)
|
||||
> Son güncelleme: 2026-10-16 · Güncelleyen: Claude (Uygulama Düzenle — görüntülenme rozeti)
|
||||
|
||||
## 1. Durum
|
||||
|
||||
- **Aşama:** 7 tamamlandı (kalite). Sıradaki: **Aşama 8 — Deploy hazırlığı**.
|
||||
- **Tamamlanan:** Aşama 0–7. Stil önizleme yalnızca dev'de: `http://localhost:4321/styleguide/`. Lighthouse (mobil, 3 koşu ortancası, yerel): A11y/BP/SEO 100 her sayfada; Perf temiz koşullarda 98–100, LCP ~1,4–1,9 sn. Yayında PageSpeed Insights ile doğrulanmalı.
|
||||
- **Açık işler:** Aşama 8. Node sunucusu yanıtları sıkıştırmıyor → A8'de Traefik `compress` middleware veya eşdeğeri şart. 3 örnek yazı (`örnek` etiketli) kullanıcı isteyince silinecek. Hakkımda tanıtım paragrafı kullanıcı metin verirse.
|
||||
- **Aşama:** Siteyi Kur tamamlandı — **site yayında** (2026-10-16, commit `486e34b`). Artık `Makale Ekle` / `Uygulama Düzenle` / `Yayına Al` akışları.
|
||||
- **Tamamlanan:** Aşama 0–8 (kod). Docker imajı yerelde build + çalıştırma + volume kalıcılığı test edildi (test kalıntıları silindi). Lighthouse (yerel): A11y/BP/SEO 100; Perf temiz koşullarda 98–100. Yayında PageSpeed Insights ile doğrulanmalı.
|
||||
- **Açık işler (kullanıcı tarafı):** Dokploy'da www → apex 301 (Advanced → Redirects; 2026-10-16 itibarıyla www hâlâ 200 veriyor). Volume `ta1sph-data` → `/app/data` doğrulanmalı (yazı oku → `/api/views/top/` → redeploy sonrası korunuyor mu). Google Search Console + sitemap. PageSpeed Insights. Volume yedeği. 3 örnek yazı (`örnek`) gerçek yazılar gelince silinecek. Hakkımda tanıtım paragrafı. İsteğe bağlı: HSTS başlığı (`server.mjs`).
|
||||
- **Bilinen sorunlar:** Arama (Pagefind) `astro dev`'de çalışmaz, yalnızca build sonrası. Kullanıcının elle başlattığı dev sunucusu yeni koleksiyonu görmüyorsa yeniden başlatılmalı. `npm audit`: 2 orta seviye (postcss-selector-parser 6.0.10, @tailwindcss/typography sabitliyor) — yalnızca build zamanı, düzeltme typography'yi eski sürüme düşürdüğü için uygulanmadı.
|
||||
|
||||
## 2. Ortam
|
||||
|
||||
- Astro 7.4.0 · TailwindCSS 4.3.3 (`@tailwindcss/vite`) · TypeScript 6 strict · zod = `astro/zod` (ayrı paket yok)
|
||||
- @astrojs/node 11.1.8 · mdx 8.1.0 · sitemap 3.7.4 · rss 4.0.19 · astro-icon 1.2.0 · pagefind 1.5.2
|
||||
- Yerel: Node v26.11.1, npm 12.2.0, Docker 29.8.1 · `package.json` engines: node >=22.22
|
||||
- Repo: yerel git (Gitea remote henüz yok; push yalnızca kullanıcı onayıyla) · Branch: `main`
|
||||
- Canlı: https://ta1sph.info (Dokploy, HomeServer Atlas) — _henüz yayında değil_
|
||||
- Yerel: Node v26.11.1, npm 12.2.0, Docker 29.8.1 · `package.json` engines: node >=24 · Docker: `node:24-slim`
|
||||
- Repo: `origin` = https://gitea.ta1sph.info/suphicakir/ta1sph.info.git · Branch: `main` · **Push'u kullanıcı yapar** (Claude push etmez)
|
||||
- Canlı: https://ta1sph.info (Dokploy, HomeServer Atlas; Traefik + Let's Encrypt, sertifikalar 2027-01-14'e kadar). Dokploy uygulaması: Gitea provider "Atlas", Autodeploy açık → `main`'e push = yayın. DNS: turkticaret.net (A @ / www → 159.146.61.151, sabit IP); Atlas LAN IP 192.168.1.108
|
||||
|
||||
## 3. Dosya Haritası
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
- `src/components/blog/` — `BlogListing` (liste düzeni + sidebar), `PostCard` (row/stack, kapaksızsa kategori ikonu), `DateBadges`, `Pager`, `Sidebar`, `SearchBox` (Pagefind JS API, özel UI), `ShareBar`, `Toc`, `Breadcrumbs`, `DiyNotice`
|
||||
- `global.css` — `.article-prose` (prose token eşlemesi), Shiki çift tema, `.code-copy`
|
||||
- `src/lib/views.ts` — `node:sqlite` sayaç (`$DATA_DIR/views.db`, varsayılan `./data`; Docker'da `/app/data` volume). Tablolar: `views(slug,count)`, `hits(hash,at)` 24 sa, `salts(day,salt)` günlük tuz. IP saklanmaz; `clientIp` önce `X-Forwarded-For`; `isBot` UA filtresi (headless dahil)
|
||||
- `src/pages/api/views/[slug].ts` (POST, 204; bilinmeyen slug 404) · `src/pages/api/views/top.ts` (GET `?limit=1..10`, JSON `{slug,title,url,count}`, 5 dk cache) — ikisi `prerender = false`
|
||||
- `src/pages/api/views/[slug].ts` (POST, 204; bilinmeyen slug 404) · `src/pages/api/views/counts.ts` (GET `{slug: count}` tüm yayındaki yazılar, 0 dahil, 60 sn cache) · `src/pages/api/views/top.ts` (GET `?limit=1..10`, JSON `{slug,title,url,count}`, 5 dk cache) — ikisi `prerender = false`
|
||||
- Görüntülenme rozeti: `DateBadges` `viewsSlug` prop'u → göz ikonlu outline rozet (`blog.json` `card.views` = "{n} görüntülenme"), `invisible` ile yer ayrılır (CLS 0), script `/api/views/counts/`'tan doldurur; API yoksa görünmez kalır. Kartlarda + yazı sayfasında. Her zaman gösterilir (0 dahil) — eşik istenirse script'te
|
||||
- Yazı sayfası `sendBeacon('/api/views/<slug>/')`; Sidebar script'i `/api/views/top/` ile `ol[data-featured]`'ı değiştirir, eksikleri build-time yedekle tamamlar
|
||||
- `src/data/about.json` (künye + ekipman grupları) · `src/data/contact.json` (harita lat/lng/zoom, bilgi satırları; `email` HTML'de açık yazılmaz, JS ile birleştirilir) · `src/data/pages.json` (yasal sayfa ortak metinleri, 404)
|
||||
- `src/pages/hakkimda.astro` (ProfilePage JSON-LD) · `src/pages/iletisim.astro` (Leaflet + OSM, görünür olunca dinamik import; `divIcon` içinde `.tower-pin`) · `src/pages/[legal].astro` + `src/content/legal/{gizlilik,lisans,sorumluluk-reddi}.md` (koleksiyon `legal`, frontmatter `updated`) · `src/pages/404.astro`
|
||||
@@ -48,10 +49,13 @@
|
||||
- `public/fonts/*.woff2` + `src/styles/fonts.css` — **üretilir:** `npm run fonts` (`scripts/subset-fonts.mjs`, subset-font; Latin+Türkçe+sembol, wght Rubik 400–900 / Inter 400–700; 188 KB → 66 KB). Kaynak `@fontsource-variable/*` (devDeps). BaseLayout iki latin dosyasını preload eder
|
||||
- `public/favicon.{svg,ico}`, `apple-touch-icon.png`, `icon-192/512.png`, `icon-maskable-512.png` — **üretilir:** `npm run icons` (`scripts/generate-icons.mjs`; kırmızı zemin + siyah kule, 16px için kalın varyant)
|
||||
- `scripts/check-site.mjs` — `npm run check:site` (build sonrası): tek h1, alt, title/description/canonical, tekrar eden id, iç link + anchor
|
||||
- `server.mjs` — üretim girişi (`npm start`): `ASTRO_NODE_AUTOSTART=disabled` ile Astro handler'ını sarar; `compression` (br/gzip), güvenlik başlıkları, SIGTERM'de düzgün kapanma. `/_astro/*` immutable cache adapter'dan
|
||||
- `src/pages/api/health.ts` — `GET /api/health/` (trailing slash!) → `{status, db}`; DB yoksa 503
|
||||
- `Dockerfile` (node:24-slim, build → `npm prune --omit=dev` → runtime; USER node; HEALTHCHECK; VOLUME /app/data) · `.dockerignore` · `docker-compose.yml` (Dokploy Compose alternatifi)
|
||||
- Bağımlılıklar: runtime yalnızca `astro`, `@astrojs/node`, `compression`; geri kalan her şey devDependencies (imaj 531 MB, ~200 MB'ı node:24-slim)
|
||||
- `src/dev/Styleguide.astro` — `astro.config.mjs`'deki `devRoutes` entegrasyonu ile sadece `astro dev`'de `/styleguide/` (`_` önekli sayfalar Astro'da rota olmaz)
|
||||
- `src/lib/slug.ts` — Türkçe karakter dönüştürücü `slugify`
|
||||
- `scripts/new-post.mjs` — `npm run new:post -- "Başlık"` → `src/content/blog/<slug>/index.md` (draft: true); `slug.ts`'yi Node type-stripping ile import eder
|
||||
- `src/pages/index.astro` — geçici yer tutucu (Aşama 3'te değişecek)
|
||||
- `docs/design-reference/` — Huemint Magazine ekran görüntüleri · `CLAUDE.md` → `AGENTS.md` + bu dosya
|
||||
- Build: `astro build && pagefind --site dist/client` · statik çıktı `dist/client`, sunucu `dist/server/entry.mjs`
|
||||
|
||||
@@ -68,6 +72,11 @@
|
||||
| 2026-10-15 | Kanonik alan: apex `ta1sph.info`, www → 301 | SEO tekilleştirme |
|
||||
| 2026-10-15 | Fontlar self-host (Fontsource: Rubik + Inter) | Performans + gizlilik |
|
||||
| 2026-10-16 | Ek token'lar: `accent-fill`/`on-accent-fill`, `accent-text` (khaki'de fg'ye döner) | Ölçüm: ink/red 4.46, red-strong/khaki 3.85 → küçük metin AA'yı geçmiyordu. Kırmızı zeminli butonlar ≥19px bold |
|
||||
| 2026-10-16 | Görüntülenme sayısı kart + yazı sayfasında rozet, 0 dahil her zaman — kullanıcı isteği | Statik sayfa + istemci tarafı tek istek; yer ayırma ile kayma yok |
|
||||
| 2026-10-16 | Docker testleri **temiz git klonundan** yapılır (çalışma dizininden değil) | Yoksayılan/commit'lenmemiş dosyalar yerel testte görünmez; Dokploy repo'dan klonlar |
|
||||
| 2026-10-16 | Sıkıştırma konteyner içinde (`server.mjs` + `compression`), Traefik ayarı yok | Kullanıcının Dokploy'da ek middleware kurmasına gerek kalmaz |
|
||||
| 2026-10-16 | Docker taban imajı `node:24-slim` (LTS) | `node:sqlite` 24 ve 26'da uyarısız test edildi; 24 LTS Nisan 2028'e kadar destekli |
|
||||
| 2026-10-16 | Kalıcı veri için Dokploy'da named volume (bind mount değil) | Named volume ilk mount'ta imajdaki `node` sahipliğini alır; bind mount root olur → yazma hatası |
|
||||
| 2026-10-16 | Yazı başına OG görseli build'de (Satori) — kullanıcı kararı bana bıraktı | Paylaşımlarda başlıklı kart; harici servis yok |
|
||||
| 2026-10-16 | Favicon = kırmızı kare + siyah anten kulesi (Tabler) | "TA1" 16px'te okunmuyor; vurgu rengi + İletişim ikonuyla tutarlı |
|
||||
| 2026-10-16 | Fontlar alt kümelenip `public/fonts`'tan; `build.inlineStylesheets: 'always'` | LCP'yi geciktiren render-blocking CSS + 190 KB font; ölçümde FCP 2,3 → ~1 sn |
|
||||
@@ -100,6 +109,10 @@
|
||||
|
||||
## 6. Değişiklik Günlüğü (en yeni üstte)
|
||||
|
||||
- 2026-10-16 | Uygulama Düzenle | Okuma süresinin yanına göz ikonlu görüntülenme rozeti (kart + yazı); `/api/views/counts/`; iki tema, CLS 0, axe temiz | `src/components/blog/DateBadges.astro`, `src/components/Badge.astro`, `src/components/blog/PostCard.astro`, `src/pages/blog/[slug].astro`, `src/pages/api/views/counts.ts`, `src/lib/views.ts`, `src/data/blog.json`, `src/lib/data.ts`
|
||||
- 2026-10-16 | Yayına Al | İlk yayın: DNS (apex+www), Dokploy uygulaması, Let's Encrypt; canlı doğrulama (sayfalar, API, br, başlıklar, sitemap, RSS) | — (sunucu tarafı)
|
||||
- 2026-10-16 | Yayına Al (fix) | `.gitignore` `data/` → `/data/`: `src/data/*.json` hiç commit'lenmemişti, Dokploy build'i kırıldı; temiz klon + Docker ile doğrulandı | `.gitignore`, `src/data/`
|
||||
- 2026-10-16 | Siteyi Kur A8 | Dockerfile, .dockerignore, compose, server.mjs (sıkıştırma + güvenlik başlıkları), /api/health, build-only bağımlılıklar devDeps'e; Docker'da uçtan uca test | `Dockerfile`, `.dockerignore`, `docker-compose.yml`, `server.mjs`, `src/pages/api/health.ts`, `src/lib/views.ts`, `package.json`, `README.md`
|
||||
- 2026-10-16 | Siteyi Kur A7 | OG görselleri (yazı başına + varsayılan), favicon seti + manifest, robots.txt, font alt kümeleme + preload, CSS inline, site denetim script'i, Lighthouse | `src/lib/og.ts`, `src/pages/{og,og-default.png,robots.txt,site.webmanifest}*`, `scripts/{generate-icons,subset-fonts,check-site}.mjs`, `public/`, `src/styles/fonts.css`, `Seo.astro`, `BaseLayout.astro`, `astro.config.mjs`
|
||||
- 2026-10-16 | Siteyi Kur A6 | Hakkımda, İletişim (Leaflet harita, gizlenmiş e-posta), Gizlilik/Lisans/Sorumluluk Reddi, 404, LICENSE dosyaları; PageHeader + CodeCopy ortak bileşenleri; axe temiz | `src/pages/{hakkimda,iletisim,[legal],404}.astro`, `src/content/legal/`, `src/data/{about,contact,pages}.json`, `src/components/{PageHeader,CodeCopy,Card}.astro`, `LICENSE*`, `global.css`
|
||||
- 2026-10-16 | Siteyi Kur A5 | Görüntülenme sayacı (node:sqlite, anonim, 24 sa tekil, bot filtresi), top API, sidebar öne çıkanlar API + yedek; uçtan uca test | `src/lib/views.ts`, `src/pages/api/views/`, `src/components/blog/Sidebar.astro`, `src/pages/blog/[slug].astro`, `src/lib/posts.ts`
|
||||
|
||||
@@ -18,6 +18,14 @@ Suphi Çakır (TA1SPH) — amatör telsizcilik blog & portfolyo sitesi.
|
||||
| `npm run format` | Prettier ile biçimlendirir |
|
||||
| `npm run new:post -- "Başlık"` | `src/content/blog/<slug>/index.md` taslağı oluşturur |
|
||||
|
||||
## Yayın (Docker · Dokploy)
|
||||
|
||||
- `Dockerfile` (Node 24 LTS, çok aşamalı) → konteyner `node server.mjs`, port **4321**, kullanıcı `node`.
|
||||
- Kalıcı veri: görüntülenme sayacı `/app/data/views.db` → Dokploy'da **Volume Mount** `/app/data`.
|
||||
- Sağlık kontrolü: `GET /api/health/` (`{"status":"ok","db":true}`).
|
||||
- Ortam değişkenleri: `HOST` (0.0.0.0), `PORT` (4321), `DATA_DIR` (/app/data) — imajda varsayılan.
|
||||
- `docker-compose.yml`: Dokploy "Compose" türü için alternatif.
|
||||
|
||||
## Lisans
|
||||
|
||||
Kod [MIT](LICENSE), içerik [CC BY-SA 4.0](LICENSE-CONTENT).
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Alternative to Dokploy's "Application" type: Dokploy → Create Service → Compose.
|
||||
# Domains/HTTPS are still configured in Dokploy's UI (Traefik); no host ports are published.
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DATA_DIR: /app/data
|
||||
volumes:
|
||||
- ta1sph-data:/app/data
|
||||
expose:
|
||||
- '4321'
|
||||
|
||||
volumes:
|
||||
ta1sph-data:
|
||||
Generated
+292
-10
@@ -8,26 +8,27 @@
|
||||
"name": "ta1sph.info",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@astrojs/markdown-satteri": "^0.4.4",
|
||||
"@astrojs/mdx": "^8.1.0",
|
||||
"@astrojs/node": "^11.1.8",
|
||||
"@astrojs/rss": "^4.0.19",
|
||||
"@astrojs/sitemap": "^3.7.4",
|
||||
"@iconify-json/tabler": "^1.2.42",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"astro": "^7.4.0",
|
||||
"astro-icon": "^1.2.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"tailwindcss": "^4.3.3"
|
||||
"compression": "^1.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.10",
|
||||
"@astrojs/markdown-satteri": "^0.4.4",
|
||||
"@astrojs/mdx": "^8.1.0",
|
||||
"@astrojs/rss": "^4.0.19",
|
||||
"@astrojs/sitemap": "^3.7.4",
|
||||
"@fontsource-variable/inter": "^5.3.0",
|
||||
"@fontsource-variable/rubik": "^5.3.0",
|
||||
"@fontsource/inter": "^5.3.0",
|
||||
"@fontsource/rubik": "^5.3.0",
|
||||
"@iconify-json/tabler": "^1.2.42",
|
||||
"@tailwindcss/typography": "^0.5.20",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@types/compression": "^1.8.1",
|
||||
"@types/leaflet": "^1.9.22",
|
||||
"astro-icon": "^1.2.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"pagefind": "^1.5.2",
|
||||
"prettier": "^3.9.10",
|
||||
"prettier-plugin-astro": "^1.1.1",
|
||||
@@ -35,16 +36,18 @@
|
||||
"satori": "^0.60.0",
|
||||
"sharp": "^0.35.5",
|
||||
"subset-font": "^2.9.0",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.22.0"
|
||||
"node": ">=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@antfu/install-pkg": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz",
|
||||
"integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"package-manager-detector": "^1.8.0",
|
||||
@@ -418,6 +421,7 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-8.1.0.tgz",
|
||||
"integrity": "sha512-xzrlXrjpBYvC27MTkP+2NMiJG4Jjl2DcoNc/EF9RNxAp0dwhvXC3b22+7OO4kHJQKfISk2uy38HYp1UxX7JfSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "0.13.0",
|
||||
@@ -468,6 +472,7 @@
|
||||
"version": "4.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.19.tgz",
|
||||
"integrity": "sha512-e+z5wYeYtffQdHQO8c2tkSd2JEBdAuRXJV4ZEU5IxkYeE6e39woDd7nw1PH1Kk2tEYNCYuKdylnnbhGmt61awA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-parser": "^5.5.7",
|
||||
@@ -479,6 +484,7 @@
|
||||
"version": "3.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.4.tgz",
|
||||
"integrity": "sha512-LbKNC24bdUWcQf/pThB6qLlSqHojxGjZDURIzFocY8rlWnAn2t74nnhnK6S5x0NHriHoAduLEpVjRykmeGiVvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sitemap": "^9.0.0",
|
||||
@@ -765,6 +771,7 @@
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@cyberalien/svg-utils/-/svg-utils-1.2.19.tgz",
|
||||
"integrity": "sha512-paDJoDu+LhuH5Ma1q0BDqBpg6d16Xk22cZZrlg/s/J9oB8KgnnpU/snaEETJu2G1pwYcWQINUz6WUqu0Wm9k6A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/types": "^2.0.0"
|
||||
@@ -907,6 +914,7 @@
|
||||
"version": "1.2.42",
|
||||
"resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.2.42.tgz",
|
||||
"integrity": "sha512-fObaBh/H2zJphk3lS1dWPUf/buuGaCHQXnB4UYgKOPXQub5Ac1qEu0AHRj/0+E2FEl8pWfgM2xQWzfz7ffcY+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/types": "*"
|
||||
@@ -916,6 +924,7 @@
|
||||
"version": "5.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/tools/-/tools-5.0.14.tgz",
|
||||
"integrity": "sha512-wJ4ZE3/HZh2iBqLwG+8FOMHe7CMJAJqwSXZSpPwMdj1gZM3Z4t2hbQMVw4Vm45oQ985qn0g89PUIcDEdKTheBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cyberalien/svg-utils": "^1.2.19",
|
||||
@@ -931,12 +940,14 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@iconify/utils": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.7.tgz",
|
||||
"integrity": "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@antfu/install-pkg": "^2.0.1",
|
||||
@@ -1497,6 +1508,7 @@
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
@@ -1507,6 +1519,7 @@
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
@@ -1517,6 +1530,7 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@@ -1532,6 +1546,7 @@
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@@ -1563,6 +1578,7 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.1.0.tgz",
|
||||
"integrity": "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2083,6 +2099,7 @@
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
|
||||
"integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
@@ -2098,6 +2115,7 @@
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
||||
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
@@ -2130,6 +2148,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2150,6 +2169,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2170,6 +2190,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2190,6 +2211,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2210,6 +2232,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2230,6 +2253,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
@@ -2253,6 +2277,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
@@ -2276,6 +2301,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
@@ -2299,6 +2325,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
@@ -2322,6 +2349,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2342,6 +2370,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2359,6 +2388,7 @@
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
@@ -2368,6 +2398,7 @@
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
|
||||
"integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
@@ -2394,6 +2425,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2410,6 +2442,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2426,6 +2459,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2442,6 +2476,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2458,6 +2493,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2474,6 +2510,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
@@ -2493,6 +2530,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
@@ -2512,6 +2550,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
@@ -2531,6 +2570,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
@@ -2558,6 +2598,7 @@
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -2579,6 +2620,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2595,6 +2637,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2621,6 +2664,7 @@
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz",
|
||||
"integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "4.3.3",
|
||||
@@ -2641,6 +2685,38 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/compression": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
|
||||
"integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
@@ -2656,6 +2732,31 @@
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
|
||||
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "^5.0.0",
|
||||
"@types/serve-static": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express-serve-static-core": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz",
|
||||
"integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/qs": "*",
|
||||
"@types/range-parser": "*",
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
@@ -2672,6 +2773,13 @@
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-errors": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
||||
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/leaflet": {
|
||||
"version": "1.9.22",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.22.tgz",
|
||||
@@ -2704,20 +2812,57 @@
|
||||
"version": "24.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz",
|
||||
"integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
|
||||
"integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/sax": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
|
||||
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/send": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
|
||||
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/serve-static": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
|
||||
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-errors": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
@@ -2944,6 +3089,7 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz",
|
||||
"integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2956,6 +3102,7 @@
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
@@ -3061,6 +3208,7 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/astro-icon/-/astro-icon-1.2.0.tgz",
|
||||
"integrity": "sha512-0848V2WZuTPhqGSqLeZimLJZ5wmamMtWYzvA2kvg8s2DrUPZ3/nrTcHJWN0eJHt62jy7uvNxgTNiUJGIr7JNxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/tools": "^5.0.12",
|
||||
@@ -3106,6 +3254,15 @@
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
@@ -3253,6 +3410,56 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": ">= 1.43.0 < 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/compression": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz",
|
||||
"integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"compressible": "~2.0.18",
|
||||
"debug": "2.6.9",
|
||||
"destroy": "1.2.0",
|
||||
"negotiator": "~0.6.4",
|
||||
"on-headers": "~1.1.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/compression/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compression/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz",
|
||||
@@ -3461,6 +3668,16 @@
|
||||
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
@@ -3618,6 +3835,7 @@
|
||||
"version": "5.26.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz",
|
||||
"integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
@@ -3734,6 +3952,7 @@
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz",
|
||||
"integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -3750,6 +3969,7 @@
|
||||
"version": "5.11.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.2.tgz",
|
||||
"integrity": "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -3790,6 +4010,7 @@
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
|
||||
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/flattie": {
|
||||
@@ -3910,6 +4131,7 @@
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/h3": {
|
||||
@@ -4031,6 +4253,7 @@
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
|
||||
"integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -4083,6 +4306,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz",
|
||||
"integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -4095,6 +4319,7 @@
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
||||
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
@@ -4127,6 +4352,7 @@
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
@@ -4575,6 +4801,7 @@
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.8.6.tgz",
|
||||
"integrity": "sha512-LYSks0tNVqbtez/9OB0YFP6qLRSqkdAxJWOQpVaR67lNTbj2zNLyYpowkRMia5lXLCZKzxwoxkxH2MbbBR2aEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
@@ -4620,6 +4847,15 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.4",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
|
||||
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/neotraverse": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz",
|
||||
@@ -4711,6 +4947,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/oniguruma-parser": {
|
||||
"version": "0.12.2",
|
||||
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz",
|
||||
@@ -4825,6 +5070,7 @@
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz",
|
||||
"integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -4840,6 +5086,7 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/piccolore": {
|
||||
@@ -5410,6 +5657,26 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass-formatter": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.8.0.tgz",
|
||||
@@ -5616,6 +5883,7 @@
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz",
|
||||
"integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^24.9.2",
|
||||
@@ -5735,6 +6003,7 @@
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.5.1.tgz",
|
||||
"integrity": "sha512-UM4nTLWzQiDBseVxUGxN0wJzIk0UU89Qi7pKXz9qISo+lhviJikTuikBsK/oFRlpvYHvSdz0TVoLyQ+sEpYwxA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5833,12 +6102,14 @@
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
|
||||
"integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
||||
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -5992,6 +6263,7 @@
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unicode-trie": {
|
||||
@@ -6207,6 +6479,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/verkit": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/verkit/-/verkit-0.5.0.tgz",
|
||||
@@ -6697,6 +6978,7 @@
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz",
|
||||
"integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
||||
+14
-11
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"node": ">=22.22.0"
|
||||
"node": ">=24"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
@@ -16,29 +16,31 @@
|
||||
"astro": "astro",
|
||||
"icons": "node scripts/generate-icons.mjs",
|
||||
"check:site": "node scripts/check-site.mjs",
|
||||
"fonts": "node scripts/subset-fonts.mjs"
|
||||
"fonts": "node scripts/subset-fonts.mjs",
|
||||
"start": "node server.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/markdown-satteri": "^0.4.4",
|
||||
"@astrojs/mdx": "^8.1.0",
|
||||
"@astrojs/node": "^11.1.8",
|
||||
"@astrojs/rss": "^4.0.19",
|
||||
"@astrojs/sitemap": "^3.7.4",
|
||||
"@iconify-json/tabler": "^1.2.42",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"astro": "^7.4.0",
|
||||
"astro-icon": "^1.2.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"tailwindcss": "^4.3.3"
|
||||
"compression": "^1.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.10",
|
||||
"@astrojs/markdown-satteri": "^0.4.4",
|
||||
"@astrojs/mdx": "^8.1.0",
|
||||
"@astrojs/rss": "^4.0.19",
|
||||
"@astrojs/sitemap": "^3.7.4",
|
||||
"@fontsource-variable/inter": "^5.3.0",
|
||||
"@fontsource-variable/rubik": "^5.3.0",
|
||||
"@fontsource/inter": "^5.3.0",
|
||||
"@fontsource/rubik": "^5.3.0",
|
||||
"@iconify-json/tabler": "^1.2.42",
|
||||
"@tailwindcss/typography": "^0.5.20",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@types/compression": "^1.8.1",
|
||||
"@types/leaflet": "^1.9.22",
|
||||
"astro-icon": "^1.2.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"pagefind": "^1.5.2",
|
||||
"prettier": "^3.9.10",
|
||||
"prettier-plugin-astro": "^1.1.1",
|
||||
@@ -46,6 +48,7 @@
|
||||
"satori": "^0.60.0",
|
||||
"sharp": "^0.35.5",
|
||||
"subset-font": "^2.9.0",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Production entry: wraps Astro's Node handler with compression, security headers and graceful shutdown.
|
||||
// Run after `npm run build`: node server.mjs (env: HOST, PORT, DATA_DIR)
|
||||
import http from 'node:http';
|
||||
import compression from 'compression';
|
||||
|
||||
process.env.ASTRO_NODE_AUTOSTART = 'disabled';
|
||||
const { handler } = await import('./dist/server/entry.mjs');
|
||||
|
||||
const HOST = process.env.HOST ?? '0.0.0.0';
|
||||
const PORT = Number(process.env.PORT ?? 4321);
|
||||
|
||||
const compress = compression({ threshold: 1024 });
|
||||
|
||||
const securityHeaders = {
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
||||
'X-Frame-Options': 'SAMEORIGIN',
|
||||
'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), interest-cohort=()',
|
||||
};
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
for (const [k, v] of Object.entries(securityHeaders)) res.setHeader(k, v);
|
||||
compress(req, res, () => handler(req, res));
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => console.log(`ta1sph.info listening on http://${HOST}:${PORT}`));
|
||||
|
||||
// Docker/Dokploy send SIGTERM on redeploy: finish in-flight requests, then exit.
|
||||
for (const signal of ['SIGTERM', 'SIGINT']) {
|
||||
process.on(signal, () => {
|
||||
server.close(() => process.exit(0));
|
||||
setTimeout(() => process.exit(0), 10_000).unref();
|
||||
});
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
---
|
||||
import type { HTMLAttributes } from 'astro/types';
|
||||
import { Icon } from 'astro-icon/components';
|
||||
|
||||
type Variant = 'solid' | 'outline' | 'category';
|
||||
|
||||
export interface Props {
|
||||
export interface Props extends Omit<HTMLAttributes<'span'>, 'class'> {
|
||||
variant?: Variant;
|
||||
icon?: string;
|
||||
href?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { variant = 'solid', icon, href, class: className } = Astro.props;
|
||||
const { variant = 'solid', icon, href, class: className, ...rest } = Astro.props;
|
||||
|
||||
const variants: Record<Variant, string> = {
|
||||
// accent-fill (not accent) keeps small text ≥ 4.5:1 in both themes.
|
||||
@@ -23,6 +24,7 @@ const Tag = href ? 'a' : 'span';
|
||||
---
|
||||
|
||||
<Tag
|
||||
{...rest}
|
||||
href={href}
|
||||
class:list={[
|
||||
'inline-flex w-fit items-center gap-1 rounded-sm px-2 py-0.5 text-xs leading-5',
|
||||
|
||||
@@ -8,10 +8,12 @@ export interface Props {
|
||||
updatedDate?: Date;
|
||||
/** Reading time in minutes, shown after the dates. */
|
||||
minutes?: number;
|
||||
/** Post slug: shows a view-count badge filled from /api/views/counts/ after load. */
|
||||
viewsSlug?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { pubDate, updatedDate, minutes, class: className } = Astro.props;
|
||||
const { pubDate, updatedDate, minutes, viewsSlug, class: className } = Astro.props;
|
||||
const { primary, secondary } = dateBadges(pubDate, updatedDate);
|
||||
|
||||
const meta = (b: DateBadge) =>
|
||||
@@ -38,4 +40,36 @@ const s = secondary && meta(secondary);
|
||||
{fmt(blog.card.readingTime, { n: minutes })}
|
||||
</Badge>
|
||||
)}
|
||||
{viewsSlug && (
|
||||
// Space is reserved (invisible) so filling it in causes no layout shift; stays hidden if the API is down.
|
||||
<Badge
|
||||
variant="outline"
|
||||
icon="tabler:eye"
|
||||
class="invisible"
|
||||
data-views-slug={viewsSlug}
|
||||
data-views-template={blog.card.views}
|
||||
title={blog.card.viewsLabel}
|
||||
>
|
||||
<span data-views-text>{fmt(blog.card.views, { n: '–' })}</span>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const badges = document.querySelectorAll<HTMLElement>('[data-views-slug]');
|
||||
if (badges.length) {
|
||||
fetch('/api/views/counts/')
|
||||
.then((r) => (r.ok ? r.json() : Promise.reject()))
|
||||
.then((counts: Record<string, number>) => {
|
||||
const nf = new Intl.NumberFormat('tr-TR');
|
||||
for (const el of badges) {
|
||||
const n = counts[el.dataset.viewsSlug ?? ''] ?? 0;
|
||||
const text = el.querySelector('[data-views-text]');
|
||||
if (text)
|
||||
text.textContent = (el.dataset.viewsTemplate ?? '{n}').replace('{n}', nf.format(n));
|
||||
el.classList.remove('invisible');
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -59,6 +59,7 @@ const row = layout === 'row';
|
||||
pubDate={pubDate}
|
||||
updatedDate={updatedDate}
|
||||
minutes={readingMinutes(post)}
|
||||
viewsSlug={post.id}
|
||||
class="mt-auto pt-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"seo": {
|
||||
"title": "Hakkımda",
|
||||
"description": "Suphi Çakır (TA1SPH): DMR ID, favori talkgroup, kullandığım el telsizleri, sabit/mobil cihazlar ve antenler."
|
||||
},
|
||||
"eyebrow": "Hakkımda",
|
||||
"title": "Suphi Çakır",
|
||||
"lead": "Amatör telsizci · TA1SPH",
|
||||
"identity": {
|
||||
"title": "Künye",
|
||||
"items": [
|
||||
{ "label": "Ad Soyad", "value": "Suphi Çakır", "icon": "tabler:user" },
|
||||
{ "label": "Çağrı İşareti", "value": "TA1SPH", "icon": "tabler:antenna" },
|
||||
{ "label": "DMR ID", "value": "2866151", "icon": "tabler:id" },
|
||||
{ "label": "Favori TG", "value": "28690", "icon": "tabler:users-group" }
|
||||
]
|
||||
},
|
||||
"equipment": [
|
||||
{
|
||||
"title": "El Telsizleri",
|
||||
"icon": "tabler:device-mobile",
|
||||
"items": [
|
||||
{ "name": "Tekser UV 99" },
|
||||
{ "name": "Baofeng K5 Plus" },
|
||||
{ "name": "Quansheng K1 Mini Kong" },
|
||||
{ "name": "AnyTone 878" },
|
||||
{ "name": "Baofeng DM1701", "note": "Hotspot, OpenGD77" },
|
||||
{ "name": "Hytera P50 PoC", "note": "Yalnızca DigiVoice DMR için" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Sabit / Mobil Cihazlar",
|
||||
"icon": "tabler:car",
|
||||
"items": [{ "name": "AnyTone 578" }, { "name": "Retevis RT95" }]
|
||||
},
|
||||
{
|
||||
"title": "Antenler",
|
||||
"icon": "tabler:antenna",
|
||||
"items": [
|
||||
{ "name": "Diamond X30" },
|
||||
{ "name": "Flowerpot", "note": "UHF/VHF" },
|
||||
{ "name": "Moxon", "note": "UHF" },
|
||||
{ "name": "Ground Plane", "note": "UHF" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"seo": {
|
||||
"title": "Blog",
|
||||
"description": "Amatör telsizcilik yazıları: DMR, antenler, röleler, hotspot, cihaz incelemeleri ve kendin yap projeleri."
|
||||
},
|
||||
"list": {
|
||||
"eyebrow": "Blog",
|
||||
"title": "Yazılar",
|
||||
"categoryEyebrow": "Kategori",
|
||||
"tagEyebrow": "Etiket",
|
||||
"tagTitle": "#{tag}",
|
||||
"categoryDescription": "{name} kategorisindeki yazılar.",
|
||||
"tagDescription": "\"{tag}\" etiketli yazılar.",
|
||||
"pageTitle": "{title} — Sayfa {n}",
|
||||
"empty": "Henüz yazı yok."
|
||||
},
|
||||
"card": {
|
||||
"readMore": "Devamını oku",
|
||||
"readingTime": "{n} dk okuma",
|
||||
"views": "{n} görüntülenme",
|
||||
"viewsLabel": "Görüntülenme sayısı"
|
||||
},
|
||||
"dates": {
|
||||
"created": "Oluşturuldu",
|
||||
"updated": "Güncellendi"
|
||||
},
|
||||
"pager": {
|
||||
"label": "Sayfalar",
|
||||
"first": "İlk",
|
||||
"prev": "Önceki",
|
||||
"next": "Sonraki",
|
||||
"last": "Son",
|
||||
"page": "Sayfa {n}"
|
||||
},
|
||||
"sidebar": {
|
||||
"label": "Blog kenar çubuğu",
|
||||
"search": "Ara",
|
||||
"searchLabel": "Yazılarda ara",
|
||||
"searchPlaceholder": "Yazılarda ara…",
|
||||
"searchNoResults": "Sonuç bulunamadı.",
|
||||
"searchResults": "{n} sonuç",
|
||||
"searchUnavailable": "Arama yalnızca derlenmiş sitede çalışır.",
|
||||
"categories": "Kategoriler",
|
||||
"featured": "Öne Çıkan Yazılar",
|
||||
"tags": "Etiket Bulutu"
|
||||
},
|
||||
"post": {
|
||||
"breadcrumbLabel": "Konum",
|
||||
"toc": "İçindekiler",
|
||||
"shareLabel": "Paylaş",
|
||||
"copyLink": "Bağlantıyı Kopyala",
|
||||
"copied": "Bağlantı kopyalandı",
|
||||
"nativeShare": "Paylaş…",
|
||||
"copyCode": "Kopyala",
|
||||
"codeCopied": "Kopyalandı",
|
||||
"category": "Kategori",
|
||||
"tags": "Etiketler",
|
||||
"adjacentLabel": "Diğer yazılar",
|
||||
"prev": "Önceki yazı",
|
||||
"next": "Sonraki yazı",
|
||||
"license": "Bu yazı {license} lisansıyla paylaşılmıştır; kaynak göstererek kopyalayabilir, düzenleyebilir ve aynı lisansla paylaşabilirsiniz.",
|
||||
"licenseName": "CC BY-SA 4.0",
|
||||
"licenseHref": "/lisans/"
|
||||
},
|
||||
"diy": {
|
||||
"title": "Sorumluluk Reddi",
|
||||
"body": "Bu yazıdaki uygulamalar eğitim ve hobi amaçlıdır. Uygulamadan doğabilecek her türlü zarar, cihaz arızası ve mevzuata aykırılık sorumluluğu uygulayan kişiye aittir.",
|
||||
"link": {
|
||||
"label": "Sorumluluk reddi metninin tamamı",
|
||||
"href": "/sorumluluk-reddi/"
|
||||
}
|
||||
},
|
||||
"share": [
|
||||
{
|
||||
"id": "x",
|
||||
"label": "X'te paylaş",
|
||||
"icon": "tabler:brand-x",
|
||||
"url": "https://x.com/intent/post?url={url}&text={title}"
|
||||
},
|
||||
{
|
||||
"id": "facebook",
|
||||
"label": "Facebook'ta paylaş",
|
||||
"icon": "tabler:brand-facebook",
|
||||
"url": "https://www.facebook.com/sharer/sharer.php?u={url}"
|
||||
},
|
||||
{
|
||||
"id": "linkedin",
|
||||
"label": "LinkedIn'de paylaş",
|
||||
"icon": "tabler:brand-linkedin",
|
||||
"url": "https://www.linkedin.com/sharing/share-offsite/?url={url}"
|
||||
},
|
||||
{
|
||||
"id": "whatsapp",
|
||||
"label": "WhatsApp ile gönder",
|
||||
"icon": "tabler:brand-whatsapp",
|
||||
"url": "https://wa.me/?text={title}%20{url}"
|
||||
},
|
||||
{
|
||||
"id": "telegram",
|
||||
"label": "Telegram ile gönder",
|
||||
"icon": "tabler:brand-telegram",
|
||||
"url": "https://t.me/share/url?url={url}&text={title}"
|
||||
},
|
||||
{
|
||||
"id": "bluesky",
|
||||
"label": "Bluesky'da paylaş",
|
||||
"icon": "tabler:brand-bluesky",
|
||||
"url": "https://bsky.app/intent/compose?text={title}%20{url}"
|
||||
},
|
||||
{
|
||||
"id": "email",
|
||||
"label": "E-posta ile gönder",
|
||||
"icon": "tabler:mail",
|
||||
"url": "mailto:?subject={title}&body={url}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"slug": "dmr",
|
||||
"name": "DMR & Dijital Modlar",
|
||||
"description": "DMR, talkgroup'lar, kod plugları ve dijital ses modları.",
|
||||
"icon": "tabler:radio"
|
||||
},
|
||||
{
|
||||
"slug": "antenler",
|
||||
"name": "Antenler & DIY",
|
||||
"description": "Anten yapımı, ölçüm ve kendin yap projeleri.",
|
||||
"icon": "tabler:antenna"
|
||||
},
|
||||
{
|
||||
"slug": "cihazlar",
|
||||
"name": "Cihaz İncelemeleri",
|
||||
"description": "El telsizleri, mobil cihazlar ve aksesuarlar.",
|
||||
"icon": "tabler:device-mobile"
|
||||
},
|
||||
{
|
||||
"slug": "roleler",
|
||||
"name": "Röleler & Şebekeler",
|
||||
"description": "Röle kullanımı, frekanslar ve ton ayarları.",
|
||||
"icon": "tabler:building-broadcast-tower"
|
||||
},
|
||||
{
|
||||
"slug": "hotspot",
|
||||
"name": "Hotspot & Yazılım",
|
||||
"description": "Hotspot kurulumu, firmware ve yazılımlar.",
|
||||
"icon": "tabler:router"
|
||||
},
|
||||
{
|
||||
"slug": "mevzuat",
|
||||
"name": "Lisans & Mevzuat",
|
||||
"description": "Amatör telsizcilik lisansı ve mevzuat notları.",
|
||||
"icon": "tabler:license"
|
||||
},
|
||||
{
|
||||
"slug": "genel",
|
||||
"name": "Genel",
|
||||
"description": "Diğer notlar ve duyurular.",
|
||||
"icon": "tabler:notes"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"seo": {
|
||||
"title": "İletişim",
|
||||
"description": "TA1SPH ile iletişim: QTH İstanbul / Gaziosmanpaşa, locator KN41KB99HW, DMR talkgroup'ları, röleler, e-posta ve QRZ profilleri."
|
||||
},
|
||||
"eyebrow": "İletişim",
|
||||
"title": "İletişim",
|
||||
"icon": "tabler:building-broadcast-tower",
|
||||
"lead": "Telsizde, e-postayla ya da QRZ profillerim üzerinden ulaşabilirsiniz.",
|
||||
"infoTitle": "İstasyon Bilgileri",
|
||||
"map": {
|
||||
"title": "Konum",
|
||||
"lat": 41.08307,
|
||||
"lng": 28.91094,
|
||||
"zoom": 13,
|
||||
"popup": "TA1SPH · KN41KB99HW",
|
||||
"ariaLabel": "TA1SPH istasyon konumunu gösteren harita: İstanbul / Gaziosmanpaşa, locator KN41KB99HW",
|
||||
"note": "Harita, locator karesinin merkezini (41.08307 K, 28.91094 D) yaklaşık olarak gösterir.",
|
||||
"loading": "Harita yükleniyor…"
|
||||
},
|
||||
"items": [
|
||||
{ "label": "QTH", "icon": "tabler:map-pin", "values": ["İstanbul / Gaziosmanpaşa"] },
|
||||
{ "label": "Locator", "icon": "tabler:grid-dots", "values": ["KN41KB99HW"] },
|
||||
{ "label": "DMR TG", "icon": "tabler:radio", "values": ["28690", "28600", "28634"] },
|
||||
{
|
||||
"label": "Röleler",
|
||||
"icon": "tabler:building-broadcast-tower",
|
||||
"values": [
|
||||
"Tekstilkent 439.200 MHz (Tx CTCSS 88.5 Hz)",
|
||||
"TAMAD Esenyurt 439.250 MHz (Tx/Rx CTCSS 123.0 Hz)"
|
||||
]
|
||||
},
|
||||
{ "label": "E-posta", "icon": "tabler:mail", "email": "suphi@radio.org.tr" },
|
||||
{
|
||||
"label": "QRZ.com",
|
||||
"icon": "tabler:world",
|
||||
"href": "https://www.qrz.com/db/TA1SPH",
|
||||
"values": ["qrz.com/db/TA1SPH"]
|
||||
},
|
||||
{
|
||||
"label": "QRZ73",
|
||||
"icon": "tabler:address-book",
|
||||
"href": "https://qrz73.org.tr/callsign.php?call=TA1SPH",
|
||||
"values": ["qrz73.org.tr · TA1SPH"]
|
||||
},
|
||||
{
|
||||
"label": "TA-CallBook",
|
||||
"icon": "tabler:book",
|
||||
"href": "http://www.tacallbook.org/",
|
||||
"values": ["tacallbook.org"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"seo": {
|
||||
"description": "TA1SPH Suphi Çakır'ın amatör telsizcilik notları: DMR, röleler, antenler, cihazlar ve faydalı Türkçe kaynaklar."
|
||||
},
|
||||
"hero": {
|
||||
"numeral": "73",
|
||||
"tags": [
|
||||
{ "icon": "tabler:antenna", "label": "TA1SPH" },
|
||||
{ "icon": "tabler:map-pin", "label": "KN41KB" },
|
||||
{ "icon": "tabler:building-broadcast-tower", "label": "İstanbul" }
|
||||
],
|
||||
"asideTitle": "İstanbul'dan QRV",
|
||||
"asideBody": "Gaziosmanpaşa'dan amatör telsizcilik üzerine notlar, denemeler ve kaynaklar.",
|
||||
"asideLink": { "label": "İletişime Geç", "href": "/iletisim/" },
|
||||
"eyebrow": "TA1SPH",
|
||||
"title": "Amatör Telsizcilik Notları",
|
||||
"lead": "DMR, röleler, antenler ve cihazlar üzerine denemelerimi, öğrendiklerimi ve işe yarayan kaynakları paylaşıyorum.",
|
||||
"ctas": [
|
||||
{
|
||||
"label": "Blog'u Oku",
|
||||
"href": "/blog/",
|
||||
"variant": "primary",
|
||||
"icon": "tabler:arrow-right"
|
||||
},
|
||||
{ "label": "Hakkımda", "href": "/hakkimda/", "variant": "secondary" }
|
||||
]
|
||||
},
|
||||
"sections": [
|
||||
{
|
||||
"id": "ard",
|
||||
"type": "feature",
|
||||
"eyebrow": "Dernek",
|
||||
"title": "Amatör Radyocular Derneği",
|
||||
"body": "Amatör telsizciliği yaygınlaştırmak, eğitim vermek ve teknik projeler geliştirmek amacıyla kurulmuş bir dernek.",
|
||||
"callsign": "YM1KTC",
|
||||
"callsignLabel": "Kulüp çağrı işareti",
|
||||
"icon": "tabler:users-group",
|
||||
"link": { "label": "Derneği Ziyaret Et", "href": "https://radio.org.tr" }
|
||||
},
|
||||
{
|
||||
"id": "dmr",
|
||||
"type": "feature",
|
||||
"eyebrow": "Dijital Ses",
|
||||
"title": "DMR Türkiye",
|
||||
"body": "Türkiye'de DMR (Digital Mobile Radio) ile ilgilenen amatör telsizciler ve dijital haberleşme topluluğu için bir kaynak.",
|
||||
"icon": "tabler:radio",
|
||||
"link": { "label": "DMR Türkiye'ye Git", "href": "https://www.dmrturkiye.com" }
|
||||
},
|
||||
{
|
||||
"id": "qrv73",
|
||||
"type": "feature",
|
||||
"eyebrow": "Platform",
|
||||
"title": "QRV73! Platformu",
|
||||
"body": "Çevrimiçi radyo amatörleri platformu: amatör telsizciler için bilgi kaynakları, dokümanlar ve radyo amatörlerine ücretsiz sunulan yerli yazılımlar.",
|
||||
"icon": "tabler:apps",
|
||||
"link": { "label": "QRV73!'e Git", "href": "https://qrv73.com/" }
|
||||
},
|
||||
{
|
||||
"id": "qrz73",
|
||||
"type": "feature",
|
||||
"eyebrow": "Callbook",
|
||||
"title": "QRZ73! Platformu",
|
||||
"body": "Türkiye amatör telsizci veritabanı: çağrı işareti sorgulama, ekipman ve QSL tercihlerini paylaşabileceğiniz profiller, bant ve mod bilgisi.",
|
||||
"icon": "tabler:address-book",
|
||||
"link": { "label": "QRZ73!'e Git", "href": "https://qrz73.org.tr/" }
|
||||
},
|
||||
{
|
||||
"id": "telegram",
|
||||
"type": "cards",
|
||||
"eyebrow": "Topluluk",
|
||||
"title": "Telegram Grupları",
|
||||
"items": [
|
||||
{
|
||||
"title": "Amatör Telsizci",
|
||||
"href": "https://t.me/amatortelsizci",
|
||||
"icon": "tabler:brand-telegram"
|
||||
},
|
||||
{
|
||||
"title": "Çevrim Zamanı",
|
||||
"href": "https://t.me/cevrim_zamani",
|
||||
"icon": "tabler:brand-telegram"
|
||||
},
|
||||
{
|
||||
"title": "Amatör Radyocular Derneği (YM1KTC)",
|
||||
"href": "https://t.me/ym1ktc",
|
||||
"icon": "tabler:brand-telegram"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "latest",
|
||||
"type": "latestPosts",
|
||||
"eyebrow": "Blog",
|
||||
"title": "Son Yazılar",
|
||||
"count": 3,
|
||||
"allLink": { "label": "Tüm Yazılar", "href": "/blog/" },
|
||||
"empty": "İlk yazılar çok yakında burada olacak."
|
||||
},
|
||||
{
|
||||
"id": "sites",
|
||||
"type": "cards",
|
||||
"eyebrow": "Kaynaklar",
|
||||
"title": "Site Tanıtımları",
|
||||
"items": [
|
||||
{
|
||||
"title": "Türkiye Röleleri",
|
||||
"description": "Türkiye'deki VHF ve UHF, analog ve dijital amatör telsiz röle frekansları, bölge bölge.",
|
||||
"href": "https://ta-role.com",
|
||||
"icon": "tabler:building-broadcast-tower"
|
||||
},
|
||||
{
|
||||
"title": "Türkiye Amatör Telsiz Röle Listesi",
|
||||
"description": "Türkiye'deki amatör telsiz rölelerinin konum, frekans, CTCSS tonu ve bant bilgileri.",
|
||||
"href": "https://amatortelsizcilik.com.tr/roleler",
|
||||
"icon": "tabler:list-details"
|
||||
},
|
||||
{
|
||||
"title": "Türkçe Logbook",
|
||||
"description": "QRZ73! üzerinde QSO kayıtlarınızı yönetebileceğiniz Türkçe logbook.",
|
||||
"href": "https://qrz73.org.tr/",
|
||||
"icon": "tabler:notebook"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"legal": {
|
||||
"eyebrow": "Yasal",
|
||||
"updated": "Son güncelleme",
|
||||
"notice": "Bu metin bilgilendirme amaçlıdır ve hukuki danışmanlık yerine geçmez."
|
||||
},
|
||||
"notFound": {
|
||||
"seoTitle": "Sayfa bulunamadı",
|
||||
"description": "Aradığınız sayfa bulunamadı.",
|
||||
"eyebrow": "Sayfa bulunamadı",
|
||||
"title": "QRT — Bu sayfa yayında değil",
|
||||
"lead": "Aradığınız sayfa taşınmış, silinmiş ya da hiç var olmamış olabilir. Frekansı değiştirip tekrar deneyin.",
|
||||
"links": [
|
||||
{ "label": "Ana Sayfa", "href": "/", "variant": "primary", "icon": "tabler:home" },
|
||||
{ "label": "Blog", "href": "/blog/", "variant": "secondary", "icon": "tabler:article" }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "TA1SPH",
|
||||
"title": "TA1SPH · Suphi Çakır",
|
||||
"description": "TA1SPH Suphi Çakır'ın amatör telsizcilik notları: DMR, antenler, röleler, cihaz incelemeleri ve kendin yap (DIY) projeleri.",
|
||||
"url": "https://ta1sph.info",
|
||||
"lang": "tr",
|
||||
"locale": "tr_TR",
|
||||
"titleSeparator": " · ",
|
||||
"author": {
|
||||
"name": "Suphi Çakır",
|
||||
"callsign": "TA1SPH",
|
||||
"locator": "KN41KB"
|
||||
},
|
||||
"postsPerPage": 6,
|
||||
"nav": [
|
||||
{ "label": "Ana Sayfa", "href": "/" },
|
||||
{ "label": "Blog", "href": "/blog/" },
|
||||
{ "label": "Hakkımda", "href": "/hakkimda/" },
|
||||
{ "label": "İletişim", "href": "/iletisim/" }
|
||||
],
|
||||
"footer": {
|
||||
"about": "İstanbul'dan amatör telsizcilik üzerine notlar, denemeler ve faydalı kaynaklar.",
|
||||
"quickLinksTitle": "Bağlantılar",
|
||||
"legalTitle": "Yasal",
|
||||
"socialTitle": "Sosyal Medya",
|
||||
"rss": { "label": "RSS Akışı", "href": "/rss.xml" },
|
||||
"legal": [
|
||||
{ "label": "Gizlilik Politikası", "href": "/gizlilik/" },
|
||||
{ "label": "Lisans", "href": "/lisans/" },
|
||||
{ "label": "Sorumluluk Reddi", "href": "/sorumluluk-reddi/" }
|
||||
],
|
||||
"license": "İçerik CC BY-SA 4.0 · Kod MIT",
|
||||
"signoff": "73 de TA1SPH"
|
||||
},
|
||||
"ui": {
|
||||
"skipToContent": "İçeriğe geç",
|
||||
"mainNav": "Ana menü",
|
||||
"footerNav": "Alt menü",
|
||||
"menuOpen": "Menüyü aç",
|
||||
"menuClose": "Menüyü kapat",
|
||||
"themeToggle": "Temayı değiştir",
|
||||
"newTab": "(yeni sekmede açılır)",
|
||||
"home": "Ana sayfa"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
[
|
||||
{
|
||||
"id": "instagram",
|
||||
"label": "Instagram",
|
||||
"href": "https://www.instagram.com/suphi.cakir/",
|
||||
"icon": "tabler:brand-instagram"
|
||||
},
|
||||
{
|
||||
"id": "facebook",
|
||||
"label": "Facebook",
|
||||
"href": "https://www.facebook.com/suphi.cakir",
|
||||
"icon": "tabler:brand-facebook"
|
||||
},
|
||||
{
|
||||
"id": "x",
|
||||
"label": "X (Twitter)",
|
||||
"href": "https://x.com/suphicakir",
|
||||
"icon": "tabler:brand-x",
|
||||
"handle": "@suphicakir"
|
||||
},
|
||||
{
|
||||
"id": "bluesky",
|
||||
"label": "Bluesky",
|
||||
"href": "https://bsky.app/profile/ta1sph.bsky.social",
|
||||
"icon": "tabler:brand-bluesky"
|
||||
},
|
||||
{
|
||||
"id": "youtube",
|
||||
"label": "YouTube",
|
||||
"href": "https://www.youtube.com/@suphcakirr",
|
||||
"icon": "tabler:brand-youtube"
|
||||
}
|
||||
]
|
||||
+1
-1
@@ -144,7 +144,7 @@ const blogSchema = z.object({
|
||||
'pageTitle',
|
||||
'empty',
|
||||
]),
|
||||
card: strings(['readMore', 'readingTime']),
|
||||
card: strings(['readMore', 'readingTime', 'views', 'viewsLabel']),
|
||||
dates: strings(['created', 'updated']),
|
||||
pager: strings(['label', 'first', 'prev', 'next', 'last', 'page']),
|
||||
sidebar: strings([
|
||||
|
||||
@@ -64,3 +64,22 @@ export function clientIp(request: Request, fallback: string): string {
|
||||
|
||||
export const isBot = (ua: string | null) =>
|
||||
!ua || /bot|crawl|spider|slurp|preview|facebookexternalhit|headless|lighthouse/i.test(ua);
|
||||
|
||||
/** All counters as { slug: count } (slugs with no views are absent). */
|
||||
export function allViews(): Record<string, number> {
|
||||
const rows = open().prepare('SELECT slug, count FROM views').all() as {
|
||||
slug: string;
|
||||
count: number;
|
||||
}[];
|
||||
return Object.fromEntries(rows.map((r) => [r.slug, r.count]));
|
||||
}
|
||||
|
||||
/** Health probe: the database opens and answers a trivial query. */
|
||||
export function dbHealthy(): boolean {
|
||||
try {
|
||||
open().prepare('SELECT 1').get();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { dbHealthy } from '../../lib/views';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
// GET /api/health — Docker HEALTHCHECK / Dokploy. 503 if the view-counter DB is unavailable.
|
||||
export const GET: APIRoute = () => {
|
||||
const db = dbHealthy();
|
||||
return Response.json(
|
||||
{ status: db ? 'ok' : 'degraded', db },
|
||||
{ status: db ? 200 : 503, headers: { 'Cache-Control': 'no-store' } },
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getPosts } from '../../../lib/posts';
|
||||
import { allViews } from '../../../lib/views';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
// GET /api/views/counts/ — { slug: count } for every published post (0 when never viewed).
|
||||
export const GET: APIRoute = async () => {
|
||||
const views = allViews();
|
||||
const counts = Object.fromEntries((await getPosts()).map((p) => [p.id, views[p.id] ?? 0]));
|
||||
return Response.json(counts, { headers: { 'Cache-Control': 'public, max-age=60' } });
|
||||
};
|
||||
@@ -100,7 +100,12 @@ const jsonLd = [
|
||||
{title}
|
||||
</h1>
|
||||
<p class="text-fg-muted text-lg">{description}</p>
|
||||
<DateBadges pubDate={pubDate} updatedDate={updatedDate} minutes={readingMinutes(post)} />
|
||||
<DateBadges
|
||||
pubDate={pubDate}
|
||||
updatedDate={updatedDate}
|
||||
minutes={readingMinutes(post)}
|
||||
viewsSlug={post.id}
|
||||
/>
|
||||
</header>
|
||||
|
||||
{cover && (
|
||||
|
||||
Reference in New Issue
Block a user