Merge pull request #3538 from omnivore-app/feat/web-multiselect-header

Improvements to the web multiselect header
This commit is contained in:
Jackson Harper 2024-02-16 13:37:22 +08:00 committed by GitHub
commit d8194363eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 343 additions and 698 deletions

View file

@ -1,10 +1,6 @@
import { getLuminance } from 'color2k'
import { useRouter } from 'next/router'
import { Button } from './Button'
import { SpanBox, HStack } from './LayoutPrimitives'
import { Circle, X } from 'phosphor-react'
import { isDarkTheme } from '../../lib/themeUpdater'
import { theme } from '../tokens/stitches.config'
type LabelChipProps = {
text: string

View file

@ -7,7 +7,6 @@ import {
import { Box, HStack } from '../elements/LayoutPrimitives'
import { StyledText, StyledAnchor } from '../elements/StyledText'
import { TooltipWrapped } from './Tooltip'
import Link from 'next/link'
const TooltipStyle = {
backgroundColor: '#F9D354',

View file

@ -1,8 +1,5 @@
import { ReactNode, useEffect, useMemo, useRef } from 'react'
import { styled } from '../tokens/stitches.config'
import { Box, HStack, VStack } from './LayoutPrimitives'
import { HStack, VStack } from './LayoutPrimitives'
import { Button } from './Button'
import { Dropdown, DropdownOption } from './DropdownElements'
import { CaretDownIcon } from './icons/CaretDownIcon'
type ShowLinkMode = 'none' | 'link' | 'pdf'

View file

@ -4,8 +4,28 @@ import { IconProps } from './IconProps'
import { SpanBox } from '../LayoutPrimitives'
import React from 'react'
import { MultiSelectMode } from '../../templates/homeFeed/LibraryHeader'
export class HeaderCheckboxIcon extends React.Component<IconProps> {
type HeaderCheckboxIconProps = {
multiSelectMode: MultiSelectMode
}
export const HeaderCheckboxIcon = (
props: HeaderCheckboxIconProps
): JSX.Element => {
switch (props.multiSelectMode) {
case 'search':
case 'visible':
return <HeaderCheckboxCheckedIcon />
case 'none':
case 'off':
return <HeaderCheckboxUncheckedIcon />
case 'some':
return <HeaderCheckboxHalfCheckedIcon />
}
}
export class HeaderCheckboxUncheckedIcon extends React.Component<IconProps> {
render() {
return (
<SpanBox
@ -54,3 +74,89 @@ export class HeaderCheckboxIcon extends React.Component<IconProps> {
)
}
}
export class HeaderCheckboxCheckedIcon extends React.Component<IconProps> {
render() {
return (
<SpanBox
css={{
display: 'flex',
'--inner-color': 'var(--colors-thHeaderIconInner)',
'--ring-color': 'var(--colors-thHeaderIconRing)',
'&:hover': {
'--ring-fill': '#007AFF10',
},
}}
>
<svg
width="41"
height="40"
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="0.5"
y="0.5"
width="39"
height="39"
rx="19.5"
style={{
fill: 'var(--ring-fill)',
stroke: 'var(--ring-color)',
}}
/>
<g>
<path
d="M25.9341 11.6667C27.5674 11.6667 28.9007 12.9476 28.9857 14.5601L28.9899 14.7226V25.2776C28.9899 26.9109 27.7091 28.2442 26.0966 28.3292L25.9341 28.3334H15.3791C14.5967 28.3335 13.8442 28.0334 13.2764 27.4951C12.7087 26.9569 12.369 26.2213 12.3274 25.4401L12.3232 25.2776V14.7226C12.3232 13.0892 13.6041 11.7559 15.2166 11.6709L15.3791 11.6667H25.9341ZM23.7457 17.7442C23.5895 17.588 23.3775 17.5003 23.1566 17.5003C22.9356 17.5003 22.7237 17.588 22.5674 17.7442L19.8232 20.4876L18.7457 19.4109L18.6674 19.3417C18.4999 19.2122 18.2894 19.1513 18.0786 19.1714C17.8679 19.1915 17.6726 19.291 17.5326 19.4498C17.3926 19.6087 17.3183 19.8148 17.3247 20.0264C17.3312 20.2381 17.418 20.4393 17.5674 20.5892L19.2341 22.2559L19.3124 22.3251C19.4727 22.4495 19.673 22.5111 19.8755 22.4983C20.078 22.4856 20.2689 22.3994 20.4124 22.2559L23.7457 18.9226L23.8149 18.8442C23.9393 18.6839 24.0009 18.4837 23.9881 18.2812C23.9754 18.0787 23.8892 17.8877 23.7457 17.7442Z"
fill="#007AFF"
/>
</g>
</svg>
</SpanBox>
)
}
}
export class HeaderCheckboxHalfCheckedIcon extends React.Component<IconProps> {
render() {
return (
<SpanBox
css={{
display: 'flex',
'--inner-color': '#007AFF',
'--ring-color': 'var(--colors-thHeaderIconRing)',
'&:hover': {
'--ring-fill': '#007AFF10',
},
}}
>
<svg
width="41"
height="40"
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="0.5"
y="0.5"
width="39"
height="39"
rx="19.5"
style={{
fill: 'var(--ring-fill)',
stroke: 'var(--ring-color)',
}}
/>
<g>
<path
d="M25.8332 11.7496C26.4962 11.7496 27.1321 12.013 27.6009 12.4819C28.0698 12.9507 28.3332 13.5866 28.3332 14.2496V25.9163C28.3332 26.5793 28.0698 27.2152 27.6009 27.6841C27.1321 28.1529 26.4962 28.4163 25.8332 28.4163H14.1665C13.5035 28.4163 12.8676 28.1529 12.3987 27.6841C11.9299 27.2152 11.6665 26.5793 11.6665 25.9163V14.2496C11.6665 13.5866 11.9299 12.9507 12.3987 12.4819C12.8676 12.013 13.5035 11.7496 14.1665 11.7496H25.8332ZM22.4998 19.2496H17.4998L17.4023 19.2555C17.1914 19.2806 16.998 19.3852 16.8617 19.5481C16.7254 19.711 16.6564 19.9198 16.6689 20.1318C16.6813 20.3438 16.7743 20.5431 16.9287 20.6889C17.0831 20.8347 17.2874 20.9161 17.4998 20.9163H22.4998L22.5973 20.9105C22.8082 20.8854 23.0016 20.7807 23.1379 20.6178C23.2743 20.4549 23.3433 20.2462 23.3308 20.0341C23.3184 19.8221 23.2254 19.6228 23.071 19.477C22.9165 19.3312 22.7122 19.2499 22.4998 19.2496Z"
fill="#007AFF"
/>
</g>
</svg>
</SpanBox>
)
}
}

View file

@ -14,22 +14,21 @@ export function OmnivoreFestiveLogo(
const href = props.href || '/home'
return (
(<Link
<Link
passHref
href={href}
style={{
textDecoration: 'none',
display: 'flex',
alignItems: 'center',
}}>
}}
>
<Image
src="/static/images/omnivore-logo-santa.png"
width="27"
height="27"
alt=""
/>
</Link>)
);
</Link>
)
}

View file

@ -1,6 +1,3 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { config } from '../../tokens/stitches.config'
import { OmnivoreLogoBase } from './OmnivoreLogoBase'

View file

@ -1,21 +1,8 @@
import { isAndroid } from '../../lib/deviceType'
import { styled, theme } from '../tokens/stitches.config'
import { StyledText } from '../elements/StyledText'
import { Button } from '../elements/Button'
import { HStack, Box } from '../elements/LayoutPrimitives'
import { PenWithColorIcon } from '../elements/images/PenWithColorIcon'
import {
Note,
Tag,
Trash,
Copy,
Circle,
CopySimple,
CheckCircle,
} from 'phosphor-react'
import { TrashIcon } from '../elements/icons/TrashIcon'
import { Circle, CheckCircle } from 'phosphor-react'
import { LabelIcon } from '../elements/icons/LabelIcon'
import { NotebookIcon } from '../elements/icons/NotebookIcon'
import { highlightColor, highlightColors } from '../../lib/themeUpdater'

View file

@ -1,20 +1,8 @@
import { useState } from 'react'
import { Box, SpanBox } from '../elements/LayoutPrimitives'
import { LibraryItemNode } from '../../lib/networking/queries/useGetLibraryItemsQuery'
import { Box } from '../elements/LayoutPrimitives'
import { Button } from '../elements/Button'
import { theme } from '../tokens/stitches.config'
import {
ArchiveBox,
Book,
BookOpen,
Copy,
DotsThree,
Notebook,
Tag,
Trash,
Tray,
} from 'phosphor-react'
//import { CardMenu } from '../CardMenu'
import { BookOpen, Copy } from 'phosphor-react'
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
import { Highlight } from '../../lib/networking/fragments/highlightFragment'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'

View file

@ -9,15 +9,11 @@ import {
SpanBox,
HStack,
} from '../elements/LayoutPrimitives'
import { styled, theme } from '../tokens/stitches.config'
import { styled } from '../tokens/stitches.config'
import { HighlightViewNote } from './HighlightNotes'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import {
highlightColor,
highlightColorVar,
isDarkTheme,
} from '../../lib/themeUpdater'
import { highlightColorVar } from '../../lib/themeUpdater'
import { ReadableItem } from '../../lib/networking/queries/useGetLibraryItemsQuery'
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
import {
@ -54,7 +50,6 @@ const StyledQuote = styled(Blockquote, {
})
export function HighlightView(props: HighlightViewProps): JSX.Element {
const isDark = isDarkTheme()
const [noteMode, setNoteMode] = useState<'preview' | 'edit'>('preview')
const [isOpen, setIsOpen] = useState(false)

View file

@ -1,8 +1,8 @@
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { ChangeEvent, useMemo } from 'react'
import { useMemo } from 'react'
import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { RecommendedFlairIcon } from '../../elements/icons/RecommendedFlairIcon'
import { PinnedFlairIcon } from '../../elements/icons/PinnedFlairIcon'
import { FavoriteFlairIcon } from '../../elements/icons/FavoriteFlairIcon'

View file

@ -195,7 +195,7 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
const handleCheckChanged = useCallback(() => {
const newValue = !isChecked
setIsChecked(item.id, newValue)
}, [setIsChecked, isChecked, props])
}, [item, setIsChecked, isChecked, props])
return (
<VStack css={{ p: '0px', m: '0px', width: '100%' }}>

View file

@ -4,14 +4,7 @@ import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryIt
import { LinkedItemCardAction } from './CardTypes'
import { Button } from '../../elements/Button'
import { theme } from '../../tokens/stitches.config'
import {
ArchiveBox,
DotsThree,
Notebook,
Tag,
Trash,
Tray,
} from 'phosphor-react'
import { DotsThree } from 'phosphor-react'
import { CardMenu } from '../CardMenu'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { ArchiveIcon } from '../../elements/icons/ArchiveIcon'

View file

@ -1,7 +1,5 @@
import { Box, HStack } from '../elements/LayoutPrimitives'
import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo'
import { HStack } from '../elements/LayoutPrimitives'
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
import { PrimaryDropdown } from '../templates/PrimaryDropdown'
import { DEFAULT_HEADER_HEIGHT } from '../templates/homeFeed/HeaderSpacer'
import { LogoBox } from '../elements/LogoBox'

View file

@ -131,10 +131,11 @@ export function PrimaryDropdown(props: PrimaryDropdownProps): JSX.Element {
return (
<Dropdown
side="top"
triggerElement={
props.children ?? <TriggerButton name={viewerData?.me?.name ?? 'O'} />
}
css={{ width: '240px' }}
css={{ width: '240px', ml: '15px' }}
>
<HStack
alignment="center"
@ -278,7 +279,7 @@ function ThemeSection(props: PrimaryDropdownProps): JSX.Element {
updateTheme(newTheme)
setDisplayTheme(newTheme)
},
[displayTheme, setDisplayTheme]
[setDisplayTheme]
)
return (

View file

@ -7,10 +7,8 @@ import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardSho
import { useRouter } from 'next/router'
import { ConfirmationModal } from '../patterns/ConfirmationModal'
import { KeyboardShortcutListModal } from './KeyboardShortcutListModal'
import { logoutMutation } from '../../lib/networking/mutations/logoutMutation'
import { deinitAnalytics, setupAnalytics } from '../../lib/analytics'
import { setupAnalytics } from '../../lib/analytics'
import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { logout } from '../../lib/logout'
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'

View file

@ -28,9 +28,8 @@ export function AddBulkLabelsModal(
const [tabCount, setTabCount] = useState(-1)
const [inputValue, setInputValue] = useState('')
const [tabStartValue, setTabStartValue] = useState('')
const [errorMessage, setErrorMessage] = useState<string | undefined>(
undefined
)
const [errorMessage, setErrorMessage] =
useState<string | undefined>(undefined)
const errorTimeoutRef = useRef<NodeJS.Timeout | undefined>()
const [highlightLastLabel, setHighlightLastLabel] = useState(false)
const [isSaving, setIsSaving] = useState(false)

View file

@ -2,7 +2,7 @@ import { useState } from 'react'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { HighlightView } from '../../patterns/HighlightView'
type HighlightViewItemProps = {

View file

@ -1,5 +1,4 @@
import { Box, HStack, VStack, SpanBox } from '../../elements/LayoutPrimitives'
import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -17,7 +16,6 @@ import { TrashIcon } from '../../elements/icons/TrashIcon'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { SetHighlightLabelsModalPresenter } from './SetLabelsModalPresenter'
import { Button } from '../../elements/Button'
import { ArticleNotes } from '../../patterns/ArticleNotes'
import { useGetArticleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
import { formattedShortTime } from '../../../lib/dateFormatting'
@ -59,9 +57,8 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
const [noteText, setNoteText] = useState<string>('')
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
useState<undefined | string>(undefined)
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
undefined
)
const [labelsTarget, setLabelsTarget] =
useState<Highlight | undefined>(undefined)
const noteState = useRef<NoteState>({
isCreating: false,
note: undefined,
@ -87,34 +84,37 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
}
})()
},
[]
[props]
)
const createNote = useCallback((text: string) => {
noteState.current.isCreating = true
noteState.current.createStarted = new Date()
;(async () => {
try {
const success = await createHighlightMutation({
id: newNoteId,
shortId: nanoid(8),
type: 'NOTE',
articleId: props.item.id,
annotation: text,
})
if (success) {
noteState.current.note = success
const createNote = useCallback(
(text: string) => {
noteState.current.isCreating = true
noteState.current.createStarted = new Date()
;(async () => {
try {
const success = await createHighlightMutation({
id: newNoteId,
shortId: nanoid(8),
type: 'NOTE',
articleId: props.item.id,
annotation: text,
})
if (success) {
noteState.current.note = success
noteState.current.isCreating = false
} else {
setErrorSaving('Error creating note')
}
} catch (error) {
console.error('error creating note: ', error)
noteState.current.isCreating = false
} else {
setErrorSaving('Error creating note')
}
} catch (error) {
console.error('error creating note: ', error)
noteState.current.isCreating = false
setErrorSaving('Error creating note')
}
})()
}, [])
})()
},
[props, newNoteId]
)
const highlights = useMemo(() => {
const result = articleData?.article.article.highlights
@ -133,7 +133,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
if (highlights && props.onAnnotationsChanged) {
props.onAnnotationsChanged(highlights)
}
}, [highlights])
}, [props, highlights])
const sortedHighlights = useMemo(() => {
const sorted = (a: number, b: number) => {
@ -188,7 +188,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
}
createNote(text)
},
[noteText, noteState, createNote, updateNote, highlights]
[noteState, createNote, updateNote]
)
const deleteDocumentNote = useCallback(() => {
@ -204,7 +204,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
noteState.current.note = undefined
})()
setNoteText('')
}, [noteState, highlights])
}, [props, noteState, highlights])
const [errorSaving, setErrorSaving] = useState<string | undefined>(undefined)
const [lastChanged, setLastChanged] = useState<Date | undefined>(undefined)

