Pull in link to highlight changes

This commit is contained in:
Jackson Harper 2022-05-03 14:04:40 -07:00
parent 5d42b65611
commit c5549d72a6
5 changed files with 112 additions and 83 deletions

View file

@ -6,6 +6,7 @@ import { styled } from '../tokens/stitches.config'
type HighlightViewProps = {
highlight: Highlight
scrollToHighlight?: (arg: string) => void;
author?: string
title?: string
}
@ -15,25 +16,23 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
() => props.highlight.quote.split('\n'),
[props.highlight.quote]
)
const annotation = props.highlight.annotation ?? '';
const StyledQuote = styled(Blockquote, {
margin: '0px 24px 16px 24px',
fontSize: '18px',
lineHeight: '27px',
color: '$textDefault',
margin: '0px 0px 24px 0px',
fontSize: '14px',
lineHeight: '1.5',
fontFamily: 'Inter',
color: '$omnivoreGray',
cursor: 'pointer',
})
const scrollToHighlight = () => props.scrollToHighlight ? props.scrollToHighlight(props.highlight.id) : null
return (
<VStack css={{ width: '100%', boxSizing: 'border-box' }}>
{annotation && (
<Box css={{p:'16px', m:'16px', ml:'24px', mr:'24px', borderRadius: '6px', bg: '$grayBase'}}>
<StyledText style='shareHighlightModalAnnotation'>{annotation}</StyledText>
</Box>)
}
<StyledQuote>
<VStack css={{ width: '100%', bg: '$omnivoreYellow', p: '20px' }}>
<StyledQuote onClick={scrollToHighlight}>
{props.highlight.prefix}
<SpanBox css={{ bg: '$highlightBackground', p: '1px', borderRadius: '2px', }}>
<SpanBox css={{ bg: '$highlight', p: '1px', borderRadius: '2px' }}>
{lines.map((line: string, index: number) => (
<Fragment key={index}>
{line}
@ -48,10 +47,11 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
</SpanBox>
{props.highlight.suffix}
</StyledQuote>
<Box css={{p: '24px', pt: '0', width: '100%', boxSizing: 'border-box'}}>
{props.author && props.title &&(
<StyledText style="highlightTitleAndAuthor">{props.title + props.author}</StyledText>
<Box css={{}}>
{props.author && (
<StyledText style="highlightAuthor">{props.author}</StyledText>
)}
<StyledText style="highlightTitle">{props.title}</StyledText>
</Box>
</VStack>
)

View file

@ -21,8 +21,10 @@ import { ArticleMutations } from '../../../lib/articleActions'
export type ArticleProps = {
articleId: string
content: string
highlightReady: boolean
initialAnchorIndex: number
initialReadingProgress?: number
highlightHref: MutableRefObject<string | null>
scrollElementRef: MutableRefObject<HTMLDivElement | null>
articleMutations: ArticleMutations
}
@ -139,50 +141,53 @@ export function Article(props: ArticleProps): JSX.Element {
return
}
if (!shouldScrollToInitialPosition) {
return
}
setShouldScrollToInitialPosition(false)
if (props.initialReadingProgress && props.initialReadingProgress >= 98) {
return
}
const anchorElement = document.querySelector(
`[data-omnivore-anchor-idx='${props.initialAnchorIndex.toString()}']`
)
if (anchorElement) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const calculateOffset = (obj: any): number => {
let offset = 0
if (obj.offsetParent) {
do {
offset += obj.offsetTop
} while ((obj = obj.offsetParent))
return offset
}
return 0
if (props.highlightReady) {
if (!shouldScrollToInitialPosition) {
return
}
if (props.scrollElementRef.current) {
props.scrollElementRef.current?.scroll(
0,
calculateOffset(anchorElement)
)
} else {
window.document.documentElement.scroll(
0,
calculateOffset(anchorElement)
)
setShouldScrollToInitialPosition(false)
if (props.initialReadingProgress && props.initialReadingProgress >= 98) {
return
}
const anchorElement = props.highlightHref.current
? document.querySelector(
`[omnivore-highlight-id="${props.highlightHref.current}"]`
)
: document.querySelector(
`[data-omnivore-anchor-idx='${props.initialAnchorIndex.toString()}']`
)
if (anchorElement) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const calculateOffset = (obj: any): number => {
let offset = 0
if (obj.offsetParent) {
do {
offset += obj.offsetTop
} while ((obj = obj.offsetParent))
return offset
}
return 0
}
const calculatedOffset = calculateOffset(anchorElement)
if (props.scrollElementRef.current) {
props.scrollElementRef.current?.scroll(0, calculatedOffset - 100)
} else {
window.document.documentElement.scroll(0, calculatedOffset - 100)
}
}
}
}, [
props.highlightReady,
props.scrollElementRef,
props.initialAnchorIndex,
props.initialReadingProgress,
props.scrollElementRef,
shouldScrollToInitialPosition,
])

View file

@ -6,7 +6,7 @@ import { ArticleSubtitle } from './../../patterns/ArticleSubtitle'
import { theme, ThemeId } from './../../tokens/stitches.config'
import { HighlightsLayer } from '../../templates/article/HighlightsLayer'
import { Button } from '../../elements/Button'
import { MutableRefObject, useEffect, useState } from 'react'
import { MutableRefObject, useEffect, useState, useRef } from 'react'
import { ReportIssuesModal } from './ReportIssuesModal'
import { reportIssueMutation } from '../../../lib/networking/mutations/reportIssueMutation'
import { ArticleHeaderToolbar } from './ArticleHeaderToolbar'
@ -15,6 +15,7 @@ import { updateThemeLocally } from '../../../lib/themeUpdater'
import { ArticleMutations } from '../../../lib/articleActions'
import { LabelChip } from '../../elements/LabelChip'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { HighlightLocation, makeHighlightStartEndOffset } from '../../../lib/highlights/highlightGenerator'
type ArticleContainerProps = {
article: ArticleAttributes
@ -36,6 +37,11 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
const [showShareModal, setShowShareModal] = useState(false)
const [showReportIssuesModal, setShowReportIssuesModal] = useState(false)
const [fontSize, setFontSize] = useState(props.fontSize ?? 20)
const highlightHref = useRef(window.location.hash ? window.location.hash.split('#')[1] : null)
const [highlightReady, setHighlightReady] = useState(false)
const [highlightLocations, setHighlightLocations] = useState<
HighlightLocation[]
>([])
const updateFontSize = async (newFontSize: number) => {
if (fontSize !== newFontSize) {
@ -48,6 +54,21 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
updateFontSize(props.fontSize ?? 20)
}, [props.fontSize])
// Load the highlights
useEffect(() => {
const res: HighlightLocation[] = []
props.article.highlights.forEach((highlight) => {
try {
const offset = makeHighlightStartEndOffset(highlight)
res.push(offset)
} catch (err) {
console.error(err)
}
})
setHighlightLocations(res)
setHighlightReady(true)
}, [props.article.highlights, setHighlightLocations])
// Listen for font size and color mode change events sent from host apps (ios, macos...)
useEffect(() => {
const increaseFontSize = async () => {
@ -158,6 +179,8 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
)}
</VStack>
<Article
highlightReady={highlightReady}
highlightHref={highlightHref}
articleId={props.article.id}
content={props.article.content}
initialAnchorIndex={props.article.readingProgressAnchorIndex}
@ -182,6 +205,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
<Box css={{ height: '100px' }} />
</Box>
<HighlightsLayer
highlightLocations={highlightLocations}
highlights={props.article.highlights}
articleTitle={props.article.title}
articleAuthor={props.article.author ?? ''}

View file

@ -29,6 +29,7 @@ type HighlightsLayerProps = {
highlightsBaseURL: string
setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
articleMutations: ArticleMutations
highlightLocations: HighlightLocation[]
}
type HighlightModalAction = 'none' | 'addComment' | 'share'
@ -49,9 +50,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const [highlightModalAction, setHighlightModalAction] =
useState<HighlightActionProps>({ highlightModalAction: 'none' })
const [highlightLocations, setHighlightLocations] = useState<
HighlightLocation[]
>([])
const focusedHighlightMousePos = useRef({ pageX: 0, pageY: 0 })
const [focusedHighlight, setFocusedHighlight] = useState<
@ -59,26 +57,12 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
>(undefined)
const [selectionData, setSelectionData] = useSelection(
highlightLocations,
props.highlightLocations,
false //noteModal.open,
)
const canShareNative = useCanShareNative()
// Load the highlights
useEffect(() => {
const res: HighlightLocation[] = []
highlights.forEach((highlight) => {
try {
const offset = makeHighlightStartEndOffset(highlight)
res.push(offset)
} catch (err) {
console.error(err)
}
})
setHighlightLocations(res)
}, [highlights, setHighlightLocations])
const removeHighlightCallback = useCallback(
async (id?: string) => {
const highlightId = id || focusedHighlight?.id
@ -89,7 +73,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
if (didDeleteHighlight) {
removeHighlights(
highlights.map(($0) => $0.id),
highlightLocations
props.highlightLocations
)
setHighlights(highlights.filter(($0) => $0.id !== highlightId))
setFocusedHighlight(undefined)
@ -97,16 +81,16 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
console.error('Failed to delete highlight')
}
},
[focusedHighlight, highlights, highlightLocations]
[focusedHighlight, highlights, props.highlightLocations]
)
const updateHighlightsCallback = useCallback(
(highlight: Highlight) => {
removeHighlights([highlight.id], highlightLocations)
removeHighlights([highlight.id], props.highlightLocations)
const keptHighlights = highlights.filter(($0) => $0.id !== highlight.id)
setHighlights([...keptHighlights, highlight])
},
[highlights, highlightLocations]
[highlights, props.highlightLocations]
)
const handleNativeShare = useCallback(
@ -159,7 +143,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
selection: selection,
articleId: props.articleId,
existingHighlights: highlights,
highlightStartEndOffsets: highlightLocations,
highlightStartEndOffsets: props.highlightLocations,
annotation: note,
}, props.articleMutations)
@ -214,10 +198,22 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
selectionData,
setSelectionData,
canShareNative,
highlightLocations,
props.highlightLocations,
]
)
const scrollToHighlight = (id: string) => {
const foundElement = document.querySelector(`[omnivore-highlight-id="${id}"]`)
if(foundElement){
foundElement.scrollIntoView({
block: 'center',
behavior: 'smooth'
})
window.location.hash = `#${id}`
props.setShowHighlightsModal(false)
}
}
// Detect mouseclick on a highlight -- call `setFocusedHighlight` when highlight detected
const handleClickHighlight = useCallback(
(event: MouseEvent) => {
@ -261,7 +257,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
})
} else setFocusedHighlight(undefined)
},
[highlights, highlightLocations]
[highlights, props.highlightLocations]
)
useEffect(() => {
@ -469,9 +465,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
if (props.showHighlightsModal) {
return (
<HighlightsModal
highlights={highlights}
onOpenChange={() => props.setShowHighlightsModal(false)}
deleteHighlightAction={(highlightId: string) => {
highlights={highlights}
onOpenChange={() => props.setShowHighlightsModal(false)}
scrollToHighlight={scrollToHighlight}
deleteHighlightAction={(highlightId: string) => {
removeHighlightCallback(highlightId)
}}
/>

View file

@ -19,6 +19,7 @@ import { Pen, Trash } from 'phosphor-react'
type HighlightsModalProps = {
highlights: Highlight[]
scrollToHighlight?: (arg: string) => void;
deleteHighlightAction?: (highlightId: string) => void
onOpenChange: (open: boolean) => void
}
@ -60,6 +61,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
key={highlight.id}
highlight={highlight}
showDelete={!!props.deleteHighlightAction}
scrollToHighlight={props.scrollToHighlight}
deleteHighlightAction={() => {
if (props.deleteHighlightAction) {
props.deleteHighlightAction(highlight.id)
@ -82,6 +84,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
type ModalHighlightViewProps = {
highlight: Highlight
showDelete: boolean
scrollToHighlight?: (arg: string) => void;
deleteHighlightAction: () => void
}
@ -156,7 +159,7 @@ function ModalHighlightView(props: ModalHighlightViewProps): JSX.Element {
return (
<>
<VStack>
<HighlightView highlight={props.highlight} />
<HighlightView scrollToHighlight={props.scrollToHighlight} highlight={props.highlight} />
{props.highlight.annotation && !isEditing ? (
<StyledText css={{ px: '24px' }}>{props.highlight.annotation}</StyledText>
) : null}