Skip to content

Commit

Permalink
Transforms working
Browse files Browse the repository at this point in the history
  • Loading branch information
fileformat committed Jan 20, 2025
1 parent b4ff158 commit 4c6d9fd
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 66 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Outline Viewer [<img alt="Outline Viewer logo" src="public/favicon.svg" height="90" align="right" />](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

Expand Down
6 changes: 3 additions & 3 deletions messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
6 changes: 3 additions & 3 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
6 changes: 3 additions & 3 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
6 changes: 3 additions & 3 deletions messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
87 changes: 46 additions & 41 deletions src/app/view.html/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -44,81 +44,86 @@ 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;
}

return (
<>
<Container maxWidth={false} disableGutters={true} sx={{ minHeight: '100vh' }}>
<Container maxWidth={false} disableGutters={true} sx={{ minHeight: '100vh' }}>
<NavBar debug={showDebug} exit={showExit} language={showLanguage} messages={sme.messages} mode={showMode} title={title} returnUrl={returnUrl} />
<Container
maxWidth="md"
disableGutters={true}
sx={{ alignItems: "center", display: "flex", flexDirection: "column", justifyContent: "top",minHeight: '100vh' }}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
}}
<Container
maxWidth="md"
disableGutters={true}
sx={{ alignItems: "center", display: "flex", flexDirection: "column", justifyContent: "top", minHeight: '100vh' }}
>
{sme.success || items.length ? <OpmlTreeView items={items} /> : <h1>Failed to load outline</h1>}
</Box>
<PoweredBy />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
}}
>
{sme.success || items.length ? <OpmlTreeView items={items} /> : <h1>Failed to load outline</h1>}
</Box>
<PoweredBy />
</Container>
</Container>
</Container>
</>

);
}

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) {
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProTip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function ProTip() {
<Typography sx={{ mt: 6, mb: 3, textAlign: 'center', color: 'text.secondary' }}>
{t.rich('tip', {
Icon: () => <LightBulbIcon sx={{ mr: 1, verticalAlign: 'middle' }} />,
Link: (chunks) => <NextLink href="/integration.html">{chunks}</NextLink>,
Link: (chunks) => <NextLink href="https://www.xml.style/opml/">{chunks}</NextLink>,
})}
</Typography>
);
Expand Down
16 changes: 7 additions & 9 deletions src/components/TransformSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, (s: string) => string>([
const transformMap = new Map<string, (item:TreeItem) => 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;
}

Expand Down

0 comments on commit 4c6d9fd

Please sign in to comment.