import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'
import { StyledText } from '../../elements/StyledText'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { Circle, DotsThree, MagnifyingGlass, X } from '@phosphor-icons/react'
import {
Subscription,
SubscriptionType,
} from '../../../lib/networking/queries/useGetSubscriptionsQuery'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { theme } from '../../tokens/stitches.config'
import { useRegisterActions } from 'kbar'
import { LogoBox } from '../../elements/LogoBox'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import { SavedSearch } from '../../../lib/networking/fragments/savedSearchFragment'
import { ToggleCaretDownIcon } from '../../elements/icons/ToggleCaretDownIcon'
import Link from 'next/link'
import { ToggleCaretRightIcon } from '../../elements/icons/ToggleCaretRightIcon'
import { NavMenuFooter } from './Footer'
import { FollowingIcon } from '../../elements/icons/FollowingIcon'
import { HomeIcon } from '../../elements/icons/HomeIcon'
import { LibraryIcon } from '../../elements/icons/LibraryIcon'
import { HighlightsIcon } from '../../elements/icons/HighlightsIcon'
import { CoverImage } from '../../elements/CoverImage'
import { OutlinedLabelChip } from '../../elements/OutlinedLabelChip'
import { NewsletterIcon } from '../../elements/icons/NewsletterIcon'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { useRouter } from 'next/router'
import { DiscoverIcon } from '../../elements/icons/DiscoverIcon'
import { escapeQuotes } from '../../../utils/helper'
import { Shortcut } from '../../../lib/networking/shortcuts/useShortcuts'
export const LIBRARY_LEFT_MENU_WIDTH = '275px'
type LibraryFilterMenuProps = {
setShowAddLinkModal: (show: boolean) => void
searchTerm: string | undefined
applySearchQuery: (searchTerm: string) => void
showFilterMenu: boolean
setShowFilterMenu: (show: boolean) => void
}
export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element {
return (
<>
{/* This spacer pushes library content to the right of
the fixed left side menu. */}
>
)
}
const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
return (
}
/>
}
/>
}
/>
}
/>
}
/>
)
}
const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => {
const router = useRouter()
const [shortcuts] = usePersistedState({
key: 'library-shortcuts',
isSessionStorage: false,
initialValue: [],
})
return (
SHORTCUTS
}
css={{ ml: 'auto' }}
>
{
router.push(`/settings/shortcuts`)
}}
title="Edit shortcuts"
/>
{shortcuts.map((shortcut) => {
const selected = props.searchTerm === shortcut.filter
return (
{
props.applySearchQuery(shortcut.filter)
props.setShowFilterMenu(false)
e.preventDefault()
}}
>
{(shortcut.type == 'feed' || shortcut.type == 'newsletter') && (
)}
{shortcut.type == 'search' && (
)}
{shortcut.type == 'label' && }
)
})}
)
}
type ShortcutItemProps = {
shortcut: Shortcut
}
const FeedOrNewsletterShortcut = (props: ShortcutItemProps): JSX.Element => {
return (
{props.shortcut.icon ? (
) : props.shortcut.type == 'newsletter' ? (
) : (
)}
{props.shortcut.name}
)
}
const SearchShortcut = (props: ShortcutItemProps): JSX.Element => {
return (
{props.shortcut.name}
)
}
const LabelShortcut = (props: ShortcutItemProps): JSX.Element => {
return (
)
}
function SavedSearches(
props: LibraryFilterMenuProps & { savedSearches: SavedSearch[] | undefined }
): JSX.Element {
const sortedSearches = useMemo(() => {
return props.savedSearches
?.filter((it) => it.visible)
?.sort(
(left: SavedSearch, right: SavedSearch) =>
left.position - right.position
)
}, [props.savedSearches])
useRegisterActions(
(sortedSearches ?? []).map((item, idx) => {
const key = String(idx + 1)
return {
id: `saved_search_${key}`,
name: item.name,
shortcut: [key],
section: 'Saved Searches',
keywords: '?' + item.name,
perform: () => {
props.applySearchQuery(item.filter)
},
}
}),
[props.savedSearches]
)
const [collapsed, setCollapsed] = usePersistedState({
key: `--saved-searches-collapsed`,
initialValue: false,
})
return (
{!collapsed &&
sortedSearches &&
sortedSearches?.map((item) => (
))}
{!collapsed && sortedSearches !== undefined && (
)}
)
}
function Subscriptions(
props: LibraryFilterMenuProps & { subscriptions: Subscription[] | undefined }
): JSX.Element {
const [collapsed, setCollapsed] = usePersistedState({
key: `--subscriptions-collapsed`,
initialValue: false,
})
const sortedSubscriptions = useMemo(() => {
if (!props.subscriptions) {
return []
}
return props.subscriptions
.filter((s) => s.status == 'ACTIVE')
.sort((a, b) => a.name.localeCompare(b.name))
}, [props.subscriptions])
useRegisterActions(
(sortedSubscriptions ?? []).map((subscription, idx) => {
const key = String(idx + 1)
const name = subscription.name
return {
id: `subscription_${key}`,
section: 'Subscriptions',
name: name,
keywords: '*' + name,
perform: () => {
props.applySearchQuery(`subscription:\"${escapeQuotes(name)}\"`)
},
}
}),
[sortedSubscriptions]
)
return (
{!collapsed ? (
<>
{(sortedSubscriptions ?? []).map((item) => {
switch (item.type) {
case SubscriptionType.NEWSLETTER:
return (
)
case SubscriptionType.RSS:
return (
)
}
})}
>
) : (
)}
)
}
function Labels(
props: LibraryFilterMenuProps & { labels: Label[] }
): JSX.Element {
const [collapsed, setCollapsed] = usePersistedState({
key: `--labels-collapsed`,
initialValue: false,
})
const sortedLabels = useMemo(() => {
return props.labels.sort((left: Label, right: Label) =>
left.name.localeCompare(right.name)
)
}, [props.labels])
return (
{!collapsed && (
<>
{sortedLabels.map((item) => {
return
})}
>
)}
)
}
type MenuPanelProps = {
title: string
children: ReactNode
editFunc?: () => void
editTitle?: string
hideBottomBorder?: boolean
collapsed: boolean
setCollapsed: (collapsed: boolean) => void
}
function MenuPanel(props: MenuPanelProps): JSX.Element {
return (
{props.title}
{props.children}
)
}
type NavButtonProps = {
text: string
icon: ReactNode
filterTerm: string
searchTerm: string | undefined
applySearchQuery: (searchTerm: string) => void
setShowFilterMenu: (show: boolean) => void
}
type NavButtonRedirectProps = {
text: string
icon: ReactNode
redirectLocation: string
}
function NavRedirectButton(props: NavButtonRedirectProps): JSX.Element {
const [selected, setSelected] = useState(false)
const router = useRouter()
useEffect(() => {
setSelected(window.location.pathname.includes(props.redirectLocation))
}, [])
return (
{
router.push(props.redirectLocation)
e.preventDefault()
}}
>
{props.icon}
{props.text}
)
}
function NavButton(props: NavButtonProps): JSX.Element {
const isInboxFilter = (filter: string) => {
return filter === '' || filter === 'in:inbox'
}
const selected = useMemo(() => {
if (isInboxFilter(props.filterTerm) && !props.searchTerm) {
return true
}
return props.searchTerm === props.filterTerm
}, [props.searchTerm, props.filterTerm])
return (
{
props.applySearchQuery(props.filterTerm)
props.setShowFilterMenu(false)
e.preventDefault()
}}
>
{props.icon}
{props.text}
)
}
type FilterButtonProps = {
text: string
filterTerm: string
searchTerm: string | undefined
applySearchQuery: (searchTerm: string) => void
setShowFilterMenu: (show: boolean) => void
}
function FilterButton(props: FilterButtonProps): JSX.Element {
const isInboxFilter = (filter: string) => {
return filter === '' || filter === 'in:inbox'
}
const selected = useMemo(() => {
if (isInboxFilter(props.filterTerm) && !props.searchTerm) {
return true
}
return props.searchTerm === props.filterTerm
}, [props.searchTerm, props.filterTerm])
return (
{
props.applySearchQuery(props.filterTerm)
props.setShowFilterMenu(false)
e.preventDefault()
}}
>
{props.text}
)
}
type LabelButtonProps = {
label: Label
searchTerm: string | undefined
applySearchQuery: (searchTerm: string) => void
}
function LabelButton(props: LabelButtonProps): JSX.Element {
const labelId = `checkbox-label-${props.label.id}`
const checkboxRef = useRef(null)
const state = useMemo(() => {
const term = props.searchTerm ?? ''
if (term.indexOf(`label:\"${escapeQuotes(props.label.name)}\"`) >= 0) {
return 'on'
}
return 'off'
}, [props.searchTerm, props.label])
return (
{
const escapedLabelName = escapeQuotes(props.label.name)
if (e.target.checked) {
props.applySearchQuery(
`${props.searchTerm ?? ''} label:\"${escapedLabelName}\"`
)
} else {
const query =
props.searchTerm?.replace(
`label:\"${escapedLabelName}\"`,
''
) ?? ''
props.applySearchQuery(query)
}
}}
/>
)
}
type EditButtonProps = {
title: string
destination: string
}
function EditButton(props: EditButtonProps): JSX.Element {
return (
{props.title}
)
}