diff --git a/src/content/internet-telewizja/section.yaml b/src/content/internet-telewizja/section.yaml index 8e4b6c2..aa86d17 100644 --- a/src/content/internet-telewizja/section.yaml +++ b/src/content/internet-telewizja/section.yaml @@ -1,6 +1,10 @@ sections: - title: "Dekoder telewizyjny" image: "VIP4302.png" + button: + text: "Zobacz możliwości dekodera →" + url: "/internet-telewizja/mozliwosci" + title: "Zobacz możliwości dekodera" 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. @@ -16,4 +20,4 @@ sections: - Standardowe wejścia/wyjścia dla dźwięku, obrazu i danych: - Interfejsy tylnego panelu obejmują m.in. Ethernet, USB, HDMI, CVBS, Optyczny i analogowy audio 3,5mm - Przedni panel zawiera m.in. diodę LED i odbiornik podczerwieni - - Wymiary modelu (szer/dł/wys): 130 x 130 x 26 mm \ No newline at end of file + - Wymiary modelu (szer/dł/wys): 130 x 130 x 26 mm diff --git a/src/lib/mozliwosci.ts b/src/lib/mozliwosci.ts new file mode 100644 index 0000000..9c41e39 --- /dev/null +++ b/src/lib/mozliwosci.ts @@ -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(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 (
url
) + const urls = s.match(/https?:\/\/[^\s<"]+/g) ?? []; + return urls.map((u) => u.trim()); +} + +function extractScreens(screen: any): string[] { + if (!screen) return []; + + // przypadek: https://...png + if (typeof screen === "string") return extractUrlsFromString(screen); + + // przypadek:
URL
...
+ // 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 { + 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; +} diff --git a/src/pages/internet-telewizja/mozliwosci.astro b/src/pages/internet-telewizja/mozliwosci.astro new file mode 100644 index 0000000..9f439d5 --- /dev/null +++ b/src/pages/internet-telewizja/mozliwosci.astro @@ -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(); +} +--- + + + +
+
+

Możliwości JAMBOX

+
+

Funkcje i udogodnienia dostępne w JAMBOX.

+
+
+ + { + err && ( +
+

Nie udało się pobrać danych

+

{err}

+
+ ) + } +
+ + { + !err && ( + <> + {items.map((it, index) => { + const reverse = index % 2 === 1; + const imageUrl = it.screens?.[0] || ""; + const hasImage = !!imageUrl; + + return ( +
+
+ {hasImage && ( + {it.title} + )} + +
+

{it.title}

+ + + + +
+
+
+ ); + })} + + ) + } +