View file

@ -4,7 +4,7 @@ import { Sidebar } from 'phosphor-react'
import { theme } from '../../tokens/stitches.config'
import { Button } from '../../elements/Button'
import { ExportIcon } from '../../elements/icons/ExportIcon'
import { useCallback, useMemo } from 'react'
import { useCallback } from 'react'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { useGetArticleQuery } from '../../../lib/networking/queries/useGetArticleQuery'

View file

@ -9,7 +9,7 @@ import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { useCallback, useState } from 'react'
import { ArrowsIn, ArrowsOut, X } from 'phosphor-react'
import { X } from 'phosphor-react'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { diff_match_patch } from 'diff-match-patch'
@ -37,17 +37,15 @@ export const getHighlightLocation = (patch: string): number | undefined => {
export function NotebookModal(props: NotebookModalProps): JSX.Element {
const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false)
const [allAnnotations, setAllAnnotations] = useState<Highlight[] | undefined>(
undefined
)
const [allAnnotations, setAllAnnotations] =
useState<Highlight[] | undefined>(undefined)
const [deletedHighlights, setDeletedAnnotations] = useState<
Highlight[] | undefined
>(undefined)
const [deletedHighlights, setDeletedAnnotations] =
useState<Highlight[] | undefined>(undefined)
const handleClose = useCallback(() => {
props.onClose(allAnnotations ?? [], deletedHighlights ?? [])
}, [props, allAnnotations])
}, [props, allAnnotations, deletedHighlights])
const handleAnnotationsChange = useCallback((allAnnotations: Highlight[]) => {
setAllAnnotations(allAnnotations)
@ -161,11 +159,6 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
)
}
type SizeToggleProps = {
mode: 'normal' | 'maximized'
setMode: (mode: 'normal' | 'maximized') => void
}
function CloseButton(props: { close: () => void }): JSX.Element {
return (
<Button

View file

@ -13,15 +13,10 @@ import { TickedRangeSlider } from '../../elements/TickedRangeSlider'
import { showSuccessToast } from '../../../lib/toastHelpers'
import { ReaderSettings } from '../../../lib/hooks/useReaderSettings'
import { useCallback, useState } from 'react'
import {
getCurrentLocalTheme,
currentThemeName,
updateTheme,
} from '../../../lib/themeUpdater'
import { updateTheme } from '../../../lib/themeUpdater'
import { LineHeightIncreaseIcon } from '../../elements/images/LineHeightIncreaseIconProps'
import { LineHeightDecreaseIcon } from '../../elements/images/LineHeightDecreaseIcon'
import * as Switch from '@radix-ui/react-switch'
import { Checkbox } from '@radix-ui/react-checkbox'
import { useCurrentTheme } from '../../../lib/hooks/useCurrentTheme'
import { useDarkModeListener } from '../../../lib/hooks/useDarkModeListener'
@ -571,7 +566,7 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
}
function ThemeSelector(): JSX.Element {
const isDarkMode = useDarkModeListener()
useDarkModeListener()
const { currentTheme, setCurrentTheme, resetSystemTheme } = useCurrentTheme()
return (

View file

@ -241,7 +241,7 @@ function Footer(props: FooterProps): JSX.Element {
alignment="center"
distribution="start"
css={{ gap: '8px', fontSize: '12px', pointer: 'cursor' }}
onClick={async (event) => {
onClick={async () => {
switch (textMatch) {
case 'available':
await props.selectEnteredLabel()
@ -414,7 +414,6 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
filteredLabels,
focusedIndex,
createLabelFromFilterText,
router,
toggleLabel,
]
)
@ -423,7 +422,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
const _filterText = inputValue
setInputValue('')
return createLabelFromFilterText(_filterText)
}, [inputValue])
}, [inputValue, setInputValue, createLabelFromFilterText])
const selectEnteredLabel = useCallback(() => {
const label = labels.find(
@ -433,7 +432,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
return Promise.resolve()
}
return toggleLabel(label)
}, [labels, inputValue])
}, [labels, inputValue, toggleLabel])
return (
<VStack

View file

@ -1,8 +1,6 @@
import { Tray } from 'phosphor-react'
import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
import { Button } from '../../elements/Button'
import { HStack } from '../../elements/LayoutPrimitives'
import { TooltipWrapped } from '../../elements/Tooltip'
import { theme } from '../../tokens/stitches.config'
import { ReaderDropdownMenu } from '../../patterns/ReaderDropdownMenu'
import { ArchiveIcon } from '../../elements/icons/ArchiveIcon'

View file

@ -1,22 +1,17 @@
import { useCallback, useRef, useState } from 'react'
import * as Progress from '@radix-ui/react-progress'
import { File, Info } from 'phosphor-react'
import toast from 'react-hot-toast'
import { locale, timeZone } from '../../../lib/dateFormatting'
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { Button } from '../../elements/Button'
import { FormInput } from '../../elements/FormElements'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import {
ModalButtonBar,
ModalContent,
ModalOverlay,
ModalRoot,
ModalTitleBar,
} from '../../elements/ModalPrimitives'
import { CloseButton } from '../../elements/CloseButton'
import { StyledText } from '../../elements/StyledText'
import { styled } from '@stitches/react'
import Dropzone, {
Accept,
@ -76,8 +71,8 @@ export function AddLinkModal(props: AddLinkModalProps): JSX.Element {
<Box css={{ width: '100%' }}>
{selectedTab == 'link' && <AddLinkTab {...props} />}
{selectedTab == 'feed' && <AddFeedTab {...props} />}
{selectedTab == 'opml' && <UploadOPMLTab {...props} />}
{selectedTab == 'pdf' && <UploadPDFTab {...props} />}
{selectedTab == 'opml' && <UploadOPMLTab />}
{selectedTab == 'pdf' && <UploadPDFTab />}
{selectedTab == 'import' && <UploadImportTab {...props} />}
</Box>
</VStack>
@ -95,7 +90,7 @@ const AddLinkTab = (props: AddLinkModalProps): JSX.Element => {
await props.handleLinkSubmission(link, timeZone, locale)
props.onOpenChange(false)
},
[errorMessage, setErrorMessage]
[props, errorMessage, setErrorMessage]
)
return (
@ -144,7 +139,7 @@ const AddFeedTab = (props: AddLinkModalProps): JSX.Element => {
showSuccessToast('New feed has been added.')
props.onOpenChange(false)
},
[errorMessage, setErrorMessage]
[props, errorMessage, setErrorMessage]
)
return (
@ -167,20 +162,17 @@ type AddFromURLProps = {
const AddFromURL = (props: AddFromURLProps): JSX.Element => {
const [url, setURL] = useState('')
const validateURL = useCallback(
(link: string) => {
try {
const url = new URL(link)
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
return false
}
} catch (e) {
const validateURL = useCallback((link: string) => {
try {
const url = new URL(link)
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
return false
}
return true
},
[url]
)
} catch (e) {
return false
}
return true
}, [])
return (
<VStack css={{ width: '100%', height: '180px' }}>
@ -258,7 +250,7 @@ const AddFromURL = (props: AddFromURLProps): JSX.Element => {
)
}
const UploadOPMLTab = (props: AddLinkModalProps): JSX.Element => {
const UploadOPMLTab = (): JSX.Element => {
return (
<VStack
alignment="start"
@ -278,7 +270,7 @@ const UploadOPMLTab = (props: AddLinkModalProps): JSX.Element => {
)
}
const UploadPDFTab = (props: AddLinkModalProps): JSX.Element => {
const UploadPDFTab = (): JSX.Element => {
return (
<VStack
alignment="start"

View file

@ -1,29 +1,8 @@
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import { PinnedSearch } from '../../../pages/settings/pinned-searches'
import { Box } from '../../elements/LayoutPrimitives'
export const DEFAULT_HEADER_HEIGHT = '85px'
// export const useGetHeaderHeight = () => {
// const [hidePinnedSearches] = usePersistedState({
// key: '--library-hide-pinned-searches',
// initialValue: false,
// isSessionStorage: false,
// })
// const [pinnedSearches] = usePersistedState<PinnedSearch[] | null>({
// key: `--library-pinned-searches`,
// initialValue: [],
// isSessionStorage: false,
// })
// if (hidePinnedSearches || !pinnedSearches?.length) {
// return '90px'
// }
// return '90px'
// }
export function HeaderSpacer(): JSX.Element {
// const headerHeight = useGetHeaderHeight()
return (
<Box
css={{

View file

@ -37,28 +37,17 @@ import { EditLibraryItemModal } from './EditItemModals'
import { EmptyLibrary } from './EmptyLibrary'
import { HighlightItemsLayout } from './HighlightsLayout'
import { LibraryFilterMenu } from '../navMenu/LibraryMenu'
import {
LibraryHeader,
MultiSelectMode,
headerControlWidths,
} from './LibraryHeader'
import { LibraryHeader, MultiSelectMode } from './LibraryHeader'
import { UploadModal } from '../UploadModal'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
import {
showErrorToast,
showSuccessToast,
showSuccessToastWithUndo,
} from '../../../lib/toastHelpers'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter'
import { NotebookPresenter } from '../article/NotebookPresenter'
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
import { articleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
import { searchQuery } from '../../../lib/networking/queries/search'
import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon'
import { theme } from '../../tokens/stitches.config'
import { PinnedSearch } from '../../../pages/settings/pinned-searches'
import { PinnedButtons } from './PinnedButtons'
import { PinnedSearch } from '../../../pages/settings/pinned-searches'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
export type LibraryMode = 'reads' | 'highlights'

View file

@ -1,24 +1,15 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { theme } from '../../tokens/stitches.config'
import { FormInput } from '../../elements/FormElements'
import { searchBarCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { locale, timeZone } from '../../../lib/dateFormatting'
import { Button, IconButton } from '../../elements/Button'
import {
FunnelSimple,
MagnifyingGlass,
Prohibit,
Plus,
X,
} from 'phosphor-react'
import { FunnelSimple, MagnifyingGlass, Plus, X } from 'phosphor-react'
import { LayoutType } from './HomeFeedContainer'
import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo'
import { DEFAULT_HEADER_HEIGHT, HeaderSpacer } from './HeaderSpacer'
import { LIBRARY_LEFT_MENU_WIDTH } from '../navMenu/LibraryMenu'
import { CardCheckbox } from '../../patterns/LibraryCards/LibraryCardStyles'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { AddBulkLabelsModal } from '../article/AddBulkLabelsModal'
@ -26,12 +17,6 @@ import { Label } from '../../../lib/networking/fragments/labelFragment'
import { ArchiveIcon } from '../../elements/icons/ArchiveIcon'
import { TrashIcon } from '../../elements/icons/TrashIcon'
import { LabelIcon } from '../../elements/icons/LabelIcon'
import { ListViewIcon } from '../../elements/icons/ListViewIcon'
import { GridViewIcon } from '../../elements/icons/GridViewIcon'
import { CaretDownIcon } from '../../elements/icons/CaretDownIcon'
import { PinnedButtons } from './PinnedButtons'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import { PinnedSearch } from '../../../pages/settings/pinned-searches'
import { HeaderCheckboxIcon } from '../../elements/icons/HeaderCheckboxIcon'
import { HeaderSearchIcon } from '../../elements/icons/HeaderSearchIcon'
import { HeaderToggleGridIcon } from '../../elements/icons/HeaderToggleGridIcon'
@ -134,9 +119,10 @@ function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element {
}}
>
{props.multiSelectMode !== 'off' ? (
<HStack alignment="center" css={{ width: '100% ' }}>
<>
<CheckBoxButton {...props} />
<MultiSelectControls {...props} />
</HStack>
</>
) : (
<HeaderControls {...props} />
)}
@ -144,6 +130,31 @@ function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element {
)
}
const CheckBoxButton = (props: LibraryHeaderProps): JSX.Element => {
return (
<Button
title="Select multiple"
style="plainIcon"
css={{ display: 'flex', '&:hover': { opacity: '1.0' } }}
onClick={(e) => {
switch (props.multiSelectMode) {
case 'off':
case 'none':
case 'some':
props.setMultiSelectMode('visible')
break
default:
props.setMultiSelectMode('off')
break
}
e.preventDefault()
}}
>
<HeaderCheckboxIcon multiSelectMode={props.multiSelectMode} />
</Button>
)
}
const HeaderControls = (props: LibraryHeaderProps): JSX.Element => {
const [showSearchBar, setShowSearchBar] = useState(false)
return (
@ -156,17 +167,7 @@ const HeaderControls = (props: LibraryHeaderProps): JSX.Element => {
>
<MenuHeaderButton {...props} />
</SpanBox>
<Button
title="Select multiple"
style="plainIcon"
css={{ display: 'flex', '&:hover': { opacity: '1.0' } }}
onClick={(e) => {
props.setMultiSelectMode('visible')
e.preventDefault()
}}
>
<HeaderCheckboxIcon />
</Button>
<CheckBoxButton {...props} />
{showSearchBar ? (
<SearchBox {...props} setShowSearchBar={setShowSearchBar} />
@ -441,262 +442,125 @@ type ControlButtonBoxProps = {
function MultiSelectControls(props: ControlButtonBoxProps): JSX.Element {
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showLabelsModal, setShowLabelsModal] = useState(false)
return (
<HStack alignment="center" distribution="center" css={{ gap: '20px' }}>
<Button
style="outline"
onClick={(e) => {
props.performMultiSelectAction(BulkAction.ARCHIVE)
e.preventDefault()
}}
>
<ArchiveIcon
size={20}
color={theme.colors.thTextContrast2.toString()}
/>
<SpanBox css={{ '@lgDown': { display: 'none' } }}>Archive</SpanBox>
</Button>
<Button
style="outline"
onClick={(e) => {
setShowLabelsModal(true)
e.preventDefault()
}}
>
<LabelIcon size={20} color={theme.colors.thTextContrast2.toString()} />
<SpanBox css={{ '@lgDown': { display: 'none' } }}>Add Labels</SpanBox>
</Button>
<Button
style="outline"
onClick={(e) => {
setShowConfirmDelete(true)
e.preventDefault()
}}
>
<TrashIcon size={20} color={theme.colors.thTextContrast2.toString()} />
<SpanBox css={{ '@lgDown': { display: 'none' } }}>Delete</SpanBox>
</Button>
<Button
style="cancel"
onClick={(e) => {
props.setMultiSelectMode('off')
e.preventDefault()
}}
>
<Prohibit
width={20}
height={20}
color={theme.colors.thTextContrast2.toString()}
/>
<SpanBox css={{ '@lgDown': { display: 'none' } }}>Cancel</SpanBox>
</Button>
{showConfirmDelete && (
<ConfirmationModal
message={`You are about to delete ${props.numItemsSelected} items. All associated notes and highlights will be deleted.`}
acceptButtonLabel={'Delete'}
onAccept={() => {
props.performMultiSelectAction(BulkAction.DELETE)
}}
onOpenChange={(open: boolean) => {
setShowConfirmDelete(false)
}}
/>
)}
{showLabelsModal && (
<AddBulkLabelsModal
bulkSetLabels={(labels: Label[]) => {
const labelIds = labels.map((l) => l.id)
props.performMultiSelectAction(BulkAction.ADD_LABELS, labelIds)
}}
onOpenChange={(open: boolean) => {
setShowLabelsModal(false)
}}
/>
)}
</HStack>
)
}
type SearchControlButtonBoxProps = ControlButtonBoxProps
function SearchControlButtonBox(
props: SearchControlButtonBoxProps
): JSX.Element {
return (
<>
<Button
style="plainIcon"
css={{ display: 'flex', marginLeft: 'auto' }}
onClick={(e) => {
props.updateLayout(
props.layout == 'GRID_LAYOUT' ? 'LIST_LAYOUT' : 'GRID_LAYOUT'
)
e.preventDefault()
}}
>
{props.layout == 'GRID_LAYOUT' ? (
<ListViewIcon size={30} color={'#898989'} />
) : (
<GridViewIcon size={30} color={'#898989'} />
)}
</Button>
</>
)
}
const MuliSelectControl = (props: ControlButtonBoxProps): JSX.Element => {
const [isChecked, setIsChecked] = useState(false)
useEffect(() => {
if (props.multiSelectMode === 'off' || props.multiSelectMode === 'none') {
setIsChecked(false)
}
}, [props.multiSelectMode])
const compact = false
return (
<Box
css={{
display: 'flex',
padding: '10px',
height: '38px',
width: '100%',
maxWidth: '521px',
bg: '$thLibrarySearchbox',
borderRadius: '6px',
boxShadow: '0px 1px 2px 0px rgba(0, 0, 0, 0.05);',
'@mdDown': {
mx: '5px',
},
borderRadius: '100px',
border: '2px solid transparent',
boxShadow:
'0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);',
}}
>
<SpanBox
<HStack
alignment="center"
distribution="end"
css={{
flex: 1,
display: 'flex',
gap: '5px',
alignItems: 'center',
width: '100%',
height: '100%',
gap: '15px',
pl: compact ? '10px' : '15px',
pr: compact ? '5px' : '10px',
}}
onClick={(e) => {
e.preventDefault()
}}
>
<CardCheckbox
isChecked={isChecked}
handleChanged={() => {
const newValue = !isChecked
props.setMultiSelectMode(newValue ? 'visible' : 'off')
setIsChecked(newValue)
<SpanBox
css={{
fontSize: '14px',
fontFamily: '$display',
marginRight: 'auto',
}}
/>
<SpanBox css={{ display: 'flex', pb: '2px' }}>
<Dropdown
triggerElement={
<CaretDownIcon
size={9}
color={theme.colors.graySolid.toString()}
/>
}
>
<DropdownOption
onSelect={() => {
setIsChecked(true)
props.setMultiSelectMode('visible')
}}
title="All"
/>
{/* <DropdownOption
onSelect={() => {
setIsChecked(true)
props.setMultiSelectMode('search')
}}
title="All matching search"
/> */}
</Dropdown>
>
{props.numItemsSelected} items selected
</SpanBox>
</SpanBox>
<Button
title="Archive"
css={{ display: 'flex' }}
style="plainIcon"
onClick={(e) => {
props.performMultiSelectAction(BulkAction.ARCHIVE)
e.preventDefault()
}}
>
<ArchiveIcon
size={20}
color={theme.colors.thTextContrast2.toString()}
/>
</Button>
<Button
title="Add labels"
css={{ display: 'flex' }}
style="plainIcon"
onClick={(e) => {
setShowLabelsModal(true)
e.preventDefault()
}}
>
<LabelIcon
size={20}
color={theme.colors.thTextContrast2.toString()}
/>
</Button>
<Button
title="Delete"
css={{ display: 'flex' }}
style="plainIcon"
onClick={(e) => {
setShowConfirmDelete(true)
e.preventDefault()
}}
>
<TrashIcon
size={20}
color={theme.colors.thTextContrast2.toString()}
/>
</Button>
{showConfirmDelete && (
<ConfirmationModal
message={`You are about to delete ${props.numItemsSelected} items. All associated notes and highlights will be deleted.`}
acceptButtonLabel={'Delete'}
onAccept={() => {
props.performMultiSelectAction(BulkAction.DELETE)
}}
onOpenChange={(open: boolean) => {
setShowConfirmDelete(false)
}}
/>
)}
{showLabelsModal && (
<AddBulkLabelsModal
bulkSetLabels={(labels: Label[]) => {
const labelIds = labels.map((l) => l.id)
props.performMultiSelectAction(BulkAction.ADD_LABELS, labelIds)
}}
onOpenChange={(open: boolean) => {
setShowLabelsModal(false)
}}
/>
)}
<Button
title="Cancel"
css={{ display: 'flex', mr: '10px' }}
style="plainIcon"
onClick={(event) => {
props.setMultiSelectMode('off')
}}
tabIndex={-1}
>
<X
width={20}
height={20}
color={theme.colors.thTextContrast2.toString()}
/>
</Button>
</HStack>
</Box>
)
}
// function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
// return (
// <>
// <SpanBox
// css={{
// flex: 1,
// display: 'flex',
// gap: '2px',
// alignItems: 'center',
// }}
// >
// <SpanBox
// css={{
// color: '#55B938',
// paddingLeft: '5px',
// fontSize: '12px',
// fontWeight: '600',
// fontFamily: '$inter',
// '@xlgDown': {
// paddingLeft: '5px',
// },
// }}
// >
// {props.numItemsSelected}{' '}
// <SpanBox
// css={{
// '@media (max-width: 1280px)': { display: 'none' },
// }}
// >
// selected
// </SpanBox>
// </SpanBox>
// </SpanBox>
// )}
// {/* {props.multiSelectMode !== 'off' ? (
// <>
// <MultiSelectControls {...props} />
// <SpanBox css={{ flex: 1 }}></SpanBox>
// </>
// ) : (
// <SearchControlButtonBox {...props} />
// )} */}
// {/* </HStack> */}
// // {props.setShowInlineSearch && props.multiSelectMode === 'off' && (
// // <HStack
// // alignment="center"
// // distribution="end"
// // css={{
// // marginLeft: 'auto',
// // marginRight: '20px',
// // width: '100px',
// // height: '100%',
// // gap: '20px',
// // '@md': {
// // display: 'none',
// // },
// // }}
// // >
// // <Button
// // style="ghost"
// // onClick={() => {
// // props.setShowInlineSearch && props.setShowInlineSearch(true)
// // }}
// // css={{
// // display: 'flex',
// // }}
// // >
// // <MagnifyingGlass
// // size={20}
// // color={theme.colors.graySolid.toString()}
// // />
// // </Button>
// // <PrimaryDropdown
// // showThemeSection={true}
// // layout={props.layout}
// // updateLayout={props.updateLayout}
// // />
// // </HStack>
// // )}
// </>
// )
// }

View file

@ -1,185 +0,0 @@
import { ReactNode, useEffect, useRef, useState } from 'react'
import { StyledText } from '../../elements/StyledText'
import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
import { SearchIcon } from '../../elements/images/SearchIcon'
import { theme } from '../../tokens/stitches.config'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { FormInput } from '../../elements/FormElements'
import { searchBarCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { Button } from '../../elements/Button'
import { X } from 'phosphor-react'
type LibrarySearchBarProps = {
searchTerm?: string
applySearchQuery: (searchQuery: string) => void
}
type LibraryFilter =
| 'in:inbox'
| 'in:all'
| 'in:archive'
| 'type:file'
| 'type:highlights'
| `saved:${string}`
| `sort:read`
// get last week's date
const recentlySavedStartDate = new Date(
new Date().getTime() - 7 * 24 * 60 * 60 * 1000
).toLocaleDateString('en-US')
const FOCUSED_BOXSHADOW = '0px 0px 2px 2px rgba(255, 234, 159, 0.56)'
export function LibrarySearchBar(props: LibrarySearchBarProps): JSX.Element {
const [focused, setFocused] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const [searchTerm, setSearchTerm] = useState(props.searchTerm || '')
useEffect(() => {
setSearchTerm(props.searchTerm || '')
}, [props.searchTerm])
return (
<VStack css={{ width: '100%' }}>
<HStack
alignment="center"
distribution="start"
css={{
bg: '$grayBg',
width: '100%',
borderRadius: '8px',
flex: 1,
border: '1px solid $grayBorder',
maxHeight: '40px',
boxShadow: focused ? FOCUSED_BOXSHADOW : 'unset',
}}
>
<DropdownFilterMenu
triggerElement={
<HStack
alignment="center"
distribution="between"
css={{
width: '5em',
height: '100%',
bg: '$grayBgActive',
px: '$3',
borderRadius: '8px 0px 0px 8px',
'&:hover:': { bg: '$grayBgHover' },
}}
>
<StyledText style="controlButton" css={{ m: '8px', ml: '0px' }}>
Filters
</StyledText>
<Box
css={{ width: '8px', color: '$grayText' }}
className="dropdown-arrow"
/>
</HStack>
}
onFilterChange={(filter: LibraryFilter) => {
props.applySearchQuery(filter)
}}
/>
<Box css={{ mx: '$2', pt: '$1' }}>
<span>
<SearchIcon strokeColor={theme.colors.graySolid.toString()} />
</span>
</Box>
<Box css={{ width: '100%' }}>
<form
onSubmit={(event) => {
event.preventDefault()
props.applySearchQuery(searchTerm || '')
inputRef.current?.blur()
}}
>
<FormInput
ref={inputRef}
type="text"
value={searchTerm}
placeholder="Search"
onFocus={(event) => {
event.target.select()
setFocused(true)
}}
onBlur={() => {
setFocused(false)
}}
onChange={(event) => {
setSearchTerm(event.target.value)
}}
/>
</form>
</Box>
{searchTerm && (
<Button
style="plainIcon"
onClick={(event) => {
event.preventDefault()
setSearchTerm('')
props.applySearchQuery('')
inputRef.current?.blur()
}}
css={{
display: 'flex',
flexDirection: 'row',
mr: '8px',
height: '100%',
alignItems: 'center',
}}
>
<X
width={16}
height={16}
color={theme.colors.grayTextContrast.toString()}
/>
</Button>
)}
</HStack>
</VStack>
)
}
type DropdownFilterMenuProps = {
triggerElement: ReactNode
onFilterChange: (filter: LibraryFilter) => void
}
export function DropdownFilterMenu(
props: DropdownFilterMenuProps
): JSX.Element {
return (
<Dropdown triggerElement={props.triggerElement}>
<DropdownOption
onSelect={() => props.onFilterChange('in:inbox')}
title="Inbox"
/>
<DropdownOption
onSelect={() => props.onFilterChange('in:all')}
title="All"
/>
<DropdownOption
onSelect={() => props.onFilterChange('in:archive')}
title="Archived"
/>
<DropdownOption
onSelect={() => props.onFilterChange('type:file')}
title="Files"
/>
<DropdownOption
onSelect={() => props.onFilterChange('type:highlights')}
title="Highlights"
/>
<DropdownOption
onSelect={() => props.onFilterChange(`saved:${recentlySavedStartDate}`)}
title="Recently Saved"
/>
<DropdownOption
onSelect={() => props.onFilterChange(`sort:read`)}
title="Recently Read"
/>
</Dropdown>
)
}

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { useEffect, useState } from 'react'
import { HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { theme } from '../../tokens/stitches.config'
import { Button } from '../../elements/Button'

View file

@ -19,9 +19,6 @@ import { SavedSearch } from '../../../lib/networking/fragments/savedSearchFragme
import { ToggleCaretDownIcon } from '../../elements/icons/ToggleCaretDownIcon'
import Link from 'next/link'
import { ToggleCaretRightIcon } from '../../elements/icons/ToggleCaretRightIcon'
import { SplitButton } from '../../elements/SplitButton'
import { AvatarDropdown } from '../../elements/AvatarDropdown'
import { PrimaryDropdown } from '../PrimaryDropdown'
import { NavMenuFooter } from './Footer'
export const LIBRARY_LEFT_MENU_WIDTH = '275px'

View file

@ -8,7 +8,6 @@ import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import { SettingsLayout } from '../SettingsLayout'
import { SuggestionBox } from '../../elements/SuggestionBox'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import { FeatureHelpBox } from '../../elements/FeatureHelpBox'

View file

@ -14,10 +14,10 @@ export const useCopyLink = (
url || (typeof window !== 'undefined' ? window.location.toString() : '')
const copyLink = useCallback(async (): Promise<void> => {
const res = await navigator.clipboard.writeText(link)
await navigator.clipboard.writeText(link)
setIsLinkCopied(true)
window.setTimeout(() => setIsLinkCopied(false), 5000)
}, [link, type])
}, [link])
return { link, copyLink, isLinkCopied }
}

View file

@ -72,7 +72,12 @@ export function useCurrentTheme() {
setCurrentThemeInternal(themeId)
updateThemeLocally(themeId)
},
[setCurrentThemeInternal]
[
currentThemeInternal,
setCurrentThemeInternal,
setPreferredDarkTheme,
setPreferredLightTheme,
]
)
// This is used when the user disables "System" theme

View file

@ -39,8 +39,6 @@ export type ReaderSettings = {
export const useReaderSettings = (): ReaderSettings => {
applyStoredTheme()
const [, updateState] = useState({})
const [fontSize, setFontSize] = usePersistedState({
key: 'fontSize',
initialValue: 20,

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react'
import { useCallback, useEffect, useReducer } from 'react'
import { setLabelsMutation } from '../networking/mutations/setLabelsMutation'
import { Label } from '../networking/fragments/labelFragment'
import { showErrorToast } from '../toastHelpers'

View file

@ -1,6 +1,5 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { State } from '../fragments/articleFragment'
export type UpdateUserInput = {
name: string

View file

@ -1,6 +1,5 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { State } from '../fragments/articleFragment'
export type UpdateUserProfileInput = {
userId: string

View file

@ -1,11 +1,6 @@
import { gql } from 'graphql-request'
import useSWRImmutable, { Cache } from 'swr'
import {
gqlFetcher,
makeGqlFetcher,
RequestContext,
ssrFetcher,
} from '../networkHelpers'
import { Cache } from 'swr'
import { gqlFetcher, makeGqlFetcher } from '../networkHelpers'
import {
articleFragment,
ContentReader,

View file

@ -32,7 +32,6 @@
"@stitches/react": "^1.2.5",
"antd": "4.24.3",
"axios": "^1.2.0",
"color2k": "^2.0.0",
"cookie": "^0.5.0",
"csv-file-validator": "^2.1.0",
"dayjs": "^1.11.7",

View file

@ -1,16 +1,7 @@
import { useRouter } from 'next/router'
import {
useGetArticleQuery,
ArticleAttributes,
} from '../../../lib/networking/queries/useGetArticleQuery'
import { useGetArticleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
import { applyStoredTheme } from '../../../lib/themeUpdater'
import { useMemo } from 'react'
import {
EmptySettingsRow,
SettingsTable,
SettingsTableRow,
} from '../../../components/templates/settings/SettingsTable'
import { StyledText } from '../../../components/elements/StyledText'
import {
Box,
HStack,

View file

@ -1,9 +1,8 @@
import { useGetArticleQuery } from '../../../../lib/networking/queries/useGetArticleQuery'
import { Box, VStack } from './../../../../components/elements/LayoutPrimitives'
import { ArticleContainer } from './../../../../components/templates/article/ArticleContainer'
import { useRef, useState, useEffect } from 'react'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import { webBaseURL } from '../../../../lib/appConfig'
import { LoadingView } from '../../../../components/patterns/LoadingView'
import { cookieValue } from '../../../../lib/cookieHelpers'
import { applyStoredTheme } from '../../../../lib/themeUpdater'

View file

@ -9,7 +9,7 @@ import { SettingsLayout } from '../../components/templates/SettingsLayout'
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
import { Button } from '../../components/elements/Button'
import { HStack, VStack } from '../../components/elements/LayoutPrimitives'
import { VStack } from '../../components/elements/LayoutPrimitives'
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
import { Loader } from '../../components/templates/SavingRequest'
import { deleteAccountMutation } from '../../lib/networking/mutations/deleteAccountMutation'

View file

@ -22,7 +22,6 @@ import {
SettingsTableRow,
} from '../../../components/templates/settings/SettingsTable'
import { ConfirmationModal } from '../../../components/patterns/ConfirmationModal'
import { SuggestionBox } from '../../../components/elements/SuggestionBox'
enum TextType {
EmailAddress,

View file

@ -17,13 +17,7 @@ import { applyStoredTheme, isDarkTheme } from '../../lib/themeUpdater'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { Label, LabelColor } from '../../lib/networking/fragments/labelFragment'
import { StyledText } from '../../components/elements/StyledText'
import {
ArrowClockwise,
DotsThree,
PencilSimple,
Trash,
Plus,
} from 'phosphor-react'
import { ArrowClockwise, DotsThree, PencilSimple, Trash } from 'phosphor-react'
import { GenericTableCardProps } from '../../utils/settings-page/labels/types'
import { labelColorObjects } from '../../utils/settings-page/labels/labelColorObjects'
import { TooltipWrapped } from '../../components/elements/Tooltip'
@ -35,7 +29,6 @@ import {
import { LabelChip } from '../../components/elements/LabelChip'
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
import { InfoLink } from '../../components/elements/InfoLink'
import { SuggestionBox } from '../../components/elements/SuggestionBox'
import { usePersistedState } from '../../lib/hooks/usePersistedState'
import { FeatureHelpBox } from '../../components/elements/FeatureHelpBox'

View file

@ -11842,11 +11842,6 @@ color-support@^1.1.2, color-support@^1.1.3:
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
color2k@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/color2k/-/color2k-2.0.2.tgz#ac2b4aea11c822a6bcb70c768b5a289f4fffcebb"
integrity sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==
color@3.0.x:
version "3.0.0"
resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a"