Merge pull request #1932 from omnivore-app/use-url-in-save-request

use url in save request
This commit is contained in:
Jackson Harper
2023-03-20 18:16:23 +08:00
committed by GitHub
8 changed files with 210 additions and 119 deletions

View File

@ -1,25 +1,25 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import express from 'express'
import { CreateArticleErrorCode } from '../generated/graphql'
import { isSiteBlockedForParse } from '../utils/blocked'
import cors from 'cors'
import { buildLogger } from '../utils/logger'
import { corsConfig } from '../utils/corsConfig'
import { createPageSaveRequest } from '../services/create_page_save_request'
import { initModels } from '../server'
import { kx } from '../datalayer/knex_config'
import { getClaimsByToken } from '../utils/auth'
import * as jwt from 'jsonwebtoken'
import { env } from '../env'
import { Claims } from '../resolvers/types'
import { getRepository } from '../entity/utils'
import { Speech, SpeechState } from '../entity/speech'
import { getPageById, updatePage } from '../elastic/pages'
import { generateDownloadSignedUrl } from '../utils/uploads'
import { enqueueTextToSpeech } from '../utils/createTask'
import { createPubSubClient } from '../datalayer/pubsub'
import { htmlToSpeechFile } from '@omnivore/text-to-speech-handler'
import cors from 'cors'
import express from 'express'
import * as jwt from 'jsonwebtoken'
import { kx } from '../datalayer/knex_config'
import { createPubSubClient } from '../datalayer/pubsub'
import { getPageById, updatePage } from '../elastic/pages'
import { Speech, SpeechState } from '../entity/speech'
import { getRepository } from '../entity/utils'
import { env } from '../env'
import { CreateArticleErrorCode } from '../generated/graphql'
import { Claims } from '../resolvers/types'
import { initModels } from '../server'
import { createPageSaveRequest } from '../services/create_page_save_request'
import { getClaimsByToken } from '../utils/auth'
import { isSiteBlockedForParse } from '../utils/blocked'
import { corsConfig } from '../utils/corsConfig'
import { enqueueTextToSpeech } from '../utils/createTask'
import { buildLogger } from '../utils/logger'
import { generateDownloadSignedUrl } from '../utils/uploads'
interface SpeechInput {
voice?: string
@ -74,6 +74,7 @@ export function articleRouter() {
return res.send({
articleSavingRequestId: result.id,
url: result.url,
})
})

View File

