omnivore/packages/web/lib/highlights/createHighlight.ts

158 lines
4.3 KiB
TypeScript
Raw Permalink Normal View History

2022-02-11 17:24:33 +00:00
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,
HighlightType,
} from '../networking/fragments/highlightFragment'
2022-02-11 17:24:33 +00:00
import { removeHighlights } from './deleteHighlight'
import { ArticleMutations } from '../articleActions'
2023-03-31 04:26:03 +00:00
import { NodeHtmlMarkdown } from 'node-html-markdown'
2022-02-11 17:24:33 +00:00
type CreateHighlightInput = {
selection: SelectionAttributes
articleId: string
annotation?: string
2023-08-24 04:00:35 +00:00
color?: string
2022-02-11 17:24:33 +00:00
existingHighlights: Highlight[]
highlightStartEndOffsets: HighlightLocation[]
highlightPositionPercent?: number
highlightPositionAnchorIndex?: number
2022-02-11 17:24:33 +00:00
}
type CreateHighlightOutput = {
highlights?: Highlight[]
errorMessage?: string
newHighlightIndex?: number
}
2023-03-31 04:26:03 +00:00
/* ********************************************************* *
* Re-use
* If using it several times, creating an instance saves time
* ********************************************************* */
const nhm = new NodeHtmlMarkdown(
/* options (optional) */ {},
/* customTransformers (optional) */ undefined,
/* customCodeBlockTranslators (optional) */ undefined
)
export const htmlToMarkdown = (html: string) => {
return nhm.translate(/* html */ html)
}
2022-02-11 17:24:33 +00:00
export async function createHighlight(
input: CreateHighlightInput,
articleMutations: ArticleMutations
2022-02-11 17:24:33 +00:00
): Promise<CreateHighlightOutput> {
if (!input.selection.selection) {
return {}
}
console.log(' overlapping: ', input.selection.overlapHighlights)
2022-02-11 17:24:33 +00:00
const shouldMerge = input.selection.overlapHighlights.length > 0
const { range, selection } = input.selection
extendRangeToWordBoundaries(range)
2023-03-31 06:04:42 +00:00
// Create a temp container for copying the range HTML
const container = document.createElement('div')
2023-03-31 04:26:03 +00:00
container.appendChild(range.cloneContents())
2022-02-11 17:24:33 +00:00
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
)
2022-02-11 17:24:33 +00:00
}
const highlightAttributes = makeHighlightNodeAttributes(
patch,
id,
2023-08-24 04:00:35 +00:00
annotations.length > 0,
2024-02-19 07:38:54 +00:00
false,
2023-08-24 04:00:35 +00:00
input.color
2022-02-11 17:24:33 +00:00
)
const newHighlightAttributes = {
id,
shortId: nanoid(8),
patch,
type: 'HIGHLIGHT' as HighlightType,
2023-03-31 06:04:42 +00:00
2023-08-24 04:00:35 +00:00
color: input.color,
2023-03-31 06:04:42 +00:00
prefix: highlightAttributes.prefix,
suffix: highlightAttributes.suffix,
quote: htmlToMarkdown(container.innerHTML),
html: container.innerHTML,
2022-02-11 17:24:33 +00:00
annotation: annotations.length > 0 ? annotations.join('\n') : undefined,
articleId: input.articleId,
highlightPositionPercent: input.highlightPositionPercent,
highlightPositionAnchorIndex: input.highlightPositionAnchorIndex,
2022-02-11 17:24:33 +00:00
}
let highlight: Highlight | undefined
let keptHighlights = input.existingHighlights
if (shouldMerge) {
highlight = await articleMutations.mergeHighlightMutation({
2022-02-11 17:24:33 +00:00
...newHighlightAttributes,
overlapHighlightIdList: input.selection.overlapHighlights,
})
keptHighlights = input.existingHighlights.filter(
($0) => !input.selection.overlapHighlights.includes($0.id)
)
} else {
highlight = await articleMutations.createHighlightMutation(
newHighlightAttributes
)
2022-02-11 17:24:33 +00:00
}
document.dispatchEvent(new Event('highlightsUpdated'))
2022-02-11 17:24:33 +00:00
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',
}
}
}