mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-09-13 20:25:28 +02:00
Resolve conflicts using files from Stirling-2.0
This commit is contained in:
@@ -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, '<').replace(/>/g, '>');
|
||||
|
||||
// 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>
|
||||
Reference in New Issue
Block a user