Merge pull request #316 from omnivore-app/fix/bound-highlight-responses

Bridging for GQL highlight responses
This commit is contained in:
Jackson Harper 2022-03-25 12:56:35 -07:00 committed by GitHub
commit 59da82ddc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 102 additions and 176 deletions

View file

@ -1454,7 +1454,7 @@
CODE_SIGN_ENTITLEMENTS = Entitlements/Omnivore.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QJF2XZ86HB;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = InfoPlists/Omnivore.plist;
@ -1463,7 +1463,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.4.2;
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
PRODUCT_NAME = Omnivore;
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1483,7 +1483,7 @@
CODE_SIGN_ENTITLEMENTS = "SafariExtension (iOS).entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QJF2XZ86HB;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "InfoPlists/SafariExtension-iOS.plist";
@ -1495,7 +1495,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.4.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
@ -1522,7 +1522,7 @@
CODE_SIGN_ENTITLEMENTS = "SafariExtension (iOS)Release.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QJF2XZ86HB;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "InfoPlists/SafariExtension-iOS.plist";
@ -1534,7 +1534,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.4.2;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
"-framework",
@ -1687,7 +1687,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Entitlements/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 62;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QJF2XZ86HB;
INFOPLIST_FILE = InfoPlists/ShareExtension.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -1696,7 +1696,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.4.2;
PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension";
PRODUCT_NAME = ShareExtension;
SDKROOT = iphoneos;
@ -1741,7 +1741,7 @@
CODE_SIGN_ENTITLEMENTS = Entitlements/Omnivore.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 62;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QJF2XZ86HB;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = InfoPlists/Omnivore.plist;
@ -1750,7 +1750,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.4.2;
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
PRODUCT_NAME = Omnivore;
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1769,7 +1769,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Entitlements/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 62;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QJF2XZ86HB;
INFOPLIST_FILE = InfoPlists/ShareExtension.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -1778,7 +1778,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.4.2;
PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension";
PRODUCT_NAME = ShareExtension;
SDKROOT = iphoneos;

View file

@ -56,7 +56,9 @@ struct WebReader: UIViewRepresentable {
webView.configuration.userContentController.add(webView, name: "viewerAction")
webView.configuration.userContentController.addScriptMessageHandler(context.coordinator, contentWorld: .page, name: "articleAction")
webView.configuration.userContentController.addScriptMessageHandler(
context.coordinator, contentWorld: .page, name: "articleAction"
)
context.coordinator.linkHandler = openLinkAction
context.coordinator.webViewActionHandler = webViewActionHandler

View file

@ -9,6 +9,14 @@ struct SafariWebLink: Identifiable {
let url: URL
}
func encodeHighlightResult(_ highlight: Highlight) -> [String: Any]? {
let data = try? JSONEncoder().encode(highlight)
if let data = data, let dictionary = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] {
return dictionary
}
return nil
}
final class WebReaderViewModel: ObservableObject {
@Published var isLoading = false
@Published var articleContent: ArticleContent?
@ -42,13 +50,18 @@ final class WebReaderViewModel: ObservableObject {
highlightID: messageBody["id"] as? String ?? "",
quote: messageBody["quote"] as? String ?? "",
patch: messageBody["patch"] as? String ?? "",
articleId: messageBody["articleId"] as? String ?? ""
articleId: messageBody["articleId"] as? String ?? "",
annotation: messageBody["annotation"] as? String ?? ""
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
replyHandler([], "createHighlight: Error encoding response")
} receiveValue: { highlight in
if let highlight = encodeHighlightResult(highlight) {
replyHandler(["result": highlight], nil)
} else {
replyHandler([], "createHighlight: Error encoding response")
}
}
.store(in: &subscriptions)
}
@ -85,9 +98,13 @@ final class WebReaderViewModel: ObservableObject {
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
replyHandler([], "mergeHighlight: Error encoding response")
} receiveValue: { highlight in
if let highlight = encodeHighlightResult(highlight) {
replyHandler(["result": highlight], nil)
} else {
replyHandler([], "mergeHighlight: Error encoding response")
}
}
.store(in: &subscriptions)
}
@ -104,9 +121,10 @@ final class WebReaderViewModel: ObservableObject {
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
replyHandler([], "updateHighlight: Error encoding response")
} receiveValue: { highlight in
// Update highlight JS code just expects the highlight ID back
replyHandler(["result": highlight.id], nil)
}
.store(in: &subscriptions)
}

