From 4c6d9fdf0efb48692fceb89849f11c572cdbcd27 Mon Sep 17 00:00:00 2001 From: Andrew Marcuse Date: Mon, 20 Jan 2025 14:15:18 -0500 Subject: [PATCH] Transforms working --- README.md | 6 +-- messages/de.json | 6 +-- messages/en.json | 6 +-- messages/es.json | 6 +-- messages/fr.json | 6 +-- src/app/view.html/page.tsx | 87 ++++++++++++++++-------------- src/components/ProTip.tsx | 2 +- src/components/TransformSelect.tsx | 16 +++--- 8 files changed, 69 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 019664b..e50ecc8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Outline Viewer [Outline Viewer logo](https://opml.xml.style/) -[![NodePing status](https://img.shields.io/nodeping/status/9rpjcz1i-8nzx-442d-8yzk-tm7l5zfhbllw?label=Current%20status)](https://nodeping.com/reports/checks/9rpjcz1i-8nzx-442d-8yzk-tm7l5zfhbllw) -[![NodePing uptime](https://img.shields.io/nodeping/uptime/9rpjcz1i-8nzx-442d-8yzk-tm7l5zfhbllw?label=30-day%20uptime)](https://nodeping.com/reports/uptime/9rpjcz1i-8nzx-442d-8yzk-tm7l5zfhbllw) +[![NodePing status](https://img.shields.io/nodeping/status/O0XVZ8N8-AB6K-4DZG-80XJ-5P7F7HP3DCMG?label=Current%20status)](https://nodeping.com/reports/checks/O0XVZ8N8-AB6K-4DZG-80XJ-5P7F7HP3DCMG) +[![NodePing uptime](https://img.shields.io/nodeping/uptime/O0XVZ8N8-AB6K-4DZG-80XJ-5P7F7HP3DCMG?label=30-day%20uptime)](https://nodeping.com/reports/uptime/O0XVZ8N8-AB6K-4DZG-80XJ-5P7F7HP3DCMG) [![deploy](https://github.com/fileformat/opml.xml.style/actions/workflows/gcr-deploy.yaml/badge.svg)](https://github.com/fileformat/opml.xml.style/actions/workflows/gcr-deploy.yaml) -This is a graphical viewer for `opml.xml` files. Try it at [opml.xml.style](https://opml.xml.style/)! +This is a graphical viewer for `opml.xml` outlines. Try it at [opml.xml.style](https://opml.xml.style/)! ## Running locally diff --git a/messages/de.json b/messages/de.json index c85d2bc..002993d 100644 --- a/messages/de.json +++ b/messages/de.json @@ -46,15 +46,15 @@ "url": "URL" }, "Transform": { - "initialcap": "Erster Buchstabe groß, Interpunktion zu Leerzeichen", + "initialcap": "Erster Buchstabe groß", "label": "Seitennamen-Transformation", "original": "Keine Änderung", - "titlecase": "Titel-Schreibweise, Interpunktion zu Leerzeichen" + "titlecase": "Titel-Schreibweise" }, "ViewPage": { "feed_icon_alt": "RSS/Atom-Feed-Symbol", "home": "Startseite", - "poweredby": "Angetrieben durch opml.xml.style", + "poweredby": "Angetrieben durch xml.style", "title": "Gliederung" } } \ No newline at end of file diff --git a/messages/en.json b/messages/en.json index bacfb44..a088c34 100644 --- a/messages/en.json +++ b/messages/en.json @@ -46,15 +46,15 @@ "url": "URL" }, "Transform": { - "initialcap": "First letter capitalized, punctation to spaces", + "initialcap": "First letter capitalized", "label": "Page name transformation", "original": "No change", - "titlecase": "Title cased, punctuation to spaces" + "titlecase": "Title cased" }, "ViewPage": { "feed_icon_alt": "RSS/Atom Feed Icon", "home": "Home page", - "poweredby": "Powered by opml.xml.style", + "poweredby": "Powered by xml.style", "title": "Outline" } } \ No newline at end of file diff --git a/messages/es.json b/messages/es.json index 86ae368..8ab3597 100644 --- a/messages/es.json +++ b/messages/es.json @@ -46,15 +46,15 @@ "url": "URL" }, "Transform": { - "initialcap": "Primera letra en mayúscula, puntuación a espacios", + "initialcap": "Primera letra en mayúscula", "label": "Transformación del nombre de la página", "original": "Sin cambio", - "titlecase": "Título en mayúsculas, puntuación a espacios" + "titlecase": "Título en mayúsculas" }, "ViewPage": { "feed_icon_alt": "Icono de alimentación RSS/Atom", "home": "Página de inicio", - "poweredby": "Desarrollado por opml.xml.style", + "poweredby": "Desarrollado por xml.style", "title": "Mapa del sitio" } } \ No newline at end of file diff --git a/messages/fr.json b/messages/fr.json index 1ee0fe3..b271620 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -46,15 +46,15 @@ "url": "URL" }, "Transform": { - "initialcap": "Première lettre en majuscule, ponctuation en espaces", + "initialcap": "Première lettre en majuscule", "label": "Transformation du nom de la page", "original": "Aucun changement", - "titlecase": "Majuscule à chaque mot, ponctuation en espaces" + "titlecase": "Majuscule à chaque mot" }, "ViewPage": { "feed_icon_alt": "Icône de flux RSS/Atom", "home": "Page d'accueil", - "poweredby": "Propulsé par opml.xml.style", + "poweredby": "Propulsé par xml.style", "title": "Plan du site" } } \ No newline at end of file diff --git a/src/app/view.html/page.tsx b/src/app/view.html/page.tsx index a98da3d..8ba6eea 100644 --- a/src/app/view.html/page.tsx +++ b/src/app/view.html/page.tsx @@ -12,7 +12,7 @@ import { PoweredBy } from '@/components/PoweredBy'; import { trackUsage } from '@/lib/usage'; import { DEFAULT_SORT } from '@/components/SortSelect'; import { loadOutline } from '@/lib/loadOutline'; -//import { DEFAULT_TRANSFORM } from '@/components/TransformSelect'; +import { DEFAULT_TRANSFORM, getTransform } from '@/components/TransformSelect'; export default async function View({ searchParams, @@ -44,19 +44,27 @@ export default async function View({ trackUsage(url_str); - const sme:OpmlData = await loadOutline(url_str); - const items:TreeItem[] = sme.root.children; + const sme: OpmlData = await loadOutline(url_str); + const items: TreeItem[] = sme.root.children; if (useRssStyle) { transform(items, addRssStyle); } if (sort === 'name') { - sortTreeName(items); + sortTree(items, (a, b) => a.label.localeCompare(b.label)); } else if (sort === 'dirfirst') { - sortTreeDirFirst(items); + sortTree(items, compareDirFirst); + } else if (sort === 'url') { + sortTree(items, compareUrl); } + const transformer = getTransform(getFirst(urlParams['transform'], DEFAULT_TRANSFORM)); + if (transformer) { + transform(items, transformer); + } + + const title = sme.title || customTitle; if (!sme.success) { showDebug = true; @@ -64,61 +72,58 @@ export default async function View({ return ( <> - + - - - {sme.success || items.length ? :

Failed to load outline

} -
- + + {sme.success || items.length ? :

Failed to load outline

} +
+ +
-
); } -function sortTreeName(items: TreeItem[]) { + + +function sortTree(items: TreeItem[], sortfn: (a: TreeItem, b: TreeItem) => number) { if (items.length == 0) { return; } if (items.length > 1) { - items.sort((a, b) => a.label.localeCompare(b.label)); + items.sort(sortfn); } for (const item of items) { - sortTreeName(item.children); + sortTree(item.children, sortfn); } } -function sortTreeDirFirst(items: TreeItem[]) { - if (items.length == 0) { - return; - } - if (items.length > 1) { - items.sort((a, b) => { - if (a.children.length > 0 && b.children.length == 0) { - return -1; - } else if (a.children.length == 0 && b.children.length > 0) { - return 1; - } - return a.label.localeCompare(b.label) - }); +function compareDirFirst(a: TreeItem, b: TreeItem) { + if (a.children.length > 0 && b.children.length == 0) { + return -1; + } else if (a.children.length == 0 && b.children.length > 0) { + return 1; } + return a.label.localeCompare(b.label) +} - for (const item of items) { - sortTreeDirFirst(item.children); - } +function compareUrl(a: TreeItem, b: TreeItem) { + const aUrl = a.htmlUrl || a.xmlUrl || a.label; + const bUrl = b.htmlUrl || b.xmlUrl || b.label; + return aUrl.localeCompare(bUrl); } function addRssStyle(item: TreeItem) { @@ -127,7 +132,7 @@ function addRssStyle(item: TreeItem) { } } -function transform(items: TreeItem[], transformer: (item:TreeItem) => void) { +function transform(items: TreeItem[], transformer: (item: TreeItem) => void) { for (const item of items) { transformer(item); if (item.children.length > 0) { diff --git a/src/components/ProTip.tsx b/src/components/ProTip.tsx index add4dc7..fcaa41f 100644 --- a/src/components/ProTip.tsx +++ b/src/components/ProTip.tsx @@ -19,7 +19,7 @@ export default function ProTip() { {t.rich('tip', { Icon: () => , - Link: (chunks) => {chunks}, + Link: (chunks) => {chunks}, })} ); diff --git a/src/components/TransformSelect.tsx b/src/components/TransformSelect.tsx index 59b0d8a..875ccda 100644 --- a/src/components/TransformSelect.tsx +++ b/src/components/TransformSelect.tsx @@ -4,35 +4,33 @@ import FormControl from '@mui/material/FormControl'; import Select from '@mui/material/Select'; import { useTranslations } from 'next-intl'; import { UseFormRegisterReturn } from 'react-hook-form'; +import { TreeItem } from '@/lib/types'; type Transform = (typeof transforms)[number] const transforms = ['original', 'initialcap', 'titlecase'] as const; -//LATER: maybe also \p{Symbol}? - -function initialCap(s: string): string { - s = s.replace(/[^\p{Letter}\p{Number}]+/gu, ' '); - return s.length == 0 ? s : s.charAt(0).toUpperCase() + s.slice(1); +function initialCap(item: TreeItem): void { + item.label = item.label.length == 0 ? item.label : item.label.charAt(0).toUpperCase() + item.label.slice(1); } function titleCaseWord(s: string): string { return s.length == 0 ? s : s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); } -function titleCase(s: string): string { - return s.replace(/[^\p{Letter}\p{Number}]+/gu, ' ').split(/\s+/).map(titleCaseWord).join(' '); +function titleCase(item: TreeItem): void { + item.label = item.label.split(/\s+/).map(titleCaseWord).join(' '); } const DEFAULT_TRANSFORM:Transform = 'initialcap'; -const transformMap = new Map string>([ +const transformMap = new Map void>([ ['initialcap', initialCap], ['titlecase', titleCase], ]); -function getTransform(value: string): ((s: string) => string) | null { +function getTransform(value: string): ((item: TreeItem) => void) | null { return transformMap.get(value) || null; }