Warnings clean up
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { getLuminance, lighten, parseToRgba, toHsla } from 'color2k'
|
||||
import { getLuminance } from 'color2k'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button } from './Button'
|
||||
import { SpanBox, HStack } from './LayoutPrimitives'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useMemo, useRef, useState } from 'react'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { styled } from '../tokens/stitches.config'
|
||||
import { Box, HStack, SpanBox } from './LayoutPrimitives'
|
||||
import { Box, HStack } from './LayoutPrimitives'
|
||||
import { StyledText } from './StyledText'
|
||||
import {
|
||||
LabelColorDropdownProps,
|
||||
@ -116,7 +116,6 @@ export const LabelColorDropdown = (props: LabelColorDropdownProps) => {
|
||||
}
|
||||
|
||||
function LabelOption(props: LabelOptionProps): JSX.Element {
|
||||
const { color, isDropdownOption, isCreateMode, labelId } = props
|
||||
return (
|
||||
<HStack
|
||||
alignment="center"
|
||||
@ -156,19 +155,6 @@ function LabelOption(props: LabelOptionProps): JSX.Element {
|
||||
)
|
||||
}
|
||||
|
||||
function getLabelColorObject(color: LabelColor) {
|
||||
if (labelColorObjects[color]) {
|
||||
return labelColorObjects[color]
|
||||
}
|
||||
const colorObject: LabelColorObject = {
|
||||
colorName: 'Custom',
|
||||
text: color,
|
||||
border: color + '66',
|
||||
background: color + '0D',
|
||||
}
|
||||
return colorObject
|
||||
}
|
||||
|
||||
function LabelColorIcon(props: { color: string }): JSX.Element {
|
||||
return (
|
||||
<Box
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import AutosizeInput from 'react-input-autosize'
|
||||
import { Box, SpanBox } from './LayoutPrimitives'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { Label } from '../../lib/networking/fragments/labelFragment'
|
||||
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
|
||||
import { LabelChip } from './LabelChip'
|
||||
import { isTouchScreenDevice } from '../../lib/deviceType'
|
||||
import { EditLabelLabelChip } from './EditLabelChip'
|
||||
import { LabelsDispatcher } from '../../lib/hooks/useSetPageLabels'
|
||||
|
||||
type LabelsPickerProps = {
|
||||
selectedLabels: Label[]
|
||||
@ -16,7 +16,7 @@ type LabelsPickerProps = {
|
||||
clearInputState: () => void
|
||||
|
||||
onFocus?: () => void
|
||||
setSelectedLabels: (labels: Label[]) => void
|
||||
dispatchLabels: LabelsDispatcher
|
||||
|
||||
deleteLastLabel: () => void
|
||||
selectOrCreateLabel: (value: string) => void
|
||||
@ -33,32 +33,42 @@ type LabelsPickerProps = {
|
||||
export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
|
||||
const inputRef = useRef<HTMLInputElement | null>()
|
||||
const availableLabels = useGetLabelsQuery()
|
||||
const {
|
||||
focused,
|
||||
inputValue,
|
||||
tabCount,
|
||||
tabStartValue,
|
||||
selectedLabels,
|
||||
setInputValue,
|
||||
setTabCount,
|
||||
setTabStartValue,
|
||||
} = props
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTouchScreenDevice() && props.focused && inputRef.current) {
|
||||
if (!isTouchScreenDevice() && focused && inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, [props.focused])
|
||||
}, [focused])
|
||||
|
||||
const autoComplete = useCallback(() => {
|
||||
const lowerCasedValue = props.inputValue.toLowerCase()
|
||||
const lowerCasedValue = inputValue.toLowerCase()
|
||||
|
||||
if (lowerCasedValue.length < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
let _tabCount = props.tabCount
|
||||
let _tabStartValue = props.tabStartValue.toLowerCase()
|
||||
let _tabCount = tabCount
|
||||
let _tabStartValue = tabStartValue.toLowerCase()
|
||||
|
||||
if (_tabCount === -1) {
|
||||
_tabCount = 0
|
||||
_tabStartValue = lowerCasedValue
|
||||
|
||||
props.setTabCount(0)
|
||||
props.setTabStartValue(lowerCasedValue)
|
||||
setTabCount(0)
|
||||
setTabStartValue(lowerCasedValue)
|
||||
} else {
|
||||
_tabCount = props.tabCount + 1
|
||||
props.setTabCount(_tabCount)
|
||||
_tabCount = tabCount + 1
|
||||
setTabCount(_tabCount)
|
||||
}
|
||||
|
||||
const matches = availableLabels.labels.filter((l) =>
|
||||
@ -66,21 +76,29 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
|
||||
)
|
||||
|
||||
if (_tabCount < matches.length) {
|
||||
props.setInputValue(matches[_tabCount].name)
|
||||
setInputValue(matches[_tabCount].name)
|
||||
} else if (matches.length > 0) {
|
||||
props.setTabCount(0)
|
||||
props.setInputValue(matches[0].name)
|
||||
setTabCount(0)
|
||||
setInputValue(matches[0].name)
|
||||
}
|
||||
}, [props.inputValue, availableLabels, props.tabCount, props.tabStartValue])
|
||||
}, [
|
||||
inputValue,
|
||||
availableLabels,
|
||||
tabCount,
|
||||
tabStartValue,
|
||||
setInputValue,
|
||||
setTabCount,
|
||||
setTabStartValue,
|
||||
])
|
||||
|
||||
const clearTabState = useCallback(() => {
|
||||
props.setTabCount(-1)
|
||||
props.setTabStartValue('')
|
||||
}, [])
|
||||
setTabCount(-1)
|
||||
setTabStartValue('')
|
||||
}, [setTabCount, setTabStartValue])
|
||||
|
||||
const isEmpty = useMemo(() => {
|
||||
return props.selectedLabels.length === 0 && props.inputValue.length === 0
|
||||
}, [props.inputValue, props.selectedLabels])
|
||||
return selectedLabels.length === 0 && inputValue.length === 0
|
||||
}, [inputValue, selectedLabels])
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -129,6 +147,7 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
|
||||
inputRef.current?.focus()
|
||||
props.setHighlightLastLabel(false)
|
||||
inputRef.current?.setSelectionRange(0, inputRef.current?.value.length)
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
{props.selectedLabels.map((label, idx) => (
|
||||
@ -144,7 +163,10 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
|
||||
if (idx !== -1) {
|
||||
const _selectedLabels = props.selectedLabels
|
||||
_selectedLabels.splice(idx, 1)
|
||||
props.setSelectedLabels([..._selectedLabels])
|
||||
props.dispatchLabels({
|
||||
type: 'SAVE',
|
||||
labels: [..._selectedLabels],
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -2,7 +2,6 @@ import type { ReactNode } from 'react'
|
||||
import { Dropdown, DropdownOption } from '../elements/DropdownElements'
|
||||
import { LibraryItemNode } from '../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { isVipUser } from '../../lib/featureFlag'
|
||||
|
||||
export type CardMenuDropdownAction =
|
||||
| 'mark-read'
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import { ReactNode, useMemo, useState } from 'react'
|
||||
import { HStack, VStack } from './../elements/LayoutPrimitives'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownOption,
|
||||
DropdownSeparator,
|
||||
} from '../elements/DropdownElements'
|
||||
import { Dropdown, DropdownOption } from '../elements/DropdownElements'
|
||||
import { StyledText } from '../elements/StyledText'
|
||||
import { Button } from '../elements/Button'
|
||||
import { currentThemeName } from '../../lib/themeUpdater'
|
||||
|
||||
@ -4,7 +4,7 @@ import type { LinkedItemCardProps } from './CardTypes'
|
||||
import { CoverImage } from '../../elements/CoverImage'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { DotsThreeVertical } from 'phosphor-react'
|
||||
import Link from 'next/link'
|
||||
import { CardMenu } from '../CardMenu'
|
||||
@ -104,12 +104,13 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
|
||||
}
|
||||
|
||||
const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
|
||||
const { isChecked, setIsChecked, item } = props
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const originText = siteName(props.item.originalArticleUrl, props.item.url)
|
||||
|
||||
const handleCheckChanged = useCallback(() => {
|
||||
props.setIsChecked(props.item.id, !props.isChecked)
|
||||
}, [props.isChecked])
|
||||
setIsChecked(item.id, !isChecked)
|
||||
}, [setIsChecked, isChecked])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { CaretDown, CaretUp } from 'phosphor-react'
|
||||
import { MetaStyle, timeAgo, TitleStyle } from './LibraryCardStyles'
|
||||
import { styled } from '@stitches/react'
|
||||
|
||||
@ -76,12 +76,13 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
|
||||
export function LibraryListCardContent(
|
||||
props: LinkedItemCardProps
|
||||
): JSX.Element {
|
||||
const { isChecked, setIsChecked, item } = props
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const originText = siteName(props.item.originalArticleUrl, props.item.url)
|
||||
|
||||
const handleCheckChanged = useCallback(() => {
|
||||
props.setIsChecked(props.item.id, !props.isChecked)
|
||||
}, [props.isChecked])
|
||||
setIsChecked(item.id, !isChecked)
|
||||
}, [isChecked, setIsChecked, item])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Box, HStack, VStack } from '../elements/LayoutPrimitives'
|
||||
import { Box, HStack } from '../elements/LayoutPrimitives'
|
||||
import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo'
|
||||
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { PrimaryDropdown } from '../templates/PrimaryDropdown'
|
||||
|
||||
@ -13,7 +13,7 @@ import * as Progress from '@radix-ui/react-progress'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
import { uploadFileRequestMutation } from '../../lib/networking/mutations/uploadFileMutation'
|
||||
import axios from 'axios'
|
||||
import { CheckCircle, File } from 'phosphor-react'
|
||||
import { File } from 'phosphor-react'
|
||||
import { showErrorToast } from '../../lib/toastHelpers'
|
||||
|
||||
const DragnDropContainer = styled('div', {
|
||||
|
||||
@ -1,23 +1,12 @@
|
||||
import { Separator } from '@radix-ui/react-separator'
|
||||
import {
|
||||
ArchiveBox,
|
||||
Notebook,
|
||||
Info,
|
||||
TagSimple,
|
||||
Trash,
|
||||
Tray,
|
||||
Tag,
|
||||
} from 'phosphor-react'
|
||||
import { ArchiveBox, Notebook, Info, Trash, Tray, Tag } from 'phosphor-react'
|
||||
import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { Box, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { TooltipWrapped } from '../../elements/Tooltip'
|
||||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
import { useReaderSettings } from '../../../lib/hooks/useReaderSettings'
|
||||
import { ReaderSettings } from '../../../lib/hooks/useReaderSettings'
|
||||
import { useRef } from 'react'
|
||||
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { SetLabelsModal } from './SetLabelsModal'
|
||||
|
||||
export type ArticleActionsMenuLayout = 'top' | 'side'
|
||||
|
||||
@ -25,6 +14,7 @@ type ArticleActionsMenuProps = {
|
||||
article?: ArticleAttributes
|
||||
layout: ArticleActionsMenuLayout
|
||||
showReaderDisplaySettings?: boolean
|
||||
readerSettings: ReaderSettings
|
||||
articleActionHandler: (action: string, arg?: unknown) => void
|
||||
}
|
||||
|
||||
@ -45,7 +35,6 @@ const MenuSeparator = (props: MenuSeparatorProps): JSX.Element => {
|
||||
export function ArticleActionsMenu(
|
||||
props: ArticleActionsMenuProps
|
||||
): JSX.Element {
|
||||
const readerSettings = useReaderSettings()
|
||||
const displaySettingsButtonRef = useRef<HTMLElement | null>(null)
|
||||
|
||||
return (
|
||||
@ -73,7 +62,7 @@ export function ArticleActionsMenu(
|
||||
<>
|
||||
<Button
|
||||
style="articleActionIcon"
|
||||
onClick={() => readerSettings.setShowSetLabelsModal(true)}
|
||||
onClick={() => props.readerSettings.setShowSetLabelsModal(true)}
|
||||
>
|
||||
<TooltipWrapped
|
||||
tooltipContent="Edit labels (l)"
|
||||
@ -216,30 +205,6 @@ export function ArticleActionsMenu(
|
||||
<DotsThree size={24} color={theme.colors.readerFont.toString()} />
|
||||
</Button> */}
|
||||
</Box>
|
||||
|
||||
{props.article && readerSettings.showSetLabelsModal && (
|
||||
<SetLabelsModal
|
||||
provider={props.article}
|
||||
onOpenChange={(open: boolean) => {
|
||||
readerSettings.setShowSetLabelsModal(false)
|
||||
}}
|
||||
onLabelsUpdated={(labels: Label[]) => {
|
||||
props.articleActionHandler('refreshLabels', labels)
|
||||
}}
|
||||
save={async (labels: Label[]) => {
|
||||
if (props.article?.id) {
|
||||
const result =
|
||||
(await setLabelsMutation(
|
||||
props.article?.id,
|
||||
labels.map((l) => l.id)
|
||||
)) ?? []
|
||||
props.article.labels = result
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
return Promise.resolve(labels)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -12,19 +12,13 @@ import { Button } from '../../elements/Button'
|
||||
import { useEffect, useState, useRef, useMemo, useCallback } from 'react'
|
||||
import { ReportIssuesModal } from './ReportIssuesModal'
|
||||
import { reportIssueMutation } from '../../../lib/networking/mutations/reportIssueMutation'
|
||||
import {
|
||||
currentTheme,
|
||||
updateTheme,
|
||||
updateThemeLocally,
|
||||
} from '../../../lib/themeUpdater'
|
||||
import { updateTheme, updateThemeLocally } from '../../../lib/themeUpdater'
|
||||
import { ArticleMutations } from '../../../lib/articleActions'
|
||||
import { LabelChip } from '../../elements/LabelChip'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { Recommendation } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { Avatar } from '../../elements/Avatar'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import Downshift from 'downshift'
|
||||
import { LabelsPicker } from '../../elements/LabelsPicker'
|
||||
|
||||
type ArticleContainerProps = {
|
||||
viewer: UserBasicData
|
||||
@ -145,9 +139,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
||||
|
||||
const updateFontSize = useCallback(
|
||||
(newFontSize: number) => {
|
||||
if (fontSize !== newFontSize) {
|
||||
setFontSize(newFontSize)
|
||||
}
|
||||
setFontSize(newFontSize)
|
||||
},
|
||||
[setFontSize]
|
||||
)
|
||||
@ -155,7 +147,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
||||
useEffect(() => {
|
||||
setLabels(props.labels)
|
||||
updateFontSize(props.fontSize ?? 20)
|
||||
}, [props.labels, props.fontSize])
|
||||
}, [props.labels, props.fontSize, updateFontSize])
|
||||
|
||||
// Listen for preference change events sent from host apps (ios, macos...)
|
||||
useEffect(() => {
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import {
|
||||
LibraryItem,
|
||||
ReadableItem,
|
||||
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { HighlightView } from '../../patterns/HighlightView'
|
||||
|
||||
@ -22,12 +22,9 @@ import { NotebookModal } from './NotebookModal'
|
||||
import { showErrorToast } from '../../../lib/toastHelpers'
|
||||
import { ArticleMutations } from '../../../lib/articleActions'
|
||||
import { isTouchScreenDevice } from '../../../lib/deviceType'
|
||||
import { SetLabelsModal } from './SetLabelsModal'
|
||||
import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { useRegisterActions } from 'kbar'
|
||||
import { SetHighlightLabelsModalPresenter } from './SetLabelsModalPresenter'
|
||||
|
||||
type HighlightsLayerProps = {
|
||||
viewer: UserBasicData
|
||||
@ -673,18 +670,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
|
||||
if (labelsTarget) {
|
||||
return (
|
||||
<SetLabelsModal
|
||||
provider={labelsTarget}
|
||||
onOpenChange={function (): void {
|
||||
setLabelsTarget(undefined)
|
||||
}}
|
||||
save={function (labels: Label[]): Promise<Label[] | undefined> {
|
||||
const result = setLabelsForHighlight(
|
||||
labelsTarget.id,
|
||||
labels.map((label) => label.id)
|
||||
)
|
||||
return result
|
||||
}}
|
||||
<SetHighlightLabelsModalPresenter
|
||||
highlight={labelsTarget}
|
||||
highlightId={labelsTarget.id}
|
||||
onOpenChange={() => setLabelsTarget(undefined)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,27 +4,21 @@ import { theme } from '../../tokens/stitches.config'
|
||||
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { BookOpen, PencilLine, X } from 'phosphor-react'
|
||||
import { SetLabelsModal } from './SetLabelsModal'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight'
|
||||
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { diff_match_patch } from 'diff-match-patch'
|
||||
import { highlightsAsMarkdown } from '../homeFeed/HighlightItem'
|
||||
import 'react-markdown-editor-lite/lib/index.css'
|
||||
import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
|
||||
import { HighlightNoteBox, MarkdownNote } from '../../patterns/HighlightNotes'
|
||||
import { HighlightNoteBox } from '../../patterns/HighlightNotes'
|
||||
import { HighlightViewItem } from './HighlightViewItem'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { TrashIcon } from '../../elements/images/TrashIcon'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import {
|
||||
LibraryItem,
|
||||
ReadableItem,
|
||||
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { SetHighlightLabelsModalPresenter } from './SetLabelsModalPresenter'
|
||||
|
||||
type NotebookProps = {
|
||||
viewer: UserBasicData
|
||||
@ -289,6 +283,7 @@ export function Notebook(props: NotebookProps): JSX.Element {
|
||||
},
|
||||
[annotations, props.item]
|
||||
)
|
||||
|
||||
return (
|
||||
<VStack
|
||||
distribution="start"
|
||||
@ -385,21 +380,10 @@ export function Notebook(props: NotebookProps): JSX.Element {
|
||||
/>
|
||||
)}
|
||||
{labelsTarget && (
|
||||
<SetLabelsModal
|
||||
provider={labelsTarget}
|
||||
onOpenChange={function (open: boolean): void {
|
||||
setLabelsTarget(undefined)
|
||||
}}
|
||||
onLabelsUpdated={function (labels: Label[]): void {
|
||||
updateState({})
|
||||
}}
|
||||
save={function (labels: Label[]): Promise<Label[] | undefined> {
|
||||
const result = setLabelsForHighlight(
|
||||
labelsTarget.id,
|
||||
labels.map((label) => label.id)
|
||||
)
|
||||
return result
|
||||
}}
|
||||
<SetHighlightLabelsModalPresenter
|
||||
highlight={labelsTarget}
|
||||
highlightId={labelsTarget.id}
|
||||
onOpenChange={() => setLabelsTarget(undefined)}
|
||||
/>
|
||||
)}
|
||||
{props.showConfirmDeleteNote && (
|
||||
|
||||
@ -3,7 +3,7 @@ import {
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
} from '../../elements/ModalPrimitives'
|
||||
import { HStack, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { HStack } from '../../elements/LayoutPrimitives'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
@ -19,7 +19,6 @@ import 'react-markdown-editor-lite/lib/index.css'
|
||||
import { Notebook } from './Notebook'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { MarkdownNote } from '../../patterns/HighlightNotes'
|
||||
|
||||
type NotebookModalProps = {
|
||||
viewer: UserBasicData
|
||||
@ -49,7 +48,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
props.onClose(allAnnotations ?? [], deletedAnnotations ?? [])
|
||||
}, [allAnnotations, deletedAnnotations])
|
||||
}, [props, allAnnotations, deletedAnnotations])
|
||||
|
||||
const handleAnnotationsChange = useCallback(
|
||||
(allAnnotations, deletedAnnotations) => {
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
import { TickedRangeSlider } from '../../elements/TickedRangeSlider'
|
||||
import { showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { ReaderSettings } from '../../../lib/hooks/useReaderSettings'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { currentThemeName, updateTheme } from '../../../lib/themeUpdater'
|
||||
import { LineHeightIncreaseIcon } from '../../elements/images/LineHeightIncreaseIconProps'
|
||||
import { LineHeightDecreaseIcon } from '../../elements/images/LineHeightDecreaseIcon'
|
||||
@ -70,6 +70,8 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
|
||||
}
|
||||
|
||||
function AdvancedSettings(props: SettingsProps): JSX.Element {
|
||||
const { readerSettings } = props
|
||||
|
||||
return (
|
||||
<VStack
|
||||
css={{ width: '100%', minHeight: '320px', p: '10px' }}
|
||||
@ -122,9 +124,9 @@ function AdvancedSettings(props: SettingsProps): JSX.Element {
|
||||
</Label>
|
||||
<SwitchRoot
|
||||
id="justify-text"
|
||||
checked={props.readerSettings.justifyText ?? false}
|
||||
checked={readerSettings.justifyText ?? false}
|
||||
onCheckedChange={(checked) => {
|
||||
props.readerSettings.setJustifyText(checked)
|
||||
readerSettings.setJustifyText(checked)
|
||||
}}
|
||||
>
|
||||
<SwitchThumb />
|
||||
@ -153,9 +155,9 @@ function AdvancedSettings(props: SettingsProps): JSX.Element {
|
||||
</Label>
|
||||
<SwitchRoot
|
||||
id="high-contrast-text"
|
||||
checked={props.readerSettings.highContrastText ?? false}
|
||||
checked={readerSettings.highContrastText ?? false}
|
||||
onCheckedChange={(checked) => {
|
||||
props.readerSettings.setHighContrastText(checked)
|
||||
readerSettings.setHighContrastText(checked)
|
||||
}}
|
||||
>
|
||||
<SwitchThumb />
|
||||
@ -196,17 +198,18 @@ const Label = styled('label', {
|
||||
})
|
||||
|
||||
function BasicSettings(props: SettingsProps): JSX.Element {
|
||||
const { readerSettings } = props
|
||||
return (
|
||||
<VStack css={{ width: '100%' }}>
|
||||
<FontControls readerSettings={props.readerSettings} />
|
||||
<FontControls readerSettings={readerSettings} />
|
||||
|
||||
<HorizontalDivider />
|
||||
|
||||
<LayoutControls readerSettings={props.readerSettings} />
|
||||
<LayoutControls readerSettings={readerSettings} />
|
||||
|
||||
<HorizontalDivider />
|
||||
|
||||
<ThemeSelector {...props} />
|
||||
<ThemeSelector />
|
||||
|
||||
<HorizontalDivider />
|
||||
|
||||
@ -228,10 +231,10 @@ function BasicSettings(props: SettingsProps): JSX.Element {
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
props.readerSettings.setFontFamily('Inter')
|
||||
props.readerSettings.setMarginWidth(290)
|
||||
props.readerSettings.setLineHeight(150)
|
||||
props.readerSettings.actionHandler('resetReaderSettings')
|
||||
readerSettings.setFontFamily('Inter')
|
||||
readerSettings.setMarginWidth(290)
|
||||
readerSettings.setLineHeight(150)
|
||||
readerSettings.actionHandler('resetReaderSettings')
|
||||
showSuccessToast('Reader Preferences Reset', {
|
||||
position: 'bottom-right',
|
||||
})
|
||||
@ -272,6 +275,7 @@ type FontControlsProps = {
|
||||
}
|
||||
|
||||
function FontControls(props: FontControlsProps): JSX.Element {
|
||||
const { readerSettings } = props
|
||||
const FontSelect = styled('select', {
|
||||
pl: '5px',
|
||||
height: '30px',
|
||||
@ -281,16 +285,16 @@ function FontControls(props: FontControlsProps): JSX.Element {
|
||||
fontSize: '12px',
|
||||
background: '$thBackground',
|
||||
border: '1px solid $thBorderColor',
|
||||
fontFamily: props.readerSettings.fontFamily,
|
||||
fontFamily: readerSettings.fontFamily,
|
||||
textTransform: 'capitalize',
|
||||
borderRadius: '4px',
|
||||
})
|
||||
|
||||
const handleFontSizeChange = useCallback(
|
||||
(value) => {
|
||||
props.readerSettings.actionHandler('setFontSize', value)
|
||||
readerSettings.actionHandler('setFontSize', value)
|
||||
},
|
||||
[props.readerSettings.actionHandler]
|
||||
[readerSettings]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -304,13 +308,13 @@ function FontControls(props: FontControlsProps): JSX.Element {
|
||||
<FontSelect
|
||||
css={{ marginLeft: 'auto' }}
|
||||
tabIndex={-1}
|
||||
defaultValue={props.readerSettings.fontFamily}
|
||||
defaultValue={readerSettings.fontFamily}
|
||||
onChange={(e: React.FormEvent<HTMLSelectElement>) => {
|
||||
const font = e.currentTarget.value
|
||||
if (FONT_FAMILIES.indexOf(font) < 0) {
|
||||
return
|
||||
}
|
||||
props.readerSettings.setFontFamily(font)
|
||||
readerSettings.setFontFamily(font)
|
||||
}}
|
||||
>
|
||||
{FONT_FAMILIES.map((family) => (
|
||||
@ -329,7 +333,7 @@ function FontControls(props: FontControlsProps): JSX.Element {
|
||||
style="plainIcon"
|
||||
css={{ py: '0px', width: '60px' }}
|
||||
onClick={() => {
|
||||
props.readerSettings.actionHandler('decrementFontSize')
|
||||
readerSettings.actionHandler('decrementFontSize')
|
||||
}}
|
||||
>
|
||||
<SpanBox
|
||||
@ -391,11 +395,13 @@ type LayoutControlsProps = {
|
||||
}
|
||||
|
||||
function LayoutControls(props: LayoutControlsProps): JSX.Element {
|
||||
const { readerSettings } = props
|
||||
|
||||
const handleMarginWidthChange = useCallback(
|
||||
(value) => {
|
||||
props.readerSettings.setMarginWidth(value)
|
||||
readerSettings.setMarginWidth(value)
|
||||
},
|
||||
[props.readerSettings.actionHandler, props.readerSettings.setMarginWidth]
|
||||
[readerSettings]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -425,10 +431,10 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
|
||||
css={{ py: '0px', width: '60px' }}
|
||||
onClick={() => {
|
||||
const newMarginWith = Math.max(
|
||||
props.readerSettings.marginWidth - 45,
|
||||
readerSettings.marginWidth - 45,
|
||||
200
|
||||
)
|
||||
props.readerSettings.setMarginWidth(newMarginWith)
|
||||
readerSettings.setMarginWidth(newMarginWith)
|
||||
}}
|
||||
>
|
||||
<ArrowsHorizontal size={24} color="#969696" />
|
||||
@ -437,7 +443,7 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
|
||||
min={200}
|
||||
max={560}
|
||||
step={45}
|
||||
value={props.readerSettings.marginWidth}
|
||||
value={readerSettings.marginWidth}
|
||||
onChange={handleMarginWidthChange}
|
||||
/>
|
||||
<Button
|
||||
@ -445,10 +451,10 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
|
||||
css={{ py: '0px', width: '60px' }}
|
||||
onClick={() => {
|
||||
const newMarginWith = Math.min(
|
||||
props.readerSettings.marginWidth + 45,
|
||||
readerSettings.marginWidth + 45,
|
||||
560
|
||||
)
|
||||
props.readerSettings.setMarginWidth(newMarginWith)
|
||||
readerSettings.setMarginWidth(newMarginWith)
|
||||
}}
|
||||
>
|
||||
<ArrowsInLineHorizontal size={24} color="#969696" />
|
||||
@ -522,7 +528,7 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
|
||||
)
|
||||
}
|
||||
|
||||
function ThemeSelector(props: ReaderSettingsProps): JSX.Element {
|
||||
function ThemeSelector(): JSX.Element {
|
||||
const [currentTheme, setCurrentTheme] = useState(currentThemeName())
|
||||
return (
|
||||
<VStack
|
||||
|
||||
@ -1,23 +1,17 @@
|
||||
import { useCallback, useRef, useState, useMemo, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
|
||||
import {
|
||||
Check,
|
||||
Circle,
|
||||
PencilSimple,
|
||||
Plus,
|
||||
WarningCircle,
|
||||
} from 'phosphor-react'
|
||||
import { Check, Circle, Plus, WarningCircle } from 'phosphor-react'
|
||||
import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
|
||||
import { useRouter } from 'next/router'
|
||||
import { LabelsPicker } from '../../elements/LabelsPicker'
|
||||
import { LabelsDispatcher } from '../../../lib/hooks/useSetPageLabels'
|
||||
|
||||
export interface LabelsProvider {
|
||||
labels?: Label[]
|
||||
@ -31,9 +25,7 @@ type SetLabelsControlProps = {
|
||||
clearInputState: () => void
|
||||
|
||||
selectedLabels: Label[]
|
||||
setSelectedLabels: (labels: Label[]) => void
|
||||
|
||||
onLabelsUpdated?: (labels: Label[]) => void
|
||||
dispatchLabels: LabelsDispatcher
|
||||
|
||||
tabCount: number
|
||||
setTabCount: (count: number) => void
|
||||
@ -49,27 +41,9 @@ type SetLabelsControlProps = {
|
||||
errorMessage?: string
|
||||
}
|
||||
|
||||
type HeaderProps = {
|
||||
type HeaderProps = SetLabelsControlProps & {
|
||||
focused: boolean
|
||||
resetFocusedIndex: () => void
|
||||
|
||||
inputValue: string
|
||||
setInputValue: (value: string) => void
|
||||
clearInputState: () => void
|
||||
|
||||
selectedLabels: Label[]
|
||||
setSelectedLabels: (labels: Label[]) => void
|
||||
|
||||
tabCount: number
|
||||
setTabCount: (count: number) => void
|
||||
tabStartValue: string
|
||||
setTabStartValue: (value: string) => void
|
||||
|
||||
highlightLastLabel: boolean
|
||||
setHighlightLastLabel: (set: boolean) => void
|
||||
|
||||
deleteLastLabel: () => void
|
||||
selectOrCreateLabel: (value: string) => void
|
||||
}
|
||||
|
||||
const StyledLabel = styled('label', {
|
||||
@ -93,7 +67,7 @@ function Header(props: HeaderProps): JSX.Element {
|
||||
inputValue={props.inputValue}
|
||||
setInputValue={props.setInputValue}
|
||||
selectedLabels={props.selectedLabels}
|
||||
setSelectedLabels={props.setSelectedLabels}
|
||||
dispatchLabels={props.dispatchLabels}
|
||||
tabCount={props.tabCount}
|
||||
setTabCount={props.setTabCount}
|
||||
tabStartValue={props.tabStartValue}
|
||||
@ -263,6 +237,8 @@ function Footer(props: FooterProps): JSX.Element {
|
||||
|
||||
export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
const router = useRouter()
|
||||
const { inputValue, setInputValue, selectedLabels, setHighlightLastLabel } =
|
||||
props
|
||||
const { labels, revalidate } = useGetLabelsQuery()
|
||||
// Move focus through the labels list on tab or arrow up/down keys
|
||||
const [focusedIndex, setFocusedIndex] = useState<number | undefined>(
|
||||
@ -271,22 +247,22 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
setFocusedIndex(undefined)
|
||||
}, [props.inputValue])
|
||||
}, [inputValue])
|
||||
|
||||
const isSelected = useCallback(
|
||||
(label: Label): boolean => {
|
||||
return props.selectedLabels.some((other) => {
|
||||
return selectedLabels.some((other) => {
|
||||
return other.id === label.id
|
||||
})
|
||||
},
|
||||
[props.selectedLabels]
|
||||
[selectedLabels]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (focusedIndex === 0) {
|
||||
props.setHighlightLastLabel(false)
|
||||
setHighlightLastLabel(false)
|
||||
}
|
||||
}, [focusedIndex])
|
||||
}, [setHighlightLastLabel, focusedIndex])
|
||||
|
||||
const toggleLabel = useCallback(
|
||||
async (label: Label) => {
|
||||
@ -298,13 +274,9 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
} else {
|
||||
newSelectedLabels = [...props.selectedLabels, label]
|
||||
}
|
||||
props.setSelectedLabels(newSelectedLabels)
|
||||
props.dispatchLabels({ type: 'SAVE', labels: newSelectedLabels })
|
||||
props.provider.labels = newSelectedLabels
|
||||
|
||||
if (props.onLabelsUpdated) {
|
||||
props.onLabelsUpdated(newSelectedLabels)
|
||||
}
|
||||
|
||||
props.clearInputState()
|
||||
revalidate()
|
||||
},
|
||||
@ -317,12 +289,12 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
}
|
||||
return labels
|
||||
.filter((label) => {
|
||||
return label.name.toLowerCase().includes(props.inputValue.toLowerCase())
|
||||
return label.name.toLowerCase().includes(inputValue.toLowerCase())
|
||||
})
|
||||
.sort((left: Label, right: Label) => {
|
||||
return left.name.localeCompare(right.name)
|
||||
})
|
||||
}, [labels, props.inputValue])
|
||||
}, [labels, inputValue])
|
||||
|
||||
const createLabelFromFilterText = useCallback(
|
||||
async (text: string) => {
|
||||
@ -336,7 +308,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
showErrorToast('Failed to create label', { position: 'bottom-right' })
|
||||
}
|
||||
},
|
||||
[props.inputValue, toggleLabel]
|
||||
[toggleLabel]
|
||||
)
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
@ -352,7 +324,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
}
|
||||
// If the `Create New label` button isn't visible we skip it
|
||||
// when navigating with the arrow keys
|
||||
if (focusedIndex === maxIndex && !props.inputValue) {
|
||||
if (focusedIndex === maxIndex && !inputValue) {
|
||||
newIndex = maxIndex - 2
|
||||
}
|
||||
setFocusedIndex(newIndex)
|
||||
@ -367,7 +339,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
}
|
||||
// If the `Create New label` button isn't visible we skip it
|
||||
// when navigating with the arrow keys
|
||||
if (focusedIndex === maxIndex - 2 && !props.inputValue) {
|
||||
if (focusedIndex === maxIndex - 2 && !inputValue) {
|
||||
newIndex = maxIndex
|
||||
}
|
||||
setFocusedIndex(newIndex)
|
||||
@ -379,8 +351,8 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
return
|
||||
}
|
||||
if (focusedIndex === maxIndex - 1) {
|
||||
const _filterText = props.inputValue
|
||||
props.setInputValue('')
|
||||
const _filterText = inputValue
|
||||
setInputValue('')
|
||||
await createLabelFromFilterText(_filterText)
|
||||
return
|
||||
}
|
||||
@ -393,7 +365,8 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
}
|
||||
},
|
||||
[
|
||||
props.inputValue,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
filteredLabels,
|
||||
focusedIndex,
|
||||
createLabelFromFilterText,
|
||||
@ -412,12 +385,13 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
provider={props.provider}
|
||||
focused={focusedIndex === undefined}
|
||||
resetFocusedIndex={() => setFocusedIndex(undefined)}
|
||||
inputValue={props.inputValue}
|
||||
setInputValue={props.setInputValue}
|
||||
inputValue={inputValue}
|
||||
setInputValue={setInputValue}
|
||||
selectedLabels={props.selectedLabels}
|
||||
setSelectedLabels={props.setSelectedLabels}
|
||||
dispatchLabels={props.dispatchLabels}
|
||||
tabCount={props.tabCount}
|
||||
setTabCount={props.setTabCount}
|
||||
tabStartValue={props.tabStartValue}
|
||||
@ -472,7 +446,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
|
||||
))}
|
||||
</VStack>
|
||||
<Footer
|
||||
filterText={props.inputValue}
|
||||
filterText={inputValue}
|
||||
focused={focusedIndex === filteredLabels.length + 1}
|
||||
/>
|
||||
</VStack>
|
||||
|
||||
@ -13,17 +13,20 @@ import { showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
|
||||
import { LabelsDispatcher } from '../../../lib/hooks/useSetPageLabels'
|
||||
|
||||
type SetLabelsModalProps = {
|
||||
provider: LabelsProvider
|
||||
|
||||
onLabelsUpdated?: (labels: Label[]) => void
|
||||
onOpenChange: (open: boolean) => void
|
||||
save: (labels: Label[]) => Promise<Label[] | undefined>
|
||||
|
||||
selectedLabels: Label[]
|
||||
dispatchLabels: LabelsDispatcher
|
||||
}
|
||||
|
||||
export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const { selectedLabels, dispatchLabels } = props
|
||||
const availableLabels = useGetLabelsQuery()
|
||||
const [tabCount, setTabCount] = useState(-1)
|
||||
const [tabStartValue, setTabStartValue] = useState('')
|
||||
@ -33,24 +36,6 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
const errorTimeoutRef = useRef<NodeJS.Timeout | undefined>()
|
||||
const [highlightLastLabel, setHighlightLastLabel] = useState(false)
|
||||
|
||||
const [selectedLabels, setSelectedLabels] = useState(
|
||||
props.provider.labels ?? []
|
||||
)
|
||||
|
||||
const containsTemporaryLabel = (labels: Label[]) => {
|
||||
return !!labels.find((l) => '_temporary' in l)
|
||||
}
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
;(async () => {
|
||||
await props.save(selectedLabels)
|
||||
props.onOpenChange(open)
|
||||
})()
|
||||
},
|
||||
[props, selectedLabels]
|
||||
)
|
||||
|
||||
const showMessage = useCallback(
|
||||
(msg: string, timeout?: number) => {
|
||||
if (errorTimeoutRef.current) {
|
||||
@ -83,14 +68,14 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
if (inputValue.length > 0) {
|
||||
setHighlightLastLabel(false)
|
||||
}
|
||||
}, [inputValue, showMessage])
|
||||
}, [errorMessage, inputValue, showMessage])
|
||||
|
||||
const clearInputState = useCallback(() => {
|
||||
setTabCount(-1)
|
||||
setInputValue('')
|
||||
setTabStartValue('')
|
||||
setHighlightLastLabel(false)
|
||||
}, [tabCount, tabStartValue, highlightLastLabel])
|
||||
}, [])
|
||||
|
||||
const createLabelAsync = useCallback(
|
||||
(newLabels: Label[], tempLabel: Label) => {
|
||||
@ -107,20 +92,23 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
})
|
||||
if (idx !== -1) {
|
||||
currentLabels[idx] = newLabel
|
||||
setSelectedLabels([...currentLabels])
|
||||
dispatchLabels({ type: 'SAVE', labels: [...currentLabels] })
|
||||
} else {
|
||||
setSelectedLabels([...currentLabels, newLabel])
|
||||
dispatchLabels({
|
||||
type: 'SAVE',
|
||||
labels: [...currentLabels, newLabel],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
showMessage(`Error creating label ${tempLabel.name}`, 5000)
|
||||
if (idx !== -1) {
|
||||
currentLabels.splice(idx, 1)
|
||||
setSelectedLabels([...currentLabels])
|
||||
dispatchLabels({ type: 'SAVE', labels: [...currentLabels] })
|
||||
}
|
||||
}
|
||||
})()
|
||||
},
|
||||
[selectedLabels]
|
||||
[dispatchLabels, showMessage]
|
||||
)
|
||||
|
||||
const selectOrCreateLabel = useCallback(
|
||||
@ -140,7 +128,7 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
(l) => l.name.toLowerCase() == lowerCasedValue
|
||||
)
|
||||
if (!isAdded) {
|
||||
setSelectedLabels([...current, existing])
|
||||
dispatchLabels({ type: 'SAVE', labels: [...current, existing] })
|
||||
clearInputState()
|
||||
} else {
|
||||
showMessage(`label ${value} already added.`, 5000)
|
||||
@ -152,10 +140,9 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
color: randomLabelColorHex(),
|
||||
description: '',
|
||||
createdAt: new Date(),
|
||||
_temporary: true,
|
||||
}
|
||||
const newLabels = [...current, tempLabel]
|
||||
setSelectedLabels(newLabels)
|
||||
dispatchLabels({ type: 'TEMP', labels: newLabels })
|
||||
clearInputState()
|
||||
|
||||
createLabelAsync(newLabels, tempLabel)
|
||||
@ -164,6 +151,7 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
[
|
||||
availableLabels,
|
||||
selectedLabels,
|
||||
dispatchLabels,
|
||||
clearInputState,
|
||||
createLabelAsync,
|
||||
showMessage,
|
||||
@ -174,23 +162,15 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
if (highlightLastLabel) {
|
||||
const current = selectedLabels
|
||||
current.pop()
|
||||
setSelectedLabels([...current])
|
||||
dispatchLabels({ type: 'SAVE', labels: [...current] })
|
||||
setHighlightLastLabel(false)
|
||||
} else {
|
||||
setHighlightLastLabel(true)
|
||||
}
|
||||
}, [highlightLastLabel, selectedLabels])
|
||||
|
||||
useEffect(() => {
|
||||
if (!containsTemporaryLabel(selectedLabels)) {
|
||||
;(async () => {
|
||||
await props.save(selectedLabels)
|
||||
})()
|
||||
}
|
||||
}, [props.save, selectedLabels])
|
||||
}, [highlightLastLabel, selectedLabels, dispatchLabels])
|
||||
|
||||
return (
|
||||
<ModalRoot defaultOpen onOpenChange={onOpenChange}>
|
||||
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
css={{
|
||||
@ -199,21 +179,20 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
|
||||
}}
|
||||
onPointerDownOutside={(event) => {
|
||||
event.preventDefault()
|
||||
onOpenChange(false)
|
||||
props.onOpenChange(false)
|
||||
}}
|
||||
>
|
||||
<VStack distribution="start" css={{ height: '100%' }}>
|
||||
<SpanBox css={{ pt: '0px', px: '16px', width: '100%' }}>
|
||||
<ModalTitleBar title="Labels" onOpenChange={onOpenChange} />
|
||||
<ModalTitleBar title="Labels" onOpenChange={props.onOpenChange} />
|
||||
</SpanBox>
|
||||
<SetLabelsControl
|
||||
provider={props.provider}
|
||||
inputValue={inputValue}
|
||||
setInputValue={setInputValue}
|
||||
clearInputState={clearInputState}
|
||||
selectedLabels={selectedLabels}
|
||||
setSelectedLabels={setSelectedLabels}
|
||||
onLabelsUpdated={props.onLabelsUpdated}
|
||||
selectedLabels={props.selectedLabels}
|
||||
dispatchLabels={props.dispatchLabels}
|
||||
tabCount={tabCount}
|
||||
setTabCount={setTabCount}
|
||||
tabStartValue={tabStartValue}
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useSetPageLabels } from '../../../lib/hooks/useSetPageLabels'
|
||||
import { LabelsProvider } from './SetLabelsControl'
|
||||
import { SetLabelsModal } from './SetLabelsModal'
|
||||
import { useSetHighlightLabels } from '../../../lib/hooks/useSetHighlightLabels'
|
||||
|
||||
type SetPageLabelsModalPresenterProps = {
|
||||
articleId: string
|
||||
article: LabelsProvider
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function SetPageLabelsModalPresenter(
|
||||
props: SetPageLabelsModalPresenterProps
|
||||
): JSX.Element {
|
||||
const [labels, dispatchLabels] = useSetPageLabels(props.articleId)
|
||||
|
||||
useEffect(() => {
|
||||
dispatchLabels({
|
||||
type: 'RESET',
|
||||
labels: props.article.labels ?? [],
|
||||
})
|
||||
}, [props.article, dispatchLabels])
|
||||
|
||||
return (
|
||||
<SetLabelsModal
|
||||
provider={props.article}
|
||||
selectedLabels={labels.labels}
|
||||
dispatchLabels={dispatchLabels}
|
||||
onOpenChange={props.onOpenChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SetHighlightLabelsModalPresenterProps = {
|
||||
highlightId: string
|
||||
highlight: LabelsProvider
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function SetHighlightLabelsModalPresenter(
|
||||
props: SetHighlightLabelsModalPresenterProps
|
||||
): JSX.Element {
|
||||
const [labels, dispatchLabels] = useSetHighlightLabels(props.highlightId)
|
||||
|
||||
useEffect(() => {
|
||||
dispatchLabels({
|
||||
type: 'RESET',
|
||||
labels: props.highlight.labels ?? [],
|
||||
})
|
||||
}, [props.highlight, dispatchLabels])
|
||||
|
||||
return (
|
||||
<SetLabelsModal
|
||||
provider={props.highlight}
|
||||
selectedLabels={labels.labels}
|
||||
dispatchLabels={dispatchLabels}
|
||||
onOpenChange={props.onOpenChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -2,7 +2,6 @@ import {
|
||||
ArchiveBox,
|
||||
DotsThreeOutline,
|
||||
Info,
|
||||
TagSimple,
|
||||
TextAa,
|
||||
Trash,
|
||||
Tray,
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { Item } from '@radix-ui/react-dropdown-menu'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { DotsThreeVertical } from 'phosphor-react'
|
||||
import { useCallback } from 'react'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
@ -12,7 +10,7 @@ import {
|
||||
DropdownOption,
|
||||
DropdownSeparator,
|
||||
} from '../../elements/DropdownElements'
|
||||
import { Box, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
|
||||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
|
||||
|
||||
@ -12,8 +12,6 @@ import {
|
||||
PageType,
|
||||
State,
|
||||
} from '../../../lib/networking/fragments/articleFragment'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
|
||||
import {
|
||||
SearchItem,
|
||||
TypeaheadSearchItemsData,
|
||||
@ -33,7 +31,6 @@ import { StyledText } from '../../elements/StyledText'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
|
||||
import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
|
||||
import { SetLabelsModal } from '../article/SetLabelsModal'
|
||||
import { Box, HStack, VStack } from './../../elements/LayoutPrimitives'
|
||||
import { AddLinkModal } from './AddLinkModal'
|
||||
import { EditLibraryItemModal } from './EditItemModals'
|
||||
@ -45,6 +42,7 @@ import { UploadModal } from '../UploadModal'
|
||||
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
|
||||
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter'
|
||||
|
||||
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
|
||||
export type LibraryMode = 'reads' | 'highlights'
|
||||
@ -1005,23 +1003,9 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
|
||||
/>
|
||||
)}
|
||||
{props.labelsTarget?.node.id && (
|
||||
<SetLabelsModal
|
||||
provider={props.labelsTarget.node}
|
||||
onLabelsUpdated={(labels: Label[]) => {
|
||||
if (props.labelsTarget) {
|
||||
props.labelsTarget.node.labels = labels
|
||||
updateState({})
|
||||
}
|
||||
}}
|
||||
save={(labels: Label[]) => {
|
||||
if (props.labelsTarget?.node.id) {
|
||||
return setLabelsMutation(
|
||||
props.labelsTarget.node.id,
|
||||
labels.map((label) => label.id)
|
||||
)
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}}
|
||||
<SetPageLabelsModalPresenter
|
||||
articleId={props.labelsTarget.node.id}
|
||||
article={props.labelsTarget.node}
|
||||
onOpenChange={() => {
|
||||
if (props.labelsTarget) {
|
||||
const activate = props.labelsTarget
|
||||
|
||||
@ -418,20 +418,6 @@ function MultiSelectControlButtonBox(
|
||||
/>
|
||||
<SpanBox css={{ '@lgDown': { display: 'none' } }}>Archive</SpanBox>
|
||||
</Button>
|
||||
{/* <Button
|
||||
style="outline"
|
||||
onClick={(e) => {
|
||||
props.performMultiSelectAction(BulkAction.ADD_LABELS)
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<TagSimple
|
||||
width={20}
|
||||
height={20}
|
||||
color={theme.colors.thTextContrast2.toString()}
|
||||
/>
|
||||
<SpanBox css={{ '@lgDown': { display: 'none' } }}>Label</SpanBox>
|
||||
</Button> */}
|
||||
<Button
|
||||
style="outline"
|
||||
onClick={(e) => {
|
||||
|
||||
69
packages/web/lib/hooks/useSetHighlightLabels.tsx
Normal file
69
packages/web/lib/hooks/useSetHighlightLabels.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { useReducer } from 'react'
|
||||
import { Label } from '../networking/fragments/labelFragment'
|
||||
import { showErrorToast } from '../toastHelpers'
|
||||
import { setLabelsForHighlight } from '../networking/mutations/setLabelsForHighlight'
|
||||
|
||||
export type LabelAction = 'RESET' | 'TEMP' | 'SAVE'
|
||||
export type LabelsDispatcher = (action: {
|
||||
type: LabelAction
|
||||
labels: Label[]
|
||||
}) => void
|
||||
|
||||
export const useSetHighlightLabels = (
|
||||
highlightId?: string
|
||||
): [{ labels: Label[] }, LabelsDispatcher] => {
|
||||
const labelsReducer = (
|
||||
state: {
|
||||
labels: Label[]
|
||||
},
|
||||
action: {
|
||||
type: string
|
||||
labels: Label[]
|
||||
}
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'RESET': {
|
||||
return {
|
||||
labels: action.labels,
|
||||
}
|
||||
}
|
||||
case 'TEMP': {
|
||||
return {
|
||||
labels: action.labels,
|
||||
}
|
||||
}
|
||||
case 'SAVE': {
|
||||
const labelIds = action.labels.map((l) => l.id)
|
||||
if (highlightId) {
|
||||
;(async () => {
|
||||
const result = await setLabelsForHighlight(highlightId, labelIds)
|
||||
if (result) {
|
||||
dispatchLabels({
|
||||
type: 'RESET',
|
||||
// Use original list so we don't reorder
|
||||
labels: action.labels ?? [],
|
||||
})
|
||||
} else {
|
||||
showErrorToast('Error saving labels', {
|
||||
position: 'bottom-right',
|
||||
})
|
||||
}
|
||||
})()
|
||||
} else {
|
||||
showErrorToast('Unable to update labels')
|
||||
}
|
||||
return {
|
||||
labels: action.labels,
|
||||
}
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const [labels, dispatchLabels] = useReducer(labelsReducer, {
|
||||
labels: [],
|
||||
})
|
||||
|
||||
return [labels, dispatchLabels]
|
||||
}
|
||||
71
packages/web/lib/hooks/useSetPageLabels.tsx
Normal file
71
packages/web/lib/hooks/useSetPageLabels.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { useReducer } from 'react'
|
||||
import { setLabelsMutation } from '../networking/mutations/setLabelsMutation'
|
||||
import { Label } from '../networking/fragments/labelFragment'
|
||||
import { showErrorToast } from '../toastHelpers'
|
||||
|
||||
export type LabelAction = 'RESET' | 'TEMP' | 'SAVE'
|
||||
export type LabelsDispatcher = (action: {
|
||||
type: LabelAction
|
||||
labels: Label[]
|
||||
}) => void
|
||||
|
||||
export const useSetPageLabels = (
|
||||
articleId?: string
|
||||
): [{ labels: Label[] }, LabelsDispatcher] => {
|
||||
const labelsReducer = (
|
||||
state: {
|
||||
labels: Label[]
|
||||
},
|
||||
action: {
|
||||
type: string
|
||||
labels: Label[]
|
||||
}
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'RESET': {
|
||||
return {
|
||||
labels: action.labels,
|
||||
}
|
||||
}
|
||||
case 'TEMP': {
|
||||
return {
|
||||
labels: action.labels,
|
||||
}
|
||||
}
|
||||
case 'SAVE': {
|
||||
const labelIds = action.labels.map((l) => l.id)
|
||||
if (articleId) {
|
||||
;(async () => {
|
||||
const result = await setLabelsMutation(articleId, labelIds)
|
||||
if (result) {
|
||||
dispatchLabels({
|
||||
type: 'RESET',
|
||||
// Use the original labels value here so we dont re-order
|
||||
labels: action.labels ?? [],
|
||||
})
|
||||
} else {
|
||||
showErrorToast('Error saving labels', {
|
||||
position: 'bottom-right',
|
||||
})
|
||||
}
|
||||
})()
|
||||
} else {
|
||||
showErrorToast('Unable to update labels', {
|
||||
position: 'bottom-right',
|
||||
})
|
||||
}
|
||||
return {
|
||||
labels: action.labels,
|
||||
}
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const [labels, dispatchLabels] = useReducer(labelsReducer, {
|
||||
labels: [],
|
||||
})
|
||||
|
||||
return [labels, dispatchLabels]
|
||||
}
|
||||
@ -12,11 +12,10 @@ import {
|
||||
UpdateTitleEvent,
|
||||
} from './../../../components/templates/article/ArticleContainer'
|
||||
import { PdfArticleContainerProps } from './../../../components/templates/article/PdfArticleContainer'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
|
||||
import { navigationCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { webBaseURL } from '../../../lib/appConfig'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation'
|
||||
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
|
||||
@ -36,12 +35,12 @@ import { SkeletonArticleContainer } from '../../../components/templates/article/
|
||||
import { useRegisterActions } from 'kbar'
|
||||
import { deleteLinkMutation } from '../../../lib/networking/mutations/deleteLinkMutation'
|
||||
import { ConfirmationModal } from '../../../components/patterns/ConfirmationModal'
|
||||
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
|
||||
import { ReaderHeader } from '../../../components/templates/reader/ReaderHeader'
|
||||
import { EditArticleModal } from '../../../components/templates/homeFeed/EditItemModals'
|
||||
import { VerticalArticleActionsMenu } from '../../../components/templates/article/VerticalArticleActions'
|
||||
import { PdfHeaderSpacer } from '../../../components/templates/article/PdfHeaderSpacer'
|
||||
import { EpubContainerProps } from '../../../components/templates/article/EpubContainer'
|
||||
import { useSetPageLabels } from '../../../lib/hooks/useSetPageLabels'
|
||||
|
||||
const PdfArticleContainerNoSSR = dynamic<PdfArticleContainerProps>(
|
||||
() => import('./../../../components/templates/article/PdfArticleContainer'),
|
||||
@ -56,8 +55,6 @@ const EpubContainerNoSSR = dynamic<EpubContainerProps>(
|
||||
export default function Home(): JSX.Element {
|
||||
const router = useRouter()
|
||||
const { cache, mutate } = useSWRConfig()
|
||||
const { slug } = router.query
|
||||
|
||||
const [showEditModal, setShowEditModal] = useState(false)
|
||||
const [showHighlightsModal, setShowHighlightsModal] = useState(false)
|
||||
const { viewerData } = useGetViewerQuery()
|
||||
@ -69,12 +66,12 @@ export default function Home(): JSX.Element {
|
||||
includeFriendsHighlights: false,
|
||||
})
|
||||
const article = articleData?.article.article
|
||||
const [labels, setLabels] = useState<Label[]>([])
|
||||
useEffect(() => {
|
||||
if (article?.labels) {
|
||||
setLabels(article.labels)
|
||||
}
|
||||
}, [article])
|
||||
dispatchLabels({
|
||||
type: 'RESET',
|
||||
labels: article?.labels ?? [],
|
||||
})
|
||||
}, [articleData?.article.article])
|
||||
|
||||
useKeyboardShortcuts(navigationCommands(router))
|
||||
|
||||
@ -150,7 +147,10 @@ export default function Home(): JSX.Element {
|
||||
}
|
||||
break
|
||||
case 'refreshLabels':
|
||||
setLabels(arg as Label[])
|
||||
dispatchLabels({
|
||||
type: 'RESET',
|
||||
labels: arg as Label[],
|
||||
})
|
||||
break
|
||||
case 'showHighlights':
|
||||
setShowHighlightsModal(true)
|
||||
@ -364,6 +364,8 @@ export default function Home(): JSX.Element {
|
||||
[readerSettings]
|
||||
)
|
||||
|
||||
const [labels, dispatchLabels] = useSetPageLabels(article?.id)
|
||||
|
||||
if (articleFetchError && articleFetchError.indexOf('NOT_FOUND') > -1) {
|
||||
router.push('/404')
|
||||
return <LoadingView />
|
||||
@ -377,6 +379,7 @@ export default function Home(): JSX.Element {
|
||||
article={article}
|
||||
layout="top"
|
||||
showReaderDisplaySettings={article?.contentReader != 'PDF'}
|
||||
readerSettings={readerSettings}
|
||||
articleActionHandler={actionHandler}
|
||||
/>
|
||||
}
|
||||
@ -430,6 +433,7 @@ export default function Home(): JSX.Element {
|
||||
<ArticleActionsMenu
|
||||
article={article}
|
||||
layout="side"
|
||||
readerSettings={readerSettings}
|
||||
showReaderDisplaySettings={true}
|
||||
articleActionHandler={actionHandler}
|
||||
/>
|
||||
@ -467,7 +471,7 @@ export default function Home(): JSX.Element {
|
||||
margin={readerSettings.marginWidth}
|
||||
lineHeight={readerSettings.lineHeight}
|
||||
fontFamily={readerSettings.fontFamily}
|
||||
labels={labels}
|
||||
labels={labels.labels}
|
||||
showHighlightsModal={showHighlightsModal}
|
||||
setShowHighlightsModal={setShowHighlightsModal}
|
||||
justifyText={readerSettings.justifyText ?? undefined}
|
||||
@ -523,15 +527,8 @@ export default function Home(): JSX.Element {
|
||||
{article && readerSettings.showSetLabelsModal && (
|
||||
<SetLabelsModal
|
||||
provider={article}
|
||||
onLabelsUpdated={(labels: Label[]) => {
|
||||
actionHandler('refreshLabels', labels)
|
||||
}}
|
||||
save={(labels: Label[]) => {
|
||||
return setLabelsMutation(
|
||||
article.linkId,
|
||||
labels.map((label) => label.id)
|
||||
)
|
||||
}}
|
||||
selectedLabels={labels.labels}
|
||||
dispatchLabels={dispatchLabels}
|
||||
onOpenChange={() => readerSettings.setShowSetLabelsModal(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user