Track the highlight position percent and anchor index when saving on web

This commit is contained in:
Jackson Harper 2023-02-01 16:42:36 +08:00
parent 4c04bcffa8
commit fe9ed7aa13
6 changed files with 85 additions and 38 deletions

View file

@ -88,6 +88,7 @@ struct WebReaderContent {
window.localStorage.setItem("theme", "\(themeKey)")
window.prefersHighContrastFont = \(prefersHighContrastText)
window.enableHighlightBar = \(isMacApp)
window.enableSelectToHighlight = true
</script>
<script src="bundle.js"></script>
<script src="mathJaxConfiguration.js" id="MathJax-script"></script>

View file

@ -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

View file

@ -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<Highlight | undefined>(
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({

View file

@ -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<CreateHighlightOutput> {
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) {

View file

@ -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<SelectionAttributes | null>(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]
}

View file

@ -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 = {