mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	Feature/undo page break (#2389)
* Fix delete selected Fix add page break where selected Added undo logic for page breaks * Add pages undo capability * Fix page break when selected logic
This commit is contained in:
		
							parent
							
								
									99d1b46d97
								
							
						
					
					
						commit
						d888ed1ae0
					
				| @ -1,8 +1,10 @@ | ||||
| import { MovePageUpCommand, MovePageDownCommand } from "./commands/move-page.js"; | ||||
| import { RemoveSelectedCommand } from "./commands/remove.js"; | ||||
| import { RotateAllCommand, RotateElementCommand } from "./commands/rotate.js"; | ||||
| import { SplitAllCommand } from "./commands/split.js"; | ||||
| import { UndoManager } from "./UndoManager.js"; | ||||
| import {MovePageUpCommand, MovePageDownCommand} from './commands/move-page.js'; | ||||
| import {RemoveSelectedCommand} from './commands/remove.js'; | ||||
| import {RotateAllCommand, RotateElementCommand} from './commands/rotate.js'; | ||||
| import {SplitAllCommand} from './commands/split.js'; | ||||
| import {UndoManager} from './UndoManager.js'; | ||||
| import {PageBreakCommand} from './commands/page-break.js'; | ||||
| import {AddFilesCommand} from './commands/add-page.js'; | ||||
| 
 | ||||
| class PdfContainer { | ||||
|   fileName; | ||||
| @ -34,7 +36,7 @@ class PdfContainer { | ||||
|     this.updateSelectedPagesDisplay = this.updateSelectedPagesDisplay.bind(this); | ||||
|     this.toggleSelectPageVisibility = this.toggleSelectPageVisibility.bind(this); | ||||
|     this.updatePagesFromCSV = this.updatePagesFromCSV.bind(this); | ||||
|     this.addFilesBlankAll = this.addFilesBlankAll.bind(this) | ||||
|     this.addFilesBlankAll = this.addFilesBlankAll.bind(this); | ||||
|     this.removeAllElements = this.removeAllElements.bind(this); | ||||
|     this.resetPages = this.resetPages.bind(this); | ||||
| 
 | ||||
| @ -63,7 +65,7 @@ class PdfContainer { | ||||
|     window.updatePagesFromCSV = this.updatePagesFromCSV; | ||||
|     window.updateSelectedPagesDisplay = this.updateSelectedPagesDisplay; | ||||
|     window.updatePageNumbersAndCheckboxes = this.updatePageNumbersAndCheckboxes; | ||||
|     window.addFilesBlankAll = this.addFilesBlankAll | ||||
|     window.addFilesBlankAll = this.addFilesBlankAll; | ||||
|     window.removeAllElements = this.removeAllElements; | ||||
|     window.resetPages = this.resetPages; | ||||
| 
 | ||||
| @ -76,7 +78,7 @@ class PdfContainer { | ||||
| 
 | ||||
|       undoBtn.disabled = !canUndo; | ||||
|       redoBtn.disabled = !canRedo; | ||||
|     }) | ||||
|     }); | ||||
| 
 | ||||
|     window.undo = () => { | ||||
|       if (undoManager.canUndo()) undoManager.undo(); | ||||
| @ -84,7 +86,7 @@ class PdfContainer { | ||||
|         undoBtn.disabled = !undoManager.canUndo(); | ||||
|         redoBtn.disabled = !undoManager.canRedo(); | ||||
|       } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     window.redo = () => { | ||||
|       if (undoManager.canRedo()) undoManager.redo(); | ||||
| @ -92,15 +94,15 @@ class PdfContainer { | ||||
|         undoBtn.disabled = !undoManager.canUndo(); | ||||
|         redoBtn.disabled = !undoManager.canRedo(); | ||||
|       } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     const filenameInput = document.getElementById("filename-input"); | ||||
|     const downloadBtn = document.getElementById("export-button"); | ||||
|     const filenameInput = document.getElementById('filename-input'); | ||||
|     const downloadBtn = document.getElementById('export-button'); | ||||
| 
 | ||||
|     filenameInput.onkeyup = this.updateFilename; | ||||
|     filenameInput.onkeydown = this.preventIllegalChars; | ||||
|     filenameInput.disabled = false; | ||||
|     filenameInput.innerText = ""; | ||||
|     filenameInput.innerText = ''; | ||||
|     downloadBtn.disabled = true; | ||||
|   } | ||||
| 
 | ||||
| @ -128,86 +130,99 @@ class PdfContainer { | ||||
|     return movePageCommand; | ||||
|   } | ||||
| 
 | ||||
|   addFiles(nextSiblingElement, blank = false) { | ||||
|     if (blank) { | ||||
|   async addFiles(element) { | ||||
|     let addFilesCommand = new AddFilesCommand( | ||||
|       element, | ||||
|       window.selectedPages, | ||||
|       this.addFilesAction.bind(this), | ||||
|       this.pagesContainer | ||||
|     ); | ||||
| 
 | ||||
|       this.addFilesBlank(nextSiblingElement); | ||||
|     await addFilesCommand.execute(); | ||||
| 
 | ||||
|     } else { | ||||
|       var input = document.createElement("input"); | ||||
|       input.type = "file"; | ||||
|     this.undoManager.pushUndoClearRedo(addFilesCommand); | ||||
|   } | ||||
| 
 | ||||
|   async addFilesAction(nextSiblingElement) { | ||||
|     let pages = []; | ||||
|     return new Promise((resolve) => { | ||||
|       var input = document.createElement('input'); | ||||
|       input.type = 'file'; | ||||
|       input.multiple = true; | ||||
|       input.setAttribute("accept", "application/pdf,image/*"); | ||||
|       input.setAttribute('accept', 'application/pdf,image/*'); | ||||
| 
 | ||||
|       input.onchange = async (e) => { | ||||
|         const files = e.target.files; | ||||
| 
 | ||||
|         this.addFilesFromFiles(files, nextSiblingElement); | ||||
|         this.updateFilename(files ? files[0].name : ""); | ||||
|         const selectAll = document.getElementById("select-pages-container"); | ||||
|         selectAll.classList.toggle("hidden", false); | ||||
|         if (files.length > 0) { | ||||
|           pages = await this.addFilesFromFiles(files, nextSiblingElement, pages); | ||||
|           this.updateFilename(files[0].name); | ||||
|           const selectAll = document.getElementById('select-pages-container'); | ||||
|           selectAll.classList.toggle('hidden', false); | ||||
|         } | ||||
|         resolve(pages); | ||||
|       }; | ||||
| 
 | ||||
|       input.click(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async addFilesFromFiles(files, nextSiblingElement) { | ||||
|     this.fileName = files[0].name; | ||||
|     for (var i = 0; i < files.length; i++) { | ||||
|       const startTime = Date.now(); | ||||
|       let processingTime, errorMessage = null, pageCount = 0; | ||||
|       try { | ||||
|       const file = files[i]; | ||||
|       if (file.type === "application/pdf") { | ||||
|         const { renderer, pdfDocument } = await this.loadFile(file); | ||||
|         pageCount = renderer.pageCount || 0; | ||||
|         await this.addPdfFile(renderer, pdfDocument, nextSiblingElement); | ||||
|       } else if (file.type.startsWith("image/")) { | ||||
|         await this.addImageFile(file, nextSiblingElement); | ||||
|       } | ||||
|       processingTime = Date.now() - startTime; | ||||
|       this.captureFileProcessingEvent(true, file, processingTime, null, pageCount); | ||||
|     } catch (error) { | ||||
|       processingTime = Date.now() - startTime; | ||||
|       errorMessage = error.message || "Unknown error"; | ||||
|       this.captureFileProcessingEvent(false, files[i], processingTime, errorMessage, pageCount); | ||||
|     } | ||||
|     } | ||||
| 
 | ||||
|     document.querySelectorAll(".enable-on-file").forEach((element) => { | ||||
|       element.disabled = false; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|  captureFileProcessingEvent(success, file, processingTime, errorMessage, pageCount) { | ||||
|   try{ | ||||
|   if(analyticsEnabled){ | ||||
|   posthog.capture('file_processing', { | ||||
|     success, | ||||
|     file_type: file?.type || 'unknown', | ||||
|     file_size: file?.size || 0, | ||||
|     processing_time: processingTime, | ||||
|     error_message: errorMessage, | ||||
|     pdf_pages: pageCount, | ||||
|   }); | ||||
| } | ||||
| }catch{ | ||||
| } | ||||
| } | ||||
|   async addFilesFromFiles(files, nextSiblingElement, pages) { | ||||
|     this.fileName = files[0].name; | ||||
|     for (var i = 0; i < files.length; i++) { | ||||
|       const startTime = Date.now(); | ||||
|       let processingTime, | ||||
|         errorMessage = null, | ||||
|         pageCount = 0; | ||||
|       try { | ||||
|         const file = files[i]; | ||||
|         if (file.type === 'application/pdf') { | ||||
|           const {renderer, pdfDocument} = await this.loadFile(file); | ||||
|           pageCount = renderer.pageCount || 0; | ||||
|           pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages); | ||||
|         } else if (file.type.startsWith('image/')) { | ||||
|           pages = await this.addImageFile(file, nextSiblingElement, pages); | ||||
|         } | ||||
|         processingTime = Date.now() - startTime; | ||||
|         this.captureFileProcessingEvent(true, file, processingTime, null, pageCount); | ||||
|       } catch (error) { | ||||
|         processingTime = Date.now() - startTime; | ||||
|         errorMessage = error.message || 'Unknown error'; | ||||
|         this.captureFileProcessingEvent(false, files[i], processingTime, errorMessage, pageCount); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     document.querySelectorAll('.enable-on-file').forEach((element) => { | ||||
|       element.disabled = false; | ||||
|     }); | ||||
|     return pages; | ||||
|   } | ||||
| 
 | ||||
|   async addFilesBlank(nextSiblingElement) { | ||||
|   captureFileProcessingEvent(success, file, processingTime, errorMessage, pageCount) { | ||||
|     try { | ||||
|       if (analyticsEnabled) { | ||||
|         posthog.capture('file_processing', { | ||||
|           success, | ||||
|           file_type: file?.type || 'unknown', | ||||
|           file_size: file?.size || 0, | ||||
|           processing_time: processingTime, | ||||
|           error_message: errorMessage, | ||||
|           pdf_pages: pageCount, | ||||
|         }); | ||||
|       } | ||||
|     } catch {} | ||||
|   } | ||||
| 
 | ||||
|   async addFilesBlank(nextSiblingElement, pages) { | ||||
|     let doc = await PDFLib.PDFDocument.create(); | ||||
|     let docBytes = await doc.save(); | ||||
| 
 | ||||
|     const url = URL.createObjectURL(new Blob([docBytes], { type: 'application/pdf' })); | ||||
|     const url = URL.createObjectURL(new Blob([docBytes], {type: 'application/pdf'})); | ||||
| 
 | ||||
|     const renderer = await this.toRenderer(url); | ||||
| 
 | ||||
|     await this.addPdfFile(renderer, doc, nextSiblingElement); | ||||
|     pages = await this.addPdfFile(renderer, doc, nextSiblingElement, pages); | ||||
|     return pages; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   rotateElement(element, deg) { | ||||
|     let rotateCommand = new RotateElementCommand(element, deg); | ||||
|     rotateCommand.execute(); | ||||
| @ -215,37 +230,43 @@ class PdfContainer { | ||||
|     return rotateCommand; | ||||
|   } | ||||
| 
 | ||||
|   async addPdfFile(renderer, pdfDocument, nextSiblingElement) { | ||||
|   async addPdfFile(renderer, pdfDocument, nextSiblingElement, pages) { | ||||
|     for (var i = 0; i < renderer.pageCount; i++) { | ||||
|       const div = document.createElement("div"); | ||||
|       const div = document.createElement('div'); | ||||
| 
 | ||||
|       div.classList.add("page-container"); | ||||
|       div.id = "page-container-" + (i + 1); | ||||
|       var img = document.createElement("img"); | ||||
|       img.classList.add("page-image"); | ||||
|       div.classList.add('page-container'); | ||||
|       div.id = 'page-container-' + (i + 1); | ||||
|       var img = document.createElement('img'); | ||||
|       img.classList.add('page-image'); | ||||
|       const imageSrc = await renderer.renderPage(i); | ||||
|       img.src = imageSrc; | ||||
|       img.pageIdx = i; | ||||
|       img.rend = renderer; | ||||
|       img.doc = pdfDocument; | ||||
|       div.appendChild(img); | ||||
| 
 | ||||
|       this.pdfAdapters.forEach((adapter) => { | ||||
|         adapter.adapt?.(div); | ||||
|       }); | ||||
| 
 | ||||
|       if (nextSiblingElement) { | ||||
|         this.pagesContainer.insertBefore(div, nextSiblingElement); | ||||
|       } else { | ||||
|         this.pagesContainer.appendChild(div); | ||||
|       } | ||||
| 
 | ||||
|       pages.push(div); | ||||
|     } | ||||
| 
 | ||||
|     return pages; | ||||
|   } | ||||
| 
 | ||||
|   async addImageFile(file, nextSiblingElement) { | ||||
|     const div = document.createElement("div"); | ||||
|     div.classList.add("page-container"); | ||||
|   async addImageFile(file, nextSiblingElement, pages) { | ||||
|     const div = document.createElement('div'); | ||||
|     div.classList.add('page-container'); | ||||
| 
 | ||||
|     var img = document.createElement("img"); | ||||
|     img.classList.add("page-image"); | ||||
|     var img = document.createElement('img'); | ||||
|     img.classList.add('page-image'); | ||||
|     img.src = URL.createObjectURL(file); | ||||
|     div.appendChild(img); | ||||
| 
 | ||||
| @ -257,17 +278,19 @@ class PdfContainer { | ||||
|     } else { | ||||
|       this.pagesContainer.appendChild(div); | ||||
|     } | ||||
|     pages.push(div); | ||||
|     return pages; | ||||
|   } | ||||
| 
 | ||||
|   async loadFile(file) { | ||||
|     var objectUrl = URL.createObjectURL(file); | ||||
|     var pdfDocument = await this.toPdfLib(objectUrl); | ||||
|     var renderer = await this.toRenderer(objectUrl); | ||||
|     return { renderer, pdfDocument }; | ||||
|     return {renderer, pdfDocument}; | ||||
|   } | ||||
| 
 | ||||
|   async toRenderer(objectUrl) { | ||||
|     pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdfjs-legacy/pdf.worker.mjs"; | ||||
|     pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; | ||||
|     const pdf = await pdfjsLib.getDocument(objectUrl).promise; | ||||
|     return { | ||||
|       document: pdf, | ||||
| @ -275,7 +298,7 @@ class PdfContainer { | ||||
|       renderPage: async function (pageIdx) { | ||||
|         const page = await this.document.getPage(pageIdx + 1); | ||||
| 
 | ||||
|         const canvas = document.createElement("canvas"); | ||||
|         const canvas = document.createElement('canvas'); | ||||
| 
 | ||||
|         // set the canvas size to the size of the page
 | ||||
|         if (page.rotate == 90 || page.rotate == 270) { | ||||
| @ -288,8 +311,8 @@ class PdfContainer { | ||||
| 
 | ||||
|         // render the page onto the canvas
 | ||||
|         var renderContext = { | ||||
|           canvasContext: canvas.getContext("2d"), | ||||
|           viewport: page.getViewport({ scale: 1 }), | ||||
|           canvasContext: canvas.getContext('2d'), | ||||
|           viewport: page.getViewport({scale: 1}), | ||||
|         }; | ||||
| 
 | ||||
|         await page.render(renderContext).promise; | ||||
| @ -316,7 +339,7 @@ class PdfContainer { | ||||
|       //if in page select mode is active rotate only selected pages
 | ||||
|       if (window.selectPage && !window.selectedPages.includes(pageIndex)) continue; | ||||
| 
 | ||||
|       const img = child.querySelector("img"); | ||||
|       const img = child.querySelector('img'); | ||||
|       if (!img) continue; | ||||
| 
 | ||||
|       elementsToRotate.push(img); | ||||
| @ -328,12 +351,12 @@ class PdfContainer { | ||||
|     this.undoManager.pushUndoClearRedo(rotateAllCommand); | ||||
|   } | ||||
| 
 | ||||
|   removeAllElements(){ | ||||
|     let pageContainerNodeList = document.querySelectorAll(".page-container"); | ||||
|   removeAllElements() { | ||||
|     let pageContainerNodeList = document.querySelectorAll('.page-container'); | ||||
|     for (var i = 0; i < pageContainerNodeList.length; i++) { | ||||
|       pageContainerNodeList[i].remove(); | ||||
|     } | ||||
|     document.querySelectorAll(".enable-on-file").forEach((element) => { | ||||
|     document.querySelectorAll('.enable-on-file').forEach((element) => { | ||||
|       element.disabled = true; | ||||
|     }); | ||||
|   } | ||||
| @ -345,25 +368,24 @@ class PdfContainer { | ||||
|       window.selectedPages, | ||||
|       this.updatePageNumbersAndCheckboxes | ||||
|     ); | ||||
| 
 | ||||
|     removeSelectedCommand.execute(); | ||||
|     this.undoManager.pushUndoClearRedo(removeSelectedCommand); | ||||
|   } | ||||
| 
 | ||||
|   toggleSelectAll() { | ||||
|     const checkboxes = document.querySelectorAll(".pdf-actions_checkbox"); | ||||
|     const checkboxes = document.querySelectorAll('.pdf-actions_checkbox'); | ||||
|     window.selectAll = !window.selectAll; | ||||
|     const selectIcon = document.getElementById("select-All-Container"); | ||||
|     const deselectIcon = document.getElementById("deselect-All-Container"); | ||||
|     const selectIcon = document.getElementById('select-All-Container'); | ||||
|     const deselectIcon = document.getElementById('deselect-All-Container'); | ||||
| 
 | ||||
|     if (selectIcon.style.display === "none") { | ||||
|       selectIcon.style.display = "inline"; | ||||
|       deselectIcon.style.display = "none"; | ||||
|     if (selectIcon.style.display === 'none') { | ||||
|       selectIcon.style.display = 'inline'; | ||||
|       deselectIcon.style.display = 'none'; | ||||
|     } else { | ||||
|       selectIcon.style.display = "none"; | ||||
|       deselectIcon.style.display = "inline"; | ||||
|       selectIcon.style.display = 'none'; | ||||
|       deselectIcon.style.display = 'inline'; | ||||
|     } | ||||
|     checkboxes.forEach((checkbox) => { | ||||
| 
 | ||||
|       checkbox.checked = window.selectAll; | ||||
| 
 | ||||
|       const pageNumber = Array.from(checkbox.parentNode.parentNode.children).indexOf(checkbox.parentNode) + 1; | ||||
| @ -386,18 +408,20 @@ class PdfContainer { | ||||
|   parseCSVInput(csvInput, maxPageIndex) { | ||||
|     const pages = new Set(); | ||||
| 
 | ||||
|     csvInput.split(",").forEach((item) => { | ||||
|       const range = item.split("-").map((p) => parseInt(p.trim())); | ||||
|     csvInput.split(',').forEach((item) => { | ||||
|       const range = item.split('-').map((p) => parseInt(p.trim())); | ||||
|       if (range.length === 2) { | ||||
|         const [start, end] = range; | ||||
|         for (let i = start; i <= end && i <= maxPageIndex; i++) { | ||||
|           if (i > 0) { // Ensure the page number is greater than 0
 | ||||
|           if (i > 0) { | ||||
|             // Ensure the page number is greater than 0
 | ||||
|             pages.add(i); | ||||
|           } | ||||
|         } | ||||
|       } else if (range.length === 1 && Number.isInteger(range[0])) { | ||||
|         const page = range[0]; | ||||
|         if (page > 0 && page <= maxPageIndex) { // Ensure page is within valid range
 | ||||
|         if (page > 0 && page <= maxPageIndex) { | ||||
|           // Ensure page is within valid range
 | ||||
|           pages.add(page); | ||||
|         } | ||||
|       } | ||||
| @ -407,24 +431,24 @@ class PdfContainer { | ||||
|   } | ||||
| 
 | ||||
|   updatePagesFromCSV() { | ||||
|     const csvInput = document.getElementById("csv-input").value; | ||||
|     const csvInput = document.getElementById('csv-input').value; | ||||
| 
 | ||||
|     const allPages = this.pagesContainer.querySelectorAll(".page-container"); | ||||
|     const allPages = this.pagesContainer.querySelectorAll('.page-container'); | ||||
|     const maxPageIndex = allPages.length; | ||||
| 
 | ||||
|     window.selectedPages = this.parseCSVInput(csvInput, maxPageIndex); | ||||
| 
 | ||||
|     this.updateSelectedPagesDisplay(); | ||||
| 
 | ||||
|     const allCheckboxes = document.querySelectorAll(".pdf-actions_checkbox"); | ||||
|     const allCheckboxes = document.querySelectorAll('.pdf-actions_checkbox'); | ||||
|     allCheckboxes.forEach((checkbox) => { | ||||
|       const page = parseInt(checkbox.getAttribute("data-page-number")); | ||||
|       const page = parseInt(checkbox.getAttribute('data-page-number')); | ||||
|       checkbox.checked = window.selectedPages.includes(page); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   formatSelectedPages(pages) { | ||||
|     if (pages.length === 0) return ""; | ||||
|     if (pages.length === 0) return ''; | ||||
| 
 | ||||
|     pages.sort((a, b) => a - b); // Sort the page numbers in ascending order
 | ||||
|     const ranges = []; | ||||
| @ -445,27 +469,27 @@ class PdfContainer { | ||||
|     // Add the last range
 | ||||
|     ranges.push(start === end ? `${start}` : `${start}-${end}`); | ||||
| 
 | ||||
|     return ranges.join(", "); | ||||
|     return ranges.join(', '); | ||||
|   } | ||||
| 
 | ||||
|   updateSelectedPagesDisplay() { | ||||
|     const selectedPagesList = document.getElementById("selected-pages-list"); | ||||
|     const selectedPagesInput = document.getElementById("csv-input"); | ||||
|     selectedPagesList.innerHTML = ""; // Clear the list
 | ||||
|     const selectedPagesList = document.getElementById('selected-pages-list'); | ||||
|     const selectedPagesInput = document.getElementById('csv-input'); | ||||
|     selectedPagesList.innerHTML = ''; // Clear the list
 | ||||
|     window.selectedPages.sort((a, b) => a - b); | ||||
|     window.selectedPages.forEach((page) => { | ||||
|       const pageItem = document.createElement("div"); | ||||
|       pageItem.className = "page-item"; | ||||
|       const pageItem = document.createElement('div'); | ||||
|       pageItem.className = 'page-item'; | ||||
| 
 | ||||
|       const pageNumber = document.createElement("span"); | ||||
|       const pageNumber = document.createElement('span'); | ||||
|       const pagelabel = /*[[#{multiTool.page}]]*/ 'Page'; | ||||
|       pageNumber.className = "selected-page-number"; | ||||
|       pageNumber.className = 'selected-page-number'; | ||||
|       pageNumber.innerText = `${pagelabel} ${page}`; | ||||
|       pageItem.appendChild(pageNumber); | ||||
| 
 | ||||
|       const removeBtn = document.createElement("span"); | ||||
|       removeBtn.className = "remove-btn"; | ||||
|       removeBtn.innerHTML = "✕"; | ||||
|       const removeBtn = document.createElement('span'); | ||||
|       removeBtn.className = 'remove-btn'; | ||||
|       removeBtn.innerHTML = '✕'; | ||||
| 
 | ||||
|       // Remove page from selected pages list and update display and checkbox
 | ||||
|       removeBtn.onclick = () => { | ||||
| @ -489,7 +513,7 @@ class PdfContainer { | ||||
|   parsePageRanges(ranges) { | ||||
|     const pages = new Set(); | ||||
| 
 | ||||
|     ranges.split(',').forEach(range => { | ||||
|     ranges.split(',').forEach((range) => { | ||||
|       const [start, end] = range.split('-').map(Number); | ||||
|       if (end) { | ||||
|         for (let i = start; i <= end; i++) { | ||||
| @ -503,23 +527,25 @@ class PdfContainer { | ||||
|     return Array.from(pages).sort((a, b) => a - b); | ||||
|   } | ||||
| 
 | ||||
|   addFilesBlankAll() { | ||||
|     const allPages = this.pagesContainer.querySelectorAll(".page-container"); | ||||
|     allPages.forEach((page, index) => { | ||||
|       if (index !== 0) { | ||||
|         this.addFiles(page, true) | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|   async addFilesBlankAll() { | ||||
|     const allPages = this.pagesContainer.querySelectorAll('.page-container'); | ||||
| 
 | ||||
|   splitAll() { | ||||
|     const allPages = this.pagesContainer.querySelectorAll(".page-container"); | ||||
|     let splitAllCommand = new SplitAllCommand( | ||||
|     let pageBreakCommand = new PageBreakCommand( | ||||
|       allPages, | ||||
|       window.selectPage, | ||||
|       window.selectedPages, | ||||
|       "split-before" | ||||
|       this.addFilesBlank.bind(this), | ||||
|       this.pagesContainer | ||||
|     ); | ||||
| 
 | ||||
|     await pageBreakCommand.execute(); | ||||
| 
 | ||||
|     this.undoManager.pushUndoClearRedo(pageBreakCommand); | ||||
|   } | ||||
| 
 | ||||
|   splitAll() { | ||||
|     const allPages = this.pagesContainer.querySelectorAll('.page-container'); | ||||
|     let splitAllCommand = new SplitAllCommand(allPages, window.selectPage, window.selectedPages, 'split-before'); | ||||
|     splitAllCommand.execute(); | ||||
| 
 | ||||
|     this.undoManager.pushUndoClearRedo(splitAllCommand); | ||||
| @ -529,7 +555,7 @@ class PdfContainer { | ||||
|     const baseDocument = await PDFLib.PDFDocument.load(baseDocBytes); | ||||
|     const pageNum = baseDocument.getPages().length; | ||||
| 
 | ||||
|     splitters.sort((a, b) => a - b);; // We'll sort the separator indexes just in case querySelectorAll does something funny.
 | ||||
|     splitters.sort((a, b) => a - b); // We'll sort the separator indexes just in case querySelectorAll does something funny.
 | ||||
|     splitters.push(pageNum); // We'll also add a faux separator at the end in order to get the pages after the last separator.
 | ||||
| 
 | ||||
|     const splitDocuments = []; | ||||
| @ -540,18 +566,18 @@ class PdfContainer { | ||||
| 
 | ||||
|       let firstPage = splitterIndex === 0 ? 0 : splitters[splitterIndex - 1]; | ||||
| 
 | ||||
|       const pageIndices = Array.from({ length: splitterPosition - firstPage }, (value, key) => firstPage + key); | ||||
|       const pageIndices = Array.from({length: splitterPosition - firstPage}, (value, key) => firstPage + key); | ||||
| 
 | ||||
|       const copiedPages = await subDocument.copyPages(baseDocument, pageIndices); | ||||
| 
 | ||||
|       copiedPages.forEach(copiedPage => { | ||||
|       copiedPages.forEach((copiedPage) => { | ||||
|         subDocument.addPage(copiedPage); | ||||
|       }); | ||||
| 
 | ||||
|       const subDocumentBytes = await subDocument.save(); | ||||
| 
 | ||||
|       splitDocuments.push(subDocumentBytes); | ||||
|     }; | ||||
|     } | ||||
| 
 | ||||
|     return splitDocuments; | ||||
|   } | ||||
| @ -560,8 +586,10 @@ class PdfContainer { | ||||
|     const zip = new JSZip(); | ||||
| 
 | ||||
|     for (let i = 0; i < pdfBytesArray.length; i++) { | ||||
|       const documentBlob = new Blob([pdfBytesArray[i]], { type: "application/pdf" }); | ||||
|       zip.file(baseNameString + "-" + (i + 1) + ".pdf", documentBlob); | ||||
|       const documentBlob = new Blob([pdfBytesArray[i]], { | ||||
|         type: 'application/pdf', | ||||
|       }); | ||||
|       zip.file(baseNameString + '-' + (i + 1) + '.pdf', documentBlob); | ||||
|     } | ||||
| 
 | ||||
|     return zip; | ||||
| @ -569,10 +597,10 @@ class PdfContainer { | ||||
| 
 | ||||
|   async exportPdf(selected) { | ||||
|     const pdfDoc = await PDFLib.PDFDocument.create(); | ||||
|     const pageContainers = this.pagesContainer.querySelectorAll(".page-container"); // Select all .page-container elements
 | ||||
|     const pageContainers = this.pagesContainer.querySelectorAll('.page-container'); // Select all .page-container elements
 | ||||
|     for (var i = 0; i < pageContainers.length; i++) { | ||||
|       if (!selected || window.selectedPages.includes(i + 1)) { | ||||
|         const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
 | ||||
|         const img = pageContainers[i].querySelector('img'); // Find the img element within each .page-container
 | ||||
|         if (!img) continue; | ||||
|         let page; | ||||
|         if (img.doc) { | ||||
| @ -612,7 +640,7 @@ class PdfContainer { | ||||
|         } | ||||
|         const rotation = img.style.rotate; | ||||
|         if (rotation) { | ||||
|           const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, "")); | ||||
|           const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, '')); | ||||
|           page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle)); | ||||
|         } | ||||
|       } | ||||
| @ -621,11 +649,11 @@ class PdfContainer { | ||||
|     pdfDoc.setProducer(stirlingPDFLabel); | ||||
| 
 | ||||
|     const pdfBytes = await pdfDoc.save(); | ||||
|     const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" }); | ||||
|     const pdfBlob = new Blob([pdfBytes], {type: 'application/pdf'}); | ||||
| 
 | ||||
|     const filenameInput = document.getElementById("filename-input"); | ||||
|     const filenameInput = document.getElementById('filename-input'); | ||||
| 
 | ||||
|     let inputArr = filenameInput.value.split("."); | ||||
|     let inputArr = filenameInput.value.split('.'); | ||||
| 
 | ||||
|     if (inputArr !== null && inputArr !== undefined && inputArr.length > 0) { | ||||
|       inputArr = inputArr.filter((n) => n); // remove all empty strings, nulls or undefined
 | ||||
| @ -634,17 +662,18 @@ class PdfContainer { | ||||
|         inputArr.pop(); // remove right part after last dot
 | ||||
|       } | ||||
| 
 | ||||
|       filenameInput.value = inputArr.join(""); | ||||
|       filenameInput.value = inputArr.join(''); | ||||
|       this.fileName = filenameInput.value; | ||||
|     } | ||||
| 
 | ||||
|     const separators = this.pagesContainer.querySelectorAll(".split-before"); | ||||
|     if (separators.length !== 0) { // Split the pdf if there are separators.
 | ||||
|       const baseName = this.fileName ? this.fileName : "managed"; | ||||
|     const separators = this.pagesContainer.querySelectorAll('.split-before'); | ||||
|     if (separators.length !== 0) { | ||||
|       // Split the pdf if there are separators.
 | ||||
|       const baseName = this.fileName ? this.fileName : 'managed'; | ||||
| 
 | ||||
|       const pagesArray = Array.from(this.pagesContainer.children); | ||||
|       const splitters = []; | ||||
|       separators.forEach(page => { | ||||
|       separators.forEach((page) => { | ||||
|         const pageIndex = pagesArray.indexOf(page); | ||||
|         if (pageIndex !== 0) { | ||||
|           splitters.push(pageIndex); | ||||
| @ -655,80 +684,80 @@ class PdfContainer { | ||||
|       const archivedDocuments = await this.nameAndArchiveFiles(splitDocuments, baseName); | ||||
| 
 | ||||
|       const self = this; | ||||
|       archivedDocuments.generateAsync({ type: "base64" }).then(function (base64) { | ||||
|         const url = "data:application/zip;base64," + base64; | ||||
|         self.downloadLink = document.createElement("a"); | ||||
|       archivedDocuments.generateAsync({type: 'base64'}).then(function (base64) { | ||||
|         const url = 'data:application/zip;base64,' + base64; | ||||
|         self.downloadLink = document.createElement('a'); | ||||
|         self.downloadLink.href = url; | ||||
|         self.downloadLink.setAttribute("download", baseName + ".zip"); | ||||
|         self.downloadLink.setAttribute("target", "_blank"); | ||||
|         self.downloadLink.setAttribute('download', baseName + '.zip'); | ||||
|         self.downloadLink.setAttribute('target', '_blank'); | ||||
|         self.downloadLink.click(); | ||||
|       }); | ||||
| 
 | ||||
|     } else { // Continue normally if there are no separators
 | ||||
|     } else { | ||||
|       // Continue normally if there are no separators
 | ||||
| 
 | ||||
|       const url = URL.createObjectURL(pdfBlob); | ||||
|       const downloadOption = localStorage.getItem("downloadOption"); | ||||
|       const downloadOption = localStorage.getItem('downloadOption'); | ||||
| 
 | ||||
|       if (!filenameInput.value.includes(".pdf")) { | ||||
|         filenameInput.value = filenameInput.value + ".pdf"; | ||||
|       if (!filenameInput.value.includes('.pdf')) { | ||||
|         filenameInput.value = filenameInput.value + '.pdf'; | ||||
|         this.fileName = filenameInput.value; | ||||
|       } | ||||
| 
 | ||||
|       if (downloadOption === "sameWindow") { | ||||
|       if (downloadOption === 'sameWindow') { | ||||
|         // Open the file in the same window
 | ||||
|         window.location.href = url; | ||||
|       } else if (downloadOption === "newWindow") { | ||||
|       } else if (downloadOption === 'newWindow') { | ||||
|         // Open the file in a new window
 | ||||
|         window.open(url, "_blank"); | ||||
|         window.open(url, '_blank'); | ||||
|       } else { | ||||
|         // Download the file
 | ||||
|         this.downloadLink = document.createElement("a"); | ||||
|         this.downloadLink.id = "download-link"; | ||||
|         this.downloadLink = document.createElement('a'); | ||||
|         this.downloadLink.id = 'download-link'; | ||||
|         this.downloadLink.href = url; | ||||
|         // downloadLink.download = this.fileName ? this.fileName : 'managed.pdf';
 | ||||
|         // downloadLink.download = this.fileName;
 | ||||
|         this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf"); | ||||
|         this.downloadLink.setAttribute("target", "_blank"); | ||||
|         this.downloadLink.setAttribute('download', this.fileName ? this.fileName : 'managed.pdf'); | ||||
|         this.downloadLink.setAttribute('target', '_blank'); | ||||
|         this.downloadLink.onclick = this.setDownloadAttribute; | ||||
|         this.downloadLink.click(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|    resetPages() { | ||||
|     const pageContainers = this.pagesContainer.querySelectorAll(".page-container"); | ||||
|   resetPages() { | ||||
|     const pageContainers = this.pagesContainer.querySelectorAll('.page-container'); | ||||
| 
 | ||||
|     pageContainers.forEach((container, index) => { | ||||
|       container.id = "page-container-" + (index + 1); | ||||
|       container.id = 'page-container-' + (index + 1); | ||||
|     }); | ||||
| 
 | ||||
|     const checkboxes = document.querySelectorAll(".pdf-actions_checkbox"); | ||||
|     const checkboxes = document.querySelectorAll('.pdf-actions_checkbox'); | ||||
|     window.selectAll = false; | ||||
|     const selectIcon = document.getElementById("select-All-Container"); | ||||
|     const deselectIcon = document.getElementById("deselect-All-Container"); | ||||
|     const selectIcon = document.getElementById('select-All-Container'); | ||||
|     const deselectIcon = document.getElementById('deselect-All-Container'); | ||||
| 
 | ||||
|       selectIcon.style.display = "inline"; | ||||
|       deselectIcon.style.display = "none"; | ||||
|     selectIcon.style.display = 'inline'; | ||||
|     deselectIcon.style.display = 'none'; | ||||
| 
 | ||||
|     checkboxes.forEach((checkbox) => { | ||||
|       const pageNumber = Array.from(checkbox.parentNode.parentNode.children).indexOf(checkbox.parentNode) + 1; | ||||
| 
 | ||||
|         const index = window.selectedPages.indexOf(pageNumber); | ||||
|         if (index !== -1) { | ||||
|           window.selectedPages.splice(index, 1); | ||||
|         } | ||||
|       const index = window.selectedPages.indexOf(pageNumber); | ||||
|       if (index !== -1) { | ||||
|         window.selectedPages.splice(index, 1); | ||||
|       } | ||||
|     }); | ||||
|     window.toggleSelectPageVisibility(); | ||||
|   } | ||||
| 
 | ||||
|   setDownloadAttribute() { | ||||
|     this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf"); | ||||
|     this.downloadLink.setAttribute('download', this.fileName ? this.fileName : 'managed.pdf'); | ||||
|   } | ||||
| 
 | ||||
|   updateFilename(fileName = "") { | ||||
|     const filenameInput = document.getElementById("filename-input"); | ||||
|     const pagesContainer = document.getElementById("pages-container"); | ||||
|     const downloadBtn = document.getElementById("export-button"); | ||||
|   updateFilename(fileName = '') { | ||||
|     const filenameInput = document.getElementById('filename-input'); | ||||
|     const pagesContainer = document.getElementById('pages-container'); | ||||
|     const downloadBtn = document.getElementById('export-button'); | ||||
| 
 | ||||
|     downloadBtn.disabled = pagesContainer.childElementCount === 0; | ||||
| 
 | ||||
| @ -752,38 +781,36 @@ class PdfContainer { | ||||
|     // }
 | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   toggleSelectPageVisibility() { | ||||
|     window.selectPage = !window.selectPage; | ||||
|     const checkboxes = document.querySelectorAll(".pdf-actions_checkbox"); | ||||
|     checkboxes.forEach(checkbox => { | ||||
|       checkbox.classList.toggle("hidden", !window.selectPage); | ||||
|     const checkboxes = document.querySelectorAll('.pdf-actions_checkbox'); | ||||
|     checkboxes.forEach((checkbox) => { | ||||
|       checkbox.classList.toggle('hidden', !window.selectPage); | ||||
|     }); | ||||
|     const deleteButton = document.getElementById("delete-button"); | ||||
|     deleteButton.classList.toggle("hidden", !window.selectPage); | ||||
|     const selectedPages = document.getElementById("selected-pages-display"); | ||||
|     selectedPages.classList.toggle("hidden", !window.selectPage); | ||||
|     const selectAll = document.getElementById("select-All-Container"); | ||||
|     selectAll.classList.toggle("hidden", !window.selectPage); | ||||
|     const exportSelected = document.getElementById("export-selected-button"); | ||||
|     exportSelected.classList.toggle("hidden", !window.selectPage); | ||||
|     const selectPagesButton = document.getElementById("select-pages-button"); | ||||
|     selectPagesButton.style.opacity = window.selectPage ? "1" : "0.5"; | ||||
|     const deleteButton = document.getElementById('delete-button'); | ||||
|     deleteButton.classList.toggle('hidden', !window.selectPage); | ||||
|     const selectedPages = document.getElementById('selected-pages-display'); | ||||
|     selectedPages.classList.toggle('hidden', !window.selectPage); | ||||
|     const selectAll = document.getElementById('select-All-Container'); | ||||
|     selectAll.classList.toggle('hidden', !window.selectPage); | ||||
|     const exportSelected = document.getElementById('export-selected-button'); | ||||
|     exportSelected.classList.toggle('hidden', !window.selectPage); | ||||
|     const selectPagesButton = document.getElementById('select-pages-button'); | ||||
|     selectPagesButton.style.opacity = window.selectPage ? '1' : '0.5'; | ||||
| 
 | ||||
|     if (window.selectPage) { | ||||
|       this.updatePageNumbersAndCheckboxes(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   updatePageNumbersAndCheckboxes() { | ||||
|     const pageDivs = document.querySelectorAll(".pdf-actions_container"); | ||||
|     const pageDivs = document.querySelectorAll('.pdf-actions_container'); | ||||
| 
 | ||||
|     pageDivs.forEach((div, index) => { | ||||
|       const pageNumber = index + 1; | ||||
|       const checkbox = div.querySelector(".pdf-actions_checkbox"); | ||||
|       const checkbox = div.querySelector('.pdf-actions_checkbox'); | ||||
|       checkbox.id = `selectPageCheckbox-${pageNumber}`; | ||||
|       checkbox.setAttribute("data-page-number", pageNumber); | ||||
|       checkbox.setAttribute('data-page-number', pageNumber); | ||||
|       checkbox.checked = window.selectedPages.includes(pageNumber); | ||||
|     }); | ||||
|   } | ||||
| @ -801,8 +828,10 @@ function detectImageType(uint8Array) { | ||||
|   } | ||||
| 
 | ||||
|   // Check for TIFF signature (little-endian and big-endian)
 | ||||
|   if ((uint8Array[0] === 73 && uint8Array[1] === 73 && uint8Array[2] === 42 && uint8Array[3] === 0) || | ||||
|     (uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42)) { | ||||
|   if ( | ||||
|     (uint8Array[0] === 73 && uint8Array[1] === 73 && uint8Array[2] === 42 && uint8Array[3] === 0) || | ||||
|     (uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42) | ||||
|   ) { | ||||
|     return 'TIFF'; | ||||
|   } | ||||
| 
 | ||||
| @ -814,6 +843,4 @@ function detectImageType(uint8Array) { | ||||
|   return 'UNKNOWN'; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| export default PdfContainer; | ||||
|  | ||||
							
								
								
									
										53
									
								
								src/main/resources/static/js/multitool/commands/add-page.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/main/resources/static/js/multitool/commands/add-page.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| import {Command} from './command.js'; | ||||
| 
 | ||||
| export class AddFilesCommand extends Command { | ||||
|   constructor(element, selectedPages, addFilesAction, pagesContainer) { | ||||
|     super(); | ||||
|     this.element = element; | ||||
|     this.selectedPages = selectedPages; | ||||
|     this.addFilesAction = addFilesAction; | ||||
|     this.pagesContainer = pagesContainer; | ||||
|     this.addedElements = []; | ||||
|   } | ||||
| 
 | ||||
|   async execute() { | ||||
|     const undoBtn = document.getElementById('undo-btn'); | ||||
|     undoBtn.disabled = true; | ||||
|     if (this.element) { | ||||
|       const newElement = await this.addFilesAction(this.element); | ||||
|       if (newElement) { | ||||
|         this.addedElements = newElement; | ||||
|       } | ||||
|     } else { | ||||
|       const newElement = await this.addFilesAction(false); | ||||
|       if (newElement) { | ||||
|         this.addedElements = newElement; | ||||
|       } | ||||
|     } | ||||
|     undoBtn.disabled = false; | ||||
|   } | ||||
| 
 | ||||
|   undo() { | ||||
|     this.addedElements.forEach((element) => { | ||||
|       const nextSibling = element.nextSibling; | ||||
|       this.pagesContainer.removeChild(element); | ||||
| 
 | ||||
|       if (this.pagesContainer.childElementCount === 0) { | ||||
|         const filenameInput = document.getElementById('filename-input'); | ||||
|         const filenameParagraph = document.getElementById('filename'); | ||||
|         const downloadBtn = document.getElementById('export-button'); | ||||
| 
 | ||||
|         filenameInput.disabled = true; | ||||
|         filenameInput.value = ''; | ||||
|         filenameParagraph.innerText = ''; | ||||
|         downloadBtn.disabled = true; | ||||
|       } | ||||
| 
 | ||||
|       element._nextSibling = nextSibling; | ||||
|     }); | ||||
|     this.addedElements = []; | ||||
|   } | ||||
|   redo() { | ||||
|     this.execute(); | ||||
|   } | ||||
| } | ||||
| @ -10,7 +10,9 @@ export class DeletePageCommand extends Command { | ||||
|     this.filenameInputValue = document.getElementById("filename-input").value; | ||||
| 
 | ||||
|     const filenameParagraph = document.getElementById("filename"); | ||||
|     this.filenameParagraphText = filenameParagraph ? filenameParagraph.innerText : ""; | ||||
|     this.filenameParagraphText = filenameParagraph | ||||
|       ? filenameParagraph.innerText | ||||
|       : ""; | ||||
|   } | ||||
| 
 | ||||
|   execute() { | ||||
|  | ||||
| @ -0,0 +1,59 @@ | ||||
| import {Command} from './command.js'; | ||||
| 
 | ||||
| export class PageBreakCommand extends Command { | ||||
|   constructor(elements, isSelectedInWindow, selectedPages, pageBreakCallback, pagesContainer) { | ||||
|     super(); | ||||
|     this.elements = elements; | ||||
|     this.isSelectedInWindow = isSelectedInWindow; | ||||
|     this.selectedPages = selectedPages; | ||||
|     this.pageBreakCallback = pageBreakCallback; | ||||
|     this.pagesContainer = pagesContainer; | ||||
|     this.addedElements = []; | ||||
|     this.originalStates = Array.from(elements, (element) => ({ | ||||
|       element, | ||||
|       hasContent: element.innerHTML.trim() !== '', | ||||
|     })); | ||||
|   } | ||||
| 
 | ||||
|   async execute() { | ||||
|     const undoBtn = document.getElementById('undo-btn'); | ||||
|     undoBtn.disabled = true; | ||||
|     for (const [index, element] of this.elements.entries()) { | ||||
|       if (!this.isSelectedInWindow || this.selectedPages.includes(index)) { | ||||
|         if (index !== 0) { | ||||
|           const newElement = await this.pageBreakCallback(element, this.addedElements); | ||||
| 
 | ||||
|           if (newElement) { | ||||
|             this.addedElements = newElement; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     undoBtn.disabled = false; | ||||
|   } | ||||
| 
 | ||||
|   undo() { | ||||
|     this.addedElements.forEach((element) => { | ||||
|       const nextSibling = element.nextSibling; | ||||
| 
 | ||||
|       this.pagesContainer.removeChild(element); | ||||
| 
 | ||||
|       if (this.pagesContainer.childElementCount === 0) { | ||||
|         const filenameInput = document.getElementById('filename-input'); | ||||
|         const filenameParagraph = document.getElementById('filename'); | ||||
|         const downloadBtn = document.getElementById('export-button'); | ||||
| 
 | ||||
|         filenameInput.disabled = true; | ||||
|         filenameInput.value = ''; | ||||
|         filenameParagraph.innerText = ''; | ||||
|         downloadBtn.disabled = true; | ||||
|       } | ||||
| 
 | ||||
|       element._nextSibling = nextSibling; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   redo() { | ||||
|     this.execute(); | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user