diff --git a/packages/api/src/routers/article_router.ts b/packages/api/src/routers/article_router.ts index f20a4d29a..d3543ab0f 100644 --- a/packages/api/src/routers/article_router.ts +++ b/packages/api/src/routers/article_router.ts @@ -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, }) }) diff --git a/packages/web/components/templates/homeFeed/AddLinkModal.tsx b/packages/web/components/templates/homeFeed/AddLinkModal.tsx index de17c92d7..282033cd6 100644 --- a/packages/web/components/templates/homeFeed/AddLinkModal.tsx +++ b/packages/web/components/templates/homeFeed/AddLinkModal.tsx @@ -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( () => ( @@ -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 diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index c4b0134f6..70d39ba04 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -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 diff --git a/packages/web/lib/networking/queries/useGetArticleSavingStatus.tsx b/packages/web/lib/networking/queries/useGetArticleSavingStatus.tsx index ff170293c..9ab686311 100644 --- a/packages/web/lib/networking/queries/useGetArticleSavingStatus.tsx +++ b/packages/web/lib/networking/queries/useGetArticleSavingStatus.tsx @@ -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 {} } diff --git a/packages/web/pages/api/save.ts b/packages/web/pages/api/save.ts index 71d4735d9..a43e6ccb7 100644 --- a/packages/web/pages/api/save.ts +++ b/packages/web/pages/api/save.ts @@ -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 } diff --git a/packages/web/pages/article/index.tsx b/packages/web/pages/article/index.tsx new file mode 100644 index 000000000..52f96e887 --- /dev/null +++ b/packages/web/pages/article/index.tsx @@ -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(undefined) + + applyStoredTheme(false) + + useEffect(() => { + if (!router.isReady) return + setUrl(router.query.url as string) + }, [router.isReady, router.query.url]) + + return ( + + } + alwaysDisplayToolbar={false} + pageMetaDataProps={{ + title: 'Saving link', + path: router.pathname, + }} + > + + + + + + + {url ? : } + + + + ) +} diff --git a/packages/web/pages/article/sr/[id].tsx b/packages/web/pages/article/sr/[id].tsx index 90ef594c8..ce256a270 100644 --- a/packages/web/pages/article/sr/[id].tsx +++ b/packages/web/pages/article/sr/[id].tsx @@ -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={ @@ -44,52 +44,56 @@ export default function ArticleSavingRequestPage(): JSX.Element { }} > - - + - - {articleId ? : } - + {articleId ? : } + ) } 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 ( - - ) + return } diff --git a/packages/web/public/sw.js b/packages/web/public/sw.js index 11aa1bc31..ceed2950c 100644 --- a/packages/web/public/sw.js +++ b/packages/web/public/sw.js @@ -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; }); }