mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Track the highlight position percent and anchor index when saving on web
This commit is contained in:
parent
4c04bcffa8
commit
fe9ed7aa13
6 changed files with 85 additions and 38 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue