diff --git a/static/pdf.js/pandora.css b/static/pdf.js/pandora.css new file mode 100644 index 000000000..89295a5a6 --- /dev/null +++ b/static/pdf.js/pandora.css @@ -0,0 +1,38 @@ + +.toolbarButton.cropFile::before, + .secondaryToolbarButton.cropFile::before { + mask-image: url(custom/toolbarButton-crop.png); +} +@media screen and (min-resolution: 2dppx) { + .toolbarButton.cropFile::before, + .secondaryToolbarButton.cropFile::before { + mask-image: url(custom/toolbarButton-crop@2x.png); + } +} + +.verticalToolbarSeparator.hiddenMediumView, +#print, +#openFile, +#editorModeButtons { + display: none; +} + +.page .crop-overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + //background: rgba(0,0,0,0.5); + cursor: crosshair; + z-index: 100; +} +.page .crop-overlay.inactive { + pointer-events: none; + cursor: default; +} + +.page .crop-overlay canvas { + width: 100%; + height: 100%; +} diff --git a/static/pdf.js/pandora.js b/static/pdf.js/pandora.js new file mode 100644 index 000000000..50a86bd4e --- /dev/null +++ b/static/pdf.js/pandora.js @@ -0,0 +1,215 @@ +var cropInactive = true +var info = {} +var cache = {} +var documentId +var baseUrl = document.location.protocol + '//' + document.location.host + +var div = document.createElement("div") +div.innerHTML = ` + +` +var cropFile = div.querySelector("#cropFile") + +document.querySelector('#toolbarViewerRight').insertBefore(cropFile, document.querySelector('#toolbarViewerRight').firstChild) + +async function archiveAPI(action, data) { + var url = baseUrl + '/api/' + var key = JSON.stringify([action, data]) + if (!cache[key]) { + var response = await fetch(url, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + action: action, + data: data + }) + }) + cache[key] = await response.json() + } + return cache[key] +} + +function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie !== '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i].trim(); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; +} + +function renderCropOverlay(root, documentId, page) { + var canvas = document.createElement('canvas') + root.appendChild(canvas) + canvas.width = canvas.clientWidth + canvas.height = canvas.clientHeight + var ctx = canvas.getContext('2d'); + var viewerContainer = document.querySelector('#viewerContainer') + var bounds = root.getBoundingClientRect(); + var base = 2048 + var scale = Math.max(bounds.height, bounds.width) / base + var last_mousex = last_mousey = 0; + var mousex = mousey = 0; + var mousedown = false; + var p = { + top: 0, + left: 0, + bottom: 0, + right: 0 + } + + canvas.addEventListener('mousedown', function(e) { + let bounds = root.getBoundingClientRect(); + last_mousex = e.clientX - bounds.left; + last_mousey = e.clientY - bounds.top; + p.top = parseInt(last_mousey / scale) + p.left = parseInt(last_mousex / scale) + mousedown = true; + }); + + document.addEventListener('mouseup', function(e) { + if (mousedown) { + mousedown = false; + p.bottom = parseInt(mousey / scale) + p.right = parseInt(mousex / scale) + + if (p.top > p.bottom) { + var t = p.top + p.top = p.bottom + p.bottom = t + } + if (p.left > p.right) { + var t = p.left + p.left = p.right + p.right = t + } + var url = `${baseUrl}/documents/${documentId}/2048p${page},${p.left},${p.top},${p.right},${p.bottom}.jpg` + info.url = `${baseUrl}/document/${documentId}/${page}` + info.page = page + if (p.left != p.right && p.top != p.bottom) { + var context = formatOutput(info, url) + copyToClipboard(context) + addToRecent({ + document: documentId, + page: parseInt(page), + title: info.title, + type: 'fragment', + link: `${baseUrl}/documents/${documentId}/${page}`, + src: url + }) + } + } + }); + + canvas.addEventListener('mousemove', function(e) { + let bounds = root.getBoundingClientRect(); + mousex = e.clientX - bounds.left; + mousey = e.clientY - bounds.top; + + if(mousedown) { + ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight) + ctx.beginPath() + var width = mousex - last_mousex + var height = mousey - last_mousey + ctx.rect(last_mousex, last_mousey, width, height) + ctx.strokeStyle = 'black'; + ctx.lineWidth = 2; + ctx.stroke(); + } + }); +} + + +const copyToClipboard = str => { + const el = document.createElement('textarea'); + el.value = str; + el.setAttribute('readonly', ''); + el.style.position = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); +}; + + +function getInfo(documentId) { + archiveAPI('getDocument', {id: documentId, keys: ['title', 'pages']}).then(result => { + info.title = result.data.title + info.pages = result.data.pages + info.id = documentId + }) +} + +function formatOutput(info, url) { + var output = `${url}\n${info.title}, Page ${info.page}\n${info.url}` + return output +} + +const addToRecent = obj => { + var recent = [] + if (localStorage['recentClippings']) { + recent = JSON.parse(localStorage['recentClippings']) + } + recent.unshift(obj) + localStorage['recentClippings'] = JSON.stringify(recent) +} + +function initOverlay() { + document.querySelector('#cropFile').addEventListener('click', event=> { + if (cropInactive) { + event.target.style.background = 'red' + cropInactive = false + document.querySelectorAll('.crop-overlay.inactive').forEach(element => { + element.classList.remove('inactive') + }) + } else { + event.target.style.background = '' + cropInactive = true + document.querySelectorAll('.crop-overlay').forEach(element => { + element.classList.add('inactive') + }) + } + }) + var first = true + PDFViewerApplication.initializedPromise.then(function() { + PDFViewerApplication.pdfViewer.eventBus.on("pagesinit", function(event) { + documentId = PDFViewerApplication.url.split('/').splice(-2).shift() + getInfo(documentId) + document.querySelector('#viewerContainer').addEventListener('scroll', event => { + if (window.parent && window.parent.postMessage) { + if (first) { + first = false + } else { + window.parent.postMessage({event: 'scrolled', top: event.target.scrollTop}) + } + } + }) + }) + PDFViewerApplication.pdfViewer.eventBus.on("pagerender", function(event) { + var page = event.pageNumber.toString() + var div = event.source.div + var overlay = document.createElement('div') + overlay.classList.add('crop-overlay') + overlay.id = 'overlay' + page + if (cropInactive) { + overlay.classList.add('inactive') + } + div.appendChild(overlay) + renderCropOverlay(overlay, documentId, page) + }) + }) +} + +document.addEventListener('DOMContentLoaded', function() { + window.PDFViewerApplication ? initOverlay() : document.addEventListener("webviewerloaded", initOverlay) +})