Dodana strona możliwości dekodera
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
sections:
|
sections:
|
||||||
- title: "Dekoder telewizyjny"
|
- title: "Dekoder telewizyjny"
|
||||||
image: "VIP4302.png"
|
image: "VIP4302.png"
|
||||||
|
button:
|
||||||
|
text: "Zobacz możliwości dekodera →"
|
||||||
|
url: "/internet-telewizja/mozliwosci"
|
||||||
|
title: "Zobacz możliwości dekodera"
|
||||||
content: |
|
content: |
|
||||||
Arris 4302 HD to kompaktowy sprzęt z możliwością korzystania z jakości HD. Oprogramowanie Kyanit z wygodnym i szybkim interfejsem użytkownika.
|
Arris 4302 HD to kompaktowy sprzęt z możliwością korzystania z jakości HD. Oprogramowanie Kyanit z wygodnym i szybkim interfejsem użytkownika.
|
||||||
|
|
||||||
|
|||||||
117
src/lib/mozliwosci.ts
Normal file
117
src/lib/mozliwosci.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { XMLParser } from "fast-xml-parser";
|
||||||
|
|
||||||
|
const URL = "https://www.jambox.pl/xml/mozliwosci.xml";
|
||||||
|
|
||||||
|
// mały cache w pamięci procesu (SSR)
|
||||||
|
let cache: { ts: number; items: Feature[] } | null = null;
|
||||||
|
|
||||||
|
export type Feature = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
teaser?: string;
|
||||||
|
description?: string;
|
||||||
|
screens: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function toArray<T>(v: T | T[] | undefined | null): T[] {
|
||||||
|
if (!v) return [];
|
||||||
|
return Array.isArray(v) ? v : [v];
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimalne “od-HTML-owanie” dla itd.
|
||||||
|
function cleanHtmlText(s?: string): string {
|
||||||
|
if (!s) return "";
|
||||||
|
return String(s)
|
||||||
|
.replace(/ /g, " ")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function slugify(s: string) {
|
||||||
|
return cleanHtmlText(s)
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[\u0105]/g, "a")
|
||||||
|
.replace(/[\u0107]/g, "c")
|
||||||
|
.replace(/[\u0119]/g, "e")
|
||||||
|
.replace(/[\u0142]/g, "l")
|
||||||
|
.replace(/[\u0144]/g, "n")
|
||||||
|
.replace(/[\u00f3]/g, "o")
|
||||||
|
.replace(/[\u015b]/g, "s")
|
||||||
|
.replace(/[\u017a\u017c]/g, "z")
|
||||||
|
.replace(/[^a-z0-9]+/g, "-")
|
||||||
|
.replace(/(^-|-$)/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractUrlsFromString(s: string): string[] {
|
||||||
|
// wyciąga URL-e zarówno z czystego tekstu, jak i z HTML (<div>url</div>)
|
||||||
|
const urls = s.match(/https?:\/\/[^\s<"]+/g) ?? [];
|
||||||
|
return urls.map((u) => u.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractScreens(screen: any): string[] {
|
||||||
|
if (!screen) return [];
|
||||||
|
|
||||||
|
// przypadek: <screen>https://...png</screen>
|
||||||
|
if (typeof screen === "string") return extractUrlsFromString(screen);
|
||||||
|
|
||||||
|
// przypadek: <screen><div class="field-item">URL</div>...</screen>
|
||||||
|
// fast-xml-parser zrobi np. screen.div albo screen["div"]
|
||||||
|
const divs = (screen as any)?.div;
|
||||||
|
if (divs) {
|
||||||
|
return toArray(divs)
|
||||||
|
.map((d) => {
|
||||||
|
if (typeof d === "string") return d;
|
||||||
|
// czasem parser daje obiekt z #text
|
||||||
|
return d?.["#text"] ?? "";
|
||||||
|
})
|
||||||
|
.flatMap((x) => extractUrlsFromString(String(x)))
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback: spróbuj stringifikacji i regex na URL
|
||||||
|
return extractUrlsFromString(JSON.stringify(screen));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchMozliwosci(ttlMs = 60_000): Promise<Feature[]> {
|
||||||
|
const now = Date.now();
|
||||||
|
if (cache && now - cache.ts < ttlMs) return cache.items;
|
||||||
|
|
||||||
|
const res = await fetch(URL, {
|
||||||
|
headers: { accept: "application/xml,text/xml,*/*" },
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error(`JAMBOX XML: HTTP ${res.status}`);
|
||||||
|
|
||||||
|
const xml = await res.text();
|
||||||
|
|
||||||
|
const parser = new XMLParser({
|
||||||
|
ignoreAttributes: false,
|
||||||
|
attributeNamePrefix: "@_",
|
||||||
|
trimValues: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const parsed = parser.parse(xml);
|
||||||
|
|
||||||
|
const nodes = toArray(parsed?.xml?.node ?? parsed?.node);
|
||||||
|
|
||||||
|
const items: Feature[] = nodes
|
||||||
|
.map((n: any) => {
|
||||||
|
const title = cleanHtmlText(n?.title ?? "");
|
||||||
|
const teaser = cleanHtmlText(n?.teaser ?? "");
|
||||||
|
const description = cleanHtmlText(n?.description ?? "");
|
||||||
|
|
||||||
|
const icon = typeof n?.icon === "string" ? n.icon.trim() : undefined;
|
||||||
|
const screens = extractScreens(n?.screen);
|
||||||
|
|
||||||
|
const id = slugify(title || "feature");
|
||||||
|
|
||||||
|
return { id, title, icon, teaser, description, screens };
|
||||||
|
})
|
||||||
|
.filter((x) => x.title);
|
||||||
|
|
||||||
|
cache = { ts: now, items };
|
||||||
|
return items;
|
||||||
|
}
|
||||||
96
src/pages/internet-telewizja/mozliwosci.astro
Normal file
96
src/pages/internet-telewizja/mozliwosci.astro
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||||||
|
import Markdown from "../../islands/Markdown.jsx";
|
||||||
|
import { fetchMozliwosci, type Feature } from "../../lib/mozliwosci";
|
||||||
|
|
||||||
|
let items: Feature[] = [];
|
||||||
|
let err = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
items = await fetchMozliwosci(60_000);
|
||||||
|
} catch (e) {
|
||||||
|
err = e instanceof Error ? e.message : String(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMarkdown(it: Feature) {
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (it.teaser) parts.push(`> ${it.teaser}\n`);
|
||||||
|
if (it.description) parts.push(it.description);
|
||||||
|
return parts.join("\n\n").trim();
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<DefaultLayout title="Możliwości JAMBOX">
|
||||||
|
<!-- NAGŁÓWEK STRONY – zgodny z FUZ -->
|
||||||
|
<section class="f-section" id="top">
|
||||||
|
<div class="f-section-grid-single">
|
||||||
|
<h1 class="f-section-title">Możliwości JAMBOX</h1>
|
||||||
|
<div class="fuz-markdown max-w-none">
|
||||||
|
<p>Funkcje i udogodnienia dostępne w JAMBOX.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
err && (
|
||||||
|
<div class="mt-6 max-w-7xl mx-auto text-left rounded-2xl border border-red-300 bg-red-50 p-4">
|
||||||
|
<p class="font-bold">Nie udało się pobrać danych</p>
|
||||||
|
<p class="opacity-80">{err}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{
|
||||||
|
!err && (
|
||||||
|
<>
|
||||||
|
{items.map((it, index) => {
|
||||||
|
const reverse = index % 2 === 1;
|
||||||
|
const imageUrl = it.screens?.[0] || "";
|
||||||
|
const hasImage = !!imageUrl;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section class="f-section" id={it.id}>
|
||||||
|
<div
|
||||||
|
class={`f-section-grid ${
|
||||||
|
hasImage
|
||||||
|
? "md:grid-cols-2"
|
||||||
|
: "md:grid-cols-1"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{hasImage && (
|
||||||
|
<img
|
||||||
|
src={imageUrl}
|
||||||
|
alt={it.title}
|
||||||
|
class={`f-section-image ${
|
||||||
|
reverse
|
||||||
|
? "md:order-1"
|
||||||
|
: "md:order-2"
|
||||||
|
} rounded-2xl border border-[--f-border-color]`}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={`${hasImage ? (reverse ? "md:order-2" : "md:order-1") : ""}`}
|
||||||
|
>
|
||||||
|
<h2 class="f-section-title">{it.title}</h2>
|
||||||
|
|
||||||
|
<Markdown text={buildMarkdown(it)} />
|
||||||
|
|
||||||
|
<div class="f-section-nav">
|
||||||
|
<a href="#top" class="btn btn-outline">
|
||||||
|
Do góry ↑
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</DefaultLayout>
|
||||||
Reference in New Issue
Block a user