Files
omnivore/packages/web/lib/highlights/createHighlight.ts
Jackson Harper 6daa599b76 Separate the mutations out of lower level components
This will let us handle the mutations differently on native
iOS and should help in avoiding making web calls directly from
the web view, so we can avoid CORs and introduce a caching
layer.
2022-03-22 13:58:39 -07:00

119 lines
3.1 KiB
TypeScript

import { v4 as uuidv4 } from 'uuid'
import { nanoid } from 'nanoid'
import type { SelectionAttributes } from './highlightHelpers'
import {
generateDiffPatch,
isValidLength,
makeHighlightNodeAttributes,
} from './highlightGenerator'
import type { HighlightLocation } from './highlightGenerator'
import { extendRangeToWordBoundaries } from './normalizeHighlightRange'
import type { Highlight } from '../networking/fragments/highlightFragment'
import { removeHighlights } from './deleteHighlight'
import { ArticleMutations } from '../articleActions'
type CreateHighlightInput = {
selection: SelectionAttributes
articleId: string
annotation?: string
existingHighlights: Highlight[]
highlightStartEndOffsets: HighlightLocation[]
}
type CreateHighlightOutput = {
highlights?: Highlight[]
errorMessage?: string
newHighlightIndex?: number
}
export async function createHighlight(
input: CreateHighlightInput,
articleMutations: ArticleMutations
): Promise<CreateHighlightOutput> {
if (!input.selection.selection) {
return {}
}
const shouldMerge = input.selection.overlapHighlights.length > 0
const { range, selection } = input.selection
extendRangeToWordBoundaries(range)
const id = uuidv4()
const patch = generateDiffPatch(range)
if (!isValidLength(patch)) {
return { errorMessage: 'Highlight is too long' }
}
if (!selection.isCollapsed) {
selection.collapseToStart()
}
const annotations: string[] = []
if (input.annotation) {
annotations.push(input.annotation)
}
if (shouldMerge) {
input.selection.overlapHighlights.forEach((id) => {
const highlight = input.existingHighlights.find(($0) => $0.id === id)
const annotation = highlight?.annotation
if (annotation) {
annotations.push(annotation)
}
})
removeHighlights(input.selection.overlapHighlights, input.highlightStartEndOffsets)
}
const highlightAttributes = makeHighlightNodeAttributes(
patch,
id,
annotations.length > 0
)
const newHighlightAttributes = {
prefix: highlightAttributes.prefix,
suffix: highlightAttributes.suffix,
quote: highlightAttributes.quote,
id,
shortId: nanoid(8),
patch,
annotation: annotations.length > 0 ? annotations.join('\n') : undefined,
articleId: input.articleId,
}
let highlight: Highlight | undefined
let keptHighlights = input.existingHighlights
if (shouldMerge) {
const result = await articleMutations.mergeHighlightMutation({
...newHighlightAttributes,
overlapHighlightIdList: input.selection.overlapHighlights,
})
highlight = result?.mergeHighlight.highlight
keptHighlights = input.existingHighlights.filter(
($0) => !input.selection.overlapHighlights.includes($0.id)
)
} else {
highlight = await articleMutations.createHighlightMutation(newHighlightAttributes)
}
if (highlight) {
const highlights = [...keptHighlights, highlight]
return {
highlights,
newHighlightIndex:
highlights.length > 0 ? highlights.length - 1 : undefined,
}
} else {
return {
highlights: input.existingHighlights,
errorMessage: 'Could not create highlight',
}
}
}