diff options
| author | Fuwn <[email protected]> | 2026-02-08 00:18:30 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-08 00:18:30 -0800 |
| commit | e6cfa9ccc0b51e34690c3db38216daa659ae147f (patch) | |
| tree | d8b0a471fa86e08034ed412da1e163fae2263685 /apps/web/app/reader | |
| parent | feat: add option to show favicons next to feed names in entry list (diff) | |
| download | asa.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')
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(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/ /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> |