diff --git a/examples/advanced/grading-papers-comments-annotations/package.json b/examples/advanced/grading-papers-comments-annotations/package.json index e11d3c67b2..4c3cf6d349 100644 --- a/examples/advanced/grading-papers-comments-annotations/package.json +++ b/examples/advanced/grading-papers-comments-annotations/package.json @@ -9,9 +9,10 @@ "preview": "vite preview" }, "dependencies": { - "pdfjs-dist": "4.3.136", + "pdfjs-dist": "^5.4.296", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "superdoc": "latest" }, "devDependencies": { "@vitejs/plugin-react": "^4.0.4", diff --git a/examples/advanced/grading-papers-comments-annotations/src/App.jsx b/examples/advanced/grading-papers-comments-annotations/src/App.jsx index 9542114e0b..2849be6f0c 100644 --- a/examples/advanced/grading-papers-comments-annotations/src/App.jsx +++ b/examples/advanced/grading-papers-comments-annotations/src/App.jsx @@ -1,12 +1,14 @@ -import '@harbour-enterprises/superdoc/style.css'; +import 'superdoc/style.css'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import Header from './components/Header.jsx'; import AssignmentHeader from './components/AssignmentHeader.jsx'; import Drawer from './components/Drawer.jsx'; -import { SuperDoc } from '@harbour-enterprises/superdoc'; +import { SuperDoc } from 'superdoc'; import NickPDF from '/nick.pdf?url'; import * as pdfjsLib from 'pdfjs-dist/build/pdf.mjs'; -import * as pdfjsViewer from 'pdfjs-dist/web/pdf_viewer.mjs'; + +// Set worker globally outside SD. +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url,).toString(); const defaultWhiteboardOpacity = 1; const disabledWhiteboardOpacity = 0.5; @@ -16,7 +18,7 @@ const App = () => { const docFileRef = useRef(null); // UI state only (do not store SuperDoc instance in state). - const [whiteboardReady, setWhiteboardReady] = useState(false); + const [whiteboardInitialized, setWhiteboardInit] = useState(false); const [whiteboardEnabled, setWhiteboardEnabled] = useState(true); const [activeTool, setActiveTool] = useState('select'); @@ -51,15 +53,15 @@ const App = () => { const superdoc = superdocRef.current; if (!superdoc) return; superdoc.on('whiteboard:change', (data) => { - console.log('whiteboard:change', { data }); + console.log('whiteboard:change', data); }); superdoc.on('whiteboard:tool', (tool) => { setActiveTool(tool); }); }, []); - const onWhiteboardReady = useCallback((whiteboard) => { - setWhiteboardReady(true); + const onWhiteboardInit = useCallback((whiteboard) => { + setWhiteboardInit(true); setActiveTool(whiteboard?.getTool?.() ?? 'select'); registerStickers(); registerComments(); @@ -75,7 +77,7 @@ const App = () => { const superdocInstance = new SuperDoc({ selector: '#superdoc', - document: { data: docFileRef.current }, + document: docFileRef.current, toolbar: 'superdoc-toolbar', licenseKey: 'public_license_key_superdocinternal_ad7035140c4b', telemetry: { enabled: false }, @@ -87,15 +89,16 @@ const App = () => { excludeItems: [ 'acceptTrackedChangeBySelection', 'rejectTrackedChangeOnSelection', - 'zoom', + // 'zoom', 'documentMode', ], }, pdf: { pdfLib: pdfjsLib, - pdfViewer: pdfjsViewer, - setWorker: true, - textLayerMode: 0, + setWorker: false, + // workerSrc: '', + textLayer: true, + // outputScale: 1.5, }, whiteboard: { enabled: whiteboardEnabled, @@ -113,10 +116,10 @@ const App = () => { superdocRef.current = superdocInstance; window.superdoc = superdocInstance; - superdocInstance.on('whiteboard:ready', ({ whiteboard }) => { - onWhiteboardReady(whiteboard); + superdocInstance.on('whiteboard:init', ({ whiteboard }) => { + onWhiteboardInit(whiteboard); }); - }, [onWhiteboardReady]); + }, [onWhiteboardInit]); // Load selected file and boot SuperDoc. const handleNewFile = useCallback(async (fileName) => { @@ -163,7 +166,7 @@ const App = () => { const exportWhiteboard = useCallback(() => { const data = superdocRef.current?.whiteboard?.getWhiteboardData(); if (!data) return; - console.log('[Whiteboard] export', { data }); + console.log('[Whiteboard] export', data); console.log('[Whiteboard] export json:', JSON.stringify(data, null, 2)); }, []); @@ -202,7 +205,7 @@ const App = () => {

Document Viewer

- {whiteboardReady && ( + {whiteboardInitialized && (
{toolButtons.map((tool) => ( diff --git a/examples/advanced/grading-papers-comments-annotations/src/style.css b/examples/advanced/grading-papers-comments-annotations/src/style.css index f9962255b1..8fa57c4866 100644 --- a/examples/advanced/grading-papers-comments-annotations/src/style.css +++ b/examples/advanced/grading-papers-comments-annotations/src/style.css @@ -696,9 +696,29 @@ body { font-size: 12px; } -.superdoc-pdf-viewer-container { +.sd-pdf-viewer { border: 1px solid #DBDBDB; border-radius: 8px; + overflow: hidden; +} + +.sd-pdf-viewer-page { + border-bottom: 1px solid #DBDBDB; +} + +.sd-pdf-viewer-page { + border-top: 1px solid #dfdfdf; + border-bottom: 1px solid #dfdfdf; +} + +.sd-pdf-viewer-page:first-child { + border-radius: 16px 16px 0 0; + border-top: none; +} + +.sd-pdf-viewer-page:last-child { + border-radius: 0 0 16px 16px; + border-bottom: none; } .hiddenCanvasElement{ diff --git a/packages/superdoc/package.json b/packages/superdoc/package.json index 0b2fb0010c..ab3df13d55 100644 --- a/packages/superdoc/package.json +++ b/packages/superdoc/package.json @@ -89,7 +89,7 @@ }, "peerDependencies": { "@hocuspocus/provider": "^2.13.6", - "pdfjs-dist": ">=4.3.136 <=4.6.82", + "pdfjs-dist": "^5.4.296", "y-prosemirror": "catalog:", "yjs": "catalog:" }, diff --git a/packages/superdoc/postcss.config.mjs b/packages/superdoc/postcss.config.mjs index f7ce74d8ff..bf3c0bcb2b 100644 --- a/packages/superdoc/postcss.config.mjs +++ b/packages/superdoc/postcss.config.mjs @@ -1,15 +1,5 @@ -// packages/superdoc/src/components/PdfViewer/PdfViewer.vue -const PDF_VIEWER_CLASS = '.superdoc-pdf-viewer'; - export default { plugins: [ (await import('postcss-nested')).default, - // https://github.com/dbtedman/postcss-prefixwrap - // This is necessary for pdf.js style scoping. - (await import('postcss-prefixwrap')).default(PDF_VIEWER_CLASS, { - whitelist: ['pdf-viewer.css'], - ignoredSelectors: [], - prefixRootTags: false, - }), ] } diff --git a/packages/superdoc/src/SuperDoc.test.js b/packages/superdoc/src/SuperDoc.test.js index 317df33aac..0c74cf747a 100644 --- a/packages/superdoc/src/SuperDoc.test.js +++ b/packages/superdoc/src/SuperDoc.test.js @@ -98,7 +98,6 @@ const FloatingCommentsStub = stubComponent('FloatingComments'); const CommentsLayerStub = stubComponent('CommentsLayer'); const HrbrFieldsLayerStub = stubComponent('HrbrFieldsLayer'); const AiLayerStub = stubComponent('AiLayer'); -const PdfViewerStub = stubComponent('PdfViewer'); const HtmlViewerStub = stubComponent('HtmlViewer'); // Mock @superdoc/super-editor with stubs and PresentationEditor class @@ -118,10 +117,6 @@ vi.mock('@superdoc/super-editor', () => ({ }, })); -vi.mock('./components/PdfViewer/PdfViewer.vue', () => ({ - default: PdfViewerStub, -})); - vi.mock('./components/HtmlViewer/HtmlViewer.vue', () => ({ default: HtmlViewerStub, })); diff --git a/packages/superdoc/src/SuperDoc.vue b/packages/superdoc/src/SuperDoc.vue index 379f415302..b02961f5b8 100644 --- a/packages/superdoc/src/SuperDoc.vue +++ b/packages/superdoc/src/SuperDoc.vue @@ -57,6 +57,17 @@ const { } = storeToRefs(superdocStore); const { handlePageReady, modules, user, getDocument } = superdocStore; +/* +NOTE: new PdfViewer does not emit page-loaded. Hrbr fields/annotations +rely on handlePageReady; revisit when wiring fields for PDF. + +From the old code: +const containerBounds = container.getBoundingClientRect(); +containerBounds.originalWidth = width; +containerBounds.originalHeight = height; +emit('page-loaded', documentId, index, containerBounds); +*/ + //prettier-ignore const { getConfig, @@ -135,6 +146,7 @@ const superdocStyleVars = computed(() => { // Refs const layers = ref(null); +const pdfViewerRef = ref(null); // Comments layer const commentsLayer = ref(null); @@ -687,10 +699,12 @@ const getSelectionPosition = computed(() => { return { x: null, y: null }; } - const top = selectionPosition.value.top; - const left = selectionPosition.value.left; - const right = selectionPosition.value.right; - const bottom = selectionPosition.value.bottom; + const isPdf = selectionPosition.value.source === 'pdf'; + const zoom = isPdf ? (activeZoom.value ?? 100) / 100 : 1; + const top = selectionPosition.value.top * zoom; + const left = selectionPosition.value.left * zoom; + const right = selectionPosition.value.right * zoom; + const bottom = selectionPosition.value.bottom * zoom; const style = { zIndex: 500, borderRadius: '4px', @@ -733,7 +747,9 @@ const handleSelectionChange = (selection) => { activeSelection.value = selection; // Place the tools menu at the level of the selection - let top = selection.selectionBounds.top; + const isPdf = selection.source === 'pdf' || selection.source?.value === 'pdf'; + const zoom = isPdf ? (activeZoom.value ?? 100) / 100 : 1; + const top = selection.selectionBounds.top * zoom; toolsMenuPosition.top = top + 'px'; toolsMenuPosition.right = isMobileView ? '0' : '-25px'; }; @@ -795,7 +811,7 @@ const getPdfPageNumberFromEvent = (event) => { const y = event?.clientY; if (typeof x !== 'number' || typeof y !== 'number') return null; const elements = document.elementsFromPoint(x, y); - const pageEl = elements.find((el) => el?.classList?.contains?.('pdf-page')); + const pageEl = elements.find((el) => el?.dataset?.pdfPage != null); if (pageEl) { const pageNumber = Number(pageEl.dataset?.pageNumber); return Number.isFinite(pageNumber) ? pageNumber : null; @@ -821,7 +837,7 @@ const handleSelectionStart = (e) => { const zoom = activeZoom.value / 100; const x = (e.clientX - layerBounds.left) / zoom; const y = (e.clientY - layerBounds.top) / zoom; - updateSelection({ startX: x, startY: y, page: pageNumber }); + updateSelection({ startX: x, startY: y, page: pageNumber, source: 'pdf' }); selectionLayer.value.addEventListener('mousemove', handleDragMove); }); }; @@ -850,6 +866,7 @@ const handleDragEnd = (e) => { }, page: pageNumber ?? 1, documentId: documents.value[0].id, + source: 'pdf', }); handleSelectionChange(selection); @@ -873,12 +890,27 @@ const handlePdfClick = (e) => { handleSelectionStart(e); }; +const handlePdfSelectionRaw = ({ selectionBounds, documentId, page }) => { + if (!selectionBounds || !documentId) return; + const selection = useSelection({ + selectionBounds, + documentId, + page, + source: 'pdf', + }); + handleSelectionChange(selection); +}; + watch( () => activeZoom.value, (zoom) => { if (proxy.$superdoc.config.useLayoutEngine !== false) { PresentationEditor.setGlobalZoom((zoom ?? 100) / 100); } + + const pdfViewer = getPDFViewer(); + pdfViewer?.updateScale((zoom ?? 100) / 100); + nextTick(() => { updateWhiteboardPageSizes(); updateWhiteboardPageOffsets(); @@ -910,6 +942,10 @@ const { documents, modules, }); + +const getPDFViewer = () => { + return Array.isArray(pdfViewerRef.value) ? pdfViewerRef.value[0] : pdfViewerRef.value; +};