From fe9ed7aa13dd0f2181edc76c47361260afe7e846 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 1 Feb 2023 16:42:36 +0800 Subject: [PATCH] Track the highlight position percent and anchor index when saving on web --- .../Views/WebReader/WebReaderContent.swift | 1 + .../Views/Article/OmnivoreWebView.swift | 21 +++--- .../templates/article/HighlightsLayer.tsx | 73 ++++++++++++++----- .../web/lib/highlights/createHighlight.ts | 14 +++- packages/web/lib/highlights/useSelection.tsx | 10 +-- .../networking/fragments/highlightFragment.ts | 4 + 6 files changed, 85 insertions(+), 38 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift index ab2cea59d..0df5750b3 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift @@ -88,6 +88,7 @@ struct WebReaderContent { window.localStorage.setItem("theme", "\(themeKey)") window.prefersHighContrastFont = \(prefersHighContrastText) window.enableHighlightBar = \(isMacApp) + window.enableSelectToHighlight = true diff --git a/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift b/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift index 7405e4777..a76c02c4b 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift @@ -196,15 +196,17 @@ public final class OmnivoreWebView: WKWebView { private func setDefaultMenu() { currentMenu = .defaultMenu - if #available(iOS 16.0, *) { - // on iOS16 we use menuBuilder to create these items - } else { - let annotate = UIMenuItem(title: "Annotate", action: #selector(annotateSelection)) - let highlight = UIMenuItem(title: LocalText.genericHighlight, action: #selector(highlightSelection)) - // let share = UIMenuItem(title: "Share", action: #selector(shareSelection)) + setHighlightMenu() - UIMenuController.shared.menuItems = [highlight, /* share, */ annotate] - } +// if #available(iOS 16.0, *) { +// // on iOS16 we use menuBuilder to create these items +// } else { +// let annotate = UIMenuItem(title: "Annotate", action: #selector(annotateSelection)) +// let highlight = UIMenuItem(title: LocalText.genericHighlight, action: #selector(highlightSelection)) +// // let share = UIMenuItem(title: "Share", action: #selector(shareSelection)) +// +// UIMenuController.shared.menuItems = [highlight, /* share, */ annotate] +// } } private func setHighlightMenu() { @@ -230,7 +232,8 @@ public final class OmnivoreWebView: WKWebView { } @objc func menuDidHide() { - setDefaultMenu() + // setDefaultMenu() + setHighlightMenu() } // swiftlint:disable:next line_length diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx index c29fec273..4dda397db 100644 --- a/packages/web/components/templates/article/HighlightsLayer.tsx +++ b/packages/web/components/templates/article/HighlightsLayer.tsx @@ -73,10 +73,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { Highlight | undefined >(undefined) - const [selectionData, setSelectionData] = useSelection( - highlightLocations, - false //noteModal.open, - ) + const [selectionData, setSelectionData] = useSelection(highlightLocations) const [labelsTarget, setLabelsTarget] = useState( undefined @@ -192,6 +189,41 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { [props.highlightBarDisabled] ) + const selectionPercentPos = (selection: Selection): number | undefined => { + if ( + selection.rangeCount > 0 && + window && + window.document.scrollingElement + ) { + const percent = + (selection.getRangeAt(0).getBoundingClientRect().y + window.scrollY) / + window.document.scrollingElement.scrollHeight + return Math.min(Math.max(0, percent * 100), 100) + } + return undefined + } + + const selectionAnchorIndex = (selection: Selection): number | undefined => { + if (selection.rangeCount > 0) { + const containerElement = () => { + const node = selection.getRangeAt(0).startContainer + if (node.nodeType == Node.ELEMENT_NODE) { + return node as HTMLElement + } + return node.parentElement + } + let walk = containerElement() + while (walk) { + const idx = Number(walk.getAttribute('data-omnivore-anchor-idx')) + if (idx > 0) { + return idx + } + walk = walk.parentElement + } + } + return undefined + } + const createHighlightFromSelection = async ( selection: SelectionAttributes, note?: string @@ -203,6 +235,8 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { existingHighlights: highlights, highlightStartEndOffsets: highlightLocations, annotation: note, + highlightPositionPercent: selectionPercentPos(selection.selection), + highlightPositionAnchorIndex: selectionAnchorIndex(selection.selection), }, props.articleMutations ) @@ -258,20 +292,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { ] ) - 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) => { @@ -424,6 +444,23 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { ] ) + useEffect(() => { + ;(async () => { + if ( + 'enableSelectToHighlight' in window && + window.enableSelectToHighlight && + selectionData + ) { + // console.log('auto highlight: ', selectionData.selection.toString()) + await createHighlightCallback('none', undefined) + selectionData.selection.collapseToStart() + // handleClickHighlight(selectionData.mouseEvent) + // setSelectionData(null) + // window.getSelection()?.removeAllRanges() + } + })() + }, [selectionData, setSelectionData]) + const dispatchHighlightError = (action: string, error: unknown) => { if (props.isAppleAppEmbed) { window?.webkit?.messageHandlers.highlightAction?.postMessage({ diff --git a/packages/web/lib/highlights/createHighlight.ts b/packages/web/lib/highlights/createHighlight.ts index 7262e3252..045f28819 100644 --- a/packages/web/lib/highlights/createHighlight.ts +++ b/packages/web/lib/highlights/createHighlight.ts @@ -18,6 +18,8 @@ type CreateHighlightInput = { annotation?: string existingHighlights: Highlight[] highlightStartEndOffsets: HighlightLocation[] + highlightPositionPercent?: number + highlightPositionAnchorIndex?: number } type CreateHighlightOutput = { @@ -30,7 +32,6 @@ export async function createHighlight( input: CreateHighlightInput, articleMutations: ArticleMutations ): Promise { - if (!input.selection.selection) { return {} } @@ -65,7 +66,10 @@ export async function createHighlight( annotations.push(annotation) } }) - removeHighlights(input.selection.overlapHighlights, input.highlightStartEndOffsets) + removeHighlights( + input.selection.overlapHighlights, + input.highlightStartEndOffsets + ) } const highlightAttributes = makeHighlightNodeAttributes( @@ -83,6 +87,8 @@ export async function createHighlight( patch, annotation: annotations.length > 0 ? annotations.join('\n') : undefined, articleId: input.articleId, + highlightPositionPercent: input.highlightPositionPercent, + highlightPositionAnchorIndex: input.highlightPositionAnchorIndex, } let highlight: Highlight | undefined @@ -98,7 +104,9 @@ export async function createHighlight( ($0) => !input.selection.overlapHighlights.includes($0.id) ) } else { - highlight = await articleMutations.createHighlightMutation(newHighlightAttributes) + highlight = await articleMutations.createHighlightMutation( + newHighlightAttributes + ) } if (highlight) { diff --git a/packages/web/lib/highlights/useSelection.tsx b/packages/web/lib/highlights/useSelection.tsx index 5070b182e..10fb104fc 100644 --- a/packages/web/lib/highlights/useSelection.tsx +++ b/packages/web/lib/highlights/useSelection.tsx @@ -7,10 +7,8 @@ import { import type { SelectionAttributes } from './highlightHelpers' export function useSelection( - highlightLocations: HighlightLocation[], - isDisabled: boolean + highlightLocations: HighlightLocation[] ): [SelectionAttributes | null, (x: SelectionAttributes | null) => void] { - const disabled = isDisabled const [selectionAttributes, setSelectionAttributes] = useState(null) @@ -142,10 +140,6 @@ export function useSelection( }, [selectionAttributes?.selection]) useEffect(() => { - if (disabled) { - return - } - document.addEventListener('mouseup', handleFinishTouch) document.addEventListener('touchend', handleFinishTouch) document.addEventListener('contextmenu', handleFinishTouch) @@ -157,7 +151,7 @@ export function useSelection( document.removeEventListener('contextmenu', handleFinishTouch) document.removeEventListener('copyTextSelection', copyTextSelection) } - }, [highlightLocations, handleFinishTouch, disabled, copyTextSelection]) + }, [highlightLocations, handleFinishTouch, copyTextSelection]) return [selectionAttributes, setSelectionAttributes] } diff --git a/packages/web/lib/networking/fragments/highlightFragment.ts b/packages/web/lib/networking/fragments/highlightFragment.ts index fc7bfabb2..d7ca16cf4 100644 --- a/packages/web/lib/networking/fragments/highlightFragment.ts +++ b/packages/web/lib/networking/fragments/highlightFragment.ts @@ -13,6 +13,8 @@ export const highlightFragment = gql` createdByMe updatedAt sharedAt + highlightPositionPercent + highlightPositionAnchorIndex labels { id name @@ -34,6 +36,8 @@ export type Highlight = { updatedAt: string sharedAt: string labels?: Label[] + highlightPositionPercent?: number + highlightPositionAnchorIndex?: number } export type User = {