diff --git a/packages/web/components/elements/icons/ConfusedSlothIcon.tsx b/packages/web/components/elements/icons/ConfusedSlothIcon.tsx
index 47f300a3e..7b99e3799 100644
--- a/packages/web/components/elements/icons/ConfusedSlothIcon.tsx
+++ b/packages/web/components/elements/icons/ConfusedSlothIcon.tsx
@@ -8,7 +8,6 @@ import React from 'react'
export function ConfusedSlothIcon(): JSX.Element {
const { currentThemeIsDark } = useCurrentTheme()
- console.log('is dark mdoe: ', currentThemeIsDark)
return currentThemeIsDark ? (
) : (
diff --git a/packages/web/components/patterns/ArticleNotes.tsx b/packages/web/components/patterns/ArticleNotes.tsx
index abdceb74c..d0e10559c 100644
--- a/packages/web/components/patterns/ArticleNotes.tsx
+++ b/packages/web/components/patterns/ArticleNotes.tsx
@@ -80,6 +80,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
const saveText = useCallback(
(text: string) => {
;(async () => {
+ console.log('saving text: ', text)
const success = await updateHighlightMutation({
annotation: text,
libraryItemId: props.targetId,
diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx
index 887296470..ab007581d 100644
--- a/packages/web/components/patterns/HighlightNotes.tsx
+++ b/packages/web/components/patterns/HighlightNotes.tsx
@@ -50,6 +50,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
const saveText = useCallback(
(text: string, updateTime: Date, interactive: boolean) => {
;(async () => {
+ console.log('updating highlight text')
const success = await updateHighlightMutation({
annotation: text,
libraryItemId: props.targetId,
diff --git a/packages/web/components/patterns/ReaderDropdownMenu.tsx b/packages/web/components/patterns/ReaderDropdownMenu.tsx
index a9b32e798..6f3daee05 100644
--- a/packages/web/components/patterns/ReaderDropdownMenu.tsx
+++ b/packages/web/components/patterns/ReaderDropdownMenu.tsx
@@ -4,7 +4,7 @@ import {
DropdownOption,
DropdownSeparator,
} from '../elements/DropdownElements'
-import { ArticleAttributes } from '../../lib/networking/queries/useGetArticleQuery'
+import { ArticleAttributes } from '../../lib/networking/library_items/useLibraryItems'
import { State } from '../../lib/networking/fragments/articleFragment'
type DropdownMenuProps = {
diff --git a/packages/web/components/templates/article/ArticleActionsMenu.tsx b/packages/web/components/templates/article/ArticleActionsMenu.tsx
index 582eeea6e..85c36053c 100644
--- a/packages/web/components/templates/article/ArticleActionsMenu.tsx
+++ b/packages/web/components/templates/article/ArticleActionsMenu.tsx
@@ -1,5 +1,5 @@
import { Separator } from '@radix-ui/react-separator'
-import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
+import { ArticleAttributes } from '../../../lib/networking/library_items/useLibraryItems'
import { Button } from '../../elements/Button'
import { Box, SpanBox } from '../../elements/LayoutPrimitives'
import { styled, theme } from '../../tokens/stitches.config'
diff --git a/packages/web/components/templates/article/ArticleContainer.tsx b/packages/web/components/templates/article/ArticleContainer.tsx
index f0c61bfd5..15ab8bdf6 100644
--- a/packages/web/components/templates/article/ArticleContainer.tsx
+++ b/packages/web/components/templates/article/ArticleContainer.tsx
@@ -1,7 +1,3 @@
-import {
- ArticleAttributes,
- TextDirection,
-} from '../../../lib/networking/queries/useGetArticleQuery'
import { Article } from './../../../components/templates/article/Article'
import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives'
import { StyledText } from './../../elements/StyledText'
@@ -19,11 +15,13 @@ 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/library_items/useLibraryItems'
+import {
+ ArticleAttributes,
+ Recommendation,
+ TextDirection,
+} from '../../../lib/networking/library_items/useLibraryItems'
import { Avatar } from '../../elements/Avatar'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
-import { AISummary } from './AISummary'
-import { userHasFeature } from '../../../lib/featureFlag'
type ArticleContainerProps = {
viewer: UserBasicData
diff --git a/packages/web/components/templates/article/EpubContainer.tsx b/packages/web/components/templates/article/EpubContainer.tsx
index ea9ae366e..ebf2558d8 100644
--- a/packages/web/components/templates/article/EpubContainer.tsx
+++ b/packages/web/components/templates/article/EpubContainer.tsx
@@ -1,4 +1,4 @@
-import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
+import { ArticleAttributes } from '../../../lib/networking/library_items/useLibraryItems'
import { Box, VStack } from '../../elements/LayoutPrimitives'
import { v4 as uuidv4 } from 'uuid'
import { nanoid } from 'nanoid'
@@ -42,13 +42,15 @@ type EpubPatch = {
export default function EpubContainer(props: EpubContainerProps): JSX.Element {
const epubRef = useRef(null)
const renditionRef = useRef(undefined)
- const [shareTarget, setShareTarget] =
- useState(undefined)
+ const [shareTarget, setShareTarget] = useState(
+ undefined
+ )
const [touchStart, setTouchStart] = useState(0)
const [notebookKey, setNotebookKey] = useState(uuidv4())
const [noteTarget, setNoteTarget] = useState(undefined)
- const [noteTargetPageIndex, setNoteTargetPageIndex] =
- useState(undefined)
+ const [noteTargetPageIndex, setNoteTargetPageIndex] = useState<
+ number | undefined
+ >(undefined)
const highlightsRef = useRef([])
const book = useMemo(() => {
diff --git a/packages/web/components/templates/article/HighlightNoteModal.tsx b/packages/web/components/templates/article/HighlightNoteModal.tsx
index c505f96b3..42b416e04 100644
--- a/packages/web/components/templates/article/HighlightNoteModal.tsx
+++ b/packages/web/components/templates/article/HighlightNoteModal.tsx
@@ -9,8 +9,8 @@ import { VStack } from '../../elements/LayoutPrimitives'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { useCallback, useState } from 'react'
import { StyledTextArea } from '../../elements/StyledTextArea'
-import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
import { showErrorToast } from '../../../lib/toastHelpers'
+import { useUpdateHighlight } from '../../../lib/networking/highlights/useItemHighlights'
type HighlightNoteModalProps = {
author: string
@@ -25,6 +25,7 @@ type HighlightNoteModalProps = {
export function HighlightNoteModal(
props: HighlightNoteModalProps
): JSX.Element {
+ const updateHighlight = useUpdateHighlight()
const [noteContent, setNoteContent] = useState(
props.highlight?.annotation ?? ''
)
@@ -38,20 +39,24 @@ export function HighlightNoteModal(
const saveNoteChanges = useCallback(async () => {
if (noteContent != props.highlight?.annotation && props.highlight?.id) {
- const result = await updateHighlightMutation({
- libraryItemId: props.libraryItemId,
- highlightId: props.highlight?.id,
- annotation: noteContent,
- color: props.highlight?.color,
- })
-
- if (result) {
+ console.log('updating highlight textsdsdfsd')
+ try {
+ const result = await updateHighlight.mutateAsync({
+ itemId: props.libraryItemId,
+ input: {
+ libraryItemId: props.libraryItemId,
+ highlightId: props.highlight?.id,
+ annotation: noteContent,
+ color: props.highlight?.color,
+ },
+ })
props.onUpdate({ ...props.highlight, annotation: noteContent })
props.onOpenChange(false)
- } else {
+ return result?.id
+ } catch (err) {
showErrorToast('Error updating your note', { position: 'bottom-right' })
+ return undefined
}
- document.dispatchEvent(new Event('highlightsUpdated'))
}
if (!props.highlight && props.createHighlightForNote) {
const result = await props.createHighlightForNote(noteContent)
diff --git a/packages/web/components/templates/article/Notebook.tsx b/packages/web/components/templates/article/Notebook.tsx
index 29dbecf30..ce09af5e4 100644
--- a/packages/web/components/templates/article/Notebook.tsx
+++ b/packages/web/components/templates/article/Notebook.tsx
@@ -16,10 +16,10 @@ import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery
import { ReadableItem } from '../../../lib/networking/library_items/useLibraryItems'
import { SetHighlightLabelsModalPresenter } from './SetLabelsModalPresenter'
import { ArticleNotes } from '../../patterns/ArticleNotes'
-import { useGetArticleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
import { formattedShortTime } from '../../../lib/dateFormatting'
import { isDarkTheme } from '../../../lib/themeUpdater'
import { sortHighlights } from '../../../lib/highlights/sortHighlights'
+import { useGetLibraryItemContent } from '../../../lib/networking/library_items/useLibraryItems'
type NotebookContentProps = {
viewer: UserBasicData
@@ -43,11 +43,10 @@ type NoteState = {
export function NotebookContent(props: NotebookContentProps): JSX.Element {
const isDark = isDarkTheme()
- const { articleData, mutate } = useGetArticleQuery({
- slug: props.item.slug,
- username: props.viewer.profile.username,
- includeFriendsHighlights: false,
- })
+ const { data: article } = useGetLibraryItemContent(
+ props.viewer.profile.username as string,
+ props.item.slug as string
+ )
const [noteText, setNoteText] = useState('')
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
useState(undefined)
@@ -112,7 +111,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
)
const highlights = useMemo(() => {
- const result = articleData?.article.article.highlights
+ const result = article?.highlights
const note = result?.find((h) => h.type === 'NOTE')
if (note) {
noteState.current.note = note
@@ -122,7 +121,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
setNoteText('')
}
return result
- }, [articleData])
+ }, [article])
useEffect(() => {
if (highlights && props.onAnnotationsChanged) {
@@ -179,16 +178,6 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
const [lastChanged, setLastChanged] = useState(undefined)
const [lastSaved, setLastSaved] = useState(undefined)
- useEffect(() => {
- const highlightsUpdated = () => {
- mutate()
- }
- document.addEventListener('highlightsUpdated', highlightsUpdated)
- return () => {
- document.removeEventListener('highlightsUpdated', highlightsUpdated)
- }
- }, [mutate])
-
return (
{
- mutate()
- }}
/>
))}
{sortedHighlights.length === 0 && (
@@ -298,7 +284,6 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
props.item.id,
showConfirmDeleteHighlightId
)
- mutate()
if (success) {
showSuccessToast('Highlight deleted.', {
position: 'bottom-right',
@@ -333,7 +318,6 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
console.log('update highlight: ', highlight)
}}
onOpenChange={() => {
- mutate()
setLabelsTarget(undefined)
}}
/>
diff --git a/packages/web/components/templates/article/PdfArticleContainer.tsx b/packages/web/components/templates/article/PdfArticleContainer.tsx
index 1a684720c..cbe7bf84b 100644
--- a/packages/web/components/templates/article/PdfArticleContainer.tsx
+++ b/packages/web/components/templates/article/PdfArticleContainer.tsx
@@ -1,4 +1,4 @@
-import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
+import { ArticleAttributes } from '../../../lib/networking/library_items/useLibraryItems'
import { Box } from '../../elements/LayoutPrimitives'
import { v4 as uuidv4 } from 'uuid'
import { nanoid } from 'nanoid'
@@ -36,8 +36,9 @@ export default function PdfArticleContainer(
const containerRef = useRef(null)
const [notebookKey, setNotebookKey] = useState(uuidv4())
const [noteTarget, setNoteTarget] = useState(undefined)
- const [noteTargetPageIndex, setNoteTargetPageIndex] =
- useState(undefined)
+ const [noteTargetPageIndex, setNoteTargetPageIndex] = useState<
+ number | undefined
+ >(undefined)
const highlightsRef = useRef([])
const annotationOmnivoreId = (annotation: Annotation): string | undefined => {
diff --git a/packages/web/components/templates/article/VerticalArticleActions.tsx b/packages/web/components/templates/article/VerticalArticleActions.tsx
index 63507b074..689faefab 100644
--- a/packages/web/components/templates/article/VerticalArticleActions.tsx
+++ b/packages/web/components/templates/article/VerticalArticleActions.tsx
@@ -1,4 +1,4 @@
-import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
+import { ArticleAttributes } from '../../../lib/networking/library_items/useLibraryItems'
import { Button } from '../../elements/Button'
import { HStack } from '../../elements/LayoutPrimitives'
import { theme } from '../../tokens/stitches.config'
diff --git a/packages/web/components/templates/homeFeed/EditItemModals.tsx b/packages/web/components/templates/homeFeed/EditItemModals.tsx
index fca5f99e0..6bbff5685 100644
--- a/packages/web/components/templates/homeFeed/EditItemModals.tsx
+++ b/packages/web/components/templates/homeFeed/EditItemModals.tsx
@@ -1,7 +1,7 @@
import dayjs, { Dayjs } from 'dayjs'
import { useCallback, useState } from 'react'
import { updatePageMutation } from '../../../lib/networking/mutations/updatePageMutation'
-import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
+import { ArticleAttributes } from '../../../lib/networking/library_items/useLibraryItems'
import { LibraryItem } from '../../../lib/networking/library_items/useLibraryItems'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { CloseButton } from '../../elements/CloseButton'
diff --git a/packages/web/components/templates/library/LibraryContainer.tsx b/packages/web/components/templates/library/LibraryContainer.tsx
index ee8ae35fc..c89c67b9b 100644
--- a/packages/web/components/templates/library/LibraryContainer.tsx
+++ b/packages/web/components/templates/library/LibraryContainer.tsx
@@ -18,7 +18,10 @@ import {
LibraryItemNode,
LibraryItems,
LibraryItemsQueryInput,
+ useArchiveItem,
+ useDeleteItem,
useGetLibraryItems,
+ useUpdateItemReadStatus,
} from '../../../lib/networking/library_items/useLibraryItems'
import {
useGetViewerQuery,
@@ -110,6 +113,10 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element {
const [linkToEdit, setLinkToEdit] = useState()
const [linkToUnsubscribe, setLinkToUnsubscribe] = useState()
+ const archiveItem = useArchiveItem()
+ const deleteItem = useDeleteItem()
+ const updateItemReadStatus = useUpdateItemReadStatus()
+
const [queryInputs, setQueryInputs] =
useState(defaultQuery)
@@ -121,19 +128,6 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element {
error: fetchItemsError,
} = useGetLibraryItems(props.folder, queryInputs)
- // useEffect(() => {
- // const handleRevalidate = () => {
- // ;(async () => {
- // console.log('revalidating library')
- // await mutate()
- // })()
- // }
- // document.addEventListener('revalidateLibrary', handleRevalidate)
- // return () => {
- // document.removeEventListener('revalidateLibrary', handleRevalidate)
- // }
- // }, [mutate])
-
useEffect(() => {
if (queryValue.startsWith('#')) {
debouncedFetchSearchResults(
@@ -167,13 +161,6 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element {
window.localStorage.setItem('nav-return', router.asPath)
}, [router.asPath])
- // const hasMore = useMemo(() => {
- // if (!itemsPages) {
- // return false
- // }
- // return itemsPages[itemsPages.length - 1].search.pageInfo.hasNextPage
- // }, [itemsPages])
-
const libraryItems = useMemo(() => {
const items =
itemsPages?.pages
@@ -387,64 +374,98 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element {
return
}
- // switch (action) {
- // case 'showDetail':
- // const username = viewerData?.me?.profile.username
- // if (username) {
- // setActiveCardId(item.node.id)
- // if (item.node.state === State.PROCESSING) {
- // router.push(`/article?url=${encodeURIComponent(item.node.url)}`)
- // } else {
- // const dl =
- // item.node.pageType === PageType.HIGHLIGHTS
- // ? `#${item.node.id}`
- // : ''
- // router.push(`/${username}/${item.node.slug}` + dl)
- // }
- // }
- // break
- // case 'showOriginal':
- // const url = item.node.originalArticleUrl
- // if (url) {
- // window.open(url, '_blank')
- // }
- // break
- // case 'archive':
- // performActionOnItem('archive', item)
- // break
- // case 'unarchive':
- // performActionOnItem('unarchive', item)
- // break
- // case 'delete':
- // performActionOnItem('delete', item)
- // break
- // case 'restore':
- // performActionOnItem('restore', item)
- // break
- // case 'mark-read':
- // performActionOnItem('mark-read', item)
- // break
- // case 'mark-unread':
- // performActionOnItem('mark-unread', item)
- // break
- // case 'set-labels':
- // setLabelsTarget(item)
- // break
- // case 'open-notebook':
- // if (!notebookTarget) {
- // setNotebookTarget(item)
- // } else {
- // setNotebookTarget(undefined)
- // }
- // break
- // case 'unsubscribe':
- // performActionOnItem('unsubscribe', item)
- // case 'update-item':
- // performActionOnItem('update-item', item)
- // break
- // default:
- // console.warn('unknown action: ', action)
- // }
+ switch (action) {
+ case 'showDetail':
+ const username = viewerData?.me?.profile.username
+ if (username) {
+ setActiveCardId(item.node.id)
+ if (item.node.state === State.PROCESSING) {
+ router.push(`/article?url=${encodeURIComponent(item.node.url)}`)
+ } else {
+ router.push(`/${username}/${item.node.slug}`)
+ }
+ }
+ break
+ case 'showOriginal':
+ const url = item.node.originalArticleUrl
+ if (url) {
+ window.open(url, '_blank')
+ }
+ break
+ case 'archive':
+ case 'unarchive':
+ try {
+ await archiveItem.mutateAsync({
+ linkId: item.node.id,
+ archived: action == 'archive',
+ })
+ } catch {
+ showErrorToast(`Error ${action}ing item`, {
+ position: 'bottom-right',
+ })
+ return
+ }
+ showSuccessToast(`Item ${action}d`, {
+ position: 'bottom-right',
+ })
+ break
+ case 'delete':
+ try {
+ await deleteItem.mutateAsync(item.node.id)
+ } catch {
+ showErrorToast(`Error deleting item`, {
+ position: 'bottom-right',
+ })
+ return
+ }
+ showSuccessToast(`Item deleted`, {
+ position: 'bottom-right',
+ })
+ break
+ case 'mark-read':
+ case 'mark-unread':
+ const desc = action == 'mark-read' ? 'read' : 'unread'
+ const values =
+ action == 'mark-read'
+ ? {
+ readingProgressPercent: 100,
+ readingProgressTopPercent: 100,
+ readingProgressAnchorIndex: 0,
+ }
+ : {
+ readingProgressPercent: 0,
+ readingProgressTopPercent: 0,
+ readingProgressAnchorIndex: 0,
+ }
+ try {
+ await updateItemReadStatus.mutateAsync({
+ id: item.node.id,
+ force: true,
+ ...values,
+ })
+ } catch {
+ showErrorToast(`Error marking as ${desc}`, {
+ position: 'bottom-right',
+ })
+ return
+ }
+ break
+ case 'set-labels':
+ setLabelsTarget(item)
+ break
+ case 'open-notebook':
+ if (!notebookTarget) {
+ setNotebookTarget(item)
+ } else {
+ setNotebookTarget(undefined)
+ }
+ break
+ case 'unsubscribe':
+ setLinkToUnsubscribe(item.node)
+ break
+ default:
+ console.warn('unknown action: ', action)
+ }
}
const modalTargetItem = useMemo(() => {
@@ -1187,9 +1208,6 @@ export function LibraryItemsLayout(
{props.showEditTitleModal && (
- props.actionHandler('update-item', item)
- }
onOpenChange={() => {
props.setShowEditTitleModal(false)
props.setLinkToEdit(undefined)
diff --git a/packages/web/lib/articleActions.ts b/packages/web/lib/articleActions.ts
index 3218a39df..cb6f8da8b 100644
--- a/packages/web/lib/articleActions.ts
+++ b/packages/web/lib/articleActions.ts
@@ -1,8 +1,8 @@
import { Highlight } from './networking/fragments/highlightFragment'
import { ArticleReadingProgressMutationInput } from './networking/mutations/articleReadingProgressMutation'
-import { CreateHighlightInput } from './networking/mutations/createHighlightMutation'
import { MergeHighlightInput } from './networking/mutations/mergeHighlightMutation'
import { UpdateHighlightInput } from './networking/mutations/updateHighlightMutation'
+import { CreateHighlightInput } from './networking/highlights/useItemHighlights'
export type ArticleMutations = {
createHighlightMutation: (
diff --git a/packages/web/lib/highlights/createHighlight.ts b/packages/web/lib/highlights/createHighlight.ts
index 9b165483b..6c5d45e75 100644
--- a/packages/web/lib/highlights/createHighlight.ts
+++ b/packages/web/lib/highlights/createHighlight.ts
@@ -54,7 +54,7 @@ export async function createHighlight(
if (!input.selection.selection) {
return {}
}
-
+ console.log(' overlapping: ', input.selection.overlapHighlights)
const shouldMerge = input.selection.overlapHighlights.length > 0
const { range, selection } = input.selection
diff --git a/packages/web/lib/highlights/useSelection.tsx b/packages/web/lib/highlights/useSelection.tsx
index c32db86d2..6d12c74ea 100644
--- a/packages/web/lib/highlights/useSelection.tsx
+++ b/packages/web/lib/highlights/useSelection.tsx
@@ -8,19 +8,20 @@ import type { SelectionAttributes } from './highlightHelpers'
/**
* Get the range of text with {@link SelectionAttributes} that user has selected
- *
+ *
* Event Handlers for detecting/using new highlight selection are registered
- *
+ *
* If the new highlight selection overlaps with existing highlights, the new selection is merged.
- *
+ *
* @param highlightLocations existing highlights
* @returns selection range and its setter
*/
export function useSelection(
highlightLocations: HighlightLocation[]
): [SelectionAttributes | null, (x: SelectionAttributes | null) => void] {
- const [touchStartPos, setTouchStartPos] =
- useState<{ x: number; y: number } | undefined>(undefined)
+ const [touchStartPos, setTouchStartPos] = useState<
+ { x: number; y: number } | undefined
+ >(undefined)
const [selectionAttributes, setSelectionAttributes] =
useState(null)
@@ -246,32 +247,38 @@ async function makeSelectionRange(): Promise<
/**
* Edge case:
- * If the selection ends on range endContainer (or startContainer in reverse select) but no text is selected (i.e. selection ends at
- * an empty area), the preceding text is highlighted due to range normalizing.
+ * If the selection ends on range endContainer (or startContainer in reverse select) but no text is selected (i.e. selection ends at
+ * an empty area), the preceding text is highlighted due to range normalizing.
* This is a visual bug and would sometimes lead to weird highlight behavior during removal.
*/
const selectionEndNode = selection.focusNode
const selectionEndOffset = selection.focusOffset
- const selectionStartNode = isReverseSelected ? range.endContainer : range.startContainer
+ const selectionStartNode = isReverseSelected
+ ? range.endContainer
+ : range.startContainer
if (selectionEndNode?.nodeType === Node.TEXT_NODE) {
- const selectionEndNodeEdgeIndex = isReverseSelected ? selectionEndNode.textContent?.length : 0
+ const selectionEndNodeEdgeIndex = isReverseSelected
+ ? selectionEndNode.textContent?.length
+ : 0
- if (selectionStartNode !== selectionEndNode &&
- selectionEndOffset == selectionEndNodeEdgeIndex) {
- clipRangeToNearestAnchor(range, selectionEndNode, isReverseSelected)
+ if (
+ selectionStartNode !== selectionEndNode &&
+ selectionEndOffset == selectionEndNodeEdgeIndex
+ ) {
+ clipRangeToNearestAnchor(range, selectionEndNode, isReverseSelected)
}
- }
-
+ }
+
return isRangeAllowed ? { range, isReverseSelected, selection } : undefined
}
/**
* Clip selection range to the beginning/end of the adjacent anchor element
- *
+ *
* @param range selection range
* @param selectionEndNode the node where the selection ended at
- * @param isReverseSelected
+ * @param isReverseSelected
*/
const clipRangeToNearestAnchor = (
range: Range,
@@ -279,30 +286,44 @@ const clipRangeToNearestAnchor = (
isReverseSelected: boolean
) => {
let nearestAnchorElement = selectionEndNode.parentElement
- while (nearestAnchorElement !== null && !nearestAnchorElement.hasAttribute('data-omnivore-anchor-idx')) {
- nearestAnchorElement = nearestAnchorElement.parentElement;
+ while (
+ nearestAnchorElement !== null &&
+ !nearestAnchorElement.hasAttribute('data-omnivore-anchor-idx')
+ ) {
+ nearestAnchorElement = nearestAnchorElement.parentElement
}
if (!nearestAnchorElement) {
- throw Error('Unable to find nearest anchor element for node: ' + selectionEndNode)
+ throw Error(
+ 'Unable to find nearest anchor element for node: ' + selectionEndNode
+ )
}
- let anchorId = Number(nearestAnchorElement.getAttribute('data-omnivore-anchor-idx')!)
+ let anchorId = Number(
+ nearestAnchorElement.getAttribute('data-omnivore-anchor-idx')!
+ )
let adjacentAnchorId, adjacentAnchor, adjacentAnchorOffset
if (isReverseSelected) {
// move down to find adjacent anchor node and clip at its beginning
adjacentAnchorId = anchorId + 1
- adjacentAnchor = document.querySelectorAll(`[data-omnivore-anchor-idx='${adjacentAnchorId}']`)[0]
+ adjacentAnchor = document.querySelectorAll(
+ `[data-omnivore-anchor-idx='${adjacentAnchorId}']`
+ )[0]
adjacentAnchorOffset = 0
range.setStart(adjacentAnchor, adjacentAnchorOffset)
} else {
// move up to find adjacent anchor node and clip at its end
do {
adjacentAnchorId = --anchorId
- adjacentAnchor = document.querySelectorAll(`[data-omnivore-anchor-idx='${adjacentAnchorId}']`)[0]
+ adjacentAnchor = document.querySelectorAll(
+ `[data-omnivore-anchor-idx='${adjacentAnchorId}']`
+ )[0]
} while (adjacentAnchor.contains(selectionEndNode))
if (adjacentAnchor.textContent) {
let lastTextNodeChild = adjacentAnchor.lastChild
- while (!!lastTextNodeChild && lastTextNodeChild.nodeType !== Node.TEXT_NODE) {
- lastTextNodeChild = lastTextNodeChild.previousSibling;
+ while (
+ !!lastTextNodeChild &&
+ lastTextNodeChild.nodeType !== Node.TEXT_NODE
+ ) {
+ lastTextNodeChild = lastTextNodeChild.previousSibling
}
adjacentAnchor = lastTextNodeChild
adjacentAnchorOffset = adjacentAnchor?.nodeValue?.length ?? 0
@@ -326,7 +347,7 @@ export type RangeEndPos = {
/**
* Return coordinates of the screen area occupied by the last line of user selection
- *
+ *
* @param range range of user selection
* @param getFirst whether to get first line of user selection. Get last if false (default)
* @returns {RangeEndPos} selection coordinates
diff --git a/packages/web/lib/hooks/useLibraryItemActions.tsx b/packages/web/lib/hooks/useLibraryItemActions.tsx
index 18474f702..fce259c47 100644
--- a/packages/web/lib/hooks/useLibraryItemActions.tsx
+++ b/packages/web/lib/hooks/useLibraryItemActions.tsx
@@ -1,18 +1,24 @@
import { useCallback } from 'react'
-import { setLinkArchivedMutation } from '../networking/mutations/setLinkArchivedMutation'
import {
showErrorToast,
showSuccessToast,
showSuccessToastWithUndo,
} from '../toastHelpers'
-import { deleteLinkMutation } from '../networking/mutations/deleteLinkMutation'
import { updatePageMutation } from '../networking/mutations/updatePageMutation'
import { State } from '../networking/fragments/articleFragment'
-import { moveToFolderMutation } from '../networking/mutations/moveToLibraryMutation'
+import {
+ useArchiveItem,
+ useDeleteItem,
+ useMoveItemToFolder,
+} from '../networking/library_items/useLibraryItems'
export default function useLibraryItemActions() {
- const archiveItem = useCallback(async (itemId: string) => {
- const result = await setLinkArchivedMutation({
+ const archiveItem = useArchiveItem()
+ const deleteItem = useDeleteItem()
+ const moveItem = useMoveItemToFolder()
+
+ const doArchiveItem = useCallback(async (itemId: string) => {
+ const result = await archiveItem.mutateAsync({
linkId: itemId,
archived: true,
})
@@ -27,8 +33,8 @@ export default function useLibraryItemActions() {
return !!result
}, [])
- const deleteItem = useCallback(async (itemId: string, undo: () => void) => {
- const result = await deleteLinkMutation(itemId)
+ const doDeleteItem = useCallback(async (itemId: string, undo: () => void) => {
+ const result = await deleteItem.mutateAsync(itemId)
if (result) {
showSuccessToastWithUndo('Item removed', async () => {
@@ -52,8 +58,8 @@ export default function useLibraryItemActions() {
return !!result
}, [])
- const moveItem = useCallback(async (itemId: string) => {
- const result = await moveToFolderMutation(itemId, 'inbox')
+ const doMoveItem = useCallback(async (itemId: string) => {
+ const result = await moveItem.mutateAsync({ itemId, folder: 'inbox' })
if (result) {
showSuccessToast('Moved to library', { position: 'bottom-right' })
} else {
@@ -85,5 +91,10 @@ export default function useLibraryItemActions() {
[]
)
- return { archiveItem, deleteItem, moveItem, shareItem }
+ return {
+ archiveItem: doArchiveItem,
+ deleteItem: doDeleteItem,
+ moveItem: doMoveItem,
+ shareItem,
+ }
}
diff --git a/packages/web/lib/networking/highlights/gql.tsx b/packages/web/lib/networking/highlights/gql.tsx
new file mode 100644
index 000000000..9225f8795
--- /dev/null
+++ b/packages/web/lib/networking/highlights/gql.tsx
@@ -0,0 +1,77 @@
+import { gql } from 'graphql-request'
+import { highlightFragment } from '../fragments/highlightFragment'
+
+export const GQL_CREATE_HIGHLIGHT = gql`
+ mutation CreateHighlight($input: CreateHighlightInput!) {
+ createHighlight(input: $input) {
+ ... on CreateHighlightSuccess {
+ highlight {
+ ...HighlightFields
+ }
+ }
+
+ ... on CreateHighlightError {
+ errorCodes
+ }
+ }
+ }
+ ${highlightFragment}
+`
+
+export const GQL_DELETE_HIGHLIGHT = gql`
+ mutation DeleteHighlight($highlightId: ID!) {
+ deleteHighlight(highlightId: $highlightId) {
+ ... on DeleteHighlightSuccess {
+ highlight {
+ id
+ }
+ }
+ ... on DeleteHighlightError {
+ errorCodes
+ }
+ }
+ }
+`
+
+export const GQL_UPDATE_HIGHLIGHT = gql`
+ mutation UpdateHighlight($input: UpdateHighlightInput!) {
+ updateHighlight(input: $input) {
+ ... on UpdateHighlightSuccess {
+ highlight {
+ id
+ }
+ }
+
+ ... on UpdateHighlightError {
+ errorCodes
+ }
+ }
+ }
+`
+
+export const GQL_MERGE_HIGHLIGHT = gql`
+ mutation MergeHighlight($input: MergeHighlightInput!) {
+ mergeHighlight(input: $input) {
+ ... on MergeHighlightSuccess {
+ highlight {
+ id
+ shortId
+ quote
+ prefix
+ suffix
+ patch
+ color
+ createdAt
+ updatedAt
+ annotation
+ sharedAt
+ createdByMe
+ }
+ overlapHighlightIdList
+ }
+ ... on MergeHighlightError {
+ errorCodes
+ }
+ }
+ }
+`
diff --git a/packages/web/lib/networking/highlights/useItemHighlights.tsx b/packages/web/lib/networking/highlights/useItemHighlights.tsx
new file mode 100644
index 000000000..da62088ef
--- /dev/null
+++ b/packages/web/lib/networking/highlights/useItemHighlights.tsx
@@ -0,0 +1,209 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { gqlFetcher } from '../networkHelpers'
+import {
+ GQL_CREATE_HIGHLIGHT,
+ GQL_DELETE_HIGHLIGHT,
+ GQL_MERGE_HIGHLIGHT,
+ GQL_UPDATE_HIGHLIGHT,
+} from './gql'
+import { updateItemProperty } from '../library_items/useLibraryItems'
+import { Highlight, HighlightType } from '../fragments/highlightFragment'
+import { UpdateHighlightInput } from '../mutations/updateHighlightMutation'
+import { MergeHighlightInput } from '../mutations/mergeHighlightMutation'
+
+export const useCreateHighlight = () => {
+ const queryClient = useQueryClient()
+ const createHighlight = async (variables: {
+ itemId: string
+ input: CreateHighlightInput
+ }) => {
+ const result = (await gqlFetcher(GQL_CREATE_HIGHLIGHT, {
+ input: variables.input,
+ })) as CreateHighlightData
+ if (result.createHighlight.errorCodes?.length) {
+ throw new Error(result.createHighlight.errorCodes[0])
+ }
+ return result.createHighlight.highlight
+ }
+ return useMutation({
+ mutationFn: createHighlight,
+ onSuccess: (newHighlight, variables) => {
+ if (newHighlight) {
+ updateItemProperty(queryClient, variables.itemId, (item) => {
+ return {
+ ...item,
+ highlights: [...item.highlights, newHighlight],
+ }
+ })
+ }
+ },
+ })
+}
+
+export const useDeleteHighlight = () => {
+ const queryClient = useQueryClient()
+ const deleteHighlight = async (variables: {
+ itemId: string
+ highlightId: string
+ }) => {
+ const result = (await gqlFetcher(GQL_DELETE_HIGHLIGHT, {
+ highlightId: variables.highlightId,
+ })) as DeleteHighlightData
+ if (result.deleteHighlight.errorCodes?.length) {
+ throw new Error(result.deleteHighlight.errorCodes[0])
+ }
+ return result.deleteHighlight.highlight
+ }
+ return useMutation({
+ mutationFn: deleteHighlight,
+ onSuccess: (deletedHighlight, variables) => {
+ if (deletedHighlight) {
+ updateItemProperty(queryClient, variables.itemId, (item) => {
+ return {
+ ...item,
+ highlights: item.highlights.filter(
+ (h) => h.id != deletedHighlight.id
+ ),
+ }
+ })
+ }
+ },
+ })
+}
+
+export const useUpdateHighlight = () => {
+ const queryClient = useQueryClient()
+ const updateHighlight = async (variables: {
+ itemId: string
+ input: UpdateHighlightInput
+ }) => {
+ const result = (await gqlFetcher(GQL_UPDATE_HIGHLIGHT, {
+ input: variables.input,
+ })) as UpdateHighlightData
+ if (result.updateHighlight.errorCodes?.length) {
+ throw new Error(result.updateHighlight.errorCodes[0])
+ }
+ return result.updateHighlight.highlight
+ }
+ return useMutation({
+ mutationFn: updateHighlight,
+ onSuccess: (updatedHighlight, variables) => {
+ if (updatedHighlight) {
+ updateItemProperty(queryClient, variables.itemId, (item) => {
+ return {
+ ...item,
+ highlights: [
+ ...item.highlights.filter((h) => h.id != updatedHighlight.id),
+ updatedHighlight,
+ ],
+ }
+ })
+ }
+ },
+ })
+}
+
+export const useMergeHighlight = () => {
+ const queryClient = useQueryClient()
+ const mergeHighlight = async (variables: {
+ itemId: string
+ input: MergeHighlightInput
+ }) => {
+ const result = (await gqlFetcher(GQL_MERGE_HIGHLIGHT, {
+ input: {
+ id: variables.input.id,
+ shortId: variables.input.shortId,
+ articleId: variables.input.articleId,
+ patch: variables.input.patch,
+ quote: variables.input.quote,
+ prefix: variables.input.prefix,
+ suffix: variables.input.suffix,
+ html: variables.input.html,
+ annotation: variables.input.annotation,
+ overlapHighlightIdList: variables.input.overlapHighlightIdList,
+ highlightPositionPercent: variables.input.highlightPositionPercent,
+ highlightPositionAnchorIndex:
+ variables.input.highlightPositionAnchorIndex,
+ },
+ })) as MergeHighlightData
+ if (result.mergeHighlight.errorCodes?.length) {
+ throw new Error(result.mergeHighlight.errorCodes[0])
+ }
+ return result.mergeHighlight
+ }
+ return useMutation({
+ mutationFn: mergeHighlight,
+ onSuccess: (mergeHighlights, variables) => {
+ if (mergeHighlights && mergeHighlights.highlight) {
+ const newHighlight = mergeHighlights.highlight
+ const mergedIds = mergeHighlights.overlapHighlightIdList ?? []
+ updateItemProperty(queryClient, variables.itemId, (item) => {
+ return {
+ ...item,
+ highlights: [
+ ...item.highlights.filter((h) => mergedIds.indexOf(h.id) == -1),
+ newHighlight,
+ ],
+ }
+ })
+ }
+ },
+ })
+}
+
+type MergeHighlightData = {
+ mergeHighlight: MergeHighlightResult
+}
+
+type MergeHighlightResult = {
+ highlight?: Highlight
+ overlapHighlightIdList?: string[]
+ errorCodes?: string[]
+}
+
+type UpdateHighlightData = {
+ updateHighlight: UpdateHighlightResult
+}
+
+type UpdateHighlightResult = {
+ highlight?: Highlight
+ errorCodes?: string[]
+}
+
+type DeleteHighlightData = {
+ deleteHighlight: DeleteHighlightResult
+}
+
+type DeleteHighlightResult = {
+ highlight?: Highlight
+ errorCodes?: string[]
+}
+
+type CreateHighlightData = {
+ createHighlight: CreateHighlightResult
+}
+
+type CreateHighlightResult = {
+ highlight?: Highlight
+ errorCodes?: string[]
+}
+
+export type CreateHighlightInput = {
+ id: string
+ shortId: string
+ articleId: string
+
+ prefix?: string
+ suffix?: string
+ quote?: string
+ html?: string
+ color?: string
+ annotation?: string
+
+ patch?: string
+
+ highlightPositionPercent?: number
+ highlightPositionAnchorIndex?: number
+
+ type?: HighlightType
+}
diff --git a/packages/web/lib/networking/library_items/gql.tsx b/packages/web/lib/networking/library_items/gql.tsx
index cc8ce12f2..f9215c9b2 100644
--- a/packages/web/lib/networking/library_items/gql.tsx
+++ b/packages/web/lib/networking/library_items/gql.tsx
@@ -132,6 +132,19 @@ export const GQL_DELETE_LIBRARY_ITEM = gql`
}
`
+export const GQL_MOVE_ITEM_TO_FOLDER = gql`
+ mutation MoveToFolder($id: ID!, $folder: String!) {
+ moveToFolder(id: $id, folder: $folder) {
+ ... on MoveToFolderSuccess {
+ success
+ }
+ ... on MoveToFolderError {
+ errorCodes
+ }
+ }
+ }
+`
+
export const GQL_UPDATE_LIBRARY_ITEM = gql`
mutation UpdatePage($input: UpdatePageInput!) {
updatePage(input: $input) {
diff --git a/packages/web/lib/networking/library_items/useLibraryItems.tsx b/packages/web/lib/networking/library_items/useLibraryItems.tsx
index 7b92719da..174e8a917 100644
--- a/packages/web/lib/networking/library_items/useLibraryItems.tsx
+++ b/packages/web/lib/networking/library_items/useLibraryItems.tsx
@@ -11,18 +11,15 @@ import { ContentReader, PageType, State } from '../fragments/articleFragment'
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
import { makeGqlFetcher, requestHeaders } from '../networkHelpers'
import { Label } from '../fragments/labelFragment'
-import { moveToFolderMutation } from '../mutations/moveToLibraryMutation'
import {
GQL_DELETE_LIBRARY_ITEM,
GQL_GET_LIBRARY_ITEM_CONTENT,
+ GQL_MOVE_ITEM_TO_FOLDER,
GQL_SEARCH_QUERY,
GQL_SET_LINK_ARCHIVED,
GQL_UPDATE_LIBRARY_ITEM,
} from './gql'
-import { parseGraphQLResponse } from '../queries/gql-errors'
import { gqlEndpoint } from '../../appConfig'
-import { GraphQLResponse } from 'graphql-request/dist/types'
-import { ArticleAttributes } from '../queries/useGetArticleQuery'
function gqlFetcher(
query: string,
@@ -63,25 +60,55 @@ const updateItemPropertyInCache = (
propertyName: string,
propertyValue: any
) => {
- const setter = createDictionary(propertyName, propertyValue)
+ updateItemProperty(queryClient, itemId, (oldItem) => {
+ const setter = createDictionary(propertyName, propertyValue)
+ return {
+ ...oldItem,
+ ...setter,
+ }
+ })
+}
+
+export const updateItemProperty = (
+ queryClient: QueryClient,
+ itemId: string,
+ updateFunc: (input: ArticleAttributes) => ArticleAttributes
+) => {
+ let foundItemSlug: string | undefined
const keys = queryClient
.getQueryCache()
.findAll({ queryKey: ['libraryItems'] })
keys.forEach((query) => {
queryClient.setQueryData(query.queryKey, (data: any) => {
if (!data) return data
- return {
+ const updatedData = {
...data,
pages: data.pages.map((page: any) => ({
...page,
- edges: page.edges.map((edge: any) =>
- edge.node.id === itemId
- ? { ...edge, node: { ...edge.node, ...setter } }
- : edge
- ),
+ edges: page.edges.map((edge: any) => {
+ if (edge.node.id === itemId) {
+ foundItemSlug = edge.node.slug
+ return {
+ ...edge,
+ node: { ...edge.node, ...updateFunc(edge.node) },
+ }
+ }
+ return edge
+ }),
})),
}
+ return updatedData
})
+ if (foundItemSlug)
+ queryClient.setQueryData(
+ ['libraryItem', foundItemSlug],
+ (oldData: ArticleAttributes) => {
+ return {
+ ...oldData,
+ ...updateFunc(oldData),
+ }
+ }
+ )
})
}
@@ -93,7 +120,6 @@ const updateItemPropertiesInCache = (
const keys = queryClient
.getQueryCache()
.findAll({ queryKey: ['libraryItems'] })
- console.log('updateItemPropertiesInCache::libraryItems: ', keys)
keys.forEach((query) => {
queryClient.setQueryData(query.queryKey, (data: any) => {
if (!data) return data
@@ -243,6 +269,43 @@ export const useRestoreItem = () => {
})
}
+export const useMoveItemToFolder = () => {
+ const queryClient = useQueryClient()
+ const restoreItem = async (variables: { itemId: string; folder: string }) => {
+ const result = (await gqlFetcher(GQL_MOVE_ITEM_TO_FOLDER, {
+ id: variables.itemId,
+ folder: variables.folder,
+ })) as UpdateLibraryItemData
+ if (result.updatePage.errorCodes?.length) {
+ throw new Error(result.updatePage.errorCodes[0])
+ }
+ return result.updatePage
+ }
+ return useMutation({
+ mutationFn: restoreItem,
+ onMutate: async (variables: { itemId: string; folder: string }) => {
+ await queryClient.cancelQueries({ queryKey: ['libraryItems'] })
+ updateItemPropertyInCache(
+ queryClient,
+ variables.itemId,
+ 'folder',
+ variables.folder
+ )
+ return { previousItems: queryClient.getQueryData(['libraryItems']) }
+ },
+ onError: (error, itemId, context) => {
+ if (context?.previousItems) {
+ queryClient.setQueryData(['libraryItems'], context.previousItems)
+ }
+ },
+ onSettled: async () => {
+ await queryClient.invalidateQueries({
+ queryKey: ['libraryItems'],
+ })
+ },
+ })
+}
+
export const useUpdateItemReadStatus = () => {
const queryClient = useQueryClient()
const updateItemReadStatus = async (
@@ -291,11 +354,6 @@ export const useGetLibraryItemContent = (username: string, slug: string) => {
return useQuery({
queryKey: ['libraryItem', slug],
queryFn: async () => {
- console.log('input: ', {
- slug,
- username,
- includeFriendsHighlights: false,
- })
const response = (await gqlFetcher(GQL_GET_LIBRARY_ITEM_CONTENT, {
slug,
username,
@@ -313,6 +371,36 @@ export const useGetLibraryItemContent = (username: string, slug: string) => {
})
}
+export type TextDirection = 'RTL' | 'LTR'
+
+export type ArticleAttributes = {
+ id: string
+ title: string
+ url: string
+ originalArticleUrl: string
+ author?: string
+ image?: string
+ savedAt: string
+ createdAt: string
+ publishedAt?: string
+ description?: string
+ wordsCount?: number
+ contentReader: ContentReader
+ readingProgressPercent: number
+ readingProgressTopPercent?: number
+ readingProgressAnchorIndex: number
+ slug: string
+ folder: string
+ savedByViewer?: boolean
+ content: string
+ highlights: Highlight[]
+ linkId: string
+ labels?: Label[]
+ state?: State
+ directionality?: TextDirection
+ recommendations?: Recommendation[]
+}
+
type ArticleResult = {
article?: ArticleAttributes
errorCodes?: string[]
diff --git a/packages/web/lib/networking/mutations/createHighlightMutation.ts b/packages/web/lib/networking/mutations/createHighlightMutation.ts
index f21268b96..703f2bfd2 100644
--- a/packages/web/lib/networking/mutations/createHighlightMutation.ts
+++ b/packages/web/lib/networking/mutations/createHighlightMutation.ts
@@ -5,26 +5,7 @@ import {
highlightFragment,
HighlightType,
} from './../fragments/highlightFragment'
-
-export type CreateHighlightInput = {
- id: string
- shortId: string
- articleId: string
-
- prefix?: string
- suffix?: string
- quote?: string
- html?: string
- color?: string
- annotation?: string
-
- patch?: string
-
- highlightPositionPercent?: number
- highlightPositionAnchorIndex?: number
-
- type?: HighlightType
-}
+import { CreateHighlightInput } from '../highlights/useItemHighlights'
type CreateHighlightOutput = {
createHighlight: InnerCreateHighlightOutput
diff --git a/packages/web/lib/networking/mutations/deleteLinkMutation.ts b/packages/web/lib/networking/mutations/deleteLinkMutation.ts
deleted file mode 100644
index c76d35e47..000000000
--- a/packages/web/lib/networking/mutations/deleteLinkMutation.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { gql } from 'graphql-request'
-import { gqlFetcher } from '../networkHelpers'
-
-export async function deleteLinkMutation(
- linkId: string
-): Promise {
- const mutation = gql`
- mutation SetBookmarkArticle($input: SetBookmarkArticleInput!) {
- setBookmarkArticle(input: $input) {
- ... on SetBookmarkArticleSuccess {
- bookmarkedArticle {
- id
- }
- }
- ... on SetBookmarkArticleError {
- errorCodes
- }
- }
- }`
-
- try {
- const data = await gqlFetcher(mutation, { input: { articleID: linkId, bookmark: false }})
- return data
- } catch (error) {
- console.log('SetBookmarkArticleOutput error', error)
- return undefined
- }
-}
diff --git a/packages/web/lib/networking/mutations/moveToLibraryMutation.ts b/packages/web/lib/networking/mutations/moveToLibraryMutation.ts
deleted file mode 100644
index 205312dd6..000000000
--- a/packages/web/lib/networking/mutations/moveToLibraryMutation.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { gql } from 'graphql-request'
-import { gqlFetcher } from '../networkHelpers'
-
-type MoveToFolderResponseData = {
- success?: boolean
- errorCodes?: string[]
-}
-
-type MoveToFolderResponse = {
- moveToFolder?: MoveToFolderResponseData
-}
-
-export async function moveToFolderMutation(
- itemId: string,
- folder: string
-): Promise {
- const mutation = gql`
- mutation MoveToFolder($id: ID!, $folder: String!) {
- moveToFolder(id: $id, folder: $folder) {
- ... on MoveToFolderSuccess {
- success
- }
- ... on MoveToFolderError {
- errorCodes
- }
- }
- }
- `
-
- try {
- const response = await gqlFetcher(mutation, { id: itemId, folder })
- const data = response as MoveToFolderResponse | undefined
- if (data?.moveToFolder?.errorCodes) {
- return false
- }
- return data?.moveToFolder?.success ?? false
- } catch (error) {
- console.log('MoveToFolder error', error)
- return false
- }
-}
diff --git a/packages/web/lib/networking/mutations/setLinkArchivedMutation.ts b/packages/web/lib/networking/mutations/setLinkArchivedMutation.ts
deleted file mode 100644
index cf25ec90c..000000000
--- a/packages/web/lib/networking/mutations/setLinkArchivedMutation.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { gql } from 'graphql-request'
-import { gqlFetcher } from '../networkHelpers'
-
-type SetLinkArchivedInput = {
- linkId: string
- archived: boolean
-}
-
-export async function setLinkArchivedMutation(
- input: SetLinkArchivedInput
-): Promise | undefined> {
- const mutation = gql`
- mutation SetLinkArchived($input: ArchiveLinkInput!) {
- setLinkArchived(input: $input) {
- ... on ArchiveLinkSuccess {
- linkId
- message
- }
- ... on ArchiveLinkError {
- message
- errorCodes
- }
- }
- }
- `
-
- try {
- const data = await gqlFetcher(mutation, { input })
- return data as Record | undefined
- } catch (error) {
- console.log('SetLinkArchivedInput error', error)
- return undefined
- }
-}
diff --git a/packages/web/lib/networking/mutations/updateShareHighlightCommentMutation.ts b/packages/web/lib/networking/mutations/updateShareHighlightCommentMutation.ts
deleted file mode 100644
index d563862d0..000000000
--- a/packages/web/lib/networking/mutations/updateShareHighlightCommentMutation.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { gql } from 'graphql-request'
-import { gqlFetcher } from '../networkHelpers'
-
-type ShareHighlightCommentMutationInput = {
- highlightId: string
- annotation?: string
-}
-
-export async function shareHighlightCommentMutation(
- input: ShareHighlightCommentMutationInput
-): Promise {
- const mutation = gql`
- mutation UpdateHighlight($input: UpdateHighlightInput!) {
- updateHighlight(input: $input) {
- ... on UpdateHighlightSuccess {
- highlight {
- id
- }
- }
-
- ... on UpdateHighlightError {
- errorCodes
- }
- }
- }
- `
-
- try {
- await gqlFetcher(mutation, { input })
- return true
- } catch {
- return false
- }
-}
diff --git a/packages/web/lib/networking/queries/useGetArticleOriginalHtmlQuery.tsx b/packages/web/lib/networking/queries/useGetArticleOriginalHtmlQuery.tsx
index 94870fd94..02ace5c84 100644
--- a/packages/web/lib/networking/queries/useGetArticleOriginalHtmlQuery.tsx
+++ b/packages/web/lib/networking/queries/useGetArticleOriginalHtmlQuery.tsx
@@ -1,6 +1,7 @@
import { gql } from 'graphql-request'
import useSWRImmutable from 'swr'
import { makeGqlFetcher, RequestContext, ssrFetcher } from '../networkHelpers'
+import { ArticleAttributes } from '../library_items/useLibraryItems'
type ArticleQueryInput = {
username?: string
@@ -17,11 +18,6 @@ type NestedArticleData = {
errorCodes?: string[]
}
-export type ArticleAttributes = {
- id: string
- originalHtml: string
-}
-
const query = gql`
query GetArticle($username: String!, $slug: String!) {
article(username: $username, slug: $slug) {
diff --git a/packages/web/lib/networking/queries/useGetArticleQuery.tsx b/packages/web/lib/networking/queries/useGetArticleQuery.tsx
index 48139d510..63974022b 100644
--- a/packages/web/lib/networking/queries/useGetArticleQuery.tsx
+++ b/packages/web/lib/networking/queries/useGetArticleQuery.tsx
@@ -9,7 +9,11 @@ import {
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
import { ScopedMutator } from 'swr/dist/_internal'
import { Label, labelFragment } from '../fragments/labelFragment'
-import { LibraryItems, Recommendation } from '../library_items/useLibraryItems'
+import {
+ ArticleAttributes,
+ LibraryItems,
+ Recommendation,
+} from '../library_items/useLibraryItems'
import useSWR from 'swr'
import { recommendationFragment } from '../library_items/gql'
@@ -36,36 +40,6 @@ type NestedArticleData = {
errorCodes?: string[]
}
-export type TextDirection = 'RTL' | 'LTR'
-
-export type ArticleAttributes = {
- id: string
- title: string
- url: string
- originalArticleUrl: string
- author?: string
- image?: string
- savedAt: string
- createdAt: string
- publishedAt?: string
- description?: string
- wordsCount?: number
- contentReader: ContentReader
- readingProgressPercent: number
- readingProgressTopPercent?: number
- readingProgressAnchorIndex: number
- slug: string
- folder: string
- savedByViewer?: boolean
- content: string
- highlights: Highlight[]
- linkId: string
- labels?: Label[]
- state?: State
- directionality?: TextDirection
- recommendations?: Recommendation[]
-}
-
const query = gql`
query GetArticle(
$username: String!
diff --git a/packages/web/lib/networking/queries/useGetArticleSavingStatus.tsx b/packages/web/lib/networking/queries/useGetArticleSavingStatus.tsx
index 8a87f10ac..a99cdef22 100644
--- a/packages/web/lib/networking/queries/useGetArticleSavingStatus.tsx
+++ b/packages/web/lib/networking/queries/useGetArticleSavingStatus.tsx
@@ -3,7 +3,7 @@ import useSWR from 'swr'
import { articleFragment } from '../fragments/articleFragment'
import { highlightFragment } from '../fragments/highlightFragment'
import { makeGqlFetcher } from '../networkHelpers'
-import { ArticleAttributes } from './useGetArticleQuery'
+import { ArticleAttributes } from '../library_items/useLibraryItems'
type ArticleSavingStatusInput = {
id?: string
diff --git a/packages/web/pages/[username]/[slug]/debug.tsx b/packages/web/pages/[username]/[slug]/debug.tsx
index f933e2abc..2e74926fa 100644
--- a/packages/web/pages/[username]/[slug]/debug.tsx
+++ b/packages/web/pages/[username]/[slug]/debug.tsx
@@ -1,5 +1,4 @@
import { useRouter } from 'next/router'
-import { useGetArticleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
import { applyStoredTheme } from '../../../lib/themeUpdater'
import { useMemo } from 'react'
import {
@@ -7,6 +6,7 @@ import {
HStack,
SpanBox,
} from '../../../components/elements/LayoutPrimitives'
+import { useGetLibraryItemContent } from '../../../lib/networking/library_items/useLibraryItems'
type ArticleAttribute = {
name: string
@@ -15,11 +15,10 @@ type ArticleAttribute = {
export default function Debug(): JSX.Element {
const router = useRouter()
- const { articleData, articleFetchError, isLoading } = useGetArticleQuery({
- username: router.query.username as string,
- slug: router.query.slug as string,
- includeFriendsHighlights: false,
- })
+ const { data: article } = useGetLibraryItemContent(
+ router.query.username as string,
+ router.query.slug as string
+ )
applyStoredTheme()
@@ -30,13 +29,11 @@ export default function Debug(): JSX.Element {
// return sortedAttributes.sort((a, b) =>
// a.createdAt.localeCompare(b.createdAt)
// )
- if (!articleData?.article.article) {
+ if (!article) {
return []
}
const result: ArticleAttribute[] = []
- const article = articleData.article.article
-
result.push({ name: 'id', value: article.id })
result.push({ name: 'linkId', value: article.linkId })
@@ -171,7 +168,7 @@ export default function Debug(): JSX.Element {
// recommendations?: Recommendation[]
return result
- }, [articleData])
+ }, [article])
return (
<>
diff --git a/packages/web/pages/[username]/[slug]/index.tsx b/packages/web/pages/[username]/[slug]/index.tsx
index c48ed2859..2d36747f8 100644
--- a/packages/web/pages/[username]/[slug]/index.tsx
+++ b/packages/web/pages/[username]/[slug]/index.tsx
@@ -39,6 +39,14 @@ import {
useGetLibraryItemContent,
useUpdateItemReadStatus,
} from '../../../lib/networking/library_items/useLibraryItems'
+import {
+ CreateHighlightInput,
+ useCreateHighlight,
+ useDeleteHighlight,
+ useMergeHighlight,
+ useMergeHighlights,
+ useUpdateHighlight,
+} from '../../../lib/networking/highlights/useItemHighlights'
const PdfArticleContainerNoSSR = dynamic(
() => import('./../../../components/templates/article/PdfArticleContainer'),
@@ -59,13 +67,16 @@ export default function Reader(): JSX.Element {
const archiveItem = useArchiveItem()
const deleteItem = useDeleteItem()
const updateItemReadStatus = useUpdateItemReadStatus()
+ const createHighlight = useCreateHighlight()
+ const deleteHighlight = useDeleteHighlight()
+ const updateHighlight = useUpdateHighlight()
+ const mergeHighlight = useMergeHighlight()
const { data: libraryItem, error: articleFetchError } =
useGetLibraryItemContent(
router.query.username as string,
router.query.slug as string
)
- console.log('articleFetchError: ', articleFetchError)
useEffect(() => {
dispatchLabels({
type: 'RESET',
@@ -574,10 +585,59 @@ export default function Reader(): JSX.Element {
libraryItem.directionality ?? readerSettings.textDirection
}
articleMutations={{
- createHighlightMutation,
- deleteHighlightMutation,
- mergeHighlightMutation,
- updateHighlightMutation,
+ createHighlightMutation: async (
+ input: CreateHighlightInput
+ ) => {
+ try {
+ const result = await createHighlight.mutateAsync({
+ itemId: libraryItem.id,
+ input,
+ })
+ return result
+ } catch (err) {
+ console.log('error creating highlight', err)
+ return undefined
+ }
+ },
+ deleteHighlightMutation: async (
+ libraryItemId,
+ highlightId: string
+ ) => {
+ try {
+ await deleteHighlight.mutateAsync({
+ itemId: libraryItem.id,
+ highlightId,
+ })
+ return true
+ } catch (err) {
+ console.log('error deleting highlight', err)
+ return false
+ }
+ },
+ mergeHighlightMutation: async (input) => {
+ try {
+ const result = await mergeHighlight.mutateAsync({
+ itemId: libraryItem.id,
+ input,
+ })
+ return result?.highlight
+ } catch (err) {
+ console.log('error merging highlight', err)
+ return undefined
+ }
+ },
+ updateHighlightMutation: async (input) => {
+ try {
+ const result = await updateHighlight.mutateAsync({
+ itemId: libraryItem.id,
+ input,
+ })
+ return result?.id
+ } catch (err) {
+ console.log('error updating highlight', err)
+ return undefined
+ }
+ },
articleReadingProgressMutation: async (
input: ArticleReadingProgressMutationInput
) => {
diff --git a/packages/web/pages/app/[username]/link-request/[id].tsx b/packages/web/pages/app/[username]/link-request/[id].tsx
index a1d96f8ff..abd41ee02 100644
--- a/packages/web/pages/app/[username]/link-request/[id].tsx
+++ b/packages/web/pages/app/[username]/link-request/[id].tsx
@@ -4,7 +4,6 @@ import { Box } from '../../../../components/elements/LayoutPrimitives'
import { useGetArticleSavingStatus } from '../../../../lib/networking/queries/useGetArticleSavingStatus'
import { ErrorComponent } from '../../../../components/templates/SavingRequest'
import { useSWRConfig } from 'swr'
-import { cacheArticle } from '../../../../lib/networking/queries/useGetArticleQuery'
import { PrimaryLayout } from '../../../../components/templates/PrimaryLayout'
import { applyStoredTheme } from '../../../../lib/themeUpdater'
@@ -81,10 +80,6 @@ function PrimaryContent(props: PrimaryContentProps): JSX.Element {
)
}
- if (article) {
- cacheArticle(mutate, props.username, article)
- }
-
if (successRedirectPath) {
router.replace(
`/app${successRedirectPath}?isAppEmbedView=true&highlightBarDisabled=true`
diff --git a/packages/web/pages/highlights-old.tsx b/packages/web/pages/highlights-old.tsx
deleted file mode 100644
index abdc5a183..000000000
--- a/packages/web/pages/highlights-old.tsx
+++ /dev/null
@@ -1,352 +0,0 @@
-import {
- autoUpdate,
- offset,
- size,
- useFloating,
- useHover,
- useInteractions,
-} from '@floating-ui/react'
-import { NextRouter, useRouter } from 'next/router'
-import { useCallback, useMemo, useState } from 'react'
-import { Toaster } from 'react-hot-toast'
-import ReactMarkdown from 'react-markdown'
-import remarkGfm from 'remark-gfm'
-import { TrashIcon } from '../components/elements/icons/TrashIcon'
-import { LabelChip } from '../components/elements/LabelChip'
-import { Box, HStack, VStack } from '../components/elements/LayoutPrimitives'
-import { ConfirmationModal } from '../components/patterns/ConfirmationModal'
-import { HighlightHoverActions } from '../components/patterns/HighlightHoverActions'
-import { HighlightViewNote } from '../components/patterns/HighlightNotes'
-import { timeAgo } from '../components/patterns/LibraryCards/LibraryCardStyles'
-import { SetHighlightLabelsModalPresenter } from '../components/templates/article/SetLabelsModalPresenter'
-import { EmptyHighlights } from '../components/templates/homeFeed/EmptyHighlights'
-import { LibraryFilterMenu } from '../components/templates/navMenu/LibraryMenu'
-import { theme } from '../components/tokens/stitches.config'
-import { useApplyLocalTheme } from '../lib/hooks/useApplyLocalTheme'
-import { useFetchMore } from '../lib/hooks/useFetchMoreScroll'
-import { Highlight } from '../lib/networking/fragments/highlightFragment'
-import { deleteHighlightMutation } from '../lib/networking/mutations/deleteHighlightMutation'
-import { useGetHighlights } from '../lib/networking/queries/useGetHighlights'
-import {
- useGetViewerQuery,
- UserBasicData,
-} from '../lib/networking/queries/useGetViewerQuery'
-import { highlightColor } from '../lib/themeUpdater'
-import { showErrorToast, showSuccessToast } from '../lib/toastHelpers'
-
-const PAGE_SIZE = 10
-
-export default function HighlightsPage(): JSX.Element {
- const router = useRouter()
- const viewer = useGetViewerQuery()
- const [showFilterMenu, setShowFilterMenu] = useState(false)
- const [_, setShowAddLinkModal] = useState(false)
-
- useApplyLocalTheme()
-
- const { isLoading, setSize, size, data, mutate } = useGetHighlights({
- first: PAGE_SIZE,
- })
-
- const hasMore = useMemo(() => {
- if (!data) {
- return false
- }
- return data[data.length - 1].highlights.pageInfo.hasNextPage
- }, [data])
-
- const handleFetchMore = useCallback(() => {
- if (isLoading || !hasMore) {
- return
- }
- setSize(size + 1)
- }, [isLoading, hasMore, setSize, size])
-
- useFetchMore(handleFetchMore)
-
- const highlights = useMemo(() => {
- if (!data) {
- return []
- }
- return data.flatMap((res) => res.highlights.edges.map((edge) => edge.node))
- }, [data])
-
- if (!highlights.length) {
- return (
-
-
-
- )
- }
-
- return (
-
-
-
- {
- router?.push(`/home?q=${searchQuery}`)
- }}
- />
-
- {highlights.map((highlight) => {
- return (
- viewer.viewerData?.me && (
-
- )
- )
- })}
-
-
- )
-}
-
-type HighlightCardProps = {
- highlight: Highlight
- viewer: UserBasicData
- router: NextRouter
- mutate: () => void
-}
-
-type HighlightAnnotationProps = {
- highlight: Highlight
-}
-
-function HighlightAnnotation({
- highlight,
-}: HighlightAnnotationProps): JSX.Element {
- const [noteMode, setNoteMode] = useState<'edit' | 'preview'>('preview')
- const [annotation, setAnnotation] = useState(highlight.annotation)
-
- return (
- {
- setAnnotation(highlight.annotation)
- }}
- />
- )
-}
-
-function HighlightCard(props: HighlightCardProps): JSX.Element {
- const [isOpen, setIsOpen] = useState(false)
- const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
- useState(undefined)
- const [labelsTarget, setLabelsTarget] = useState(
- undefined
- )
-
- const viewInReader = useCallback(
- (highlightId: string) => {
- const router = props.router
- const viewer = props.viewer
- const item = props.highlight.libraryItem
-
- if (!router || !router.isReady || !viewer || !item) {
- showErrorToast('Error navigating to highlight')
- return
- }
-
- router.push(
- {
- pathname: '/[username]/[slug]',
- query: {
- username: viewer.profile.username,
- slug: item.slug,
- },
- hash: highlightId,
- },
- `${viewer.profile.username}/${item.slug}#${highlightId}`,
- {
- scroll: false,
- }
- )
- },
- [props.highlight.libraryItem, props.viewer, props.router]
- )
-
- const { refs, floatingStyles, context } = useFloating({
- open: isOpen,
- onOpenChange: setIsOpen,
- middleware: [
- offset({
- mainAxis: -25,
- }),
- size(),
- ],
- placement: 'top-end',
- whileElementsMounted: autoUpdate,
- })
-
- const hover = useHover(context)
-
- const { getReferenceProps, getFloatingProps } = useInteractions([hover])
-
- return (
-
-
-
-
-
-
- {timeAgo(props.highlight.updatedAt)}
-
- {props.highlight.quote && (
-
- {props.highlight.quote}
-
- )}
-
- {props.highlight.labels && (
-
- {props.highlight.labels.map((label) => {
- return (
-
- )
- })}
-
- )}
-
- {props.highlight.libraryItem?.title}
-
-
- {props.highlight.libraryItem?.author}
-
- {showConfirmDeleteHighlightId && (
- {
- ;(async () => {
- const highlightId = showConfirmDeleteHighlightId
- const success = await deleteHighlightMutation(
- props.highlight.libraryItem?.id || '',
- showConfirmDeleteHighlightId
- )
- props.mutate()
- if (success) {
- showSuccessToast('Highlight deleted.', {
- position: 'bottom-right',
- })
- const event = new CustomEvent('deleteHighlightbyId', {
- detail: highlightId,
- })
- document.dispatchEvent(event)
- } else {
- showErrorToast('Error deleting highlight', {
- position: 'bottom-right',
- })
- }
- })()
- setShowConfirmDeleteHighlightId(undefined)
- }}
- onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
- icon={
-
- }
- />
- )}
- {labelsTarget && (
- {
- // Don't actually need to do something here
- console.log('update highlight: ', highlight)
- }}
- onOpenChange={() => {
- props.mutate()
- setLabelsTarget(undefined)
- }}
- />
- )}
-
- )
-}