parent
9eb3e3f708
commit
ea8a83271e
|
|
@ -1,7 +1,5 @@
|
|||
import { createAtomHooks } from "@renderer/lib/jotai"
|
||||
import { getStorageNS } from "@renderer/lib/ns"
|
||||
import { atom } from "jotai"
|
||||
import { atomWithStorage } from "jotai/utils"
|
||||
|
||||
export const [, , useAppIsReady, , appIsReady, setAppIsReady] = createAtomHooks(
|
||||
atom(false),
|
||||
|
|
@ -10,10 +8,3 @@ export const [, , useAppIsReady, , appIsReady, setAppIsReady] = createAtomHooks(
|
|||
export const [, , useAppSearchOpen, , , setAppSearchOpen] = createAtomHooks(
|
||||
atom(false),
|
||||
)
|
||||
|
||||
export const [, , useFeedColumnShow, , , setFeedColumnShow] = createAtomHooks(
|
||||
atomWithStorage(
|
||||
getStorageNS("feed-column-show"),
|
||||
true,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -47,3 +47,6 @@ viewAtom.onMount = () => {
|
|||
setSidebarActiveView(view)
|
||||
}
|
||||
}
|
||||
export const [, , useFeedColumnShow, , , setFeedColumnShow] = createAtomHooks(
|
||||
atom(true),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -56,16 +56,17 @@ export const parseHtml = (
|
|||
|
||||
const hastTree = pipeline.runSync(tree, file)
|
||||
|
||||
let imgCount = 0
|
||||
const images = [] as string[]
|
||||
|
||||
visit(tree, "element", (node) => {
|
||||
if (node.tagName === "img") {
|
||||
imgCount++
|
||||
if (node.tagName === "img" && node.properties.src) {
|
||||
images.push(node.properties.src as string)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
imgCount,
|
||||
hastTree,
|
||||
images,
|
||||
toContent: () =>
|
||||
toJsxRuntime(hastTree, {
|
||||
Fragment,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
import { setAppSearchOpen } from "@renderer/atoms/app"
|
||||
import { useGeneralSettingKey } from "@renderer/atoms/settings/general"
|
||||
import {
|
||||
setFeedColumnShow,
|
||||
useFeedColumnShow,
|
||||
useSidebarActiveView,
|
||||
} from "@renderer/atoms/sidebar"
|
||||
import { Logo } from "@renderer/components/icons/logo"
|
||||
import { ActionButton } from "@renderer/components/ui/button"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@renderer/components/ui/popover"
|
||||
import { ProfileButton } from "@renderer/components/user-button"
|
||||
import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
|
||||
import { stopPropagation } from "@renderer/lib/dom"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { m } from "framer-motion"
|
||||
import type { FC, PropsWithChildren } from "react"
|
||||
import { useCallback, useRef, useState } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import { toast } from "sonner"
|
||||
|
||||
const useBackHome = (active: number) => {
|
||||
const navigate = useNavigateEntry()
|
||||
|
||||
return useCallback(
|
||||
(overvideActive?: number) => {
|
||||
navigate({
|
||||
feedId: null,
|
||||
entryId: null,
|
||||
view: overvideActive ?? active,
|
||||
})
|
||||
},
|
||||
[active, navigate],
|
||||
)
|
||||
}
|
||||
|
||||
export const FeedColumnHeader = () => {
|
||||
const [active] = useSidebarActiveView()
|
||||
|
||||
const navigateBackHome = useBackHome(active)
|
||||
const normalStyle =
|
||||
!window.electron || window.electron.process.platform !== "darwin"
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"ml-5 mr-3 flex items-center",
|
||||
|
||||
normalStyle ? "ml-4 justify-between" : "justify-end",
|
||||
)}
|
||||
>
|
||||
{normalStyle && (
|
||||
<LogoContextMenu>
|
||||
<div
|
||||
className="relative flex items-center gap-1 font-default text-lg font-semibold"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
navigateBackHome()
|
||||
}}
|
||||
>
|
||||
<Logo className="mr-1 size-6" />
|
||||
|
||||
{APP_NAME}
|
||||
</div>
|
||||
</LogoContextMenu>
|
||||
)}
|
||||
<div
|
||||
className="relative flex items-center gap-1"
|
||||
onClick={stopPropagation}
|
||||
>
|
||||
<SearchActionButton />
|
||||
|
||||
<Link to="/discover" tabIndex={-1}>
|
||||
<ActionButton shortcut="Meta+T" tooltip="Add">
|
||||
<i className="i-mgc-add-cute-re size-5 text-theme-vibrancyFg" />
|
||||
</ActionButton>
|
||||
</Link>
|
||||
<ProfileButton method="modal" />
|
||||
<LayoutActionButton />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const LayoutActionButton = () => {
|
||||
const feedColumnShow = useFeedColumnShow()
|
||||
|
||||
return (
|
||||
<m.div
|
||||
animate={{ width: !feedColumnShow ? "auto" : 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<ActionButton
|
||||
tooltip="Toggle Sidebar"
|
||||
icon={
|
||||
<i className={cn(!feedColumnShow ? "i-mgc-layout-leftbar-open-cute-re " : "i-mgc-layout-leftbar-close-cute-re", "text-theme-vibrancyFg")} />
|
||||
}
|
||||
onClick={() => setFeedColumnShow(!feedColumnShow)}
|
||||
/>
|
||||
</m.div>
|
||||
)
|
||||
}
|
||||
|
||||
const LogoContextMenu: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const logoRef = useRef<SVGSVGElement>(null)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger
|
||||
asChild
|
||||
onContextMenu={() => {
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="!p-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(logoRef.current?.outerHTML || "")
|
||||
setOpen(false)
|
||||
toast.success("Copied to clipboard")
|
||||
}}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-1 py-0.5 text-sm outline-none",
|
||||
"focus-within:outline-transparent hover:bg-theme-item-hover dark:hover:bg-neutral-800",
|
||||
"gap-2 text-foreground/80 [&_svg]:size-3",
|
||||
)}
|
||||
>
|
||||
<Logo ref={logoRef} />
|
||||
<span>Copy Logo SVG</span>
|
||||
</button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
const SearchActionButton = () => {
|
||||
const canSearch = useGeneralSettingKey("dataPersist")
|
||||
if (!canSearch) return null
|
||||
return (
|
||||
<ActionButton
|
||||
shortcut="Meta+K"
|
||||
tooltip="Search"
|
||||
onClick={() => setAppSearchOpen(true)}
|
||||
>
|
||||
<i className="i-mgc-search-2-cute-re size-5 text-theme-vibrancyFg" />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,16 +1,7 @@
|
|||
import { setAppSearchOpen } from "@renderer/atoms/app"
|
||||
import { getReadonlyRoute } from "@renderer/atoms/route"
|
||||
import { useGeneralSettingKey } from "@renderer/atoms/settings/general"
|
||||
import { useUISettingKey } from "@renderer/atoms/settings/ui"
|
||||
import { useSidebarActiveView } from "@renderer/atoms/sidebar"
|
||||
import { Logo } from "@renderer/components/icons/logo"
|
||||
import { ActionButton } from "@renderer/components/ui/button"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@renderer/components/ui/popover"
|
||||
import { ProfileButton } from "@renderer/components/user-button"
|
||||
import { HotKeyScopeMap, views } from "@renderer/constants"
|
||||
import { shortcuts } from "@renderer/constants/shortcuts"
|
||||
import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
|
||||
|
|
@ -30,18 +21,12 @@ import type { MotionValue } from "framer-motion"
|
|||
import { m, useSpring } from "framer-motion"
|
||||
import { atom, useAtomValue } from "jotai"
|
||||
import { Lethargy } from "lethargy"
|
||||
import type { FC, PropsWithChildren } from "react"
|
||||
import {
|
||||
useCallback,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
import type { PropsWithChildren } from "react"
|
||||
import { useCallback, useLayoutEffect, useRef } from "react"
|
||||
import { isHotkeyPressed, useHotkeys } from "react-hotkeys-hook"
|
||||
import { Link } from "react-router-dom"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { WindowUnderBlur } from "../../components/ui/background"
|
||||
import { FeedColumnHeader } from "./header"
|
||||
import { FeedList } from "./list"
|
||||
|
||||
const lethargy = new Lethargy()
|
||||
|
|
@ -168,6 +153,7 @@ export function FeedColumn({ children }: PropsWithChildren) {
|
|||
|
||||
const handler = () => {
|
||||
const width = $carousel.clientWidth
|
||||
|
||||
jotaiStore.set(carouselWidthAtom, width)
|
||||
}
|
||||
handler()
|
||||
|
|
@ -177,9 +163,6 @@ export function FeedColumn({ children }: PropsWithChildren) {
|
|||
}
|
||||
}, [])
|
||||
|
||||
const normalStyle =
|
||||
!window.electron || window.electron.process.platform !== "darwin"
|
||||
|
||||
const unreadByView = useUnreadByView()
|
||||
|
||||
const showSidebarUnreadCount = useUISettingKey("sidebarShowUnreadCount")
|
||||
|
|
@ -193,42 +176,7 @@ export function FeedColumn({ children }: PropsWithChildren) {
|
|||
className="relative flex h-full flex-col space-y-3 rounded-l-[12px] pt-2.5"
|
||||
onClick={useCallback(() => navigateBackHome(), [navigateBackHome])}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"ml-5 mr-3 flex items-center",
|
||||
|
||||
normalStyle ? "ml-4 justify-between" : "justify-end",
|
||||
)}
|
||||
>
|
||||
{normalStyle && (
|
||||
<LogoContextMenu>
|
||||
<div
|
||||
className="relative flex items-center gap-1 font-default text-lg font-semibold"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
navigateBackHome()
|
||||
}}
|
||||
>
|
||||
<Logo className="mr-1 size-6" />
|
||||
|
||||
{APP_NAME}
|
||||
</div>
|
||||
</LogoContextMenu>
|
||||
)}
|
||||
<div
|
||||
className="relative flex items-center gap-1"
|
||||
onClick={stopPropagation}
|
||||
>
|
||||
<SearchActionButton />
|
||||
|
||||
<Link to="/discover" tabIndex={-1}>
|
||||
<ActionButton shortcut="Meta+T" tooltip="Add">
|
||||
<i className="i-mgc-add-cute-re size-5 text-theme-vibrancyFg" />
|
||||
</ActionButton>
|
||||
</Link>
|
||||
<ProfileButton method="modal" />
|
||||
</div>
|
||||
</div>
|
||||
<FeedColumnHeader />
|
||||
|
||||
<div
|
||||
className="flex w-full justify-between px-3 text-xl text-theme-vibrancyFg"
|
||||
|
|
@ -338,53 +286,3 @@ const SwipeWrapper: Component<{
|
|||
</m.div>
|
||||
)
|
||||
}
|
||||
|
||||
const SearchActionButton = () => {
|
||||
const canSearch = useGeneralSettingKey("dataPersist")
|
||||
if (!canSearch) return null
|
||||
return (
|
||||
<ActionButton
|
||||
shortcut="Meta+K"
|
||||
tooltip="Search"
|
||||
onClick={() => setAppSearchOpen(true)}
|
||||
>
|
||||
<i className="i-mgc-search-2-cute-re size-5 text-theme-vibrancyFg" />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
|
||||
const LogoContextMenu: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const logoRef = useRef<SVGSVGElement>(null)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger
|
||||
asChild
|
||||
onContextMenu={() => {
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="!p-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(logoRef.current?.outerHTML || "")
|
||||
setOpen(false)
|
||||
toast.success("Copied to clipboard")
|
||||
}}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-1 py-0.5 text-sm outline-none",
|
||||
"focus-within:outline-transparent hover:bg-theme-item-hover dark:hover:bg-neutral-800",
|
||||
"gap-2 text-foreground/80 [&_svg]:size-3",
|
||||
)}
|
||||
>
|
||||
<Logo ref={logoRef} />
|
||||
<span>Copy Logo SVG</span>
|
||||
</button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { repository } from "@pkg"
|
||||
import { useFeedColumnShow } from "@renderer/atoms/app"
|
||||
import { setMainContainerElement } from "@renderer/atoms/dom"
|
||||
import { getUISettings, setUISetting } from "@renderer/atoms/settings/ui"
|
||||
import { setFeedColumnShow, useFeedColumnShow } from "@renderer/atoms/sidebar"
|
||||
import { useLoginModalShow, useWhoami } from "@renderer/atoms/user"
|
||||
import { AppErrorBoundary } from "@renderer/components/common/AppErrorBoundary"
|
||||
import { ErrorComponentType } from "@renderer/components/errors"
|
||||
|
|
@ -9,6 +9,7 @@ import { PanelSplitter } from "@renderer/components/ui/divider"
|
|||
import { DeclarativeModal } from "@renderer/components/ui/modal/stacked/declarative-modal"
|
||||
import { NoopChildren } from "@renderer/components/ui/modal/stacked/utils"
|
||||
import { RootPortal } from "@renderer/components/ui/portal"
|
||||
import { shortcuts } from "@renderer/constants/shortcuts"
|
||||
import { preventDefault } from "@renderer/lib/dom"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { EnvironmentIndicator } from "@renderer/modules/app/EnvironmentIndicator"
|
||||
|
|
@ -20,8 +21,10 @@ import { CornerPlayer } from "@renderer/modules/feed-column/corner-player"
|
|||
import { CmdF } from "@renderer/modules/panel/cmdf"
|
||||
import { SearchCmdK } from "@renderer/modules/panel/cmdk"
|
||||
import { CmdNTrigger } from "@renderer/modules/panel/cmdn"
|
||||
import { throttle } from "lodash-es"
|
||||
import type { PropsWithChildren } from "react"
|
||||
import { useRef } from "react"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useHotkeys } from "react-hotkeys-hook"
|
||||
import { useResizable } from "react-resizable-layout"
|
||||
import { Outlet } from "react-router-dom"
|
||||
|
||||
|
|
@ -87,9 +90,7 @@ export function Component() {
|
|||
// NOTE: tabIndex for main element can get by `document.activeElement`
|
||||
tabIndex={-1}
|
||||
>
|
||||
<AppErrorBoundary
|
||||
errorType={errorTypes}
|
||||
>
|
||||
<AppErrorBoundary errorType={errorTypes}>
|
||||
<Outlet />
|
||||
</AppErrorBoundary>
|
||||
</main>
|
||||
|
|
@ -138,14 +139,61 @@ const FeedResponsiveResizerContainer = ({
|
|||
})
|
||||
|
||||
const feedColumnShow = useFeedColumnShow()
|
||||
const [feedColumnTempShow, setFeedColumnTempShow] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = throttle((e: MouseEvent) => {
|
||||
const mouseX = e.clientX
|
||||
const { feedColWidth } = getUISettings()
|
||||
|
||||
if (mouseX < feedColWidth) {
|
||||
setFeedColumnTempShow(true)
|
||||
} else {
|
||||
setFeedColumnTempShow(false)
|
||||
}
|
||||
}, 300)
|
||||
|
||||
document.addEventListener("mousemove", handler)
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", handler)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useHotkeys(shortcuts.layout.toggleSidebar.key, () => {
|
||||
setFeedColumnShow(!feedColumnShow)
|
||||
})
|
||||
|
||||
const [delayShowSplitter, setDelayShowSplitter] = useState(feedColumnShow)
|
||||
|
||||
useEffect(() => {
|
||||
let timer: any
|
||||
if (feedColumnShow) {
|
||||
// eslint-disable-next-line @eslint-react/web-api/no-leaked-timeout
|
||||
timer = setTimeout(() => {
|
||||
setDelayShowSplitter(true)
|
||||
}, 200)
|
||||
} else {
|
||||
// eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect
|
||||
setDelayShowSplitter(false)
|
||||
}
|
||||
|
||||
return () => {
|
||||
timer = clearTimeout(timer)
|
||||
}
|
||||
}, [feedColumnShow])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 overflow-hidden",
|
||||
"absolute inset-y-0 z-10",
|
||||
!feedColumnShow ? "-translate-x-full" : "",
|
||||
"absolute inset-y-0 z-10 duration-200",
|
||||
feedColumnTempShow &&
|
||||
!feedColumnShow &&
|
||||
"shadow-drawer-right z-[12] border-r",
|
||||
!feedColumnShow && !feedColumnTempShow ?
|
||||
"-translate-x-full delay-200" :
|
||||
"",
|
||||
)}
|
||||
style={{
|
||||
"width": `${position}px`,
|
||||
|
|
@ -163,7 +211,7 @@ const FeedResponsiveResizerContainer = ({
|
|||
}}
|
||||
/>
|
||||
|
||||
{feedColumnShow && <PanelSplitter {...separatorProps} />}
|
||||
{delayShowSplitter && <PanelSplitter {...separatorProps} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,6 +279,13 @@
|
|||
[data-theme="dark"] .shadow-drawer-left {
|
||||
box-shadow: -12px 0px 17px 2px rgba(0, 0, 0, 0.653);
|
||||
}
|
||||
|
||||
.shadow-drawer-right {
|
||||
box-shadow: 12px 0px 17px 2px rgba(41, 41, 41, 0.1);
|
||||
}
|
||||
[data-theme="dark"] .shadow-drawer-right {
|
||||
box-shadow: 12px 0px 17px 2px rgba(0, 0, 0, 0.653);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
|
|
|
|||
Loading…
Reference in New Issue