diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt index 415a27484..5410b5f23 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt @@ -66,7 +66,7 @@ fun WebReader(params: WebReaderParams) { webViewClient = object : WebViewClient() { } - addJavascriptInterface(WebMessageHandler(), "WebMessageHandler") + addJavascriptInterface(AndroidWebKitMessageHandler(), "AndroidWebKitMessageHandler") loadDataWithBaseURL("file:///android_asset/", styledContent, "text/html; charset=utf-8", "utf-8", null); } @@ -139,7 +139,7 @@ class OmnivoreWebView(context: Context) : WebView(context) { } -class WebMessageHandler { +class AndroidWebKitMessageHandler { @JavascriptInterface fun handleMessage(jsonString: String) { val message = JSONObject(jsonString) diff --git a/packages/appreader/src/index.jsx b/packages/appreader/src/index.jsx index 573988fd1..5ee77d405 100644 --- a/packages/appreader/src/index.jsx +++ b/packages/appreader/src/index.jsx @@ -18,7 +18,7 @@ const mutation = async (name, input) => { } else { // Send android a message console.log('sending android a message', message) - WebMessageHandler.handleMessage(JSON.stringify(message)) + AndroidWebKitMessageHandler.handleMessage(JSON.stringify(message)) } } diff --git a/packages/web/additional.d.ts b/packages/web/additional.d.ts index c121550ba..3519e849f 100644 --- a/packages/web/additional.d.ts +++ b/packages/web/additional.d.ts @@ -1,5 +1,9 @@ export {} +declare type AndroidWebKitMessageHandler = { + handleMessage: (string) => void +} + declare global { interface Window { webkit?: Webkit @@ -9,6 +13,7 @@ declare global { Intercom: Function intercomSettings: IntercomSettings analytics?: Analytics + AndroidWebKitMessageHandler?: AndroidWebKitMessageHandler } } diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx index ec426cf52..60cf13413 100644 --- a/packages/web/components/templates/article/Article.tsx +++ b/packages/web/components/templates/article/Article.tsx @@ -84,21 +84,22 @@ export function Article(props: ArticleProps): JSX.Element { window.webkit.messageHandlers.readingProgressUpdate?.postMessage({ progress: readingProgress, }) + } else if (typeof window?.AndroidWebKitMessageHandler != 'undefined') { + window.AndroidWebKitMessageHandler.handleMessage( + JSON.stringify({ progress: readingProgress }) + ) } }, [readingProgress]) - useScrollWatcher( - (changeset: ScrollOffsetChangeset) => { - if (window && window.document.scrollingElement) { - const newReadingProgress = - window.scrollY / window.document.scrollingElement.scrollHeight - const adjustedReadingProgress = - newReadingProgress > 0.92 ? 1 : newReadingProgress - debouncedSetReadingProgress(adjustedReadingProgress * 100) - } - }, - 1000 - ) + useScrollWatcher((changeset: ScrollOffsetChangeset) => { + if (window && window.document.scrollingElement) { + const newReadingProgress = + window.scrollY / window.document.scrollingElement.scrollHeight + const adjustedReadingProgress = + newReadingProgress > 0.92 ? 1 : newReadingProgress + debouncedSetReadingProgress(adjustedReadingProgress * 100) + } + }, 1000) const layoutImages = useCallback( (image: HTMLImageElement, container: HTMLDivElement | null) => { @@ -127,46 +128,46 @@ export function Article(props: ArticleProps): JSX.Element { if (typeof window === 'undefined') { return } - if (!shouldScrollToInitialPosition) { - return - } + if (!shouldScrollToInitialPosition) { + return + } - setShouldScrollToInitialPosition(false) + setShouldScrollToInitialPosition(false) - // If we are scrolling to a highlight, dont scroll to read position - if (props.highlightHref.current) { - return - } + // If we are scrolling to a highlight, dont scroll to read position + if (props.highlightHref.current) { + return + } - if (props.initialReadingProgress && props.initialReadingProgress >= 98) { - return - } + 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()}']` - ) + 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 + 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 } - const calculatedOffset = calculateOffset(anchorElement) - window.document.documentElement.scroll(0, calculatedOffset - 100) + return 0 } + + const calculatedOffset = calculateOffset(anchorElement) + window.document.documentElement.scroll(0, calculatedOffset - 100) + } }, [ props.initialAnchorIndex, props.initialReadingProgress, diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx index ab3574406..f48007b59 100644 --- a/packages/web/components/templates/article/HighlightsLayer.tsx +++ b/packages/web/components/templates/article/HighlightsLayer.tsx @@ -1,4 +1,10 @@ -import { useEffect, useRef, useCallback, useState, MutableRefObject } from 'react' +import { + useEffect, + useRef, + useCallback, + useState, + MutableRefObject, +} from 'react' import { makeHighlightStartEndOffset } from '../../../lib/highlights/highlightGenerator' import type { HighlightLocation } from '../../../lib/highlights/highlightGenerator' import { useSelection } from '../../../lib/highlights/useSelection' @@ -30,7 +36,6 @@ type HighlightsLayerProps = { scrollToHighlight: MutableRefObject setShowHighlightsModal: React.Dispatch> articleMutations: ArticleMutations - } type HighlightModalAction = 'none' | 'addComment' | 'share' @@ -88,7 +93,9 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { // that now that all the content has been injected into the // page. if (props.scrollToHighlight.current) { - const anchorElement = document.querySelector(`[omnivore-highlight-id="${props.scrollToHighlight.current}"]`) + const anchorElement = document.querySelector( + `[omnivore-highlight-id="${props.scrollToHighlight.current}"]` + ) if (anchorElement) { anchorElement.scrollIntoView({ behavior: 'auto' }) } @@ -100,7 +107,8 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { const highlightId = id || focusedHighlight?.id if (!highlightId) return - const didDeleteHighlight = await props.articleMutations.deleteHighlightMutation(highlightId) + const didDeleteHighlight = + await props.articleMutations.deleteHighlightMutation(highlightId) if (didDeleteHighlight) { removeHighlights( @@ -154,6 +162,13 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { actionID: 'annotate', annotation: inputs.highlight?.annotation ?? '', }) + } else if (typeof window?.AndroidWebKitMessageHandler != 'undefined') { + window.AndroidWebKitMessageHandler.handleMessage( + JSON.stringify({ + actionID: 'annotate', + annotation: inputs.highlight?.annotation ?? '', + }) + ) } else { inputs.createHighlightForNote = async (note?: string) => { if (!inputs.selectionData) { @@ -171,13 +186,16 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { selection: SelectionAttributes, note?: string ): Promise => { - const result = await createHighlight({ - selection: selection, - articleId: props.articleId, - existingHighlights: highlights, - highlightStartEndOffsets: highlightLocations, - annotation: note, - }, props.articleMutations) + const result = await createHighlight( + { + selection: selection, + articleId: props.articleId, + existingHighlights: highlights, + highlightStartEndOffsets: highlightLocations, + annotation: note, + }, + props.articleMutations + ) if (!result.highlights || result.highlights.length == 0) { // TODO: show an error message @@ -235,16 +253,18 @@ 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) - } - } + 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( @@ -269,13 +289,17 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { // In the native app we post a message with the rect of the // highlight, so the app can display a native menu const rect = (target as Element).getBoundingClientRect() - window?.webkit?.messageHandlers.viewerAction?.postMessage({ + const message = { actionID: 'showMenu', rectX: rect.x, rectY: rect.y, rectWidth: rect.width, rectHeight: rect.height, - }) + } + window?.webkit?.messageHandlers.viewerAction?.postMessage(message) + window?.AndroidWebKitMessageHandler?.handleMessage( + JSON.stringify(message) + ) setFocusedHighlight(highlight) } } else if ((target as Element).hasAttribute(highlightNoteIdAttribute)) { @@ -326,13 +350,19 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { break case 'share': if (props.isAppleAppEmbed) { - // send action to native app (naive app doesn't handle this yet so it's a no-op) - window?.webkit?.messageHandlers.highlightAction?.postMessage({ + const message = { actionID: 'share', highlightID: focusedHighlight?.id, - }) + } + window?.webkit?.messageHandlers.highlightAction?.postMessage( + message + ) } + window?.AndroidWebKitMessageHandler?.handleMessage( + JSON.stringify(message) + ) + if (focusedHighlight) { if (canShareNative) { handleNativeShare(focusedHighlight.shortId) @@ -392,7 +422,9 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { } const speakingSection = async (event: SpeakingSectionEvent) => { - const item = document.querySelector(`[data-omnivore-anchor-idx="${event.anchorIdx}"]`) + const item = document.querySelector( + `[data-omnivore-anchor-idx="${event.anchorIdx}"]` + ) const otherItems = document.querySelectorAll('.speakingSection') otherItems.forEach((other) => { if (other != item) { @@ -435,7 +467,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { document.addEventListener('saveAnnotation', saveAnnotation) document.addEventListener('speakingSection', speakingSection) - return () => { document.removeEventListener('annotate', annotate) document.removeEventListener('highlight', highlight) @@ -445,7 +476,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { document.removeEventListener('dismissHighlight', dismissHighlight) document.removeEventListener('saveAnnotation', saveAnnotation) document.removeEventListener('speakingSection', speakingSection) - } }) @@ -523,4 +553,4 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { } return <> -} \ No newline at end of file +}