diff --git a/src/content-scripts/content_script/global.ts b/src/content-scripts/content_script/global.ts index d88dd43ea5..82af122ac5 100644 --- a/src/content-scripts/content_script/global.ts +++ b/src/content-scripts/content_script/global.ts @@ -259,6 +259,7 @@ export async function main( RemoteContentSharingByTabsInterface<'caller'> >() const searchBG = runInBackground() + const pdfViewerBG = runInBackground() const contentScriptsBG = runInBackground< ContentScriptsInterface<'caller'> >() @@ -1081,6 +1082,16 @@ export async function main( openPDFinViewer: async (originalPageURL) => { let urlToOpen = originalPageURL + if ( + urlToOpen.includes('memex.cloud') && + urlToOpen.includes('upload_id') + ) { + const url = new URL(urlToOpen) + const uploadId = url.searchParams.get('upload_id') + urlToOpen = await pdfViewerBG.getTempPdfAccessUrl( + uploadId, + ) + } if ( urlToOpen.includes('https://arxiv.org/pdf/') && !urlToOpen.includes('.pdf') @@ -1091,7 +1102,9 @@ export async function main( await contentScriptsBG.openPdfInViewer({ fullPageUrl: urlToOpen, }) + return true }, + events: sidebarEvents, browserAPIs: browser, }) @@ -1221,6 +1234,32 @@ export async function main( services: createUIServices(), renderUpdateNotifBanner: () => null, bgScriptBG, + openPDFinViewer: async (originalPageURL) => { + let urlToOpen = originalPageURL + + if ( + urlToOpen.includes('memex.cloud') && + urlToOpen.includes('upload_id') + ) { + const url = new URL(urlToOpen) + const uploadId = url.searchParams.get('upload_id') + console.log('uploadId', uploadId) + urlToOpen = await pdfViewerBG.getTempPdfAccessUrl( + uploadId, + ) + console.log('urlToOpen', urlToOpen) + } + if ( + urlToOpen.includes('https://arxiv.org/pdf/') && + !urlToOpen.includes('.pdf') + ) { + urlToOpen = urlToOpen.concat('.pdf') + } + + await contentScriptsBG.openPdfInViewer({ + fullPageUrl: urlToOpen, + }) + }, }, upgradeModalProps: { createCheckOutLink: bgScriptBG.createCheckoutLink, diff --git a/src/content-scripts/content_script/in-page-ui-injections.ts b/src/content-scripts/content_script/in-page-ui-injections.ts index b03c3e77b5..77a9cb91f0 100644 --- a/src/content-scripts/content_script/in-page-ui-injections.ts +++ b/src/content-scripts/content_script/in-page-ui-injections.ts @@ -82,6 +82,8 @@ export const main: InPageUIInjectionsMain = async ({ query: 'settings', }, ), + searchDisplayProps.searchBG, + searchDisplayProps.openPDFinViewer, ) } catch (err) { console.error(err) diff --git a/src/dashboard-refactor/index.tsx b/src/dashboard-refactor/index.tsx index 5d04549005..5f04d989b6 100644 --- a/src/dashboard-refactor/index.tsx +++ b/src/dashboard-refactor/index.tsx @@ -867,7 +867,14 @@ export class DashboardContainer extends StatefulUIElement< heightAndWidth={'40px'} hoverOff /> - Drop PDF here to open it + + Download, then drop the PDF here + + + In rare cases the PDF file is not reachable + directly. In this case, you can drag and drop the + PDF file here. + @@ -2375,6 +2382,12 @@ const DropZoneTitle = styled.div` text-align: center; ` +const DropZoneSubTitle = styled.div` + color: ${(props) => props.theme.colors.greyScale6}; + font-size: 18px; + text-align: center; +` + const MainContent = styled.div<{ inPageMode: boolean }>` diff --git a/src/dashboard-refactor/types.ts b/src/dashboard-refactor/types.ts index aad7d9fd79..6048869c63 100644 --- a/src/dashboard-refactor/types.ts +++ b/src/dashboard-refactor/types.ts @@ -133,6 +133,7 @@ export type DashboardDependencies = { openSettings?: () => void bgScriptBG?: RemoteBGScriptInterface<'caller'> getPortalElement?: () => HTMLElement + openPDFinViewer?: (url: string) => Promise } & ( | { inPageMode: true diff --git a/src/in-page-ui/ribbon/react/containers/ribbon/types.ts b/src/in-page-ui/ribbon/react/containers/ribbon/types.ts index 874865b9b5..22f904f7a0 100644 --- a/src/in-page-ui/ribbon/react/containers/ribbon/types.ts +++ b/src/in-page-ui/ribbon/react/containers/ribbon/types.ts @@ -45,7 +45,7 @@ export interface RibbonContainerDependencies { > currentUser?: UserReference getRootElement: () => HTMLElement - openPDFinViewer: (url: string) => Promise + openPDFinViewer: (url: string) => Promise events: AnnotationsSidebarInPageEventEmitter browserAPIs: Browser } diff --git a/src/search-injection/components/ResultItem.js b/src/search-injection/components/ResultItem.js index 7500b0c385..85d342b2c4 100644 --- a/src/search-injection/components/ResultItem.js +++ b/src/search-injection/components/ResultItem.js @@ -2,61 +2,70 @@ import React from 'react' import PropTypes from 'prop-types' import niceTime from 'src/util/nice-time' import styled from 'styled-components' - -const showTags = (tags) => { - return tags.map((tag, i) => {tag}) -} - -const ResultItem = (props) => ( - - +import { normalizeUrl } from '@worldbrain/memex-common/lib/url-utils/normalize' +import LoadingIndicator from '@worldbrain/memex-common/lib/common-ui/components/loading-indicator' + +const ResultItem = (props) => { + return ( + + {props.isLoadingPDFReader && ( + + + Opening PDF + + )} - - { - props.url - .split('://')[1] - .replace('www.', '') - .split('/')[0] - } - + {normalizeUrl(props.url).split('/')[0]} {niceTime(props.displayTime)} {props.title} - {props.tags.length > 0 && ( - {showTags(props.tags)} - )} - - -) + + ) +} -const Root = styled.a` +const RootContainer = styled.div` + height: fit-content; + width: fill-available; + border-bottom: 1px solid ${(props) => props.theme.colors.greyScale3}; + background: ${(props) => props.theme.colors.black}; padding: 20px 20px; text-decoration: none !important; display: flex; + position: relative; border-bottom: 1px solid ${(props) => props.theme.colors.greyScale3}; &:last-child { border-bottom: none; } -` - -const RootContainer = styled.div` - height: fit-content; - width: fill-available; - border-bottom: 1px solid ${(props) => props.theme.colors.greyScale3}; - background: ${(props) => props.theme.colors.black}; &:last-child { border-bottom: none; } - &:hover ${Root} { + &:hover { background: ${(props) => props.theme.colors.greyScale2}; + cursor: pointer; } ` +const LoadingBlocker = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: ${(props) => props.theme.colors.black}98; + backdrop-filter: blur(5px); + display: flex; + justify-content: center; + align-items: center; + color: white; + font-size: 14px; + grid-gap: 10px; +` + const InfoContainer = styled.div` display: flex; justify-content: center; @@ -65,26 +74,6 @@ const InfoContainer = styled.div` width: fill-available; grid-gap: 8px; ` - -const TagItem = styled.div` - padding: 2px 8px; - border-radius: 3px; - background-color: ${(props) => props.theme.colors.greyScale3}; - color: white; - display: flex; - align-items: center; - justify-content: center; - height: 20px; - font-size: 12px; - font-weight: 400; -` - -const TagBox = styled.div` - margin-top: 5px; - display: flex; - grid-gap: 5px; -` - const Title = styled.div` font-size: 15px; color: ${(props) => props.theme.colors.white}; @@ -115,8 +104,7 @@ ResultItem.propTypes = { displayTime: PropTypes.number.isRequired, url: PropTypes.string.isRequired, title: PropTypes.string.isRequired, - tags: PropTypes.array.isRequired, - onLinkClick: PropTypes.func.isRequired, + isLoadingPDFReader: PropTypes.bool, } export default ResultItem diff --git a/src/search-injection/components/Results.tsx b/src/search-injection/components/Results.tsx index 8c62bf8725..6114a3f32c 100644 --- a/src/search-injection/components/Results.tsx +++ b/src/search-injection/components/Results.tsx @@ -29,6 +29,7 @@ interface ResultsProps { updateQuery: (query: string) => Promise query: string openSettings: () => void + openPDFinViewer: (url: string) => Promise } interface ResultsState { diff --git a/src/search-injection/components/container.tsx b/src/search-injection/components/container.tsx index 0cd98a50fe..1d6e35531b 100644 --- a/src/search-injection/components/container.tsx +++ b/src/search-injection/components/container.tsx @@ -24,6 +24,7 @@ import LoadingIndicator from '@worldbrain/memex-common/lib/common-ui/components/ import Icon from '@worldbrain/memex-common/lib/common-ui/components/icon' import IconBox from '@worldbrain/memex-common/lib/common-ui/components/icon-box' import { UITaskState } from '@worldbrain/memex-common/lib/main-ui/types' +import SearchBackground from 'src/search/background' const search = browser.runtime.getURL('/img/search.svg') @@ -38,6 +39,8 @@ export interface Props { updateQuery: (query: string) => Promise query: string openSettings: () => void + searchBG: SearchBackground + openPDFinViewer: (url: string) => Promise } interface State { @@ -49,6 +52,7 @@ interface State { isNotif: boolean position: null | 'side' | 'above' notification: any + isLoadingPDFReader?: string isStickyContainer: boolean } @@ -85,6 +89,7 @@ class Container extends React.Component { isNotif: true, notification: {}, isStickyContainer: true, + isLoadingPDFReader: null, } async componentDidMount() { @@ -171,7 +176,16 @@ class Container extends React.Component { } } - handleResultLinkClick = () => {} + handleResultLinkClick = async (url: string) => { + if (url.includes('memex.cloud') || url.includes('pdf')) { + this.setState({ isLoadingPDFReader: url }) + + await this.props.openPDFinViewer(url) + this.setState({ isLoadingPDFReader: null }) + } else { + this.handleClickOpenNewTabButton(url) + } + } renderResultItems() { if (this.props.searchResDocs == null) { @@ -182,18 +196,21 @@ class Container extends React.Component { ) } else if (this.props.searchResDocs.length > 0) { const resultItems = this.props.searchResDocs.map((result, i) => ( - <> + this.handleResultLinkClick(result.url)} + > - + )) return resultItems @@ -400,7 +417,7 @@ class Container extends React.Component { { updateQuery={this.props.updateQuery} query={this.props.query} openSettings={this.props.openSettings} + openPDFinViewer={this.props.openPDFinViewer} /> ) } } +const ClickItem = styled.div`` + const SearchLink = styled.span` padding-left: 2px; cursor: pointer; diff --git a/src/search-injection/pdf-open-button.tsx b/src/search-injection/pdf-open-button.tsx index e2390ffda0..4ae3e2ce7b 100644 --- a/src/search-injection/pdf-open-button.tsx +++ b/src/search-injection/pdf-open-button.tsx @@ -112,16 +112,6 @@ export const handleRenderPDFOpenButton = async ( let pdfOriginalUrl = null let buttonBarHeight = '24px' - if ( - window.location.href.includes('https://arxiv.org/pdf/') && - !window.location.href.includes('.pdf') - ) { - pdfOriginalUrl = window.location.href + '.pdf' - } - if (window.location.href.includes('.pdf')) { - pdfOriginalUrl = window.location.href - } - const target = document.createElement('div') target.setAttribute('id', constants.REACT_ROOTS.pdfOpenButtons) @@ -152,12 +142,18 @@ export const handleRenderPDFOpenButton = async ( } if (element.src && element.src.includes('.pdf')) { pdfOriginalUrl = element.src + } else if ( + window.location.href.includes('https://arxiv.org/pdf/') && + !window.location.href.includes('.pdf') + ) { + pdfOriginalUrl = window.location.href + '.pdf' + } else if (window.location.href.includes('.pdf')) { + pdfOriginalUrl = window.location.href + } else { + pdfOriginalUrl = null } - if (pdfOriginalUrl == null) { - return - } - + console.log('pdfOriginalUrl', pdfOriginalUrl) ReactDOM.render( position: 'side' | 'above' openSettings: () => void + searchBG: SearchBackground + openPDFinViewer: (url: string) => Promise } interface RootState { @@ -43,7 +48,7 @@ interface RootState { class Root extends React.Component { state: RootState = { - searchResDocsProcessed: [], + searchResDocsProcessed: null, } async componentDidMount() { @@ -70,15 +75,38 @@ class Root extends React.Component { matchHighlights: true, matchPageTitleUrl: true, }) - this.setState({ - searchResDocsProcessed: searchRes.docs.map((d) => ({ + + const contentIdentifier = async (url) => { + if (url.includes('memex.cloud/')) { + const urlData = await this.props.searchBG.resolvePdfPageFullUrls( + url, + ) + const originalURL = urlData?.originalLocation ?? url + return originalURL + } else { + return url + } + } + + const searchResDocsProcessedPromises = searchRes.docs.map( + async (d) => ({ searchEngine: this.props.searchEngine, displayTime: d.displayTime, title: d.fullTitle ?? d.fullUrl, - url: d.fullUrl, + url: (await contentIdentifier(d.fullUrl)).toString(), onLinkClick: () => null, // Gets filled in later - tags: [], - })), + isPDF: isMemexPageAPdf({ url: d.fullUrl }), + }), + ) + + const searchResDocsProcessed = await Promise.all( + searchResDocsProcessedPromises, + ) + + console.log('searchResDocsProcessed', searchResDocsProcessed) + + this.setState({ + searchResDocsProcessed: searchResDocsProcessed, }) } @@ -109,6 +137,8 @@ class Root extends React.Component { updateQuery={this.updateQuery} query={props.query} openSettings={props.openSettings} + searchBG={this.props.searchBG} + openPDFinViewer={props.openPDFinViewer} /> @@ -120,7 +150,6 @@ class Root extends React.Component { export const handleRenderSearchInjection = async ( query, requestSearcher, - //{ docs, totalCount }, searchEngine, syncSettings: SyncSettingsStore< | 'extension' @@ -131,8 +160,10 @@ export const handleRenderSearchInjection = async ( | 'dashboard' >, openSettings, + searchBG, + openPDFinViewer, ) => { - // docs: (array of objects) returned by the search + // docs: (array of objects) returned by the search. // totalCount: (int) number of results found // Injects CSS into the search page. // Calls renderComponent to render the react component @@ -347,6 +378,8 @@ export const handleRenderSearchInjection = async ( renderComponent={renderComponent} requestSearcher={requestSearcher} openSettings={openSettings} + searchBG={searchBG} + openPDFinViewer={openPDFinViewer} />, root, ) diff --git a/src/search-injection/types.ts b/src/search-injection/types.ts index 6c0bfb7920..3d2fdb8a26 100644 --- a/src/search-injection/types.ts +++ b/src/search-injection/types.ts @@ -2,6 +2,8 @@ import { PowerUpModalVersion } from 'src/authentication/upgrade-modal/types' import type { ErrorDisplayProps } from './error-display' import { AuthRemoteFunctionsInterface } from 'src/authentication/background/types' import { ContentScriptsInterface } from 'src/content-scripts/background/types' +import { ContentIdentifier } from 'src/search/types' +import SearchBackground from 'src/search/background' export type SearchEngineName = 'google' | 'duckduckgo' | 'brave' | 'bing' export interface SearchEngineInfo { @@ -23,7 +25,6 @@ export interface ResultItemProps { title: string displayTime: number searchEngine: SearchEngineName - tags: [] onLinkClick: React.MouseEventHandler }