mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-03-16 21:12:06 +01:00
Features ------------- Custom application name via APP_NAME docker env (These next 3 are done with OCRMyPDF) Extra features to OCR for scanned page cleanup (tilt/noise fixing) Adding OCR ability to read and output to text file Added Dedicated PDF/A conversion page Bug fixes -------------- Fix concurrent calls on Libre and OCRMyPDF jbig fix for compressions Fix for compression metadata issues due to forced conversions to PDF/A Other -------- Removal of UK US language and just using "English" due to extra development time Still issue with concurrent files for PDF to image... will fix later sorry
407 lines
17 KiB
HTML
407 lines
17 KiB
HTML
<head th:fragment="head">
|
|
|
|
<!-- Metadata -->
|
|
<meta charset="UTF-8">
|
|
|
|
<title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
|
|
<link rel="shortcut icon" href="favicon.svg">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<!-- jQuery -->
|
|
<script src="js/jquery.min.js"></script>
|
|
|
|
<!-- jQuery -->
|
|
<script src="js/jszip.min.js"></script>
|
|
|
|
<!-- Bootstrap -->
|
|
<script src="js/popper.min.js"></script>
|
|
<script src="js/bootstrap.min.js"></script>
|
|
<link rel="stylesheet" href="css/bootstrap.min.css">
|
|
<link rel="stylesheet" href="css/bootstrap-icons.css">
|
|
|
|
<!-- PDF.js -->
|
|
<script src="pdfjs/pdf.js"></script>
|
|
|
|
<!-- Custom -->
|
|
<link rel="stylesheet" href="css/general.css">
|
|
<link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
|
|
|
|
<script>
|
|
function toggleDarkMode() {
|
|
var checkbox = document.getElementById("toggle-dark-mode");
|
|
var darkModeStyles = document.getElementById("dark-mode-styles");
|
|
if (checkbox.checked) {
|
|
localStorage.setItem("dark-mode", "on");
|
|
darkModeStyles.disabled = false;
|
|
} else {
|
|
localStorage.setItem("dark-mode", "off");
|
|
darkModeStyles.disabled = true;
|
|
}
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
var darkModeStyles = document.getElementById("dark-mode-styles");
|
|
var checkbox = document.getElementById("toggle-dark-mode");
|
|
|
|
// Check if the user has already set a preference
|
|
if (localStorage.getItem("dark-mode") == "on") {
|
|
darkModeStyles.disabled = false;
|
|
checkbox.checked = true;
|
|
} else if (localStorage.getItem("dark-mode") == "off") {
|
|
darkModeStyles.disabled = true;
|
|
checkbox.checked = false;
|
|
} else {
|
|
// Check the OS's default dark mode setting
|
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
darkModeStyles.disabled = false;
|
|
checkbox.checked = true;
|
|
} else {
|
|
darkModeStyles.disabled = true;
|
|
checkbox.checked = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
</script>
|
|
</head>
|
|
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}">
|
|
<div class="custom-file-chooser">
|
|
<div class="custom-file">
|
|
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple>
|
|
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
|
|
</div>
|
|
<div class="selected-files"></div>
|
|
</div>
|
|
<br>
|
|
<div id="progressBarContainer" style="display: none; position: relative;">
|
|
<div class="progress" style="height: 1rem;">
|
|
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
|
<span class="sr-only">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
|
|
$('form').submit(function(event) {
|
|
console.log("start download code")
|
|
var files = $('#fileInput-input')[0].files;
|
|
var url = this.action;
|
|
console.log(url)
|
|
event.preventDefault(); // Prevent the default form handling behavior
|
|
/* Check if ${multiple} is false */
|
|
var multiple = [[${multiple}]] || false;
|
|
var override = $('#override').val() || '';
|
|
console.log("override=" + override)
|
|
if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) {
|
|
console.log("multi parallel download")
|
|
submitMultiPdfForm(event,url);
|
|
} else {
|
|
console.log("start single download")
|
|
|
|
// Get the selected download option from localStorage
|
|
const downloadOption = localStorage.getItem('downloadOption');
|
|
|
|
var formData = new FormData($('form')[0]);
|
|
|
|
// Send the request to the server using the fetch() API
|
|
fetch(url, {
|
|
method: 'POST',
|
|
body: formData
|
|
}).then(response => {
|
|
if (!response) {
|
|
throw new Error('Received null response for file ' + i);
|
|
}
|
|
console.log("load single download")
|
|
|
|
|
|
// Extract the filename from the Content-Disposition header, if present
|
|
let filename = null;
|
|
const contentDispositionHeader = response.headers.get('Content-Disposition');
|
|
console.log(contentDispositionHeader)
|
|
if (contentDispositionHeader && contentDispositionHeader.indexOf('attachment') !== -1) {
|
|
filename = contentDispositionHeader.split('filename=')[1].replace(/"/g, '');
|
|
} else {
|
|
// If the Content-Disposition header is not present or does not contain the filename, use a default filename
|
|
filename = 'download';
|
|
}
|
|
console.log("filename=" + filename)
|
|
|
|
|
|
const contentType = response.headers.get('Content-Type');
|
|
console.log("contentType=" + contentType)
|
|
// Check if the response is a PDF or an image
|
|
if (contentType.includes('pdf') || contentType.includes('image')) {
|
|
response.blob().then(blob => {
|
|
console.log("pdf/image")
|
|
|
|
// Perform the appropriate action based on the download option
|
|
if (downloadOption === 'sameWindow') {
|
|
console.log("same window")
|
|
|
|
// Open the file in the same window
|
|
window.location.href = URL.createObjectURL(blob);
|
|
} else if (downloadOption === 'newWindow') {
|
|
console.log("new window")
|
|
|
|
// Open the file in a new window
|
|
window.open(URL.createObjectURL(blob), '_blank');
|
|
} else {
|
|
console.log("else save")
|
|
|
|
// Download the file
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = filename;
|
|
link.click();
|
|
}
|
|
});
|
|
} else if (contentType.includes('json')) {
|
|
// Handle the JSON response
|
|
response.json().then(data => {
|
|
// Format the error message
|
|
const errorMessage = JSON.stringify(data, null, 2);
|
|
|
|
// Display the error message in an alert
|
|
alert(`An error occurred: ${errorMessage}`);
|
|
});
|
|
|
|
} else {
|
|
response.blob().then(blob => {
|
|
console.log("else save 2 zip")
|
|
|
|
// For ZIP files or other file types, just download the file
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = filename;
|
|
link.click();
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.log("error listener")
|
|
|
|
// Extract the error message and stack trace from the response
|
|
const errorMessage = error.message;
|
|
const stackTrace = error.stack;
|
|
|
|
// Create an error message to display to the user
|
|
const message = `${errorMessage}\n\n${stackTrace}`;
|
|
|
|
// Display the error message to the user
|
|
alert(message);
|
|
});
|
|
}
|
|
});
|
|
|
|
async function submitMultiPdfForm(event, url) {
|
|
// Get the selected PDF files
|
|
let files = $('#fileInput-input')[0].files;
|
|
|
|
// Get the existing form data
|
|
let formData = new FormData($('form')[0]);
|
|
formData.delete('fileInput');
|
|
|
|
// Show the progress bar
|
|
$('#progressBarContainer').show();
|
|
|
|
// Initialize the progress bar
|
|
let progressBar = $('#progressBar');
|
|
progressBar.css('width', '0%');
|
|
progressBar.attr('aria-valuenow', 0);
|
|
progressBar.attr('aria-valuemax', files.length);
|
|
|
|
// Check the flag in localStorage, default to 4
|
|
const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
|
|
const zipFiles = files.length > zipThreshold;
|
|
|
|
// Initialize JSZip instance if needed
|
|
let jszip = null;
|
|
if (zipFiles) {
|
|
jszip = new JSZip();
|
|
}
|
|
|
|
// Submit each PDF file in parallel
|
|
let promises = [];
|
|
for (let i = 0; i < files.length; i++) {
|
|
let promise = new Promise(async function(resolve, reject) {
|
|
let fileFormData = new FormData();
|
|
fileFormData.append('fileInput', files[i]);
|
|
for (let pair of formData.entries()) {
|
|
fileFormData.append(pair[0], pair[1]);
|
|
}
|
|
console.log(fileFormData);
|
|
|
|
try {
|
|
let response = await fetch(url, {
|
|
method: 'POST',
|
|
body: fileFormData
|
|
});
|
|
|
|
if (!response) {
|
|
throw new Error('Received null response for file ' + i);
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Error submitting request for file ${i}: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
let contentDisposition = response.headers.get('content-disposition');
|
|
let fileName = "file.pdf"
|
|
if (!contentDisposition) {
|
|
//throw new Error('Content-Disposition header not found for file ' + i);
|
|
} else {
|
|
fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
|
}
|
|
console.log('Received response for file ' + i + ': ' + response);
|
|
|
|
|
|
|
|
let blob = await response.blob();
|
|
if (zipFiles) {
|
|
// Add the file to the ZIP archive
|
|
jszip.file(fileName, blob);
|
|
resolve();
|
|
} else {
|
|
// Download the file directly
|
|
let url = window.URL.createObjectURL(blob);
|
|
let a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = fileName;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
resolve();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error submitting request for file ' + i + ': ' + error);
|
|
|
|
// Set default values or fallbacks for error properties
|
|
let status = error && error.status || 500;
|
|
let statusText = error && error.statusText || 'Internal Server Error';
|
|
let message = error && error.message || 'An error occurred while processing your request.';
|
|
|
|
// Reject the Promise to signal that the request has failed
|
|
reject();
|
|
// Redirect to error page with Spring Boot error parameters
|
|
let url = '/error?status=' + status + '&error=' + encodeURIComponent(statusText) + '&message=' + encodeURIComponent(message);
|
|
window.location.href = url;
|
|
}
|
|
});
|
|
|
|
// Update the progress bar as each request finishes
|
|
promise.then(function() {
|
|
updateProgressBar(progressBar, files);
|
|
});
|
|
|
|
promises.push(promise);
|
|
}
|
|
|
|
// Wait for all requests to finish
|
|
try {
|
|
await Promise.all(promises);
|
|
} catch (error) {
|
|
console.error('Error while uploading files: ' + error);
|
|
}
|
|
|
|
// Update the progress bar
|
|
progressBar.css('width', '100%');
|
|
progressBar.attr('aria-valuenow', files.length);
|
|
|
|
// After all requests are finished, download the ZIP file if needed
|
|
if (zipFiles) {
|
|
try {
|
|
let content = await jszip.generateAsync({ type: "blob" });
|
|
let url = window.URL.createObjectURL(content);
|
|
let a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = "files.zip";
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
} catch (error) {
|
|
console.error('Error generating ZIP file: ' + error);
|
|
}
|
|
}
|
|
}
|
|
function updateProgressBar(progressBar, files) {
|
|
let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
|
|
progressBar.css('width', progress + '%');
|
|
progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
|
|
}
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<script th:inline="javascript">
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const fileInput = document.getElementById('fileInput-input');
|
|
|
|
// Prevent default behavior for drag events
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
fileInput.addEventListener(eventName, preventDefaults, false);
|
|
});
|
|
|
|
function preventDefaults(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
|
|
// Add drop event listener
|
|
fileInput.addEventListener('drop', handleDrop, false);
|
|
|
|
function handleDrop(e) {
|
|
const dt = e.dataTransfer;
|
|
const files = dt.files;
|
|
fileInput.files = files;
|
|
handleFileInputChange(fileInput)
|
|
}
|
|
|
|
});
|
|
|
|
|
|
$([[${"#"+name+"-input"}]]).on("change", function() {
|
|
handleFileInputChange(this);
|
|
});
|
|
|
|
function handleFileInputChange(inputElement) {
|
|
const files = $(inputElement).get(0).files;
|
|
const fileNames = Array.from(files).map(f => f.name);
|
|
const selectedFilesContainer = $(inputElement).siblings(".selected-files");
|
|
selectedFilesContainer.empty();
|
|
fileNames.forEach(fileName => {
|
|
selectedFilesContainer.append("<div>" + fileName + "</div>");
|
|
});
|
|
console.log("fileNames.length=" + fileNames.length)
|
|
if (fileNames.length === 1) {
|
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
|
|
} else if (fileNames.length > 1) {
|
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " files selected");
|
|
} else {
|
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html([[#{pdfPrompt}]]);
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<style>
|
|
.custom-file-label {
|
|
padding-right: 90px;
|
|
}
|
|
|
|
.selected-files {
|
|
margin-top: 10px;
|
|
max-height: 150px;
|
|
overflow-y: auto;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
</th:block> |