refactor: split list store from feed store (#654)

* init

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

* update

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

* fix: type

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

* chore: auto-fix linting and formatting issues

* refactor: list

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

* chore: auto-fix linting and formatting issues

* feat: hydrate list data

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

* chore: auto-fix linting and formatting issues

* feat: context feed to list action

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

* update

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

* add tooltip

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

* cetner

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

---------

Signed-off-by: Innei <i@innei.in>
Co-authored-by: Innei <Innei@users.noreply.github.com>
This commit is contained in:
Innei 2024-10-01 22:30:25 +08:00 committed by GitHub
parent 16938f6078
commit 5ef516f0eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 958 additions and 554 deletions

View File

@ -52,7 +52,6 @@
"@types/node": "^22.7.4",
"electron": "32.1.2",
"electron-devtools-installer": "3.2.0",
"hono": "4.6.3",
"typescript": "^5.6.2"
"hono": "4.6.3"
}
}

View File

@ -119,7 +119,6 @@
"fake-indexeddb": "6.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.6.2",
"vitest": "2.0"
}
}

View File

@ -106,8 +106,8 @@ const ContextMenuCheckboxItem = React.forwardRef<
{...props}
>
<span className="absolute left-2 flex items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<i className="i-mgc-check-filled size-4" />
<ContextMenuPrimitive.ItemIndicator asChild>
<i className="i-mgc-check-filled size-3" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}

View File

