mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Write a PDF.js wrapper to replace pspdfkit
This commit is contained in:
parent
eab1c2a47d
commit
93d05c14e5
186 changed files with 1821 additions and 2984 deletions
|
|
@ -21,6 +21,7 @@
|
|||
"ignorePatterns": ["next.config.js", "jest.config.js"],
|
||||
"rules": {
|
||||
"functional/no-mixed-type": 0,
|
||||
"react/react-in-jsx-scope": 0
|
||||
"react/react-in-jsx-scope": 0,
|
||||
"@typescript-eslint/ban-ts-comment" : 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Note this docker file is meant for local testing
|
||||
# and not for production.
|
||||
|
||||
FROM node:18.16-alpine as builder
|
||||
FROM node:22.12-alpine as builder
|
||||
ENV NODE_OPTIONS=--max-old-space-size=8192
|
||||
ARG APP_ENV
|
||||
ARG BASE_URL
|
||||
|
|
@ -12,7 +12,7 @@ ENV NEXT_PUBLIC_BASE_URL=$BASE_URL
|
|||
ENV NEXT_PUBLIC_SERVER_BASE_URL=$SERVER_BASE_URL
|
||||
ENV NEXT_PUBLIC_HIGHLIGHTS_BASE_URL=$HIGHLIGHTS_BASE_URL
|
||||
|
||||
RUN apk add g++ make python3
|
||||
RUN apk add g++ make python3 py3-setuptools
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ RUN echo "module.exports = {}" > ./packages/web/next.config.js
|
|||
RUN yarn workspace @omnivore/web build
|
||||
|
||||
|
||||
FROM node:18.16-alpine as runner
|
||||
FROM node:22.12-alpine as builder
|
||||
LABEL org.opencontainers.image.source="https://github.com/omnivore-app/omnivore"
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Note this docker file is meant for local testing
|
||||
# and not for production.
|
||||
|
||||
FROM node:18.16-alpine as builder
|
||||
FROM node:22.12-alpine as builder
|
||||
ENV NODE_OPTIONS=--max-old-space-size=8192
|
||||
ARG APP_ENV
|
||||
ARG BASE_URL
|
||||
|
|
@ -12,7 +12,7 @@ ENV NEXT_PUBLIC_BASE_URL=$BASE_URL
|
|||
ENV NEXT_PUBLIC_SERVER_BASE_URL=$SERVER_BASE_URL
|
||||
ENV NEXT_PUBLIC_HIGHLIGHTS_BASE_URL=$HIGHLIGHTS_BASE_URL
|
||||
|
||||
RUN apk add g++ make python3
|
||||
RUN apk add g++ make python3 py3-setuptools
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ COPY ./packages/web/next.config.self.js ./packages/web/next.config.js
|
|||
RUN yarn workspace @omnivore/web build
|
||||
|
||||
|
||||
FROM node:18.16-alpine as runner
|
||||
FROM node:22.12-alpine as runner
|
||||
LABEL org.opencontainers.image.source="https://github.com/omnivore-app/omnivore"
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export function HighlightBar(props: HighlightBarProps): JSX.Element {
|
|||
borderRadius: '5px',
|
||||
border: '1px solid $thHighlightBar',
|
||||
boxShadow: `0px 4px 4px 0px rgba(0, 0, 0, 0.15)`,
|
||||
|
||||
zIndex: 999,
|
||||
...(props.displayAtBottom && {
|
||||
bottom: 'calc(38px + env(safe-area-inset-bottom, 40px))',
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,280 @@
|
|||
import {
|
||||
ArticleAttributes,
|
||||
ArticleReadingProgressMutationInput,
|
||||
useUpdateItemReadStatus,
|
||||
} from '../../../../lib/networking/library_items/useLibraryItems'
|
||||
import { HStack, VStack } from '../../../elements/LayoutPrimitives'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { UserBasicData } from '../../../../lib/networking/queries/useGetViewerQuery'
|
||||
import 'react-sliding-pane/dist/react-sliding-pane.css'
|
||||
import {
|
||||
CreateHighlightInput,
|
||||
useCreateHighlight,
|
||||
useDeleteHighlight,
|
||||
useMergeHighlight,
|
||||
useUpdateHighlight,
|
||||
} from '../../../../lib/networking/highlights/useItemHighlights'
|
||||
import 'pdfjs-dist/web/pdf_viewer.css'
|
||||
import { EventBus, PDFViewer } from 'pdfjs-dist/types/web/pdf_viewer'
|
||||
import PdfViewer from './PdfViewer'
|
||||
import PdfToolbar from './PdfToolbar'
|
||||
import PdfSearchBar from './PdfSearchBar'
|
||||
import { NotebookHeader } from '../NotebookHeader'
|
||||
import { NotebookContent } from '../Notebook'
|
||||
import { ResizableSidebar } from '../ResizableSidebar'
|
||||
import { PDFDocumentProxy } from 'pdfjs-dist'
|
||||
import { PDFLinkService } from 'pdfjs-dist/types/web/pdf_link_service'
|
||||
import PdfSideBar from './PdfSideBar'
|
||||
|
||||
export type PdfArticleContainerProps = {
|
||||
viewer: UserBasicData
|
||||
article: ArticleAttributes
|
||||
showHighlightsModal: boolean
|
||||
setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
export default function PdfArticleContainer(props: PdfArticleContainerProps) {
|
||||
// @ts-ignore
|
||||
const pdfJS = import('pdfjs-dist/build/pdf.min.mjs')
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
const [pdfViewer, setPdfViewer] = useState<PDFViewer | null>(null)
|
||||
const [eventBus, setEventBus] = useState<EventBus | null>(null)
|
||||
const [pageNumber, setPageNumber] = useState<number>(1)
|
||||
const [pageCount, setTotalPageCount] = useState<number>(0)
|
||||
const [showSearch, setShowSearch] = useState(false)
|
||||
const [showToolbar, setShowToolbar] = useState(true)
|
||||
const [sidebarActive, setSidebarActive] = useState<boolean>(false)
|
||||
|
||||
const createHighlight = useCreateHighlight()
|
||||
const deleteHighlight = useDeleteHighlight()
|
||||
const mergeHighlight = useMergeHighlight()
|
||||
const updateHighlight = useUpdateHighlight()
|
||||
const updateItemReadStatus = useUpdateItemReadStatus()
|
||||
|
||||
const createPdfViewer = async (): Promise<PDFViewer> => {
|
||||
const pdfJSLib = await pdfJS
|
||||
const pdfjsViewer = await import('pdfjs-dist/web/pdf_viewer.mjs')
|
||||
|
||||
pdfJSLib.GlobalWorkerOptions.workerSrc =
|
||||
window.location.origin + '/pdfjs-dist/build/pdf.worker.min.mjs'
|
||||
|
||||
const eventBus = new pdfjsViewer.EventBus()
|
||||
const pdfLinkService = new pdfjsViewer.PDFLinkService({
|
||||
eventBus,
|
||||
})
|
||||
const pdfFindController = new pdfjsViewer.PDFFindController({
|
||||
eventBus,
|
||||
linkService: pdfLinkService,
|
||||
})
|
||||
|
||||
const pdfScriptingManager = new pdfjsViewer.PDFScriptingManager({
|
||||
eventBus,
|
||||
sandboxBundleSrc:
|
||||
window.location.origin + '/pdfjs-dist/build/pdf.sandbox.mjs',
|
||||
})
|
||||
|
||||
const pdfViewer = new pdfjsViewer.PDFViewer({
|
||||
container: containerRef.current!,
|
||||
eventBus,
|
||||
linkService: pdfLinkService,
|
||||
findController: pdfFindController,
|
||||
scriptingManager: pdfScriptingManager,
|
||||
})
|
||||
pdfScriptingManager.setViewer(pdfViewer)
|
||||
pdfLinkService.pdfViewer = pdfViewer
|
||||
|
||||
return pdfViewer
|
||||
}
|
||||
|
||||
const loadPdfDocument = async (): Promise<PDFDocumentProxy> => {
|
||||
const pdfJsLib = await pdfJS
|
||||
const loadingTask = pdfJsLib.getDocument({
|
||||
url: props.article.url,
|
||||
cMapUrl: window.location.origin + '/pdfjs-dist/cmaps/',
|
||||
cMapPacked: true,
|
||||
enableXfa: true,
|
||||
})
|
||||
|
||||
return loadingTask.promise
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Uses the existing mechanism to hide the reader toolbar from pspdfkit
|
||||
document.addEventListener('pdfReaderUpdateSettings', () => {
|
||||
const show = localStorage.getItem('reader-show-pdf-tool-bar')
|
||||
const showBar = show ? JSON.parse(show) == true : false
|
||||
setShowToolbar(showBar)
|
||||
})
|
||||
;(async () => {
|
||||
const pdfViewer = await createPdfViewer()
|
||||
const pdfDocument = await loadPdfDocument()
|
||||
|
||||
setTotalPageCount(pdfDocument.numPages)
|
||||
pdfViewer.setDocument(pdfDocument)
|
||||
|
||||
const linkService = pdfViewer.linkService as PDFLinkService
|
||||
linkService.setDocument(pdfDocument, null)
|
||||
|
||||
// Doesn't seem to get applied straight away, causing an issue where the scale would
|
||||
// be set to 0. We do a 200 ms timeout to avoid this bug.
|
||||
setTimeout(() => {
|
||||
pdfViewer.currentScale = 1
|
||||
pdfViewer.scrollPageIntoView({
|
||||
pageNumber: props.article.readingProgressAnchorIndex,
|
||||
})
|
||||
}, 200)
|
||||
|
||||
setPdfViewer(pdfViewer)
|
||||
setEventBus(pdfViewer.eventBus)
|
||||
|
||||
pdfViewer.eventBus.on(
|
||||
'pagechanging',
|
||||
(e: { previous: number; pageNumber: number }) => {
|
||||
console.log('Page Changing....')
|
||||
setPageNumber(e.pageNumber)
|
||||
}
|
||||
)
|
||||
})()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<VStack css={{ width: '100%' }}>
|
||||
{showSearch && <PdfSearchBar pdfViewer={pdfViewer} eventBus={eventBus} />}
|
||||
{showToolbar && (
|
||||
<PdfToolbar
|
||||
setShowSidebar={setSidebarActive}
|
||||
sidebarActive={sidebarActive}
|
||||
viewer={props.viewer}
|
||||
article={props.article}
|
||||
pdfViewer={pdfViewer}
|
||||
eventBus={eventBus}
|
||||
pageNumber={pageNumber}
|
||||
setPageNumber={setPageNumber}
|
||||
totalPageNumbers={pageCount}
|
||||
showSearch={showSearch}
|
||||
setShowSearch={setShowSearch}
|
||||
/>
|
||||
)}
|
||||
|
||||
<HStack>
|
||||
<PdfSideBar
|
||||
setPage={(page: number) => {
|
||||
if (pdfViewer) {
|
||||
pdfViewer.currentPageNumber = page
|
||||
}
|
||||
}}
|
||||
pdfDocument={pdfViewer?.pdfDocument}
|
||||
activePage={pageNumber}
|
||||
sidebarActive={sidebarActive}
|
||||
totalPages={pageCount}
|
||||
/>
|
||||
<PdfViewer
|
||||
viewer={props.viewer}
|
||||
article={props.article}
|
||||
containerRef={containerRef}
|
||||
eventBus={eventBus}
|
||||
sidebarActive={sidebarActive}
|
||||
pdfViewer={pdfViewer}
|
||||
articleMutations={{
|
||||
createHighlightMutation: async (input: CreateHighlightInput) => {
|
||||
try {
|
||||
return await createHighlight.mutateAsync({
|
||||
itemId: props.article.id,
|
||||
slug: props.article.slug,
|
||||
input,
|
||||
})
|
||||
} catch (err) {
|
||||
console.log('error creating highlight', err)
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
deleteHighlightMutation: async (
|
||||
_libraryItemId: string,
|
||||
highlightId: string
|
||||
) => {
|
||||
try {
|
||||
await deleteHighlight.mutateAsync({
|
||||
itemId: props.article.id,
|
||||
slug: props.article.slug,
|
||||
highlightId,
|
||||
})
|
||||
return true
|
||||
} catch (err) {
|
||||
console.log('error deleting highlight', err)
|
||||
return false
|
||||
}
|
||||
},
|
||||
mergeHighlightMutation: async (input) => {
|
||||
try {
|
||||
const result = await mergeHighlight.mutateAsync({
|
||||
itemId: props.article.id,
|
||||
slug: props.article.slug,
|
||||
input,
|
||||
})
|
||||
return result?.highlight
|
||||
} catch (err) {
|
||||
console.log('error merging highlight', err)
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
updateHighlightMutation: async (input) => {
|
||||
try {
|
||||
const result = await updateHighlight.mutateAsync({
|
||||
itemId: props.article.id,
|
||||
slug: props.article.slug,
|
||||
input,
|
||||
})
|
||||
return result?.id
|
||||
} catch (err) {
|
||||
console.log('error updating highlight', err)
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
articleReadingProgressMutation: async (
|
||||
input: ArticleReadingProgressMutationInput
|
||||
) => {
|
||||
try {
|
||||
await updateItemReadStatus.mutateAsync({
|
||||
itemId: props.article.id,
|
||||
slug: props.article.slug,
|
||||
input,
|
||||
})
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
<ResizableSidebar
|
||||
isShow={props.showHighlightsModal}
|
||||
onClose={() => {
|
||||
props.setShowHighlightsModal(false)
|
||||
}}
|
||||
>
|
||||
<NotebookHeader
|
||||
viewer={props.viewer}
|
||||
item={props.article}
|
||||
setShowNotebook={props.setShowHighlightsModal}
|
||||
/>
|
||||
<NotebookContent
|
||||
viewer={props.viewer}
|
||||
item={props.article}
|
||||
viewInReader={(highlightId) => {
|
||||
const highlight = props.article.highlights?.filter(
|
||||
(it) => it.id == highlightId
|
||||
)
|
||||
if (highlight && highlight.length == 1) {
|
||||
const pageId = highlight[0].highlightPositionAnchorIndex
|
||||
|
||||
if (pdfViewer) {
|
||||
pdfViewer.currentPageNumber = pageId ?? 1
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ResizableSidebar>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import { HStack } from '../../../elements/LayoutPrimitives'
|
||||
import React, { ChangeEvent, useEffect, useState } from 'react'
|
||||
import { DEFAULT_HEADER_HEIGHT } from '../../homeFeed/HeaderSpacer'
|
||||
import 'react-sliding-pane/dist/react-sliding-pane.css'
|
||||
import 'pdfjs-dist/web/pdf_viewer.css'
|
||||
import { EventBus, PDFViewer } from 'pdfjs-dist/types/web/pdf_viewer'
|
||||
import { CaretLeft, CaretRight } from '@phosphor-icons/react'
|
||||
import { SearchInput, ToolbarButton } from './Style'
|
||||
|
||||
export type PdfSearchProps = {
|
||||
pdfViewer: PDFViewer | null
|
||||
eventBus: EventBus | null
|
||||
}
|
||||
|
||||
export default function PdfSearchBar(props: PdfSearchProps) {
|
||||
const [searchTerm, setSearchTerm] = useState<string | null>(null)
|
||||
const [hasResults, setHasResults] = useState(false)
|
||||
const [foundMatches, setFoundMatches] = useState(0)
|
||||
const [currentMatch, setCurrentMatch] = useState(1)
|
||||
|
||||
useEffect(() => {
|
||||
if (props.eventBus) {
|
||||
props.eventBus.on(
|
||||
'updatefindmatchescount',
|
||||
(data: { matchesCount: { total: number } }) => {
|
||||
setHasResults(true)
|
||||
setCurrentMatch(1)
|
||||
setFoundMatches(data.matchesCount.total)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return () => {
|
||||
props.eventBus?.dispatch('find', { type: '', query: '' })
|
||||
}
|
||||
}, [props.eventBus])
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (searchTerm) {
|
||||
props.eventBus?.dispatch('find', {
|
||||
type: '',
|
||||
caseSensitive: false,
|
||||
findPrevious: false,
|
||||
highlightAll: true,
|
||||
phraseSearch: true,
|
||||
query: searchTerm,
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
}, [searchTerm, props.eventBus])
|
||||
|
||||
const search = (input: ChangeEvent<HTMLInputElement>) =>
|
||||
setSearchTerm(input.target.value)
|
||||
|
||||
const find = (forward: number) => () => {
|
||||
if (currentMatch < foundMatches && currentMatch + forward > 1) {
|
||||
props.eventBus?.dispatch('find', {
|
||||
type: 'again',
|
||||
caseSensitive: false,
|
||||
findPrevious: forward != 1,
|
||||
highlightAll: true,
|
||||
phraseSearch: true,
|
||||
query: searchTerm,
|
||||
})
|
||||
|
||||
setCurrentMatch(currentMatch + forward)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack
|
||||
distribution="start"
|
||||
css={{
|
||||
position: 'fixed',
|
||||
alignItems: 'flex-start',
|
||||
width: '400px',
|
||||
right: '5px',
|
||||
padding: '5px',
|
||||
top: `calc(${DEFAULT_HEADER_HEIGHT} + 18px)`,
|
||||
height: '35px',
|
||||
borderTop: '1px solid black',
|
||||
boxShadow: 'rgba(61, 66, 78, 0.5) 0px 1px 2px 0px',
|
||||
background: '$thBackground4',
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<SearchInput onChange={search} />
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '280px',
|
||||
top: '10px',
|
||||
fontSize: '10px',
|
||||
}}
|
||||
>
|
||||
{hasResults ? `${currentMatch} of ${foundMatches}` : ''}
|
||||
</div>
|
||||
<div>
|
||||
<ToolbarButton
|
||||
onClick={find(-1)}
|
||||
css={{
|
||||
borderTopLeftRadius: '10px',
|
||||
borderBottomLeftRadius: '10px',
|
||||
backgroundColor: '$thBackground5',
|
||||
}}
|
||||
>
|
||||
<CaretLeft />
|
||||
</ToolbarButton>
|
||||
<ToolbarButton
|
||||
css={{
|
||||
borderTopRightRadius: '10px',
|
||||
borderBottomRightRadius: '10px',
|
||||
backgroundColor: '$thBackground5',
|
||||
}}
|
||||
onClick={find(1)}
|
||||
>
|
||||
<CaretRight />
|
||||
</ToolbarButton>
|
||||
</div>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
107
packages/web/components/templates/article/pdf.js/PdfSideBar.tsx
Normal file
107
packages/web/components/templates/article/pdf.js/PdfSideBar.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { VStack } from '../../../elements/LayoutPrimitives'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import 'react-sliding-pane/dist/react-sliding-pane.css'
|
||||
import 'pdfjs-dist/web/pdf_viewer.css'
|
||||
import { PDFDocumentProxy } from 'pdfjs-dist'
|
||||
|
||||
export type PdfSidebarProps = {
|
||||
sidebarActive: boolean
|
||||
|
||||
pdfDocument: PDFDocumentProxy | undefined
|
||||
|
||||
totalPages: number
|
||||
activePage: number
|
||||
setPage: (page: number) => void
|
||||
}
|
||||
|
||||
export default function PdfSideBar(props: PdfSidebarProps) {
|
||||
const [images, setImages] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
if (props.pdfDocument) {
|
||||
const propsArray = [...Array(props.pdfDocument.numPages ?? 0)];
|
||||
|
||||
const dataUrls = await Promise.all(propsArray.map(async (_it, i) => {
|
||||
const page = await props.pdfDocument!.getPage(i + 1)
|
||||
const viewport = page.getViewport({ scale: 0.3 })
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
canvas.width = viewport.width
|
||||
canvas.height = viewport.height
|
||||
if (ctx) {
|
||||
const task = page.render({ canvasContext: ctx, viewport: viewport })
|
||||
return task.promise.then(() => canvas.toDataURL())
|
||||
}
|
||||
|
||||
return "";
|
||||
}))
|
||||
|
||||
console.log("Setting Data Urls");
|
||||
setImages(dataUrls)
|
||||
}
|
||||
})()
|
||||
}, [props.pdfDocument])
|
||||
|
||||
if (!props.sidebarActive) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack
|
||||
distribution="start"
|
||||
css={{
|
||||
alignItems: 'flex-start',
|
||||
position: 'fixed',
|
||||
left: '0px',
|
||||
top: '100px',
|
||||
padding: '7px',
|
||||
paddingTop: '10px',
|
||||
width: '250px',
|
||||
height: 'calc(100% - 100px)',
|
||||
borderTop: '1px solid black',
|
||||
boxShadow: 'rgba(61, 66, 78, 0.5) 0px 1px 2px 0px',
|
||||
background: '$thBackground2',
|
||||
overflow: 'scroll',
|
||||
}}
|
||||
>
|
||||
{[...Array(props.totalPages)].map((_it, idx) => (
|
||||
<VStack
|
||||
key={`pdf-page-${idx}`}
|
||||
css={{ width: '80%', margin: 'auto', height: '100%' }}
|
||||
onClick={() => props.setPage(idx + 1)}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
idx == props.activePage - 1
|
||||
? { outline: '5px solid var(--colors-thBackgroundActive)', width: '100%' }
|
||||
: { width: '100%', height: '100%' }
|
||||
}
|
||||
>
|
||||
<img
|
||||
width={'100%'}
|
||||
alt={`page-${idx + 1}`}
|
||||
src={images[idx]}
|
||||
style={{ display: 'block', objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{ margin: 'auto', marginTop: '3px', marginBottom: '3px' }}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: 'var(--colors-thLeftMenuBackground)',
|
||||
paddingLeft: '10px',
|
||||
paddingRight: '10px',
|
||||
borderRadius: '10px',
|
||||
}}
|
||||
>
|
||||
{idx + 1}
|
||||
</span>
|
||||
</div>
|
||||
</VStack>
|
||||
))}
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
180
packages/web/components/templates/article/pdf.js/PdfToolbar.tsx
Normal file
180
packages/web/components/templates/article/pdf.js/PdfToolbar.tsx
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import { ArticleAttributes } from '../../../../lib/networking/library_items/useLibraryItems'
|
||||
import { HStack } from '../../../elements/LayoutPrimitives'
|
||||
import React, { useState } from 'react'
|
||||
import { DEFAULT_HEADER_HEIGHT } from '../../homeFeed/HeaderSpacer'
|
||||
import { UserBasicData } from '../../../../lib/networking/queries/useGetViewerQuery'
|
||||
import 'react-sliding-pane/dist/react-sliding-pane.css'
|
||||
import 'pdfjs-dist/web/pdf_viewer.css'
|
||||
import { EventBus, PDFViewer } from 'pdfjs-dist/types/web/pdf_viewer'
|
||||
import {
|
||||
CaretLeft,
|
||||
CaretRight,
|
||||
CornersIn,
|
||||
Download,
|
||||
FrameCorners,
|
||||
MagnifyingGlass,
|
||||
MagnifyingGlassMinus,
|
||||
MagnifyingGlassPlus,
|
||||
Sidebar,
|
||||
} from '@phosphor-icons/react'
|
||||
import { PageInput, ToolbarButton, ToolbarIconButton } from './Style'
|
||||
|
||||
export type PdfArticleToolbarProps = {
|
||||
viewer: UserBasicData
|
||||
article: ArticleAttributes
|
||||
pdfViewer: PDFViewer | null
|
||||
eventBus: EventBus | null
|
||||
|
||||
pageNumber: number
|
||||
totalPageNumbers: number
|
||||
setPageNumber: React.Dispatch<React.SetStateAction<number>>
|
||||
|
||||
showSearch: boolean
|
||||
setShowSearch: React.Dispatch<React.SetStateAction<boolean>>
|
||||
|
||||
setShowSidebar: React.Dispatch<React.SetStateAction<boolean>>
|
||||
sidebarActive: boolean
|
||||
}
|
||||
|
||||
export default function PdfToolbar(props: PdfArticleToolbarProps): JSX.Element {
|
||||
const [zoomMode, setZoomMode] = useState('page-fit')
|
||||
|
||||
const zoom = (step: number) => () => {
|
||||
setZoomMode('zoom')
|
||||
if (step == 1) {
|
||||
props.pdfViewer?.increaseScale({ steps: 1 })
|
||||
return
|
||||
}
|
||||
|
||||
props.pdfViewer?.decreaseScale()
|
||||
}
|
||||
|
||||
const changePage = (step: number) => () => {
|
||||
setPage({ target: { value: (props.pageNumber + step).toString() } })
|
||||
}
|
||||
|
||||
const setPage = (e: { target: { value: string } }) => {
|
||||
const newPage = e.target.value
|
||||
if (!isNaN(parseInt(newPage))) {
|
||||
const clamped = Math.max(
|
||||
1,
|
||||
Math.min(parseInt(newPage), props.totalPageNumbers)
|
||||
)
|
||||
props.setPageNumber(clamped)
|
||||
if (props.pdfViewer) {
|
||||
props.pdfViewer.currentPageNumber = clamped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const zoomModeToggle = async () => {
|
||||
const newZoomMode = zoomMode == 'page-fit' ? 'zoom' : 'page-fit'
|
||||
setZoomMode(newZoomMode)
|
||||
|
||||
if (newZoomMode == 'page-fit') {
|
||||
if (props.pdfViewer) {
|
||||
props.pdfViewer.currentScaleValue = 'page-fit'
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (props.pdfViewer) {
|
||||
const pdfPage = await props.pdfViewer.pdfDocument?.getPage(
|
||||
props.pageNumber
|
||||
)
|
||||
const viewPort = pdfPage!.getViewport({
|
||||
scale: 1700 / pdfPage!.getViewport({ scale: 1.0 }).width,
|
||||
})
|
||||
props.pdfViewer.currentScale = viewPort.scale
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack
|
||||
distribution="start"
|
||||
css={{
|
||||
position: 'fixed',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
top: `calc(${DEFAULT_HEADER_HEIGHT} - 25px)`,
|
||||
height: '44px',
|
||||
lineHeight: '40px',
|
||||
borderTop: '1px solid black',
|
||||
boxShadow: 'rgba(61, 66, 78, 0.5) 0px 1px 2px 0px',
|
||||
background: '$thBackground2',
|
||||
fontSize: '24px',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<ToolbarIconButton onClick={() => props.setShowSidebar(!props.sidebarActive)} css={ props.sidebarActive ? { background: '$thBackground' } : {}}>
|
||||
<Sidebar />
|
||||
</ToolbarIconButton>
|
||||
<HStack style={{ padding: '2px 10px 0 10px' }}>
|
||||
<span style={{ fontSize: '16px ' }}>Page {' '}</span>
|
||||
<HStack
|
||||
style={{ paddingLeft: '5px', paddingRight: '5px', margin: 'auto' }}
|
||||
>
|
||||
<ToolbarButton
|
||||
onClick={changePage(-1)}
|
||||
css={{
|
||||
borderTopLeftRadius: '10px',
|
||||
borderBottomLeftRadius: '10px',
|
||||
}}
|
||||
>
|
||||
<CaretLeft />
|
||||
</ToolbarButton>
|
||||
<PageInput
|
||||
type="number"
|
||||
name="pageNumber"
|
||||
onChange={setPage}
|
||||
value={props.pageNumber}
|
||||
/>
|
||||
<ToolbarButton
|
||||
onClick={changePage(1)}
|
||||
css={{
|
||||
borderTopRightRadius: '10px',
|
||||
borderBottomRightRadius: '10px',
|
||||
backgroundColor: '$thBackground5',
|
||||
}}
|
||||
>
|
||||
<CaretRight />
|
||||
</ToolbarButton>
|
||||
</HStack>
|
||||
<span style={{ fontSize: '16px' }}>
|
||||
{' '} of {props.totalPageNumbers}
|
||||
</span>
|
||||
</HStack>
|
||||
<HStack>
|
||||
<ToolbarIconButton onClick={zoom(-1)}>
|
||||
<MagnifyingGlassMinus />
|
||||
</ToolbarIconButton>
|
||||
<ToolbarIconButton onClick={zoom(1)}>
|
||||
<MagnifyingGlassPlus />
|
||||
</ToolbarIconButton>
|
||||
<ToolbarIconButton onClick={zoomModeToggle}>
|
||||
{zoomMode == 'page-fit' ? <FrameCorners /> : <CornersIn />}
|
||||
</ToolbarIconButton>
|
||||
</HStack>
|
||||
|
||||
<div style={{ flexGrow: 1 }}></div>
|
||||
<HStack>
|
||||
<ToolbarIconButton
|
||||
onClick={() => props.setShowSearch(!props.showSearch)}
|
||||
css={{ backgroundColor: props.showSearch ? '$thBackground' : 'none' }}
|
||||
>
|
||||
<MagnifyingGlass />
|
||||
</ToolbarIconButton>
|
||||
<a
|
||||
href={props.article.url}
|
||||
target="_blank"
|
||||
download
|
||||
style={{ all: 'unset' }}
|
||||
>
|
||||
<ToolbarIconButton>
|
||||
<Download />
|
||||
</ToolbarIconButton>
|
||||
</a>
|
||||
</HStack>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
463
packages/web/components/templates/article/pdf.js/PdfViewer.tsx
Normal file
463
packages/web/components/templates/article/pdf.js/PdfViewer.tsx
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
import {
|
||||
ArticleAttributes,
|
||||
LibraryItemNode,
|
||||
} from '../../../../lib/networking/library_items/useLibraryItems'
|
||||
import { Box } from '../../../elements/LayoutPrimitives'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import {
|
||||
MutableRefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { DEFAULT_HEADER_HEIGHT } from '../../homeFeed/HeaderSpacer'
|
||||
import { UserBasicData } from '../../../../lib/networking/queries/useGetViewerQuery'
|
||||
import 'react-sliding-pane/dist/react-sliding-pane.css'
|
||||
import 'pdfjs-dist/web/pdf_viewer.css'
|
||||
import { EventBus, PDFViewer } from 'pdfjs-dist/types/web/pdf_viewer'
|
||||
import { PDFPageView } from 'pdfjs-dist/types/web/pdf_page_view'
|
||||
import { HighlightAction, HighlightBar } from '../../../patterns/HighlightBar'
|
||||
import { isTouchScreenDevice } from '../../../../lib/deviceType'
|
||||
import { ArticleMutations } from '../../../../lib/articleActions'
|
||||
import { HighlightNoteModal } from '../HighlightNoteModal'
|
||||
import type { Highlight } from '../../../../lib/networking/fragments/highlightFragment'
|
||||
|
||||
export type PdfArticleContainerProps = {
|
||||
viewer: UserBasicData
|
||||
article: ArticleAttributes
|
||||
containerRef: MutableRefObject<HTMLDivElement | null>
|
||||
pdfViewer: PDFViewer | null
|
||||
eventBus: EventBus | null
|
||||
sidebarActive: boolean
|
||||
|
||||
articleMutations: ArticleMutations
|
||||
}
|
||||
|
||||
export default function PdfViewer(props: PdfArticleContainerProps) {
|
||||
const [highlights, setHighlights] = useState(props.article.highlights ?? [])
|
||||
const [pageCoordinates, setPageCoordinates] = useState<{
|
||||
pageX: number
|
||||
pageY: number
|
||||
}>({ pageX: 0, pageY: 0 })
|
||||
const [showHighlightModal, setShowHighlightModal] = useState(false)
|
||||
const [noteTarget, setNoteTarget] = useState<Highlight | undefined>(undefined)
|
||||
const [clickedHighlight, setClickedHighlight] =
|
||||
useState<Highlight | null>(null)
|
||||
const [currentPageNum, setCurrentPageNum] = useState(1)
|
||||
|
||||
const colorMap: Record<string, string> = useMemo(
|
||||
() => ({
|
||||
yellow: 'rgba(255, 210, 52, 0.3)',
|
||||
red: 'rgba(251, 154, 154, 0.3)',
|
||||
green: 'rgba(85, 198, 137, 0.3)',
|
||||
blue: 'rgba(106, 177, 255, 0.3)',
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
const addHighlightToPage = useCallback(
|
||||
(page: PDFPageView, highlight: Highlight) => {
|
||||
const scale = page.viewport.scale
|
||||
const element = document.createElement('div')
|
||||
element.id = highlight.id
|
||||
const boundingRects = JSON.parse(highlight.patch).rects
|
||||
const color = highlight.color || 'yellow'
|
||||
|
||||
boundingRects.forEach((rect: number[]) => {
|
||||
const svgElement = document.createElement('svg')
|
||||
svgElement.className = 'highlight'
|
||||
svgElement.setAttribute('viewBox', '0 0 1 1')
|
||||
svgElement.style.borderRadius = '0px'
|
||||
svgElement.style.background = colorMap[color]
|
||||
svgElement.style.position = 'absolute'
|
||||
svgElement.style.top = `${rect[0] * scale}px`
|
||||
svgElement.style.left = `${rect[1] * scale}px`
|
||||
svgElement.style.width = `${rect[2] * scale}px`
|
||||
svgElement.style.height = `${rect[3] * scale}px`
|
||||
svgElement.style.cursor = 'pointer'
|
||||
svgElement.style.zIndex = '3'
|
||||
|
||||
svgElement.innerHTML = ``
|
||||
svgElement.innerHTML += `
|
||||
<defs>
|
||||
<path id="path_p1_2" vector-effect="non-scaling-stroke" d="M0 1 V0 H1 V1 Z"></path>
|
||||
<clipPath id="clip_path_p1_2" clipPathUnits="objectBoundingBox">
|
||||
<use href="#path_p1_2" class="clip"></use>
|
||||
</clipPath></defs>
|
||||
<use href="#path_p1_2"></use>
|
||||
`
|
||||
|
||||
svgElement.addEventListener('click', (evt) => {
|
||||
setClickedHighlight(highlight)
|
||||
setPageCoordinates({ pageX: evt.clientX, pageY: evt.clientY })
|
||||
setShowHighlightModal(true)
|
||||
})
|
||||
|
||||
element.prepend(svgElement)
|
||||
})
|
||||
|
||||
page.div.children[0].append(element)
|
||||
},
|
||||
[colorMap]
|
||||
)
|
||||
|
||||
const getBoundingRects = (
|
||||
canvasBoundingBox: DOMRect,
|
||||
highlightBoundingBoxes: DOMRectList,
|
||||
scaleFactor: number
|
||||
): number[][] => {
|
||||
const rects = Array.from(highlightBoundingBoxes).map((rect) => [
|
||||
(rect.top - canvasBoundingBox.y) / scaleFactor,
|
||||
(rect.left - canvasBoundingBox.x) / scaleFactor,
|
||||
rect.width / scaleFactor,
|
||||
rect.height / scaleFactor,
|
||||
])
|
||||
|
||||
return Object.values(
|
||||
rects.reduce((acc, curr) => {
|
||||
acc[`l${curr[1]}w${curr[2]}}`] = curr
|
||||
return acc
|
||||
}, {} as Record<string, number[]>)
|
||||
)
|
||||
}
|
||||
|
||||
const addNoteToNewHighlight = async (
|
||||
note: string | undefined,
|
||||
noteTarget: Highlight
|
||||
) => {
|
||||
if (props.pdfViewer && props.pdfViewer._pages) {
|
||||
const currentPageIndex = props.pdfViewer.currentPageNumber - 1
|
||||
const page = props.pdfViewer._pages[currentPageIndex]
|
||||
const highlight = await props.articleMutations.createHighlightMutation({
|
||||
id: noteTarget.id,
|
||||
shortId: noteTarget.id.slice(0, 12),
|
||||
articleId: noteTarget.libraryItem?.id ?? props.article.id,
|
||||
quote: noteTarget.quote,
|
||||
color: noteTarget?.color || 'yellow',
|
||||
patch: noteTarget.patch,
|
||||
highlightPositionAnchorIndex: noteTarget.highlightPositionAnchorIndex,
|
||||
annotation: note,
|
||||
})
|
||||
if (highlight) {
|
||||
addHighlightToPage(page, highlight)
|
||||
setHighlights([...highlights, highlight])
|
||||
}
|
||||
return highlight
|
||||
}
|
||||
}
|
||||
|
||||
const copyHighlightedText = async (quote: string) => {
|
||||
await navigator.clipboard.writeText(quote)
|
||||
}
|
||||
|
||||
const viewNoteTextForClickedHighlight = (clickedHighlight: Highlight) => {
|
||||
setNoteTarget(clickedHighlight)
|
||||
}
|
||||
|
||||
const updateColorForClickedHighlight = async (
|
||||
pdfPage: PDFPageView,
|
||||
clickedHighlight: Highlight,
|
||||
newColor: string
|
||||
) => {
|
||||
await props.articleMutations.updateHighlightMutation({
|
||||
highlightId: clickedHighlight.id,
|
||||
color: newColor,
|
||||
})
|
||||
|
||||
document.getElementById(clickedHighlight.id)?.remove()
|
||||
clickedHighlight.color = newColor
|
||||
addHighlightToPage(pdfPage, clickedHighlight)
|
||||
|
||||
setClickedHighlight(null)
|
||||
}
|
||||
|
||||
const createHighlightWithNote = (
|
||||
id: string,
|
||||
quote: string,
|
||||
pageNumber: number,
|
||||
rects: number[][]
|
||||
) => {
|
||||
setNoteTarget({
|
||||
id,
|
||||
type: 'HIGHLIGHT',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
sharedAt: new Date().toISOString(),
|
||||
createdByMe: true,
|
||||
shortId: id.slice(0, 12),
|
||||
libraryItem: { id: props.article.id } as unknown as LibraryItemNode,
|
||||
quote,
|
||||
color: 'yellow',
|
||||
patch: JSON.stringify({ page: pageNumber + 1, rects }),
|
||||
highlightPositionAnchorIndex: pageNumber + 1,
|
||||
})
|
||||
setShowHighlightModal(false)
|
||||
}
|
||||
|
||||
const createHighlightFromColour = async (
|
||||
id: string,
|
||||
quote: string,
|
||||
color: string,
|
||||
page: PDFPageView,
|
||||
pageNumber: number,
|
||||
rects: number[][]
|
||||
) => {
|
||||
const highlight = await props.articleMutations.createHighlightMutation({
|
||||
id,
|
||||
shortId: id.slice(0, 12),
|
||||
articleId: props.article.id,
|
||||
quote,
|
||||
color,
|
||||
patch: JSON.stringify({ page: pageNumber, rects }),
|
||||
highlightPositionAnchorIndex: pageNumber,
|
||||
})
|
||||
|
||||
if (highlight) {
|
||||
addHighlightToPage(page as PDFPageView, highlight)
|
||||
setHighlights([...highlights, highlight])
|
||||
}
|
||||
}
|
||||
|
||||
const onAddHighlightClick = async (
|
||||
action: HighlightAction,
|
||||
param?: string
|
||||
) => {
|
||||
setShowHighlightModal(false)
|
||||
|
||||
if (props.pdfViewer?._pages && window) {
|
||||
const id = uuidv4()
|
||||
|
||||
const clientRects = window.getSelection()?.getRangeAt(0).getClientRects()
|
||||
const scaleFactor = props.pdfViewer._pages[0].viewport.scale
|
||||
const currentPageNumber = props.pdfViewer.currentPageNumber
|
||||
const page = props.pdfViewer._pages[currentPageNumber - 1]
|
||||
|
||||
const canvas = document.querySelector(
|
||||
`[data-page-number='${currentPageNumber}']`
|
||||
)?.children[0]
|
||||
const canvasRect = canvas?.getBoundingClientRect()
|
||||
|
||||
const quote = window.getSelection()?.toString()
|
||||
|
||||
const isTextHighlighted = clientRects && clientRects.length > 0 && quote
|
||||
// We copy the highlighted text.
|
||||
// If a highlight is selected, we copy the text from that instead.
|
||||
if (action == 'copy') {
|
||||
return copyHighlightedText(clickedHighlight?.quote ?? quote ?? '')
|
||||
}
|
||||
|
||||
if (action == 'comment' && !quote && clickedHighlight) {
|
||||
return viewNoteTextForClickedHighlight(clickedHighlight)
|
||||
}
|
||||
|
||||
if (action == 'updateColor' && clickedHighlight) {
|
||||
return updateColorForClickedHighlight(
|
||||
page,
|
||||
clickedHighlight,
|
||||
param ?? 'yellow'
|
||||
)
|
||||
}
|
||||
|
||||
if (isTextHighlighted && canvasRect) {
|
||||
const rects = getBoundingRects(canvasRect, clientRects, scaleFactor)
|
||||
|
||||
if (action == 'comment') {
|
||||
return createHighlightWithNote(id, quote, currentPageNumber, rects)
|
||||
}
|
||||
|
||||
if (action == 'create') {
|
||||
return createHighlightFromColour(
|
||||
id,
|
||||
quote,
|
||||
param || 'yellow',
|
||||
page,
|
||||
currentPageNumber,
|
||||
rects
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Here we detect whether some text has been highlighted. If it has, we view the
|
||||
// highlight modal.
|
||||
useEffect(() => {
|
||||
const detectHighlightedText = () => {
|
||||
if (window) {
|
||||
const clientRectsArray = Array.from(
|
||||
window.getSelection()?.getRangeAt(0).getClientRects() ?? []
|
||||
)
|
||||
const quote = window.getSelection()?.toString()
|
||||
|
||||
if (clientRectsArray.length > 0 && quote) {
|
||||
const rect = clientRectsArray[clientRectsArray.length - 1]
|
||||
setPageCoordinates({ pageX: rect.x + 10, pageY: rect.y + 15 })
|
||||
setShowHighlightModal(true)
|
||||
setClickedHighlight(null)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
setPageCoordinates({ pageX: -1000, pageY: -1000 })
|
||||
setShowHighlightModal(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (props.containerRef?.current) {
|
||||
props.containerRef.current.addEventListener(
|
||||
'mouseup',
|
||||
detectHighlightedText
|
||||
)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (props.containerRef && props.containerRef.current) {
|
||||
props.containerRef?.current.removeEventListener(
|
||||
'mouseup',
|
||||
detectHighlightedText
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
props.containerRef,
|
||||
setPageCoordinates,
|
||||
setShowHighlightModal,
|
||||
props.pdfViewer,
|
||||
setHighlights,
|
||||
highlights,
|
||||
])
|
||||
|
||||
// When a page zooms, it internally re-renders using pdf.js. We add a function
|
||||
// to ensure that when a page newly renders that we re-add all the highlights,
|
||||
// as those are removed.
|
||||
useEffect(() => {
|
||||
const render = (data: { source: PDFPageView }) => {
|
||||
const page = data.source
|
||||
if (page) {
|
||||
highlights
|
||||
?.filter((it) => it.highlightPositionAnchorIndex == page.id)
|
||||
.forEach((it) => addHighlightToPage(page as PDFPageView, it))
|
||||
}
|
||||
}
|
||||
const setPage = (e: { pageNumber: number }) =>
|
||||
setCurrentPageNum(e.pageNumber)
|
||||
|
||||
if (props.eventBus && props.pdfViewer) {
|
||||
props.eventBus.on('textlayerrendered', render)
|
||||
props.eventBus.on('pagechanging', setPage)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (props.eventBus) {
|
||||
props.eventBus.off('textlayerrendered', render)
|
||||
props.eventBus.off('pagechanging', setPage)
|
||||
}
|
||||
}
|
||||
}, [props.eventBus, props.pdfViewer, highlights, addHighlightToPage])
|
||||
|
||||
// Here is where we add our percentage read markers. We debounce for 2.5s before
|
||||
// adding. We also set the page number so that we can go back to the correct
|
||||
// page on reload.
|
||||
useEffect(() => {
|
||||
let timeoutId: NodeJS.Timeout | null = null
|
||||
const savePercentOnScroll = () => {
|
||||
timeoutId && clearTimeout(timeoutId)
|
||||
setShowHighlightModal(false)
|
||||
|
||||
timeoutId = setTimeout(async () => {
|
||||
if (props.containerRef && props.containerRef.current) {
|
||||
const bottomProgress =
|
||||
(props.containerRef.current.scrollTop +
|
||||
props.containerRef.current.clientHeight) /
|
||||
props.containerRef.current.scrollHeight
|
||||
|
||||
await props.articleMutations.articleReadingProgressMutation({
|
||||
id: props.article.id,
|
||||
readingProgressTopPercent: bottomProgress * 100,
|
||||
readingProgressPercent: bottomProgress * 100,
|
||||
readingProgressAnchorIndex: currentPageNum + 1,
|
||||
})
|
||||
}
|
||||
}, 2500)
|
||||
}
|
||||
|
||||
if (props.containerRef?.current) {
|
||||
props.containerRef.current.addEventListener('scroll', savePercentOnScroll)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (props.containerRef?.current) {
|
||||
timeoutId && clearTimeout(timeoutId)
|
||||
props.containerRef.current.removeEventListener(
|
||||
'scroll',
|
||||
savePercentOnScroll
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
props.containerRef,
|
||||
currentPageNum,
|
||||
props.article.id,
|
||||
props.articleMutations,
|
||||
])
|
||||
|
||||
return (
|
||||
<Box id="article-wrapper" css={{ flexGrow: 1 }}>
|
||||
{noteTarget && (
|
||||
<HighlightNoteModal
|
||||
highlight={clickedHighlight ?? undefined}
|
||||
libraryItemId={props.article.id}
|
||||
libraryItemSlug={props.article.slug}
|
||||
createHighlightForNote={(note: string | undefined) => {
|
||||
if (noteTarget) {
|
||||
return addNoteToNewHighlight(note, noteTarget)
|
||||
}
|
||||
|
||||
return Promise.resolve(noteTarget)
|
||||
}}
|
||||
onUpdate={(updatedHighlight: Highlight) => {
|
||||
const indexOf = highlights?.findIndex(
|
||||
(it) => it.id == updatedHighlight.id
|
||||
)
|
||||
if (indexOf && indexOf > -1) {
|
||||
highlights[indexOf] = updatedHighlight
|
||||
setHighlights(highlights)
|
||||
}
|
||||
setClickedHighlight(null)
|
||||
}}
|
||||
onOpenChange={() => {
|
||||
setNoteTarget(undefined)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showHighlightModal && (
|
||||
<>
|
||||
<HighlightBar
|
||||
anchorCoordinates={pageCoordinates}
|
||||
isNewHighlight={!clickedHighlight}
|
||||
handleButtonClick={onAddHighlightClick}
|
||||
isSharedToFeed={false}
|
||||
displayAtBottom={isTouchScreenDevice()}
|
||||
highlightColor={clickedHighlight?.color || 'yellow'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
ref={props.containerRef}
|
||||
className={'viewerContainer'}
|
||||
style={{
|
||||
width: `calc(100% - ${props.sidebarActive ? '250px' : '0px'})`,
|
||||
left: !props.sidebarActive ? 0 : '250px',
|
||||
height: `calc(100vh - ${DEFAULT_HEADER_HEIGHT} - 20px)`,
|
||||
overflow: 'scroll',
|
||||
top: '100px',
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<div id="viewer" className="pdfViewer"></div>
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
52
packages/web/components/templates/article/pdf.js/Style.tsx
Normal file
52
packages/web/components/templates/article/pdf.js/Style.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { styled } from '../../../tokens/stitches.config'
|
||||
|
||||
export const ToolbarIconButton = styled('div', {
|
||||
paddingLeft: '10px',
|
||||
paddingTop: '5px',
|
||||
height: '43px',
|
||||
width: '44px',
|
||||
'&:hover': {
|
||||
background: '$thBackground'
|
||||
},
|
||||
})
|
||||
|
||||
export const ToolbarButton = styled('button', {
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: 'normal',
|
||||
color: '$grayTextContrast',
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
padding: '5px 3px 5px 3px',
|
||||
background: 'none',
|
||||
outline: 'inherit',
|
||||
border: '1px solid black',
|
||||
backgroundColor: '$thBackground5',
|
||||
'&:hover': {
|
||||
background: '$thBackground'
|
||||
},
|
||||
})
|
||||
|
||||
export const PageInput = styled('input', {
|
||||
width: '40px',
|
||||
height: '26px',
|
||||
padding: '4px',
|
||||
textAlign: 'center',
|
||||
border: '1px solid black',
|
||||
borderLeft: '0px',
|
||||
borderRight: '1px',
|
||||
backgroundColor: '$thBackground5',
|
||||
})
|
||||
|
||||
export const SearchInput = styled('input', {
|
||||
height: '26px',
|
||||
width: '330px',
|
||||
padding: '4px',
|
||||
border: '1px solid black',
|
||||
borderRadius: '4px',
|
||||
paddingRight: '60px',
|
||||
marginRight: '5px',
|
||||
backgroundColor: '$thFormInput',
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
}
|
||||
})
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
const ContentSecurityPolicy = `
|
||||
default-src 'self';
|
||||
base-uri 'self';
|
||||
connect-src 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://proxy-prod.omnivore-image-cache.app https://accounts.google.com https://proxy-demo.omnivore-image-cache.app https://storage.googleapis.com https://widget.intercom.io https://api-iam.intercom.io https://static.intercomassets.com https://downloads.intercomcdn.com https://platform.twitter.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io wss://nexus-europe-websocket.intercom.io wss://nexus-australia-websocket.intercom.io https://uploads.intercomcdn.com https://tools.applemediaservices.com wss://www.tiktok.com *.sentry.io;
|
||||
connect-src 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://proxy-prod.omnivore-image-cache.app https://accounts.google.com https://proxy-demo.omnivore-image-cache.app https://storage.googleapis.com https://widget.intercom.io https://api-iam.intercom.io https://static.intercomassets.com https://downloads.intercomcdn.com https://platform.twitter.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io wss://nexus-europe-websocket.intercom.io wss://nexus-australia-websocket.intercom.io https://uploads.intercomcdn.com https://tools.applemediaservices.com wss://www.tiktok.com *.sentry.io 127.0.0.1 http://localhost:1010;
|
||||
font-src 'self' data: https://cdn.jsdelivr.net https://js.intercomcdn.com https://fonts.intercomcdn.com;
|
||||
form-action 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://getpocket.com/auth/authorize https://intercom.help https://api-iam.intercom.io https://api-iam.eu.intercom.io https://api-iam.au.intercom.io https://www.notion.so https://api.notion.com;
|
||||
frame-ancestors 'none';
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@
|
|||
"next": "^13.5.6",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"pdfjs-dist": "^4.9.155",
|
||||
"pspdfkit": "^2023.4.6",
|
||||
"re-resizable": "^6.9.11",
|
||||
"react": "^18.2.0",
|
||||
|
|
@ -111,4 +112,4 @@
|
|||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import {
|
|||
import { useGetViewer } from '../../../lib/networking/viewer/useGetViewer'
|
||||
|
||||
const PdfArticleContainerNoSSR = dynamic<PdfArticleContainerProps>(
|
||||
() => import(`./../../../components/templates/article/NativePdfArticleContainer`),
|
||||
() => import(`./../../../components/templates/article/pdf.js/PdfArticleContainer`),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
|
|
|
|||
21
packages/web/public/pdf.min.mjs
Normal file
21
packages/web/public/pdf.min.mjs
Normal file
File diff suppressed because one or more lines are too long
21
packages/web/public/pdf.worker.min.mjs
Normal file
21
packages/web/public/pdf.worker.min.mjs
Normal file
File diff suppressed because one or more lines are too long
BIN
packages/web/public/pdfjs-dist/cmaps/78-EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/78-EUC-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/78-EUC-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/78-EUC-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/78-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/78-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/78-RKSJ-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/78-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/78-RKSJ-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/78-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/78-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/78-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/78ms-RKSJ-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/78ms-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/78ms-RKSJ-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/78ms-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/83pv-RKSJ-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/83pv-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/90ms-RKSJ-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/90ms-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/90ms-RKSJ-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/90ms-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/90msp-RKSJ-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/90msp-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/90msp-RKSJ-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/90msp-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/90pv-RKSJ-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/90pv-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/90pv-RKSJ-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/90pv-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Add-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Add-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Add-RKSJ-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Add-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Add-RKSJ-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Add-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Add-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Add-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-0.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-0.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-1.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-1.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-2.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-2.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-3.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-3.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-4.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-4.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-5.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-5.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-6.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-6.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-UCS2.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-CNS1-UCS2.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-0.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-0.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-1.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-1.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-2.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-2.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-3.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-3.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-4.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-4.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-5.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-5.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-UCS2.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-GB1-UCS2.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-0.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-0.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-1.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-1.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-2.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-2.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-3.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-3.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-4.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-4.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-5.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-5.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-6.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-6.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-UCS2.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Japan1-UCS2.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Korea1-0.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Korea1-0.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Korea1-1.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Korea1-1.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Korea1-2.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Korea1-2.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Korea1-UCS2.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Adobe-Korea1-UCS2.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/B5-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/B5-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/B5-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/B5-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/B5pc-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/B5pc-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/B5pc-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/B5pc-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/CNS-EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/CNS-EUC-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/CNS-EUC-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/CNS-EUC-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/CNS1-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/CNS1-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/CNS1-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/CNS1-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/CNS2-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/CNS2-H.bcmap
Normal file
Binary file not shown.
3
packages/web/public/pdfjs-dist/cmaps/CNS2-V.bcmap
Normal file
3
packages/web/public/pdfjs-dist/cmaps/CNS2-V.bcmap
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
àRCopyright 1990-2009 Adobe Systems Incorporated.
|
||||
All rights reserved.
|
||||
See ./LICENSEáCNS2-H
|
||||
BIN
packages/web/public/pdfjs-dist/cmaps/ETHK-B5-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/ETHK-B5-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/ETHK-B5-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/ETHK-B5-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/ETen-B5-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/ETen-B5-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/ETen-B5-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/ETen-B5-V.bcmap
Normal file
Binary file not shown.
3
packages/web/public/pdfjs-dist/cmaps/ETenms-B5-H.bcmap
Normal file
3
packages/web/public/pdfjs-dist/cmaps/ETenms-B5-H.bcmap
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
àRCopyright 1990-2009 Adobe Systems Incorporated.
|
||||
All rights reserved.
|
||||
See ./LICENSEá ETen-B5-H` ^
|
||||
BIN
packages/web/public/pdfjs-dist/cmaps/ETenms-B5-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/ETenms-B5-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/EUC-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/EUC-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/EUC-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Ext-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Ext-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Ext-RKSJ-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Ext-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Ext-RKSJ-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Ext-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/Ext-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/Ext-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GB-EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GB-EUC-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GB-EUC-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GB-EUC-V.bcmap
Normal file
Binary file not shown.
4
packages/web/public/pdfjs-dist/cmaps/GB-H.bcmap
Normal file
4
packages/web/public/pdfjs-dist/cmaps/GB-H.bcmap
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
àRCopyright 1990-2009 Adobe Systems Incorporated.
|
||||
All rights reserved.
|
||||
See ./LICENSE!!<21>º]aX!!]`<60>21<32>> <09>p<0B>z<EFBFBD>$]‚<06>"R‚d<E2809A>-Uƒ7<C692>*„
4„%<25>+ „Z „{<7B>/…%…<<3C>9K…b<E280A6>1]†.<2E>"‡‰`]‡,<2C>"]ˆ
|
||||
<EFBFBD>"]ˆh<CB86>"]‰F<E280B0>"]Š$<24>"]‹<02>"]‹`<60>"]Œ><3E>"]<5D><1C>"]<5D>z<EFBFBD>"]ŽX<C5BD>"]<5D>6<EFBFBD>"]<5D><14>"]<5D>r<EFBFBD>"]‘P<E28098>"]’.<2E>"]“<0C>"]“j<E2809C>"]”H<E2809D>"]•&<26>"]–<04>"]–b<E28093>"]—@<40>"]˜<1E>"]˜|<7C>"]™Z<E284A2>"]š8<C5A1>"]›<16>"]›t<E280BA>"]œR<C593>"]<5D>0<EFBFBD>"]ž<0E>"]žl<C5BE>"]ŸJ<C5B8>"] (<28>"]¡<06>"]¡d<C2A1>"]¢B<C2A2>"]£ <20>"X£~<7E>']¤W<C2A4>"]¥5<C2A5>"]¦<13>"]¦q<C2A6>"]§O<C2A7>"]¨-<2D>"]©<0B>"]©i<C2A9>"]ªG<C2AA>"]«%<25>"]¬<03>"]¬a<C2AC>"]?<3F>"]®<1D>"]®{<7B>"]¯Y<C2AF>"]°7<C2B0>"]±<15>"]±s<C2B1>"]²Q<C2B2>"]³/<2F>"]´
<0A>"]´k<C2B4>"]µI<C2B5>"]¶'<27>"]·<05>"]·c<C2B7>"]¸A<C2B8>"]¹<1F>"]¹}<7D>"]º[<5B>"]»9
|
||||
BIN
packages/web/public/pdfjs-dist/cmaps/GB-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GB-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBK-EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBK-EUC-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBK-EUC-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBK-EUC-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBK2K-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBK2K-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBK2K-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBK2K-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBKp-EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBKp-EUC-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBKp-EUC-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBKp-EUC-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBT-EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBT-EUC-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBT-EUC-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBT-EUC-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBT-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBT-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBT-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBT-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBTpc-EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBTpc-EUC-H.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBTpc-EUC-V.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBTpc-EUC-V.bcmap
Normal file
Binary file not shown.
BIN
packages/web/public/pdfjs-dist/cmaps/GBpc-EUC-H.bcmap
Normal file
BIN
packages/web/public/pdfjs-dist/cmaps/GBpc-EUC-H.bcmap
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue