mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	Add document splitting functionality to the multi-tools page (#1808)
* Add a split button on top of the insert button in multitool viewer. * Add placeholder splitFileButtonCallback method. * Remove unused splitFileButtonContainer element. * Add this binding to setActions for splitFileButtonCallback * Add test log for adding separators. * Add test log for adding separators. * Remove test logs and add visual indicators to separators instead. * Add splitting functionality to multi-tools. * Prevent trying to split from index 0. * Hide the split button for the first page to avoid confusion. * Change the class name 'cutBefore' to 'split-before' to fall mroe in line with already existing classes. * Add dummy methods for splitting and compressing documents. * Remove form submission, begin work on client side splitting. * Add client side document splitting. * Add client side archiving for the split documents. * Fix a bug that adds an empty page to splitted documents due to a sorting error. * Add a 'Split All' button and the relevant functionality. --------- Co-authored-by: kazandaki <ahmetfiratusta@gmail.com> Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									7ccb4d59b0
								
							
						
					
					
						commit
						3c04486348
					
				@ -127,6 +127,19 @@ label {
 | 
			
		||||
  margin-bottom: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.page-container.split-before {
 | 
			
		||||
  border-left: 1px dashed var(--md-sys-color-on-surface);
 | 
			
		||||
  padding-left: -1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.page-container.split-before:first-child {
 | 
			
		||||
  border-left: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.page-container:first-child .pdf-actions_split-file-button {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Pushes the last item to the left */
 | 
			
		||||
.page-container:last-child {
 | 
			
		||||
  margin-right: auto;
 | 
			
		||||
@ -171,6 +184,7 @@ label {
 | 
			
		||||
.page-container:last-child:lang(nqo),
 | 
			
		||||
/* N'Ko */
 | 
			
		||||
.page-container:last-child:lang(bqi)
 | 
			
		||||
 | 
			
		||||
/* Bakhtiari */
 | 
			
		||||
  {
 | 
			
		||||
  margin-left: auto !important;
 | 
			
		||||
 | 
			
		||||
@ -117,3 +117,12 @@ html[dir="rtl"] .pdf-actions_container:last-child>.pdf-actions_insert-file-butto
 | 
			
		||||
  aspect-ratio: 1;
 | 
			
		||||
  border-radius: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pdf-actions_split-file-button {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 25%;
 | 
			
		||||
  right: 50%;
 | 
			
		||||
  translate: 0 -50%;
 | 
			
		||||
  aspect-ratio: 1;
 | 
			
		||||
  border-radius: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -73,7 +73,12 @@ class PdfActionsManager {
 | 
			
		||||
    this.addFiles(imgContainer);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setActions({ movePageTo, addFiles, rotateElement }) {
 | 
			
		||||
  splitFileButtonCallback(e) {
 | 
			
		||||
    var imgContainer = this.getPageContainer(e.target);
 | 
			
		||||
    imgContainer.classList.toggle("split-before");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setActions({ movePageTo, addPdfs, rotateElement }) {
 | 
			
		||||
    this.movePageTo = movePageTo;
 | 
			
		||||
    this.addFiles = addFiles;
 | 
			
		||||
    this.rotateElement = rotateElement;
 | 
			
		||||
@ -84,6 +89,7 @@ class PdfActionsManager {
 | 
			
		||||
    this.rotateCWButtonCallback = this.rotateCWButtonCallback.bind(this);
 | 
			
		||||
    this.deletePageButtonCallback = this.deletePageButtonCallback.bind(this);
 | 
			
		||||
    this.insertFileButtonCallback = this.insertFileButtonCallback.bind(this);
 | 
			
		||||
    this.splitFileButtonCallback = this.splitFileButtonCallback.bind(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  adapt(div) {
 | 
			
		||||
@ -140,6 +146,12 @@ class PdfActionsManager {
 | 
			
		||||
    insertFileButton.onclick = this.insertFileButtonCallback;
 | 
			
		||||
    insertFileButtonContainer.appendChild(insertFileButton);
 | 
			
		||||
 | 
			
		||||
    const splitFileButton = document.createElement("button");
 | 
			
		||||
    splitFileButton.classList.add("btn", "btn-primary", "pdf-actions_split-file-button");
 | 
			
		||||
    splitFileButton.innerHTML = `<span class="material-symbols-rounded">cut</span>`;
 | 
			
		||||
    splitFileButton.onclick = this.splitFileButtonCallback;
 | 
			
		||||
    insertFileButtonContainer.appendChild(splitFileButton);
 | 
			
		||||
 | 
			
		||||
    div.appendChild(insertFileButtonContainer);
 | 
			
		||||
 | 
			
		||||
    // add this button to every element, but only show it on the last one :D
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,9 @@ class PdfContainer {
 | 
			
		||||
    this.setDownloadAttribute = this.setDownloadAttribute.bind(this);
 | 
			
		||||
    this.preventIllegalChars = this.preventIllegalChars.bind(this);
 | 
			
		||||
    this.addImageFile = this.addImageFile.bind(this);
 | 
			
		||||
    this.nameAndArchiveFiles = this.nameAndArchiveFiles.bind(this);
 | 
			
		||||
    this.splitPDF = this.splitPDF.bind(this);
 | 
			
		||||
    this.splitAll = this.splitAll.bind(this);
 | 
			
		||||
 | 
			
		||||
    this.pdfAdapters = pdfAdapters;
 | 
			
		||||
 | 
			
		||||
@ -34,6 +37,7 @@ class PdfContainer {
 | 
			
		||||
    window.addFiles = this.addFiles;
 | 
			
		||||
    window.exportPdf = this.exportPdf;
 | 
			
		||||
    window.rotateAll = this.rotateAll;
 | 
			
		||||
    window.splitAll = this.splitAll;
 | 
			
		||||
 | 
			
		||||
    const filenameInput = document.getElementById("filename-input");
 | 
			
		||||
    const downloadBtn = document.getElementById("export-button");
 | 
			
		||||
@ -212,6 +216,61 @@ class PdfContainer {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  splitAll() {
 | 
			
		||||
    const allPages = this.pagesContainer.querySelectorAll(".page-container");
 | 
			
		||||
    if (this.pagesContainer.querySelectorAll(".split-before").length > 0) {
 | 
			
		||||
      allPages.forEach(page => {
 | 
			
		||||
        page.classList.remove("split-before");
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      allPages.forEach(page => {
 | 
			
		||||
        page.classList.add("split-before");
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async splitPDF(baseDocBytes, splitters) {
 | 
			
		||||
    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.push(pageNum); // We'll also add a faux separator at the end in order to get the pages after the last separator.
 | 
			
		||||
 | 
			
		||||
    const splitDocuments = [];
 | 
			
		||||
    for (const splitterPosition of splitters) {
 | 
			
		||||
      const subDocument = await PDFLib.PDFDocument.create();
 | 
			
		||||
 | 
			
		||||
      const splitterIndex = splitters.indexOf(splitterPosition);
 | 
			
		||||
 | 
			
		||||
      let firstPage = splitterIndex === 0 ? 0 : splitters[splitterIndex - 1];
 | 
			
		||||
 | 
			
		||||
      const pageIndices = Array.from({ length: splitterPosition - firstPage }, (value, key) => firstPage + key);
 | 
			
		||||
 | 
			
		||||
      const copiedPages = await subDocument.copyPages(baseDocument, pageIndices);
 | 
			
		||||
 | 
			
		||||
      copiedPages.forEach(copiedPage => {
 | 
			
		||||
        subDocument.addPage(copiedPage);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const subDocumentBytes = await subDocument.save();
 | 
			
		||||
 | 
			
		||||
      splitDocuments.push(subDocumentBytes);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return splitDocuments;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async nameAndArchiveFiles(pdfBytesArray, baseNameString) {
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return zip;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async exportPdf() {
 | 
			
		||||
    const pdfDoc = await PDFLib.PDFDocument.create();
 | 
			
		||||
    const pageContainers = this.pagesContainer.querySelectorAll(".page-container"); // Select all .page-container elements
 | 
			
		||||
@ -262,8 +321,6 @@ class PdfContainer {
 | 
			
		||||
    }
 | 
			
		||||
    const pdfBytes = await pdfDoc.save();
 | 
			
		||||
    const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" });
 | 
			
		||||
    const url = URL.createObjectURL(pdfBlob);
 | 
			
		||||
    const downloadOption = localStorage.getItem("downloadOption");
 | 
			
		||||
 | 
			
		||||
    const filenameInput = document.getElementById("filename-input");
 | 
			
		||||
 | 
			
		||||
@ -280,6 +337,37 @@ class PdfContainer {
 | 
			
		||||
      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 pagesArray = Array.from(this.pagesContainer.children);
 | 
			
		||||
      const splitters = [];
 | 
			
		||||
      separators.forEach(page => {
 | 
			
		||||
        const pageIndex = pagesArray.indexOf(page);
 | 
			
		||||
        if (pageIndex !== 0) {
 | 
			
		||||
          splitters.push(pageIndex);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const splitDocuments = await this.splitPDF(pdfBytes, splitters);
 | 
			
		||||
      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");
 | 
			
		||||
        self.downloadLink.href = url;
 | 
			
		||||
        self.downloadLink.setAttribute("download", baseName + ".zip");
 | 
			
		||||
        self.downloadLink.setAttribute("target", "_blank");
 | 
			
		||||
        self.downloadLink.click();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    } else { // Continue normally if there are no separators
 | 
			
		||||
 | 
			
		||||
      const url = URL.createObjectURL(pdfBlob);
 | 
			
		||||
      const downloadOption = localStorage.getItem("downloadOption");
 | 
			
		||||
 | 
			
		||||
      if (!filenameInput.value.includes(".pdf")) {
 | 
			
		||||
        filenameInput.value = filenameInput.value + ".pdf";
 | 
			
		||||
        this.fileName = filenameInput.value;
 | 
			
		||||
@ -304,6 +392,7 @@ class PdfContainer {
 | 
			
		||||
        this.downloadLink.click();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  setDownloadAttribute() {
 | 
			
		||||
 | 
			
		||||
@ -42,6 +42,11 @@
 | 
			
		||||
                      rotate_right
 | 
			
		||||
                    </span>
 | 
			
		||||
                  </button>
 | 
			
		||||
                  <button class="btn btn-secondary enable-on-file" onclick="splitAll()" disabled>
 | 
			
		||||
                    <span class="material-symbols-rounded">
 | 
			
		||||
                      cut
 | 
			
		||||
                    </span>
 | 
			
		||||
                  </button>
 | 
			
		||||
                  <button id="export-button" class="btn btn-primary enable-on-file" onclick="exportPdf()" disabled>
 | 
			
		||||
                    <span class="material-symbols-rounded">
 | 
			
		||||
                      download
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user