@ -2,10 +2,11 @@ import type { Transaction } from "dexie"
import Dexie from "dexie"
import { LOCAL_DB_NAME } from "./constants"
import { dbSchemaV1, dbSchemaV2, dbSchemaV3, dbSchemaV4, dbSchemaV5 } from "./db_schema"
import { dbSchemaV1, dbSchemaV2, dbSchemaV3, dbSchemaV4, dbSchemaV5, dbSchemaV6 } from "./db_schema"
import type { DB_Cleaner } from "./schemas/cleaner"
import type { DB_Entry, DB_EntryRelated } from "./schemas/entry"
import type { DB_Feed, DB_FeedUnread } from "./schemas/feed"
import type { DB_List } from "./schemas/list"
import type { DB_Subscription } from "./schemas/subscription"
export interface LocalDBSchemaMap {
@ -15,6 +16,7 @@ export interface LocalDBSchemaMap {
entryRelated: DB_EntryRelated
feedUnreads: DB_FeedUnread
cleaner: DB_Cleaner
lists: DB_List
}
// Define a local DB
@ -24,6 +26,7 @@ export class BrowserDB extends Dexie {
public subscriptions: BrowserDBTable<"subscriptions">
public entryRelated: BrowserDBTable<"entryRelated">
public feedUnreads: BrowserDBTable<"feedUnreads">
public lists: BrowserDBTable<"lists">
public cleaner: BrowserDBTable<"cleaner">
constructor() {
@ -33,6 +36,7 @@ export class BrowserDB extends Dexie {
this.version(3).stores(dbSchemaV3)
this.version(4).stores(dbSchemaV4)
this.version(5).stores(dbSchemaV5)
this.version(6).stores(dbSchemaV6)
this.entries = this.table("entries")
this.feeds = this.table("feeds")
@ -40,6 +44,7 @@ export class BrowserDB extends Dexie {
this.entryRelated = this.table("entryRelated")
this.feedUnreads = this.table("feedUnreads")
this.cleaner = this.table("cleaner")
this.lists = this.table("lists")
}
async upgradeToV2(trans: Transaction) {

View File

@ -28,3 +28,8 @@ export const dbSchemaV5 = {
...dbSchemaV4,
cleaner: "&refId, visitedAt",
}
export const dbSchemaV6 = {
...dbSchemaV5,
lists: "&id, title",
}

View File

@ -1,8 +1,8 @@
import type { FeedOrListRespModel } from "~/models"
import type { FeedModel } from "~/models"
export type DB_FeedUnread = {
id: string
count: number
}
export type DB_Feed = FeedOrListRespModel & { id: string }
export type DB_Feed = FeedModel & { id: string }

View File

@ -0,0 +1,12 @@
export type DB_List = {
id: string
title: string
createdAt: number
updatedAt: number
description: string
fee: number
image: string
ownerUserId: string
timelineUpdatedAt: string
feedIds: string[]
}

View File

@ -4,14 +4,19 @@ import { useTranslation } from "react-i18next"
import { whoami } from "~/atoms/user"
import { useModalStack } from "~/components/ui/modal"
import type { FeedViewType } from "~/lib/enum"
import type { NativeMenuItem } from "~/lib/native-menu"
import { useFeedClaimModal } from "~/modules/claim"
import { FeedForm } from "~/modules/discover/feed-form"
import { Queries } from "~/queries"
import { getFeedById, useAddFeedToFeedList, useFeedById } from "~/store/feed"
import {
getFeedById,
useAddFeedToFeedList,
useFeedById,
useRemoveFeedFromFeedList,
} from "~/store/feed"
import { useListById, useListByView } from "~/store/list"
import { subscriptionActions, useSubscriptionByFeedId } from "~/store/subscription"
import { useAuthQuery } from "../common"
import { useNavigateEntry } from "./useNavigateEntry"
import { getRouteParams } from "./useRouteParams"
import { useDeleteSubscription } from "./useSubscriptionActions"
@ -38,25 +43,23 @@ export const useFeedActions = ({
const navigateEntry = useNavigateEntry()
const isEntryList = type === "entryList"
const listList = useAuthQuery(Queries.lists.list())
const addMutation = useAddFeedToFeedList()
const removeMutation = useRemoveFeedFromFeedList()
const listByView = useListByView(view!)
const items = useMemo(() => {
if (!feed) return []
const isList = feed?.type === "list"
const items: NativeMenuItem[] = [
...(!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,
label: t("sidebar.feed_actions.mark_all_as_read"),
shortcut: "Meta+Shift+A",
disabled: isEntryList,
click: () => subscriptionActions.markReadByFeedIds({ feedIds: [feedId] }),
},
...(!feed.ownerUserId && !!feed.id && !false
? [
{
type: "text" as const,
@ -74,11 +77,7 @@ export const useFeedActions = ({
? [
{
type: "text" as const,
label: t(
isList
? "sidebar.feed_actions.list_owned_by_you"
: "sidebar.feed_actions.feed_owned_by_you",
),
label: t("sidebar.feed_actions.feed_owned_by_you"),
},
]
: []),
@ -86,41 +85,44 @@ export const useFeedActions = ({
type: "separator" as const,
disabled: isEntryList,
},
...(!isList
? [
{
type: "text" as const,
label: t("sidebar.feed_column.context_menu.add_feeds_to_list"),
enabled: !!listList.data?.length,
submenu: listList.data?.map((list) => ({
label: list.title || "",
type: "text" as const,
click() {
return addMutation.mutate({
feedId,
listId: list.id,
})
},
})),
{
type: "text" as const,
label: t("sidebar.feed_column.context_menu.add_feeds_to_list"),
enabled: !!listByView?.length,
submenu: listByView?.map((list) => {
const isIncluded = list.feedIds.includes(feedId)
return {
label: list.title || "",
type: "text" as const,
checked: isIncluded,
click() {
if (!isIncluded) {
addMutation.mutate({
feedId,
listId: list.id,
})
} else {
removeMutation.mutate({
feedId,
listId: list.id,
})
}
},
{
type: "separator" as const,
disabled: isEntryList,
},
]
: []),
}
}),
},
{
type: "separator" as const,
disabled: isEntryList,
},
{
type: "text" as const,
label: isEntryList ? t("sidebar.feed_actions.edit_feed") : t("sidebar.feed_actions.edit"),
shortcut: "E",
click: () => {
present({
title: isList
? t("sidebar.feed_actions.edit_list")
: t("sidebar.feed_actions.edit_feed"),
content: ({ dismiss }) => (
<FeedForm asWidget id={feedId} onSuccess={dismiss} isList={isList} />
),
title: t("sidebar.feed_actions.edit_feed"),
content: ({ dismiss }) => <FeedForm asWidget id={feedId} onSuccess={dismiss} />,
})
},
},
@ -134,11 +136,7 @@ export const useFeedActions = ({
},
{
type: "text" as const,
label: t(
isList
? "sidebar.feed_actions.navigate_to_list"
: "sidebar.feed_actions.navigate_to_feed",
),
label: t("sidebar.feed_actions.navigate_to_feed"),
shortcut: "Meta+G",
disabled: !isEntryList || getRouteParams().feedId === feedId,
click: () => {
@ -151,61 +149,46 @@ export const useFeedActions = ({
},
{
type: "text" as const,
label: t(
isList
? "sidebar.feed_actions.open_list_in_browser"
: "sidebar.feed_actions.open_feed_in_browser",
{ which: t(window.electron ? "words.browser" : "words.newTab") },
),
label: t("sidebar.feed_actions.open_feed_in_browser", {
which: t(window.electron ? "words.browser" : "words.newTab"),
}),
disabled: isEntryList,
shortcut: "O",
click: () =>
window.open(
isList
? `${WEB_URL}/list/${feedId}?view=${view}`
: `${WEB_URL}/feed/${feedId}?view=${view}`,
"_blank",
),
click: () => window.open(`${WEB_URL}/feed/${feedId}?view=${view}`, "_blank"),
},
{
type: "text" as const,
label: t("sidebar.feed_actions.open_site_in_browser", {
which: t(window.electron ? "words.browser" : "words.newTab"),
}),
shortcut: "Meta+O",
disabled: isEntryList,
click: () => {
const feed = getFeedById(feedId)
if (feed) {
"siteUrl" in feed && feed.siteUrl && window.open(feed.siteUrl, "_blank")
}
},
},
...(!isList
? [
{
type: "text" as const,
label: t("sidebar.feed_actions.open_site_in_browser", {
which: t(window.electron ? "words.browser" : "words.newTab"),
}),
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(
isList ? "sidebar.feed_actions.copy_list_url" : "sidebar.feed_actions.copy_feed_url",
),
label: t("sidebar.feed_actions.copy_feed_url"),
disabled: isEntryList,
shortcut: "Meta+C",
click: () => {
const url = isList ? `${WEB_URL}/list/${feedId}?view=${view}` : feed.url
// @ts-expect-error
const { url } = feed || {}
if (!url) return
navigator.clipboard.writeText(url)
},
},
{
type: "text" as const,
label: t(
isList ? "sidebar.feed_actions.copy_list_id" : "sidebar.feed_actions.copy_feed_id",
),
label: t("sidebar.feed_actions.copy_feed_id"),
shortcut: "Meta+Shift+C",
disabled: isEntryList,
click: () => {
@ -216,17 +199,117 @@ export const useFeedActions = ({
return items
}, [
t,
claimFeed,
deleteSubscription,
feed,
feedId,
t,
isEntryList,
navigateEntry,
listByView,
feedId,
claimFeed,
addMutation,
present,
deleteSubscription,
subscription,
navigateEntry,
view,
])
return { items }
}
export const useListActions = ({ listId, view }: { listId: string; view: FeedViewType }) => {
const { t } = useTranslation()
const list = useListById(listId)
const subscription = useSubscriptionByFeedId(listId)
const { present } = useModalStack()
const deleteSubscription = useDeleteSubscription({})
const navigateEntry = useNavigateEntry()
const items = useMemo(() => {
if (!list) return []
const items: NativeMenuItem[] = [
...(list.ownerUserId === whoami()?.id
? [
{
type: "text" as const,
label: t("sidebar.feed_actions.list_owned_by_you"),
},
]
: []),
{
type: "separator" as const,
disabled: false,
},
{
type: "text" as const,
label: t("sidebar.feed_actions.edit"),
shortcut: "E",
click: () => {
present({
title: t("sidebar.feed_actions.edit_list"),
content: ({ dismiss }) => <FeedForm asWidget id={listId} onSuccess={dismiss} isList />,
})
},
},
{
type: "text" as const,
label: t("sidebar.feed_actions.unfollow"),
shortcut: "Meta+Backspace",
click: () => deleteSubscription.mutate(subscription),
},
{
type: "text" as const,
label: t("sidebar.feed_actions.navigate_to_list"),
shortcut: "Meta+G",
disabled: getRouteParams().feedId === listId,
click: () => {
navigateEntry({ feedId: listId })
},
},
{
type: "separator" as const,
disabled: false,
},
{
type: "text" as const,
label: t("sidebar.feed_actions.open_list_in_browser", {
which: t(window.electron ? "words.browser" : "words.newTab"),
}),
disabled: false,
shortcut: "O",
click: () => window.open(`${WEB_URL}/list/${listId}?view=${view}`, "_blank"),
},
{
type: "separator",
disabled: false,
},
{
type: "text" as const,
label: t("sidebar.feed_actions.copy_list_url"),
disabled: false,
shortcut: "Meta+C",
click: () => {
const url = `${WEB_URL}/list/${listId}?view=${view}`
navigator.clipboard.writeText(url)
},
},
{
type: "text" as const,
label: t("sidebar.feed_actions.copy_list_id"),
shortcut: "Meta+Shift+C",
disabled: false,
click: () => {
navigator.clipboard.writeText(listId)
},
},
]
return items
}, [list, t, present, deleteSubscription, subscription, navigateEntry, listId, view])
return { items }
}

View File

@ -11,7 +11,9 @@ import {
FeedUnreadService,
SubscriptionService,
} from "~/services"
import { ListService } from "~/services/list"
import type { FlatEntryModel } from "~/store/entry"
import { listActions } from "~/store/list"
import { entryActions, useEntryStore } from "../store/entry/store"
import { feedActions, useFeedStore } from "../store/feed"
@ -25,7 +27,13 @@ export const setHydrated = (v: boolean) => {
export const hydrateDatabaseToStore = async () => {
async function hydrate() {
const now = Date.now()
await Promise.all([hydrateFeed(), hydrateSubscription(), hydrateFeedUnread(), hydrateEntry()])
await Promise.all([
hydrateFeed(),
hydrateSubscription(),
hydrateFeedUnread(),
hydrateEntry(),
hydrateList(),
])
window.__dbIsReady = true
const costTime = Date.now() - now
@ -89,6 +97,11 @@ async function hydrateSubscription() {
subscriptionActions.upsertMany(subscriptions)
}
async function hydrateList() {
const lists = await ListService.findAll()
listActions.upsertMany(lists)
}
const logHydrateError = (message: string) => {
// eslint-disable-next-line no-console
console.debug(`Hydrate error: ${message}, maybe local database data is dirty.`)

View File

@ -14,6 +14,7 @@ export type NativeMenuItem =
shortcut?: string
disabled?: boolean
submenu?: NativeMenuItem[]
checked?: boolean
}
| { type: "separator"; disabled?: boolean }

View File

@ -25,7 +25,7 @@ export type TransactionModel = ExtractBizResponse<
export type FeedModel = ExtractBizResponse<typeof apiClient.feeds.$get>["data"]["feed"]
type ListModelPoplutedFeeds = ExtractBizResponse<typeof apiClient.lists.$get>["data"]["list"]
export type ListModelPoplutedFeeds = ExtractBizResponse<typeof apiClient.lists.$get>["data"]["list"]
export type ListModel = Omit<ListModelPoplutedFeeds, "feeds">
export type FeedOrListRespModel = FeedModel | ListModelPoplutedFeeds
export type FeedOrListModel = FeedModel | ListModel

View File

@ -10,13 +10,13 @@ import { LoadingCircle } from "~/components/ui/loading"
import { ROUTE_FEED_IN_FOLDER, views } from "~/constants"
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
import { getRouteParams, useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
import { useAnyPointDown, useAuthQuery, useInputComposition } from "~/hooks/common"
import { useAnyPointDown, useInputComposition } from "~/hooks/common"
import { stopPropagation } from "~/lib/dom"
import type { FeedViewType } from "~/lib/enum"
import { showNativeMenu } from "~/lib/native-menu"
import { cn, sortByAlphabet } from "~/lib/utils"
import { Queries } from "~/queries"
import { getPreferredTitle, useAddFeedToFeedList, useFeedStore } from "~/store/feed"
import { useListByView } from "~/store/list"
import { subscriptionActions, useSubscriptionByFeedId } from "~/store/subscription"
import { useFeedUnreadStore } from "~/store/unread"
@ -46,29 +46,21 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
const folderName = subscription?.category || subscription.defaultCategory
const showCollapse = sortByUnreadFeedList.length > 1 || subscription?.category
const open = folderName ? categoryOpenStateData[folderName] : true
const shouldOpen =
useRouteParamsSelector((s) => typeof s.feedId === "string" && ids.includes(s.feedId)) ||
ids.length === 1
const itemsRef = useRef<HTMLDivElement>(null)
const toggleCategoryOpenState = (e) => {
e.stopPropagation()
if (!isCategoryEditing) {
setCategoryActive()
const [open, setOpen] = useState(() => {
if (!showCollapse) return true
if (folderName && typeof categoryOpenStateData[folderName] === "boolean") {
return categoryOpenStateData[folderName]
}
if (view !== undefined && folderName) {
subscriptionActions.toggleCategoryOpenState(view, folderName)
}
}
return false
})
const shouldOpen = useRouteParamsSelector(
(s) => typeof s.feedId === "string" && ids.includes(s.feedId),
)
useEffect(() => {
if (shouldOpen) {
if (!open && view !== undefined && folderName) {
subscriptionActions.changeCategoryOpenState(view, folderName, true)
}
setOpen(true)
const $items = itemsRef.current
@ -78,13 +70,31 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
behavior: "smooth",
})
}
}, [shouldOpen, open, view, folderName])
}, [shouldOpen])
const expansion = folderName ? categoryOpenStateData[folderName] : true
useEffect(() => {
if (showCollapse) {
setOpen(expansion)
}
}, [expansion])
const itemsRef = useRef<HTMLDivElement>(null)
const toggleCategoryOpenState = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
if (!isCategoryEditing) {
setCategoryActive()
}
if (view !== undefined && folderName) {
subscriptionActions.toggleCategoryOpenState(view, folderName)
}
}
const setCategoryActive = () => {
if (view !== undefined) {
navigate({
entryId: null,
// TODO joint feedId is too long, need to be optimized
folderName,
view,
})
@ -117,9 +127,10 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
})
const isCategoryIsWaiting = isChangePending
const listList = useAuthQuery(Queries.lists.list())
const addMutation = useAddFeedToFeedList()
const listList = useListByView(view!)
return (
<div tabIndex={-1} onClick={stopPropagation}>
{!!showCollapse && (
@ -151,8 +162,8 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
{
type: "text",
label: t("sidebar.feed_column.context_menu.add_feeds_to_list"),
enabled: !!listList.data?.length,
submenu: listList.data?.map((list) => ({
enabled: !!listList?.length,
submenu: listList?.map((list) => ({
label: list.title || "",
type: "text",
click() {
@ -172,7 +183,7 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
.filter((v) => v.view !== view)
.map((v) => ({
label: t(v.name),
type: "text",
type: "text" as const,
shortcut: (v.view + 1).toString(),
icon: v.icon,
click() {

View File

@ -8,15 +8,18 @@ import { FeedCertification } from "~/components/feed-certification"
import { FeedIcon } from "~/components/feed-icon"
import { OouiUserAnonymous } from "~/components/icons/OouiUserAnonymous"
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "~/components/ui/tooltip"
import { useFeedActions } from "~/hooks/biz/useFeedActions"
import { EllipsisHorizontalTextWithTooltip } from "~/components/ui/typography"
import { useFeedActions, useListActions } from "~/hooks/biz/useFeedActions"
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
import { useAnyPointDown } from "~/hooks/common"
import { nextFrame } from "~/lib/dom"
import type { FeedViewType } from "~/lib/enum"
import { getNewIssueUrl } from "~/lib/issues"
import { showNativeMenu } from "~/lib/native-menu"
import { cn } from "~/lib/utils"
import { getPreferredTitle, useFeedById } from "~/store/feed"
import { useListById } from "~/store/list"
import { subscriptionActions, useSubscriptionByFeedId } from "~/store/subscription"
import { useFeedUnreadStore } from "~/store/unread"
@ -68,7 +71,7 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => {
if (!feed) return null
const isFeed = feed.type === "feed" || !feed.type
const isList = feed.type === "list"
return (
<>
<div
@ -118,7 +121,7 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => {
isFeed && feed.errorAt && "text-red-900 dark:text-red-500",
)}
>
<FeedIcon fallback feed={feed} size={isList ? 28 : 16} />
<FeedIcon fallback feed={feed} size={16} />
<div className="truncate">{getPreferredTitle(feed)}</div>
{isFeed && <FeedCertification feed={feed} className="text-[15px]" />}
{isFeed && feed.errorAt && (
@ -156,10 +159,84 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => {
</Tooltip>
)}
</div>
<UnreadNumber unread={feedUnread} className="ml-2" isList={isList} />
<UnreadNumber unread={feedUnread} className="ml-2" />
</div>
</>
)
}
export const FeedItem = memo(FeedItemImpl)
const ListItemImpl: Component<{
listId: string
view: FeedViewType
}> = ({ view, listId, className }) => {
const list = useListById(listId)
const isActive = useRouteParamsSelector((routerParams) => routerParams.feedId === listId)
const { items } = useListActions({ listId, view })
const listUnread = useFeedUnreadStore((state) => state.data[listId] || 0)
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false)
useAnyPointDown(() => {
setIsContextMenuOpen(false)
})
const subscription = useSubscriptionByFeedId(listId)
const navigate = useNavigateEntry()
const handleNavigate = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
navigate({
feedId: listId,
entryId: null,
view,
})
},
[listId, navigate, view],
)
const { t } = useTranslation()
if (!list) return null
return (
<div
data-list-id={listId}
className={cn(
"flex w-full cursor-menu items-center justify-between rounded-md pr-2.5 text-sm font-medium leading-loose",
(isActive || isContextMenuOpen) && "bg-native-active",
"py-1.5 pl-2.5",
className,
)}
onClick={handleNavigate}
onDoubleClick={() => {
window.open(`${WEB_URL}/list/${listId}?view=${view}`, "_blank")
}}
onContextMenu={(e) => {
setIsContextMenuOpen(true)
showNativeMenu(items, e)
}}
>
<div className={"flex min-w-0 items-center"}>
<FeedIcon fallback feed={list} size={28} />
<EllipsisHorizontalTextWithTooltip className="truncate">
{list.title}
</EllipsisHorizontalTextWithTooltip>
{subscription.isPrivate && (
<Tooltip delayDuration={300}>
<TooltipTrigger>
<OouiUserAnonymous className="ml-1 shrink-0 text-base" />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{t("feed_item.not_publicly_visible")}</TooltipContent>
</TooltipPortal>
</Tooltip>
)}
</div>
<UnreadNumber unread={listUnread} isList className="ml-2" />
</div>
)
}
export const ListItem = memo(ListItemImpl)

View File

@ -1,6 +1,6 @@
import * as HoverCard from "@radix-ui/react-hover-card"
import { AnimatePresence, m } from "framer-motion"
import { Fragment, memo, useMemo, useState } from "react"
import { memo, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import { Link } from "react-router-dom"
@ -12,25 +12,17 @@ import { useRouteFeedId } from "~/hooks/biz/useRouteParams"
import { useAuthQuery } from "~/hooks/common"
import { stopPropagation } from "~/lib/dom"
import type { FeedViewType } from "~/lib/enum"
import { cn, sortByAlphabet } from "~/lib/utils"
import { cn } from "~/lib/utils"
import { Queries } from "~/queries"
import { getPreferredTitle, useFeedStore } from "~/store/feed"
import {
getSubscriptionByFeedId,
subscriptionActions,
useCategoryOpenStateByView,
useSubscriptionByView,
} from "~/store/subscription"
import { useFeedUnreadStore } from "~/store/unread"
import {
getFeedListSort,
setFeedListSortBy,
setFeedListSortOrder,
useFeedListSort,
useFeedListSortSelector,
} from "./atom"
import { FeedCategory } from "./category"
import { getFeedListSort, setFeedListSortBy, setFeedListSortOrder, useFeedListSort } from "./atom"
import { SortableFeedList, SortByAlphabeticalList } from "./sort-by"
import { UnreadNumber } from "./unread-number"
const useFeedsGroupedData = (view: FeedViewType) => {
@ -108,6 +100,10 @@ function FeedListImpl({ className, view }: { className?: string; view: number })
const { t } = useTranslation()
// Data prefetch
useAuthQuery(Queries.lists.list())
const hasListData = Object.keys(listsData).length > 0
return (
<div className={cn(className, "font-medium")}>
<div onClick={stopPropagation} className="mx-3 flex items-center justify-between px-2.5 py-1">
@ -163,29 +159,27 @@ function FeedListImpl({ className, view }: { className?: string; view: number })
<i className="i-mgc-star-cute-fi size-4 -translate-y-px text-amber-500" />
{t("words.starred")}
</div>
{Object.keys(listsData).length > 0 && (
{hasListData && (
<>
<div className="mt-1 flex h-6 w-full shrink-0 items-center rounded-md px-2.5 text-xs font-semibold text-theme-vibrancyFg transition-colors">
{t("words.lists")}
</div>
<SortableList
view={view}
data={listsData}
categoryOpenStateData={categoryOpenStateData}
by="alphabetical"
/>
<SortByAlphabeticalList view={view} />
</>
)}
<div
className={cn(
"flex h-6 w-full shrink-0 items-center rounded-md px-2.5 text-xs font-semibold text-theme-vibrancyFg transition-colors",
Object.keys(feedsData).length === 0 ? "mt-0" : "mt-1",
)}
>
{t("words.feeds")}
</div>
{hasListData && hasData && (
<div
className={cn(
"mb-1 flex h-6 w-full shrink-0 items-center rounded-md px-2.5 text-xs font-semibold text-theme-vibrancyFg transition-colors",
Object.keys(feedsData).length === 0 ? "mt-0" : "mt-1",
)}
>
{t("words.feeds")}
</div>
)}
{hasData ? (
<SortableList
<SortableFeedList
view={view}
data={feedsData}
categoryOpenStateData={categoryOpenStateData}
@ -297,109 +291,4 @@ const SortButton = () => {
)
}
type FeedListProps = {
view: number
data: Record<string, string[]>
categoryOpenStateData: Record<string, boolean>
}
const SortByUnreadList = ({ view, data, categoryOpenStateData }: FeedListProps) => {
const isDesc = useFeedListSortSelector((s) => s.order === "desc")
const sortedByUnread = useFeedUnreadStore((state) => {
const sortedList = [] as [string, string[]][]
const folderUnread = {} as Record<string, number>
// Calc total unread count for each folder
for (const category in data) {
folderUnread[category] = data[category].reduce((acc, cur) => (state.data[cur] || 0) + acc, 0)
}
// Sort by unread count
Object.keys(folderUnread)
.sort((a, b) => folderUnread[b] - folderUnread[a])
.forEach((key) => {
sortedList.push([key, data[key]])
})
if (!isDesc) {
sortedList.reverse()
}
return sortedList
})
return (
<Fragment>
{sortedByUnread?.map(([category, ids]) => (
<FeedCategory
key={category}
data={ids}
view={view}
categoryOpenStateData={categoryOpenStateData}
/>
))}
</Fragment>
)
}
const SortByAlphabeticalList = ({ view, data, categoryOpenStateData }: FeedListProps) => {
const categoryName2RealDisplayNameMap = useFeedStore((state) => {
const map = {} as Record<string, string>
for (const categoryName in data) {
const feedId = data[categoryName][0]
if (!feedId) {
continue
}
const feed = state.feeds[feedId]
if (!feed) {
continue
}
const hascategoryNameNotDefault = !!getSubscriptionByFeedId(feedId)?.category
const isSingle = data[categoryName].length === 1
if (!isSingle || hascategoryNameNotDefault) {
map[categoryName] = categoryName
} else {
map[categoryName] = getPreferredTitle(feed)!
}
}
return map
})
const isDesc = useFeedListSortSelector((s) => s.order === "desc")
let sortedByAlphabetical = Object.keys(data).sort((a, b) => {
const nameA = categoryName2RealDisplayNameMap[a]
const nameB = categoryName2RealDisplayNameMap[b]
return sortByAlphabet(nameA, nameB)
})
if (!isDesc) {
sortedByAlphabetical = sortedByAlphabetical.reverse()
}
return (
<Fragment>
{sortedByAlphabetical.map((category) => (
<FeedCategory
key={category}
data={data[category]}
view={view}
categoryOpenStateData={categoryOpenStateData}
/>
))}
</Fragment>
)
}
const SortableList = (props: FeedListProps & { by?: "count" | "alphabetical" }) => {
const userBy = useFeedListSortSelector((s) => s.by)
switch (props.by || userBy) {
case "count": {
return <SortByUnreadList {...props} />
}
case "alphabetical": {
return <SortByAlphabeticalList {...props} />
}
}
}
export const FeedList = memo(FeedListImpl)

View File

@ -0,0 +1,86 @@
import { Fragment, useMemo } from "react"
import { sortByAlphabet } from "~/lib/utils"
import { getPreferredTitle, useFeedStore } from "~/store/feed"
import { useListByView } from "~/store/list"
import { getSubscriptionByFeedId } from "~/store/subscription"
import { useFeedListSortSelector } from "../atom"
import { FeedCategory } from "../category"
import { ListItem } from "../item"
import type { FeedListProps, ListListProps } from "./types"
export const SortByAlphabeticalFeedList = ({
view,
data,
categoryOpenStateData,
}: FeedListProps) => {
const categoryName2RealDisplayNameMap = useFeedStore((state) => {
const map = {} as Record<string, string>
for (const categoryName in data) {
const feedId = data[categoryName][0]
if (!feedId) {
continue
}
const feed = state.feeds[feedId]
if (!feed) {
continue
}
const hascategoryNameNotDefault = !!getSubscriptionByFeedId(feedId)?.category
const isSingle = data[categoryName].length === 1
if (!isSingle || hascategoryNameNotDefault) {
map[categoryName] = categoryName
} else {
map[categoryName] = getPreferredTitle(feed)!
}
}
return map
})
const isDesc = useFeedListSortSelector((s) => s.order === "desc")
let sortedByAlphabetical = Object.keys(data).sort((a, b) => {
const nameA = categoryName2RealDisplayNameMap[a]
const nameB = categoryName2RealDisplayNameMap[b]
return sortByAlphabet(nameA, nameB)
})
if (!isDesc) {
sortedByAlphabetical = sortedByAlphabetical.reverse()
}
return (
<Fragment>
{sortedByAlphabetical.map((category) => (
<FeedCategory
key={category}
data={data[category]}
view={view}
categoryOpenStateData={categoryOpenStateData}
/>
))}
</Fragment>
)
}
export const SortByAlphabeticalListList = ({ view }: ListListProps) => {
const isDesc = useFeedListSortSelector((s) => s.order === "desc")
const lists = useListByView(view)
const sortedLists = useMemo(() => {
const res = lists.sort((a, b) => {
return sortByAlphabet(a.title ?? "", b.title ?? "")
})
return isDesc ? res.reverse() : res
}, [isDesc, lists])
return (
<div>
{sortedLists.map((list) => (
<ListItem key={list.id} listId={list.id} view={view} />
))}
</div>
)
}

View File

@ -0,0 +1,45 @@
import { Fragment } from "react"
import { useFeedUnreadStore } from "~/store/unread"
import { useFeedListSortSelector } from "../atom"
import { FeedCategory } from "../category"
import type { FeedListProps } from "./types"
export const SortByUnreadFeedList = ({ view, data, categoryOpenStateData }: FeedListProps) => {
const isDesc = useFeedListSortSelector((s) => s.order === "desc")
const sortedByUnread = useFeedUnreadStore((state) => {
const sortedList = [] as [string, string[]][]
const folderUnread = {} as Record<string, number>
// Calc total unread count for each folder
for (const category in data) {
folderUnread[category] = data[category].reduce((acc, cur) => (state.data[cur] || 0) + acc, 0)
}
// Sort by unread count
Object.keys(folderUnread)
.sort((a, b) => folderUnread[b] - folderUnread[a])
.forEach((key) => {
sortedList.push([key, data[key]])
})
if (!isDesc) {
sortedList.reverse()
}
return sortedList
})
return (
<Fragment>
{sortedByUnread?.map(([category, ids]) => (
<FeedCategory
key={category}
data={ids}
view={view}
categoryOpenStateData={categoryOpenStateData}
/>
))}
</Fragment>
)
}

View File

@ -0,0 +1,27 @@
import { useFeedListSortSelector } from "../atom"
import { SortByAlphabeticalFeedList, SortByAlphabeticalListList } from "./SortByAlphabeticalList"
import { SortByUnreadFeedList } from "./SortByUnreadList"
import type { FeedListProps, ListListProps } from "./types"
export const SortableFeedList = (props: FeedListProps) => {
const by = useFeedListSortSelector((s) => s.by)
switch (by) {
case "count": {
return <SortByUnreadFeedList {...props} />
}
case "alphabetical": {
return <SortByAlphabeticalFeedList {...props} />
}
}
}
export const SortByAlphabeticalList = (props: ListListProps) => {
const by = useFeedListSortSelector((s) => s.by)
switch (by) {
default: {
return <SortByAlphabeticalListList {...props} />
}
}
}

View File

@ -0,0 +1,12 @@
import type { FeedViewType } from "~/lib/enum"
export type FeedListProps = {
view: number
data: Record<string, string[]>
categoryOpenStateData: Record<string, boolean>
}
export type SortBy = "count" | "alphabetical"
export type ListListProps = {
view: FeedViewType
}

View File

@ -40,7 +40,7 @@ import { views } from "~/constants"
import { useAuthQuery, useI18n } from "~/hooks/common"
import { apiClient } from "~/lib/api-fetch"
import { cn, isBizId } from "~/lib/utils"
import type { FeedModel, ListModel } from "~/models"
import type { FeedModel } from "~/models"
import { ViewSelectorRadioGroup } from "~/modules/shared/ViewSelectorRadioGroup"
import { Balance } from "~/modules/wallet/balance"
import { Queries } from "~/queries"
@ -50,6 +50,7 @@ import {
useFeedById,
useRemoveFeedFromFeedList,
} from "~/store/feed"
import { useListById } from "~/store/list"
import { subscriptionActions, useSubscriptionStore } from "~/store/subscription"
export const SettingLists = () => {
@ -86,7 +87,9 @@ export const SettingLists = () => {
<TableHead size="sm">{t.settings("lists.fee.label")}</TableHead>
<TableHead size="sm">{t.settings("lists.subscriptions")}</TableHead>
<TableHead size="sm">{t.settings("lists.earnings")}</TableHead>
<TableHead size="sm">{t.common("words.actions")}</TableHead>
<TableHead size="sm" className="center">
{t.common("words.actions")}
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="border-t-[12px] border-transparent [&_td]:!px-3">
@ -107,9 +110,18 @@ export const SettingLists = () => {
</a>
</TableCell>
<TableCell size="sm">
<span className={cn("inline-flex items-center", views[row.view].className)}>
{views[row.view].icon}
</span>
<Tooltip>
<TooltipTrigger asChild>
<span
className={cn("inline-flex items-center", views[row.view].className)}
>
{views[row.view].icon}
</span>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{t(views[row.view].name)}</TooltipContent>
</TooltipPortal>
</Tooltip>
</TableCell>
<TableCell size="sm">
<div className="flex items-center gap-1">
@ -121,7 +133,7 @@ export const SettingLists = () => {
<TableCell size="sm">
<Balance>{BigInt(row.purchaseAmount || 0n)}</Balance>
</TableCell>
<TableCell size="sm">
<TableCell size="sm" className="center">
<Tooltip>
<TooltipTrigger asChild>
<Button
@ -189,7 +201,7 @@ const formSchema = z.object({
const ListCreationModalContent = ({ dismiss, id }: { dismiss: () => void; id?: string }) => {
const { t } = useTranslation(["settings", "common"])
const list = useFeedById(id) as ListModel
const list = useListById(id)
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
@ -226,6 +238,7 @@ const ListCreationModalContent = ({ dismiss, id }: { dismiss: () => void; id?: s
Queries.lists.list().invalidate()
dismiss()
if (!list) return
if (id) subscriptionActions.changeListView(id, views[list.view].view, views[values.view].view)
},
async onError() {
@ -341,7 +354,7 @@ const ListCreationModalContent = ({ dismiss, id }: { dismiss: () => void; id?: s
}
export const ListFeedsModalContent = ({ id }: { id: string }) => {
const list = useFeedById(id) as ListModel
const list = useListById(id)
const { t } = useTranslation("settings")
const [feedSearchFor, setFeedSearchFor] = useState("")
@ -368,14 +381,15 @@ export const ListFeedsModalContent = ({ id }: { id: string }) => {
const autocompleteSuggestions: Suggestion[] = useMemo(() => {
return allFeeds
.filter((feed) => !list.feedIds?.includes(feed.id))
.filter((feed) => !list?.feedIds?.includes(feed.id))
.map((feed) => ({
name: feed.title,
value: feed.id,
}))
}, [allFeeds, list.feedIds])
}, [allFeeds, list?.feedIds])
const selectedFeedIdRef = useRef<string | null>()
if (!list) return null
return (
<>
<div className="flex items-center gap-2">

View File

@ -11,47 +11,46 @@ import { LoadingCircle } from "~/components/ui/loading"
import { usePresentFeedFormModal } from "~/hooks/biz/useFeedFormModal"
import { useTitle } from "~/hooks/common"
import type { ListModel } from "~/models"
import { useFeed } from "~/queries/feed"
import { useList } from "~/queries/lists"
import { useFeedById } from "~/store/feed/hooks"
export function Component() {
const { id } = useParams()
const feed = useFeed({
id,
isList: true,
const list = useList({
id: id!,
})
const listData = feed.data?.feed as ListModel
const isSubscribed = !!feed.data?.subscription
const listData = list.data?.list as ListModel
const isSubscribed = !!list.data?.subscription
const { t } = useTranslation("external")
useTitle(feed.data?.feed.title)
useTitle(list.data?.list.title)
const presentFeedFormModal = usePresentFeedFormModal()
return (
<>
{feed.isLoading ? (
{list.isLoading ? (
<LoadingCircle size="large" className="center fixed inset-0" />
) : (
feed.data?.feed && (
list.data?.list && (
<div className="mx-auto mt-12 flex max-w-5xl flex-col items-center justify-center p-4 lg:p-0">
<FeedIcon
fallback
feed={feed.data.feed}
feed={list.data.list}
className="mask-squircle mask shrink-0"
size={64}
/>
<div className="flex flex-col items-center">
<div className="mb-2 mt-4 flex items-center text-2xl font-bold">
<h1>{feed.data.feed.title}</h1>
<FeedCertification feed={feed.data.feed} />
<h1>{list.data.list.title}</h1>
<FeedCertification feed={list.data.list} />
</div>
<div className="mb-8 text-sm text-zinc-500">{feed.data.feed.description}</div>
<div className="mb-8 text-sm text-zinc-500">{list.data.list.description}</div>
</div>
<div className="mb-4 text-sm">
{t("feed.followsAndFeeds", {
subscriptionCount: feed.data.subscriptionCount,
subscriptionNoun: t("feed.follower", { count: feed.data.subscriptionCount }),
feedsCount: "feedCount" in feed.data ? feed.data.feedCount : 0,
subscriptionCount: list.data?.subscriptionCount,
subscriptionNoun: t("feed.follower", { count: list.data?.subscriptionCount }),
feedsCount: "feedCount" in listData ? listData.feedCount : 0,
feedsNoun: t("feed.feeds", { count: listData?.feedIds?.length }),
appName: APP_NAME,
})}
@ -82,7 +81,7 @@ export function Component() {
{listData.feedIds
?.slice(0, 5)
.map((feedId) => <FeedRow feedId={feedId} key={feedId} />)}
{"feedCount" in feed.data && (
{"feedCount" in list.data && (
<div
onClick={() => {
presentFeedFormModal({
@ -92,7 +91,7 @@ export function Component() {
className="text-sm text-zinc-500"
>
{t("feed.follow_to_view_all", {
count: feed.data.feedCount,
count: list.data.feedCount || 0,
})}
</div>
)}

View File

@ -4,6 +4,7 @@ import { useHotkeys } from "react-hotkeys-hook"
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuPortal,
@ -112,7 +113,11 @@ const Item = memo(({ item }: { item: NativeMenuItem }) => {
return <ContextMenuSeparator />
}
case "text": {
const Wrapper = item.submenu ? ContextMenuSubTrigger : ContextMenuItem
const Wrapper = item.submenu
? ContextMenuSubTrigger
: typeof item.checked === "boolean"
? ContextMenuCheckboxItem
: ContextMenuItem
const Sub = item.submenu ? ContextMenuSub : Fragment
return (
@ -122,6 +127,7 @@ const Item = memo(({ item }: { item: NativeMenuItem }) => {
disabled={item.enabled === false || (item.click === undefined && !item.submenu)}
onClick={onClick}
className="flex items-center gap-2"
checked={item.checked}
>
{item.icon}
{item.label}

View File

@ -1,9 +1,19 @@
import { useAuthQuery } from "~/hooks/common"
import { defineQuery } from "~/lib/defineQuery"
import { feedActions } from "~/store/feed"
import { listActions } from "~/store/list"
export const lists = {
list: () =>
defineQuery(["lists"], async () => feedActions.fetchOwnedLists(), {
defineQuery(["lists"], async () => listActions.fetchOwnedLists(), {
rootKey: ["lists"],
}),
byId: ({ id }: { id: string }) =>
defineQuery(["lists", id], async () => listActions.fetchListById(id), {
rootKey: ["lists"],
}),
}
export const useList = ({ id }: { id: string }) =>
useAuthQuery(lists.byId({ id }), {
enabled: !!id,
})

View File

@ -1,11 +1,11 @@
import { browserDB } from "~/database"
import type { FeedOrListModel } from "~/models/types"
import type { FeedModel, FeedOrListModel } from "~/models/types"
import { BaseService } from "./base"
import { CleanerService } from "./cleaner"
type TargetModelWithId = FeedOrListModel & { id: string }
class ServiceStatic extends BaseService<TargetModelWithId> {
type FeedModelWithId = FeedModel & { id: string }
class ServiceStatic extends BaseService<FeedModelWithId> {
constructor() {
super(browserDB.feeds)
}
@ -15,13 +15,13 @@ class ServiceStatic extends BaseService<TargetModelWithId> {
CleanerService.reset(filterData.map((d) => ({ type: "feed", id: d.id! })))
return this.table.bulkPut(filterData as TargetModelWithId[])
return this.table.bulkPut(filterData as FeedModelWithId[])
}
override async upsert(data: FeedOrListModel): Promise<string | null> {
if (!data.id) return null
CleanerService.reset([{ type: "feed", id: data.id }])
return this.table.put(data as TargetModelWithId)
return this.table.put(data as FeedModelWithId)
}
async bulkDelete(ids: string[]) {

View File

@ -0,0 +1,37 @@
import { omit } from "lodash-es"
import { browserDB } from "~/database"
import type { ListModel } from "~/models/types"
import { BaseService } from "./base"
import { CleanerService } from "./cleaner"
class ServiceStatic extends BaseService<{ id: string }> {
constructor() {
super(browserDB.lists)
}
override async upsertMany(data: ListModel[]) {
CleanerService.reset(data.map((d) => ({ type: "feed", id: d.id! })))
// FIXME The backend should not pass these computed attributes, and these need to be removed here.
// Subsequent refactoring of the backend data flow should not nest computed attributes
return this.table.bulkPut(data.map((d) => omit(d, "owner")))
}
override async findAll() {
return super.findAll() as unknown as ListModel[]
}
override async upsert(data: ListModel): Promise<string | null> {
if (!data.id) return null
CleanerService.reset([{ type: "feed", id: data.id }])
return this.table.put(omit(data, "owner"))
}
async bulkDelete(ids: string[]) {
return this.table.bulkDelete(ids)
}
}
export const ListService = new ServiceStatic()

View File

@ -5,7 +5,7 @@ import { isNil, merge, omit } from "lodash-es"
import { runTransactionInScope } from "~/database"
import { apiClient } from "~/lib/api-fetch"
import { getEntriesParams, omitObjectUndefinedValue } from "~/lib/utils"
import type { CombinedEntryModel, EntryModel, FeedOrListRespModel } from "~/models"
import type { CombinedEntryModel, EntryModel, FeedModel, FeedOrListRespModel } from "~/models"
import { EntryService } from "~/services"
import { feedActions } from "../feed"
@ -249,7 +249,7 @@ class EntryActions {
}),
)
// Insert to feed store
feedActions.upsertMany(feeds)
feedActions.upsertMany(feeds as FeedModel[])
const newStarIds = new Set(get().starIds)
for (const entryId in entryCollection) {
newStarIds.add(entryId)

View File

@ -9,8 +9,9 @@ import { useRouteParams } from "~/hooks/biz/useRouteParams"
import { apiClient } from "~/lib/api-fetch"
import type { FeedOrListRespModel } from "~/models"
import { listActions } from "../list"
import { getSubscriptionByFeedId } from "../subscription"
import { feedActions, useFeedStore } from "./store"
import { useFeedStore } from "./store"
import type { FeedQueryParams } from "./types"
export const useFeedById = (feedId: Nullable<string>): FeedOrListRespModel | null =>
@ -75,7 +76,7 @@ export const useAddFeedToFeedList = (options?: {
json: payload,
})
feeds.data.forEach((feed) => feedActions.addFeedToFeedList(payload.listId, feed))
feeds.data.forEach((feed) => listActions.addFeedToFeedList(payload.listId, feed))
},
onSuccess: () => {
toast.success(t("lists.feeds.add.success"))
@ -96,7 +97,7 @@ export const useRemoveFeedFromFeedList = (options?: {
const { t } = useTranslation("settings")
return useMutation({
mutationFn: async (payload: { feedId: string; listId: string }) => {
feedActions.removeFeedFromFeedList(payload.listId, payload.feedId)
listActions.removeFeedFromFeedList(payload.listId, payload.feedId)
await apiClient.lists.feeds.$delete({
json: {
listId: payload.listId,

View File

@ -5,13 +5,7 @@ import { nanoid } from "nanoid"
import { whoami } from "~/atoms/user"
import { runTransactionInScope } from "~/database"
import { apiClient } from "~/lib/api-fetch"
import type {
FeedModel,
FeedOrListModel,
FeedOrListRespModel,
ListModel,
UserModel,
} from "~/models"
import type { FeedModel, FeedOrListModel, FeedOrListRespModel, UserModel } from "~/models"
import { FeedService } from "~/services"
import { getSubscriptionByFeedId } from "../subscription"
@ -30,16 +24,9 @@ class FeedActions {
set({ feeds: {} })
}
upsertMany(feeds: FeedOrListRespModel[]) {
upsertMany(feeds: FeedModel[]) {
runTransactionInScope(() => {
FeedService.upsertMany(
feeds.map((feed) => {
if (feed.type === "list") {
return omit(feed, ["feeds"])
}
return feed
}),
)
FeedService.upsertMany(feeds)
})
set((state) =>
produce(state, (state) => {
@ -73,9 +60,6 @@ class FeedActions {
const nonce = feed["nonce"] || nanoid(8)
state.feeds[nonce] = { ...feed, id: nonce }
}
if ("feeds" in feed && feed.feeds) {
this.upsertMany(feed.feeds)
}
}
}),
)
@ -114,50 +98,20 @@ class FeedActions {
})
}
async addFeedToFeedList(listId: string, feed: FeedModel) {
const list = get().feeds[listId] as ListModel
if (!list) return
feedActions.upsertMany([feed])
this.patch(listId, {
feedIds: [feed.id, ...list.feedIds],
})
feedActions.upsertMany([get().feeds[listId]])
}
async removeFeedFromFeedList(listId: string, feedId: string) {
const list = get().feeds[listId] as ListModel
if (!list) return
this.patch(listId, {
feedIds: list.feedIds.filter((id) => id !== feedId),
})
feedActions.upsertMany([get().feeds[listId]])
}
// API Fetcher
//
async fetchFeedById({ id, url, isList }: FeedQueryParams) {
const res = isList
? await apiClient.lists.$get({
query: {
listId: id!,
},
})
: await apiClient.feeds.$get({
query: {
id,
url,
},
})
async fetchFeedById({ id, url }: FeedQueryParams) {
const res = await apiClient.feeds.$get({
query: {
id,
url,
},
})
const nonce = nanoid(8)
const finalData = {
...("list" in res.data ? res.data.list : res.data.feed),
}
const finalData = res.data.feed
if (!finalData.id) {
finalData["nonce"] = nonce
}
@ -168,13 +122,6 @@ class FeedActions {
feed: !finalData.id ? { ...finalData, id: nonce } : finalData,
}
}
async fetchOwnedLists() {
const res = await apiClient.lists.list.$get()
this.upsertMany(res.data)
return res.data
}
}
export const feedActions = new FeedActions()

View File

@ -1,15 +1,8 @@
import type { FeedOrListModel, FeedOrListRespModel } from "~/models"
import type { FeedOrListModel } from "~/models"
type FeedId = string
export interface FeedState {
feeds: Record<FeedId, FeedOrListModel>
}
export interface FeedActions {
upsertMany: (feeds: FeedOrListRespModel[]) => void
clear: () => void
patch: (feedId: FeedId, patch: Partial<FeedOrListModel>) => void
}
export type FeedQueryParams = { id?: string; url?: string; isList?: boolean }

View File

@ -0,0 +1,11 @@
import type { FeedViewType } from "~/lib/enum"
import type { ListModel } from "~/models"
import { useListStore } from "./store"
export const useListById = (listId: Nullable<string>): ListModel | null =>
useListStore((state) => (listId ? state.lists[listId] : null))
export const useListByView = (view: FeedViewType) => {
return useListStore((state) => Object.values(state.lists).filter((list) => list.view === view))
}

View File

@ -0,0 +1,3 @@
export * from "./hooks"
export * from "./store"
export * from "./types"

View File

@ -0,0 +1,84 @@
import { runTransactionInScope } from "~/database"
import { apiClient } from "~/lib/api-fetch"
import type { FeedModel, ListModel, ListModelPoplutedFeeds } from "~/models"
import { ListService } from "~/services/list"
import { feedActions } from "../feed"
import { createImmerSetter, createZustandStore } from "../utils/helper"
import type { ListState } from "./types"
export const useListStore = createZustandStore<ListState>("list")(() => ({
lists: {},
}))
const set = createImmerSetter(useListStore)
const get = useListStore.getState
class ListActionStatic {
upsertMany(lists: ListModelPoplutedFeeds[]) {
if (lists.length === 0) return
const feeds = [] as FeedModel[]
set((state) => {
for (const list of lists) {
state.lists[list.id] = list
if (list.feeds)
for (const feed of list.feeds) {
feeds.push(feed)
}
}
feedActions.upsertMany(feeds)
return state
})
runTransactionInScope(() => ListService.upsertMany(lists))
}
async fetchOwnedLists() {
const res = await apiClient.lists.list.$get()
this.upsertMany(res.data)
return res.data
}
private patch(listId: string, data: Partial<ListModel>) {
set((state) => {
state.lists[listId] = { ...state.lists[listId], ...data }
return state
})
runTransactionInScope(async () => {
const patchedData = get().lists[listId]
if (!patchedData) return
return ListService.upsert(patchedData as ListModel)
})
}
async addFeedToFeedList(listId: string, feed: FeedModel) {
const list = get().lists[listId]
if (!list) return
feedActions.upsertMany([feed])
this.patch(listId, {
feedIds: [feed.id, ...list.feedIds],
})
}
async removeFeedFromFeedList(listId: string, feedId: string) {
const list = get().lists[listId] as ListModel
if (!list) return
this.patch(listId, {
feedIds: list.feedIds.filter((id) => id !== feedId),
})
}
async fetchListById(id: string) {
const res = await apiClient.lists.$get({ query: { listId: id } })
this.upsertMany([res.data.list])
return res.data
}
}
export const listActions = new ListActionStatic()

View File

@ -0,0 +1,7 @@
import type { ListModel } from "~/models"
type FeedId = string
export interface ListState {
lists: Record<FeedId, ListModel>
}

View File

@ -53,7 +53,6 @@ class SearchActions {
const feedsMap = new Map(feeds.map((feed) => [feed.id, feed]))
const entriesFuse = this.createFuse(entries, ["title", "content", "description", "id"])
// @ts-expect-error
const feedsFuse = this.createFuse(feeds, ["title", "description", "id", "siteUrl", "url"])
const subscriptionsFuse = this.createFuse(subscriptions, ["title", "category"])

View File

@ -7,11 +7,12 @@ import { runTransactionInScope } from "~/database"
import { apiClient } from "~/lib/api-fetch"
import { FeedViewType } from "~/lib/enum"
import { capitalizeFirstLetter } from "~/lib/utils"
import type { SubscriptionModel } from "~/models"
import type { FeedModel, ListModelPoplutedFeeds, SubscriptionModel } from "~/models"
import { SubscriptionService } from "~/services"
import { entryActions } from "../entry"
import { feedActions, getFeedById } from "../feed"
import { listActions } from "../list/store"
import { feedUnreadActions } from "../unread"
import { createZustandStore, doMutationAndTransaction } from "../utils/helper"
@ -116,8 +117,19 @@ class SubscriptionActions {
const transformedData = morphResponseData(res.data)
this.upsertMany(transformedData)
const feeds = [] as FeedModel[]
const lists = [] as ListModelPoplutedFeeds[]
for (const subscription of res.data) {
if ("feeds" in subscription) {
feeds.push(subscription.feeds)
} else {
lists.push(subscription.lists)
}
}
this.updateCategoryOpenState(transformedData.filter((s) => s.category || s.defaultCategory))
feedActions.upsertMany(res.data.map((s) => ("feeds" in s ? s.feeds : s.lists)))
feedActions.upsertMany(feeds)
listActions.upsertMany(lists)
return res.data
}

View File

@ -1,6 +1,7 @@
/* eslint-disable no-unsafe-finally */
import { produce } from "immer"
import { unstable_batchedUpdates } from "react-dom"
import type { StateCreator } from "zustand"
import type { StateCreator, StoreApi, UseBoundStore } from "zustand"
import type { PersistStorage } from "zustand/middleware"
import { devtools } from "zustand/middleware"
import { shallow } from "zustand/shallow"
@ -127,3 +128,12 @@ export const doMutationAndTransaction = async <M, T>(
return await Promise.all([wrappedMutation(), wrappedTransaction()])
}
}
export function createImmerSetter<T>(useStore: UseBoundStore<StoreApi<T>>) {
return (updater: (state: T) => void) =>
useStore.setState((state) =>
produce(state, (draft) => {
updater(draft as T)
}),
)
}

View File

@ -94,7 +94,7 @@
"tailwindcss-animate": "1.0.7",
"tsup": "8.3.0",
"tsx": "4.19.1",
"typescript": "^5.6.2",
"typescript": "5.4.5",
"vite": "^5.4.8",
"vite-bundle-analyzer": "0.12.1",
"vite-plugin-mkcert": "1.17.6",

View File

@ -97,7 +97,7 @@ importers:
version: 2.22.4(encoding@0.1.13)
'@t3-oss/env-core':
specifier: ^0.11.1
version: 0.11.1(typescript@5.6.2)(zod@3.23.8)
version: 0.11.1(typescript@5.4.5)(zod@3.23.8)
'@tailwindcss/container-queries':
specifier: 0.1.1
version: 0.1.1(tailwindcss@3.4.13)
@ -151,7 +151,7 @@ importers:
version: 9.11.1(jiti@2.0.0)
eslint-config-hyoban:
specifier: ^3.1.8
version: 3.1.8(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(tailwindcss@3.4.13)(typescript@5.6.2)
version: 3.1.8(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(tailwindcss@3.4.13)(typescript@5.4.5)
fake-indexeddb:
specifier: 6.0.0
version: 6.0.0
@ -196,13 +196,13 @@ importers:
version: 1.0.7(tailwindcss@3.4.13)
tsup:
specifier: 8.3.0
version: 8.3.0(jiti@2.0.0)(postcss@8.4.47)(tsx@4.19.1)(typescript@5.6.2)(yaml@2.5.1)
version: 8.3.0(jiti@2.0.0)(postcss@8.4.47)(tsx@4.19.1)(typescript@5.4.5)(yaml@2.5.1)
tsx:
specifier: 4.19.1
version: 4.19.1
typescript:
specifier: ^5.6.2
version: 5.6.2
specifier: 5.4.5
version: 5.4.5
vite:
specifier: ^5.4.8
version: 5.4.8(@types/node@22.7.4)(terser@5.34.1)
@ -214,7 +214,7 @@ importers:
version: 1.17.6(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1))
vite-tsconfig-paths:
specifier: 5.0.1
version: 5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1))
version: 5.0.1(typescript@5.4.5)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1))
vitest:
specifier: '2.0'
version: 2.0.5(@types/node@22.7.4)(terser@5.34.1)
@ -312,9 +312,6 @@ importers:
hono:
specifier: 4.6.3
version: 4.6.3(patch_hash=iej2g43ojhll5opwbnvyorjq4e)
typescript:
specifier: ^5.6.2
version: 5.6.2
apps/renderer:
dependencies:
@ -646,9 +643,6 @@ importers:
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
typescript:
specifier: ^5.6.2
version: 5.6.2
vitest:
specifier: '2.0'
version: 2.0.5(@types/node@22.7.4)(terser@5.34.1)
@ -8392,6 +8386,11 @@ packages:
typescript:
optional: true
typescript@5.4.5:
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
engines: {node: '>=14.17'}
hasBin: true
typescript@5.6.2:
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
engines: {node: '>=14.17'}
@ -10307,13 +10306,13 @@ snapshots:
'@eslint-community/regexpp@4.11.1': {}
'@eslint-react/ast@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@eslint-react/ast@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/typescript-estree': 8.7.0(typescript@5.4.5)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
birecord: 0.1.1
string-ts: 2.2.0
ts-pattern: 5.4.0
@ -10322,18 +10321,18 @@ snapshots:
- supports-color
- typescript
'@eslint-react/core@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@eslint-react/core@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
birecord: 0.1.1
short-unique-id: 5.2.0
ts-pattern: 5.4.0
@ -10342,46 +10341,46 @@ snapshots:
- supports-color
- typescript
'@eslint-react/eslint-plugin@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@eslint-react/eslint-plugin@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
eslint-plugin-react-debug: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
eslint-plugin-react-dom: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
eslint-plugin-react-hooks-extra: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
eslint-plugin-react-naming-convention: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
eslint-plugin-react-web-api: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
eslint-plugin-react-x: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
eslint-plugin-react-debug: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint-plugin-react-dom: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint-plugin-react-hooks-extra: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint-plugin-react-naming-convention: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint-plugin-react-web-api: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint-plugin-react-x: 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
'@eslint-react/jsx@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@eslint-react/jsx@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
ts-pattern: 5.4.0
transitivePeerDependencies:
- eslint
- supports-color
- typescript
'@eslint-react/shared@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@eslint-react/shared@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-react/tools': 1.14.2
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
picomatch: 4.0.2
transitivePeerDependencies:
- eslint
@ -10390,24 +10389,24 @@ snapshots:
'@eslint-react/tools@1.14.2': {}
'@eslint-react/types@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@eslint-react/types@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-react/tools': 1.14.2
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
transitivePeerDependencies:
- eslint
- supports-color
- typescript
'@eslint-react/var@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@eslint-react/var@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
ts-pattern: 5.4.0
transitivePeerDependencies:
- eslint
@ -12217,9 +12216,9 @@ snapshots:
'@sindresorhus/is@4.6.0': {}
'@stylistic/eslint-plugin@2.8.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@stylistic/eslint-plugin@2.8.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
eslint-visitor-keys: 4.1.0
espree: 10.2.0
@ -12237,6 +12236,12 @@ snapshots:
dependencies:
defer-to-connect: 2.0.1
'@t3-oss/env-core@0.11.1(typescript@5.4.5)(zod@3.23.8)':
dependencies:
zod: 3.23.8
optionalDependencies:
typescript: 5.4.5
'@t3-oss/env-core@0.11.1(typescript@5.6.2)(zod@3.23.8)':
dependencies:
zod: 3.23.8
@ -12462,34 +12467,34 @@ snapshots:
'@types/node': 22.7.4
optional: true
'@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-community/regexpp': 4.11.1
'@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 8.7.0
eslint: 9.11.1(jiti@2.0.0)
graphemer: 1.4.0
ignore: 5.3.2
natural-compare: 1.4.0
ts-api-utils: 1.3.0(typescript@5.6.2)
ts-api-utils: 1.3.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2)
'@typescript-eslint/typescript-estree': 8.7.0(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 8.7.0
debug: 4.3.7
eslint: 9.11.1(jiti@2.0.0)
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
@ -12498,21 +12503,21 @@ snapshots:
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/visitor-keys': 8.7.0
'@typescript-eslint/type-utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@typescript-eslint/type-utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/typescript-estree': 8.7.0(typescript@5.4.5)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
debug: 4.3.7
ts-api-utils: 1.3.0(typescript@5.6.2)
ts-api-utils: 1.3.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- eslint
- supports-color
'@typescript-eslint/types@8.7.0': {}
'@typescript-eslint/typescript-estree@8.7.0(typescript@5.6.2)':
'@typescript-eslint/typescript-estree@8.7.0(typescript@5.4.5)':
dependencies:
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/visitor-keys': 8.7.0
@ -12521,18 +12526,18 @@ snapshots:
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.6.3
ts-api-utils: 1.3.0(typescript@5.6.2)
ts-api-utils: 1.3.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0))
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2)
'@typescript-eslint/typescript-estree': 8.7.0(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
transitivePeerDependencies:
- supports-color
@ -12554,17 +12559,17 @@ snapshots:
'@unocss/core@0.62.4': {}
'@unocss/eslint-config@0.62.4(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@unocss/eslint-config@0.62.4(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@unocss/eslint-plugin': 0.62.4(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@unocss/eslint-plugin': 0.62.4(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
transitivePeerDependencies:
- eslint
- supports-color
- typescript
'@unocss/eslint-plugin@0.62.4(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
'@unocss/eslint-plugin@0.62.4(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)':
dependencies:
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@unocss/config': 0.62.4
'@unocss/core': 0.62.4
magic-string: 0.30.11
@ -13435,7 +13440,7 @@ snapshots:
config-file-ts@0.2.8-rc1:
dependencies:
glob: 10.4.5
typescript: 5.6.2
typescript: 5.4.5
consola@3.2.3: {}
@ -14134,19 +14139,19 @@ snapshots:
eslint: 9.11.1(jiti@2.0.0)
find-up-simple: 1.0.0
eslint-config-hyoban@3.1.8(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(tailwindcss@3.4.13)(typescript@5.6.2):
eslint-config-hyoban@3.1.8(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(tailwindcss@3.4.13)(typescript@5.4.5):
dependencies:
'@eslint-react/eslint-plugin': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/eslint-plugin': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint/js': 9.11.1
'@stylistic/eslint-plugin': 2.8.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@unocss/eslint-config': 0.62.4(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@stylistic/eslint-plugin': 2.8.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@unocss/eslint-config': 0.62.4(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
defu: 6.1.4
eslint: 9.11.1(jiti@2.0.0)
eslint-config-flat-gitignore: 0.3.0(eslint@9.11.1(jiti@2.0.0))
eslint-plugin-antfu: 2.7.0(eslint@9.11.1(jiti@2.0.0))
eslint-plugin-command: 0.2.6(eslint@9.11.1(jiti@2.0.0))
eslint-plugin-hyoban: 0.6.1(eslint@9.11.1(jiti@2.0.0))
eslint-plugin-import-x: 4.3.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
eslint-plugin-import-x: 4.3.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint-plugin-jsonc: 2.16.0(eslint@9.11.1(jiti@2.0.0))
eslint-plugin-package-json: 0.15.3(eslint@9.11.1(jiti@2.0.0))(jsonc-eslint-parser@2.4.0)
eslint-plugin-react-compiler: 0.0.0-experimental-f8a5409-20240829(eslint@9.11.1(jiti@2.0.0))
@ -14156,14 +14161,14 @@ snapshots:
eslint-plugin-simple-import-sort: 12.1.1(eslint@9.11.1(jiti@2.0.0))
eslint-plugin-tailwindcss: 3.17.4(tailwindcss@3.4.13)
eslint-plugin-unicorn: 55.0.0(eslint@9.11.1(jiti@2.0.0))
eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))
eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))
globals: 15.9.0
jsonc-eslint-parser: 2.4.0
local-pkg: 0.5.0
read-package-up: 11.0.0
typescript-eslint: 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
typescript-eslint: 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- '@typescript-eslint/eslint-plugin'
- supports-color
@ -14191,9 +14196,9 @@ snapshots:
dependencies:
eslint: 9.11.1(jiti@2.0.0)
eslint-plugin-import-x@4.3.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
eslint-plugin-import-x@4.3.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
debug: 4.3.7
doctrine: 3.0.0
eslint: 9.11.1(jiti@2.0.0)
@ -14244,63 +14249,63 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-react-debug@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
eslint-plugin-react-debug@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
string-ts: 2.2.0
ts-pattern: 5.4.0
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
eslint-plugin-react-dom@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
eslint-plugin-react-dom@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
ts-pattern: 5.4.0
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
eslint-plugin-react-hooks-extra@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
eslint-plugin-react-hooks-extra@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
ts-pattern: 5.4.0
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
@ -14308,22 +14313,22 @@ snapshots:
dependencies:
eslint: 9.11.1(jiti@2.0.0)
eslint-plugin-react-naming-convention@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
eslint-plugin-react-naming-convention@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
ts-pattern: 5.4.0
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
@ -14331,44 +14336,44 @@ snapshots:
dependencies:
eslint: 9.11.1(jiti@2.0.0)
eslint-plugin-react-web-api@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
eslint-plugin-react-web-api@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
birecord: 0.1.1
eslint: 9.11.1(jiti@2.0.0)
ts-pattern: 5.4.0
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
eslint-plugin-react-x@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
eslint-plugin-react-x@1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/ast': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/core': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/jsx': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/shared': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/tools': 1.14.2
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@eslint-react/types': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@eslint-react/var': 1.14.2(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/scope-manager': 8.7.0
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/types': 8.7.0
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
is-immutable-type: 5.0.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
is-immutable-type: 5.0.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
ts-pattern: 5.4.0
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
@ -14413,11 +14418,11 @@ snapshots:
semver: 7.6.3
strip-indent: 3.0.0
eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0)):
eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0)):
dependencies:
eslint: 9.11.1(jiti@2.0.0)
optionalDependencies:
'@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint-scope@8.1.0:
dependencies:
@ -15389,13 +15394,13 @@ snapshots:
is-hexadecimal@2.0.1: {}
is-immutable-type@5.0.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
is-immutable-type@5.0.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
eslint: 9.11.1(jiti@2.0.0)
ts-api-utils: 1.3.0(typescript@5.6.2)
ts-declaration-location: 1.0.4(typescript@5.6.2)
typescript: 5.6.2
ts-api-utils: 1.3.0(typescript@5.4.5)
ts-declaration-location: 1.0.4(typescript@5.4.5)
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
@ -18065,26 +18070,26 @@ snapshots:
dependencies:
utf8-byte-length: 1.0.5
ts-api-utils@1.3.0(typescript@5.6.2):
ts-api-utils@1.3.0(typescript@5.4.5):
dependencies:
typescript: 5.6.2
typescript: 5.4.5
ts-declaration-location@1.0.4(typescript@5.6.2):
ts-declaration-location@1.0.4(typescript@5.4.5):
dependencies:
minimatch: 10.0.1
typescript: 5.6.2
typescript: 5.4.5
ts-interface-checker@0.1.13: {}
ts-pattern@5.4.0: {}
tsconfck@3.1.1(typescript@5.6.2):
tsconfck@3.1.1(typescript@5.4.5):
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
tslib@2.7.0: {}
tsup@8.3.0(jiti@2.0.0)(postcss@8.4.47)(tsx@4.19.1)(typescript@5.6.2)(yaml@2.5.1):
tsup@8.3.0(jiti@2.0.0)(postcss@8.4.47)(tsx@4.19.1)(typescript@5.4.5)(yaml@2.5.1):
dependencies:
bundle-require: 5.0.0(esbuild@0.23.1)
cac: 6.7.14
@ -18104,7 +18109,7 @@ snapshots:
tree-kill: 1.2.2
optionalDependencies:
postcss: 8.4.47
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- jiti
- supports-color
@ -18133,17 +18138,19 @@ snapshots:
type-fest@4.26.1: {}
typescript-eslint@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2):
typescript-eslint@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5):
dependencies:
'@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
'@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5))(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.4.5)
optionalDependencies:
typescript: 5.6.2
typescript: 5.4.5
transitivePeerDependencies:
- eslint
- supports-color
typescript@5.4.5: {}
typescript@5.6.2: {}
ufo@1.5.4: {}
@ -18374,11 +18381,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)):
vite-tsconfig-paths@5.0.1(typescript@5.4.5)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)):
dependencies:
debug: 4.3.7
globrex: 0.1.2
tsconfck: 3.1.1(typescript@5.6.2)
tsconfck: 3.1.1(typescript@5.4.5)
optionalDependencies:
vite: 5.4.8(@types/node@22.7.4)(terser@5.34.1)
transitivePeerDependencies: