From c5549d72a65c1b85ef6c082d03f78a8d40af3d5a Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 3 May 2022 14:04:40 -0700 Subject: [PATCH] Pull in link to highlight changes --- .../web/components/patterns/HighlightView.tsx | 32 ++++---- .../components/templates/article/Article.tsx | 79 ++++++++++--------- .../templates/article/ArticleContainer.tsx | 26 +++++- .../templates/article/HighlightsLayer.tsx | 53 ++++++------- .../templates/article/HighlightsModal.tsx | 5 +- 5 files changed, 112 insertions(+), 83 deletions(-) diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 2534048ca..e69cda301 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -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 ( - - {annotation && ( - - {annotation} - ) - } - + + {props.highlight.prefix} - + {lines.map((line: string, index: number) => ( {line} @@ -48,10 +47,11 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { {props.highlight.suffix} - - {props.author && props.title &&( - {props.title + props.author} + + {props.author && ( + {props.author} )} + {props.title} ) diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx index 9c6382857..d4f931565 100644 --- a/packages/web/components/templates/article/Article.tsx +++ b/packages/web/components/templates/article/Article.tsx @@ -21,8 +21,10 @@ import { ArticleMutations } from '../../../lib/articleActions' export type ArticleProps = { articleId: string content: string + highlightReady: boolean initialAnchorIndex: number initialReadingProgress?: number + highlightHref: MutableRefObject scrollElementRef: MutableRefObject 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, ]) diff --git a/packages/web/components/templates/article/ArticleContainer.tsx b/packages/web/components/templates/article/ArticleContainer.tsx index 1ef48a9f7..78c7371b4 100644 --- a/packages/web/components/templates/article/ArticleContainer.tsx +++ b/packages/web/components/templates/article/ArticleContainer.tsx @@ -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 { )}
> articleMutations: ArticleMutations + highlightLocations: HighlightLocation[] } type HighlightModalAction = 'none' | 'addComment' | 'share' @@ -49,9 +50,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { const [highlightModalAction, setHighlightModalAction] = useState({ 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 ( props.setShowHighlightsModal(false)} - deleteHighlightAction={(highlightId: string) => { + highlights={highlights} + onOpenChange={() => props.setShowHighlightsModal(false)} + scrollToHighlight={scrollToHighlight} + deleteHighlightAction={(highlightId: string) => { removeHighlightCallback(highlightId) }} /> diff --git a/packages/web/components/templates/article/HighlightsModal.tsx b/packages/web/components/templates/article/HighlightsModal.tsx index d31557c4b..9f3ffde6c 100644 --- a/packages/web/components/templates/article/HighlightsModal.tsx +++ b/packages/web/components/templates/article/HighlightsModal.tsx @@ -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 ( <> - + {props.highlight.annotation && !isEditing ? ( {props.highlight.annotation} ) : null}