Resolve conflicts using files from Stirling-2.0

This commit is contained in:
Reece
2025-06-24 12:51:39 +01:00
parent 16e56e3208
commit a51960b199
691 changed files with 168291 additions and 0 deletions
@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{addImage.title}, header=#{addImage.header})}"></th:block>
<script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
<link rel="stylesheet" th:href="@{'/css/add-image.css'}">
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">add_photo_alternate</span>
<span class="tool-header-text" th:text="#{addImage.header}"></span>
</div>
<!-- pdf selector -->
<div
th:replace="~{fragments/common :: fileSelector(name='pdf-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script type="module" th:src="@{'/js/pages/add-image.js'}"></script>
<div class="show-on-file-selected">
<div
th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=false, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
</div>
</div>
<div class="draggable-buttons-box ignore-rtl">
<button class="btn btn-outline-secondary" th:title="#{sign.delete}"
onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())"
style="color: #C02223; border-color: #C02223; background-color: rgba(255, 0, 0, 0.1);">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash"
viewBox="0 0 16 16">
<path
d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z" />
<path
d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z" />
</svg>
</button>
<button class="btn btn-outline-secondary" th:title="#{sign.addToAll}"
onclick="DraggableUtils.addAllPagesDraggableCanvas(DraggableUtils.getLastInteracted())">
<span class="material-symbols-rounded">
content_copy
</span>
</button>
<div id="rotation-controls" class="align-items-center" style="display: none;">
<div class="input-with-icon">
<span class="material-symbols-rounded icon" style="margin-right: 3px;">
screen_rotation
</span>
<input type="number" id="rotation-input" class="form-control form-control-sm me-2" value="0" step="10"
style="width: 6rem" />
</div>
</div>
<button id="ratioToggleBtn" th:title="#{sign.maintainRatio}" class="btn btn-outline-secondary"
onclick="DraggableUtils.toggleMaintainRatio()">
<span class="material-symbols-rounded">
Aspect_Ratio
</span>
</button>
<button class="btn btn-outline-secondary" th:title="#{sign.first}" onclick="goToFirstOrLastPage(false)"
style="margin-left:auto">
<span class="material-symbols-rounded">
keyboard_double_arrow_left
</span>
</button>
<button class="btn btn-outline-secondary" th:title="#{sign.previous}" id="incrementPage"
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-chevron-left" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" />
</svg>
</button>
<button class="btn btn-outline-secondary" th:title="#{sign.next}" id="decrementPage"
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-chevron-right" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" />
</svg>
</button>
<button class="btn btn-outline-secondary" th:title="#{sign.last}" onclick="goToFirstOrLastPage(true)">
<span class="material-symbols-rounded">
keyboard_double_arrow_right
</span>
</button>
<button id="download-pdf" th:title="#{downloadPdf}" class="btn btn-outline-secondary"
style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)">
<span class="material-symbols-rounded">
download
</span>
</button>
</div>
<!-- draggables box -->
<div id="box-drag-container" class="show-on-file-selected">
<canvas id="pdf-canvas"></canvas>
<script th:src="@{'/js/draggable-utils.js'}"></script>
</div>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{addPageNumbers.title}, header=#{addPageNumbers.header})}"></th:block>
<style>
.a4container {
position: relative;
width: 50%;
aspect-ratio: 0.707/1;
border: 1px solid #ddd;
box-sizing: border-box;
background-color: white;
}
.pageNumber {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
font-size: 1em;
color: #333;
cursor: pointer;
background-color: #ccc;
width: 15%;
height: 15%;
transform: translate(-50%, -50%);
}
.pageNumber:hover {
background-color: #eee;
}
#myForm {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.selectedPosition {
background-color: #0a0;
}
.selectedPosition.selectedHovered {
background-color: #006600;
}
</style>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">123</span>
<span class="tool-header-text" th:text="#{addPageNumbers.header}"></span>
</div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/add-page-numbers'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<br>
<div class="mb-3">
<label for="customMargin" th:text="#{addPageNumbers.selectText.2}"></label>
<select class="form-control" id="customMargin" name="customMargin">
<option value="small" th:text="#{sizes.small}"></option>
<option value="medium" selected th:text="#{sizes.medium}"></option>
<option value="large" th:text="#{sizes.large}"></option>
<option value="x-large" th:text="#{sizes.x-large}"></option>
</select>
</div>
<div class="mb-3">
<label th:text="#{addPageNumbers.selectText.3}"></label>
<div class="a4container">
<div class="pageNumber" id="1" style="top: 10%; left: 10%;">1</div>
<div class="pageNumber" id="2" style="top: 10%; left: 50%;">2</div>
<div class="pageNumber" id="3" style="top: 10%; left: 90%;">3</div>
<div class="pageNumber" id="4" style="top: 50%; left: 10%;">4</div>
<div class="pageNumber" id="5" style="top: 50%; left: 50%;">5</div>
<div class="pageNumber" id="6" style="top: 50%; left: 90%;">6</div>
<div class="pageNumber" id="7" style="top: 90%; left: 10%;">7</div>
<div class="pageNumber selectedPosition" id="8" style="top: 90%; left: 50%;">8</div>
<div class="pageNumber" id="9" style="top: 90%; left: 90%;">9</div>
</div>
</div>
<input type="hidden" id="numberInput" name="position" value="8">
<div class="mb-3">
<label for="fontSize" th:text="#{addPageNumbers.fontSize}"></label>
<input type="number" class="form-control" id="fontSize" name="fontSize" min="1" value="12" required>
</div>
<div class="mb-3">
<label for="fontType" th:text="#{addPageNumbers.fontName}"></label>
<select class="form-control" id="fontType" name="fontType">
<option value="Times">Times Roman</option>
<option value="Helvetica">Helvetica</option>
<option value="Courier">Courier New</option>
</select>
</div>
<div class="mb-3">
<label for="startingNumber" th:text="#{addPageNumbers.selectText.4}"></label>
<input type="number" class="form-control" id="startingNumber" name="startingNumber" min="1" required value="1">
</div>
<div class="mb-3">
<label for="pagesToNumber" th:text="#{addPageNumbers.selectText.5}"></label>
<input type="text" class="form-control" id="pagesToNumber" name="pagesToNumber" th:placeholder="#{addPageNumbers.numberPagesDesc}">
</div>
<div class="mb-3">
<label for="customText" th:text="#{addPageNumbers.selectText.6}"></label>
<input type="text" class="form-control" id="customText" name="customText" th:placeholder="#{addPageNumbers.customNumberDesc}">
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{addPageNumbers.submit}"></button>
</form>
</div>
</div>
</div>
<script>
let cells = document.querySelectorAll('.pageNumber');
let inputField = document.getElementById('numberInput');
cells.forEach(cell => {
cell.addEventListener('click', function(e) {
cells.forEach(cell => {
cell.classList.remove('selectedPosition'); // Remove selected class from all cells
cell.classList.remove('selectedHovered'); // Also remove selectedHovered class
});
let selectedLocation = e.target.id;
inputField.value = selectedLocation;
e.target.classList.add('selectedPosition'); // Add selected class to clicked cell
e.target.classList.add('selectedHovered'); // Add selectedHovered class
});
cell.addEventListener('mouseenter', function(e) {
if(e.target.classList.contains('selectedPosition')) {
e.target.classList.add('selectedHovered');
}
});
cell.addEventListener('mouseleave', function(e) {
if(e.target.classList.contains('selectedPosition')) {
e.target.classList.remove('selectedHovered');
}
});
});
</script>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{adjustContrast.title}, header=#{adjustContrast.header})}"></th:block>
<style>
#flex-container {
display: flex;
align-items: center;
}
#sliders-container {
padding: 0 20px; /* Add some padding to separate sliders from canvas */
}
</style>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12 bg-card">
<form th:action="@{''}">
<div class="row justify-content-center">
<div class="col-md-3">
<div id="sliders-container" style="display:none;">
<h4><span th:text="#{adjustContrast.contrast}"></span> <span id="contrast-val">100</span>%</h4>
<input type="range" min="0" max="200" value="100" id="contrast-slider">
<h4><span th:text="#{adjustContrast.brightness}"></span> <span id="brightness-val">100</span>%</h4>
<input type="range" min="0" max="200" value="100" id="brightness-slider">
<h4><span th:text="#{adjustContrast.saturation}"></span> <span id="saturation-val">100</span>%</h4>
<input type="range" min="0" max="200" value="100" id="saturation-slider">
</div>
</div>
<div class="col-md-7">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">palette</span>
<span class="tool-header-text" th:text="#{adjustContrast.header}"></span>
</div>
<div class="col-md-8">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf', remoteCall='false')}"></div>
</div>
<br>
<canvas id="contrast-pdf-canvas"></canvas>
<div class="mb-3 text-left">
<button id="download-button" class="btn btn-primary" th:text="#{adjustContrast.download}"></button>
</div>
</div>
</div>
</form>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script type="module" th:src="@{'/js/pages/adjust-contrast.js'}"></script>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{autoCrop.title}, header=#{autoCrop.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">crop</span>
<span class="tool-header-text" th:text="#{autoCrop.header}"></span>
</div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/auto-crop'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{autoCrop.submit}"></button>
</form>
<p class="mt-3" th:text="#{autoCrop.credit}"></p>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{auto-rename.title}, header=#{auto-rename.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span >
<svg class="tool-header-icon advance">
<use xlink:href="/images/rename.svg#icon-rename"></use>
</svg>
</span>
<span class="tool-header-text" th:text="#{auto-rename.header}"></span>
</div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/auto-rename'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{auto-rename.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{changeMetadata.title}, header=#{changeMetadata.header})}"></th:block>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">assignment</span>
<span class="tool-header-text" th:text="#{changeMetadata.header}"></span>
</div>
<form method="post" id="form1" enctype="multipart/form-data" th:action="@{'/api/v1/misc/update-metadata'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
<div class="form-check mb-3-inline ms-3">
<input type="checkbox" id="deleteAll" name="deleteAll">
<label for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
</div>
<div class="form-check mb-3-inline ms-3">
<input type="checkbox" id="customModeCheckbox">
<label for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
</div>
<div class="mb-3">
<label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label>
<input type="text" class="form-control" id="author" name="author">
</div>
<div class="mb-3">
<label class="form-check-label" for="creationDate" th:text="#{changeMetadata.creationDate}"></label>
<input type="text" class="form-control" id="creationDate" name="creationDate" placeholder="2020/12/25 18:30:59">
</div>
<div class="mb-3">
<label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label>
<input type="text" class="form-control" id="creator" name="creator">
</div>
<div class="mb-3">
<label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label>
<input type="text" class="form-control" id="keywords" name="keywords">
</div>
<div class="mb-3">
<label class="form-check-label" for="modificationDate" th:text="#{changeMetadata.modDate}"></label>
<input type="text" class="form-control" id="modificationDate" name="modificationDate" placeholder="2020/12/25 18:30:59">
</div>
<div class="mb-3">
<label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label>
<input type="text" class="form-control" id="producer" name="producer">
</div>
<div class="mb-3">
<label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label>
<input type="text" class="form-control" id="subject" name="subject">
</div>
<div class="mb-3">
<label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label>
<input type="text" class="form-control" id="title" name="title">
</div>
<div class="mb-3">
<label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label>
<select class="form-control" id="trapped" name="trapped">
<option value="True" th:text="#{true}"></option>
<option value="False" th:text="#{false}" selected></option>
<option value="Unknown" th:text="#{unknown}"></option>
</select>
</div>
<div id="customMetadata" style="display: none;">
<h3 th:text="#{changeMetadata.selectText.4}"></h3>
<div class="mb-3" id="otherMetadataEntries"></div>
</div>
<div id="customMetadataEntries"></div>
<button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button>
<br>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{changeMetadata.submit}"></button>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script type="module" th:src="@{'/js/pages/change-metadata.js'}">
</script>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,256 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{compare.title}, header=#{compare.header})}"></th:block>
<style>
.result-column {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 15px;
overflow-y: auto;
height: calc(100vh - 400px);
white-space: pre-wrap;
}
.flex-container {
display: flex;
flex-direction: row;
}
.color-selector {
display: flex;
flex-direction: row;
align-items: center;
width: 50%;
max-height: 100px;
margin-bottom: 2rem;
}
#color-box1,
#color-box2 {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
background-color: transparent;
}
.spacer1 {
padding-right: calc(var(--bs-gutter-x) * .5);
}
.spacer2 {
padding-left: calc(var(--bs-gutter-x) * .5);
}
</style>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-9 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">compare</span>
<span class="tool-header-text" th:text="#{compare.header}"></span>
</div>
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf', remoteCall='false')}">
</div>
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput2', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf', remoteCall='false')}">
</div>
<div class="row">
<div class="flex-container">
<div class="color-selector spacer1">
<label th:text="#{compare.highlightColor.1}"></label>
<label for="color-box1"></label><input type="color" id="color-box1" value="#ff0000">
</div>
<div class="color-selector spacer2">
<label th:text="#{compare.highlightColor.2}"></label>
<label for="color-box2"></label><input type="color" id="color-box2" value="#008000">
</div>
</div>
</div>
<button class="btn btn-primary" onclick="comparePDFs()" th:text="#{compare.submit}"></button>
<div class="row">
<div class="col-md-6">
<h3 th:text="#{compare.document.1}"></h3>
<div id="result1" class="result-column"></div>
</div>
<div class="col-md-6">
<h3 th:text="#{compare.document.2}"></h3>
<div id="result2" class="result-column"></div>
</div>
</div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script th:inline="javascript">
// get the elements
var result1 = document.getElementById('result1');
var result2 = document.getElementById('result2');
// add event listeners
result1.addEventListener('scroll', function () {
result2.scrollTop = result1.scrollTop;
});
result2.addEventListener('scroll', function () {
result1.scrollTop = result2.scrollTop;
});
async function comparePDFs() {
const file1 = document.getElementById("fileInput-input").files[0];
const file2 = document.getElementById("fileInput2-input").files[0];
var color1 = document.getElementById('color-box1').value;
var color2 = document.getElementById('color-box2').value;
const complexMessage = /*[[#{compare.complex.message}]]*/ 'One or both of the provided documents are large files, accuracy of comparison may be reduced';
const largeFilesMessage = /*[[#{compare.large.file.message}]]*/ 'One or Both of the provided documents are too large to process';
const noTextMessage = /*[[#{compare.no.text.message}]]*/ 'One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison."';
if (!file1 || !file2) {
console.error("Please select two PDF files to compare");
return;
}
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
const [pdf1, pdf2] = await Promise.all([
pdfjsLib.getDocument(URL.createObjectURL(file1)).promise,
pdfjsLib.getDocument(URL.createObjectURL(file2)).promise
]);
const extractText = async (pdf) => {
const pages = [];
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
const strings = content.items.map(item => item.str);
pages.push(strings.join(" "));
}
return pages.join(" ");
};
const [text1, text2] = await Promise.all([
extractText(pdf1),
extractText(pdf2)
]);
if (text1.trim() === "" || text2.trim() === "") {
alert(noTextMessage);
return;
}
const resultDiv1 = document.getElementById("result1");
const resultDiv2 = document.getElementById("result2");
const loading = /*[[#{loading}]]*/ 'Loading...';
resultDiv1.innerHTML = loading;
resultDiv2.innerHTML = loading;
// Create a new Worker
const worker = new Worker('./js/compare/pdfWorker.js');
// Post messages to the worker
worker.postMessage({
type: 'SET_COMPLEX_MESSAGE',
message: complexMessage
});
worker.postMessage({
type: 'SET_TOO_LARGE_MESSAGE',
message: largeFilesMessage
});
// Error handling for the worker
worker.onerror = function (error) {
console.error('Worker error:', error);
};
worker.onmessage = function (e) {
const { status, differences, message } = e.data;
if (status === 'error') {
resultDiv1.innerHTML = '';
resultDiv2.innerHTML = '';
alert(message);
return;
}
if (status === 'success' && differences) {
console.log('Differences:', differences);
displayDifferences(differences);
}
if (event.data.status === 'warning') {
console.warn(event.data.message);
alert(event.data.message);
}
};
worker.postMessage({ text1, text2, color1, color2 });
const displayDifferences = (differences) => {
const resultDiv1 = document.getElementById("result1");
const resultDiv2 = document.getElementById("result2");
resultDiv1.innerHTML = "";
resultDiv2.innerHTML = "";
differences.forEach(([color, word]) => {
const span1 = document.createElement("span");
const span2 = document.createElement("span");
if (color === color2) {
span1.style.color = "transparent";
span1.style.userSelect = "none";
span2.style.color = color;
}
// If it's a deletion, show it in in the first document and transparent in the second
else if (color === color1) {
span1.style.color = color;
span2.style.color = "transparent";
span2.style.userSelect = "none";
}
// If it's unchanged, show it in black in both
else {
span1.style.color = color;
span2.style.color = color;
}
span1.textContent = word;
span2.textContent = word;
resultDiv1.appendChild(span1);
resultDiv2.appendChild(span2);
// Add space after each word, or a new line if the word ends with a full stop
const spaceOrNewline1 = document.createElement("span");
const spaceOrNewline2 = document.createElement("span");
if (word.endsWith(".")) {
spaceOrNewline1.innerHTML = "<br>";
spaceOrNewline2.innerHTML = "<br>";
} else {
spaceOrNewline1.textContent = " ";
spaceOrNewline2.textContent = " ";
}
resultDiv1.appendChild(spaceOrNewline1);
resultDiv2.appendChild(spaceOrNewline2);
});
};
}
</script>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{compress.title}, header=#{compress.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">zoom_in_map</span>
<span class="tool-header-text" th:text="#{compress.header}"></span>
</div>
<form action="#" th:action="@{'/api/v1/misc/compress-pdf'}" method="post" enctype="multipart/form-data">
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<div class="card mb-3">
<div class="card-body">
<h4 th:text="#{compress.selectText.1}"></h4>
<p th:utext="#{compress.selectText.1.1}"></p>
<label for="optimizeLevel" th:text="#{compress.selectText.2}"></label>
<select name="optimizeLevel" id="optimizeLevel" class="form-control">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5" selected>5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
</select>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" name="grayscale" id="grayscaleCheck" value="true">
<label class="form-check-label" for="grayscaleCheck" th:text="#{compress.grayscale.label}">
Convert images to grayscale for better compression
</label>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<h4 th:text="#{compress.selectText.4}"></h4>
<label for="expectedOutputSize" th:text="#{compress.selectText.5}"></label>
<input type="text" name="expectedOutputSize" id="expectedOutputSize" class="form-control">
</div>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{compress.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{home.ScannerImageSplit.title}, header=#{home.ScannerImageSplit.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">scanner</span>
<span class="tool-header-text" th:text="#{home.ScannerImageSplit.title}"></span>
</div>
<p th:if="${!isPython}" th:text="#{ScannerImageSplit.info}" class="alert alert-success text-center">Python is not installed. It is required to run.</p>
<form th:if="${isPython}" id="multiPdfForm" th:action="@{'/api/v1/misc/extract-image-scans'}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='image/*, application/pdf')}"></div>
<div class="mb-3">
<label for="angleThreshold" th:text="#{ScannerImageSplit.selectText.1}"></label>
<input type="number" class="form-control" id="angleThreshold" name="angle_threshold" value="10">
<small id="angleThresholdHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.2}"></small>
</div>
<div class="mb-3">
<label for="tolerance" th:text="#{ScannerImageSplit.selectText.3}"></label>
<input type="number" class="form-control" id="tolerance" name="tolerance" value="20">
<small id="toleranceHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.4}"></small>
</div>
<div class="mb-3">
<label for="minArea" th:text="#{ScannerImageSplit.selectText.5}"></label>
<input type="number" class="form-control" id="minArea" name="min_area" value="8000">
<small id="minAreaHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.6}"></small>
</div>
<div class="mb-3">
<label for="minContourArea" th:text="#{ScannerImageSplit.selectText.7}"></label>
<input type="number" class="form-control" id="minContourArea" name="min_contour_area" value="500">
<small id="minContourAreaHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.8}"></small>
</div>
<div class="mb-3">
<label for="borderSize" th:text="#{ScannerImageSplit.selectText.9}"></label>
<input type="number" class="form-control" id="borderSize" name="border_size" value="1">
<small id="borderSizeHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.10}"></small>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{genericSubmit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.title}, header=#{extractImages.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">photo_library</span>
<span class="tool-header-text" th:text="#{extractImages.header}"></span>
</div>
<form id="multiPdfForm" th:action="@{'/api/v1/misc/extract-images'}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<div class="mb-3">
<label th:text="#{extractImages.selectText}"></label>
<select class="form-control" name="format">
<option value="png">PNG</option>
<option value="jpg">JPG</option>
<option value="gif">GIF</option>
</select>
</div>
<div class="mb-3">
<input type="checkbox" name="allowDuplicates" id="allowDuplicates">
<label for="allowDuplicates" th:text="#{extractImages.allowDuplicates}"></label>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{extractImages.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,163 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{fakeScan.title}, header=#{fakeScan.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">scanner</span>
<span class="tool-header-text" th:text="#{fakeScan.title}"></span>
</div>
<form method="post" enctype="multipart/form-data" id="uploadForm" th:action="@{'/api/v1/misc/fake-scan'}">
<input type="hidden" name="advancedEnabled" id="advancedEnabled" value="false">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<br>
<div class="mb-3">
<label for="quality" class="form-label" th:text="#{fakeScan.quality}"></label>
<select class="form-select" id="quality" name="quality">
<option value="low" th:text="#{fakeScan.quality.low}"></option>
<option value="medium" th:text="#{fakeScan.quality.medium}"></option>
<option value="high" th:text="#{fakeScan.quality.high}" selected></option>
</select>
</div>
<div class="mb-3">
<label for="rotation" class="form-label" th:text="#{fakeScan.rotation}"></label>
<select class="form-select" id="rotation" name="rotation">
<option value="none" th:text="#{fakeScan.rotation.none}"></option>
<option value="slight" th:text="#{fakeScan.rotation.slight}" selected></option>
<option value="moderate" th:text="#{fakeScan.rotation.moderate}"></option>
<option value="severe" th:text="#{fakeScan.rotation.severe}"></option>
</select>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="advancedSettingsToggle">
<label class="form-check-label" for="advancedSettingsToggle" th:text="#{fakeScan.advancedSettings}"></label>
</div>
<div id="advancedSettings" style="display:none; border:1px solid #eee; padding:1em; border-radius:8px; margin-bottom:1em;">
<div class="mb-3">
<label for="colorspace" class="form-label" th:text="#{fakeScan.colorspace}"></label>
<select class="form-select" id="colorspace" name="colorspace">
<option value="grayscale" th:text="#{fakeScan.colorspace.grayscale}" selected></option>
<option value="color" th:text="#{fakeScan.colorspace.color}"></option>
</select>
</div>
<div class="mb-3">
<label for="border" class="form-label" th:text="#{fakeScan.border}"></label>
<input type="number" class="form-control" id="border" name="border" min="0" max="100" value="20">
</div>
<div class="mb-3">
<label for="rotate" class="form-label" th:text="#{fakeScan.rotate}"></label>
<input type="number" class="form-control" id="rotate" name="rotate" min="-15" max="15" value="0">
</div>
<div class="mb-3">
<label for="rotateVariance" class="form-label" th:text="#{fakeScan.rotateVariance}"></label>
<input type="number" class="form-control" id="rotateVariance" name="rotateVariance" min="0" max="10" value="2">
</div>
<div class="mb-3">
<label for="brightness" class="form-label" th:text="#{fakeScan.brightness}"></label>
<input type="range" class="form-range" id="brightness" name="brightness" min="0.5" max="1.5" step="0.01" value="1.0">
</div>
<div class="mb-3">
<label for="contrast" class="form-label" th:text="#{fakeScan.contrast}"></label>
<input type="range" class="form-range" id="contrast" name="contrast" min="0.5" max="1.5" step="0.01" value="1.0">
</div>
<div class="mb-3">
<label for="blur" class="form-label" th:text="#{fakeScan.blur}"></label>
<input type="range" class="form-range" id="blur" name="blur" min="0" max="5" step="0.1" value="1.0">
</div>
<div class="mb-3">
<label for="noise" class="form-label" th:text="#{fakeScan.noise}"></label>
<input type="range" class="form-range" id="noise" name="noise" min="0" max="20" step="0.1" value="8.0">
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="yellowish" name="yellowish">
<label class="form-check-label" for="yellowish" th:text="#{fakeScan.yellowish}"></label>
</div>
<div class="mb-3">
<label for="resolution" class="form-label" th:text="#{fakeScan.resolution}"></label>
<input type="number" class="form-control" id="resolution" name="resolution" min="72" max="600" value="300">
</div>
</div>
<div class="mb-3 text-left">
<button type="submit" class="btn btn-primary" th:text="#{fakeScan.submit}" id="submitBtn"></button>
</div>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
<script th:src="@{'/js/fetch-utils.js'}"></script>
<script th:inline="javascript">
// Show/hide advanced settings
document.getElementById('advancedSettingsToggle').addEventListener('change', function() {
document.getElementById('advancedSettings').style.display = this.checked ? 'block' : 'none';
});
// Form submission handling
const form = document.getElementById('uploadForm');
if (form) {
form.addEventListener('submit', function(e) {
// If advanced settings are not enabled, remove advanced fields
if (!document.getElementById('advancedSettingsToggle').checked) {
const formData = new FormData(form);
formData.delete('colorspace');
formData.delete('border');
formData.delete('rotate');
formData.delete('rotateVariance');
formData.delete('brightness');
formData.delete('contrast');
formData.delete('blur');
formData.delete('noise');
formData.delete('yellowish');
formData.delete('resolution');
}
});
}
// Initialize advanced settings state
const advancedSettingsToggle = document.getElementById('advancedSettingsToggle');
const advancedSettings = document.getElementById('advancedSettings');
if (advancedSettingsToggle && advancedSettings) {
// Helper: enable/disable all fields in advanced settings
function setAdvancedFieldsEnabled(enabled) {
const fields = advancedSettings.querySelectorAll('input, select');
fields.forEach(field => {
field.disabled = !enabled;
// If enabling and value is empty, set to default
if (enabled && (field.value === '' || field.value == null)) {
if (field.type === 'number' || field.type === 'range') {
field.value = field.defaultValue;
} else if (field.type === 'checkbox') {
field.checked = field.defaultChecked;
} else if (field.tagName === 'SELECT') {
field.value = field.querySelector('option[selected]')?.value || field.options[0].value;
}
}
});
}
// Set initial state
setAdvancedFieldsEnabled(advancedSettingsToggle.checked);
advancedSettings.style.display = advancedSettingsToggle.checked ? 'block' : 'none';
document.getElementById('advancedEnabled').value = advancedSettingsToggle.checked ? 'true' : 'false';
// Add change listener
advancedSettingsToggle.addEventListener('change', function() {
advancedSettings.style.display = this.checked ? 'block' : 'none';
setAdvancedFieldsEnabled(this.checked);
document.getElementById('advancedEnabled').value = this.checked ? 'true' : 'false';
});
}
</script>
</body>
</html>
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{flatten.title}, header=#{flatten.header})}"></th:block>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">layers_clear</span>
<span class="tool-header-text" th:text="#{flatten.header}"></span>
</div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/flatten'}" id="pdfForm" class="mb-3">
<div class="custom-file">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
</div>
<div class="form-check ms-3">
<input type="checkbox" id="flattenOnlyForms" name="flattenOnlyForms">
<label for="flattenOnlyForms" th:text="#{flatten.flattenOnlyForms}" ></label>
</div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{flatten.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{ocr.title}, header=#{ocr.header})}"></th:block>
<style>
#languages {
max-height: 400px;
overflow-y: auto;
border: 1px solid var(--md-sys-color-surface-3);
border-radius: 5px;
padding: 10px;
}
</style>
<script>
function handleLangSelection() {
let checkboxes = document.getElementsByName("languages");
let selected = false;
for (let i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
selected = true;
checkboxes[i].setAttribute('required', 'false');
}
}
if (selected) {
for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].removeAttribute('required');
}
}
else {
for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].setAttribute('required', 'true');
}
}
}
</script>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">quick_reference_all</span>
<span class="tool-header-text" th:text="#{ocr.header}"></span>
</div>
<form th:if="${#lists.size(languages) > 0}" action="#" th:action="@{'/api/v1/misc/ocr-pdf'}" method="post" enctype="multipart/form-data" class="mb-3">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<div class="mb-3">
<label for="languages" class="form-label" th:text="#{ocr.selectText.1}"></label>
<hr>
<div id="languages">
<div class="form-check" th:each="language, iterStat : ${languages}">
<input type="checkbox" th:name="languages" th:value="${language}" required th:id="${'language-' + language}" onchange="handleLangSelection()" />
<label th:for="${'language-' + language}" th:text="${language}"></label>
</div>
</div>
<hr>
</div>
<div class="mb-3">
<label th:text="#{ocr.selectText.10}"></label>
<select class="form-control" name="ocrType">
<option value="skip-text" th:text="#{ocr.selectText.6}"></option>
<option value="force-ocr" th:text="#{ocr.selectText.7}"></option>
<option value="Normal" th:text="#{ocr.selectText.8}"></option>
</select>
</div>
<br>
<label for="languages" class="form-label" th:text="#{ocr.selectText.9}"></label>
<div class="mb-3">
<label th:text="#{ocr.selectText.12}"></label>
<select class="form-control" name="ocrRenderType">
<option value="hocr">HOCR (Latin/Roman alphabet only)</option>
<option value="sandwich">Sandwich</option>
</select>
</div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{ocr.submit}"></button>
</form>
<script th:inline="javascript">
// Get language translations from Thymeleaf messages
const languageTranslations = {};
/*[# th:each="lang : ${languages}"]*/
languageTranslations['[(${lang})]'] = /*[[#{${'lang.' + lang}}]]*/[(${lang})];
/*[/]*/
// Map two-letter language codes to Tesseract language codes
const localeToTesseract = {
'en': 'eng',
'fr': 'fra',
'de': 'deu',
'es': 'spa',
'it': 'ita',
'pt': 'por',
'ru': 'rus',
'zh': 'chi_sim',
'ja': 'jpn',
'ko': 'kor',
'ar': 'ara',
'hi': 'hin',
'nl': 'nld',
'cs': 'ces',
'pl': 'pol',
'tr': 'tur',
'uk': 'ukr',
'vi': 'vie',
'sv': 'swe',
'no': 'nor',
'fi': 'fin',
'da': 'dan',
'el': 'ell',
'he': 'heb',
'hu': 'hun',
'bg': 'bul',
'ro': 'ron',
'hr': 'hrv',
'sk': 'slk',
'id': 'ind',
'th': 'tha',
'sl': 'slv'
};
// Function to get the translated language name
function getTranslatedLanguageName(shortCode) {
return languageTranslations[shortCode] || shortCode;
}
// Function to prioritize languages based on browser locale
function prioritizeLanguages() {
const languageContainer = document.getElementById('languages');
if (!languageContainer) return;
// Update all labels with translated language names
const formChecks = Array.from(languageContainer.getElementsByClassName('form-check'));
if (formChecks.length === 0) return;
formChecks.forEach(element => {
const label = element.querySelector('label');
if (label) {
const langCode = label.getAttribute('for').split('-')[1];
label.textContent = getTranslatedLanguageName(langCode);
}
});
// Get browser/UI language
const browserLanguage = document.documentElement.lang || navigator.language || navigator.userLanguage;
const uiLanguage = document.documentElement.getAttribute('data-language') || browserLanguage;
// Extract primary language code
const primaryLanguageCode = (uiLanguage || '').split(/[-_]/)[0].toLowerCase();
const tesseractPrimaryCode = localeToTesseract[primaryLanguageCode];
// Define priority language array
const priorityLanguages = [];
// Primary language first
if (tesseractPrimaryCode) {
priorityLanguages.push(tesseractPrimaryCode);
}
// English second (if not already added)
if (tesseractPrimaryCode !== 'eng') {
priorityLanguages.push('eng');
}
// Sort the elements
const sortedElements = formChecks.sort((a, b) => {
const aInput = a.querySelector('input');
const bInput = b.querySelector('input');
if (!aInput || !bInput) return 0;
const aLangCode = aInput.id.split('-')[1];
const bLangCode = bInput.id.split('-')[1];
const aIsPriority = priorityLanguages.includes(aLangCode);
const bIsPriority = priorityLanguages.includes(bLangCode);
if (aIsPriority && !bIsPriority) return -1;
if (!aIsPriority && bIsPriority) return 1;
if (aIsPriority && bIsPriority) {
// If both are priority, sort by priority order
return priorityLanguages.indexOf(aLangCode) - priorityLanguages.indexOf(bLangCode);
}
// For non-priority languages, sort alphabetically by full name
return getTranslatedLanguageName(aLangCode).localeCompare(getTranslatedLanguageName(bLangCode));
});
// Clear container and append sorted elements
languageContainer.innerHTML = '';
sortedElements.forEach(element => {
languageContainer.appendChild(element);
});
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
prioritizeLanguages();
});
</script>
<p th:text="#{ocr.help}"></p>
<a href="https://docs.stirlingpdf.com/Advanced%20Configuration/OCR">https://docs.stirlingpdf.com/Advanced%20Configuration/OCR</a>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{printFile.title}, header=#{printFile.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{printFile.header}"></h2>
<form action="#" th:action="@{'/api/v1/misc/print-file'}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf,image/*')}"></div>
<div class="card mb-3">
<div class="card-body">
<h4 th:text="#{printFile.selectText.1}">Select Printer</h4> <!-- Assuming the message code printFile.selectText.3 corresponds to "Select Printer" -->
<label for="printerName" th:text="#{printFile.selectText.2}">Printer Name:</label> <!-- Assuming the message code printFile.selectText.4 corresponds to "Printer Name:" -->
<input type="text" name="printerName" id="printerName" class="form-control">
</div>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{printFile.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{removeAnnotations.title}, header=#{removeAnnotations.header})}"></th:block>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">thread_unread</span>
<span class="tool-header-text" th:text="#{removeAnnotations.header}"></span>
</div>
<form id="pdfForm" th:action="@{''}" class="mb-3">
<div class="custom-file">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf', remoteCall='false')}"></div>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{removeAnnotations.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
<script th:src="@{'/js/local-pdf-input-download.js'}"></script>
<script>
document.getElementById('pdfForm').addEventListener('submit', async (e) => {
e.preventDefault();
const { PDFDocument } = PDFLib;
const processFile = async (file) => {
const origFileUrl = URL.createObjectURL(file);
const formPdfBytes = await window.fetchWithCsrf(origFileUrl).then(res => res.arrayBuffer());
const pdfDoc = await PDFDocument.load(formPdfBytes, { ignoreEncryption: true });
const pages = pdfDoc.getPages();
for (let i = 0; i < pages.length; ++i) {
const page = pages[i];
const annotations = page.node.Annots();
if (!annotations) continue;
const ctx = annotations.context;
for (let j = 0; j < annotations.size(); ++j) {
const annotation = annotations.get(j);
ctx.delete(annotation);
}
}
const pdfBytes = await pdfDoc.save();
const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
const fileName = (file.name ? file.name.replace('.pdf', '') : 'pdf') + '_removed_annotations.pdf';
return { processedData: pdfBlob, fileName };
};
await downloadFilesWithCallback(processFile);
});
</script>
</body>
</html>
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{removeBlanks.title}, header=#{removeBlanks.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">scan_delete</span>
<span class="tool-header-text" th:text="#{removeBlanks.header}"></span>
</div>
<form id="multiPdfForm" th:action="@{'/api/v1/misc/remove-blanks'}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<div class="mb-3">
<label for="threshold" th:text="#{removeBlanks.threshold}"></label>
<input type="number" class="form-control" id="threshold" name="threshold" value="10" min="0" max="255" step="1">
<small id="thresholdHelp" class="form-text text-muted" th:text="#{removeBlanks.thresholdDesc}"></small>
</div>
<div class="mb-3">
<label for="whitePercent" th:text="#{removeBlanks.whitePercent}"></label>
<input type="number" class="form-control" id="whitePercent" name="whitePercent" value="99.9" step="0.1" min="0.1" max="100">
<small id="whitePercentHelp" class="form-text text-muted" th:text="#{removeBlanks.whitePercentDesc}"></small>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{removeBlanks.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{repair.title}, header=#{repair.header})}"></th:block>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">build</span>
<span class="tool-header-text" th:text="#{repair.header}"></span>
</div>
<form id="multiPdfForm" th:action="@{'/api/v1/misc/repair'}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{repair.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{replace-color.title}, header=#{replace-color.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">zoom_in_map</span>
<span class="tool-header-text" th:text="#{replace-color.header}"></span>
</div>
<form action="#" th:action="@{'/api/v1/misc/replace-invert-pdf'}" method="post" enctype="multipart/form-data">
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<div class="card mb-3">
<div class="card-body">
<h4 th:text="#{replace-color.selectText.1}"></h4>
<select name="replaceAndInvertOption" id="replace-invert" class="form-control">
<option value="HIGH_CONTRAST_COLOR" th:text="#{replace-color.selectText.2}" ></option>
<option value="CUSTOM_COLOR" th:text="#{replace-color.selectText.3}"></option>
<option value="FULL_INVERSION" th:text="#{replace-color.selectText.4}" selected></option>
</select>
</div>
</div>
<div class="card mb-3" id="high-contrast-options" style="display: none">
<div class="card-body">
<h4 th:text="#{replace-color.selectText.5}"></h4>
<select name="highContrastColorCombination" id="high-contrast" class="form-control">
<option value="WHITE_TEXT_ON_BLACK" th:text="#{replace-color.selectText.6}" selected></option>
<option value="BLACK_TEXT_ON_WHITE" th:text="#{replace-color.selectText.7}"></option>
<option value="YELLOW_TEXT_ON_BLACK" th:text="#{replace-color.selectText.8}"></option>
<option value="GREEN_TEXT_ON_BLACK" th:text="#{replace-color.selectText.9}"></option>
</select>
</div>
</div>
<div class="card mb-3" id = "custom-color-1" style="display: none">
<div class="card-body">
<h4 th:text="#{replace-color.selectText.10}"></h4>
<input type="color" name="textColor" id="text-color" class="form-control">
</div>
</div>
<div class="card mb-3" id = "custom-color-2" style="display: none">
<div class="card-body">
<h4 th:text="#{replace-color.selectText.11}"></h4>
<input type="color" name="backGroundColor" id="bg-color" class="form-control">
</div>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{replace-color.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
<script>
$(document).ready(function(){
$('#replace-invert').change(function() {
var selectedOption = $(this).val();
// Hide all conditional fields by default
$('#high-contrast-options').hide();
$('#custom-color-1').hide();
$('#custom-color-2').hide();
if (selectedOption === "HIGH_CONTRAST_COLOR") {
$('#high-contrast-options').show();
} else if (selectedOption === "CUSTOM_COLOR") {
$('#custom-color-1').show();
$('#custom-color-2').show();
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{showJS.title}, header=#{showJS.header})}"></th:block>
<link th:href="@{'/css/prism.css'}" rel="stylesheet">
<script th:src="@{'/js/thirdParty/prism.js'}"></script>
<style>
/* Add a max-height and make it scrollable */
#script-content {
max-height: 1000px; /* Adjust this to your preferred maximum height */
overflow-y: auto;
}
</style>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">javascript</span>
<span class="tool-header-text" th:text="#{showJS.header}"></span>
</div>
<form id="pdfInfoForm" method="post" enctype="multipart/form-data" th:action="@{'/show-javascript'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, remoteCall='false', accept='application/pdf')}"></div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{showJS.submit}"></button>
</form>
<div class="container mt-4">
<!-- Iterate over each main section in the JSON -->
<div id="script-content">
<!-- JavaScript will populate this section -->
</div>
<!-- Button to download the JSON -->
<a href="#" id="downloadJS" class="btn btn-primary mt-3" style="display: none;" th:text="#{showJS.downloadJS}">Download JSON</a>
</div>
<script th:src="@{'/js/fetch-utils.js'}"></script>
<script>
document.querySelector('#pdfInfoForm').addEventListener('submit', function(event){
event.preventDefault();
// Fetch the formData
const formData = new FormData(event.target);
fetchWithCsrf('api/v1/misc/show-javascript', {
method: 'POST',
body: formData
}).then(response => response.text())
.then(data => {
// Escape < and > characters
let escapedData = data.replace(/</g, '&lt;').replace(/>/g, '&gt;');
// Create the elements manually
let preElement = document.createElement('pre');
let codeElement = document.createElement('code');
codeElement.classList.add('language-javascript');
codeElement.textContent = escapedData; // Use textContent instead of innerHTML
preElement.appendChild(codeElement);
let scriptContent = document.querySelector('#script-content');
// Clear existing content, if any
while (scriptContent.firstChild) {
scriptContent.removeChild(scriptContent.firstChild);
}
scriptContent.appendChild(preElement);
// Highlight the code using Prism.js
Prism.highlightAll();
// Create a blob object from the data and create a URL for it
let blob = new Blob([data], {type: 'application/javascript'});
let url = URL.createObjectURL(blob);
// Set the URL as the href of the download button and provide a download name
let downloadButton = document.querySelector('#downloadJS');
downloadButton.href = url;
downloadButton.download = 'extracted.js';
downloadButton.style.display = 'block';
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,202 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{AddStampRequest.title}, header=#{AddStampRequest.header})}"></th:block>
<style>
.a4container {
position: relative;
width: 50%;
aspect-ratio: 0.707/1;
border: 1px solid #ddd;
box-sizing: border-box;
background-color: white;
}
</style>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon security">approval</span>
<span class="tool-header-text" th:text="#{AddStampRequest.header}"></span>
</div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/add-stamp'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<br>
<div class="mb-3">
<label for="pageOrder" th:text="#{pageSelectionPrompt}"></label>
<input type="text" class="form-control" id="pageOrder" name="pageNumbers" value="1" placeholder="(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)" required>
</div>
<div class="mb-3">
<label for="customMargin" class="form-label" th:text="#{AddStampRequest.customMargin}">Custom Margin</label>
<select class="form-select" id="customMargin" name="customMargin">
<option value="small" th:text="#{sizes.small}"></option>
<option value="medium" selected th:text="#{sizes.medium}"></option>
<option value="large" th:text="#{sizes.large}"></option>
<option value="x-large" th:text="#{sizes.x-large}"></option>
</select>
</div>
<div class="mb-3">
<label th:text="#{AddStampRequest.position}"></label>
<div class="a4container">
<div class="pageNumber" id="1" style="top: 10%; left: 10%;">1</div>
<div class="pageNumber" id="2" style="top: 10%; left: 50%;">2</div>
<div class="pageNumber" id="3" style="top: 10%; left: 90%;">3</div>
<div class="pageNumber" id="4" style="top: 50%; left: 10%;">4</div>
<div class="pageNumber" id="5" style="top: 50%; left: 50%;">5</div>
<div class="pageNumber" id="6" style="top: 50%; left: 90%;">6</div>
<div class="pageNumber" id="7" style="top: 90%; left: 10%;">7</div>
<div class="pageNumber selectedPosition" id="8" style="top: 90%; left: 50%;">8</div>
<div class="pageNumber" id="9" style="top: 90%; left: 90%;">9</div>
</div>
</div>
<input type="hidden" id="numberInput" name="position" value="8">
<div class="mb-3">
<label for="stampType" class="form-label" th:text="#{AddStampRequest.stampType}">Stamp Type</label>
<select class="form-select" id="stampType" name="stampType" onchange="toggleFileOption()">
<option value="text" th:text="#{watermark.type.1}">Text</option>
<option value="image" th:text="#{watermark.type.2}">Image</option>
</select>
</div>
<div id="stampTextGroup" class="mb-3">
<label for="stampText" class="form-label" th:text="#{AddStampRequest.stampText}">Stamp Text</label>
<input type="text" class="form-control" id="stampText" name="stampText">
</div>
<div id="stampImageGroup" class="mb-3" style="display: none;">
<label for="stampImage" class="form-label" th:text="#{AddStampRequest.stampImage}">Stamp Image</label>
<input type="file" class="form-control" id="stampImage" name="stampImage" accept="image/*" >
</div>
<div id="alphabetGroup" class="mb-3">
<label for="alphabet" class="form-label" th:text="#{AddStampRequest.alphabet}">Alphabet</label>
<select class="form-select" id="alphabet" name="alphabet">
<option value="roman">Roman</option>
<option value="arabic">العربية</option>
<option value="japanese">日本語</option>
<option value="korean">한국어</option>
<option value="chinese">简体中文</option>
</select>
</div>
<div class="mb-3">
<label for="fontSize" class="form-label" th:text="#{AddStampRequest.fontSize}">Font Size</label>
<input type="number" class="form-control" id="fontSize" name="fontSize" value="30">
</div>
<div class="mb-3">
<label for="rotation" class="form-label" th:text="#{AddStampRequest.rotation}">Rotation</label>
<input type="number" class="form-control" id="rotation" name="rotation" value="0">
</div>
<div class="mb-3">
<label for="opacity" class="form-label" th:text="#{AddStampRequest.opacity}">Opacity</label>
<input type="number" class="form-control" id="opacity" name="opacity" step="0.1" value="0.5">
</div>
<div class="mb-3">
<label for="overrideX" class="form-label" th:text="#{AddStampRequest.overrideX}">Override X</label>
<input type="number" class="form-control" id="overrideX" name="overrideX" value="-1">
</div>
<div class="mb-3">
<label for="overrideY" class="form-label" th:text="#{AddStampRequest.overrideY}">Override Y</label>
<input type="number" class="form-control" id="overrideY" name="overrideY" value="-1">
</div>
<div class="mb-3">
<label for="customColor" class="form-label" th:text="#{AddStampRequest.customColor}">Custom Color</label>
<div class="form-control form-control-color" style="background-color: #d3d3d3;">
<input type="color" id="customColor" name="customColor" value="#d3d3d3">
</div>
<script>
let colorInput = document.getElementById("customColor");
if (colorInput) {
let colorInputContainer = colorInput.parentElement;
if (colorInputContainer) {
colorInput.onchange = function() {
colorInputContainer.style.backgroundColor = colorInput.value;
}
colorInputContainer.style.backgroundColor = colorInput.value;
}
}
</script>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{AddStampRequest.submit}"></button>
</form>
</div>
</div>
</div>
<script>
let cells = document.querySelectorAll('.pageNumber');
let inputField = document.getElementById('numberInput');
cells.forEach(cell => {
cell.addEventListener('click', function(e) {
cells.forEach(cell => {
cell.classList.remove('selectedPosition'); // Remove selected class from all cells
cell.classList.remove('selectedHovered'); // Also remove selectedHovered class
});
let selectedLocation = e.target.id;
inputField.value = selectedLocation;
e.target.classList.add('selectedPosition'); // Add selected class to clicked cell
e.target.classList.add('selectedHovered'); // Add selectedHovered class
});
cell.addEventListener('mouseenter', function(e) {
if(e.target.classList.contains('selectedPosition')) {
e.target.classList.add('selectedHovered');
}
});
cell.addEventListener('mouseleave', function(e) {
if(e.target.classList.contains('selectedPosition')) {
e.target.classList.remove('selectedHovered');
}
});
});
function toggleFileOption() {
const stampImage = document.getElementById('stampImage');
const stampType = document.getElementById('stampType').value;
const stampTextGroup = document.getElementById('stampTextGroup');
const stampImageGroup = document.getElementById('stampImageGroup');
const alphabetGroup = document.getElementById('alphabetGroup');
if (stampType === 'text') {
if (stampImage.hasAttribute('required')) {
stampImage.removeAttribute('required');
}
stampTextGroup.style.display = 'block';
stampImageGroup.style.display = 'none';
alphabetGroup.style.display = 'block';
} else if (stampType === 'image') {
if (!stampImage.hasAttribute('required')) {
stampImage.setAttribute('required', '');
}
stampTextGroup.style.display = 'none';
stampImageGroup.style.display = 'block';
alphabetGroup.style.display = 'none';
}
}
</script>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{unlockPDFForms.title}, header=#{unlockPDFForms.header})}"></th:block>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon other">preview_off</span>
<span class="tool-header-text" th:text="#{unlockPDFForms.header}"></span>
</div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/unlock-pdf-forms'}" id="pdfForm" class="mb-3">
<div class="custom-file">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
</div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{unlockPDFForms.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>