From c7939ca0451e4cf64144d2f305980bdf0eef9230 Mon Sep 17 00:00:00 2001 From: shigahi Date: Fri, 23 Jan 2026 00:27:35 +0100 Subject: [PATCH 1/2] Add RSS feed generation (#30) - Add RssOptions interface for RSS configuration - Implement generateRssFeed function generating RSS 2.0 XML - Add /rss.xml endpoint handler in worker code - Add RSS configuration UI in Advanced Settings - Enable RSS toggle - Feed title and description inputs - Language selector (en-us, ja, de, fr, es, zh-cn, zh-tw, ko) --- src/App.tsx | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/code.ts | 65 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/src/App.tsx b/src/App.tsx index f78b71e..fbb84db 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -57,6 +57,7 @@ import code, { Custom404Options, SubdomainRedirect, RedirectRule, + RssOptions, } from "./code"; import "./styles.css"; @@ -228,6 +229,12 @@ export default function App() { SubdomainRedirect[] >([]); const [redirectRules, setRedirectRules] = useState([]); + const [rss, setRss] = useState({ + enabled: false, + title: "", + description: "", + language: "en-us", + }); function createInputHandler( setter: React.Dispatch>, @@ -328,6 +335,7 @@ export default function App() { const handleCachingChange = createFieldHandler(setCaching, caching); const handleCustomHtmlChange = createFieldHandler(setCustomHtml, customHtml); const handleCustom404Change = createFieldHandler(setCustom404, custom404); + const handleRssChange = createFieldHandler(setRss, rss); function addSubdomainRedirect(): void { setSubdomainRedirects([ @@ -448,6 +456,7 @@ export default function App() { custom404, subdomainRedirects, redirectRules, + rss, }; const script = noError ? code(codeData) : undefined; @@ -1455,6 +1464,82 @@ export default function App() { + + + + + + RSS Feed + + + Generate RSS 2.0 feed at /rss.xml + + + + handleRssChange("enabled", e.target.checked) + } + /> + + + + handleRssChange("title", e.target.value)} + value={rss.title} + variant="outlined" + size="small" + /> + + handleRssChange("description", e.target.value) + } + value={rss.description} + variant="outlined" + size="small" + /> + + Language + + + + RSS feed will include all pages defined in Pretty Links. + Add page metadata for better feed content. + + + + diff --git a/src/code.ts b/src/code.ts index 9a450e2..7e75aa5 100644 --- a/src/code.ts +++ b/src/code.ts @@ -73,6 +73,13 @@ export interface RedirectRule { permanent: boolean; } +export interface RssOptions { + enabled: boolean; + title?: string; + description?: string; + language?: string; +} + export interface CodeData { myDomain: string; notionUrl: string; @@ -94,6 +101,7 @@ export interface CodeData { custom404: Custom404Options; subdomainRedirects: SubdomainRedirect[]; redirectRules: RedirectRule[]; + rss: RssOptions; } function getId(url: string): string { @@ -128,6 +136,7 @@ export default function code(data: CodeData): string { custom404, subdomainRedirects, redirectRules, + rss, } = data; let url = myDomain.replace("https://", "").replace("http://", ""); if (url.slice(-1) === "/") url = url.slice(0, url.length - 1); @@ -251,6 +260,15 @@ ${ .join("") || "" } ]; + /* + * Step 3.9: RSS feed configuration (optional) + * Generate an RSS 2.0 feed at /rss.xml for blog-style sites + */ + const RSS_ENABLED = ${rss?.enabled || false}; + const RSS_TITLE = '${rss?.title || ""}'; + const RSS_DESCRIPTION = '${rss?.description || ""}'; + const RSS_LANGUAGE = '${rss?.language || "en-us"}'; + /* Step 4: enter a Google Font name, you can choose from https://fonts.google.com */ const GOOGLE_FONT = '${googleFont || ""}'; @@ -345,6 +363,48 @@ ${ return sitemap; } + function generateRssFeed() { + const title = RSS_TITLE || MY_DOMAIN; + const description = RSS_DESCRIPTION || \`RSS feed for \${MY_DOMAIN}\`; + const buildDate = new Date().toUTCString(); + let rss = ''; + rss += ''; + rss += ''; + rss += \`\${escapeXml(title)}\`; + rss += \`https://\${MY_DOMAIN}\`; + rss += \`\${escapeXml(description)}\`; + rss += \`\${RSS_LANGUAGE}\`; + rss += \`\${buildDate}\`; + rss += \`\`; + slugs.forEach((slug) => { + const pageId = SLUG_TO_PAGE[slug]; + const metadata = PAGE_METADATA[slug] || {}; + const itemTitle = metadata.title || PAGE_TITLE || slug || 'Home'; + const itemDescription = metadata.description || PAGE_DESCRIPTION || ''; + const itemUrl = 'https://' + MY_DOMAIN + (slug ? '/' + slug : ''); + rss += ''; + rss += \`\${escapeXml(itemTitle)}\`; + rss += \`\${itemUrl}\`; + rss += \`\${itemUrl}\`; + if (itemDescription) { + rss += \`\${escapeXml(itemDescription)}\`; + } + rss += ''; + }); + rss += ''; + rss += ''; + return rss; + } + + function escapeXml(str) { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, OPTIONS', @@ -429,6 +489,11 @@ ${ response.headers.set('content-type', 'application/xml'); return response; } + if (url.pathname === '/rss.xml' && RSS_ENABLED) { + let response = new Response(generateRssFeed()); + response.headers.set('content-type', 'application/rss+xml; charset=utf-8'); + return response; + } let response; if (url.pathname.startsWith('/app') && url.pathname.endsWith('js')) { response = await fetch(url.toString()); From 369ae1fc22755822905d86a2534d6e4c562fd4fd Mon Sep 17 00:00:00 2001 From: shigahi Date: Fri, 23 Jan 2026 00:28:39 +0100 Subject: [PATCH 2/2] Remove unused SettingsSection component --- src/App.tsx | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index fbb84db..175792d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -136,38 +136,6 @@ function FeatureCard({ icon, title, description }: FeatureCardProps) { ); } -interface SettingsSectionProps { - title: string; - subtitle?: string; - children: ReactNode; - isFirst?: boolean; -} - -function SettingsSection({ - title, - subtitle, - children, - isFirst, -}: SettingsSectionProps) { - return ( - - - {title} - - {subtitle && ( - - {subtitle} - - )} - {children} - - ); -} - export default function App() { const [slugs, setSlugs] = useState([]); const [myDomain, setMyDomain] = useState("");