summaryrefslogtreecommitdiff
path: root/apps/web/app/reader
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-08 00:18:30 -0800
committerFuwn <[email protected]>2026-02-08 00:18:30 -0800
commite6cfa9ccc0b51e34690c3db38216daa659ae147f (patch)
treed8b0a471fa86e08034ed412da1e163fae2263685 /apps/web/app/reader
parentfeat: add option to show favicons next to feed names in entry list (diff)
downloadasa.news-e6cfa9ccc0b51e34690c3db38216daa659ae147f.tar.xz
asa.news-e6cfa9ccc0b51e34690c3db38216daa659ae147f.zip
feat: add feed URL editing in subscription settings, fix notification badge styling and HTML entity decoding
Diffstat (limited to 'apps/web/app/reader')
-rw-r--r--apps/web/app/reader/_components/entry-list-item.tsx12
-rw-r--r--apps/web/app/reader/_components/sidebar-footer.tsx4
-rw-r--r--apps/web/app/reader/settings/_components/subscriptions-settings.tsx61
3 files changed, 73 insertions, 4 deletions
diff --git a/apps/web/app/reader/_components/entry-list-item.tsx b/apps/web/app/reader/_components/entry-list-item.tsx
index 7473037..ad21c28 100644
--- a/apps/web/app/reader/_components/entry-list-item.tsx
+++ b/apps/web/app/reader/_components/entry-list-item.tsx
@@ -26,7 +26,17 @@ interface EntryListItemProperties {
}
function stripHtmlTags(html: string): string {
- return html.replace(/<[^>]*>/g, "").replace(/&\w+;/g, " ").trim()
+ return html
+ .replace(/<[^>]*>/g, "")
+ .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)))
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, code) => String.fromCharCode(parseInt(code, 16)))
+ .replace(/&amp;/g, "&")
+ .replace(/&lt;/g, "<")
+ .replace(/&gt;/g, ">")
+ .replace(/&quot;/g, '"')
+ .replace(/&apos;/g, "'")
+ .replace(/&nbsp;/g, " ")
+ .trim()
}
export function EntryListItem({
diff --git a/apps/web/app/reader/_components/sidebar-footer.tsx b/apps/web/app/reader/_components/sidebar-footer.tsx
index 4b1b293..89db5b3 100644
--- a/apps/web/app/reader/_components/sidebar-footer.tsx
+++ b/apps/web/app/reader/_components/sidebar-footer.tsx
@@ -54,8 +54,8 @@ export function SidebarFooter() {
>
notifications
{unviewedNotificationCount > 0 && (
- <span className="ml-1 inline-flex h-4 min-w-4 items-center justify-center bg-accent-primary px-1 text-[0.6875rem] text-background-primary">
- {unviewedNotificationCount}
+ <span className="ml-auto shrink-0 text-[0.625rem] tabular-nums text-text-dim">
+ {unviewedNotificationCount > 999 ? "999+" : unviewedNotificationCount}
</span>
)}
</button>
diff --git a/apps/web/app/reader/settings/_components/subscriptions-settings.tsx b/apps/web/app/reader/settings/_components/subscriptions-settings.tsx
index 7257231..2c79238 100644
--- a/apps/web/app/reader/settings/_components/subscriptions-settings.tsx
+++ b/apps/web/app/reader/settings/_components/subscriptions-settings.tsx
@@ -4,6 +4,7 @@ import { useState } from "react"
import { useSubscriptions } from "@/lib/queries/use-subscriptions"
import {
useUpdateSubscriptionTitle,
+ useUpdateFeedUrl,
useMoveSubscriptionToFolder,
useUnsubscribe,
useRequestFeedRefresh,
@@ -39,8 +40,11 @@ function SubscriptionRow({
const [editedTitle, setEditedTitle] = useState(
subscription.customTitle ?? ""
)
+ const [isEditingUrl, setIsEditingUrl] = useState(false)
+ const [editedUrl, setEditedUrl] = useState(subscription.feedUrl)
const [showUnsubscribeConfirm, setShowUnsubscribeConfirm] = useState(false)
const updateTitle = useUpdateSubscriptionTitle()
+ const updateFeedUrl = useUpdateFeedUrl()
const moveToFolder = useMoveSubscriptionToFolder()
const unsubscribe = useUnsubscribe()
const requestRefresh = useRequestFeedRefresh()
@@ -55,6 +59,19 @@ function SubscriptionRow({
setIsEditingTitle(false)
}
+ function handleSaveUrl() {
+ const trimmedUrl = editedUrl.trim()
+ if (!trimmedUrl || trimmedUrl === subscription.feedUrl) {
+ setIsEditingUrl(false)
+ return
+ }
+ updateFeedUrl.mutate({
+ feedIdentifier: subscription.feedIdentifier,
+ feedUrl: trimmedUrl,
+ })
+ setIsEditingUrl(false)
+ }
+
function handleFolderChange(folderIdentifier: string) {
const sourceFolder = folderOptions.find(
(folder) => folder.identifier === subscription.folderIdentifier
@@ -118,7 +135,49 @@ function SubscriptionRow({
</button>
</div>
)}
- <p className="truncate text-text-dim">{subscription.feedUrl}</p>
+ {isEditingUrl ? (
+ <div className="flex items-center gap-2">
+ <input
+ type="text"
+ value={editedUrl}
+ onChange={(event) => setEditedUrl(event.target.value)}
+ className="min-w-0 flex-1 border border-border bg-background-primary px-2 py-1 text-text-primary outline-none focus:border-text-dim"
+ onKeyDown={(event) => {
+ if (event.key === "Enter") handleSaveUrl()
+ if (event.key === "Escape") setIsEditingUrl(false)
+ }}
+ autoFocus
+ />
+ <button
+ onClick={handleSaveUrl}
+ className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary"
+ >
+ save
+ </button>
+ <button
+ onClick={() => {
+ setEditedUrl(subscription.feedUrl)
+ setIsEditingUrl(false)
+ }}
+ className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary"
+ >
+ cancel
+ </button>
+ </div>
+ ) : (
+ <div className="flex items-center gap-2">
+ <p className="min-w-0 truncate text-text-dim">{subscription.feedUrl}</p>
+ <button
+ onClick={() => {
+ setEditedUrl(subscription.feedUrl)
+ setIsEditingUrl(true)
+ }}
+ className="shrink-0 px-2 py-1 text-text-dim transition-colors hover:text-text-secondary"
+ >
+ edit url
+ </button>
+ </div>
+ )}
<div className="mt-1 flex flex-wrap gap-x-3 gap-y-0.5 text-text-dim">
<span>last fetched: {formatRelativeTime(subscription.lastFetchedAt)}</span>
<span>interval: {formatRefreshInterval(subscription.fetchIntervalSeconds)}</span>