@ -1,17 +1,17 @@
import {
ModalRoot,
ModalContent,
ModalOverlay,
ModalTitleBar,
ModalButtonBar,
} from '../../elements/ModalPrimitives'
import { VStack, Box } from '../../elements/LayoutPrimitives'
import { useCallback, useState } from 'react'
import toast from 'react-hot-toast'
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
import { showErrorToast } from '../../../lib/toastHelpers'
import { Button } from '../../elements/Button'
import { FormInput } from '../../elements/FormElements'
import { useState, useCallback } from 'react'
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
import toast from 'react-hot-toast'
import { showErrorToast } from '../../../lib/toastHelpers'
import { Box, VStack } from '../../elements/LayoutPrimitives'
import {
ModalButtonBar,
ModalContent,
ModalOverlay,
ModalRoot,
ModalTitleBar,
} from '../../elements/ModalPrimitives'
type AddLinkModalProps = {
onOpenChange: (open: boolean) => void
@ -23,9 +23,7 @@ export function AddLinkModal(props: AddLinkModalProps): JSX.Element {
const handleLinkSubmission = useCallback(
async (link: string) => {
const result = await saveUrlMutation(link)
// const result = await saveUrlMutation(link)
if (result && result.jobId) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
if (result) {
toast(
() => (
<Box>
@ -35,7 +33,9 @@ export function AddLinkModal(props: AddLinkModalProps): JSX.Element {
style="ctaDarkYellow"
autoFocus
onClick={() => {
window.location.href = `/article/sr/${result.jobId}`
window.location.href = `/article?url=${encodeURIComponent(
link
)}`
}}
>
Read Now

View File

@ -1,6 +1,28 @@
import { Box, HStack, VStack } from './../../elements/LayoutPrimitives'
import Dropzone from 'react-dropzone'
import * as Progress from '@radix-ui/react-progress'
import axios from 'axios'
import { Action, createAction, useKBar, useRegisterActions } from 'kbar'
import debounce from 'lodash/debounce'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import Dropzone from 'react-dropzone'
import { Toaster } from 'react-hot-toast'
import TopBarProgress from 'react-topbar-progress-indicator'
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import { libraryListCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import {
PageType,
State,
} from '../../../lib/networking/fragments/articleFragment'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
import { uploadFileRequestMutation } from '../../../lib/networking/mutations/uploadFileMutation'
import {
SearchItem,
TypeaheadSearchItemsData,
typeaheadSearchQuery,
} from '../../../lib/networking/queries/typeaheadSearch'
import type {
LibraryItem,
LibraryItemsQueryInput,
@ -10,42 +32,20 @@ import {
useGetViewerQuery,
UserBasicData,
} from '../../../lib/networking/queries/useGetViewerQuery'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
import { useRouter } from 'next/router'
import { Button } from '../../elements/Button'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { StyledText } from '../../elements/StyledText'
import { AddLinkModal } from './AddLinkModal'
import { styled, theme } from '../../tokens/stitches.config'
import { libraryListCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { Toaster } from 'react-hot-toast'
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { SetLabelsModal } from '../article/SetLabelsModal'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { EmptyLibrary } from './EmptyLibrary'
import TopBarProgress from 'react-topbar-progress-indicator'
import {
PageType,
State,
} from '../../../lib/networking/fragments/articleFragment'
import { Action, createAction, useKBar, useRegisterActions } from 'kbar'
import { Box, HStack, VStack } from './../../elements/LayoutPrimitives'
import { AddLinkModal } from './AddLinkModal'
import { EditLibraryItemModal } from './EditItemModals'
import debounce from 'lodash/debounce'
import {
SearchItem,
TypeaheadSearchItemsData,
typeaheadSearchQuery,
} from '../../../lib/networking/queries/typeaheadSearch'
import axios from 'axios'
import { uploadFileRequestMutation } from '../../../lib/networking/mutations/uploadFileMutation'
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
import { LibraryHeader } from './LibraryHeader'
import { LibraryFilterMenu } from './LibraryFilterMenu'
import { EmptyLibrary } from './EmptyLibrary'
import { HighlightItemsLayout } from './HighlightsLayout'
import { LibraryFilterMenu } from './LibraryFilterMenu'
import { LibraryHeader } from './LibraryHeader'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
export type LibraryMode = 'reads' | 'highlights'
@ -288,7 +288,7 @@ export function HomeFeedContainer(): JSX.Element {
if (username) {
setActiveCardId(item.node.id)
if (item.node.state === State.PROCESSING) {
router.push(`/${username}/links/${item.node.id}`)
router.push(`/article?url=${encodeURIComponent(item.node.url)}`)
} else {
const dl =
item.node.pageType === PageType.HIGHLIGHTS

View File

@ -6,7 +6,8 @@ import { makeGqlFetcher } from '../networkHelpers'
import { ArticleAttributes } from './useGetArticleQuery'
type ArticleSavingStatusInput = {
id: string
id?: string
url?: string
}
type ArticleSavingStatusResponse = {
@ -49,10 +50,11 @@ type ArticleSavingStatusError =
export function useGetArticleSavingStatus({
id,
url,
}: ArticleSavingStatusInput): ArticleSavingStatusResponse {
const query = gql`
query ArticleSavingRequest($id: ID!) {
articleSavingRequest(id: $id) {
query ArticleSavingRequest($id: ID, $url: String) {
articleSavingRequest(id: $id, url: $url) {
... on ArticleSavingRequestSuccess {
articleSavingRequest {
id
@ -83,9 +85,9 @@ export function useGetArticleSavingStatus({
${articleFragment}
${highlightFragment}
`
const key = id ? [query, id] : [query, url]
// poll twice a second
const { data, error } = useSWR([query, id], makeGqlFetcher({ id }), {
const { data, error } = useSWR(key, makeGqlFetcher({ id, url }), {
refreshInterval: 500,
})
@ -129,7 +131,7 @@ export function useGetArticleSavingStatus({
}
}
if (status === 'PROCESSING') {
if (status === 'PROCESSING' || status === 'DELETED') {
return {}
}

View File

@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import { v4 as uuidv4 } from 'uuid'
import { SaveResponseData } from '../../lib/networking/mutations/saveUrlMutation'
import { ssrFetcher } from '../../lib/networking/networkHelpers'
import { v4 as uuidv4 } from 'uuid'
const saveUrl = async (req: NextApiRequest, url: URL) => {
const clientRequestId = uuidv4()
@ -50,8 +50,8 @@ export default async (
const url = new URL(urlStr as string)
const saveResult = await saveUrl(req, url)
console.log('saveResult: ', saveResult)
if (saveResult?.jobId) {
res.redirect(`/sr/${saveResult?.jobId}`)
if (saveResult?.url) {
res.redirect(`?url=${encodeURIComponent(url.toString())}`)
return
}

View File

@ -0,0 +1,86 @@
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import TopBarProgress from 'react-topbar-progress-indicator'
import { VStack } from '../../components/elements/LayoutPrimitives'
import { ArticleActionsMenu } from '../../components/templates/article/ArticleActionsMenu'
import { SkeletonArticleContainer } from '../../components/templates/article/SkeletonArticleContainer'
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
import { Loader } from '../../components/templates/SavingRequest'
import { theme } from '../../components/tokens/stitches.config'
import { useReaderSettings } from '../../lib/hooks/useReaderSettings'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { PrimaryContent } from '../article/sr/[id]'
export default function ArticleSavingRequestPage(): JSX.Element {
const router = useRouter()
const readerSettings = useReaderSettings()
const [url, setUrl] = useState<string | undefined>(undefined)
applyStoredTheme(false)
useEffect(() => {
if (!router.isReady) return
setUrl(router.query.url as string)
}, [router.isReady, router.query.url])
return (
<PrimaryLayout
pageTestId="home-page-tag"
headerToolbarControl={
<ArticleActionsMenu
article={undefined}
layout="top"
showReaderDisplaySettings={true}
articleActionHandler={readerSettings.actionHandler}
/>
}
alwaysDisplayToolbar={false}
pageMetaDataProps={{
title: 'Saving link',
path: router.pathname,
}}
>
<TopBarProgress />
<VStack
distribution="between"
alignment="center"
css={{
position: 'fixed',
flexDirection: 'row-reverse',
top: '-120px',
left: 8,
height: '100%',
width: '35px',
'@lgDown': {
display: 'none',
},
}}
>
<ArticleActionsMenu
article={undefined}
layout="side"
showReaderDisplaySettings={true}
articleActionHandler={readerSettings.actionHandler}
/>
</VStack>
<VStack
alignment="center"
distribution="center"
className="disable-webkit-callout"
css={{
'@smDown': {
background: theme.colors.grayBg.toString(),
},
}}
>
<SkeletonArticleContainer
margin={readerSettings.marginWidth}
fontSize={readerSettings.fontSize}
lineHeight={readerSettings.lineHeight}
>
{url ? <PrimaryContent url={url} /> : <Loader />}
</SkeletonArticleContainer>
</VStack>
</PrimaryLayout>
)
}

View File

@ -1,18 +1,18 @@
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { useGetArticleSavingStatus } from '../../../lib/networking/queries/useGetArticleSavingStatus'
import TopBarProgress from 'react-topbar-progress-indicator'
import { VStack } from '../../../components/elements/LayoutPrimitives'
import { ArticleActionsMenu } from '../../../components/templates/article/ArticleActionsMenu'
import { SkeletonArticleContainer } from '../../../components/templates/article/SkeletonArticleContainer'
import { PrimaryLayout } from '../../../components/templates/PrimaryLayout'
import {
Loader,
ErrorComponent,
Loader,
} from '../../../components/templates/SavingRequest'
import { ArticleActionsMenu } from '../../../components/templates/article/ArticleActionsMenu'
import { VStack } from '../../../components/elements/LayoutPrimitives'
import { theme } from '../../../components/tokens/stitches.config'
import { applyStoredTheme } from '../../../lib/themeUpdater'
import { useReaderSettings } from '../../../lib/hooks/useReaderSettings'
import { SkeletonArticleContainer } from '../../../components/templates/article/SkeletonArticleContainer'
import TopBarProgress from 'react-topbar-progress-indicator'
import { useGetArticleSavingStatus } from '../../../lib/networking/queries/useGetArticleSavingStatus'
import { applyStoredTheme } from '../../../lib/themeUpdater'
export default function ArticleSavingRequestPage(): JSX.Element {
const router = useRouter()
@ -32,7 +32,7 @@ export default function ArticleSavingRequestPage(): JSX.Element {
headerToolbarControl={
<ArticleActionsMenu
article={undefined}
layout='top'
layout="top"
showReaderDisplaySettings={true}
articleActionHandler={readerSettings.actionHandler}
/>
@ -44,52 +44,56 @@ export default function ArticleSavingRequestPage(): JSX.Element {
}}
>
<TopBarProgress />
<VStack distribution="between" alignment="center" css={{
position: 'fixed',
flexDirection: 'row-reverse',
top: '-120px',
left: 8,
height: '100%',
width: '35px',
'@lgDown': {
display: 'none',
},
<VStack
distribution="between"
alignment="center"
css={{
position: 'fixed',
flexDirection: 'row-reverse',
top: '-120px',
left: 8,
height: '100%',
width: '35px',
'@lgDown': {
display: 'none',
},
}}
>
<ArticleActionsMenu
article={undefined}
layout='side'
layout="side"
showReaderDisplaySettings={true}
articleActionHandler={readerSettings.actionHandler}
/>
</VStack>
<VStack
alignment="center"
distribution="center"
className="disable-webkit-callout"
css={{
'@smDown': {
background: theme.colors.grayBg.toString(),
}
}}
<VStack
alignment="center"
distribution="center"
className="disable-webkit-callout"
css={{
'@smDown': {
background: theme.colors.grayBg.toString(),
},
}}
>
<SkeletonArticleContainer
margin={readerSettings.marginWidth}
fontSize={readerSettings.fontSize}
lineHeight={readerSettings.lineHeight}
>
<SkeletonArticleContainer
margin={readerSettings.marginWidth}
fontSize={readerSettings.fontSize}
lineHeight={readerSettings.lineHeight}
>
{articleId ? <PrimaryContent articleId={articleId} /> : <Loader />}
</SkeletonArticleContainer>
{articleId ? <PrimaryContent articleId={articleId} /> : <Loader />}
</SkeletonArticleContainer>
</VStack>
</PrimaryLayout>
)
}
type PrimaryContentProps = {
articleId: string
articleId?: string
url?: string
}
function PrimaryContent(props: PrimaryContentProps): JSX.Element {
export function PrimaryContent(props: PrimaryContentProps): JSX.Element {
const router = useRouter()
const [timedOut, setTimedOut] = useState(false)
@ -121,7 +125,5 @@ function PrimaryContent(props: PrimaryContentProps): JSX.Element {
router.replace(successRedirectPath)
}
return (
<Loader />
)
return <Loader />
}

View File

@ -93,8 +93,8 @@
}).then(function (response) {
if (response.status === 200) {
return response.json().then((responseJson) => {
const savingRequestId = responseJson.articleSavingRequestId;
return currentOrigin + '/article/sr/' + savingRequestId;
const url = encodeURIComponent(responseJson.url);
return currentOrigin + '/article?url=' + url;
});
}