View file

@ -11,16 +11,16 @@ public extension DataService {
patch: String,
articleId: String,
annotation: String? = nil
) -> AnyPublisher<String, BasicError> {
) -> AnyPublisher<Highlight, BasicError> {
enum MutationResult {
case saved(id: String)
case saved(highlight: Highlight)
case error(errorCode: Enums.CreateHighlightErrorCode)
}
let selection = Selection<MutationResult, Unions.CreateHighlightResult> {
try $0.on(
createHighlightSuccess: .init {
.saved(id: try $0.highlight(selection: Selection.Highlight { try $0.id() }))
.saved(highlight: try $0.highlight(selection: highlightSelection))
},
createHighlightError: .init { .error(errorCode: try $0.errorCodes().first ?? .badData) }
)
@ -53,8 +53,8 @@ public extension DataService {
}
switch payload.data {
case let .saved(id: id):
promise(.success(id))
case let .saved(highlight: highlight):
promise(.success(highlight))
case let .error(errorCode: errorCode):
promise(.failure(.message(messageText: errorCode.rawValue)))
}

View file

@ -12,16 +12,16 @@ public extension DataService {
patch: String,
articleId: String,
overlapHighlightIdList: [String]
) -> AnyPublisher<String, BasicError> {
) -> AnyPublisher<Highlight, BasicError> {
enum MutationResult {
case saved(id: String)
case saved(highlight: Highlight)
case error(errorCode: Enums.MergeHighlightErrorCode)
}
let selection = Selection<MutationResult, Unions.MergeHighlightResult> {
try $0.on(
mergeHighlightSuccess: .init {
.saved(id: try $0.highlight(selection: Selection.Highlight { try $0.id() }))
.saved(highlight: try $0.highlight(selection: highlightSelection))
},
mergeHighlightError: .init { .error(errorCode: try $0.errorCodes().first ?? .badData) }
)
@ -57,8 +57,8 @@ public extension DataService {
}
switch payload.data {
case let .saved(id: id):
promise(.success(id))
case let .saved(highlight: highlight):
promise(.success(highlight))
case let .error(errorCode: errorCode):
promise(.failure(.message(messageText: errorCode.rawValue)))
}

View file

@ -8,16 +8,16 @@ public extension DataService {
highlightID: String,
annotation: String?,
sharedAt: Date?
) -> AnyPublisher<String, BasicError> {
) -> AnyPublisher<Highlight, BasicError> {
enum MutationResult {
case saved(id: String)
case saved(highlight: Highlight)
case error(errorCode: Enums.UpdateHighlightErrorCode)
}
let selection = Selection<MutationResult, Unions.UpdateHighlightResult> {
try $0.on(
updateHighlightSuccess: .init {
.saved(id: try $0.highlight(selection: Selection.Highlight { try $0.id() }))
.saved(highlight: try $0.highlight(selection: highlightSelection))
},
updateHighlightError: .init { .error(errorCode: try $0.errorCodes().first ?? .badData) }
)
@ -47,8 +47,8 @@ public extension DataService {
}
switch payload.data {
case let .saved(id: id):
promise(.success(id))
case let .saved(highlight: highlight):
promise(.success(highlight))
case let .error(errorCode: errorCode):
promise(.failure(.message(messageText: errorCode.rawValue)))
}

View file

@ -11,19 +11,6 @@ public extension DataService {
case error(error: String)
}
let highlightSelection = Selection.Highlight {
Highlight(
id: try $0.id(),
shortId: try $0.shortId(),
quote: try $0.quote(),
prefix: try $0.prefix(),
suffix: try $0.suffix(),
patch: try $0.patch(),
annotation: try $0.annotation(),
createdByMe: try $0.createdByMe()
)
}
let articleSelection = Selection.Article {
ArticleContent(
htmlContent: try $0.content(),

View file

@ -0,0 +1,15 @@
import Models
import SwiftGraphQL
let highlightSelection = Selection.Highlight {
Highlight(
id: try $0.id(),
shortId: try $0.shortId(),
quote: try $0.quote(),
prefix: try $0.prefix(),
suffix: try $0.suffix(),
patch: try $0.patch(),
annotation: try $0.annotation(),
createdByMe: try $0.createdByMe()
)
}

File diff suppressed because one or more lines are too long

View file

@ -69,10 +69,10 @@ function LoadedContent(props: LoadedContentProps): JSX.Element {
return props.publicArticle.highlights.length - 1
}, [props.publicArticle.highlights])
const sharedBy = useMemo(() => {
if (moreHighlightsCount < 1) return undefined
return props.publicArticle.highlights[0].user
}, [moreHighlightsCount, props.publicArticle.highlights])
// const sharedBy = useMemo(() => {
// if (moreHighlightsCount < 1) return undefined
// return props.publicArticle.highlights[0].user
// }, [moreHighlightsCount, props.publicArticle.highlights])
const articleSite = useMemo(() => {
try {
@ -124,7 +124,7 @@ function LoadedContent(props: LoadedContentProps): JSX.Element {
site={articleSite}
/>
{!showAllHighlights && moreHighlightsCount > 0 && sharedBy && (
{!showAllHighlights && moreHighlightsCount > 0 && (
<Button onClick={() => setShowAllHighlights(true)}>
<Box
css={{ width: '8px', color: '$grayBackground', mr: '4px' }}
@ -132,7 +132,7 @@ function LoadedContent(props: LoadedContentProps): JSX.Element {
/>
Read {moreHighlightsCount} more highlight
{moreHighlightsCount > 1 ? 's ' : ' '}
from {sharedBy.name}
{/* from {sharedBy.name} */}
</Button>
)}

View file

@ -15,7 +15,6 @@ import { removeHighlights } from '../../../lib/highlights/deleteHighlight'
import { createHighlight } from '../../../lib/highlights/createHighlight'
import { HighlightNoteModal } from './HighlightNoteModal'
import { ShareHighlightModal } from './ShareHighlightModal'
import { HighlightPostToFeedModal } from './HighlightPostToFeedModal'
import { HighlightsModal } from './HighlightsModal'
import { useCanShareNative } from '../../../lib/hooks/useCanShareNative'
import { showErrorToast } from '../../../lib/toastHelpers'
@ -35,7 +34,7 @@ type HighlightsLayerProps = {
articleMutations: ArticleMutations
}
type HighlightModalAction = 'none' | 'addComment' | 'postToFeed' | 'share'
type HighlightModalAction = 'none' | 'addComment' | 'share'
type HighlightActionProps = {
highlight?: Highlight
@ -113,32 +112,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
[highlights, highlightLocations]
)
const postToFeedCallback = useCallback(
async (highlight: Highlight, annotation: string | undefined) => {
await shareHighlightToFeedMutation({
id: highlight.id,
share: highlight.sharedAt == undefined,
})
await shareHighlightCommentMutation({
highlightId: highlight.id,
annotation,
})
// Toggle the sharedAt field after mutating the highlight
const mutatedHighlight = highlight
mutatedHighlight.sharedAt = highlight.sharedAt
? undefined
: new Date().toISOString()
mutatedHighlight.annotation = annotation
const unmutatedHighlights = highlights.filter(
($0) => $0.id !== highlight.id
)
setHighlights([...unmutatedHighlights, mutatedHighlight])
},
[highlights]
)
const handleNativeShare = useCallback(
(highlightID: string) => {
navigator
@ -348,16 +321,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
createHighlightCallback('share')
}
break
case 'post':
if (focusedHighlight) {
setHighlightModalAction({
highlight: focusedHighlight,
highlightModalAction: 'postToFeed',
})
} else {
createHighlightCallback('postToFeed')
}
break
case 'unshare':
console.log('unshare')
break // TODO: implement -- need to show confirmation dialog
@ -460,23 +423,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
)
}
if (
highlightModalAction?.highlightModalAction == 'postToFeed' &&
highlightModalAction.highlight
) {
return (
<HighlightPostToFeedModal
highlight={highlightModalAction.highlight}
author={props.articleAuthor}
title={props.articleTitle}
onCommit={postToFeedCallback}
onOpenChange={() =>
setHighlightModalAction({ highlightModalAction: 'none' })
}
/>
)
}
if (
highlightModalAction?.highlightModalAction == 'share' &&
highlightModalAction.highlight

View file

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

View file

@ -89,12 +89,11 @@ export async function createHighlight(
let keptHighlights = input.existingHighlights
if (shouldMerge) {
const result = await articleMutations.mergeHighlightMutation({
highlight = await articleMutations.mergeHighlightMutation({
...newHighlightAttributes,
overlapHighlightIdList: input.selection.overlapHighlights,
})
highlight = result?.mergeHighlight.highlight
keptHighlights = input.existingHighlights.filter(
($0) => !input.selection.overlapHighlights.includes($0.id)
)

View file

@ -67,12 +67,16 @@ function nodeAttributesFromHighlight(
const patch = highlight.patch
const id = highlight.id
const withNote = !!highlight.annotation
const customColor = !highlight.createdByMe
? stringToColour(highlight.user.profile.username)
: undefined
const tooltip = !highlight.createdByMe
? `Created by: @${highlight.user.profile.username}`
: undefined
const customColor = undefined
const tooltip = undefined
// We've disabled shared highlights, so passing undefined
// here now, and removing the user object from highlights
// !highlight.createdByMe
// ? stringToColour(highlight.user.profile.username)
// : undefined
// const tooltip = !highlight.createdByMe
// ? `Created by: @${highlight.user.profile.username}`
// : undefined
return makeHighlightNodeAttributes(patch, id, withNote, customColor, tooltip)
}

View file

@ -9,19 +9,9 @@ export const highlightFragment = gql`
suffix
patch
annotation
createdAt
createdByMe
updatedAt
sharedAt
user {
id
name
profile {
id
pictureUrl
username
}
}
createdByMe
}
`
@ -33,11 +23,9 @@ export type Highlight = {
suffix?: string
patch: string
annotation?: string
createdAt: string
updatedAt: string
user: User
createdByMe: boolean
sharedAt?: string
updatedAt: string
sharedAt: string
}
export type User = {

View file

@ -1,6 +1,6 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { Highlight } from './../fragments/highlightFragment'
import { Highlight, highlightFragment } from './../fragments/highlightFragment'
export type CreateHighlightInput = {
prefix: string
@ -28,7 +28,7 @@ export async function createHighlightMutation(
createHighlight(input: $input) {
... on CreateHighlightSuccess {
highlight {
...NewHighlight
...HighlightFields
}
}
@ -37,7 +37,7 @@ export async function createHighlightMutation(
}
}
}
${NewHighlightFragment}
${highlightFragment}
`
try {
@ -48,28 +48,3 @@ export async function createHighlightMutation(
return undefined
}
}
const NewHighlightFragment = gql`
fragment NewHighlight on Highlight {
id
shortId
quote
prefix
suffix
patch
createdAt
updatedAt
annotation
sharedAt
user {
id
name
profile {
id
pictureUrl
username
}
}
createdByMe
}
`

View file

@ -25,7 +25,7 @@ type InnerMergeHighlightOutput = {
export async function mergeHighlightMutation(
input: MergeHighlightInput
): Promise<MergeHighlightOutput | undefined> {
): Promise<Highlight | undefined> {
const mutation = gql`
mutation MergeHighlight($input: MergeHighlightInput!) {
mergeHighlight(input: $input) {
@ -41,15 +41,6 @@ export async function mergeHighlightMutation(
updatedAt
annotation
sharedAt
user {
id
name
profile {
id
pictureUrl
username
}
}
createdByMe
}
overlapHighlightIdList
@ -63,7 +54,8 @@ export async function mergeHighlightMutation(
try {
const data = await gqlFetcher(mutation, { input })
return data as MergeHighlightOutput | undefined
const output = data as MergeHighlightOutput | undefined
return output?.mergeHighlight.highlight
} catch {
return undefined
}