Kolejne zmiany,

This commit is contained in:
dm
2025-12-15 11:28:53 +01:00
parent c0b9d5a584
commit 6b5a913666
48 changed files with 1630 additions and 868 deletions

View File

@@ -6,7 +6,7 @@ export type DocYaml = {
title: string;
visible?: boolean;
intro?: string;
content: string; // markdown
content: string;
};
export type DocEntry = DocYaml & {
@@ -32,7 +32,6 @@ export function listDocuments(): DocEntry[] {
const slug = file.replace(/\.ya?ml$/i, "");
// minimalna walidacja, żeby nic nie wybuchało
if (!data.title || typeof data.title !== "string") continue;
if (!data.content || typeof data.content !== "string") continue;
@@ -50,7 +49,6 @@ export function listDocuments(): DocEntry[] {
}
export function getDocumentBySlug(slug: string): DocEntry | null {
// akceptuj .yaml i .yml
const candidates = [`${slug}.yaml`, `${slug}.yml`];
for (const file of candidates) {

View File

@@ -1,117 +0,0 @@
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 &nbsp; itd.
function cleanHtmlText(s?: string): string {
if (!s) return "";
return String(s)
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&quot;/g, '"')
.replace(/&#39;/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;
}