Skip to content

Commit

Permalink
feat: lists (#533)
Browse files Browse the repository at this point in the history
* feat: lists and feeds sub title in feed column

* feat: lists setting panel

* fix: imports

* fix: avatar align in profile setting

* fix: styles in settings

* feat: lists setting table

* feat: list and listsubscription types

* feat: target model

* fix: target model types errors

* feat: seperate feed list and list list

* feat: edit lists

* feat: lists feeds management

* feat: new feed column

* feat: use feedId for single feed entries request

* fix: lists feedId

* feat: disable invalid functions for lists

* feat: discover target selector

* feat: list follow modal

* feat: feed icon text size

* feat: use list get api

* feat: lists edit action

* fix: radio color

* fix: radio click area

* feat: list external page

* feat: hide mark all action for list

* feat: lists unread

* fix: types

* fix: add tooltip for resize panel

Signed-off-by: Innei <i@innei.in>

* feat: add resize indicator

Signed-off-by: Innei <i@innei.in>

* fix: hide section when no lists

Signed-off-by: Innei <i@innei.in>

---------

Signed-off-by: Innei <i@innei.in>
Co-authored-by: Innei <i@innei.in>
  • Loading branch information
DIYgod and Innei authored Sep 21, 2024
1 parent 23e428a commit ffcc6c9
Show file tree
Hide file tree
Showing 61 changed files with 2,355 additions and 646 deletions.
64 changes: 38 additions & 26 deletions apps/renderer/src/components/feed-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { forwardRef, useMemo } from "react"
import { getColorScheme, stringToHue } from "~/lib/color"
import { getImageProxyUrl } from "~/lib/img-proxy"
import { cn, getUrlIcon } from "~/lib/utils"
import type { CombinedEntryModel, FeedModel } from "~/models"
import type { CombinedEntryModel, FeedModel, TargetModel } from "~/models"

import { PlatformIcon } from "./ui/platform-icon"

Expand Down Expand Up @@ -75,7 +75,7 @@ export function FeedIcon({
siteUrl,
useMedia,
}: {
feed?: FeedModel
feed?: TargetModel
entry?: CombinedEntryModel["entries"]
fallbackUrl?: string
className?: string
Expand All @@ -98,8 +98,8 @@ export function FeedIcon({
}

const colors = useMemo(
() => getColorScheme(stringToHue(feed?.title || feed?.url || siteUrl!), true),
[feed?.title, feed?.url, siteUrl],
() => getColorScheme(stringToHue(feed?.title || (feed as FeedModel)?.url || siteUrl!), true),
[feed?.title, (feed as FeedModel)?.url, siteUrl],

Check warning on line 102 in apps/renderer/src/components/feed-icon.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (18.x)

React Hook useMemo has a missing dependency: 'feed'. Either include it or remove the dependency array

Check warning on line 102 in apps/renderer/src/components/feed-icon.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (18.x)

React Hook useMemo has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked
)
let ImageElement: ReactNode
let finalSrc = ""
Expand All @@ -109,6 +109,32 @@ export function FeedIcon({
height: size,
}

const fallbackIcon = (
<span
style={
{
...sizeStyle,
"--fo-light-background": colors.light.background,
"--fo-dark-background": colors.dark.background,
} as any
}
className={cn(
"flex shrink-0 items-center justify-center rounded-sm",
"bg-[var(--fo-light-background)] text-white dark:bg-[var(--fo-dark-background)]",
"mr-2",
className,
)}
>
<span
style={{
fontSize: size / 2,
}}
>
{!!feed?.title && feed.title[0]}
</span>
</span>
)

switch (true) {
case !feed && !!siteUrl: {
const [src] = getFeedIconSrc({
Expand Down Expand Up @@ -137,9 +163,9 @@ export function FeedIcon({
break
}
case !!fallbackUrl:
case !!feed?.siteUrl: {
case !!(feed as FeedModel)?.siteUrl: {
const [src, fallbackSrc] = getFeedIconSrc({
siteUrl: feed?.siteUrl || fallbackUrl,
siteUrl: (feed as FeedModel)?.siteUrl || fallbackUrl,
fallback,
proxy: {
width: size * 2,
Expand All @@ -150,7 +176,7 @@ export function FeedIcon({

ImageElement = (
<PlatformIcon
url={feed?.siteUrl || fallbackUrl}
url={(feed as FeedModel)?.siteUrl || fallbackUrl}
style={sizeStyle}
className={cn("center mr-2", className)}
>
Expand All @@ -163,6 +189,10 @@ export function FeedIcon({
)
break
}
case !!feed?.title && !!feed.title[0]: {
ImageElement = fallbackIcon
break
}
default: {
ImageElement = <i className="i-mgc-link-cute-re mr-2 shrink-0" style={sizeStyle} />
break
Expand All @@ -183,25 +213,7 @@ export function FeedIcon({
>
{ImageElement}
</AvatarImage>
<AvatarFallback asChild>
<span
style={
{
...sizeStyle,
"--fo-light-background": colors.light.background,
"--fo-dark-background": colors.dark.background,
} as any
}
className={cn(
"flex shrink-0 items-center justify-center rounded-sm",
"bg-[var(--fo-light-background)] text-white dark:bg-[var(--fo-dark-background)]",
"mr-2",
className,
)}
>
<span className="text-[9px]">{!!feed?.title && feed.title[0]}</span>
</span>
</AvatarFallback>
<AvatarFallback asChild>{fallbackIcon}</AvatarFallback>
</Avatar>
)
}
Expand Down
18 changes: 12 additions & 6 deletions apps/renderer/src/components/feed-summary.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { WEB_URL } from "@follow/shared/constants"

import { FeedIcon } from "~/components/feed-icon"
import { cn } from "~/lib/utils"
import type { FeedModel } from "~/models"
import type { TargetModel } from "~/models"

import { FeedCertification } from "./feed-certification"
import { EllipsisHorizontalTextWithTooltip } from "./ui/typography"
Expand All @@ -10,14 +12,14 @@ export function FollowSummary({
docs,
className,
}: {
feed: FeedModel
feed: TargetModel
docs?: string
className?: string
}) {
return (
<div className={cn("flex select-text flex-col gap-2 text-sm", className)}>
<a
href={feed.siteUrl || void 0}
href={feed.type === "feed" ? feed.siteUrl || void 0 : `${WEB_URL}/list/${feed.id}`}
target="_blank"
className="flex items-center"
rel="noreferrer"
Expand All @@ -31,7 +33,7 @@ export function FollowSummary({
<div className="truncate text-base font-semibold leading-tight">
<div className="flex items-center">
{feed.title}
<FeedCertification className="center" feed={feed} />
{feed.type === "feed" && <FeedCertification className="center" feed={feed} />}
</div>
<EllipsisHorizontalTextWithTooltip className="truncate text-xs font-normal text-zinc-500">
{feed.description}
Expand All @@ -40,8 +42,12 @@ export function FollowSummary({
</a>
<div className="flex items-center gap-1 truncate text-zinc-500">
<i className="i-mgc-right-cute-re shrink-0" />
<a href={feed.url || docs} target="_blank" rel="noreferrer">
{feed.url || docs}
<a
href={feed.type === "feed" ? feed.url || docs : `${WEB_URL}/list/${feed.id}`}
target="_blank"
rel="noreferrer"
>
{feed.type === "feed" ? feed.url || docs : `list:${feed.id}`}
</a>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ const safeUrl = (url: string, baseUrl: string) => {
export const MarkdownLink = (props: LinkProps) => {
const { view, feedId } = useEntryContentContext()

const feedSiteUrl = useFeedByIdSelector(feedId, (feed) => feed?.siteUrl)
const feedSiteUrl = useFeedByIdSelector(feedId, (feed) =>
"siteUrl" in feed ? feed.siteUrl : undefined,
)

const populatedFullHref = useMemo(() => {
const { href } = props
Expand Down
9 changes: 6 additions & 3 deletions apps/renderer/src/components/ui/radio-group/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,24 @@ export const Radio: FC<
onChange?.(e)
})
return (
<div className={cn("flex items-center gap-2", wrapperClassName)}>
<div className={cn("flex items-center", wrapperClassName)}>
<input
{...rest}
type="radio"
id={id ?? fallbackId}
className={cn(
"radio radio-accent radio-sm disabled:radio-current disabled:cursor-not-allowed disabled:text-theme-disabled",
"radio radio-accent radio-sm accent-accent disabled:radio-current disabled:cursor-not-allowed disabled:text-theme-disabled",
className,
)}
value={value}
checked={ctxValue === value}
onChange={handleChange}
/>

<label className={cn(rest.disabled ? "text-theme-disabled" : "")} htmlFor={id ?? fallbackId}>
<label
className={cn(rest.disabled ? "text-theme-disabled" : "", "pl-2")}
htmlFor={id ?? fallbackId}
>
{label}
</label>
</div>
Expand Down
4 changes: 2 additions & 2 deletions apps/renderer/src/database/schemas/feed.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { FeedModel } from "~/models"
import type { TargetModel } from "~/models"

export type DB_FeedUnread = {
id: string
count: number
}

export type DB_Feed = FeedModel & { id: string }
export type DB_Feed = TargetModel & { id: string }
92 changes: 64 additions & 28 deletions apps/renderer/src/hooks/biz/useFeedActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const useFeedActions = ({

const items = useMemo(() => {
if (!feed) return []
const isList = feed?.type === "list"
const items: NativeMenuItem[] = [
{
type: "text" as const,
Expand All @@ -46,7 +47,9 @@ export const useFeedActions = ({
click: () => {
present({
title: t("sidebar.feed_actions.edit_feed"),
content: ({ dismiss }) => <FeedForm asWidget id={feedId} onSuccess={dismiss} />,
content: ({ dismiss }) => (
<FeedForm asWidget id={feedId} onSuccess={dismiss} isList={isList} />
),
})
},
},
Expand All @@ -60,7 +63,11 @@ export const useFeedActions = ({
},
{
type: "text" as const,
label: t("sidebar.feed_actions.navigate_to_feed"),
label: t(
isList
? "sidebar.feed_actions.navigate_to_list"
: "sidebar.feed_actions.navigate_to_feed",
),
shortcut: "Meta+G",
disabled: !isEntryList || getRouteParams().feedId === feedId,
click: () => {
Expand All @@ -71,14 +78,18 @@ export const useFeedActions = ({
type: "separator" as const,
disabled: isEntryList,
},
{
type: "text",
label: t("sidebar.feed_actions.mark_all_as_read"),
shortcut: "Meta+Shift+A",
disabled: isEntryList,
click: () => subscriptionActions.markReadByFeedIds([feedId]),
},
...(!feed.ownerUserId && !!feed.id
...(!isList
? [
{
type: "text" as const,
label: t("sidebar.feed_actions.mark_all_as_read"),
shortcut: "Meta+Shift+A",
disabled: isEntryList,
click: () => subscriptionActions.markReadByFeedIds({ feedIds: [feedId] }),
},
]
: []),
...(!feed.ownerUserId && !!feed.id && !isList
? [
{
type: "text" as const,
Expand All @@ -96,7 +107,11 @@ export const useFeedActions = ({
? [
{
type: "text" as const,
label: t("sidebar.feed_actions.feed_owned_by_you"),
label: t(
isList
? "sidebar.feed_actions.list_owned_by_you"
: "sidebar.feed_actions.feed_owned_by_you",
),
},
]
: []),
Expand All @@ -106,37 +121,58 @@ export const useFeedActions = ({
},
{
type: "text" as const,
label: t("sidebar.feed_actions.open_feed_in_browser"),
label: t(
isList
? "sidebar.feed_actions.open_list_in_browser"
: "sidebar.feed_actions.open_feed_in_browser",
),
disabled: isEntryList,
shortcut: "O",
click: () => window.open(`${WEB_URL}/feed/${feedId}?view=${view}`, "_blank"),
},
{
type: "text" as const,
label: t("sidebar.feed_actions.open_site_in_browser"),
shortcut: "Meta+O",
disabled: isEntryList,
click: () => {
const feed = getFeedById(feedId)
if (feed) {
feed.siteUrl && window.open(feed.siteUrl, "_blank")
}
},
click: () =>
window.open(
isList
? `${WEB_URL}/list/${feedId}?view=${view}`
: `${WEB_URL}/feed/${feedId}?view=${view}`,
"_blank",
),
},
...(!isList
? [
{
type: "text" as const,
label: t("sidebar.feed_actions.open_site_in_browser"),
shortcut: "Meta+O",
disabled: isEntryList,
click: () => {
const feed = getFeedById(feedId)
if (feed) {
"siteUrl" in feed && feed.siteUrl && window.open(feed.siteUrl, "_blank")
}
},
},
]
: []),
{
type: "separator",
disabled: isEntryList,
},
{
type: "text" as const,
label: t("sidebar.feed_actions.copy_feed_url"),
label: t(
isList ? "sidebar.feed_actions.copy_list_url" : "sidebar.feed_actions.copy_feed_url",
),
disabled: isEntryList,
shortcut: "Meta+C",
click: () => navigator.clipboard.writeText(feed.url),
click: () => {
const url = isList ? `${WEB_URL}/list/${feedId}?view=${view}` : feed.url
navigator.clipboard.writeText(url)
},
},
{
type: "text" as const,
label: t("sidebar.feed_actions.copy_feed_id"),
label: t(
isList ? "sidebar.feed_actions.copy_list_id" : "sidebar.feed_actions.copy_feed_id",
),
shortcut: "Meta+Shift+C",
disabled: isEntryList,
click: () => {
Expand Down
3 changes: 2 additions & 1 deletion apps/renderer/src/hooks/biz/useSubscriptionActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export const useDeleteSubscription = ({ onSuccess }: { onSuccess?: () => void })
// TODO store action
await apiClient.subscriptions.$post({
json: {
url: feed.url,
url: feed.type === "feed" ? feed.url : undefined,
listId: feed.type === "list" ? feed.id : undefined,
view: subscription.view,
category: subscription.category,
isPrivate: subscription.isPrivate,
Expand Down
6 changes: 5 additions & 1 deletion apps/renderer/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export function getEntriesParams({ id, view }: { id?: number | string; view?: nu
if (id === FEED_COLLECTION_LIST) {
params.isCollection = true
} else if (id && id !== ROUTE_FEED_PENDING) {
params.feedIdList = `${id}`.split(",")
if (id.toString().includes(",")) {
params.feedIdList = `${id}`.split(",")
} else {
params.feedId = `${id}`
}
}
if (view === FeedViewType.SocialMedia) {
params.withContent = true
Expand Down
Loading

0 comments on commit ffcc6c9

Please sign in to comment.