Compare commits
18 Commits
4e44fff8c0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a805610cba | ||
|
|
40929b3604 | ||
|
|
6b73e91ddb | ||
|
|
abc3bd4d41 | ||
|
|
e0095ee10f | ||
|
|
bbb6742849 | ||
|
|
2c30704a11 | ||
|
|
95b308455c | ||
|
|
146e738c09 | ||
|
|
92edbad2c2 | ||
|
|
b065db4faf | ||
|
|
ed513957c3 | ||
|
|
12be46d038 | ||
|
|
0d967ea6c8 | ||
|
|
259ee007db | ||
|
|
e9f440353d | ||
|
|
f91b557efd | ||
|
|
832ee2e796 |
1425
astro-pages-dump.txt
1425
astro-pages-dump.txt
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
minify: "esbuild",
|
minify: "esbuild",
|
||||||
cssMinify: "esbuild",
|
cssMinify: "esbuild",
|
||||||
|
chunkSizeWarningLimit: 500,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
integrations: [
|
integrations: [
|
||||||
|
|||||||
95
astro.py
95
astro.py
@@ -1,95 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def iter_astro_files(root: Path) -> list[Path]:
|
|
||||||
"""Zwraca listę plików .astro w root (rekursywnie), posortowaną stabilnie."""
|
|
||||||
files = [p for p in root.rglob("*.astro") if p.is_file()]
|
|
||||||
# sortowanie po ścieżce względnej dla powtarzalności
|
|
||||||
files.sort(key=lambda p: str(p.as_posix()).lower())
|
|
||||||
return files
|
|
||||||
|
|
||||||
|
|
||||||
def read_text_fallback(p: Path) -> str:
|
|
||||||
"""
|
|
||||||
Czyta plik jako tekst:
|
|
||||||
- najpierw UTF-8
|
|
||||||
- jeśli się nie da, to z BOM/latin-1 jako awaryjne (bez crasha)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return p.read_text(encoding="utf-8")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
# Spróbuj UTF-8 z BOM
|
|
||||||
try:
|
|
||||||
return p.read_text(encoding="utf-8-sig")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
# Ostatecznie: latin-1 (nie idealne, ale nie przerwie działania)
|
|
||||||
return p.read_text(encoding="latin-1")
|
|
||||||
|
|
||||||
|
|
||||||
def build_output(pages_dir: Path, files: list[Path]) -> str:
|
|
||||||
rel_root = pages_dir.parent.parent # zwykle ./src -> parent parent? NIEPEWNE, więc liczymy inaczej
|
|
||||||
# lepiej: relatywnie do katalogu projektu (cwd)
|
|
||||||
cwd = Path.cwd()
|
|
||||||
|
|
||||||
lines: list[str] = []
|
|
||||||
lines.append(f"ASTRO DUMP (root: {pages_dir.resolve()})")
|
|
||||||
lines.append(f"Found files: {len(files)}")
|
|
||||||
lines.append("=" * 80)
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
for f in files:
|
|
||||||
rel = f.relative_to(cwd) if f.is_relative_to(cwd) else f
|
|
||||||
content = read_text_fallback(f)
|
|
||||||
|
|
||||||
lines.append(f"FILE: {rel.as_posix()}")
|
|
||||||
lines.append("-" * 80)
|
|
||||||
lines.append(content.rstrip("\n"))
|
|
||||||
lines.append("")
|
|
||||||
lines.append("=" * 80)
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Zrzuca wszystkie pliki .astro z ./src/pages do jednego pliku txt (ścieżka + zawartość)."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pages",
|
|
||||||
default="src/pages",
|
|
||||||
help="Ścieżka do katalogu pages (domyślnie: src/pages)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--out",
|
|
||||||
default="astro-pages-dump.txt",
|
|
||||||
help="Plik wyjściowy (domyślnie: astro-pages-dump.txt)",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
pages_dir = Path(args.pages).resolve()
|
|
||||||
out_file = Path(args.out).resolve()
|
|
||||||
|
|
||||||
if not pages_dir.exists() or not pages_dir.is_dir():
|
|
||||||
print(f"[ERR] Nie znaleziono katalogu: {pages_dir}", file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
files = iter_astro_files(pages_dir)
|
|
||||||
|
|
||||||
dump = build_output(pages_dir, files)
|
|
||||||
|
|
||||||
out_file.write_text(dump, encoding="utf-8")
|
|
||||||
print(f"[OK] Zapisano: {out_file} (files: {len(files)})")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, List, Set, Tuple
|
|
||||||
|
|
||||||
# --- Regexy do zbierania klas z CSS ---
|
|
||||||
# Łapie ".class-name" w selektorach (pomija np. ".5" i rzeczy z escapami – prosto, ale działa w praktyce)
|
|
||||||
CSS_CLASS_RE = re.compile(r'(?<![\\\w-])\.([a-zA-Z_-][\w-]*)')
|
|
||||||
|
|
||||||
# --- Regexy do zbierania stringów klas z Astro/JSX/TSX ---
|
|
||||||
# 1) class="..."
|
|
||||||
CLASS_ATTR_RE = re.compile(r'\bclass\s*=\s*("([^"]*)"|\'([^\']*)\')', re.IGNORECASE)
|
|
||||||
# 2) className="..."
|
|
||||||
CLASSNAME_ATTR_RE = re.compile(r'\bclassName\s*=\s*("([^"]*)"|\'([^\']*)\')', re.IGNORECASE)
|
|
||||||
|
|
||||||
# Astro: class:list={{ a: cond, "b c": cond2 }} / class:list={[...]} – tu łapiemy stringi w środku
|
|
||||||
STRING_LIT_RE = re.compile(r'("([^"]+)"|\'([^\']+)\')')
|
|
||||||
|
|
||||||
# Dla wyszukiwania tokenów klas (żeby "f-card" nie matchowało jako fragment "f-card-x")
|
|
||||||
def token_pattern(cls: str) -> re.Pattern:
|
|
||||||
return re.compile(r'(?<![\w-])' + re.escape(cls) + r'(?![\w-])')
|
|
||||||
|
|
||||||
def read_text(path: Path) -> str:
|
|
||||||
try:
|
|
||||||
return path.read_text(encoding="utf-8", errors="ignore")
|
|
||||||
except Exception:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def collect_css_classes(css_dir: Path) -> Tuple[Set[str], Dict[str, Set[Path]]]:
|
|
||||||
classes: Set[str] = set()
|
|
||||||
where: Dict[str, Set[Path]] = {}
|
|
||||||
|
|
||||||
for p in css_dir.rglob("*.css"):
|
|
||||||
txt = read_text(p)
|
|
||||||
for m in CSS_CLASS_RE.finditer(txt):
|
|
||||||
c = m.group(1)
|
|
||||||
classes.add(c)
|
|
||||||
where.setdefault(c, set()).add(p)
|
|
||||||
|
|
||||||
return classes, where
|
|
||||||
|
|
||||||
def collect_used_classes(code_dir: Path, candidates: Set[str]) -> Tuple[Set[str], Dict[str, Set[Path]]]:
|
|
||||||
used: Set[str] = set()
|
|
||||||
used_where: Dict[str, Set[Path]] = {c: set() for c in candidates}
|
|
||||||
|
|
||||||
exts = {".astro", ".jsx", ".tsx", ".js", ".ts"}
|
|
||||||
# prekompilacja patternów dla szybkości
|
|
||||||
patterns = {c: token_pattern(c) for c in candidates}
|
|
||||||
|
|
||||||
for p in code_dir.rglob("*"):
|
|
||||||
if not p.is_file():
|
|
||||||
continue
|
|
||||||
if p.suffix.lower() not in exts:
|
|
||||||
continue
|
|
||||||
|
|
||||||
txt = read_text(p)
|
|
||||||
if not txt:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# szybki filtr: jeśli żaden kandydat nie ma nawet prefiksu "f-" / "jmb-" itd,
|
|
||||||
# to i tak skan tokenowy jest ok, ale tu robimy prosty scan wszystkich.
|
|
||||||
for c, pat in patterns.items():
|
|
||||||
if pat.search(txt):
|
|
||||||
used.add(c)
|
|
||||||
used_where[c].add(p)
|
|
||||||
|
|
||||||
# usuń puste wpisy
|
|
||||||
used_where = {k: v for k, v in used_where.items() if v}
|
|
||||||
return used, used_where
|
|
||||||
|
|
||||||
def main():
|
|
||||||
ap = argparse.ArgumentParser(description="Znajdź potencjalnie nieużywane klasy CSS w projekcie Astro/JSX.")
|
|
||||||
ap.add_argument("--css", required=True, help="Katalog z plikami CSS (np. src/styles)")
|
|
||||||
ap.add_argument("--code", required=True, help="Katalog z kodem (np. src)")
|
|
||||||
ap.add_argument("--min-len", type=int, default=3, help="Minimalna długość nazwy klasy (domyślnie 3)")
|
|
||||||
ap.add_argument("--prefix", action="append", default=[], help="Filtruj klasy po prefiksie (np. --prefix f- --prefix jmb-)")
|
|
||||||
ap.add_argument("--show-where", action="store_true", help="Pokaż gdzie zdefiniowano klasę w CSS")
|
|
||||||
args = ap.parse_args()
|
|
||||||
|
|
||||||
css_dir = Path(args.css).resolve()
|
|
||||||
code_dir = Path(args.code).resolve()
|
|
||||||
|
|
||||||
if not css_dir.exists():
|
|
||||||
raise SystemExit(f"Brak katalogu CSS: {css_dir}")
|
|
||||||
if not code_dir.exists():
|
|
||||||
raise SystemExit(f"Brak katalogu code: {code_dir}")
|
|
||||||
|
|
||||||
all_classes, defined_where = collect_css_classes(css_dir)
|
|
||||||
|
|
||||||
# filtr długości + prefiksów
|
|
||||||
classes = {c for c in all_classes if len(c) >= args.min_len}
|
|
||||||
if args.prefix:
|
|
||||||
classes = {c for c in classes if any(c.startswith(px) for px in args.prefix)}
|
|
||||||
|
|
||||||
used, used_where = collect_used_classes(code_dir, classes)
|
|
||||||
unused = sorted(classes - used)
|
|
||||||
|
|
||||||
print(f"CSS katalog: {css_dir}")
|
|
||||||
print(f"CODE katalog: {code_dir}")
|
|
||||||
print(f"Klasy w CSS: {len(classes)} (po filtrach)")
|
|
||||||
print(f"Użyte w kodzie:{len(used)}")
|
|
||||||
print(f"NIEUŻYTE: {len(unused)}")
|
|
||||||
print("-" * 60)
|
|
||||||
|
|
||||||
for c in unused:
|
|
||||||
print(c)
|
|
||||||
if args.show_where:
|
|
||||||
files = sorted(defined_where.get(c, []))
|
|
||||||
for f in files:
|
|
||||||
rel = f.relative_to(css_dir.parent) if css_dir.parent in f.parents else f
|
|
||||||
print(f" defined in: {rel}")
|
|
||||||
print("-" * 60)
|
|
||||||
|
|
||||||
# opcjonalnie: pokaż top kilka użyć
|
|
||||||
# (jak chcesz, dopiszę flagę na raport "gdzie użyte")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fuz20",
|
"name": "fuz-site",
|
||||||
|
"type": "module",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
BIN
public/files/cennik_uslug_dodatkowych.pdf
Normal file
BIN
public/files/cennik_uslug_dodatkowych.pdf
Normal file
Binary file not shown.
BIN
public/files/cennik_uslug_tv.pdf
Normal file
BIN
public/files/cennik_uslug_tv.pdf
Normal file
Binary file not shown.
BIN
public/files/formularz_odstapienia_net.pdf
Normal file
BIN
public/files/formularz_odstapienia_net.pdf
Normal file
Binary file not shown.
BIN
public/files/informacje_przedumowne_net.pdf
Normal file
BIN
public/files/informacje_przedumowne_net.pdf
Normal file
Binary file not shown.
BIN
public/files/informacje_przedumowne_tv.pdf
Normal file
BIN
public/files/informacje_przedumowne_tv.pdf
Normal file
Binary file not shown.
BIN
public/files/informacje_urządzenia_net.pdf
Normal file
BIN
public/files/informacje_urządzenia_net.pdf
Normal file
Binary file not shown.
BIN
public/files/oświadczenia_net.pdf
Normal file
BIN
public/files/oświadczenia_net.pdf
Normal file
Binary file not shown.
BIN
public/files/podsumowanie_warunków_umowy_net.pdf
Normal file
BIN
public/files/podsumowanie_warunków_umowy_net.pdf
Normal file
Binary file not shown.
BIN
public/files/podsumowanie_warunków_umowy_tv.pdf
Normal file
BIN
public/files/podsumowanie_warunków_umowy_tv.pdf
Normal file
Binary file not shown.
BIN
public/files/umowa_internet.pdf
Normal file
BIN
public/files/umowa_internet.pdf
Normal file
Binary file not shown.
BIN
public/files/umowa_tv.pdf
Normal file
BIN
public/files/umowa_tv.pdf
Normal file
Binary file not shown.
15
public/robots.txt
Normal file
15
public/robots.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# robots.txt dla FUZ Adam Rojek
|
||||||
|
# https://www.fuz.pl/robots.txt
|
||||||
|
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Sitemap
|
||||||
|
Sitemap: https://www.fuz.dariuszm.eu/sitemap.xml
|
||||||
|
|
||||||
|
# Crawl-delay (opcjonalnie)
|
||||||
|
# Crawl-delay: 1
|
||||||
|
|
||||||
|
# Zablokuj crawlowanie zbędnych ścieżek (jeśli są)
|
||||||
|
Disallow: /pages/api/
|
||||||
|
Disallow: /_astro/
|
||||||
@@ -12,31 +12,3 @@ const sorted = cities.sort((a: string, b: any) => a.localeCompare(b, "pl"));
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.fuz-cities-box {
|
|
||||||
|
|
||||||
background: var(--f-background);
|
|
||||||
color: var(--f-text);
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.fuz-cities-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fuz-cities-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
|
||||||
gap: 6px 14px;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fuz-city-item {
|
|
||||||
color: var(--f-text);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -102,21 +102,21 @@ const domId = `fuz-map-${Math.random().toString(36).slice(2)}`;
|
|||||||
position: { lat, lng: lon }
|
position: { lat, lng: lon }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (title || desc) {
|
// if (title || desc) {
|
||||||
const { InfoWindow } = await google.maps.importLibrary("maps");
|
// const { InfoWindow } = await google.maps.importLibrary("maps");
|
||||||
const info = new InfoWindow({
|
// const info = new InfoWindow({
|
||||||
content: `
|
// content: `
|
||||||
<div class="f-info-window">
|
// <div class="f-info-window">
|
||||||
<div class="f-info-header">
|
// <div class="f-info-header">
|
||||||
<div class="f-info-city">${title}</div>
|
// <div class="f-info-city">${title}</div>
|
||||||
<div class="f-info-street">${desc}</div>
|
// <div class="f-info-street">${desc}</div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
`
|
// `
|
||||||
});
|
// });
|
||||||
|
|
||||||
info.open({ map, anchor: marker });
|
// info.open({ map, anchor: marker });
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error initializing map:", error);
|
console.error("Error initializing map:", error);
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ const sectionImages = import.meta.glob<{ default: ImageMetadata }>(
|
|||||||
const sectionImage = section.image ? findSectionImage(sectionImages, section.image) : null;
|
const sectionImage = section.image ? findSectionImage(sectionImages, section.image) : null;
|
||||||
const isAboveFold = index === 0;
|
const isAboveFold = index === 0;
|
||||||
|
|
||||||
// ✅ Konfiguracja wyświetlania
|
// Konfiguracja wyświetlania
|
||||||
const showTitle = section.showTitle !== false; // domyślnie true
|
const showTitle = section.showTitle !== false; // domyślnie true
|
||||||
|
|
||||||
// ✅ Formatowanie listy miejscowości jako string
|
// Formatowanie listy miejscowości jako string
|
||||||
const citiesText = cities.join(", ");
|
const citiesText = cities.join(", ");
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -66,33 +66,6 @@ const citiesText = cities.join(", ");
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<!-- {
|
|
||||||
section.button && (
|
|
||||||
<div class="f-section-nav mt-6">
|
|
||||||
|
|
||||||
<a href={section.button.url}
|
|
||||||
class="btn btn-primary"
|
|
||||||
title={section.button.title}
|
|
||||||
>
|
|
||||||
{section.button.text}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
} -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- <style>
|
|
||||||
.f-cities-paragraph {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.8;
|
|
||||||
color: var(--f-text, #212529);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .f-cities-paragraph {
|
|
||||||
color: var(--f-text-dark, #f7fafc);
|
|
||||||
}
|
|
||||||
</style> -->
|
|
||||||
@@ -1,21 +1,68 @@
|
|||||||
tytul: Dokumenty
|
tytul: Dokumenty
|
||||||
opis: Strona zawiera dokumnety do pobrania lub przeczytania
|
opis: |
|
||||||
|
Poniżej dostępne są dokumenty regulujące zasady świadczenia usług, w szczególności regulaminy, cenniki oraz obowiązujące umowy.
|
||||||
|
|
||||||
grupy:
|
grupy:
|
||||||
pobierz:
|
# Grupa 1: Polityki i regulaminy
|
||||||
tytul: Pobierz
|
polityki:
|
||||||
pliki:
|
tytul: Polityki i regulaminy
|
||||||
- nazwa: Lista kanałów EVIO TV
|
|
||||||
file: /public/files/EVIO TV.pdf
|
|
||||||
- nazwa: Lista kanałów JAMBOX TV
|
|
||||||
file: /public/files/EVIO TV.pdf
|
|
||||||
- nazwa: Lista kanałów coś
|
|
||||||
file: /public/files/EVIO TV.pdf
|
|
||||||
|
|
||||||
otworz:
|
|
||||||
tytul: Przeczytaj
|
|
||||||
pliki:
|
pliki:
|
||||||
- nazwa: Polityka prywatności
|
- nazwa: Polityka prywatności
|
||||||
slug: polityka-prywatnosci
|
slug: polityka-prywatnosci
|
||||||
- nazwa: Promocja przykład do wyswietlenia
|
|
||||||
slug: promocja
|
# - nazwa: Regulamin świadczenia usług
|
||||||
|
# slug: regulamin-uslug
|
||||||
|
|
||||||
|
# - nazwa: Informacja o przetwarzaniu danych osobowych
|
||||||
|
# slug: rodo
|
||||||
|
|
||||||
|
# Grupa 2: Internet
|
||||||
|
internet:
|
||||||
|
tytul: Internet
|
||||||
|
pliki:
|
||||||
|
- nazwa: Umowa Internet
|
||||||
|
file: /files/umowa_internet.pdf
|
||||||
|
|
||||||
|
- nazwa: Informacje przedumowne Internet
|
||||||
|
file: /files/informacje_przedumowne_net.pdf
|
||||||
|
|
||||||
|
- nazwa: Podsumowanie warunków umowy Internet
|
||||||
|
file: /files/podsumowanie_warunków_umowy_net.pdf
|
||||||
|
|
||||||
|
- nazwa: Oświadczenia Internet
|
||||||
|
file: /files/oświadczenia_net.pdf
|
||||||
|
|
||||||
|
- nazwa: Informacje urządzenia Internet
|
||||||
|
file: /files/informacje_urządzenia_net.pdf
|
||||||
|
|
||||||
|
- nazwa: Formularz odstąpienia Internet
|
||||||
|
file: /files/formularz_odstapienia_net.pdf
|
||||||
|
|
||||||
|
# Grupa 3: Telewizja
|
||||||
|
telewizja:
|
||||||
|
tytul: Internet i Telewizja
|
||||||
|
pliki:
|
||||||
|
- nazwa: Umowa TV
|
||||||
|
file: /files/umowa_tv.pdf
|
||||||
|
|
||||||
|
- nazwa: Podsumowanie warunków umowy TV
|
||||||
|
file: /files/podsumowanie_warunków_umowy_tv.pdf
|
||||||
|
|
||||||
|
- nazwa: Informacje przedumowne TV
|
||||||
|
file: /files/informacje_przedumowne_tv.pdf
|
||||||
|
|
||||||
|
- nazwa: Lista kanałów EVIO TV
|
||||||
|
file: /files/EVIO TV.pdf
|
||||||
|
|
||||||
|
- nazwa: Lista kanałów JAMBOX TV
|
||||||
|
file: /files/JAMBOX TV.pdf
|
||||||
|
|
||||||
|
# Grupa 4: Cenniki
|
||||||
|
cenniki:
|
||||||
|
tytul: Cenniki
|
||||||
|
pliki:
|
||||||
|
- nazwa: Cennik usług TV
|
||||||
|
file: /files/cennik_uslug_tv.pdf
|
||||||
|
|
||||||
|
- nazwa: Cennik usług dodatkowych
|
||||||
|
file: /files/cennik_uslug_dodatkowych.pdf
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# tytuł dokumentu jednoczesnie tytułem strony <title></title>
|
||||||
title: "Polityka Prywatności"
|
title: "Polityka Prywatności"
|
||||||
|
# opis wstawiany w <meta name="description" content=""
|
||||||
|
description: Polityka prywatności, opisuje zasady ochrony Twoich danych osobowych.
|
||||||
visible: true
|
visible: true
|
||||||
intro: Polityka prywatności, opisuje zasady ochrony Twoich danych osobowych.
|
|
||||||
content: |
|
content: |
|
||||||
## §1. Informacje podstawowe.
|
## §1. Informacje podstawowe.
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
title: "Promocja świąteczna"
|
|
||||||
visible: true
|
|
||||||
intro: Przykładowo gdybysmy dodali promocję do dokumentów
|
|
||||||
content: |
|
|
||||||
Jeśli kupujesz w sklepach internetowych, prawdopodobnie co pewien czas natykasz się na opisy, które nie zachęcają do zakupów.
|
|
||||||
Do najczęściej powtarzanych błędów opisów produktów należą:
|
|
||||||
|
|
||||||
- brak konkretów – klient chce wiedzieć, z czego produkt jest wykonany, jakie ma wymiary czy funkcje, a nie tylko, że jest „wysokiej jakości”;
|
|
||||||
- zbyt techniczny język – warto dostosować ton komunikacji do odbiorcy, unikając skomplikowanych terminów (i w drugą stronę – jeśli sprzedajesz towar skierowany do profesjonalistów, nie trzeba w opisie ze szczegółami wyjaśniać, jak działa czy do czego służy);
|
|
||||||
- brak narracji – storytelling w opisach produktów pomaga zbudować emocjonalne zaangażowanie klienta;
|
|
||||||
|
|
||||||
ignorowanie pytań klientów – warto analizować najczęstsze pytania i uwzględniać odpowiedzi w opisach; jeśli np. często dostajesz zapytania dotyczące tego, czy produkt jest wodoodporny, lepiej napisać o tym od razu w opisie;
|
|
||||||
|
|
||||||
zbyt długie i skomplikowane opisy – należy dbać o przejrzystość treści, używać krótkich akapitów i list wypunktowanych; klient poszukuje konkretów, a nie zawiłych opowieści, które trudno się czyta.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
page:
|
page:
|
||||||
title: "Dokumenty - FUZ Adam Rojek | Regulaminy i Umowy"
|
title: "Dokumenty - FUZ Adam Rojek | Regulaminy i Umowy"
|
||||||
description: "Dokumenty FUZ: regulamin świadczenia usług, wzór umowy, cennik, polityka prywatności, warunki techniczne. Wszystkie dokumenty do pobrania w formacie PDF."
|
description: "Dokumenty i regulaminy, cenniki oraz umowy związane z usługami oferowanymi przez naszą firmę."
|
||||||
image: "/og/dokumenty-og.png"
|
image: "/og/dokumenty-og.png"
|
||||||
url: "/dokumenty"
|
url: "/dokumenty"
|
||||||
keywords:
|
keywords:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
page:
|
page:
|
||||||
title: "FUZ Adam Rojek - Internet Światłowodowy Wyszków | Szybki i Stabilny"
|
title: "FUZ Adam Rojek - Internet Światłowodowy Wyszków"
|
||||||
description: "Internet światłowodowy w Wyszkowie i okolicach. Lokalny operator z doświadczeniem - stabilne łącze, profesjonalny serwis, konkurencyjne ceny. Sprawdź dostępność!"
|
description: "Internet światłowodowy w Wyszkowie i okolicach. Lokalny operator z doświadczeniem - stabilne łącze, profesjonalny serwis, konkurencyjne ceny."
|
||||||
image: "/og/home-og.png"
|
image: "/og/home-og.png"
|
||||||
url: "/"
|
url: "/"
|
||||||
keywords:
|
keywords:
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ opis: |
|
|||||||
Wybierz rodzaj budynku i czas trwania umowy
|
Wybierz rodzaj budynku i czas trwania umowy
|
||||||
uwaga: |
|
uwaga: |
|
||||||
Powyższe „ceny brutto z Rabatami 15zł”
|
Powyższe „ceny brutto z Rabatami 15zł”
|
||||||
uwzględniają rabat -15 zł (z czego -5 zł - Rabat za wyrażenie zgody na otrzymywanie Rachunków/faktur VAT za świadczone
|
uwzględniają rabat -15 zł (z czego -5 zł - Rabat za wyrażenie zgody na otrzymywanie Rachunków/faktur VAT za świadczone przez Dostawcę Usług usługi telekomunikacyjne drogą elektroniczną na wskazany w umowie adres e-mail oraz za pośrednictwem EBOK;
|
||||||
przez Dostawcę Usług usługi telekomunikacyjne drogą elektroniczną na wskazany w umowie adres mail oraz za pośrednictwem EBOK; -10 zł - Rabat pod warunkiem
|
-10 zł - Rabat pod warunkiem złożenia wniosku o dostarczanie przez Dostawcę Usług treści każdej proponowanej zmiany warunków Umowy, w tym określonych w Umowie, Informacjach Przedumownych oraz danych Dostawcy Usług (chyba że przepisy powszechnie
|
||||||
złożenia wniosku o dostarczanie przez Dostawcę Usług treści każdej proponowanej zmiany warunków Umowy, w tym określonych w Umowie, Informacjach Przedumownych oraz danych Dostawcy Usług (chyba że przepisy powszechnie obowiązującego prawa przewidują wyłącznie zawiadomienia poprzez publiczne ogłoszenie), jak
|
obowiązującego prawa przewidują wyłącznie zawiadomienia poprzez publiczne ogłoszenie), jak również kontakt w ramach procedur reklamacyjnych, w tym w szczególności przesłania odpowiedzi na reklamację, na podany w Umowie adres poczty elektronicznej).
|
||||||
również kontaktowanie się ze mną w ramach procedur reklamacyjnych, w tym w szczególności przesłania odpowiedzi na reklamację, na podany w Umowie adres poczty
|
|
||||||
elektronicznej).
|
|
||||||
cena_opis: "zł/mies."
|
cena_opis: "zł/mies."
|
||||||
|
|
||||||
cards:
|
cards:
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
title: Możliwości telewizji JAMBOX
|
||||||
|
description: Poznaj funkcje i udogodnienia dostępne na dekoderach telewizji JAMBOX.
|
||||||
sections:
|
sections:
|
||||||
- title: CatchUp - Archiwum TV
|
- title: CatchUp - Archiwum TV
|
||||||
image: https://www.jambox.pl/sites/default/files/jambox-kyanit-catchup1.png
|
image: https://www.jambox.pl/sites/default/files/jambox-kyanit-catchup1.png
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from "preact/hooks";
|
import { useMemo } from "preact/hooks";
|
||||||
import { marked } from "marked";
|
import { marked } from "marked";
|
||||||
import { useLocalSearch } from "../../hooks/useLocalSearch.js";
|
import { useLocalSearch } from "../../hooks/useLocalSearch.js";
|
||||||
import { highlightText, highlightHtml } from "../../lib/highlightUtils.js";
|
import { highlightText, highlightHtml } from "../../lib/highlightUtils.jsx";
|
||||||
import "../../styles/jambox-search.css";
|
import "../../styles/jambox-search.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +46,7 @@ export default function JamboxMozliwosciSearch({ items = [] }) {
|
|||||||
<div className="f-chsearch__top">
|
<div className="f-chsearch__top">
|
||||||
<div className="f-chsearch__inputwrap">
|
<div className="f-chsearch__inputwrap">
|
||||||
<input
|
<input
|
||||||
|
name="search"
|
||||||
className="f-chsearch__input"
|
className="f-chsearch__input"
|
||||||
type="search"
|
type="search"
|
||||||
value={search.query}
|
value={search.query}
|
||||||
@@ -85,6 +86,8 @@ export default function JamboxMozliwosciSearch({ items = [] }) {
|
|||||||
className={`f-section-image ${reverse ? "md:order-1" : "md:order-2"}`}
|
className={`f-section-image ${reverse ? "md:order-1" : "md:order-2"}`}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
|
width="800"
|
||||||
|
height="600"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const jsonExtra = meta.extraSchema ? JSON.stringify(meta.extraSchema) : null;
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="canonical" href={meta.canonical} />
|
<link rel="canonical" href={meta.canonical} />
|
||||||
|
<link rel="sitemap" type="application/xml" title="Sitemap" href="/sitemap.xml" />
|
||||||
|
|
||||||
<!-- OpenGraph -->
|
<!-- OpenGraph -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|||||||
@@ -11,13 +11,44 @@ import Cookie from "../islands/Cookie.jsx";
|
|||||||
|
|
||||||
import rawCookie from "../content/site/cookie.yaml?raw";
|
import rawCookie from "../content/site/cookie.yaml?raw";
|
||||||
const cookieCfg = yaml.parse(rawCookie);
|
const cookieCfg = yaml.parse(rawCookie);
|
||||||
const { seo } = Astro.props;
|
|
||||||
|
// ✅ Pobierz wszystkie możliwe props
|
||||||
|
const {
|
||||||
|
seo, // Pełny obiekt SEO (stary sposób)
|
||||||
|
title, // Indywidualny title (nowy sposób)
|
||||||
|
description, // Indywidualny description (nowy sposób)
|
||||||
|
image, // Opcjonalny image
|
||||||
|
keywords, // Opcjonalne keywords
|
||||||
|
url, // Opcjonalny url
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
// ✅ PRIORYTET: title/description → seo → undefined
|
||||||
|
let finalSeo;
|
||||||
|
|
||||||
|
// Jeśli mamy indywidualne pola (title lub description) - użyj ich
|
||||||
|
if (title || description) {
|
||||||
|
finalSeo = {
|
||||||
|
page: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
keywords,
|
||||||
|
url,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Jeśli nie ma indywidualnych, ale jest seo object - użyj go
|
||||||
|
else if (seo) {
|
||||||
|
finalSeo = seo;
|
||||||
|
}
|
||||||
|
// Jeśli nic nie ma - undefined (użyje global defaults z BaseHead)
|
||||||
|
else {
|
||||||
|
finalSeo = undefined;
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="pl" class="scroll-smooth">
|
<html lang="pl" class="scroll-smooth">
|
||||||
<head>
|
<BaseHead seo={finalSeo} />
|
||||||
<BaseHead seo={seo} />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="min-h-screen flex flex-col">
|
<body class="min-h-screen flex flex-col">
|
||||||
<Header />
|
<Header />
|
||||||
|
|||||||
@@ -396,3 +396,90 @@ export function getRequiredEnv(key: string): string {
|
|||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== SITEMAP HELPERS ====================
|
||||||
|
|
||||||
|
export type SitemapUrl = {
|
||||||
|
loc: string;
|
||||||
|
lastmod?: string;
|
||||||
|
changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
||||||
|
priority?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generuje sitemap XML z listy URL-i
|
||||||
|
* @param urls - Tablica URL-i do sitemap
|
||||||
|
* @returns String z XML sitemap
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const urls = [
|
||||||
|
* { loc: 'https://example.com/', priority: 1.0, changefreq: 'daily' },
|
||||||
|
* { loc: 'https://example.com/about', priority: 0.8, changefreq: 'monthly' }
|
||||||
|
* ];
|
||||||
|
* const xml = generateSitemapXml(urls);
|
||||||
|
*/
|
||||||
|
export function generateSitemapXml(urls: SitemapUrl[]): string {
|
||||||
|
const urlEntries = urls.map(url => {
|
||||||
|
const parts = [
|
||||||
|
' <url>',
|
||||||
|
` <loc>${url.loc}</loc>`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (url.lastmod) {
|
||||||
|
parts.push(` <lastmod>${url.lastmod}</lastmod>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.changefreq) {
|
||||||
|
parts.push(` <changefreq>${url.changefreq}</changefreq>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.priority !== undefined) {
|
||||||
|
parts.push(` <priority>${url.priority.toFixed(1)}</priority>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push(' </url>');
|
||||||
|
return parts.join('\n');
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
${urlEntries}
|
||||||
|
</urlset>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatyczne określenie changefreq na podstawie ścieżki URL
|
||||||
|
* @param path - Ścieżka URL (np. "/internet-swiatlowodowy")
|
||||||
|
* @returns Częstotliwość zmian
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* inferChangeFreq('/') // => 'daily'
|
||||||
|
* inferChangeFreq('/dokumenty/regulamin') // => 'yearly'
|
||||||
|
*/
|
||||||
|
export function inferChangeFreq(path: string): SitemapUrl['changefreq'] {
|
||||||
|
if (path === '/') return 'daily';
|
||||||
|
if (path.includes('/internet-') || path.includes('/telefon')) return 'weekly';
|
||||||
|
if (path.includes('/mapa-zasiegu') || path.includes('/kontakt')) return 'monthly';
|
||||||
|
if (path.includes('/dokumenty')) return 'yearly';
|
||||||
|
if (path.includes('/premium')) return 'monthly';
|
||||||
|
return 'weekly';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatyczne określenie priority na podstawie ścieżki URL
|
||||||
|
* @param path - Ścieżka URL
|
||||||
|
* @returns Priorytet (0.0 - 1.0)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* inferPriority('/') // => 1.0
|
||||||
|
* inferPriority('/dokumenty/polityka-prywatnosci') // => 0.5
|
||||||
|
*/
|
||||||
|
export function inferPriority(path: string): number {
|
||||||
|
if (path === '/') return 1.0;
|
||||||
|
if (path.includes('/internet-') || path.includes('/telefon')) return 0.9;
|
||||||
|
if (path.includes('/mapa-zasiegu') || path.includes('/kontakt')) return 0.8;
|
||||||
|
if (path.includes('/premium')) return 0.7;
|
||||||
|
if (path.includes('/telewizja-mozliwosci')) return 0.7;
|
||||||
|
if (path.includes('/dokumenty')) return 0.5;
|
||||||
|
return 0.7;
|
||||||
|
}
|
||||||
@@ -4,14 +4,15 @@ import yaml from "js-yaml";
|
|||||||
|
|
||||||
export type DocYaml = {
|
export type DocYaml = {
|
||||||
title: string;
|
title: string;
|
||||||
|
description: string;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
intro?: string;
|
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DocEntry = DocYaml & {
|
export type DocEntry = DocYaml & {
|
||||||
slug: string;
|
slug: string;
|
||||||
file: string;
|
file: string;
|
||||||
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DOCS_DIR = path.join(process.cwd(), "src", "content", "document");
|
const DOCS_DIR = path.join(process.cwd(), "src", "content", "document");
|
||||||
@@ -34,13 +35,14 @@ export function listDocuments(): DocEntry[] {
|
|||||||
|
|
||||||
if (!data.title || typeof data.title !== "string") continue;
|
if (!data.title || typeof data.title !== "string") continue;
|
||||||
if (!data.content || typeof data.content !== "string") continue;
|
if (!data.content || typeof data.content !== "string") continue;
|
||||||
|
if (!data.description || typeof data.description !== "string") continue;
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
slug,
|
slug,
|
||||||
file,
|
file,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
|
description: data.description,
|
||||||
visible: data.visible ?? false,
|
visible: data.visible ?? false,
|
||||||
intro: data.intro ?? "",
|
|
||||||
content: data.content,
|
content: data.content,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -60,14 +62,15 @@ export function getDocumentBySlug(slug: string): DocEntry | null {
|
|||||||
|
|
||||||
if (!data.title || typeof data.title !== "string") return null;
|
if (!data.title || typeof data.title !== "string") return null;
|
||||||
if (!data.content || typeof data.content !== "string") return null;
|
if (!data.content || typeof data.content !== "string") return null;
|
||||||
|
if (!data.description || typeof data.description !== "string") continue;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slug,
|
slug,
|
||||||
file,
|
file,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
visible: data.visible ?? false,
|
visible: data.visible ?? false,
|
||||||
intro: data.intro ?? "",
|
|
||||||
content: data.content,
|
content: data.content,
|
||||||
|
description: data.description,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ if (!doc || doc.visible !== true) {
|
|||||||
const html = marked.parse(doc.content);
|
const html = marked.parse(doc.content);
|
||||||
---
|
---
|
||||||
|
|
||||||
<DefaultLayout title={doc.title}>
|
<DefaultLayout title={doc.title} description={doc.description}>
|
||||||
<section class="f-section">
|
<section class="f-section">
|
||||||
<div class="f-section-grid-single">
|
<div class="f-section-grid-single">
|
||||||
<a href="/dokumenty" class="f-document-link">
|
<a href="/dokumenty" class="f-document-link">
|
||||||
|
|||||||
@@ -6,72 +6,94 @@ import {
|
|||||||
normalizePublicHref,
|
normalizePublicHref,
|
||||||
type DocumentsYaml,
|
type DocumentsYaml,
|
||||||
} from "../../lib/astro-helpers";
|
} from "../../lib/astro-helpers";
|
||||||
|
import "../../styles/document.css";
|
||||||
|
|
||||||
const doc = loadYaml<DocumentsYaml>("./src/content/document/documents.yaml");
|
const doc = loadYaml<DocumentsYaml>("./src/content/document/documents.yaml");
|
||||||
const seo = loadYaml("./src/content/document/seo.yaml");
|
const seo = loadYaml("./src/content/document/seo.yaml");
|
||||||
|
|
||||||
const pageTitle = doc?.tytul ?? "Dokumenty";
|
const pageTitle = doc?.tytul ?? "Dokumenty";
|
||||||
const pageDesc = doc?.opis ?? "";
|
const pageDesc = doc?.opis ?? "";
|
||||||
|
|
||||||
const groups = doc?.grupy ?? {};
|
const groups = doc?.grupy ?? {};
|
||||||
const left = groups["otworz"] ?? {};
|
|
||||||
const right = groups["pobierz"] ?? {};
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<DefaultLayout seo={seo}>
|
<DefaultLayout seo={seo}>
|
||||||
{/* CONTENT */}
|
|
||||||
<section class="f-section">
|
<section class="f-section">
|
||||||
<div class="f-section-grid-top md:grid-cols-2 gap-10 items-start">
|
<div class="f-section-grid md:grid-cols-1">
|
||||||
{/* ===== LEWA – CZYTAJ ===== */}
|
<h1 class="f-section-title">{pageTitle}</h1>
|
||||||
<div>
|
{pageDesc && <Markdown text={pageDesc} />}
|
||||||
<h3 class="f-section-title">{left.tytul ?? "Przeczytaj"}</h3>
|
|
||||||
|
|
||||||
{
|
|
||||||
!left.pliki?.length ? (
|
|
||||||
<p class="opacity-70 mt-4">Brak dokumentów.</p>
|
|
||||||
) : (
|
|
||||||
<div class="f-documents-grid">
|
|
||||||
{left.pliki.map((p) =>
|
|
||||||
p.slug ? (
|
|
||||||
<a
|
|
||||||
class="f-document-card"
|
|
||||||
href={`/dokumenty/${p.slug}`}
|
|
||||||
title={p.nazwa}
|
|
||||||
>
|
|
||||||
<div class="f-document-title">{p.nazwa}</div>
|
|
||||||
</a>
|
|
||||||
) : null,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div>
|
<section class="f-section">
|
||||||
<h3 class="f-section-title">{right.tytul ?? "Pobierz"}</h3>
|
<div class="f-section-documents">
|
||||||
|
<div class="f-documents-columns">
|
||||||
{
|
{
|
||||||
!right.pliki?.length ? (
|
Object.entries(groups).map(([key, group]) => (
|
||||||
<p class="opacity-70 mt-4">Brak plików.</p>
|
<div class="f-documents-group" key={key}>
|
||||||
|
<h3 class="f-section-title3">{group.tytul}</h3>
|
||||||
|
|
||||||
|
{!group.pliki?.length ? (
|
||||||
|
<p class="f-documents-empty">Brak dokumentów.</p>
|
||||||
) : (
|
) : (
|
||||||
<div class="f-documents-grid">
|
<div class="f-documents-list">
|
||||||
{right.pliki.map((p) => {
|
{group.pliki.map((p) => {
|
||||||
const href = normalizePublicHref(p.file);
|
// Określ czy to slug (czytaj) czy file (pobierz)
|
||||||
|
const isRead = !!p.slug;
|
||||||
|
const href = isRead
|
||||||
|
? `/dokumenty/${p.slug}`
|
||||||
|
: normalizePublicHref(p.file);
|
||||||
|
|
||||||
if (!href) return null;
|
if (!href) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
class="f-document-card"
|
class="f-document-link"
|
||||||
href={href}
|
href={href}
|
||||||
download
|
{...(isRead ? {} : { download: true })}
|
||||||
title={p.nazwa}
|
title={p.nazwa}
|
||||||
>
|
>
|
||||||
<div class="f-document-title">{p.nazwa}</div>
|
<span class="f-document-icon">
|
||||||
|
{isRead ? (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
|
||||||
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||||
|
<polyline points="7 10 12 15 17 10" />
|
||||||
|
<line x1="12" x2="12" y1="15" y2="3" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span class="f-document-name">{p.nazwa}</span>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type YamlSection = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type YamlData = {
|
type YamlData = {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
sections?: YamlSection[];
|
sections?: YamlSection[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,10 +24,16 @@ let items: Array<{
|
|||||||
content: string;
|
content: string;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
|
let pageTitle = "";
|
||||||
|
let pageDescription = "";
|
||||||
let err = "";
|
let err = "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = loadYaml<YamlData>("./src/content/internet-telewizja/telewizja-mozliwosci.yaml");
|
const data = loadYaml<YamlData>(
|
||||||
|
"./src/content/internet-telewizja/telewizja-mozliwosci.yaml",
|
||||||
|
);
|
||||||
|
pageTitle = data?.title || pageTitle;
|
||||||
|
pageDescription = data?.description || pageDescription;
|
||||||
const sections = safeArray<YamlSection>(data?.sections);
|
const sections = safeArray<YamlSection>(data?.sections);
|
||||||
|
|
||||||
items = sections
|
items = sections
|
||||||
@@ -42,7 +50,7 @@ try {
|
|||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<DefaultLayout title="Możliwości JAMBOX">
|
<DefaultLayout title={pageTitle} description={pageDescription}>
|
||||||
<section class="f-section" id="top">
|
<section class="f-section" id="top">
|
||||||
<div class="f-section-grid-single">
|
<div class="f-section-grid-single">
|
||||||
<h1 class="f-section-title">Możliwości JAMBOX</h1>
|
<h1 class="f-section-title">Możliwości JAMBOX</h1>
|
||||||
|
|||||||
@@ -42,17 +42,20 @@ const form = data.form;
|
|||||||
|
|
||||||
<DefaultLayout seo={seo}>
|
<DefaultLayout seo={seo}>
|
||||||
<section class="f-section">
|
<section class="f-section">
|
||||||
|
<!-- ✅ Zmieniona struktura - osobne bloki zamiast grida -->
|
||||||
<div class="f-contact-grid">
|
<div class="f-contact-grid">
|
||||||
{/* row 1: tytuły */}
|
|
||||||
<h1 class="f-section-title m-0">{data.title}</h1>
|
|
||||||
<h1 class="f-section-title m-0">{data.contactFormTitle}</h1>
|
|
||||||
|
|
||||||
{/* row 2: treść */}
|
{/* Lewa kolumna: Kontakt */}
|
||||||
|
<div class="f-contact-column">
|
||||||
|
<h1 class="f-section-title">{data.title}</h1>
|
||||||
<div class="f-contact-item">
|
<div class="f-contact-item">
|
||||||
<Markdown text={data.description} />
|
<Markdown text={data.description} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="form">
|
{/* Prawa kolumna: Formularz */}
|
||||||
|
<div class="f-contact-column" id="form">
|
||||||
|
<h2 class="f-section-title">{data.contactFormTitle}</h2>
|
||||||
|
|
||||||
<form id="contactForm" class="f-contact-form">
|
<form id="contactForm" class="f-contact-form">
|
||||||
<div class="f-contact-form-inner">
|
<div class="f-contact-form-inner">
|
||||||
@@ -135,6 +138,7 @@ const form = data.form;
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const seo = loadYaml("./src/content/mapa-zasiegu/seo.yaml");
|
|||||||
class="w-full md:w-[340px] bg-[var(--f-background)] text-[var(--f-text)]
|
class="w-full md:w-[340px] bg-[var(--f-background)] text-[var(--f-text)]
|
||||||
pt-6 px-6 flex flex-col gap-6 overflow-y-auto z-40"
|
pt-6 px-6 flex flex-col gap-6 overflow-y-auto z-40"
|
||||||
>
|
>
|
||||||
<h3 class="text-3xl">Sprawdź dostępność usług</h3>
|
<h1 class="text-3xl">Sprawdź dostępność usług</h1>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
Wybierz swoją miejscowość i ulicę oraz numer budynku, aby sprawdzić
|
Wybierz swoją miejscowość i ulicę oraz numer budynku, aby sprawdzić
|
||||||
dostępność usług światłowodowych FUZ.
|
dostępność usług światłowodowych FUZ.
|
||||||
@@ -49,7 +49,9 @@ const seo = loadYaml("./src/content/mapa-zasiegu/seo.yaml");
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<div class="container md:pl-16">
|
||||||
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||||
|
</div>
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
let fiberLayer = null;
|
let fiberLayer = null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import { globby } from 'globby';
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
const base = "https://www.fuz.pl";
|
|
||||||
|
|
||||||
// Pobieramy wszystkie pliki .astro
|
|
||||||
const files = await globby([
|
|
||||||
"src/pages/**/*.astro",
|
|
||||||
"!src/pages/_*.astro", // pomiń pliki zaczynające się od _
|
|
||||||
"!src/pages/**/[...*", // pomiń catch-all
|
|
||||||
"!src/pages/**/[**", // pomiń dynamiczne parametry
|
|
||||||
"!src/pages/sitemap.xml.js", // pomiń samą sitemapę
|
|
||||||
"!src/pages/api/**" // pomiń API endpoints
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Konwersja ścieżek plikowych → URL-e
|
|
||||||
const urls = files.map((file) => {
|
|
||||||
let url = file
|
|
||||||
.replace("src/pages", "")
|
|
||||||
.replace(".astro", "");
|
|
||||||
|
|
||||||
// obsługa index: /index.astro → /
|
|
||||||
if (url.endsWith("/index")) {
|
|
||||||
url = url.replace("/index", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
});
|
|
||||||
|
|
||||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
||||||
${urls
|
|
||||||
.map((url) => {
|
|
||||||
return `
|
|
||||||
<url>
|
|
||||||
<loc>${base}${url}</loc>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>${url === "/" ? "1.0" : "0.8"}</priority>
|
|
||||||
</url>`;
|
|
||||||
})
|
|
||||||
.join("")}
|
|
||||||
</urlset>`;
|
|
||||||
|
|
||||||
return new Response(body, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/xml",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
100
src/pages/sitemap.xml.ts
Normal file
100
src/pages/sitemap.xml.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
import {
|
||||||
|
getEnv,
|
||||||
|
generateSitemapXml,
|
||||||
|
inferChangeFreq,
|
||||||
|
inferPriority,
|
||||||
|
type SitemapUrl
|
||||||
|
} from '../lib/astro-helpers';
|
||||||
|
import { listDocuments } from '../lib/documents';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamiczny sitemap generator
|
||||||
|
* GET /sitemap.xml
|
||||||
|
*/
|
||||||
|
export const GET: APIRoute = async ({ site }) => {
|
||||||
|
const base = site?.toString().replace(/\/$/, '') ||
|
||||||
|
getEnv('PUBLIC_SITE_URL') ||
|
||||||
|
"https://www.fuz.pl";
|
||||||
|
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const urls: SitemapUrl[] = [];
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// STATYCZNE STRONY
|
||||||
|
// ========================================
|
||||||
|
const staticPages = [
|
||||||
|
'/',
|
||||||
|
'/internet-swiatlowodowy',
|
||||||
|
'/internet-telewizja',
|
||||||
|
'/telefon',
|
||||||
|
'/mapa-zasiegu',
|
||||||
|
'/kontakt',
|
||||||
|
'/dokumenty',
|
||||||
|
'/premium',
|
||||||
|
'/telewizja-mozliwosci',
|
||||||
|
];
|
||||||
|
|
||||||
|
staticPages.forEach(path => {
|
||||||
|
urls.push({
|
||||||
|
loc: `${base}${path}`,
|
||||||
|
lastmod: now,
|
||||||
|
changefreq: inferChangeFreq(path),
|
||||||
|
priority: inferPriority(path),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// DYNAMICZNE STRONY: Dokumenty
|
||||||
|
// ========================================
|
||||||
|
try {
|
||||||
|
const docs = listDocuments();
|
||||||
|
|
||||||
|
docs
|
||||||
|
.filter(d => d.visible === true)
|
||||||
|
.forEach(d => {
|
||||||
|
const path = `/dokumenty/${d.slug}`;
|
||||||
|
urls.push({
|
||||||
|
loc: `${base}${path}`,
|
||||||
|
lastmod: now,
|
||||||
|
changefreq: 'yearly',
|
||||||
|
priority: 0.5,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Sitemap: Could not load documents:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// DYNAMICZNE STRONY: Premium packages (opcjonalnie)
|
||||||
|
// ========================================
|
||||||
|
// TODO: Jeśli masz dynamiczne pakiety premium, dodaj tutaj:
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
const packages = await loadPremiumPackages();
|
||||||
|
packages.forEach(p => {
|
||||||
|
urls.push({
|
||||||
|
loc: `${base}/premium/${p.tid}`,
|
||||||
|
lastmod: now,
|
||||||
|
changefreq: 'monthly',
|
||||||
|
priority: 0.7,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Sitemap: Could not load premium packages:', e);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// GENERUJ XML
|
||||||
|
// ========================================
|
||||||
|
const sitemap = generateSitemapXml(urls);
|
||||||
|
|
||||||
|
return new Response(sitemap, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml; charset=utf-8',
|
||||||
|
'Cache-Control': 'public, max-age=3600', // 1h cache
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,15 +1,37 @@
|
|||||||
.f-document-card {
|
.f-section-documents {
|
||||||
@apply flex items-center gap-2 text-lg;
|
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
|
||||||
}
|
}
|
||||||
.f-document-card:hover {
|
.f-documents-columns {
|
||||||
@apply shadow-sm;
|
@apply grid grid-cols-1 gap-8 mt-1;
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .f-document-icon {
|
@media (min-width: 768px) {
|
||||||
@apply text-2xl leading-none mt-1;
|
.f-documents-columns {
|
||||||
} */
|
@apply grid-cols-2;
|
||||||
|
}
|
||||||
.f-document-title {
|
}
|
||||||
@apply font-normal ;
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.f-documents-columns {
|
||||||
|
@apply grid-cols-3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-section-title3{
|
||||||
|
@apply border-b-[1px] border-[var(--f-offers-border)];
|
||||||
|
}
|
||||||
|
.f-documents-group {
|
||||||
|
@apply bg-[--f-bg] text-[--f-text] border border-[--f-offers-border] rounded-2xl shadow-md p-6 relative flex flex-col gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-documents-list {
|
||||||
|
@apply flex flex-col gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-document-link {
|
||||||
|
@apply flex items-center py-1 gap-2 rounded-lg text-[var(--f-text,#111827)] no-underline transition-all duration-200 bg-transparent border border-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-document-icon {
|
||||||
|
@apply flex-shrink-0 w-5 h-5 transition-all duration-200 text-[var(--f-text-muted,#6b7280)];
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
.gm-style-iw-c {
|
.gm-style-iw-c {
|
||||||
background: var(--f-background) !important;
|
background: var(--f-background) !important;
|
||||||
border-radius: 14px !important;
|
border-radius: 14px !important;
|
||||||
box-shadow: 0 4px 18px rgba(0,0,0,0.12) !important;
|
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.12) !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,3 +121,30 @@
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.fuz-cities-box {
|
||||||
|
|
||||||
|
background: var(--f-background);
|
||||||
|
color: var(--f-text);
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuz-cities-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuz-cities-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
||||||
|
gap: 6px 14px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fuz-city-item {
|
||||||
|
color: var(--f-text);
|
||||||
|
}
|
||||||
@@ -41,6 +41,10 @@
|
|||||||
@apply text-4xl md:text-5xl font-bold mb-2 text-[--f-header];
|
@apply text-4xl md:text-5xl font-bold mb-2 text-[--f-header];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.f-section-title3 {
|
||||||
|
@apply text-2xl md:text-2xl font-bold mb-2 text-[--f-header];
|
||||||
|
}
|
||||||
|
|
||||||
.f-section-nav {
|
.f-section-nav {
|
||||||
@apply mt-0 flex justify-center;
|
@apply mt-0 flex justify-center;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user