Improve highlight scrolling from notebooks
This commit is contained in:
@ -18,7 +18,6 @@ type HighlightViewProps = {
|
||||
highlight: Highlight
|
||||
author?: string
|
||||
title?: string
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
updateHighlight: (highlight: Highlight) => void
|
||||
}
|
||||
|
||||
@ -67,13 +66,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
|
||||
paddingLeft: '15px',
|
||||
}}
|
||||
>
|
||||
<StyledQuote
|
||||
onClick={() => {
|
||||
if (props.scrollToHighlight) {
|
||||
props.scrollToHighlight(props.highlight.id)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledQuote>
|
||||
<SpanBox
|
||||
css={{
|
||||
'> *': {
|
||||
|
||||
@ -18,14 +18,15 @@ 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'
|
||||
|
||||
type ArticleContainerProps = {
|
||||
viewer: UserBasicData
|
||||
article: ArticleAttributes
|
||||
labels: Label[]
|
||||
articleMutations: ArticleMutations
|
||||
isAppleAppEmbed: boolean
|
||||
highlightBarDisabled: boolean
|
||||
highlightsBaseURL: string
|
||||
margin?: number
|
||||
fontSize?: number
|
||||
fontFamily?: string
|
||||
@ -107,15 +108,12 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
||||
const [showReportIssuesModal, setShowReportIssuesModal] = useState(false)
|
||||
const [fontSize, setFontSize] = useState(props.fontSize ?? 20)
|
||||
// iOS app embed can overide the original margin and line height
|
||||
const [maxWidthPercentageOverride, setMaxWidthPercentageOverride] = useState<
|
||||
number | null
|
||||
>(null)
|
||||
const [lineHeightOverride, setLineHeightOverride] = useState<number | null>(
|
||||
null
|
||||
)
|
||||
const [fontFamilyOverride, setFontFamilyOverride] = useState<string | null>(
|
||||
null
|
||||
)
|
||||
const [maxWidthPercentageOverride, setMaxWidthPercentageOverride] =
|
||||
useState<number | null>(null)
|
||||
const [lineHeightOverride, setLineHeightOverride] =
|
||||
useState<number | null>(null)
|
||||
const [fontFamilyOverride, setFontFamilyOverride] =
|
||||
useState<string | null>(null)
|
||||
const [highContrastText, setHighContrastText] = useState(
|
||||
props.highContrastText ?? false
|
||||
)
|
||||
@ -388,13 +386,14 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
||||
<Box css={{ height: '100px' }} />
|
||||
</Box>
|
||||
<HighlightsLayer
|
||||
viewer={props.viewer}
|
||||
item={props.article}
|
||||
scrollToHighlight={highlightHref}
|
||||
highlights={props.article.highlights}
|
||||
articleTitle={props.article.title}
|
||||
articleAuthor={props.article.author ?? ''}
|
||||
articleId={props.article.id}
|
||||
isAppleAppEmbed={props.isAppleAppEmbed}
|
||||
highlightsBaseURL={props.highlightsBaseURL}
|
||||
highlightBarDisabled={props.highlightBarDisabled}
|
||||
showHighlightsModal={props.showHighlightsModal}
|
||||
setShowHighlightsModal={props.setShowHighlightsModal}
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
import { useState } from 'react'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import {
|
||||
LibraryItem,
|
||||
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'
|
||||
import { HighlightsMenu } from '../homeFeed/HighlightItem'
|
||||
|
||||
type HighlightViewItemProps = {
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlight: Highlight
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
|
||||
viewInReader: (highlightId: string) => void
|
||||
|
||||
deleteHighlightAction: () => void
|
||||
updateHighlight: (highlight: Highlight) => void
|
||||
|
||||
@ -26,7 +36,6 @@ export function HighlightViewItem(props: HighlightViewItemProps): JSX.Element {
|
||||
<VStack css={{ width: '100%' }}>
|
||||
<HighlightView
|
||||
highlight={props.highlight}
|
||||
scrollToHighlight={props.scrollToHighlight}
|
||||
updateHighlight={props.updateHighlight}
|
||||
/>
|
||||
<SpanBox css={{ mb: '15px' }} />
|
||||
@ -42,7 +51,10 @@ export function HighlightViewItem(props: HighlightViewItemProps): JSX.Element {
|
||||
}}
|
||||
>
|
||||
<HighlightsMenu
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
highlight={props.highlight}
|
||||
viewInReader={props.viewInReader}
|
||||
setLabelsTarget={props.setSetLabelsTarget}
|
||||
setShowConfirmDeleteHighlightId={
|
||||
props.setShowConfirmDeleteHighlightId
|
||||
|
||||
@ -26,17 +26,24 @@ 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 { useRouter } from 'next/router'
|
||||
|
||||
type HighlightsLayerProps = {
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlights: Highlight[]
|
||||
|
||||
articleId: string
|
||||
articleTitle: string
|
||||
articleAuthor: string
|
||||
isAppleAppEmbed: boolean
|
||||
highlightBarDisabled: boolean
|
||||
showHighlightsModal: boolean
|
||||
highlightsBaseURL: string
|
||||
scrollToHighlight: MutableRefObject<string | null>
|
||||
|
||||
setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
|
||||
articleMutations: ArticleMutations
|
||||
}
|
||||
@ -59,6 +66,7 @@ interface SpeakingSectionEvent extends Event {
|
||||
}
|
||||
|
||||
export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
const router = useRouter()
|
||||
const [highlights, setHighlights] = useState(props.highlights)
|
||||
const [highlightModalAction, setHighlightModalAction] =
|
||||
useState<HighlightActionProps>({ highlightModalAction: 'none' })
|
||||
@ -68,15 +76,13 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
>([])
|
||||
const focusedHighlightMousePos = useRef({ pageX: 0, pageY: 0 })
|
||||
|
||||
const [focusedHighlight, setFocusedHighlight] = useState<
|
||||
Highlight | undefined
|
||||
>(undefined)
|
||||
const [focusedHighlight, setFocusedHighlight] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
|
||||
const [selectionData, setSelectionData] = useSelection(highlightLocations)
|
||||
|
||||
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [labelsTarget, setLabelsTarget] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
|
||||
const canShareNative = useCanShareNative()
|
||||
|
||||
@ -141,7 +147,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
`[omnivore-highlight-id="${props.scrollToHighlight.current}"]`
|
||||
)
|
||||
if (anchorElement) {
|
||||
anchorElement.scrollIntoView({ behavior: 'auto' })
|
||||
anchorElement.scrollIntoView({
|
||||
block: 'center',
|
||||
behavior: 'auto',
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [highlights, setHighlightLocations, props.scrollToHighlight])
|
||||
@ -181,23 +190,20 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
[highlights, highlightLocations]
|
||||
)
|
||||
|
||||
const handleNativeShare = useCallback(
|
||||
(highlightID: string) => {
|
||||
navigator
|
||||
?.share({
|
||||
title: props.articleTitle,
|
||||
url: `${props.highlightsBaseURL}/${highlightID}`,
|
||||
})
|
||||
.then(() => {
|
||||
setFocusedHighlight(undefined)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
setFocusedHighlight(undefined)
|
||||
})
|
||||
},
|
||||
[props.articleTitle, props.highlightsBaseURL]
|
||||
)
|
||||
// const handleNativeShare = useCallback((highlightID: string) => {
|
||||
// // navigator
|
||||
// // ?.share({
|
||||
// // title: props.articleTitle,
|
||||
// // url: `${props.highlightsBaseURL}/${highlightID}`,
|
||||
// // })
|
||||
// // .then(() => {
|
||||
// // setFocusedHighlight(undefined)
|
||||
// // })
|
||||
// // .catch((error) => {
|
||||
// // console.log(error)
|
||||
// // setFocusedHighlight(undefined)
|
||||
// // })
|
||||
// }, [])
|
||||
|
||||
const openNoteModal = useCallback(
|
||||
(inputs: HighlightActionProps) => {
|
||||
@ -282,7 +288,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
}
|
||||
},
|
||||
[
|
||||
handleNativeShare,
|
||||
highlights,
|
||||
openNoteModal,
|
||||
props.articleId,
|
||||
@ -408,37 +413,37 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'share':
|
||||
if (props.isAppleAppEmbed) {
|
||||
window?.webkit?.messageHandlers.highlightAction?.postMessage({
|
||||
actionID: 'share',
|
||||
highlightID: focusedHighlight?.id,
|
||||
})
|
||||
}
|
||||
// case 'share':
|
||||
// if (props.isAppleAppEmbed) {
|
||||
// window?.webkit?.messageHandlers.highlightAction?.postMessage({
|
||||
// actionID: 'share',
|
||||
// highlightID: focusedHighlight?.id,
|
||||
// })
|
||||
// }
|
||||
|
||||
window?.AndroidWebKitMessenger?.handleIdentifiableMessage(
|
||||
'shareHighlight',
|
||||
JSON.stringify({
|
||||
highlightID: focusedHighlight?.id,
|
||||
})
|
||||
)
|
||||
// window?.AndroidWebKitMessenger?.handleIdentifiableMessage(
|
||||
// 'shareHighlight',
|
||||
// JSON.stringify({
|
||||
// highlightID: focusedHighlight?.id,
|
||||
// })
|
||||
// )
|
||||
|
||||
if (focusedHighlight) {
|
||||
if (canShareNative) {
|
||||
handleNativeShare(focusedHighlight.shortId)
|
||||
} else {
|
||||
setHighlightModalAction({
|
||||
highlight: focusedHighlight,
|
||||
highlightModalAction: 'share',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
await createHighlightCallback('share')
|
||||
}
|
||||
break
|
||||
case 'unshare':
|
||||
console.log('unshare')
|
||||
break // TODO: implement -- need to show confirmation dialog
|
||||
// if (focusedHighlight) {
|
||||
// if (canShareNative) {
|
||||
// handleNativeShare(focusedHighlight.shortId)
|
||||
// } else {
|
||||
// setHighlightModalAction({
|
||||
// highlight: focusedHighlight,
|
||||
// highlightModalAction: 'share',
|
||||
// })
|
||||
// }
|
||||
// } else {
|
||||
// await createHighlightCallback('share')
|
||||
// }
|
||||
// break
|
||||
// case 'unshare':
|
||||
// console.log('unshare')
|
||||
// break // TODO: implement -- need to show confirmation dialog
|
||||
case 'setHighlightLabels':
|
||||
if (props.isAppleAppEmbed) {
|
||||
window?.webkit?.messageHandlers.highlightAction?.postMessage({
|
||||
@ -454,7 +459,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
[
|
||||
createHighlightCallback,
|
||||
focusedHighlight,
|
||||
handleNativeShare,
|
||||
openNoteModal,
|
||||
props.highlightBarDisabled,
|
||||
props.isAppleAppEmbed,
|
||||
@ -667,9 +671,28 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
if (props.showHighlightsModal) {
|
||||
return (
|
||||
<NotebookModal
|
||||
viewer={props.viewer}
|
||||
item={props.item}
|
||||
highlights={highlights}
|
||||
pageId={props.articleId}
|
||||
onClose={handleCloseNotebook}
|
||||
viewHighlightInReader={(highlightId) => {
|
||||
// The timeout here is a bit of a hack to work around rerendering
|
||||
setTimeout(() => {
|
||||
const target = document.querySelector(
|
||||
`[omnivore-highlight-id="${highlightId}"]`
|
||||
)
|
||||
target?.scrollIntoView({
|
||||
block: 'center',
|
||||
behavior: 'auto',
|
||||
})
|
||||
}, 1)
|
||||
history.replaceState(
|
||||
undefined,
|
||||
window.location.href,
|
||||
`#${highlightId}`
|
||||
)
|
||||
props.setShowHighlightsModal(false)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -20,14 +20,22 @@ 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'
|
||||
|
||||
type NotebookProps = {
|
||||
pageId: string
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlights: Highlight[]
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
|
||||
sizeMode: 'normal' | 'maximized'
|
||||
|
||||
viewInReader: (highlightId: string) => void
|
||||
|
||||
onAnnotationsChanged?: (
|
||||
highlights: Highlight[],
|
||||
deletedAnnotations: Highlight[]
|
||||
@ -242,7 +250,7 @@ export function Notebook(props: NotebookProps): JSX.Element {
|
||||
id: noteId,
|
||||
shortId: nanoid(8),
|
||||
type: 'NOTE',
|
||||
articleId: props.pageId,
|
||||
articleId: props.item.id,
|
||||
annotation: text,
|
||||
})
|
||||
console.log('success creating annotation note: ', success)
|
||||
@ -277,7 +285,7 @@ export function Notebook(props: NotebookProps): JSX.Element {
|
||||
return
|
||||
}
|
||||
},
|
||||
[annotations, props.pageId]
|
||||
[annotations, props.item]
|
||||
)
|
||||
return (
|
||||
<VStack
|
||||
@ -304,8 +312,10 @@ export function Notebook(props: NotebookProps): JSX.Element {
|
||||
{sortedHighlights.map((highlight) => (
|
||||
<HighlightViewItem
|
||||
key={highlight.id}
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
highlight={highlight}
|
||||
scrollToHighlight={props.scrollToHighlight}
|
||||
viewInReader={props.viewInReader}
|
||||
setSetLabelsTarget={setLabelsTarget}
|
||||
setShowConfirmDeleteHighlightId={setShowConfirmDeleteHighlightId}
|
||||
deleteHighlightAction={() => {
|
||||
|
||||
@ -17,11 +17,16 @@ import { MenuTrigger } from '../../elements/MenuTrigger'
|
||||
import { highlightsAsMarkdown } from '../homeFeed/HighlightItem'
|
||||
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'
|
||||
|
||||
type NotebookModalProps = {
|
||||
pageId: string
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlights: Highlight[]
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
|
||||
viewHighlightInReader: (arg: string) => void
|
||||
onClose: (highlights: Highlight[], deletedAnnotations: Highlight[]) => void
|
||||
}
|
||||
|
||||
@ -31,16 +36,6 @@ export const getHighlightLocation = (patch: string): number | undefined => {
|
||||
return patches[0].start1 || undefined
|
||||
}
|
||||
|
||||
type AnnotationInfo = {
|
||||
loaded: boolean
|
||||
|
||||
note: Highlight | undefined
|
||||
noteId: string
|
||||
|
||||
allAnnotations: Highlight[]
|
||||
deletedAnnotations: Highlight[]
|
||||
}
|
||||
|
||||
export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
||||
const [sizeMode, setSizeMode] = useState<'normal' | 'maximized'>('normal')
|
||||
const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false)
|
||||
@ -73,6 +68,14 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
||||
})()
|
||||
}, [allAnnotations])
|
||||
|
||||
const viewInReader = useCallback(
|
||||
(highlightId) => {
|
||||
props.viewHighlightInReader(highlightId)
|
||||
handleClose()
|
||||
},
|
||||
[props, handleClose]
|
||||
)
|
||||
|
||||
return (
|
||||
<ModalRoot defaultOpen onOpenChange={handleClose}>
|
||||
<ModalOverlay />
|
||||
@ -141,6 +144,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
||||
<Notebook
|
||||
{...props}
|
||||
sizeMode={sizeMode}
|
||||
viewInReader={viewInReader}
|
||||
onAnnotationsChanged={handleAnnotationsChange}
|
||||
/>
|
||||
</ModalContent>
|
||||
|
||||
@ -2,7 +2,7 @@ import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticle
|
||||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { isDarkTheme } from '../../../lib/themeUpdater'
|
||||
import PSPDFKit from 'pspdfkit'
|
||||
import { Instance, HighlightAnnotation, List, Annotation, Rect } from 'pspdfkit'
|
||||
@ -12,15 +12,15 @@ import { deleteHighlightMutation } from '../../../lib/networking/mutations/delet
|
||||
import { articleReadingProgressMutation } from '../../../lib/networking/mutations/articleReadingProgressMutation'
|
||||
import { mergeHighlightMutation } from '../../../lib/networking/mutations/mergeHighlightMutation'
|
||||
import { useCanShareNative } from '../../../lib/hooks/useCanShareNative'
|
||||
import { webBaseURL } from '../../../lib/appConfig'
|
||||
import { pspdfKitKey } from '../../../lib/appConfig'
|
||||
import { NotebookModal } from './NotebookModal'
|
||||
import { HighlightNoteModal } from './HighlightNoteModal'
|
||||
import { showErrorToast } from '../../../lib/toastHelpers'
|
||||
import { HEADER_HEIGHT, MOBILE_HEADER_HEIGHT } from '../homeFeed/HeaderSpacer'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
|
||||
export type PdfArticleContainerProps = {
|
||||
viewerUsername: string
|
||||
viewer: UserBasicData
|
||||
article: ArticleAttributes
|
||||
showHighlightsModal: boolean
|
||||
setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
|
||||
@ -30,43 +30,41 @@ export default function PdfArticleContainer(
|
||||
props: PdfArticleContainerProps
|
||||
): JSX.Element {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
const [shareTarget, setShareTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [shareTarget, setShareTarget] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
const [notebookKey, setNotebookKey] = useState<string>(uuidv4())
|
||||
const [noteTarget, setNoteTarget] = useState<Highlight | undefined>(undefined)
|
||||
const [noteTargetPageIndex, setNoteTargetPageIndex] = useState<
|
||||
number | undefined
|
||||
>(undefined)
|
||||
const [noteTargetPageIndex, setNoteTargetPageIndex] =
|
||||
useState<number | undefined>(undefined)
|
||||
const highlightsRef = useRef<Highlight[]>([])
|
||||
const canShareNative = useCanShareNative()
|
||||
|
||||
const getHighlightURL = useCallback(
|
||||
(highlightID: string): string =>
|
||||
`${webBaseURL}/${props.viewerUsername}/${props.article.slug}/highlights/${highlightID}`,
|
||||
[props.article.slug, props.viewerUsername]
|
||||
)
|
||||
// const getHighlightURL = useCallback(
|
||||
// (highlightID: string): string =>
|
||||
// `${webBaseURL}/${props.viewerUsername}/${props.article.slug}/highlights/${highlightID}`,
|
||||
// [props.article.slug, props.viewerUsername]
|
||||
// )
|
||||
|
||||
const nativeShare = useCallback(
|
||||
async (highlightID: string, title: string) => {
|
||||
await navigator?.share({
|
||||
title: title,
|
||||
url: getHighlightURL(highlightID),
|
||||
})
|
||||
},
|
||||
[getHighlightURL]
|
||||
)
|
||||
// const nativeShare = useCallback(
|
||||
// async (highlightID: string, title: string) => {
|
||||
// await navigator?.share({
|
||||
// title: title,
|
||||
// url: getHighlightURL(highlightID),
|
||||
// })
|
||||
// },
|
||||
// [getHighlightURL]
|
||||
// )
|
||||
|
||||
const handleOpenShare = useCallback(
|
||||
(highlight: Highlight) => {
|
||||
if (canShareNative) {
|
||||
nativeShare(highlight.shortId, props.article.title)
|
||||
} else {
|
||||
setShareTarget(highlight)
|
||||
}
|
||||
},
|
||||
[nativeShare, canShareNative, props.article.title]
|
||||
)
|
||||
// const handleOpenShare = useCallback(
|
||||
// (highlight: Highlight) => {
|
||||
// if (canShareNative) {
|
||||
// nativeShare(highlight.shortId, props.article.title)
|
||||
// } else {
|
||||
// setShareTarget(highlight)
|
||||
// }
|
||||
// },
|
||||
// [nativeShare, canShareNative, props.article.title]
|
||||
// )
|
||||
|
||||
const annotationOmnivoreId = (annotation: Annotation): string | undefined => {
|
||||
if (
|
||||
@ -178,23 +176,23 @@ export default function PdfArticleContainer(
|
||||
instance.setSelectedAnnotation(null)
|
||||
},
|
||||
}
|
||||
const share = {
|
||||
type: 'custom' as const,
|
||||
title: 'Share',
|
||||
id: 'tooltip-share-annotation',
|
||||
className: 'TooltipItem-Share',
|
||||
onPress: () => {
|
||||
if (
|
||||
annotation.customData &&
|
||||
annotation.customData.omnivoreHighlight &&
|
||||
(annotation.customData.omnivoreHighlight as Highlight).shortId
|
||||
) {
|
||||
const data = annotation.customData.omnivoreHighlight as Highlight
|
||||
handleOpenShare(data)
|
||||
}
|
||||
instance.setSelectedAnnotation(null)
|
||||
},
|
||||
}
|
||||
// const share = {
|
||||
// type: 'custom' as const,
|
||||
// title: 'Share',
|
||||
// id: 'tooltip-share-annotation',
|
||||
// className: 'TooltipItem-Share',
|
||||
// onPress: () => {
|
||||
// if (
|
||||
// annotation.customData &&
|
||||
// annotation.customData.omnivoreHighlight &&
|
||||
// (annotation.customData.omnivoreHighlight as Highlight).shortId
|
||||
// ) {
|
||||
// const data = annotation.customData.omnivoreHighlight as Highlight
|
||||
// handleOpenShare(data)
|
||||
// }
|
||||
// instance.setSelectedAnnotation(null)
|
||||
// },
|
||||
// }
|
||||
return [copy, note, remove]
|
||||
}
|
||||
|
||||
@ -424,7 +422,6 @@ export default function PdfArticleContainer(
|
||||
|
||||
document.addEventListener('deleteHighlightbyId', async (event) => {
|
||||
const annotationId = (event as CustomEvent).detail as string
|
||||
console.log(' DELETING ANNOTATION BY ID: ', annotationId)
|
||||
for (let pageIdx = 0; pageIdx < instance.totalPageCount; pageIdx++) {
|
||||
const annotations = await instance.getAnnotations(pageIdx)
|
||||
for (let annIdx = 0; annIdx < annotations.size; annIdx++) {
|
||||
@ -433,7 +430,6 @@ export default function PdfArticleContainer(
|
||||
continue
|
||||
}
|
||||
const storedId = annotationOmnivoreId(annotation)
|
||||
console.log(' --- storedId:', storedId)
|
||||
if (storedId == annotationId) {
|
||||
await instance.delete(annotation)
|
||||
await deleteHighlightMutation(annotationId)
|
||||
@ -495,7 +491,8 @@ export default function PdfArticleContainer(
|
||||
{props.showHighlightsModal && (
|
||||
<NotebookModal
|
||||
key={notebookKey}
|
||||
pageId={props.article.id}
|
||||
viewer={props.viewer}
|
||||
item={props.article}
|
||||
highlights={highlightsRef.current}
|
||||
onClose={(updatedHighlights, deletedAnnotations) => {
|
||||
console.log(
|
||||
@ -511,6 +508,10 @@ export default function PdfArticleContainer(
|
||||
})
|
||||
props.setShowHighlightsModal(false)
|
||||
}}
|
||||
viewHighlightInReader={(highlightId) => {
|
||||
// TODO: scroll to highlight in PDF
|
||||
props.setShowHighlightsModal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@ -1,19 +1,50 @@
|
||||
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'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownOption,
|
||||
DropdownSeparator,
|
||||
} from '../../elements/DropdownElements'
|
||||
import { Box, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
|
||||
type HighlightsMenuProps = {
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlight: Highlight
|
||||
|
||||
viewInReader: (highlightId: string) => void
|
||||
|
||||
setLabelsTarget: (target: Highlight) => void
|
||||
setShowConfirmDeleteHighlightId: (set: string) => void
|
||||
}
|
||||
|
||||
const StyledLinkItem = styled('a', {
|
||||
display: 'flex',
|
||||
fontSize: '14px',
|
||||
fontWeight: '400',
|
||||
py: '10px',
|
||||
px: '15px',
|
||||
borderRadius: 3,
|
||||
cursor: 'pointer',
|
||||
color: '$utilityTextDefault',
|
||||
textDecoration: 'none',
|
||||
|
||||
'&:hover': {
|
||||
outline: 'none',
|
||||
backgroundColor: '$grayBgHover',
|
||||
},
|
||||
})
|
||||
|
||||
export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
||||
const copyHighlight = useCallback(() => {
|
||||
const quote = props.highlight.quote
|
||||
@ -69,6 +100,28 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
||||
}}
|
||||
title="Delete"
|
||||
/>
|
||||
<DropdownSeparator />
|
||||
<Link
|
||||
href={`/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`}
|
||||
>
|
||||
<StyledLinkItem
|
||||
onClick={(event) => {
|
||||
console.log('event.ctrlKey: ', event.ctrlKey, event.metaKey)
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
window.open(
|
||||
`/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`,
|
||||
'_blank'
|
||||
)
|
||||
return
|
||||
}
|
||||
props.viewInReader(props.highlight.id)
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
>
|
||||
View In Reader
|
||||
</StyledLinkItem>
|
||||
</Link>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { HighlighterCircle } from 'phosphor-react'
|
||||
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { useCallback, useEffect, useReducer, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import {
|
||||
@ -361,6 +362,8 @@ type HighlightListProps = {
|
||||
}
|
||||
|
||||
function HighlightList(props: HighlightListProps): JSX.Element {
|
||||
const router = useRouter()
|
||||
|
||||
const exportHighlights = useCallback(() => {
|
||||
;(async () => {
|
||||
if (!props.item.node.highlights) {
|
||||
@ -373,6 +376,36 @@ function HighlightList(props: HighlightListProps): JSX.Element {
|
||||
})()
|
||||
}, [props.item.node.highlights])
|
||||
|
||||
const viewInReader = useCallback(
|
||||
(highlightId) => {
|
||||
if (!router || !router.isReady || !props.viewer) {
|
||||
showErrorToast('Error navigating to highlight')
|
||||
return
|
||||
}
|
||||
console.log(
|
||||
'pushing user: ',
|
||||
props.viewer,
|
||||
'slug: ',
|
||||
props.item.node.slug
|
||||
)
|
||||
router.push(
|
||||
{
|
||||
pathname: '/[username]/[slug]',
|
||||
query: {
|
||||
username: props.viewer.profile.username,
|
||||
slug: props.item.node.slug,
|
||||
},
|
||||
hash: highlightId,
|
||||
},
|
||||
`${props.viewer.profile.username}/${props.item.node.slug}#${highlightId}`,
|
||||
{
|
||||
scroll: false,
|
||||
}
|
||||
)
|
||||
},
|
||||
[router, props]
|
||||
)
|
||||
|
||||
return (
|
||||
<VStack
|
||||
css={{
|
||||
@ -418,11 +451,15 @@ function HighlightList(props: HighlightListProps): JSX.Element {
|
||||
</Dropdown>
|
||||
</HStack>
|
||||
<HStack css={{ width: '100%', height: '100%' }}>
|
||||
<Notebook
|
||||
sizeMode="normal"
|
||||
pageId={props.item.node.id}
|
||||
highlights={props.item.node.highlights ?? []}
|
||||
/>
|
||||
{props.viewer && (
|
||||
<Notebook
|
||||
sizeMode="normal"
|
||||
viewer={props.viewer}
|
||||
item={props.item.node}
|
||||
highlights={props.item.node.highlights ?? []}
|
||||
viewInReader={viewInReader}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
|
||||
@ -4,7 +4,6 @@ type AppEnvironment = 'prod' | 'dev' | 'demo' | 'local'
|
||||
type BaseURLs = {
|
||||
webBaseURL: string
|
||||
serverBaseURL: string
|
||||
highlightsBaseURL: string
|
||||
}
|
||||
|
||||
type BaseURLRecords = Record<AppEnvironment, BaseURLs>
|
||||
@ -14,22 +13,18 @@ const baseURLRecords: BaseURLRecords = {
|
||||
prod: {
|
||||
webBaseURL: process.env.NEXT_PUBLIC_BASE_URL ?? '',
|
||||
serverBaseURL: process.env.NEXT_PUBLIC_SERVER_BASE_URL ?? '',
|
||||
highlightsBaseURL: process.env.NEXT_PUBLIC_HIGHLIGHTS_BASE_URL ?? '',
|
||||
},
|
||||
dev: {
|
||||
webBaseURL: process.env.NEXT_PUBLIC_DEV_BASE_URL ?? '',
|
||||
serverBaseURL: process.env.NEXT_PUBLIC_DEV_SERVER_BASE_URL ?? '',
|
||||
highlightsBaseURL: process.env.NEXT_PUBLIC_DEV_HIGHLIGHTS_BASE_URL ?? '',
|
||||
},
|
||||
demo: {
|
||||
webBaseURL: process.env.NEXT_PUBLIC_DEMO_BASE_URL ?? '',
|
||||
serverBaseURL: process.env.NEXT_PUBLIC_DEMO_SERVER_BASE_URL ?? '',
|
||||
highlightsBaseURL: process.env.NEXT_PUBLIC_DEMO_HIGHLIGHTS_BASE_URL ?? '',
|
||||
},
|
||||
local: {
|
||||
webBaseURL: process.env.NEXT_PUBLIC_LOCAL_BASE_URL ?? '',
|
||||
serverBaseURL: process.env.NEXT_PUBLIC_LOCAL_SERVER_BASE_URL ?? '',
|
||||
highlightsBaseURL: process.env.NEXT_PUBLIC_LOCAL_HIGHLIGHTS_BASE_URL ?? '',
|
||||
},
|
||||
}
|
||||
|
||||
@ -43,16 +38,6 @@ function serverBaseURL(env: AppEnvironment): string {
|
||||
return value
|
||||
}
|
||||
|
||||
function highlightsURL(env: AppEnvironment): string {
|
||||
const value = baseURLRecords[appEnv].highlightsBaseURL
|
||||
if (value.length == 0) {
|
||||
throw new Error(
|
||||
`Couldn't find environment variable for highlights base url in ${env} environment`
|
||||
)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function webURL(env: AppEnvironment): string {
|
||||
const value = baseURLRecords[appEnv].webBaseURL
|
||||
if (value.length == 0) {
|
||||
@ -96,6 +81,4 @@ export const gqlEndpoint = `${serverBaseURL(appEnv)}/api/graphql`
|
||||
|
||||
export const fetchEndpoint = `${serverBaseURL(appEnv)}/api`
|
||||
|
||||
export const highlightsBaseURL = highlightsURL(appEnv)
|
||||
|
||||
export const webBaseURL = webURL(appEnv)
|
||||
|
||||
@ -11,6 +11,12 @@ import { Label } from './../fragments/labelFragment'
|
||||
import { showErrorToast, showSuccessToast } from '../../toastHelpers'
|
||||
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
|
||||
|
||||
export interface ReadableItem {
|
||||
id: string
|
||||
title: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
export type LibraryItemsQueryInput = {
|
||||
limit: number
|
||||
sortDescending: boolean
|
||||
|
||||
@ -346,7 +346,7 @@ export default function Home(): JSX.Element {
|
||||
article={article}
|
||||
showHighlightsModal={showHighlightsModal}
|
||||
setShowHighlightsModal={setShowHighlightsModal}
|
||||
viewerUsername={viewerData.me?.profile?.username}
|
||||
viewer={viewerData.me}
|
||||
/>
|
||||
) : (
|
||||
<VStack
|
||||
@ -362,10 +362,10 @@ export default function Home(): JSX.Element {
|
||||
>
|
||||
{article && viewerData?.me ? (
|
||||
<ArticleContainer
|
||||
viewer={viewerData.me}
|
||||
article={article}
|
||||
isAppleAppEmbed={false}
|
||||
highlightBarDisabled={false}
|
||||
highlightsBaseURL={`${webBaseURL}/${viewerData.me?.profile?.username}/${slug}/highlights`}
|
||||
fontSize={readerSettings.fontSize}
|
||||
margin={readerSettings.marginWidth}
|
||||
lineHeight={readerSettings.lineHeight}
|
||||
|
||||
@ -13,6 +13,7 @@ import { mergeHighlightMutation } from '../../../../lib/networking/mutations/mer
|
||||
import { updateHighlightMutation } from '../../../../lib/networking/mutations/updateHighlightMutation'
|
||||
import { articleReadingProgressMutation } from '../../../../lib/networking/mutations/articleReadingProgressMutation'
|
||||
import Script from 'next/script'
|
||||
import { useGetViewerQuery } from '../../../../lib/networking/queries/useGetViewerQuery'
|
||||
|
||||
type AppArticleEmbedContentProps = {
|
||||
slug: string
|
||||
@ -28,9 +29,8 @@ export default function AppArticleEmbed(): JSX.Element {
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const [contentProps, setContentProps] = useState<
|
||||
AppArticleEmbedContentProps | undefined
|
||||
>(undefined)
|
||||
const [contentProps, setContentProps] =
|
||||
useState<AppArticleEmbedContentProps | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
@ -62,7 +62,7 @@ export default function AppArticleEmbed(): JSX.Element {
|
||||
function AppArticleEmbedContent(
|
||||
props: AppArticleEmbedContentProps
|
||||
): JSX.Element {
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null)
|
||||
const { viewerData } = useGetViewerQuery()
|
||||
const [showHighlightsModal, setShowHighlightsModal] = useState(false)
|
||||
|
||||
const { articleData } = useGetArticleQuery({
|
||||
@ -71,7 +71,7 @@ function AppArticleEmbedContent(
|
||||
includeFriendsHighlights: false,
|
||||
})
|
||||
|
||||
if (articleData) {
|
||||
if (articleData && viewerData?.me) {
|
||||
return (
|
||||
<Box>
|
||||
<Script async src="/static/scripts/mathJaxConfiguration.js" />
|
||||
@ -86,10 +86,10 @@ function AppArticleEmbedContent(
|
||||
className="disable-webkit-callout"
|
||||
>
|
||||
<ArticleContainer
|
||||
viewer={viewerData.me}
|
||||
article={articleData.article.article}
|
||||
isAppleAppEmbed={true}
|
||||
highlightBarDisabled={props.highlightBarDisabled}
|
||||
highlightsBaseURL={`${webBaseURL}/${props.username}/${props.slug}/highlights`}
|
||||
fontSize={props.fontSize}
|
||||
margin={props.margin}
|
||||
fontFamily={props.fontFamily}
|
||||
|
||||
Reference in New Issue
Block a user