Merge pull request #297 from omnivore-app/article-mutations

Separate the mutations out of lower level components
This commit is contained in:
Jackson Harper
2022-03-23 11:49:20 -07:00
committed by GitHub
24 changed files with 2520 additions and 172 deletions

View File

@ -8,7 +8,7 @@ struct WebReader: UIViewRepresentable {
let articleContent: ArticleContent
let item: FeedItem
let openLinkAction: (URL) -> Void
let webViewActionHandler: (WKScriptMessage) -> Void
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
let navBarVisibilityRatioUpdater: (Double) -> Void
let authToken: String
let appEnv: AppEnvironment
@ -60,6 +60,8 @@ struct WebReader: UIViewRepresentable {
webView.configuration.userContentController.add(webView, name: "viewerAction")
webView.configuration.userContentController.addScriptMessageHandler(context.coordinator, contentWorld: .page, name: "articleAction")
context.coordinator.linkHandler = openLinkAction
context.coordinator.webViewActionHandler = webViewActionHandler
context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater

View File

@ -5,36 +5,6 @@ import SwiftUI
import Views
import WebKit
struct SafariWebLink: Identifiable {
let id: UUID
let url: URL
}
// TODO: load highlights
final class WebReaderViewModel: ObservableObject {
@Published var isLoading = false
@Published var articleContent: ArticleContent?
var subscriptions = Set<AnyCancellable>()
func loadContent(dataService: DataService, slug: String) {
isLoading = true
guard let viewer = dataService.currentViewer else { return }
dataService.articleContentPublisher(username: viewer.username, slug: slug).sink(
receiveCompletion: { [weak self] completion in
guard case .failure = completion else { return }
self?.isLoading = false
},
receiveValue: { [weak self] articleContent in
self?.articleContent = articleContent
}
)
.store(in: &subscriptions)
}
}
struct WebReaderContainerView: View {
let item: FeedItem
let homeFeedViewModel: HomeFeedViewModel
@ -62,7 +32,24 @@ struct WebReaderContainerView: View {
)
}
func webViewActionHandler(message: WKScriptMessage) {
func webViewActionHandler(message: WKScriptMessage, replyHandler: WKScriptMessageReplyHandler?) {
if message.name == WebViewAction.readingProgressUpdate.rawValue {
let messageBody = message.body as? [String: Double]
if let messageBody = messageBody, let progress = messageBody["progress"] {
homeFeedViewModel.updateProgress(itemID: item.id, progress: Double(progress))
}
}
if let replyHandler = replyHandler {
viewModel.webViewActionWithReplyHandler(
message: message,
replyHandler: replyHandler,
dataService: dataService
)
return
}
if message.name == WebViewAction.highlightAction.rawValue {
handleHighlightAction(message: message)
}

View File

@ -46,8 +46,8 @@ struct WebReaderContent {
}
window.omnivoreArticle = {
id: "test",
linkId: "test",
id: "\(item.id)",
linkId: "\(item.id)",
slug: "test-slug",
createdAt: new Date().toISOString(),
savedAt: new Date().toISOString(),

View File

@ -7,8 +7,10 @@ import Utils
import Views
import WebKit
typealias WKScriptMessageReplyHandler = (Any?, String?) -> Void
final class WebReaderCoordinator: NSObject {
var webViewActionHandler: (WKScriptMessage) -> Void = { _ in }
var webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void = { _, _ in }
var linkHandler: (URL) -> Void = { _ in }
var needsReload = true
var lastSavedAnnotationID: UUID?
@ -34,7 +36,17 @@ final class WebReaderCoordinator: NSObject {
extension WebReaderCoordinator: WKScriptMessageHandler {
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
webViewActionHandler(message)
webViewActionHandler(message, nil)
}
}
extension WebReaderCoordinator: WKScriptMessageHandlerWithReply {
func userContentController(
_: WKUserContentController,
didReceive message: WKScriptMessage,
replyHandler: @escaping (Any?, String?) -> Void
) {
webViewActionHandler(message, replyHandler)
}
}

View File

@ -0,0 +1,165 @@
import Combine
import Models
import Services
import SwiftUI
import WebKit
struct SafariWebLink: Identifiable {
let id: UUID
let url: URL
}
final class WebReaderViewModel: ObservableObject {
@Published var isLoading = false
@Published var articleContent: ArticleContent?
var subscriptions = Set<AnyCancellable>()
func loadContent(dataService: DataService, slug: String) {
isLoading = true
guard let viewer = dataService.currentViewer else { return }
dataService.articleContentPublisher(username: viewer.username, slug: slug).sink(
receiveCompletion: { [weak self] completion in
guard case .failure = completion else { return }
self?.isLoading = false
},
receiveValue: { [weak self] articleContent in
self?.articleContent = articleContent
}
)
.store(in: &subscriptions)
}
func createHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
dataService.createHighlightPublisher(
shortId: messageBody["shortId"] as? String ?? "",
highlightID: messageBody["id"] as? String ?? "",
quote: messageBody["quote"] as? String ?? "",
patch: messageBody["patch"] as? String ?? "",
articleId: messageBody["articleId"] as? String ?? ""
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func deleteHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
dataService.deleteHighlightPublisher(
highlightId: messageBody["highlightId"] as? String ?? ""
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func mergeHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
dataService.mergeHighlightPublisher(
shortId: messageBody["shortId"] as? String ?? "",
highlightID: messageBody["id"] as? String ?? "",
quote: messageBody["quote"] as? String ?? "",
patch: messageBody["patch"] as? String ?? "",
articleId: messageBody["articleId"] as? String ?? "",
overlapHighlightIdList: messageBody["overlapHighlightIdList"] as? [String] ?? []
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func updateHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
dataService.updateHighlightAttributesPublisher(
highlightID: messageBody["highlightId"] as? String ?? "",
annotation: messageBody["annotation"] as? String ?? "",
sharedAt: nil
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func updateReadingProgress(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
let itemID = messageBody["id"] as? String
let readingProgress = messageBody["readingProgressPercent"] as? Double
let anchorIndex = messageBody["readingProgressAnchorIndex"] as? Int
guard let itemID = itemID, let readingProgress = readingProgress, let anchorIndex = anchorIndex else {
replyHandler(["result": false], nil)
return
}
dataService.updateArticleReadingProgressPublisher(
itemID: itemID,
readingProgress: readingProgress,
anchorIndex: anchorIndex
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func webViewActionWithReplyHandler(
message: WKScriptMessage,
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
guard let messageBody = message.body as? [String: Any] else { return }
guard let actionID = messageBody["actionID"] as? String else { return }
switch actionID {
case "deleteHighlight":
deleteHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
case "createHighlight":
createHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
case "mergeHighlight":
mergeHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
case "updateHighlight":
updateHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
case "articleReadingProgress":
updateReadingProgress(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
default:
replyHandler(nil, "Unknown actionID: \(actionID)")
}
}
}

View File

@ -10,8 +10,6 @@ public struct ArticleContent {
) {
self.htmlContent = htmlContent
self.highlights = highlights
print(highlightsJSONString)
}
public var highlightsJSONString: String {

View File

@ -9,7 +9,8 @@ public extension DataService {
highlightID: String,
quote: String,
patch: String,
articleId: String
articleId: String,
annotation: String? = nil
) -> AnyPublisher<String, BasicError> {
enum MutationResult {
case saved(id: String)
@ -28,7 +29,12 @@ public extension DataService {
let mutation = Selection.Mutation {
try $0.createHighlight(
input: InputObjects.CreateHighlightInput(
id: highlightID, shortId: shortId, articleId: articleId, patch: patch, quote: quote
id: highlightID,
shortId: shortId,
articleId: articleId,
patch: patch,
quote: quote,
annotation: OptionalArgument(annotation)
),
selection: selection
)

File diff suppressed because one or more lines are too long

View File

@ -6,8 +6,17 @@ import { applyStoredTheme } from '@omnivore/web/lib/themeUpdater'
import '@omnivore/web/styles/globals.css'
import '@omnivore/web/styles/articleInnerStyling.css'
const mutation = async (name, input) => {
const result = await window?.webkit?.messageHandlers.articleAction?.postMessage({
actionID: name,
...input
})
console.log('action result', result, result.result)
return result.result
}
const App = () => {
applyStoredTheme(false) // false to skip serevr sync
applyStoredTheme(false)
return (
<>
@ -32,6 +41,13 @@ const App = () => {
highlightsBaseURL="https://example.com"
fontSize={window.fontSize ?? 18}
margin={0}
articleMutations={{
createHighlightMutation: (input) => mutation('createHighlight', input),
deleteHighlightMutation: (highlightId) => mutation('deleteHighlight', { highlightId }),
mergeHighlightMutation: (input) => mutation('mergeHighlight', input),
updateHighlightMutation: (input) => mutation('updateHighlight', input),
articleReadingProgressMutation: (input) => mutation('articleReadingProgress', input),
}}
/>
</VStack>
</Box>

View File

@ -12,11 +12,11 @@ import {
useRef,
useState,
} from 'react'
import { articleReadingProgressMutation } from '../../../lib/networking/mutations/articleReadingProgressMutation'
import { Tweet } from 'react-twitter-widgets'
import { render } from 'react-dom'
import { isDarkTheme } from '../../../lib/themeUpdater'
import { debounce } from 'lodash'
import { ArticleMutations } from '../../../lib/articleActions'
export type ArticleProps = {
articleId: string
@ -24,6 +24,7 @@ export type ArticleProps = {
initialAnchorIndex: number
initialReadingProgress?: number
scrollElementRef: MutableRefObject<HTMLDivElement | null>
articleMutations: ArticleMutations
}
export function Article(props: ArticleProps): JSX.Element {
@ -64,7 +65,7 @@ export function Article(props: ArticleProps): JSX.Element {
useEffect(() => {
;(async () => {
if (!readingProgress) return
await articleReadingProgressMutation({
await props.articleMutations.articleReadingProgressMutation({
id: props.articleId,
// round reading progress to 100% if more than that
readingProgressPercent: readingProgress > 100 ? 100 : readingProgress,

View File

@ -19,10 +19,12 @@ import { updateThemeLocally } from '../../../lib/themeUpdater'
import { EditLabelsModal } from './EditLabelsModal'
import Script from 'next/script'
import { useRouter } from 'next/router'
import { ArticleMutations } from '../../../lib/articleActions'
type ArticleContainerProps = {
viewerUsername: string
article: ArticleAttributes
articleMutations: ArticleMutations
scrollElementRef: MutableRefObject<HTMLDivElement | null>
isAppleAppEmbed: boolean
highlightBarDisabled: boolean
@ -187,6 +189,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
content={props.article.content}
initialAnchorIndex={props.article.readingProgressAnchorIndex}
scrollElementRef={props.scrollElementRef}
articleMutations={props.articleMutations}
/>
<Button
style="ghost"
@ -216,6 +219,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
showNotesSidebar={showNotesSidebar}
highlightsBaseURL={props.highlightsBaseURL}
setShowNotesSidebar={setShowNotesSidebar}
articleMutations={props.articleMutations}
/>
{showReportIssuesModal ? (
<ReportIssuesModal

View File

@ -3,7 +3,6 @@ import { makeHighlightStartEndOffset } from '../../../lib/highlights/highlightGe
import type { HighlightLocation } from '../../../lib/highlights/highlightGenerator'
import { useSelection } from '../../../lib/highlights/useSelection'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
import { shareHighlightToFeedMutation } from '../../../lib/networking/mutations/shareHighlightToFeedMutation'
import { shareHighlightCommentMutation } from '../../../lib/networking/mutations/updateShareHighlightCommentMutation'
import {
@ -18,9 +17,9 @@ import { HighlightNoteModal } from './HighlightNoteModal'
import { ShareHighlightModal } from './ShareHighlightModal'
import { HighlightPostToFeedModal } from './HighlightPostToFeedModal'
import { HighlightsModal } from './HighlightsModal'
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
import { useCanShareNative } from '../../../lib/hooks/useCanShareNative'
import toast from 'react-hot-toast'
import { ArticleMutations } from '../../../lib/articleActions'
type HighlightsLayerProps = {
viewerUsername: string
@ -33,6 +32,7 @@ type HighlightsLayerProps = {
showNotesSidebar: boolean
highlightsBaseURL: string
setShowNotesSidebar: React.Dispatch<React.SetStateAction<boolean>>
articleMutations: ArticleMutations
}
type HighlightModalAction = 'none' | 'addComment' | 'postToFeed' | 'share'
@ -88,7 +88,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const highlightId = id || focusedHighlight?.id
if (!highlightId) return
const didDeleteHighlight = await deleteHighlightMutation(highlightId)
const didDeleteHighlight = await props.articleMutations.deleteHighlightMutation(highlightId)
if (didDeleteHighlight) {
removeHighlights(
@ -191,7 +191,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
existingHighlights: highlights,
highlightStartEndOffsets: highlightLocations,
annotation: note,
})
}, props.articleMutations)
if (!result.highlights || result.highlights.length == 0) {
// TODO: show an error message
@ -407,7 +407,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
if (focusedHighlight) {
const annotation = event.annotation ?? ''
const result = await updateHighlightMutation({
const result = await props.articleMutations.updateHighlightMutation({
highlightId: focusedHighlight.id,
annotation: event.annotation ?? '',
})

View File

@ -0,0 +1,14 @@
import { Highlight } from "./networking/fragments/highlightFragment"
import { ArticleReadingProgressMutationInput } from "./networking/mutations/articleReadingProgressMutation"
import { CreateHighlightInput } from "./networking/mutations/createHighlightMutation"
import { MergeHighlightInput, MergeHighlightOutput } from "./networking/mutations/mergeHighlightMutation"
import { UpdateHighlightInput } from "./networking/mutations/updateHighlightMutation"
export type ArticleMutations = {
createHighlightMutation: (input: CreateHighlightInput) => Promise<Highlight | undefined>
deleteHighlightMutation: (highlightId: string) => Promise<boolean>
mergeHighlightMutation: (input: MergeHighlightInput) => Promise<MergeHighlightOutput | undefined>
updateHighlightMutation: (input: UpdateHighlightInput) => Promise<string | undefined>
articleReadingProgressMutation: (input: ArticleReadingProgressMutationInput) => Promise<boolean>
}

View File

@ -9,9 +9,8 @@ import {
import type { HighlightLocation } from './highlightGenerator'
import { extendRangeToWordBoundaries } from './normalizeHighlightRange'
import type { Highlight } from '../networking/fragments/highlightFragment'
import { createHighlightMutation } from '../networking/mutations/createHighlightMutation'
import { removeHighlights } from './deleteHighlight'
import { mergeHighlightMutation } from '../networking/mutations/mergeHighlightMutation'
import { ArticleMutations } from '../articleActions'
type CreateHighlightInput = {
selection: SelectionAttributes
@ -28,7 +27,8 @@ type CreateHighlightOutput = {
}
export async function createHighlight(
input: CreateHighlightInput
input: CreateHighlightInput,
articleMutations: ArticleMutations
): Promise<CreateHighlightOutput> {
if (!input.selection.selection) {
@ -89,7 +89,7 @@ export async function createHighlight(
let keptHighlights = input.existingHighlights
if (shouldMerge) {
const result = await mergeHighlightMutation({
const result = await articleMutations.mergeHighlightMutation({
...newHighlightAttributes,
overlapHighlightIdList: input.selection.overlapHighlights,
})
@ -99,7 +99,7 @@ export async function createHighlight(
($0) => !input.selection.overlapHighlights.includes($0.id)
)
} else {
highlight = await createHighlightMutation(newHighlightAttributes)
highlight = await articleMutations.createHighlightMutation(newHighlightAttributes)
}
if (highlight) {

View File

@ -2,7 +2,11 @@ import { diff_match_patch as DiffMatchPatch } from 'diff-match-patch'
import { RefObject } from 'react'
import type { Highlight } from '../networking/fragments/highlightFragment'
import { interpolationSearch } from './interpolationSearch'
import { highlightIdAttribute, highlightNoteIdAttribute } from './highlightHelpers'
import {
highlightIdAttribute,
highlightNoteIdAttribute,
noteImage,
} from './highlightHelpers'
const highlightTag = 'omnivore_highlight'
const highlightClassname = 'highlight'
@ -10,7 +14,8 @@ const highlightWithNoteClassName = 'highlight_with_note'
const articleContainerId = 'article-container'
export const maxHighlightLength = 2000
const nonParagraphTagsRegEx = /^(a|b|basefont|bdo|big|em|font|i|s|small|span|strike|strong|su[bp]|tt|u|code|mark)$/i
const nonParagraphTagsRegEx =
/^(a|b|basefont|bdo|big|em|font|i|s|small|span|strike|strong|su[bp]|tt|u|code|mark)$/i
const highlightContentRegex = new RegExp(
`<${highlightTag}>([\\s\\S]*)<\\/${highlightTag}>`,
'i'
@ -47,10 +52,8 @@ export type HighlightNodeAttributes = {
export function makeHighlightStartEndOffset(
highlight: Highlight
): HighlightLocation {
const {
startLocation: highlightTextStart,
endLocation: highlightTextEnd,
} = nodeAttributesFromHighlight(highlight)
const { startLocation: highlightTextStart, endLocation: highlightTextEnd } =
nodeAttributesFromHighlight(highlight)
return {
id: highlight.id,
start: highlightTextStart,
@ -129,7 +132,9 @@ export function makeHighlightNodeAttributes(
}
const newHighlightSpan = document.createElement('span')
newHighlightSpan.className = withNote ? highlightWithNoteClassName : highlightClassname
newHighlightSpan.className = withNote
? highlightWithNoteClassName
: highlightClassname
newHighlightSpan.setAttribute(highlightIdAttribute, id)
customColor &&
newHighlightSpan.setAttribute(
@ -146,13 +151,18 @@ export function makeHighlightNodeAttributes(
}
if (withNote && lastElement) {
lastElement.classList.add('last_element')
const button = document.createElement('img')
button.className = 'highlight_note_button'
button.src = '/static/icons/highlight-note-icon.svg'
button.alt = 'Add note'
button.setAttribute(highlightNoteIdAttribute, id)
lastElement.appendChild(button)
const svg = noteImage()
svg.setAttribute(highlightNoteIdAttribute, id)
const ctr = document.createElement('div')
ctr.className = 'highlight_note_button'
ctr.appendChild(svg)
ctr.setAttribute(highlightNoteIdAttribute, id)
ctr.setAttribute('width', '14px')
ctr.setAttribute('height', '14px')
lastElement.appendChild(ctr)
}
return {
@ -199,9 +209,8 @@ export function generateDiffPatch(range: Range): string {
export function wrapHighlightTagAroundRange(range: Range): [number, number] {
const patch = generateDiffPatch(range)
const { highlightTextStart, highlightTextEnd } = selectionOffsetsFromPatch(
patch
)
const { highlightTextStart, highlightTextEnd } =
selectionOffsetsFromPatch(patch)
return [highlightTextStart, highlightTextEnd]
}
@ -290,11 +299,7 @@ const selectionOffsetsFromPatch = (
}
}
export function getPrefixAndSuffix({
patch,
}: {
patch: string
}): {
export function getPrefixAndSuffix({ patch }: { patch: string }): {
prefix: string
suffix: string
highlightTextStart: number
@ -305,9 +310,8 @@ export function getPrefixAndSuffix({
if (!patch) throw new Error('Invalid patch')
const { textNodes } = getArticleTextNodes()
const { highlightTextStart, highlightTextEnd } = selectionOffsetsFromPatch(
patch
)
const { highlightTextStart, highlightTextEnd } =
selectionOffsetsFromPatch(patch)
// Searching for the starting text node using interpolation search algorithm
const textNodeIndex = interpolationSearch(
textNodes.map(({ startIndex: startIndex }) => startIndex),
@ -360,9 +364,11 @@ const fillHighlight = ({
highlightTextStart: number
highlightTextEnd: number
}): FillNodeResponse => {
const { node, startIndex: startIndex, startsParagraph } = textNodes[
startingTextNodeIndex
]
const {
node,
startIndex: startIndex,
startsParagraph,
} = textNodes[startingTextNodeIndex]
const text = node.nodeValue || ''
const textBeforeHighlightLenght = highlightTextStart - startIndex

View File

@ -24,3 +24,23 @@ export function getHighlightNoteButton(highlightId: string): Element[] {
document.querySelectorAll(`[${highlightNoteIdAttribute}='${highlightId}']`)
)
}
export function noteImage(): SVGSVGElement {
const svgURI = 'http://www.w3.org/2000/svg'
const svg = document.createElementNS(svgURI, 'svg')
svg.setAttribute('viewBox', '0 0 14 14')
svg.setAttribute('width', '14')
svg.setAttribute('height', '14')
svg.setAttribute('fill', 'none')
const path = document.createElementNS(svgURI, 'path')
path.setAttribute(
'd',
'M1 5.66602C1 3.7804 1 2.83759 1.58579 2.2518C2.17157 1.66602 3.11438 1.66602 5 1.66602H9C10.8856 1.66602 11.8284 1.66602 12.4142 2.2518C13 2.83759 13 3.7804 13 5.66602V7.66601C13 9.55163 13 10.4944 12.4142 11.0802C11.8284 11.666 10.8856 11.666 9 11.666H4.63014C4.49742 11.666 4.43106 11.666 4.36715 11.6701C3.92582 11.6984 3.50632 11.8722 3.17425 12.1642C3.12616 12.2065 3.07924 12.2534 2.98539 12.3473V12.3473C2.75446 12.5782 2.639 12.6937 2.55914 12.7475C1.96522 13.1481 1.15512 12.8125 1.01838 12.1093C1 12.0148 1 11.8515 1 11.5249V5.66602Z'
)
path.setAttribute('stroke', 'rgba(255, 210, 52, 0.8)')
path.setAttribute('stroke-width', '1.8')
path.setAttribute('stroke-linejoin', 'round')
svg.appendChild(path)
return svg
}

View File

@ -136,7 +136,7 @@ export function useSelection(
document.removeEventListener('touchend', handleFinishTouch)
document.removeEventListener('contextmenu', handleFinishTouch)
}
}, [JSON.stringify(highlightLocations), handleFinishTouch, disabled])
}, [highlightLocations, handleFinishTouch, disabled])
return [selectionAttributes, setSelectionAttributes]
}

View File

@ -1,7 +1,7 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
type ArticleReadingProgressMutationInput = {
export type ArticleReadingProgressMutationInput = {
id: string
readingProgressPercent: number
readingProgressAnchorIndex: number

View File

@ -2,7 +2,7 @@ import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { Highlight } from './../fragments/highlightFragment'
type CreateHighlightInput = {
export type CreateHighlightInput = {
prefix: string
suffix: string
quote: string

View File

@ -2,7 +2,7 @@ import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { Highlight } from './../fragments/highlightFragment'
type MergeHighlightInput = {
export type MergeHighlightInput = {
id: string
shortId: string
articleId: string
@ -14,7 +14,7 @@ type MergeHighlightInput = {
overlapHighlightIdList: string[]
}
type MergeHighlightOutput = {
export type MergeHighlightOutput = {
mergeHighlight: InnerMergeHighlightOutput
}

View File

@ -1,7 +1,7 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
type UpdateHighlightInput = {
export type UpdateHighlightInput = {
highlightId: string
annotation?: string
sharedAt?: string

View File

@ -13,6 +13,11 @@ import dynamic from 'next/dynamic'
import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
import { webBaseURL } from '../../../lib/appConfig'
import { Toaster } from 'react-hot-toast'
import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation'
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
import { mergeHighlightMutation } from '../../../lib/networking/mutations/mergeHighlightMutation'
import { articleReadingProgressMutation } from '../../../lib/networking/mutations/articleReadingProgressMutation'
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
const PdfArticleContainerNoSSR = dynamic<PdfArticleContainerProps>(
() => import('./../../../components/templates/article/PdfArticleContainer'),
@ -70,6 +75,13 @@ export default function Home(): JSX.Element {
viewerUsername={viewerData.me?.profile?.username}
highlightsBaseURL={`${webBaseURL}/${viewerData.me?.profile?.username}/${slug}/highlights`}
fontSize={preferencesData?.fontSize}
articleMutations={{
createHighlightMutation,
deleteHighlightMutation,
mergeHighlightMutation,
updateHighlightMutation,
articleReadingProgressMutation,
}}
/>
</VStack>
)}

View File

@ -7,6 +7,11 @@ import { webBaseURL } from '../../../../lib/appConfig'
import { LoadingView } from '../../../../components/patterns/LoadingView'
import { cookieValue } from '../../../../lib/cookieHelpers'
import { applyStoredTheme } from '../../../../lib/themeUpdater'
import { createHighlightMutation } from '../../../../lib/networking/mutations/createHighlightMutation'
import { deleteHighlightMutation } from '../../../../lib/networking/mutations/deleteHighlightMutation'
import { mergeHighlightMutation } from '../../../../lib/networking/mutations/mergeHighlightMutation'
import { updateHighlightMutation } from '../../../../lib/networking/mutations/updateHighlightMutation'
import { articleReadingProgressMutation } from '../../../../lib/networking/mutations/articleReadingProgressMutation'
type AppArticleEmbedContentProps = {
slug: string
@ -88,6 +93,13 @@ function AppArticleEmbedContent(
fontSize={props.fontSize}
margin={props.margin}
fontFamily={props.fontFamily}
articleMutations={{
createHighlightMutation,
deleteHighlightMutation,
mergeHighlightMutation,
updateHighlightMutation,
articleReadingProgressMutation,
}}
/>
</VStack>
</Box>

2263
yarn.lock

File diff suppressed because it is too large Load Diff