diff --git a/packages/web/components/templates/NavigationLayout.tsx b/packages/web/components/templates/NavigationLayout.tsx
index 0d843d3fa..fcf01b605 100644
--- a/packages/web/components/templates/NavigationLayout.tsx
+++ b/packages/web/components/templates/NavigationLayout.tsx
@@ -23,7 +23,6 @@ import 'allotment/dist/style.css'
import { LibrarySideBar } from './library/LibrarySideBar'
export type NavigationSection =
- | 'justread'
| 'home'
| 'library'
| 'subscriptions'
diff --git a/packages/web/components/templates/library/LibraryItemsContainer.tsx b/packages/web/components/templates/library/LibraryItemsContainer.tsx
index 26b806515..5e9c3f771 100644
--- a/packages/web/components/templates/library/LibraryItemsContainer.tsx
+++ b/packages/web/components/templates/library/LibraryItemsContainer.tsx
@@ -15,9 +15,9 @@ export function LibraryItemsContainer(): JSX.Element {
-
+ {/*
-
+ */}
)
}
diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx
index 7832c220e..23bc2dde4 100644
--- a/packages/web/components/templates/navMenu/LibraryMenu.tsx
+++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx
@@ -25,7 +25,7 @@ import { HomeIcon } from '../../elements/icons/HomeIcon'
import { LibraryIcon } from '../../elements/icons/LibraryIcon'
import { HighlightsIcon } from '../../elements/icons/HighlightsIcon'
import { CoverImage } from '../../elements/CoverImage'
-import { Shortcut } from '../../../pages/settings/shortcuts'
+import { Shortcut } from './NavigationMenu'
import { OutlinedLabelChip } from '../../elements/OutlinedLabelChip'
import { NewsletterIcon } from '../../elements/icons/NewsletterIcon'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
diff --git a/packages/web/components/templates/navMenu/NavigationMenu.tsx b/packages/web/components/templates/navMenu/NavigationMenu.tsx
index a3a3131c7..1154616df 100644
--- a/packages/web/components/templates/navMenu/NavigationMenu.tsx
+++ b/packages/web/components/templates/navMenu/NavigationMenu.tsx
@@ -241,8 +241,8 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
}
/>
{
const url = new URL(path, fetchEndpoint)
try {
- const response = await fetch(url, {
+ const response = await fetch(url.toString(), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -409,7 +409,7 @@ async function setShortcuts(
async function resetShortcuts(path: string): Promise {
const url = new URL(path, fetchEndpoint)
try {
- const response = await fetch(url, {
+ const response = await fetch(url.toString(), {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
diff --git a/packages/web/lib/networking/networkHelpers.ts b/packages/web/lib/networking/networkHelpers.ts
index 8c3cd65e7..2b810fcee 100644
--- a/packages/web/lib/networking/networkHelpers.ts
+++ b/packages/web/lib/networking/networkHelpers.ts
@@ -88,9 +88,10 @@ export function apiPoster(
}
export function makePublicGqlFetcher(
+ gql: string,
variables?: unknown
): (query: string) => Promise {
- return (query: string) => gqlFetcher(query, variables, false)
+ return (query: string) => gqlFetcher(gql, variables, false)
}
// Partially apply gql variables to the request
diff --git a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx
index a8699993d..325d57c39 100644
--- a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx
+++ b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx
@@ -256,7 +256,7 @@ export function useGetLibraryItemsQuery({
pageIndex === 0 ? undefined : previousResult.search.pageInfo.endCursor,
]
},
- (_query, _l, _s, _sq, cursor) => {
+ (_query: string, _l: string, _s: string, _sq: string, cursor: string) => {
return gqlFetcher(query, { ...variables, after: cursor }, true)
},
{ revalidateFirstPage: false }
diff --git a/packages/web/lib/networking/queries/useGetPublicArticleQuery.tsx b/packages/web/lib/networking/queries/useGetPublicArticleQuery.tsx
deleted file mode 100644
index 81a0e60e8..000000000
--- a/packages/web/lib/networking/queries/useGetPublicArticleQuery.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import { gql } from 'graphql-request'
-import useSWR from 'swr'
-import { makePublicGqlFetcher, RequestContext, ssrFetcher } from '../networkHelpers'
-import { Highlight } from '../fragments/highlightFragment'
-
-type PublicArticleQueryInput = {
- username: string
- slug: string
- selectedHighlightId?: string
-}
-
-export type PublicArticleQueryOutput = {
- publicArticle?: PublicArticleAttributes
- fetchError: unknown
- isLoading: boolean
- isValidating: boolean
-}
-
-type PublicArticleData = {
- sharedArticle: NestedPublicArticleData
-}
-
-type NestedPublicArticleData = {
- article: PublicArticleAttributes
-}
-
-export type PublicArticleAttributes = {
- id: string
- title: string
- slug: string
- url: string
- author?: string
- image?: string
- description?: string
- hasContent?: boolean
- highlights: Highlight[]
-}
-
-export const PublicArticleGQLFragment = gql`
- fragment PublicArticle on Article {
- id
- title
- slug
- url
- author
- image
- description
- savedByViewer
- postedByViewer
- hasContent
- highlights {
- id
- shortId
- quote
- prefix
- suffix
- patch
- annotation
- sharedAt
- user {
- id
- name
- profile {
- id
- username
- pictureUrl
- }
- }
- }
- }
-`
-
-const query = gql`
- query GetPublicArticle(
- $username: String!
- $slug: String!
- $selectedHighlightId: String
- ) {
- sharedArticle(
- username: $username
- slug: $slug
- selectedHighlightId: $selectedHighlightId
- ) {
- ... on SharedArticleSuccess {
- article {
- ...PublicArticle
- }
- }
-
- ... on SharedArticleError {
- errorCodes
- }
- }
- }
- ${PublicArticleGQLFragment}
-`
-
-export function useGetPublicArticleQuery({
- username,
- slug,
- selectedHighlightId,
-}: PublicArticleQueryInput): PublicArticleQueryOutput {
- const variables = {
- username,
- slug,
- selectedHighlightId,
- }
-
- const { data, error, isValidating } = useSWR(
- // Only make request if username is defined
- !!username ? [query, username, slug, selectedHighlightId] : null,
- makePublicGqlFetcher(variables)
- )
- const publicArticle = (data as PublicArticleData)?.sharedArticle?.article
-
- return {
- publicArticle,
- fetchError: error as unknown,
- isLoading: !error && !publicArticle,
- isValidating,
- }
-}
-
-export async function publicArticleQuery(
- context: RequestContext,
- input: PublicArticleQueryInput
-): Promise {
- const result = (await ssrFetcher(context, query, input, false)) as PublicArticleData
- if (result.sharedArticle.article) {
- return result.sharedArticle.article
- }
-
- return Promise.reject()
-}
diff --git a/packages/web/lib/networking/queries/useValidateUsernameQuery.tsx b/packages/web/lib/networking/queries/useValidateUsernameQuery.tsx
index 467d737fc..12fda73f7 100644
--- a/packages/web/lib/networking/queries/useValidateUsernameQuery.tsx
+++ b/packages/web/lib/networking/queries/useValidateUsernameQuery.tsx
@@ -24,7 +24,8 @@ export function useValidateUsernameQuery({
// Don't fetch if username is empty
const { data, error, isValidating } = useSWR(
username ? [query, username] : null,
- makePublicGqlFetcher({ username })
+ makePublicGqlFetcher(query, { username }),
+ {}
)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/packages/web/pages/highlights.tsx b/packages/web/pages/highlights-old.tsx
similarity index 100%
rename from packages/web/pages/highlights.tsx
rename to packages/web/pages/highlights-old.tsx
diff --git a/packages/web/pages/highlights/index.tsx b/packages/web/pages/highlights/index.tsx
new file mode 100644
index 000000000..abd6619f4
--- /dev/null
+++ b/packages/web/pages/highlights/index.tsx
@@ -0,0 +1,329 @@
+import { NavigationLayout } from '../../components/templates/NavigationLayout'
+import { Box, HStack, VStack } from '../../components/elements/LayoutPrimitives'
+import { useFetchMore } from '../../lib/hooks/useFetchMoreScroll'
+import { useCallback, useMemo, useState } from 'react'
+import { useGetHighlights } from '../../lib/networking/queries/useGetHighlights'
+import { Highlight } from '../../lib/networking/fragments/highlightFragment'
+import { NextRouter, useRouter } from 'next/router'
+import {
+ UserBasicData,
+ useGetViewerQuery,
+} from '../../lib/networking/queries/useGetViewerQuery'
+import { SetHighlightLabelsModalPresenter } from '../../components/templates/article/SetLabelsModalPresenter'
+import { TrashIcon } from '../../components/elements/icons/TrashIcon'
+import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
+import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
+import { deleteHighlightMutation } from '../../lib/networking/mutations/deleteHighlightMutation'
+import { LabelChip } from '../../components/elements/LabelChip'
+import ReactMarkdown from 'react-markdown'
+import remarkGfm from 'remark-gfm'
+import { timeAgo } from '../../components/patterns/LibraryCards/LibraryCardStyles'
+import { HighlightHoverActions } from '../../components/patterns/HighlightHoverActions'
+import {
+ autoUpdate,
+ offset,
+ size,
+ useFloating,
+ useHover,
+ useInteractions,
+} from '@floating-ui/react'
+import { highlightColor } from '../../lib/themeUpdater'
+
+import { HighlightViewNote } from '../../components/patterns/HighlightNotes'
+
+const PAGE_SIZE = 10
+
+export default function Highlights(): JSX.Element {
+ const router = useRouter()
+ const viewer = useGetViewerQuery()
+ const [showFilterMenu, setShowFilterMenu] = useState(false)
+ const [_, setShowAddLinkModal] = useState(false)
+
+ 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])
+
+ return (
+
+
+ {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)
+ }}
+ />
+ )}
+
+ )
+}
diff --git a/packages/web/pages/highlightsbak/index.tsx b/packages/web/pages/highlightsbak/index.tsx
deleted file mode 100644
index f9e38bd1d..000000000
--- a/packages/web/pages/highlightsbak/index.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { NavigationLayout } from '../../components/templates/NavigationLayout'
-import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
-import { HomeFeedContainer } from '../../components/templates/homeFeed/HomeFeedContainer'
-import { VStack } from '../../components/elements/LayoutPrimitives'
-
-export default function Highlights(): JSX.Element {
- return (
-
-
- Highlights will go here
-
-
- )
-}
diff --git a/packages/web/pages/home.tsx b/packages/web/pages/home-old.tsx
similarity index 100%
rename from packages/web/pages/home.tsx
rename to packages/web/pages/home-old.tsx
diff --git a/packages/web/pages/justread/debug.tsx b/packages/web/pages/home/debug.tsx
similarity index 100%
rename from packages/web/pages/justread/debug.tsx
rename to packages/web/pages/home/debug.tsx
diff --git a/packages/web/pages/justread/index.tsx b/packages/web/pages/home/index.tsx
similarity index 99%
rename from packages/web/pages/justread/index.tsx
rename to packages/web/pages/home/index.tsx
index a7edcb344..9a854acd3 100644
--- a/packages/web/pages/justread/index.tsx
+++ b/packages/web/pages/home/index.tsx
@@ -38,7 +38,7 @@ export default function Home(): JSX.Element {
useApplyLocalTheme()
return (
-
+
{
id: search.id,
name: search.name,
type: 'label',
+ section: 'library',
filter: search.filter,
}
props.dispatchList({
@@ -364,6 +366,7 @@ const AvailableItems = (props: ListProps): JSX.Element => {
id: label.id,
type: 'label',
label: label,
+ section: 'library',
name: label.name,
filter: `label:\"${escapeQuotes(label.name)}\"`,
}
@@ -408,6 +411,7 @@ const AvailableItems = (props: ListProps): JSX.Element => {
onClick={(event) => {
const item: Shortcut = {
id: subscription.id,
+ section: 'subscriptions',
name: subscription.name,
icon: subscription.icon,
type: