HTML, CSS, JS and JAVA corrections (#810)
* CSS corrections * HTML corrections * JS corrections * JAVA corrections * remove tab * CSS corrections 2 * JS corrections 2 * back to the roots * max-linie 127 * add slash hr|br * return bootstrap-icons.css * return bootstrap-icons.min.css * return bootstrap.min.css * Update bootstrap-icons.css * Update bootstrap-icons.min.css * Update bootstrap-icons.min.css * Update bootstrap.min.css * CSS corrections * HTML corrections * JS corrections * JAVA corrections * remove tab * CSS corrections 2 * JS corrections 2 * back to the roots * max-linie 127 * add slash hr|br * return bootstrap-icons.css * Update bootstrap-icons.css * Bootstrap CSS * Update prism.css
This commit is contained in:
@@ -1,22 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title='<3')}"></th:block>
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title='<3')}"></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"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,326 +1,286 @@
|
||||
<!doctype html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{account.title})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{account.title}, header=#{account.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-9">
|
||||
<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">
|
||||
|
||||
<!-- User Settings Title -->
|
||||
<h2 class="text-center" th:text="#{account.accountSettings}">User Settings</h2>
|
||||
<hr>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'notAuthenticated'}" class="alert alert-danger">
|
||||
<span th:text="#{notAuthenticatedMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'userNotFound'}" class="alert alert-danger">
|
||||
<span th:text="#{userNotFoundMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'incorrectPassword'}" class="alert alert-danger">
|
||||
<span th:text="#{incorrectPasswordMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
|
||||
<span th:text="#{usernameExistsMessage}">Default message if not found</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- At the top of the user settings -->
|
||||
<h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
|
||||
|
||||
<div th:if="${error}" class="alert alert-danger" role="alert">
|
||||
<span th:text="${error}">Error Message</span>
|
||||
</div>
|
||||
<!-- Change Username Form -->
|
||||
<h4></h4>
|
||||
<form action="api/v1/user/change-username" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="newUsername" th:text="#{account.changeUsername}">Change Username</label>
|
||||
<input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="currentPassword" th:text="#{password}">Password</label>
|
||||
<input type="password" class="form-control" name="currentPassword" id="currentPasswordUsername" th:placeholder="#{password}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{account.changeUsername}">Change Username</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr> <!-- Separator Line -->
|
||||
|
||||
<!-- Change Password Form -->
|
||||
<h4 th:text="#{account.changePassword}">Change Password?</h4>
|
||||
<form action="api/v1/user/change-password" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
|
||||
<input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword" th:placeholder="#{account.oldPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" th:text="#{account.newPassword}">New Password</label>
|
||||
<input type="password" class="form-control" name="newPassword" id="newPassword" th:placeholder="#{account.newPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirmNewPassword" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
|
||||
<input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword" th:placeholder="#{account.confirmNewPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{account.changePassword}">Change Password</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" th:text="#{account.yourApiKey}">
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="input-group mb-3">
|
||||
<input type="password" class="form-control" id="apiKey" th:placeholder="#{account.yourApiKey}" readonly>
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" id="copyBtn" type="button" onclick="copyToClipboard()">
|
||||
<img class="blackwhite-icon" src="images/clipboard.svg" alt="Copy" style="height:20px;">
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" id="showBtn" type="button" onclick="showApiKey()">
|
||||
<img class="blackwhite-icon" id="eyeIcon" src="images/eye.svg" alt="Toggle API Key Visibility" style="height:20px;">
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">
|
||||
<img class="blackwhite-icon" id="eyeIcon" src="images/arrow-clockwise.svg" alt="Refresh API-Key" style="height:20px;">
|
||||
</button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyToClipboard() {
|
||||
const apiKeyElement = document.getElementById("apiKey");
|
||||
apiKeyElement.select();
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
|
||||
function showApiKey() {
|
||||
const apiKeyElement = document.getElementById("apiKey");
|
||||
const copyBtn = document.getElementById("copyBtn");
|
||||
const eyeIcon = document.getElementById("eyeIcon");
|
||||
if (apiKeyElement.type === "password") {
|
||||
apiKeyElement.type = "text";
|
||||
eyeIcon.src = "images/eye-slash.svg";
|
||||
copyBtn.disabled = false; // Enable copy button when API key is visible
|
||||
} else {
|
||||
apiKeyElement.type = "password";
|
||||
eyeIcon.src = "images/eye.svg";
|
||||
copyBtn.disabled = true; // Disable copy button when API key is hidden
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async function() {
|
||||
try {
|
||||
let response = await fetch('/api/v1/user/get-api-key', { method: 'POST' });
|
||||
if (response.status === 200) {
|
||||
let apiKey = await response.text();
|
||||
manageUIState(apiKey);
|
||||
} else {
|
||||
manageUIState(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('There was an error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
async function refreshApiKey() {
|
||||
try {
|
||||
let response = await fetch('/api/v1/user/update-api-key', { method: 'POST' });
|
||||
if (response.status === 200) {
|
||||
let apiKey = await response.text();
|
||||
manageUIState(apiKey);
|
||||
document.getElementById("apiKey").type = 'text';
|
||||
document.getElementById("copyBtn").disabled = false;
|
||||
} else {
|
||||
alert('Error refreshing API key.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('There was an error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function manageUIState(apiKey) {
|
||||
const apiKeyElement = document.getElementById("apiKey");
|
||||
const showBtn = document.getElementById("showBtn");
|
||||
const copyBtn = document.getElementById("copyBtn");
|
||||
|
||||
if (apiKey && apiKey.trim().length > 0) {
|
||||
apiKeyElement.value = apiKey;
|
||||
showBtn.disabled = false;
|
||||
copyBtn.disabled = true;
|
||||
} else {
|
||||
apiKeyElement.value = "";
|
||||
showBtn.disabled = true;
|
||||
copyBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const form = document.querySelector('form[action="api/v1/user/change-password"]');
|
||||
|
||||
form.addEventListener('submit', function(event) {
|
||||
const newPassword = document.getElementById('newPassword').value;
|
||||
const confirmNewPassword = document.getElementById('confirmNewPassword').value;
|
||||
|
||||
if (newPassword !== confirmNewPassword) {
|
||||
alert('New Password and Confirm New Password must match.');
|
||||
event.preventDefault(); // Prevent form submission
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<hr> <!-- Separator Line -->
|
||||
|
||||
<h4 th:text="#{account.syncTitle}">Sync browser settings with Account</h4>
|
||||
<div class="container mt-4">
|
||||
<h3 th:text="#{account.settingsCompare}">Settings Comparison:</h3>
|
||||
<table id="settingsTable" class="table table-bordered table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:text="#{account.property}">Property</th>
|
||||
<th th:text="#{account.accountSettings}">Account Setting</th>
|
||||
<th th:text="#{account.webBrowserSettings}">Web Browser Setting</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- This will be dynamically populated by JavaScript -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="buttons-container mt-3 text-center">
|
||||
<button id="syncToBrowser" class="btn btn-primary btn-sm" th:text="#{account.syncToBrowser}">Sync Account -> Browser</button>
|
||||
<button id="syncToAccount" class="btn btn-secondary btn-sm" th:text="#{account.syncToAccount}">Sync Account <- Browser</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.buttons-container {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const settingsTableBody = document.querySelector("#settingsTable tbody");
|
||||
|
||||
/*<![CDATA[*/
|
||||
var accountSettingsString = /*[[${settings}]]*/ {};
|
||||
/*]]>*/
|
||||
var accountSettings = JSON.parse(accountSettingsString);
|
||||
|
||||
let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]);
|
||||
|
||||
allKeys.forEach(key => {
|
||||
if(key === 'debug' || key === '0' || key === '1') return; // Ignoring specific keys
|
||||
|
||||
const accountValue = accountSettings[key] || '-';
|
||||
const browserValue = localStorage.getItem(key) || '-';
|
||||
|
||||
const row = settingsTableBody.insertRow();
|
||||
const propertyCell = row.insertCell(0);
|
||||
const accountCell = row.insertCell(1);
|
||||
const browserCell = row.insertCell(2);
|
||||
|
||||
propertyCell.textContent = key;
|
||||
accountCell.textContent = accountValue;
|
||||
browserCell.textContent = browserValue;
|
||||
});
|
||||
|
||||
document.getElementById('syncToBrowser').addEventListener('click', function() {
|
||||
// First, clear the local storage
|
||||
localStorage.clear();
|
||||
|
||||
// Then, set the account settings to local storage
|
||||
for (let key in accountSettings) {
|
||||
if(key !== 'debug' && key !== '0' && key !== '1') { // Only sync non-ignored keys
|
||||
localStorage.setItem(key, accountSettings[key]);
|
||||
}
|
||||
}
|
||||
location.reload(); // Refresh the page after sync
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('syncToAccount').addEventListener('click', function() {
|
||||
let form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = "api/v1/user/updateUserSettings"; // Your endpoint URL
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if(key !== 'debug' && key !== '0' && key !== '1') { // Only send non-ignored keys
|
||||
let hiddenField = document.createElement("input");
|
||||
hiddenField.type = "hidden";
|
||||
hiddenField.name = key;
|
||||
hiddenField.value = localStorage.getItem(key);
|
||||
form.appendChild(hiddenField);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="mb-3 mt-4">
|
||||
<a href="logout">
|
||||
<button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button>
|
||||
</a>
|
||||
<a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank">
|
||||
<button type="button" class="btn btn-info" th:text="#{account.adminSettings}">Admin Settings</button>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- User Settings Title -->
|
||||
<h2 class="text-center" th:text="#{account.accountSettings}">User Settings</h2>
|
||||
<hr />
|
||||
<th:block th:if="${param.messageType != null and param.messageType.size() > 0}">
|
||||
<div th:if="${param.messageType[0] == 'notAuthenticated'}" class="alert alert-danger">
|
||||
<span th:text="#{notAuthenticatedMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType[0] == 'userNotFound'}" class="alert alert-danger">
|
||||
<span th:text="#{userNotFoundMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType[0] == 'incorrectPassword'}" class="alert alert-danger">
|
||||
<span th:text="#{incorrectPasswordMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
|
||||
<span th:text="#{usernameExistsMessage}">Default message if not found</span>
|
||||
</div>
|
||||
</th:block>
|
||||
<!-- At the top of the user settings -->
|
||||
<h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
|
||||
<th:block th:if="${error}">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<span th:text="${error}">Error Message</span>
|
||||
</div>
|
||||
</th:block>
|
||||
<!-- Change Username Form -->
|
||||
<h4></h4>
|
||||
<form action="api/v1/user/change-username" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="newUsername" th:text="#{account.changeUsername}">Change Username</label>
|
||||
<input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="currentPassword" th:text="#{password}">Password</label>
|
||||
<input type="password" class="form-control" name="currentPassword" id="currentPasswordUsername" th:placeholder="#{password}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{account.changeUsername}">Change Username</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr /> <!-- Separator Line -->
|
||||
|
||||
<!-- Change Password Form -->
|
||||
<h4 th:text="#{account.changePassword}">Change Password?</h4>
|
||||
<form action="api/v1/user/change-password" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
|
||||
<input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword" th:placeholder="#{account.oldPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" th:text="#{account.newPassword}">New Password</label>
|
||||
<input type="password" class="form-control" name="newPassword" id="newPassword" th:placeholder="#{account.newPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirmNewPassword" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
|
||||
<input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword" th:placeholder="#{account.confirmNewPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{account.changePassword}">Change Password</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" th:text="#{account.yourApiKey}"></div>
|
||||
<div class="card-body">
|
||||
<div class="input-group mb-3">
|
||||
<input type="password" class="form-control" id="apiKey" th:placeholder="#{account.yourApiKey}" readonly>
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" id="copyBtn" type="button" onclick="copyToClipboard()">
|
||||
<img class="blackwhite-icon" src="images/clipboard.svg" alt="Copy" style="height:20px;">
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" id="showBtn" type="button" onclick="showApiKey()">
|
||||
<img class="blackwhite-icon" id="eyeIcon" src="images/eye.svg" alt="Toggle API Key Visibility" style="height:20px;">
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">
|
||||
<img class="blackwhite-icon" id="eyeIcon" src="images/arrow-clockwise.svg" alt="Refresh API-Key" style="height:20px;">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyToClipboard() {
|
||||
const apiKeyElement = document.getElementById("apiKey");
|
||||
apiKeyElement.select();
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
function showApiKey() {
|
||||
const apiKeyElement = document.getElementById("apiKey");
|
||||
const copyBtn = document.getElementById("copyBtn");
|
||||
const eyeIcon = document.getElementById("eyeIcon");
|
||||
if (apiKeyElement.type === "password") {
|
||||
apiKeyElement.type = "text";
|
||||
eyeIcon.src = "images/eye-slash.svg";
|
||||
copyBtn.disabled = false; // Enable copy button when API key is visible
|
||||
} else {
|
||||
apiKeyElement.type = "password";
|
||||
eyeIcon.src = "images/eye.svg";
|
||||
copyBtn.disabled = true; // Disable copy button when API key is hidden
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async function() {
|
||||
try {
|
||||
let response = await fetch('/api/v1/user/get-api-key', { method: 'POST' });
|
||||
if (response.status === 200) {
|
||||
let apiKey = await response.text();
|
||||
manageUIState(apiKey);
|
||||
} else {
|
||||
manageUIState(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('There was an error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
async function refreshApiKey() {
|
||||
try {
|
||||
let response = await fetch('/api/v1/user/update-api-key', { method: 'POST' });
|
||||
if (response.status === 200) {
|
||||
let apiKey = await response.text();
|
||||
manageUIState(apiKey);
|
||||
document.getElementById("apiKey").type = 'text';
|
||||
document.getElementById("copyBtn").disabled = false;
|
||||
} else {
|
||||
alert('Error refreshing API key.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('There was an error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function manageUIState(apiKey) {
|
||||
const apiKeyElement = document.getElementById("apiKey");
|
||||
const showBtn = document.getElementById("showBtn");
|
||||
const copyBtn = document.getElementById("copyBtn");
|
||||
|
||||
if (apiKey && apiKey.trim().length > 0) {
|
||||
apiKeyElement.value = apiKey;
|
||||
showBtn.disabled = false;
|
||||
copyBtn.disabled = true;
|
||||
} else {
|
||||
apiKeyElement.value = "";
|
||||
showBtn.disabled = true;
|
||||
copyBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const form = document.querySelector('form[action="api/v1/user/change-password"]');
|
||||
|
||||
form.addEventListener('submit', function(event) {
|
||||
const newPassword = document.getElementById('newPassword').value;
|
||||
const confirmNewPassword = document.getElementById('confirmNewPassword').value;
|
||||
|
||||
if (newPassword !== confirmNewPassword) {
|
||||
alert('New Password and Confirm New Password must match.');
|
||||
event.preventDefault(); // Prevent form submission
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<hr /> <!-- Separator Line -->
|
||||
|
||||
<h4 th:text="#{account.syncTitle}">Sync browser settings with Account</h4>
|
||||
<div class="container mt-4">
|
||||
<h3 th:text="#{account.settingsCompare}">Settings Comparison:</h3>
|
||||
<table id="settingsTable" class="table table-bordered table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:text="#{account.property}">Property</th>
|
||||
<th th:text="#{account.accountSettings}">Account Setting</th>
|
||||
<th th:text="#{account.webBrowserSettings}">Web Browser Setting</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- This will be dynamically populated by JavaScript -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="buttons-container mt-3 text-center">
|
||||
<button id="syncToBrowser" class="btn btn-primary btn-sm" th:text="#{account.syncToBrowser}">Sync Account -> Browser</button>
|
||||
<button id="syncToAccount" class="btn btn-secondary btn-sm" th:text="#{account.syncToAccount}">Sync Account <- Browser</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const settingsTableBody = document.querySelector("#settingsTable tbody");
|
||||
|
||||
/*<![CDATA[*/
|
||||
var accountSettingsString = /*[[${settings}]]*/ {};
|
||||
/*]]>*/
|
||||
var accountSettings = JSON.parse(accountSettingsString);
|
||||
|
||||
let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]);
|
||||
|
||||
allKeys.forEach(key => {
|
||||
if(key === 'debug' || key === '0' || key === '1') return; // Ignoring specific keys
|
||||
|
||||
const accountValue = accountSettings[key] || '-';
|
||||
const browserValue = localStorage.getItem(key) || '-';
|
||||
|
||||
const row = settingsTableBody.insertRow();
|
||||
const propertyCell = row.insertCell(0);
|
||||
const accountCell = row.insertCell(1);
|
||||
const browserCell = row.insertCell(2);
|
||||
|
||||
propertyCell.textContent = key;
|
||||
accountCell.textContent = accountValue;
|
||||
browserCell.textContent = browserValue;
|
||||
});
|
||||
|
||||
document.getElementById('syncToBrowser').addEventListener('click', function() {
|
||||
// First, clear the local storage
|
||||
localStorage.clear();
|
||||
|
||||
// Then, set the account settings to local storage
|
||||
for (let key in accountSettings) {
|
||||
if(key !== 'debug' && key !== '0' && key !== '1') { // Only sync non-ignored keys
|
||||
localStorage.setItem(key, accountSettings[key]);
|
||||
}
|
||||
}
|
||||
location.reload(); // Refresh the page after sync
|
||||
});
|
||||
|
||||
document.getElementById('syncToAccount').addEventListener('click', function() {
|
||||
let form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = "api/v1/user/updateUserSettings"; // Your endpoint URL
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if(key !== 'debug' && key !== '0' && key !== '1') { // Only send non-ignored keys
|
||||
let hiddenField = document.createElement("input");
|
||||
hiddenField.type = "hidden";
|
||||
hiddenField.name = key;
|
||||
hiddenField.value = localStorage.getItem(key);
|
||||
form.appendChild(hiddenField);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
<div class="mb-3 mt-4">
|
||||
<a href="logout">
|
||||
<button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button>
|
||||
</a>
|
||||
<a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank">
|
||||
<button type="button" class="btn btn-info" th:text="#{account.adminSettings}">Admin Settings</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,84 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{adminUserSettings.title}, header=#{adminUserSettings.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{adminUserSettings.title}, header=#{adminUserSettings.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<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-8">
|
||||
|
||||
<!-- User Settings Title -->
|
||||
<h2 class="text-center" th:text="#{adminUserSettings.header}">Admin User Control Settings</h2>
|
||||
<!-- User Settings Title -->
|
||||
<h2 class="text-center" th:text="#{adminUserSettings.header}">Admin User Control Settings</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:text="#{username}">Username</th>
|
||||
<th th:text="#{adminUserSettings.roles}">Roles</th>
|
||||
<th th:text="#{adminUserSettings.actions}">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="user : ${users}">
|
||||
<td th:text="${user.username}"></td>
|
||||
<td th:text="${user.getRolesAsString()}"></td>
|
||||
<td>
|
||||
<form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post">
|
||||
<button type="submit" th:text="#{delete}">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:text="#{username}">Username</th>
|
||||
<th th:text="#{adminUserSettings.roles}">Roles</th>
|
||||
<th th:text="#{adminUserSettings.actions}">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="user : ${users}">
|
||||
<td th:text="${user.username}"></td>
|
||||
<td th:text="${user.getRolesAsString()}"></td>
|
||||
<td>
|
||||
<form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post">
|
||||
<button type="submit" th:text="#{delete}">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<h2 th:text="#{adminUserSettings.addUser}">Add New User</h2>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
|
||||
<span th:text="#{usernameExistsMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<form action="/api/v1/user/admin/saveUser" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="username" th:text="#{username}">Username</label>
|
||||
<input type="text" class="form-control" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" th:text="#{password}">Password</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
||||
<select name="role" class="form-control" required>
|
||||
<option value="ROLE_ADMIN" th:text="#{adminUserSettings.admin}">Admin</option>
|
||||
<option value="ROLE_USER" th:text="#{adminUserSettings.user}">User</option>
|
||||
<option value="ROLE_LIMITED_API_USER" th:text="#{adminUserSettings.apiUser}">Limited API User</option>
|
||||
<option value="ROLE_WEB_ONLY_USER" th:text="#{adminUserSettings.webOnlyUser}">Web Only User</option>
|
||||
<option value="ROLE_DEMO_USER" th:text="#{adminUserSettings.demoUser}">Demo User</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="forceChange" name="forceChange">
|
||||
<label class="form-check-label" for="forceChange" th:text="#{adminUserSettings.forceChange}">Force user to change username/password on login</label>
|
||||
</div>
|
||||
|
||||
<!-- Add other fields as required -->
|
||||
<button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
|
||||
</form>
|
||||
</div>
|
||||
<h2 th:text="#{adminUserSettings.addUser}">Add New User</h2>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
|
||||
<span th:text="#{usernameExistsMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<form action="/api/v1/user/admin/saveUser" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="username" th:text="#{username}">Username</label>
|
||||
<input type="text" class="form-control" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" th:text="#{password}">Password</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
||||
<select name="role" class="form-control" required>
|
||||
<option value="ROLE_ADMIN" th:text="#{adminUserSettings.admin}">Admin</option>
|
||||
<option value="ROLE_USER" th:text="#{adminUserSettings.user}">User</option>
|
||||
<option value="ROLE_LIMITED_API_USER" th:text="#{adminUserSettings.apiUser}">Limited API User</option>
|
||||
<option value="ROLE_WEB_ONLY_USER" th:text="#{adminUserSettings.webOnlyUser}">Web Only User</option>
|
||||
<option value="ROLE_DEMO_USER" th:text="#{adminUserSettings.demoUser}">Demo User</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="forceChange" name="forceChange">
|
||||
<label class="form-check-label" for="forceChange" th:text="#{adminUserSettings.forceChange}">Force user to change username/password on login</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add other fields as required -->
|
||||
<button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{autoSplitPDF.title}, header=#{autoSplitPDF.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{autoSplitPDF.title}, header=#{autoSplitPDF.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{autoSplitPDF.header}"></h2>
|
||||
<!-- Added a brief description -->
|
||||
<p th:text="#{autoSplitPDF.description}"></p>
|
||||
<ul>
|
||||
<li th:text="#{autoSplitPDF.selectText.1}"></li>
|
||||
<li th:text="#{autoSplitPDF.selectText.2}"></li>
|
||||
<li th:text="#{autoSplitPDF.selectText.3}"></li>
|
||||
<li th:text="#{autoSplitPDF.selectText.4}"></li>
|
||||
</ul>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/auto-split-pdf}">
|
||||
<p th:text="#{autoSplitPDF.formPrompt}"></p>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="duplexMode" id="duplexMode">
|
||||
<label class="ms-3" for="duplexMode" th:text=#{autoSplitPDF.duplexMode}></label>
|
||||
</div>
|
||||
<p><a th:href="@{files/Auto Splitter Divider (minimal).pdf}" download th:text="#{autoSplitPDF.dividerDownload1}"></a></p>
|
||||
<p><a th:href="@{files/Auto Splitter Divider (with instructions).pdf}" download th:text="#{autoSplitPDF.dividerDownload2}"></a></p>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{autoSplitPDF.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
<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="#{autoSplitPDF.header}"></h2>
|
||||
<!-- Added a brief description -->
|
||||
<p th:text="#{autoSplitPDF.description}"></p>
|
||||
<ul>
|
||||
<li th:text="#{autoSplitPDF.selectText.1}"></li>
|
||||
<li th:text="#{autoSplitPDF.selectText.2}"></li>
|
||||
<li th:text="#{autoSplitPDF.selectText.3}"></li>
|
||||
<li th:text="#{autoSplitPDF.selectText.4}"></li>
|
||||
</ul>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/auto-split-pdf}">
|
||||
<p th:text="#{autoSplitPDF.formPrompt}"></p>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="duplexMode" id="duplexMode">
|
||||
<label class="ms-3" for="duplexMode" th:text=#{autoSplitPDF.duplexMode}></label>
|
||||
</div>
|
||||
<p><a th:href="@{files/Auto Splitter Divider (minimal).pdf}" download th:text="#{autoSplitPDF.dividerDownload1}"></a></p>
|
||||
<p><a th:href="@{files/Auto Splitter Divider (with instructions).pdf}" download th:text="#{autoSplitPDF.dividerDownload2}"></a></p>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{autoSplitPDF.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,90 +1,83 @@
|
||||
<!doctype html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{changeCreds.title}, header=#{changeCreds.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{changeCreds.title}, header=#{changeCreds.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-9">
|
||||
|
||||
<!-- User Settings Title -->
|
||||
<h2 class="text-center" th:text="#{changeCreds.header}">User Settings</h2>
|
||||
<hr>
|
||||
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'notAuthenticated'}" class="alert alert-danger">
|
||||
<span th:text="#{notAuthenticatedMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'userNotFound'}" class="alert alert-danger">
|
||||
<span th:text="#{userNotFoundMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'incorrectPassword'}" class="alert alert-danger">
|
||||
<span th:text="#{incorrectPasswordMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
|
||||
<span th:text="#{usernameExistsMessage}">Default message if not found</span>
|
||||
</div>
|
||||
|
||||
<!-- At the top of the user settings -->
|
||||
<h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
|
||||
|
||||
|
||||
<!-- Change Username Form -->
|
||||
<h4></h4>
|
||||
<h4 th:text="#{changeCreds.changeUserAndPassword}">Change Username and password</h4>
|
||||
<form action="api/v1/user/change-username-and-password" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="username" th:text="#{changeCreds.newUsername}">New Username</label>
|
||||
<input type="text" class="form-control" name="username" id="username" th:placeholder="${username}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" th:text="#{changeCreds.oldPassword}">Old Password</label>
|
||||
<input type="password" class="form-control" name="password" id="password" th:placeholder="#{changeCreds.oldPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" th:text="#{changeCreds.newPassword}">New Password</label>
|
||||
<input type="password" class="form-control" name="newPassword" id="newPassword" th:placeholder="#{changeCreds.newPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirmNewPassword" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
|
||||
<input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword" th:placeholder="#{account.confirmNewPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{changeCreds.submit}">Change credentials!</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const form = document.querySelector('form[action="api/v1/user/change-username-and-password"]');
|
||||
|
||||
form.addEventListener('submit', function(event) {
|
||||
const newPassword = document.getElementById('newPassword').value;
|
||||
const confirmNewPassword = document.getElementById('confirmNewPassword').value;
|
||||
|
||||
if (newPassword !== confirmNewPassword) {
|
||||
alert('New Password and Confirm New Password must match.');
|
||||
event.preventDefault(); // Prevent form submission
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
|
||||
<!-- User Settings Title -->
|
||||
<h2 class="text-center" th:text="#{changeCreds.header}">User Settings</h2>
|
||||
<hr />
|
||||
<th:block th:if="${param.messageType != null and param.messageType.size() > 0}">
|
||||
<div th:if="${param.messageType[0] == 'notAuthenticated'}" class="alert alert-danger">
|
||||
<span th:text="#{notAuthenticatedMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType[0] == 'userNotFound'}" class="alert alert-danger">
|
||||
<span th:text="#{userNotFoundMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType[0] == 'incorrectPassword'}" class="alert alert-danger">
|
||||
<span th:text="#{incorrectPasswordMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<div th:if="${param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
|
||||
<span th:text="#{usernameExistsMessage}">Default message if not found</span>
|
||||
</div>
|
||||
</th:block>
|
||||
<!-- At the top of the user settings -->
|
||||
<h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
|
||||
|
||||
<!-- Change Username Form -->
|
||||
<h4></h4>
|
||||
<h4 th:text="#{changeCreds.changeUserAndPassword}">Change Username and password</h4>
|
||||
<form action="api/v1/user/change-username-and-password" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="username" th:text="#{changeCreds.newUsername}">New Username</label>
|
||||
<input type="text" class="form-control" name="username" id="username" th:placeholder="${username}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" th:text="#{changeCreds.oldPassword}">Old Password</label>
|
||||
<input type="password" class="form-control" name="password" id="password" th:placeholder="#{changeCreds.oldPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" th:text="#{changeCreds.newPassword}">New Password</label>
|
||||
<input type="password" class="form-control" name="newPassword" id="newPassword" th:placeholder="#{changeCreds.newPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirmNewPassword" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
|
||||
<input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword" th:placeholder="#{account.confirmNewPassword}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{changeCreds.submit}">Change credentials!</button>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const form = document.querySelector('form[action="api/v1/user/change-username-and-password"]');
|
||||
|
||||
form.addEventListener('submit', function(event) {
|
||||
const newPassword = document.getElementById('newPassword').value;
|
||||
const confirmNewPassword = document.getElementById('confirmNewPassword').value;
|
||||
|
||||
if (newPassword !== confirmNewPassword) {
|
||||
alert('New Password and Confirm New Password must match.');
|
||||
event.preventDefault(); // Prevent form submission
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{BookToPDF.title}, header=#{BookToPDF.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{BookToPDF.title}, header=#{BookToPDF.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{BookToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/book/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{BookToPDF.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{BookToPDF.help}"></p>
|
||||
<p class="mt-3" th:text="#{BookToPDF.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
<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="#{BookToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/book/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{BookToPDF.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{BookToPDF.help}"></p>
|
||||
<p class="mt-3" th:text="#{BookToPDF.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,37 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{fileToPDF.title}, header=#{fileToPDF.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{fileToPDF.title}, header=#{fileToPDF.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{fileToPDF.header}"></h2>
|
||||
<p th:text="#{processTimeWarning}">
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/file/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{fileToPDF.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{fileToPDF.credit}"></p>
|
||||
<p class="mt-3" th:text="#{fileToPDF.supportedFileTypes}"></p>
|
||||
<p th:utext="#{fileToPDF.fileTypesList}"></p>
|
||||
<a href="https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html">https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<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="#{fileToPDF.header}"></h2>
|
||||
<p th:text="#{processTimeWarning}"></p>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/file/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{fileToPDF.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{fileToPDF.credit}"></p>
|
||||
<p class="mt-3" th:text="#{fileToPDF.supportedFileTypes}"></p>
|
||||
<p th:utext="#{fileToPDF.fileTypesList}"></p>
|
||||
<a href="https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html">https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,36 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{HTMLToPDF.title}, header=#{HTMLToPDF.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{HTMLToPDF.title}, header=#{HTMLToPDF.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="mb-3">
|
||||
<h2 th:text="#{HTMLToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/html/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='text/html,application/zip' )}"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="zoom" th:text="#{HTMLToPDF.zoom}" class="form-label"></label>
|
||||
<input type="number" step="0.1" class="form-control" id="zoom" name="zoom" value="1" />
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{HTMLToPDF.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{HTMLToPDF.help}"></p>
|
||||
<p class="mt-3" th:text="#{HTMLToPDF.credit}"></p>
|
||||
</div>
|
||||
<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="mb-3">
|
||||
<h2 th:text="#{HTMLToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/html/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='text/html,application/zip' )}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="zoom" th:text="#{HTMLToPDF.zoom}" class="form-label"></label>
|
||||
<input type="number" step="0.1" class="form-control" id="zoom" name="zoom" value="1" />
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{HTMLToPDF.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{HTMLToPDF.help}"></p>
|
||||
<p class="mt-3" th:text="#{HTMLToPDF.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,88 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title}, header=#{imageToPDF.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title}, header=#{imageToPDF.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{imageToPDF.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/img/pdf}">
|
||||
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="fitOption" th:text="#{imageToPDF.selectLabel}">Fit Options</label>
|
||||
<select class="form-control" id="fitOption" name="fitOption">
|
||||
<option value="fillPage" th:text="#{imageToPDF.fillPage}">Fill Page</option>
|
||||
<option value="fitDocumentToImage" th:text="#{imageToPDF.fitDocumentToImage}">Fit Document to Image</option>
|
||||
<option value="maintainAspectRatio" th:text="#{imageToPDF.maintainAspectRatio}">Maintain Aspect Ratio</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="autoRotate" id="autoRotate">
|
||||
<label class="ms-3" for="autoRotate" th:text=#{imageToPDF.selectText.2}></label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.colorType}"></label>
|
||||
<select class="form-control" id="colorType" name="colorType">
|
||||
<option value="color" th:text="#{pdfToImage.color}"></option>
|
||||
<option value="greyscale" th:text="#{pdfToImage.grey}"></option>
|
||||
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<input type="hidden" id="override" name="override" value="multi">
|
||||
<div class="mb-3">
|
||||
<label th:text=#{imageToPDF.selectText.3}></label>
|
||||
<select class="form-control" id="conversionType" name="conversionType" disabled>
|
||||
<option value="merge" th:text=#{imageToPDF.selectText.4}></option>
|
||||
<option value="convert" th:text=#{imageToPDF.selectText.5} selected></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<br> <br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
|
||||
<script>
|
||||
$('#fileInput-input').on('change', function() {
|
||||
var files = document.getElementById("fileInput-input").files;
|
||||
var conversionType = document.getElementById("conversionType");
|
||||
console.log("files.length=" + files.length)
|
||||
if (files.length > 1) {
|
||||
conversionType.disabled = false;
|
||||
} else {
|
||||
conversionType.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
$('#conversionType').change(function() {
|
||||
var selectedValue = $(this).val();
|
||||
var override = document.getElementById("override");
|
||||
console.log("selectedValue=" + selectedValue)
|
||||
if (selectedValue === 'merge') {
|
||||
override.value = "single";
|
||||
} else if (selectedValue === 'convert') {
|
||||
override.value = "multi";
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<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="#{imageToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/img/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="fitOption" th:text="#{imageToPDF.selectLabel}">Fit Options</label>
|
||||
<select class="form-control" id="fitOption" name="fitOption">
|
||||
<option value="fillPage" th:text="#{imageToPDF.fillPage}">Fill Page</option>
|
||||
<option value="fitDocumentToImage" th:text="#{imageToPDF.fitDocumentToImage}">Fit Document to Image</option>
|
||||
<option value="maintainAspectRatio" th:text="#{imageToPDF.maintainAspectRatio}">Maintain Aspect Ratio</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="autoRotate" id="autoRotate">
|
||||
<label class="ms-3" for="autoRotate" th:text=#{imageToPDF.selectText.2}></label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.colorType}"></label>
|
||||
<select class="form-control" id="colorType" name="colorType">
|
||||
<option value="color" th:text="#{pdfToImage.color}"></option>
|
||||
<option value="greyscale" th:text="#{pdfToImage.grey}"></option>
|
||||
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<input type="hidden" id="override" name="override" value="multi">
|
||||
<div class="mb-3">
|
||||
<label th:text=#{imageToPDF.selectText.3}></label>
|
||||
<select class="form-control" id="conversionType" name="conversionType" disabled>
|
||||
<option value="merge" th:text=#{imageToPDF.selectText.4}></option>
|
||||
<option value="convert" th:text=#{imageToPDF.selectText.5} selected></option>
|
||||
</select>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
|
||||
<script>
|
||||
$('#fileInput-input').on('change', function() {
|
||||
var files = document.getElementById("fileInput-input").files;
|
||||
var conversionType = document.getElementById("conversionType");
|
||||
console.log("files.length=" + files.length)
|
||||
if (files.length > 1) {
|
||||
conversionType.disabled = false;
|
||||
} else {
|
||||
conversionType.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
$('#conversionType').change(function() {
|
||||
var selectedValue = $(this).val();
|
||||
var override = document.getElementById("override");
|
||||
console.log("selectedValue=" + selectedValue)
|
||||
if (selectedValue === 'merge') {
|
||||
override.value = "single";
|
||||
} else if (selectedValue === 'convert') {
|
||||
override.value = "multi";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{MarkdownToPDF.title}, header=#{MarkdownToPDF.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{MarkdownToPDF.title}, header=#{MarkdownToPDF.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{MarkdownToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/markdown/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='text/markdown')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{MarkdownToPDF.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{MarkdownToPDF.help}"></p>
|
||||
<p class="mt-3" th:text="#{MarkdownToPDF.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
<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="#{MarkdownToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/markdown/pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='text/markdown')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{MarkdownToPDF.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{MarkdownToPDF.help}"></p>
|
||||
<p class="mt-3" th:text="#{MarkdownToPDF.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,55 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}"
|
||||
th:lang-direction="#{language.direction}"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToBook.title}, header=#{PDFToBook.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block
|
||||
th:insert="~{fragments/common :: head(title=#{PDFToBook.title}, header=#{PDFToBook.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{PDFToBook.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
th:action="@{api/v1/convert/pdf/book}">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToBook.selectText.1}"></label> <select
|
||||
class="form-control" name="outputFormat">
|
||||
<option value="epub">EPUB</option>
|
||||
<option value="mobi">MOBI</option>
|
||||
<option value="azw3">AZW3</option>
|
||||
<option value="docx">DOCX</option>
|
||||
<option value="rtf">RTF</option>
|
||||
<option value="txt">TXT</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="lit">LIT</option>
|
||||
<option value="fb2">FB2</option>
|
||||
<option value="pdb">PDB</option>
|
||||
<option value="lrf">LRF</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary"
|
||||
th:text="#{PDFToBook.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToBook.help}"></p>
|
||||
<p class="mt-3" th:text="#{PDFToBook.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
<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="#{PDFToBook.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/book}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToBook.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="epub">EPUB</option>
|
||||
<option value="mobi">MOBI</option>
|
||||
<option value="azw3">AZW3</option>
|
||||
<option value="docx">DOCX</option>
|
||||
<option value="rtf">RTF</option>
|
||||
<option value="txt">TXT</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="lit">LIT</option>
|
||||
<option value="fb2">FB2</option>
|
||||
<option value="pdb">PDB</option>
|
||||
<option value="lrf">LRF</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToBook.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToBook.help}"></p>
|
||||
<p class="mt-3" th:text="#{PDFToBook.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,159 +1,143 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToCSV.title}, header=#{PDFToCSV.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToCSV.title}, header=#{PDFToCSV.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
</br></br>
|
||||
<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">
|
||||
<h2 th:text="#{PDFToCSV.header}"></h2>
|
||||
<form id="PDFToCSVForm" th:action="@{api/v1/convert/pdf/csv}" method="post" enctype="multipart/form-data">
|
||||
<input id="pageId" type="hidden" name="pageId" />
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{PDFToCSV.submit}"></button>
|
||||
</form>
|
||||
<p id="instruction-text" style="margin: 0; display: none" th:text="#{PDFToCSV.prompt}"></p>
|
||||
|
||||
<div style="position: relative; display: inline-block;">
|
||||
<div>
|
||||
|
||||
<div style="display:none ;margin: 3px;position: absolute;top: 0;width: 120px;justify-content:space-between;z-index: 10" id="pagination-button-container">
|
||||
<button id='previous-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> < </button>
|
||||
<button id='next-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> > </button>
|
||||
</div>
|
||||
|
||||
<canvas id="crop-pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
|
||||
</div>
|
||||
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
let pdfCanvas = document.getElementById('crop-pdf-canvas');
|
||||
let overlayCanvas = document.getElementById('overlayCanvas');
|
||||
// let paginationBtnContainer = ;
|
||||
|
||||
let context = pdfCanvas.getContext('2d');
|
||||
|
||||
let btn1Object = document.getElementById('previous-page-btn');
|
||||
let btn2Object = document.getElementById('next-page-btn');
|
||||
overlayCanvas.width = pdfCanvas.width;
|
||||
overlayCanvas.height = pdfCanvas.height;
|
||||
|
||||
let fileInput = document.getElementById('fileInput-input');
|
||||
|
||||
let file;
|
||||
|
||||
let pdfDoc = null;
|
||||
let pageId = document.getElementById('pageId');
|
||||
let currentPage = 1;
|
||||
let totalPages = 0;
|
||||
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let rectWidth = 0;
|
||||
let rectHeight = 0;
|
||||
|
||||
btn1Object.addEventListener('click',function (e){
|
||||
|
||||
if (currentPage !== 1) {
|
||||
currentPage = currentPage - 1;
|
||||
pageId.value = currentPage;
|
||||
|
||||
if (file.type === 'application/pdf') {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (ev) {
|
||||
let typedArray = new Uint8Array(reader.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
|
||||
pdfDoc = pdf;
|
||||
totalPages = pdf.numPages;
|
||||
renderPage(currentPage);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
btn2Object.addEventListener('click',function (e){
|
||||
|
||||
if (currentPage !== totalPages){
|
||||
|
||||
currentPage=currentPage+1;
|
||||
pageId.value = currentPage;
|
||||
|
||||
if (file.type === 'application/pdf') {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
let typedArray = new Uint8Array(reader.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
|
||||
pdfDoc = pdf;
|
||||
totalPages = pdf.numPages;
|
||||
renderPage(currentPage);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', function(e) {
|
||||
|
||||
file = e.target.files[0];
|
||||
if (file.type === 'application/pdf') {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
let typedArray = new Uint8Array(reader.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
|
||||
pdfDoc = pdf;
|
||||
totalPages = pdf.numPages;
|
||||
renderPage(currentPage);
|
||||
});
|
||||
pageId.value = currentPage;
|
||||
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
document.getElementById("pagination-button-container").style.display="flex";
|
||||
document.getElementById("instruction-text").style.display="block";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function renderPage(pageNumber) {
|
||||
pdfDoc.getPage(pageNumber).then(function(page) {
|
||||
let viewport = page.getViewport({ scale: 1.0 });
|
||||
pdfCanvas.width = viewport.width;
|
||||
pdfCanvas.height = viewport.height;
|
||||
|
||||
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
|
||||
overlayCanvas.height = viewport.height;
|
||||
|
||||
let renderContext = { canvasContext: context, viewport: viewport };
|
||||
page.render(renderContext);
|
||||
pdfCanvas.classList.add("shadow-canvas");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{PDFToCSV.header}"></h2>
|
||||
<form id="PDFToCSVForm" th:action="@{api/v1/convert/pdf/csv}" method="post" enctype="multipart/form-data">
|
||||
<input id="pageId" type="hidden" name="pageId" />
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{PDFToCSV.submit}"></button>
|
||||
</form>
|
||||
<p id="instruction-text" style="margin: 0; display: none" th:text="#{PDFToCSV.prompt}"></p>
|
||||
|
||||
<div style="position: relative; display: inline-block;">
|
||||
<div>
|
||||
<div style="display:none ;margin: 3px;position: absolute;top: 0;width: 120px;justify-content:space-between;z-index: 10" id="pagination-button-container">
|
||||
<button id='previous-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> < </button>
|
||||
<button id='next-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> > </button>
|
||||
</div>
|
||||
<canvas id="crop-pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
|
||||
</div>
|
||||
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
let pdfCanvas = document.getElementById('crop-pdf-canvas');
|
||||
let overlayCanvas = document.getElementById('overlayCanvas');
|
||||
// let paginationBtnContainer = ;
|
||||
|
||||
let context = pdfCanvas.getContext('2d');
|
||||
|
||||
let btn1Object = document.getElementById('previous-page-btn');
|
||||
let btn2Object = document.getElementById('next-page-btn');
|
||||
overlayCanvas.width = pdfCanvas.width;
|
||||
overlayCanvas.height = pdfCanvas.height;
|
||||
|
||||
let fileInput = document.getElementById('fileInput-input');
|
||||
|
||||
let file;
|
||||
|
||||
let pdfDoc = null;
|
||||
let pageId = document.getElementById('pageId');
|
||||
let currentPage = 1;
|
||||
let totalPages = 0;
|
||||
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let rectWidth = 0;
|
||||
let rectHeight = 0;
|
||||
|
||||
btn1Object.addEventListener('click',function (e){
|
||||
if (currentPage !== 1) {
|
||||
currentPage = currentPage - 1;
|
||||
pageId.value = currentPage;
|
||||
|
||||
if (file.type === 'application/pdf') {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (ev) {
|
||||
let typedArray = new Uint8Array(reader.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
|
||||
pdfDoc = pdf;
|
||||
totalPages = pdf.numPages;
|
||||
renderPage(currentPage);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
btn2Object.addEventListener('click',function (e){
|
||||
if (currentPage !== totalPages){
|
||||
currentPage=currentPage+1;
|
||||
pageId.value = currentPage;
|
||||
|
||||
if (file.type === 'application/pdf') {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
let typedArray = new Uint8Array(reader.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
|
||||
pdfDoc = pdf;
|
||||
totalPages = pdf.numPages;
|
||||
renderPage(currentPage);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', function(e) {
|
||||
file = e.target.files[0];
|
||||
if (file.type === 'application/pdf') {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
let typedArray = new Uint8Array(reader.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js';
|
||||
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
|
||||
pdfDoc = pdf;
|
||||
totalPages = pdf.numPages;
|
||||
renderPage(currentPage);
|
||||
});
|
||||
pageId.value = currentPage;
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
document.getElementById("pagination-button-container").style.display="flex";
|
||||
document.getElementById("instruction-text").style.display="block";
|
||||
}
|
||||
});
|
||||
|
||||
function renderPage(pageNumber) {
|
||||
pdfDoc.getPage(pageNumber).then(function(page) {
|
||||
let viewport = page.getViewport({ scale: 1.0 });
|
||||
pdfCanvas.width = viewport.width;
|
||||
pdfCanvas.height = viewport.height;
|
||||
|
||||
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
|
||||
overlayCanvas.height = viewport.height;
|
||||
|
||||
let renderContext = { canvasContext: context, viewport: viewport };
|
||||
page.render(renderContext);
|
||||
pdfCanvas.classList.add("shadow-canvas");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToHTML.title}, header=#{PDFToHTML.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToHTML.title}, header=#{PDFToHTML.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{PDFToHTML.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/html}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToHTML.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToHTML.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
<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="#{PDFToHTML.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/html}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToHTML.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToHTML.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,61 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title}, header=#{pdfToImage.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title}, header=#{pdfToImage.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pdfToImage.header}"></h2>
|
||||
<p th:text="#{processTimeWarning}"></p>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/img}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.selectText}"></label>
|
||||
<select class="form-control" name="imageFormat">
|
||||
<option value="png">PNG</option>
|
||||
<option value="jpg">JPG</option>
|
||||
<option value="gif">GIF</option>
|
||||
<option value="tiff">TIFF</option>
|
||||
<option value="bmp">BMP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.singleOrMultiple}"></label>
|
||||
<select class="form-control" name="singleOrMultiple">
|
||||
<option value="multiple" th:text="#{pdfToImage.multi}"></option>
|
||||
<option value="single" th:text="#{pdfToImage.single}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.colorType}"></label>
|
||||
<select class="form-control" name="colorType">
|
||||
<option value="color" th:text="#{pdfToImage.color}"></option>
|
||||
<option value="greyscale" th:text="#{pdfToImage.grey}"></option>
|
||||
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="dpi">DPI:</label>
|
||||
<input type="number" name="dpi" class="form-control" id="dpi" min="1" step="1" value="300" required>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<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="#{pdfToImage.header}"></h2>
|
||||
<p th:text="#{processTimeWarning}"></p>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/img}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.selectText}"></label>
|
||||
<select class="form-control" name="imageFormat">
|
||||
<option value="png">PNG</option>
|
||||
<option value="jpg">JPG</option>
|
||||
<option value="gif">GIF</option>
|
||||
<option value="tiff">TIFF</option>
|
||||
<option value="bmp">BMP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.singleOrMultiple}"></label>
|
||||
<select class="form-control" name="singleOrMultiple">
|
||||
<option value="multiple" th:text="#{pdfToImage.multi}"></option>
|
||||
<option value="single" th:text="#{pdfToImage.single}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.colorType}"></label>
|
||||
<select class="form-control" name="colorType">
|
||||
<option value="color" th:text="#{pdfToImage.color}"></option>
|
||||
<option value="greyscale" th:text="#{pdfToImage.grey}"></option>
|
||||
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="dpi">DPI:</label>
|
||||
<input type="number" name="dpi" class="form-control" id="dpi" min="1" step="1" value="300" required>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToPDFA.title}, header=#{pdfToPDFA.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToPDFA.title}, header=#{pdfToPDFA.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pdfToPDFA.header}"></h2>
|
||||
<p>Currently doesn't work for multiple inputs at once</p>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/pdfa}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToPDFA.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{pdfToPDFA.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
<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="#{pdfToPDFA.header}"></h2>
|
||||
<p>Currently doesn't work for multiple inputs at once</p>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/pdfa}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToPDFA.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{pdfToPDFA.credit}"></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,38 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToPresentation.title}, header=#{PDFToPresentation.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToPresentation.title}, header=#{PDFToPresentation.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{PDFToPresentation.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/presentation}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToPresentation.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="ppt">PPT</option>
|
||||
<option value="pptx">PPTX</option>
|
||||
<option value="odp">ODP</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToPresentation.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToPresentation.credit}"></p>
|
||||
</div>
|
||||
<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="#{PDFToPresentation.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/presentation}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToPresentation.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="ppt">PPT</option>
|
||||
<option value="pptx">PPTX</option>
|
||||
<option value="odp">ODP</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToPresentation.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToPresentation.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToText.title}, header=#{PDFToText.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToText.title}, header=#{PDFToText.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{PDFToText.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/text}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToText.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="rtf">RTF</option>
|
||||
<option value="txt">TXT</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToText.credit}"></p>
|
||||
</div>
|
||||
<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="#{PDFToText.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/text}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToText.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="rtf">RTF</option>
|
||||
<option value="txt">TXT</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToText.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToWord.title}, header=#{PDFToWord.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToWord.title}, header=#{PDFToWord.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{PDFToWord.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/word}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToWord.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="doc">Doc</option>
|
||||
<option value="docx">DocX</option>
|
||||
<option value="odt">Odt</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToWord.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToWord.credit}"></p>
|
||||
</div>
|
||||
<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="#{PDFToWord.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/word}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToWord.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="doc">Doc</option>
|
||||
<option value="docx">DocX</option>
|
||||
<option value="odt">Odt</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToWord.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToWord.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToXML.title}, header=#{PDFToXML.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToXML.title}, header=#{PDFToXML.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{PDFToXML.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/xml}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToXML.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToXML.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
<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="#{PDFToXML.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/xml}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToXML.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToXML.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{URLToPDF.title}, header=#{URLToPDF.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{URLToPDF.title}, header=#{URLToPDF.header})}"></th:block>
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{URLToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/url/pdf}">
|
||||
<input type="text" class="form-control" id="urlInput" name="urlInput">
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{URLToPDF.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{URLToPDF.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
<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="#{URLToPDF.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/url/pdf}">
|
||||
<input type="text" class="form-control" id="urlInput" name="urlInput">
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{URLToPDF.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{URLToPDF.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,147 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{crop.title}, header=#{crop.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{crop.title}, header=#{crop.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{crop.header}"></h2>
|
||||
<form id="cropForm" action="/api/v1/general/crop" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<input id="x" type="hidden" name="x">
|
||||
<input id="y" type="hidden" name="y">
|
||||
<input id="width" type="hidden" name="width">
|
||||
<input id="height" type="hidden" name="height">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{crop.submit}"></button>
|
||||
</form>
|
||||
<div style="position: relative; display: inline-block;">
|
||||
<canvas id="crop-pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
|
||||
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
let pdfCanvas = document.getElementById('crop-pdf-canvas');
|
||||
let overlayCanvas = document.getElementById('overlayCanvas');
|
||||
|
||||
let context = pdfCanvas.getContext('2d');
|
||||
let overlayContext = overlayCanvas.getContext('2d');
|
||||
|
||||
overlayCanvas.width = pdfCanvas.width;
|
||||
overlayCanvas.height = pdfCanvas.height;
|
||||
|
||||
let isDrawing = false; // New flag to check if drawing is ongoing
|
||||
|
||||
let cropForm = document.getElementById('cropForm');
|
||||
let fileInput = document.getElementById('fileInput-input');
|
||||
let xInput = document.getElementById('x');
|
||||
let yInput = document.getElementById('y');
|
||||
let widthInput = document.getElementById('width');
|
||||
let heightInput = document.getElementById('height');
|
||||
|
||||
let pdfDoc = null;
|
||||
let currentPage = 1;
|
||||
let totalPages = 0;
|
||||
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let rectWidth = 0;
|
||||
let rectHeight = 0;
|
||||
|
||||
fileInput.addEventListener('change', function(e) {
|
||||
let file = e.target.files[0];
|
||||
if (file.type === 'application/pdf') {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
let typedArray = new Uint8Array(reader.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
|
||||
pdfDoc = pdf;
|
||||
totalPages = pdf.numPages;
|
||||
renderPage(currentPage);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
});
|
||||
|
||||
overlayCanvas.addEventListener('mousedown', function(e) {
|
||||
// Clear previously drawn rectangle on the main canvas
|
||||
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
|
||||
renderPage(currentPage); // Re-render the PDF
|
||||
|
||||
// Clear the overlay canvas to ensure old drawings are removed
|
||||
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
|
||||
|
||||
startX = e.offsetX;
|
||||
startY = e.offsetY;
|
||||
isDrawing = true;
|
||||
});
|
||||
|
||||
overlayCanvas.addEventListener('mousemove', function(e) {
|
||||
if (!isDrawing) return;
|
||||
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear previous rectangle
|
||||
|
||||
rectWidth = e.offsetX - startX;
|
||||
rectHeight = e.offsetY - startY;
|
||||
overlayContext.strokeStyle = 'red';
|
||||
overlayContext.strokeRect(startX, startY, rectWidth, rectHeight);
|
||||
});
|
||||
|
||||
overlayCanvas.addEventListener('mouseup', function(e) {
|
||||
isDrawing = false;
|
||||
|
||||
rectWidth = e.offsetX - startX;
|
||||
rectHeight = e.offsetY - startY;
|
||||
|
||||
let flippedY = pdfCanvas.height - e.offsetY;
|
||||
|
||||
xInput.value = startX;
|
||||
yInput.value = flippedY;
|
||||
widthInput.value = rectWidth;
|
||||
heightInput.value = rectHeight;
|
||||
|
||||
// Draw the final rectangle on the main canvas
|
||||
context.strokeStyle = 'red';
|
||||
context.strokeRect(startX, startY, rectWidth, rectHeight);
|
||||
|
||||
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear the overlay
|
||||
});
|
||||
|
||||
|
||||
function renderPage(pageNumber) {
|
||||
pdfDoc.getPage(pageNumber).then(function(page) {
|
||||
let viewport = page.getViewport({ scale: 1.0 });
|
||||
pdfCanvas.width = viewport.width;
|
||||
pdfCanvas.height = viewport.height;
|
||||
|
||||
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
|
||||
overlayCanvas.height = viewport.height;
|
||||
|
||||
let renderContext = { canvasContext: context, viewport: viewport };
|
||||
page.render(renderContext);
|
||||
pdfCanvas.classList.add("shadow-canvas");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<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">
|
||||
<h2 th:text="#{crop.header}"></h2>
|
||||
<form id="cropForm" action="/api/v1/general/crop" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<input id="x" type="hidden" name="x">
|
||||
<input id="y" type="hidden" name="y">
|
||||
<input id="width" type="hidden" name="width">
|
||||
<input id="height" type="hidden" name="height">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{crop.submit}"></button>
|
||||
</form>
|
||||
<div style="position: relative; display: inline-block;">
|
||||
<canvas id="crop-pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
|
||||
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
let pdfCanvas = document.getElementById('crop-pdf-canvas');
|
||||
let overlayCanvas = document.getElementById('overlayCanvas');
|
||||
|
||||
let context = pdfCanvas.getContext('2d');
|
||||
let overlayContext = overlayCanvas.getContext('2d');
|
||||
|
||||
overlayCanvas.width = pdfCanvas.width;
|
||||
overlayCanvas.height = pdfCanvas.height;
|
||||
|
||||
let isDrawing = false; // New flag to check if drawing is ongoing
|
||||
|
||||
let cropForm = document.getElementById('cropForm');
|
||||
let fileInput = document.getElementById('fileInput-input');
|
||||
let xInput = document.getElementById('x');
|
||||
let yInput = document.getElementById('y');
|
||||
let widthInput = document.getElementById('width');
|
||||
let heightInput = document.getElementById('height');
|
||||
|
||||
let pdfDoc = null;
|
||||
let currentPage = 1;
|
||||
let totalPages = 0;
|
||||
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let rectWidth = 0;
|
||||
let rectHeight = 0;
|
||||
|
||||
fileInput.addEventListener('change', function(e) {
|
||||
let file = e.target.files[0];
|
||||
if (file.type === 'application/pdf') {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
let typedArray = new Uint8Array(reader.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
|
||||
pdfDoc = pdf;
|
||||
totalPages = pdf.numPages;
|
||||
renderPage(currentPage);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
});
|
||||
|
||||
overlayCanvas.addEventListener('mousedown', function(e) {
|
||||
// Clear previously drawn rectangle on the main canvas
|
||||
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
|
||||
renderPage(currentPage); // Re-render the PDF
|
||||
|
||||
// Clear the overlay canvas to ensure old drawings are removed
|
||||
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
|
||||
|
||||
startX = e.offsetX;
|
||||
startY = e.offsetY;
|
||||
isDrawing = true;
|
||||
});
|
||||
|
||||
overlayCanvas.addEventListener('mousemove', function(e) {
|
||||
if (!isDrawing) return;
|
||||
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear previous rectangle
|
||||
|
||||
rectWidth = e.offsetX - startX;
|
||||
rectHeight = e.offsetY - startY;
|
||||
overlayContext.strokeStyle = 'red';
|
||||
overlayContext.strokeRect(startX, startY, rectWidth, rectHeight);
|
||||
});
|
||||
|
||||
overlayCanvas.addEventListener('mouseup', function(e) {
|
||||
isDrawing = false;
|
||||
|
||||
rectWidth = e.offsetX - startX;
|
||||
rectHeight = e.offsetY - startY;
|
||||
|
||||
let flippedY = pdfCanvas.height - e.offsetY;
|
||||
|
||||
xInput.value = startX;
|
||||
yInput.value = flippedY;
|
||||
widthInput.value = rectWidth;
|
||||
heightInput.value = rectHeight;
|
||||
|
||||
// Draw the final rectangle on the main canvas
|
||||
context.strokeStyle = 'red';
|
||||
context.strokeRect(startX, startY, rectWidth, rectHeight);
|
||||
|
||||
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear the overlay
|
||||
});
|
||||
|
||||
function renderPage(pageNumber) {
|
||||
pdfDoc.getPage(pageNumber).then(function(page) {
|
||||
let viewport = page.getViewport({ scale: 1.0 });
|
||||
pdfCanvas.width = viewport.width;
|
||||
pdfCanvas.height = viewport.height;
|
||||
|
||||
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
|
||||
overlayCanvas.height = viewport.height;
|
||||
|
||||
let renderContext = { canvasContext: context, viewport: viewport };
|
||||
page.render(renderContext);
|
||||
pdfCanvas.classList.add("shadow-canvas");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,131 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Error! :(</title>
|
||||
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
|
||||
<style>
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-top: 10%;
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
text-align: center;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
|
||||
.button:hover {
|
||||
background-color: #005b7f;
|
||||
}
|
||||
|
||||
.features-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
|
||||
gap: 25px 30px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
border: 1px solid rgba(0, 0, 0, .125);
|
||||
border-radius: 0.25rem;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.feature-card .card-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#support-section {
|
||||
background-color: #f9f9f9;
|
||||
padding: 4rem;
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#support-section h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#support-section p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#button-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#github-button, #discord-button {
|
||||
display: inline-block;
|
||||
padding: 1rem 2rem;
|
||||
margin: 1rem;
|
||||
background-color: #008CBA;
|
||||
color: #fff;
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-radius: 3rem;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#github-button:hover, #discord-button:hover, #home-button:hover {
|
||||
background-color: #005b7f;
|
||||
}
|
||||
|
||||
#home-button {
|
||||
display: block;
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
margin: 2em auto;
|
||||
background-color: #008CBA;
|
||||
color: white;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
border-radius: 25px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<th:block th:insert="~{fragments/common :: head(title='404 - Page Not Found | Oops, we tripped in the code!')}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<div th:insert="~{fragments/errorBanner.html :: errorBanner}"></div>
|
||||
<div class="container">
|
||||
|
||||
<div id="support-section">
|
||||
<h1 class="display-2">Oops!</h1>
|
||||
<p class="lead" th:if="${param.status == '404'}">We can't seem to find the page you're looking for.</p>
|
||||
<p class="lead" th:unless="${param.status == '404'}">Something went wrong</p>
|
||||
|
||||
<br>
|
||||
<h2>Need help / Found a issue?</h2>
|
||||
<p>If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:</p>
|
||||
<div id="button-group">
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank">Submit a ticket on GitHub</a>
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank">Join our Discord server</a>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<div th:insert="~{fragments/errorBanner.html :: errorBanner}"></div>
|
||||
<div class="container">
|
||||
<div id="support-section">
|
||||
<h1 class="display-2">Oops!</h1>
|
||||
<p class="lead" th:if="${param.status == '404'}">
|
||||
We can't seem to find the page you're looking for.
|
||||
</p>
|
||||
<p class="lead" th:unless="${param.status == '404'}">
|
||||
Something went wrong
|
||||
</p>
|
||||
<br>
|
||||
<h2>Need help / Found a issue?</h2>
|
||||
<p>
|
||||
If you're still having trouble, don't hesitate to reach out to us
|
||||
for help. You can submit a ticket on our GitHub page or contact us
|
||||
through Discord:
|
||||
</p>
|
||||
<div id="button-group">
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank">Submit a ticket on GitHub</a>
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank">Join our Discord server</a>
|
||||
</div>
|
||||
<a href="/" id="home-button">Go back to homepage</a>
|
||||
</div>
|
||||
<a href="/" id="home-button">Go back to homepage</a>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageExtracter.title}, header=#{pageExtracter.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageExtracter.title}, header=#{pageExtracter.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pageExtracter.header}"></h2>
|
||||
<form th:action="@{api/v1/general/rearrange-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<input type="hidden" id="customMode" name="customMode" value="">
|
||||
<div class="mb-3">
|
||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||
<input type="text" class="form-control" id="pageOrder" name="pageNumbers" placeholder="(e.g. 1,2,8 or 4,7,12-16 or 2n-1)" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageExtracter.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
<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="#{pageExtracter.header}"></h2>
|
||||
<form th:action="@{api/v1/general/rearrange-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<input type="hidden" id="customMode" name="customMode" value="">
|
||||
<div class="mb-3">
|
||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||
<input type="text" class="form-control" id="pageOrder" name="pageNumbers" placeholder="(e.g. 1,2,8 or 4,7,12-16 or 2n-1)" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageExtracter.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,12 +1,12 @@
|
||||
<div th:fragment="card" class="feature-card" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)}" data-bs-tags="${tags}">
|
||||
<a th:href="${cardLink}">
|
||||
<div class="d-flex align-items-center"> <!-- Add a flex container to align the SVG and title -->
|
||||
<img th:if="${svgPath}" id="card-icon" class="home-card-icon home-card-icon-colour" th:src="${svgPath}" alt="Icon" width="30" height="30">
|
||||
<h5 class="card-title ms-2" th:text="${cardTitle}"></h5> <!-- Add some margin-left (ms-2) for spacing between SVG and title -->
|
||||
</div>
|
||||
<p class="card-text" th:text="${cardText}"></p>
|
||||
</a>
|
||||
<div class="favorite-icon" onclick="toggleFavorite(this)">
|
||||
<img src="images/star.svg" alt="Favorite">
|
||||
</div>
|
||||
</div>
|
||||
<div th:fragment="card" class="feature-card" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)}" th:data-bs-tags="${tags}">
|
||||
<a th:href="${cardLink}">
|
||||
<div class="d-flex align-items-center"> <!-- Add a flex container to align the SVG and title -->
|
||||
<img th:if="${svgPath}" id="card-icon" class="home-card-icon home-card-icon-colour" th:src="${svgPath}" alt="Icon" width="30" height="30">
|
||||
<h5 class="card-title ms-2" th:text="${cardTitle}"></h5> <!-- Add some margin-left (ms-2) for spacing between SVG and title -->
|
||||
</div>
|
||||
<p class="card-text" th:text="${cardText}"></p>
|
||||
</a>
|
||||
<div class="favorite-icon" onclick="toggleFavorite(this)">
|
||||
<img src="images/star.svg" alt="Favorite">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,137 +1,144 @@
|
||||
<head th:fragment="head">
|
||||
<th:block th:fragment="head">
|
||||
<!-- Title -->
|
||||
<title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
|
||||
|
||||
<!-- Title -->
|
||||
<title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
|
||||
<!-- Metadata -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" th:content="${@appName} + (${header} != null and ${header} != '' ? ' - ' + ${header} : '')"/>
|
||||
<meta name="msapplication-TileColor" content="#2d89ef">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Metadata -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" th:content="${@appName} + (${header} != null and ${header} != '' ? ' - ' + ${header} : '')"/>
|
||||
<meta name="msapplication-TileColor" content="#2d89ef">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Icons -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=2">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=2">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=2">
|
||||
<link rel="manifest" href="/site.webmanifest?v=2">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg?v=2" color="#ca2b2a">
|
||||
<link rel="shortcut icon" href="/favicon.ico?v=2">
|
||||
<meta name="apple-mobile-web-app-title" content="Stirling PDF">
|
||||
<meta name="application-name" content="Stirling PDF">
|
||||
<meta name="msapplication-TileColor" content="#00aba9">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=2">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=2">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=2">
|
||||
<link rel="manifest" href="/site.webmanifest?v=2">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg?v=2" color="#ca2b2a">
|
||||
<link rel="shortcut icon" href="/favicon.ico?v=2">
|
||||
<meta name="apple-mobile-web-app-title" content="Stirling PDF">
|
||||
<meta name="application-name" content="Stirling PDF">
|
||||
<meta name="msapplication-TileColor" content="#00aba9">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<!-- jQuery -->
|
||||
<script src="js/thirdParty/jquery.min.js"></script>
|
||||
<script src="js/thirdParty/jszip.min.js"></script>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="js/thirdParty/jquery.min.js"></script>
|
||||
<script src="js/thirdParty/jszip.min.js"></script>
|
||||
<!-- Bootstrap -->
|
||||
<script src="js/thirdParty/popper.min.js"></script>
|
||||
<script src="js/thirdParty/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<script src="js/thirdParty/popper.min.js"></script>
|
||||
<script src="js/thirdParty/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="css/bootstrap-icons.min.css">
|
||||
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="css/bootstrap-icons.min.css">
|
||||
<!-- PDF.js -->
|
||||
<script src="pdfjs/pdf.js"></script>
|
||||
|
||||
<!-- PDF.js -->
|
||||
<script src="pdfjs/pdf.js"></script>
|
||||
<!-- PDF-Lib -->
|
||||
<script src="js/thirdParty/pdf-lib.min.js"></script>
|
||||
|
||||
<!-- PDF-Lib -->
|
||||
<script src="js/thirdParty/pdf-lib.min.js"></script>
|
||||
<!-- Custom -->
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" th:href="@{css/light-mode.css}" id="light-mode-styles">
|
||||
<link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
|
||||
<link rel="stylesheet" th:href="@{css/rainbow-mode.css}" id="rainbow-mode-styles" disabled="true">
|
||||
<link rel="stylesheet" href="css/tab-container.css">
|
||||
<link rel="stylesheet" href="css/navbar.css">
|
||||
|
||||
<!-- Custom -->
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" th:href="@{css/light-mode.css}" id="light-mode-styles">
|
||||
<link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
|
||||
<link rel="stylesheet" th:href="@{css/rainbow-mode.css}" id="rainbow-mode-styles" disabled="true">
|
||||
<link rel="stylesheet" href="css/tab-container.css">
|
||||
<script src="js/tab-container.js"></script>
|
||||
<link rel="stylesheet" th:href="@{/css/error.css}" th:if="${error}">
|
||||
|
||||
<link rel="stylesheet" href="css/home.css" th:if="${currentPage == 'home'}">
|
||||
<link rel="stylesheet" href="css/account.css" th:if="${currentPage == 'account'}">
|
||||
<link rel="stylesheet" href="css/licenses.css" th:if="${currentPage == 'licenses'}">
|
||||
<link rel="stylesheet" href="css/multi-tool.css" th:if="${currentPage == 'multi-tool'}">
|
||||
<link rel="stylesheet" href="css/rotate-pdf.css" th:if="${currentPage == 'rotate-pdf'}">
|
||||
<link rel="stylesheet" href="css/stamp.css" th:if="${currentPage == 'stamp'}">
|
||||
<link rel="stylesheet" href="css/fileSelect.css">
|
||||
<link rel="stylesheet" href="css/footer.css">
|
||||
|
||||
</head>
|
||||
<!-- Help Modal -->
|
||||
<link rel="stylesheet" href="css/errorBanner.css">
|
||||
|
||||
<script src="js/tab-container.js"></script>
|
||||
<script src="js/darkmode.js"></script>
|
||||
</th:block>
|
||||
|
||||
<th:block th:fragment="game">
|
||||
<dialog id="game-container-wrapper" class="game-container-wrapper" data-bs-modal>
|
||||
<script>
|
||||
console.log("loaded game")
|
||||
$(document).ready(function() {
|
||||
function loadGameScript(callback) {
|
||||
console.log('loadGameScript called');
|
||||
const script = document.createElement('script');
|
||||
script.src = 'js/game.js';
|
||||
script.onload = callback;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
let gameScriptLoaded = false;
|
||||
const gameDialog = document.getElementById('game-container-wrapper')
|
||||
$('#show-game-btn').on('click', function() {
|
||||
console.log('Show game button clicked');
|
||||
if (!gameScriptLoaded) {
|
||||
console.log('Show game button load');
|
||||
loadGameScript(function() {
|
||||
console.log('Game script loaded');
|
||||
window.initializeGame();
|
||||
gameScriptLoaded = true;
|
||||
});
|
||||
} else {
|
||||
window.resetGame();
|
||||
}
|
||||
gameDialog.showModal();
|
||||
});
|
||||
gameDialog.addEventListener("click", e => {
|
||||
const dialogDimensions = gameDialog.getBoundingClientRect()
|
||||
if (
|
||||
e.clientX < dialogDimensions.left ||
|
||||
e.clientX > dialogDimensions.right ||
|
||||
e.clientY < dialogDimensions.top ||
|
||||
e.clientY > dialogDimensions.bottom
|
||||
) {
|
||||
gameDialog.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<div id="game-container">
|
||||
<div id="lives">Lives: 3</div>
|
||||
<div id="score">Score: 0</div>
|
||||
<div id="high-score">High Score: 0</div>
|
||||
<div id="level">Level: 1</div>
|
||||
<img src="favicon.svg" class="player" id="player">
|
||||
</div>
|
||||
<link rel="stylesheet" href="css/game.css">
|
||||
<script>
|
||||
console.log("loaded game");
|
||||
$(document).ready(function() {
|
||||
function loadGameScript(callback) {
|
||||
console.log('loadGameScript called');
|
||||
const script = document.createElement('script');
|
||||
script.src = 'js/game.js';
|
||||
script.onload = callback;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
let gameScriptLoaded = false;
|
||||
const gameDialog = document.getElementById('game-container-wrapper');
|
||||
$('#show-game-btn').on('click', function() {
|
||||
console.log('Show game button clicked');
|
||||
if (!gameScriptLoaded) {
|
||||
console.log('Show game button load');
|
||||
loadGameScript(function() {
|
||||
console.log('Game script loaded');
|
||||
window.initializeGame();
|
||||
gameScriptLoaded = true;
|
||||
});
|
||||
} else {
|
||||
window.resetGame();
|
||||
}
|
||||
gameDialog.showModal();
|
||||
});
|
||||
gameDialog.addEventListener("click", e => {
|
||||
const dialogDimensions = gameDialog.getBoundingClientRect()
|
||||
if (
|
||||
e.clientX < dialogDimensions.left ||
|
||||
e.clientX > dialogDimensions.right ||
|
||||
e.clientY < dialogDimensions.top ||
|
||||
e.clientY > dialogDimensions.bottom
|
||||
) {
|
||||
gameDialog.close();
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<div id="game-container">
|
||||
<div id="lives">Lives: 3</div>
|
||||
<div id="score">Score: 0</div>
|
||||
<div id="high-score">High Score: 0</div>
|
||||
<div id="level">Level: 1</div>
|
||||
<img src="favicon.svg" class="player" id="player">
|
||||
</div>
|
||||
<link rel="stylesheet" href="css/game.css">
|
||||
</dialog>
|
||||
</th:block>
|
||||
|
||||
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: true, notRequired=${notRequired} ?: false">
|
||||
<script th:inline="javascript">
|
||||
const pdfPasswordPrompt =/*[[#{error.pdfPassword}]]*/ '';
|
||||
const multiple = [[${multiple}]] || false;
|
||||
const remoteCall = [[${remoteCall}]] || true;
|
||||
</script>
|
||||
<script src="js/downloader.js"></script>
|
||||
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: true, notRequired=${notRequired} ?: false">
|
||||
<script th:inline="javascript">
|
||||
const pdfPasswordPrompt = /*[[#{error.pdfPassword}]]*/ '';
|
||||
const multiple = [[${multiple}]] || false;
|
||||
const remoteCall = [[${remoteCall}]] || true;
|
||||
</script>
|
||||
<script src="js/downloader.js"></script>
|
||||
|
||||
<div class="custom-file-chooser" th:attr="data-bs-unique-id=${name},
|
||||
data-bs-element-id=${name+'-input'},
|
||||
data-bs-files-selected=#{filesSelected},
|
||||
data-bs-pdf-prompt=#{pdfPrompt}">
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple th:required="${notRequired} ? null : 'required'">
|
||||
</div>
|
||||
<div class="selected-files"></div>
|
||||
</div>
|
||||
<div class="custom-file-chooser" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple th:required="${notRequired} ? null : 'required'">
|
||||
</div>
|
||||
<div class="selected-files"></div>
|
||||
</div>
|
||||
|
||||
<div id="progressBarContainer" style="display: none; position: relative;">
|
||||
<div class="progress" style="height: 1rem;">
|
||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" id="show-game-btn" style="display:none;">Bored waiting?</button>
|
||||
|
||||
|
||||
|
||||
<script src="js/fileInput.js"></script>
|
||||
<link rel="stylesheet" href="css/fileSelect.css">
|
||||
<div id="progressBarContainer" style="display: none; position: relative;">
|
||||
<div class="progress" style="height: 1rem;">
|
||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" id="show-game-btn" style="display:none;">Bored waiting?</button>
|
||||
<script src="js/fileInput.js"></script>
|
||||
</th:block>
|
||||
@@ -1,23 +1,5 @@
|
||||
<th:block th:fragment="errorBanner">
|
||||
|
||||
<style>
|
||||
#github-button,
|
||||
#discord-button {
|
||||
display: inline-block;
|
||||
padding: 1rem 2rem;
|
||||
background-color: #008CBA;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-radius: 3rem;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#github-button:hover,
|
||||
#discord-button:hover {
|
||||
background-color: #005b7f;
|
||||
}
|
||||
</style>
|
||||
<br th:if="${message}">
|
||||
<div id="errorContainer" th:if="${message}" class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<h4 class="alert-heading" th:text="'Error: ' + ${status} + ' ' + ${error}"></h4>
|
||||
@@ -52,10 +34,10 @@
|
||||
|
||||
function copytrace() {
|
||||
var flip = false
|
||||
if(!traceVisible) {
|
||||
toggletrace()
|
||||
flip = true
|
||||
}
|
||||
if(!traceVisible) {
|
||||
toggletrace()
|
||||
flip = true
|
||||
}
|
||||
var traceContent = document.getElementById("traceContent");
|
||||
var range = document.createRange();
|
||||
range.selectNode(traceContent);
|
||||
@@ -64,7 +46,7 @@
|
||||
document.execCommand("copy");
|
||||
window.getSelection().removeAllRanges();
|
||||
if(flip){
|
||||
toggletrace()
|
||||
toggletrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +1,49 @@
|
||||
<th:block th:fragment="errorBannerPerPage">
|
||||
<div id="errorContainer" class="alert alert-danger alert-dismissible fade show" role="alert" style="display: none;">
|
||||
<h4 class="alert-heading">Error</h4>
|
||||
<p></p>
|
||||
<button type="button" class="btn btn-danger" onclick="toggletrace()">Show Stack Trace</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="copytrace()">Copy Stack Trace</button>
|
||||
<button type="button" class="btn btn-info" onclick="showHelp()">Help</button>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close" onclick="dismissError()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<!-- Stack trace section -->
|
||||
<div id="trace" style="max-height: 0; overflow: hidden;">
|
||||
<div style="background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 3px; padding: 10px; margin-top: 5px;">
|
||||
<pre id="traceContent"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="errorContainer" class="alert alert-danger alert-dismissible fade show" role="alert" style="display: none;">
|
||||
<h4 class="alert-heading">Error</h4>
|
||||
<p></p>
|
||||
<button type="button" class="btn btn-danger" onclick="toggletrace()">Show Stack Trace</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="copytrace()">Copy Stack Trace</button>
|
||||
<button type="button" class="btn btn-info" onclick="showHelp()">Help</button>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close" onclick="dismissError()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<!-- Stack trace section -->
|
||||
<div id="trace" style="max-height: 0; overflow: hidden;">
|
||||
<div style="background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 3px; padding: 10px; margin-top: 5px;">
|
||||
<pre id="traceContent"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Help Modal -->
|
||||
<link rel="stylesheet" href="css/errorBanner.css">
|
||||
|
||||
<div class="modal fade" id="helpModal" tabindex="-1" role="dialog" aria-labelledby="helpModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document" id="helpModalDialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="helpModalLabel">Help</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="container">
|
||||
<div id="support-section">
|
||||
<h1 class="display-2">Oops!</h1>
|
||||
<p class="lead">Sorry for the issue!.</p>
|
||||
<br>
|
||||
<h2>Need help / Found an issue?</h2>
|
||||
<p>If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:</p>
|
||||
<div id="button-group">
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank">GitHub - Submit a ticket</a>
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank">Discord - Submit Support post</a>
|
||||
</div>
|
||||
<a href="/" id="home-button">Go to Homepage</a>
|
||||
<a data-bs-dismiss="modal" id="home-button">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/errorBanner.js"></script>
|
||||
<div class="modal fade" id="helpModal" tabindex="-1" role="dialog" aria-labelledby="helpModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document" id="helpModalDialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="helpModalLabel">Help</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container">
|
||||
<div id="support-section">
|
||||
<h1 class="display-2">Oops!</h1>
|
||||
<p class="lead">Sorry for the issue!.</p>
|
||||
<br>
|
||||
<h2>Need help / Found an issue?</h2>
|
||||
<p>If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:</p>
|
||||
<div id="button-group">
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank">GitHub - Submit a ticket</a>
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank">Discord - Submit Support post</a>
|
||||
</div>
|
||||
<a href="/" id="home-button">Go to Homepage</a>
|
||||
<a data-bs-dismiss="modal" id="home-button">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/errorBanner.js"></script>
|
||||
</th:block>
|
||||
|
||||
@@ -1,58 +1,19 @@
|
||||
<div th:fragment="footer">
|
||||
<footer id="footer" class="text-center py-3">
|
||||
<footer th:fragment="footer" id="footer" class="text-center py-3">
|
||||
<div class="footer-center">
|
||||
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF"
|
||||
target="_blank" class="mx-1" title="Visit Github Repository"><img
|
||||
src="images/github.svg"></img></a> <a
|
||||
href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank"
|
||||
class="mx-1" title="See Docker Hub"><img src="images/docker.svg"></img></a>
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" target="_blank" class="mx-1"
|
||||
title="Join Discord Channel"><img src="images/discord.svg"></img></a>
|
||||
<a href="https://github.com/sponsors/Frooodle" target="_blank"
|
||||
class="mx-1" title="Donate"><img
|
||||
src="images/suit-heart-fill.svg"></img></a>
|
||||
|
||||
|
||||
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF" target="_blank" class="mx-1" title="Visit Github Repository">
|
||||
<img src="images/github.svg">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank" class="mx-1" title="See Docker Hub">
|
||||
<img src="images/docker.svg">
|
||||
</a>
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" target="_blank" class="mx-1" title="Join Discord Channel">
|
||||
<img src="images/discord.svg">
|
||||
</a>
|
||||
<a href="https://github.com/sponsors/Frooodle" target="_blank" class="mx-1" title="Donate">
|
||||
<img src="images/suit-heart-fill.svg">
|
||||
</a>
|
||||
<div style="color: grey;" th:if="${@appName} != 'Stirling PDF'" class="footer-powered-by" th:text="#{poweredBy} + ' Stirling PDF'"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<a href="licenses" id="licenses" target="_blank" class="mx-1" title="" th:text="#{licenses.nav}">Licenses</a>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
flex-direction: column; /* Stack children vertically */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-center {
|
||||
display: flex;
|
||||
flex-direction: column; /* Stack items vertically */
|
||||
justify-content: center; /* Center children vertically */
|
||||
align-items: center; /* Center children horizontally */
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.footer-powered-by {
|
||||
margin-top: auto; /* Pushes the text to the bottom */
|
||||
color: grey;
|
||||
text-align: center; /* Centers the text inside the div */
|
||||
width: 100%; /* Full width to center the text properly */
|
||||
}
|
||||
|
||||
.right-link-container {
|
||||
align-self: flex-end; /* Align to the end of the flex container */
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<a href="licenses" id="licenses" target="_blank" class="mx-1" title="" th:text="#{licenses.nav}">Licenses</a>
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -1,107 +1,28 @@
|
||||
<th:block th:fragment="langs">
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="bg_BG"> <img src="images/flags/bg.svg"
|
||||
alt="icon" width="20" height="15"> Български
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="ar_AR"> <img src="images/flags/sa.svg"
|
||||
alt="icon" width="20" height="15"> العربية
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="ca_CA"> <img src="images/flags/es-ct.svg"
|
||||
alt="icon" width="20" height="15"> Català
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="zh_CN"> <img src="images/flags/cn.svg"
|
||||
alt="icon" width="20" height="15"> 简体中文
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="zh_TW"> <img src="images/flags/tw.svg"
|
||||
alt="icon" width="20" height="15"> 正體中文
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="de_DE"> <img src="images/flags/de.svg"
|
||||
alt="icon" width="20" height="15"> Deutsch
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="en_GB"> <img src="images/flags/gb.svg"
|
||||
alt="icon" width="20" height="15"> English (GB)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="en_US"> <img src="images/flags/us.svg"
|
||||
alt="icon" width="20" height="15"> English (US)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="eu_ES"> <img src="images/flags/eu.svg"
|
||||
alt="icon" width="20" height="15"> Euskara
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="es_ES"> <img src="images/flags/es.svg"
|
||||
alt="icon" width="20" height="15"> Español
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="fr_FR"> <img src="images/flags/fr.svg"
|
||||
alt="icon" width="20" height="15"> Français
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="id_ID"> <img src="images/flags/id.svg"
|
||||
alt="icon" width="20" height="15"> Indonesia
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="it_IT"> <img src="images/flags/it.svg"
|
||||
alt="icon" width="20" height="15"> Italiano
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="nl_NL"> <img src="images/flags/nl.svg"
|
||||
alt="icon" width="20" height="15"> Nederlands
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="pl_PL"> <img src="images/flags/pl.svg"
|
||||
alt="icon" width="20" height="15"> Polski
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="pt_BR"> <img src="images/flags/pt_br.svg"
|
||||
alt="icon" width="20" height="15"> Português (BR)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="ro_RO"> <img src="images/flags/ro.svg"
|
||||
alt="icon" width="20" height="15"> Romanian
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="sv_SE"> <img src="images/flags/se.svg"
|
||||
alt="icon" width="20" height="15"> Svenska
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="tr_TR"> <img src="images/flags/tr.svg"
|
||||
alt="icon" width="20" height="15"> Türkçe
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="ru_RU"> <img src="images/flags/ru.svg"
|
||||
alt="icon" width="20" height="15"> Русский
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="ko_KR"> <img src="images/flags/kr.svg"
|
||||
alt="icon" width="20" height="15"> 한국어
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="ja_JP"> <img src="images/flags/jp.svg"
|
||||
alt="icon" width="20" height="15"> 日本語
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="el_GR"> <img src="images/flags/gr.svg"
|
||||
alt="icon" width="20" height="15"> Ελληνικά
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="hu_HU"> <img src="images/flags/hu.svg"
|
||||
alt="icon" width="20" height="15"> Hungarian
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-bs-language-code="hi_IN"> <img src="images/flags/in.svg"
|
||||
alt="icon" width="20" height="15"> हिन्दी
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href=""
|
||||
data-language-code="sr-Latn-RS"> <img src="images/flags/rs.svg"
|
||||
alt="icon" width="20" height="15"> Srpski
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="bg_BG"> <img src="images/flags/bg.svg" alt="icon" width="20" height="15"> Български</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ar_AR"> <img src="images/flags/sa.svg" alt="icon" width="20" height="15"> العربية</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ca_CA"> <img src="images/flags/es-ct.svg" alt="icon" width="20" height="15"> Català</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_CN"> <img src="images/flags/cn.svg" alt="icon" width="20" height="15"> 简体中文</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_TW"> <img src="images/flags/tw.svg" alt="icon" width="20" height="15"> 正體中文</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="de_DE"> <img src="images/flags/de.svg" alt="icon" width="20" height="15"> Deutsch</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_GB"> <img src="images/flags/gb.svg" alt="icon" width="20" height="15"> English (GB)</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_US"> <img src="images/flags/us.svg" alt="icon" width="20" height="15"> English (US)</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="eu_ES"> <img src="images/flags/eu.svg" alt="icon" width="20" height="15"> Euskara</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES"> <img src="images/flags/es.svg" alt="icon" width="20" height="15"> Español</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR"> <img src="images/flags/fr.svg" alt="icon" width="20" height="15"> Français</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="id_ID"> <img src="images/flags/id.svg" alt="icon" width="20" height="15"> Indonesia</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="it_IT"> <img src="images/flags/it.svg" alt="icon" width="20" height="15"> Italiano</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL"> <img src="images/flags/nl.svg" alt="icon" width="20" height="15"> Nederlands</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL"> <img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_BR"> <img src="images/flags/pt_br.svg" alt="icon" width="20" height="15"> Português (BR)</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ro_RO"> <img src="images/flags/ro.svg" alt="icon" width="20" height="15"> Romanian</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sv_SE"> <img src="images/flags/se.svg" alt="icon" width="20" height="15"> Svenska</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="tr_TR"> <img src="images/flags/tr.svg" alt="icon" width="20" height="15"> Türkçe</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ru_RU"> <img src="images/flags/ru.svg" alt="icon" width="20" height="15"> Русский</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ko_KR"> <img src="images/flags/kr.svg" alt="icon" width="20" height="15"> 한국어</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ja_JP"> <img src="images/flags/jp.svg" alt="icon" width="20" height="15"> 日本語</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="el_GR"> <img src="images/flags/gr.svg" alt="icon" width="20" height="15"> Ελληνικά</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hu_HU"> <img src="images/flags/hu.svg" alt="icon" width="20" height="15"> Hungarian</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hi_IN"> <img src="images/flags/in.svg" alt="icon" width="20" height="15"> हिन्दी</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="sr-Latn-RS"> <img src="images/flags/rs.svg" alt="icon" width="20" height="15"> Srpski</a>
|
||||
</th:block>
|
||||
|
||||
@@ -1,315 +1,237 @@
|
||||
<div th:fragment="navbar" class="mx-auto">
|
||||
<script src="js/languageSelection.js"></script>
|
||||
|
||||
<script th:inline="javascript">
|
||||
const currentVersion = /*[[${@appVersion}]]*/ '';
|
||||
const noFavourites = /*[[#{noFavourites}]]*/ '';
|
||||
</script>
|
||||
<script th:src="@{js/githubVersion.js}"></script>
|
||||
|
||||
<link rel="stylesheet" href="css/navbar.css">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container ">
|
||||
|
||||
<a class="navbar-brand" href="#" th:href="@{/}" >
|
||||
<script src="js/languageSelection.js"></script>
|
||||
<script th:inline="javascript">
|
||||
const currentVersion = /*[[${@appVersion}]]*/ '';
|
||||
const noFavourites = /*[[#{noFavourites}]]*/ '';
|
||||
</script>
|
||||
<script th:src="@{js/githubVersion.js}"></script>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container ">
|
||||
<a class="navbar-brand" href="#" th:href="@{/}" style="display: flex;">
|
||||
<img class="main-icon" src="favicon.svg?v=2" alt="icon">
|
||||
<span class="icon-text" th:text="${@navBarText}"></span>
|
||||
</a>
|
||||
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="icon-text" th:text="${@navBarText}"></span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto flex-nowrap">
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{multi-tool}" th:classappend="${currentPage}=='multi-tool' ? 'active' : ''" th:title="#{home.multiTool.desc}">
|
||||
<img class="icon" src="images/tools.svg" alt="icon">
|
||||
<span class="icon-text" th:text="#{home.multiTool.title}"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li th:if="${@enableAlphaFunctionality}" class="nav-item nav-item-separator"></li>
|
||||
<li th:if="${@enableAlphaFunctionality}" class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{pipeline}" th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}">
|
||||
<img class="icon" src="images/pipeline.svg" alt="icon">
|
||||
<span class="icon-text" th:text="#{home.pipeline.title}"></span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='crop' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' OR ${currentPage}=='auto-split-pdf' OR ${currentPage}=='extract-page' OR ${currentPage}=='pdf-to-single-page' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/file-earmark-pdf.svg" alt="icon">
|
||||
<span class="icon-text" th:text="#{navbar.pageOps}"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<!-- Existing menu items -->
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'images/union.svg', 'home.merge.title', 'home.merge.desc', 'merge.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdfs', 'images/layout-split.svg', 'home.split.title', 'home.split.desc', 'split.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'pdf-organizer', 'images/sort-numeric-down.svg', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc', 'pdfOrganiser.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'rotate-pdf', 'images/arrow-clockwise.svg', 'home.rotate.title', 'home.rotate.desc', 'rotate.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'remove-pages', 'images/file-earmark-x.svg', 'home.removePages.title', 'home.removePages.desc', 'removePages.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'multi-page-layout', 'images/page-layout.svg', 'home.pageLayout.title', 'home.pageLayout.desc', 'pageLayout.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'scale-pages', 'images/scale-pages.svg', 'home.scalePages.title', 'home.scalePages.desc', 'scalePages.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'auto-split-pdf', 'images/layout-split.svg', 'home.autoSplitPDF.title', 'home.autoSplitPDF.desc', 'autoSplitPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('adjust-contrast', 'images/adjust-contrast.svg', 'home.adjust-contrast.title', 'home.adjust-contrast.desc', 'adjust-contrast.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('crop', 'images/crop.svg', 'home.crop.title', 'home.crop.desc', 'crop.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-page', 'images/extract.svg', 'home.extractPage.title', 'home.extractPage.desc', 'extractPage.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-single-page', 'images/single-page.svg', 'home.PdfToSinglePage.title', 'home.PdfToSinglePage.desc', 'PdfToSinglePage.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'split-by-size-or-count', 'images/layout-split.svg', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'overlay-pdf', 'images/overlay.svg', 'home.overlay-pdfs.title', 'home.overlay-pdfs.desc', 'overlay-pdfs.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'split-pdf-by-sections', 'images/layout-split.svg', 'home.split-by-sections.title', 'home.split-by-sections.desc', 'split-by-sections.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' OR ${currentPage}=='pdf-to-pdfa' OR ${currentPage}=='file-to-pdf' OR ${currentPage}=='xlsx-to-pdf' OR ${currentPage}=='pdf-to-word' OR ${currentPage}=='pdf-to-presentation' OR ${currentPage}=='pdf-to-text' OR ${currentPage}=='pdf-to-html' OR ${currentPage}=='pdf-to-xml' ? 'active' : ''"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/arrow-left-right.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
|
||||
<span class="icon-text" th:text="#{navbar.convert}"></span>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{multi-tool}" th:classappend="${currentPage}=='multi-tool' ? 'active' : ''" th:title="#{home.multiTool.desc}">
|
||||
<img class="icon" src="images/tools.svg" alt="icon">
|
||||
<span class="icon-text" th:text="#{home.multiTool.title}"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<!-- Existing menu items -->
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('img-to-pdf', 'images/image.svg', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('file-to-pdf', 'images/file.svg', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('html-to-pdf', 'images/html.svg', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('url-to-pdf', 'images/url.svg', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('markdown-to-pdf', 'images/markdown.svg', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('book-to-pdf', 'images/book.svg', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags')}"></div>
|
||||
<hr class="dropdown-divider">
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'images/image.svg', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-word', 'images/file-earmark-word.svg', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-presentation', 'images/file-earmark-ppt.svg', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc', 'PDFToPresentation.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-text', 'images/filetype-txt.svg', 'home.PDFToText.title', 'home.PDFToText.desc', 'PDFToText.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-html', 'images/filetype-html.svg', 'home.PDFToHTML.title', 'home.PDFToHTML.desc', 'PDFToHTML.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-xml', 'images/filetype-xml.svg', 'home.PDFToXML.title', 'home.PDFToXML.desc', 'PDFToXML.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-pdfa', 'images/file-earmark-pdf.svg', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc', 'pdfToPDFA.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-csv', 'images/pdf-csv.svg', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'pdfToPDFA.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-book', 'images/book.svg', 'home.PDFToBook.title', 'home.PDFToBook.desc', 'PDFToBook.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
</li>
|
||||
<li th:if="${@enableAlphaFunctionality}" class="nav-item nav-item-separator"></li>
|
||||
<li th:if="${@enableAlphaFunctionality}" class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{pipeline}" th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}">
|
||||
<img class="icon" src="images/pipeline.svg" alt="icon">
|
||||
<span class="icon-text" th:text="#{home.pipeline.title}"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='crop' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' OR ${currentPage}=='auto-split-pdf' OR ${currentPage}=='extract-page' OR ${currentPage}=='pdf-to-single-page' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/file-earmark-pdf.svg" alt="icon">
|
||||
<span class="icon-text" th:text="#{navbar.pageOps}"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<!-- Existing menu items -->
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'images/union.svg', 'home.merge.title', 'home.merge.desc', 'merge.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdfs', 'images/layout-split.svg', 'home.split.title', 'home.split.desc', 'split.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-organizer', 'images/sort-numeric-down.svg', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc', 'pdfOrganiser.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('rotate-pdf', 'images/arrow-clockwise.svg', 'home.rotate.title', 'home.rotate.desc', 'rotate.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-pages', 'images/file-earmark-x.svg', 'home.removePages.title', 'home.removePages.desc', 'removePages.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('multi-page-layout', 'images/page-layout.svg', 'home.pageLayout.title', 'home.pageLayout.desc', 'pageLayout.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('scale-pages', 'images/scale-pages.svg', 'home.scalePages.title', 'home.scalePages.desc', 'scalePages.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-split-pdf', 'images/layout-split.svg', 'home.autoSplitPDF.title', 'home.autoSplitPDF.desc', 'autoSplitPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('adjust-contrast', 'images/adjust-contrast.svg', 'home.adjust-contrast.title', 'home.adjust-contrast.desc', 'adjust-contrast.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('crop', 'images/crop.svg', 'home.crop.title', 'home.crop.desc', 'crop.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-page', 'images/extract.svg', 'home.extractPage.title', 'home.extractPage.desc', 'extractPage.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-single-page', 'images/single-page.svg', 'home.PdfToSinglePage.title', 'home.PdfToSinglePage.desc', 'PdfToSinglePage.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('split-by-size-or-count', 'images/layout-split.svg', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('overlay-pdf', 'images/overlay.svg', 'home.overlay-pdfs.title', 'home.overlay-pdfs.desc', 'overlay-pdfs.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdf-by-sections', 'images/layout-split.svg', 'home.split-by-sections.title', 'home.split-by-sections.desc', 'split-by-sections.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='add-watermark' OR ${currentPage}=='cert-sign' OR ${currentPage}=='sanitize-pdf' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/shield-check.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{navbar.security}"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-password', 'images/lock.svg', 'home.addPassword.title', 'home.addPassword.desc', 'addPassword.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-password', 'images/unlock.svg', 'home.removePassword.title', 'home.removePassword.desc', 'removePassword.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('change-permissions', 'images/shield-lock.svg', 'home.permissions.title', 'home.permissions.desc', 'permissions.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-redact', 'images/eraser-fill.svg', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' OR ${currentPage}=='pdf-to-pdfa' OR ${currentPage}=='file-to-pdf' OR ${currentPage}=='xlsx-to-pdf' OR ${currentPage}=='pdf-to-word' OR ${currentPage}=='pdf-to-presentation' OR ${currentPage}=='pdf-to-text' OR ${currentPage}=='pdf-to-html' OR ${currentPage}=='pdf-to-xml' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/arrow-left-right.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
|
||||
<span class="icon-text" th:text="#{navbar.convert}"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<!-- Existing menu items -->
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('img-to-pdf', 'images/image.svg', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('file-to-pdf', 'images/file.svg', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('html-to-pdf', 'images/html.svg', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('url-to-pdf', 'images/url.svg', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('markdown-to-pdf', 'images/markdown.svg', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('book-to-pdf', 'images/book.svg', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags')}"></div>
|
||||
<hr class="dropdown-divider">
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'images/image.svg', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-word', 'images/file-earmark-word.svg', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-presentation', 'images/file-earmark-ppt.svg', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc', 'PDFToPresentation.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-text', 'images/filetype-txt.svg', 'home.PDFToText.title', 'home.PDFToText.desc', 'PDFToText.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-html', 'images/filetype-html.svg', 'home.PDFToHTML.title', 'home.PDFToHTML.desc', 'PDFToHTML.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-xml', 'images/filetype-xml.svg', 'home.PDFToXML.title', 'home.PDFToXML.desc', 'PDFToXML.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-pdfa', 'images/file-earmark-pdf.svg', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc', 'pdfToPDFA.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-csv', 'images/pdf-csv.svg', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'pdfToPDFA.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-book', 'images/book.svg', 'home.PDFToBook.title', 'home.PDFToBook.desc', 'PDFToBook.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='sign' OR ${currentPage}=='repair' OR ${currentPage}=='compare' OR ${currentPage}=='show-javascript' OR ${currentPage}=='flatten' OR ${currentPage}=='remove-blanks' OR ${currentPage}=='remove-annotations' OR ${currentPage}=='extract-image-scans' OR ${currentPage}=='change-metadata' OR ${currentPage}=='add-image' OR ${currentPage}=='ocr-pdf' OR ${currentPage}=='change-permissions' OR ${currentPage}=='extract-images' OR ${currentPage}=='compress-pdf' OR ${currentPage}=='add-page-numbers' OR ${currentPage}=='auto-rename' OR ${currentPage}=='get-info-on-pdf' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/card-list.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
|
||||
<span class="icon-text" th:text="#{navbar.other}"></span>
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<!--<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pipeline', 'images/pipeline.svg', 'home.pipeline.title', 'home.pipeline.desc', 'pipeline.tags')}"></div> -->
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('ocr-pdf', 'images/search.svg', 'home.ocr.title', 'home.ocr.desc', 'ocr.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-image', 'images/file-earmark-richtext.svg', 'home.addImage.title', 'home.addImage.desc', 'addImage.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('compress-pdf', 'images/file-zip.svg', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPdfs.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-images', 'images/images.svg', 'home.extractImages.title', 'home.extractImages.desc', 'extractImages.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('change-metadata', 'images/clipboard-data.svg', 'home.changeMetadata.title', 'home.changeMetadata.desc', 'changeMetadata.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-image-scans', 'images/scanner.svg', 'home.ScannerImageSplit.title', 'home.ScannerImageSplit.desc', 'ScannerImageSplit.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sign', 'images/sign.svg', 'home.sign.title', 'home.sign.desc', 'sign.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('flatten', 'images/flatten.svg', 'home.flatten.title', 'home.flatten.desc', 'flatten.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('repair', 'images/wrench.svg', 'home.repair.title', 'home.repair.desc', 'repair.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-blanks', 'images/blank-file.svg', 'home.removeBlanks.title', 'home.removeBlanks.desc', 'removeBlanks.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-annotations', 'images/no-chat.svg', 'home.removeAnnotations.title', 'home.removeAnnotations.desc', 'removeAnnotations.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('compare', 'images/scales.svg', 'home.compare.title', 'home.compare.desc', 'compare.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-page-numbers', 'images/add-page-numbers.svg', 'home.add-page-numbers.title', 'home.add-page-numbers.desc', 'add-page-numbers.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-rename', 'images/fonts.svg', 'home.auto-rename.title', 'home.auto-rename.desc', 'auto-rename.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('get-info-on-pdf', 'images/info.svg', 'home.getPdfInfo.title', 'home.getPdfInfo.desc', 'getPdfInfo.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('show-javascript', 'images/js.svg', 'home.showJS.title', 'home.showJS.desc', 'showJS.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('stamp', 'images/stamp.svg', 'home.AddStampRequest.title', 'home.AddStampRequest.desc', 'AddStampRequest.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='add-watermark' OR ${currentPage}=='cert-sign' OR ${currentPage}=='sanitize-pdf' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/shield-check.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{navbar.security}"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-password', 'images/lock.svg', 'home.addPassword.title', 'home.addPassword.desc', 'addPassword.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-password', 'images/unlock.svg', 'home.removePassword.title', 'home.removePassword.desc', 'removePassword.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('change-permissions', 'images/shield-lock.svg', 'home.permissions.title', 'home.permissions.desc', 'permissions.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-redact', 'images/eraser-fill.svg', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='sign' OR ${currentPage}=='repair' OR ${currentPage}=='compare' OR ${currentPage}=='show-javascript' OR ${currentPage}=='flatten' OR ${currentPage}=='remove-blanks' OR ${currentPage}=='remove-annotations' OR ${currentPage}=='extract-image-scans' OR ${currentPage}=='change-metadata' OR ${currentPage}=='add-image' OR ${currentPage}=='ocr-pdf' OR ${currentPage}=='change-permissions' OR ${currentPage}=='extract-images' OR ${currentPage}=='compress-pdf' OR ${currentPage}=='add-page-numbers' OR ${currentPage}=='auto-rename' OR ${currentPage}=='get-info-on-pdf' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/card-list.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
|
||||
<span class="icon-text" th:text="#{navbar.other}"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<!--<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pipeline', 'images/pipeline.svg', 'home.pipeline.title', 'home.pipeline.desc', 'pipeline.tags')}"></div> -->
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('view-pdf', 'images/book-opened.svg', 'home.viewPdf.title', 'home.viewPdf.desc', 'viewPdf.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('ocr-pdf', 'images/search.svg', 'home.ocr.title', 'home.ocr.desc', 'ocr.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-image', 'images/file-earmark-richtext.svg', 'home.addImage.title', 'home.addImage.desc', 'addImage.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('compress-pdf', 'images/file-zip.svg', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPdfs.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-images', 'images/images.svg', 'home.extractImages.title', 'home.extractImages.desc', 'extractImages.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('change-metadata', 'images/clipboard-data.svg', 'home.changeMetadata.title', 'home.changeMetadata.desc', 'changeMetadata.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-image-scans', 'images/scanner.svg', 'home.ScannerImageSplit.title', 'home.ScannerImageSplit.desc', 'ScannerImageSplit.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sign', 'images/sign.svg', 'home.sign.title', 'home.sign.desc', 'sign.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('flatten', 'images/flatten.svg', 'home.flatten.title', 'home.flatten.desc', 'flatten.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('repair', 'images/wrench.svg', 'home.repair.title', 'home.repair.desc', 'repair.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-blanks', 'images/blank-file.svg', 'home.removeBlanks.title', 'home.removeBlanks.desc', 'removeBlanks.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-annotations', 'images/no-chat.svg', 'home.removeAnnotations.title', 'home.removeAnnotations.desc', 'removeAnnotations.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('compare', 'images/scales.svg', 'home.compare.title', 'home.compare.desc', 'compare.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-page-numbers', 'images/add-page-numbers.svg', 'home.add-page-numbers.title', 'home.add-page-numbers.desc', 'add-page-numbers.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-rename', 'images/fonts.svg', 'home.auto-rename.title', 'home.auto-rename.desc', 'auto-rename.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('get-info-on-pdf', 'images/info.svg', 'home.getPdfInfo.title', 'home.getPdfInfo.desc', 'getPdfInfo.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('show-javascript', 'images/js.svg', 'home.showJS.title', 'home.showJS.desc', 'showJS.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('stamp', 'images/stamp.svg', 'home.AddStampRequest.title', 'home.AddStampRequest.desc', 'AddStampRequest.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav flex-nowrap">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="navbar-icon" src="images/star.svg" alt="icon" width="24" height="24">
|
||||
</a>
|
||||
<div class="dropdown-menu" id="favoritesDropdown" aria-labelledby="navbarDropdown">
|
||||
<!-- Dropdown items will be added here by JavaScript -->
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<script src="js/languageSelection.js"></script>
|
||||
<script src="js/darkmode.js"></script>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="dark-mode-toggle" href="#">
|
||||
<img class="navbar-icon" id="dark-mode-icon" src="moon.svg" alt="icon" />
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-globe2 globe-icon" viewBox="0 0 20 20">
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="languageDropdown">
|
||||
<th:block th:insert="~{fragments/languages :: langs}"></th:block>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-item">
|
||||
<!-- Settings Button -->
|
||||
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
<img class="navbar-icon" src="images/gear.svg" alt="icon" width="24" height="24">
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<!-- Search Button and Search Bar -->
|
||||
<li class="nav-item position-relative">
|
||||
<a href="#" class="nav-link" id="search-icon">
|
||||
<img class="navbar-icon" src="images/search.svg" alt="icon" width="24" height="24">
|
||||
</a>
|
||||
<!-- Search Bar -->
|
||||
<div class="collapse position-absolute" id="navbarSearch">
|
||||
<form class="d-flex p-2 bg-white border search-form" id="searchForm">
|
||||
<input class="form-control search-input" type="search" th:placeholder="#{home.searchBar}" aria-label="Search" id="navbarSearchInput">
|
||||
</form>
|
||||
<!-- Search Results -->
|
||||
<div id="searchResults" class="border p-2 bg-white search-results"></div>
|
||||
</div>
|
||||
</li>
|
||||
<style>
|
||||
#search-icon i {
|
||||
font-size: 24px; /* Adjust this to your desired size */
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
#search-icon:hover i {
|
||||
color: #666; /* Adjust this to your hover color */
|
||||
}
|
||||
|
||||
#navbarSearch {
|
||||
transition: all 0.3s;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
#navbarSearch.show {
|
||||
max-height: 300px; /* Adjust this to your desired max height */
|
||||
}
|
||||
|
||||
.search-input {
|
||||
transition: border 0.3s, box-shadow 0.3s;
|
||||
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
border-color: #666; /* Adjust this to your focus color */
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* Adjust this to your desired shadow */
|
||||
}
|
||||
|
||||
#searchResults {
|
||||
max-width: 300px; /* Adjust to your preferred width */
|
||||
transition: height 0.3s ease; /* Smooth height transition */
|
||||
}
|
||||
|
||||
/* Set a fixed height and styling for each search result item */
|
||||
.search-results a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px; /* space between icon and text */
|
||||
height: 40px; /* Adjust based on your design */
|
||||
overflow: hidden; /* Prevent content from overflowing */
|
||||
white-space: nowrap; /* Prevent text from wrapping to next line */
|
||||
text-overflow: ellipsis; /* Truncate text if it's too long */
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="navbar-icon" src="images/star.svg" alt="icon" width="24" height="24">
|
||||
</a>
|
||||
<div class="dropdown-menu" id="favoritesDropdown" aria-labelledby="navbarDropdown">
|
||||
<!-- Dropdown items will be added here by JavaScript -->
|
||||
</div>
|
||||
</li>
|
||||
<script src="js/languageSelection.js"></script>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="dark-mode-toggle" href="#">
|
||||
<img class="navbar-icon" id="dark-mode-icon" src="moon.svg" alt="icon" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-globe2 globe-icon" viewBox="0 0 20 20">
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="languageDropdown">
|
||||
<th:block th:insert="~{fragments/languages :: langs}"></th:block>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<!-- Settings Button -->
|
||||
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
<img class="navbar-icon" src="images/gear.svg" alt="icon" width="24" height="24">
|
||||
</a>
|
||||
</li>
|
||||
<!-- Search Button and Search Bar -->
|
||||
<li class="nav-item position-relative">
|
||||
<a href="#" class="nav-link" id="search-icon">
|
||||
<img class="navbar-icon" src="images/search.svg" alt="icon" width="24" height="24">
|
||||
</a>
|
||||
<!-- Search Bar -->
|
||||
<div class="collapse position-absolute" id="navbarSearch">
|
||||
<form class="d-flex p-2 bg-white border search-form" id="searchForm">
|
||||
<input class="form-control search-input" type="search" th:placeholder="#{home.searchBar}" aria-label="Search" id="navbarSearchInput">
|
||||
</form>
|
||||
<!-- Search Results -->
|
||||
<div id="searchResults" class="border p-2 bg-white search-results"></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/favourites.js"></script>
|
||||
<script src="js/search.js"></script>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
<script src="js/favourites.js"></script>
|
||||
<script src="js/search.js"></script>
|
||||
</nav>
|
||||
|
||||
<div th:insert="~{fragments/errorBannerPerPage.html :: errorBannerPerPage}"></div>
|
||||
<div class="modal fade" id="settingsModal" tabindex="-1" role="dialog" aria-labelledby="settingsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content dark-card">
|
||||
<th:block th:insert="~{fragments/errorBannerPerPage.html :: errorBannerPerPage}"></th:block>
|
||||
<div class="modal fade" id="settingsModal" tabindex="-1" role="dialog" aria-labelledby="settingsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content dark-card">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="settingsModalLabel" th:text="#{settings.title}"></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<h5 class="modal-title" id="settingsModalLabel" th:text="#{settings.title}"></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<p class="mb-0" th:utext="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
|
||||
<a href="https://github.com/sponsors/Frooodle" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary">Sponsor Stirling-PDF</button>
|
||||
</a>
|
||||
<a href="swagger-ui/index.html" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary">API</button>
|
||||
</a>
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:utext="#{settings.update}"></button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label>
|
||||
<select class="form-control" id="downloadOption">
|
||||
<option value="sameWindow" th:utext="#{settings.downloadOption.1}"></option>
|
||||
<option value="newWindow" th:utext="#{settings.downloadOption.2}"></option>
|
||||
<option value="downloadFile" th:utext="#{settings.downloadOption.3}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="zipThreshold" th:utext="#{settings.zipThreshold}"></label>
|
||||
<input type="range" class="form-range" min="1" max="9" step="1" id="zipThreshold" value="4">
|
||||
<span id="zipThresholdValue" class="ms-2"></span>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="boredWaiting">
|
||||
<label class="form-check-label" for="boredWaiting" th:text="#{bored}"></label>
|
||||
</div>
|
||||
<a th:if="${@loginEnabled}" href="account" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" th:text="#{settings.accountSettings}">Account Settings</button>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<p class="mb-0" th:utext="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
|
||||
<a href="https://github.com/sponsors/Frooodle" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary">Sponsor Stirling-PDF</button>
|
||||
</a>
|
||||
<a href="swagger-ui/index.html" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary">API</button>
|
||||
</a>
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:utext="#{settings.update}"></button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label>
|
||||
<select class="form-control" id="downloadOption">
|
||||
<option value="sameWindow" th:utext="#{settings.downloadOption.1}"></option>
|
||||
<option value="newWindow" th:utext="#{settings.downloadOption.2}"></option>
|
||||
<option value="downloadFile" th:utext="#{settings.downloadOption.3}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="zipThreshold" th:utext="#{settings.zipThreshold}"></label>
|
||||
<input type="range" class="form-range" min="1" max="9" step="1" id="zipThreshold" value="4">
|
||||
<span id="zipThresholdValue" class="ms-2"></span>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="boredWaiting">
|
||||
<label class="form-check-label" for="boredWaiting" th:text="#{bored}"></label>
|
||||
</div>
|
||||
<a th:if="${@loginEnabled}" href="account" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" th:text="#{settings.accountSettings}">Account Settings</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a th:if="${@loginEnabled}" href="logout">
|
||||
<button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button>
|
||||
</a>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
||||
<a th:if="${@loginEnabled}" href="logout">
|
||||
<button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button>
|
||||
</a>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/settings.js"></script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script src="js/settings.js"></script>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div th:fragment="navbarEntry (endpoint, imgSrc, titleKey, descKey, tagKey)" th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}">
|
||||
<a class="dropdown-item" href="#" th:href="@{${endpoint}}" th:classappend="${endpoint.equals(currentPage)} ? 'active' : ''" th:title="#{${descKey}}" th:data-bs-tags="#{${tagKey}}">
|
||||
<img class="icon" th:src="@{${imgSrc}}" alt="icon">
|
||||
<span class="icon-text" th:text="#{${titleKey}}"></span>
|
||||
</a>
|
||||
</div>
|
||||
<th:block th:fragment="navbarEntry(endpoint, imgSrc, titleKey, descKey, tagKey)" th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}">
|
||||
<a class="dropdown-item" href="#" th:href="@{${endpoint}}" th:classappend="${endpoint.equals(currentPage)} ? 'active' : ''" th:title="#{${descKey}}" th:data-bs-tags="#{${tagKey}}">
|
||||
<img class="icon" th:src="@{${imgSrc}}" alt="icon">
|
||||
<span class="icon-text" th:text="#{${titleKey}}"></span>
|
||||
</a>
|
||||
</th:block>
|
||||
|
||||
@@ -1,115 +1,106 @@
|
||||
<!doctype html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
|
||||
</head>
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
|
||||
|
||||
<link rel="stylesheet" href="css/home.css">
|
||||
|
||||
<body>
|
||||
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<!-- Jumbotron -->
|
||||
<div class="bg-light p-5 rounded d-none d-md-block" id="jumbotron">
|
||||
<div class="container">
|
||||
<h1 class="display-4" th:text="${@appName}"></h1>
|
||||
<p class="lead" th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}"></p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1 class="display-4" th:text="${@appName}"></h1>
|
||||
<p class="lead" th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}"></p>
|
||||
</div>
|
||||
</div>
|
||||
<br class="d-md-none">
|
||||
<!-- Features -->
|
||||
<script src="js/homecard.js"></script>
|
||||
|
||||
<div class=" container">
|
||||
<br>
|
||||
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}">
|
||||
<div class="features-container ">
|
||||
<br>
|
||||
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}">
|
||||
<div class="features-container">
|
||||
<th:block th:if="${@enableAlphaFunctionality}">
|
||||
<div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg', tags=#{pipeline.tags})}"></div>
|
||||
</th:block>
|
||||
<div th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', svgPath='images/book-opened.svg', tags=#{viewPdf.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg', tags=#{multiTool.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', svgPath='images/union.svg', tags=#{merge.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='split-pdfs', cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs', svgPath='images/layout-split.svg', tags=#{split.tags})}"></div>
|
||||
|
||||
<th:block th:if="${@enableAlphaFunctionality}">
|
||||
<div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg')}"></div>
|
||||
</th:block>
|
||||
<div th:replace="~{fragments/card :: card(id='rotate-pdf', cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf', svgPath='images/arrow-clockwise.svg', tags=#{rotate.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='crop', cardTitle=#{home.crop.title}, cardText=#{home.crop.desc}, cardLink='crop', svgPath='images/crop.svg', tags=#{crop.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='add-page-numbers', cardTitle=#{home.add-page-numbers.title}, cardText=#{home.add-page-numbers.desc}, cardLink='add-page-numbers', svgPath='images/add-page-numbers.svg', tags=#{add-page-numbers.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', svgPath='images/book-opened.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', svgPath='images/union.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='split-pdfs', cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs', svgPath='images/layout-split.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='adjust-contrast', cardTitle=#{home.adjust-contrast.title}, cardText=#{home.adjust-contrast.desc}, cardLink='adjust-contrast', svgPath='images/adjust-contrast.svg', tags=#{adjust-contrast.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='img-to-pdf', cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf', svgPath='images/image.svg', tags=#{imageToPdf.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-img', cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img', svgPath='images/image.svg', tags=#{pdfToImage.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='rotate-pdf', cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf', svgPath='images/arrow-clockwise.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='crop', cardTitle=#{home.crop.title}, cardText=#{home.crop.desc}, cardLink='crop', svgPath='images/crop.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='add-page-numbers', cardTitle=#{home.add-page-numbers.title}, cardText=#{home.add-page-numbers.desc}, cardLink='add-page-numbers', svgPath='images/add-page-numbers.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-organizer', cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer', svgPath='images/sort-numeric-down.svg', tags=#{pdfOrganiser.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='add-image', cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image', svgPath='images/file-earmark-richtext.svg', tags=#{addImage.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='add-watermark', cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark', svgPath='images/droplet.svg', tags=#{watermark.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='adjust-contrast', cardTitle=#{home.adjust-contrast.title}, cardText=#{home.adjust-contrast.desc}, cardLink='adjust-contrast', svgPath='images/adjust-contrast.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='img-to-pdf', cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf', svgPath='images/image.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-img', cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img', svgPath='images/image.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='file-to-pdf', cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf', svgPath='images/file.svg', tags=#{fileToPDF.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='remove-pages', cardTitle=#{home.removePages.title}, cardText=#{home.removePages.desc}, cardLink='remove-pages', svgPath='images/file-earmark-x.svg', tags=#{removePages.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='add-password', cardTitle=#{home.addPassword.title}, cardText=#{home.addPassword.desc}, cardLink='add-password', svgPath='images/lock.svg', tags=#{addPassword.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-organizer', cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer', svgPath='images/sort-numeric-down.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='add-image', cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image', svgPath='images/file-earmark-richtext.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='add-watermark', cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark', svgPath='images/droplet.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='remove-password', cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password', svgPath='images/unlock.svg', tags=#{removePassword.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='compress-pdf', cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf', svgPath='images/file-zip.svg', tags=#{compressPdfs.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='change-metadata', cardTitle=#{home.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata', svgPath='images/clipboard-data.svg', tags=#{changeMetadata.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='file-to-pdf', cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf', svgPath='images/file.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='remove-pages', cardTitle=#{home.removePages.title}, cardText=#{home.removePages.desc}, cardLink='remove-pages', svgPath='images/file-earmark-x.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='add-password', cardTitle=#{home.addPassword.title}, cardText=#{home.addPassword.desc}, cardLink='add-password', svgPath='images/lock.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='change-permissions', cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions', svgPath='images/shield-lock.svg', tags=#{permissions.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='ocr-pdf', cardTitle=#{home.ocr.title}, cardText=#{home.ocr.desc}, cardLink='ocr-pdf', svgPath='images/search.svg', tags=#{ocr.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='extract-images', cardTitle=#{home.extractImages.title}, cardText=#{home.extractImages.desc}, cardLink='extract-images', svgPath='images/images.svg', tags=#{extractImages.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='remove-password', cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password', svgPath='images/unlock.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='compress-pdf', cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf', svgPath='images/file-zip.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='change-metadata', cardTitle=#{home.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata', svgPath='images/clipboard-data.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-pdfa', cardTitle=#{home.pdfToPDFA.title}, cardText=#{home.pdfToPDFA.desc}, cardLink='pdf-to-pdfa', svgPath='images/file-earmark-pdf.svg', tags=#{pdfToPDFA.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-word', cardTitle=#{home.PDFToWord.title}, cardText=#{home.PDFToWord.desc}, cardLink='pdf-to-word', svgPath='images/file-earmark-word.svg', tags=#{PDFToWord.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-presentation', cardTitle=#{home.PDFToPresentation.title}, cardText=#{home.PDFToPresentation.desc}, cardLink='pdf-to-presentation', svgPath='images/file-earmark-ppt.svg', tags=#{PDFToPresentation.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='change-permissions', cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions', svgPath='images/shield-lock.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='ocr-pdf', cardTitle=#{home.ocr.title}, cardText=#{home.ocr.desc}, cardLink='ocr-pdf', svgPath='images/search.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='extract-images', cardTitle=#{home.extractImages.title}, cardText=#{home.extractImages.desc}, cardLink='extract-images', svgPath='images/images.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-text', cardTitle=#{home.PDFToText.title}, cardText=#{home.PDFToText.desc}, cardLink='pdf-to-text', svgPath='images/filetype-txt.svg', tags=#{PDFToText.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-html', cardTitle=#{home.PDFToHTML.title}, cardText=#{home.PDFToHTML.desc}, cardLink='pdf-to-html', svgPath='images/filetype-html.svg', tags=#{PDFToHTML.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-xml', cardTitle=#{home.PDFToXML.title}, cardText=#{home.PDFToXML.desc}, cardLink='pdf-to-xml', svgPath='images/filetype-xml.svg', tags=#{PDFToXML.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-pdfa', cardTitle=#{home.pdfToPDFA.title}, cardText=#{home.pdfToPDFA.desc}, cardLink='pdf-to-pdfa', svgPath='images/file-earmark-pdf.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-word', cardTitle=#{home.PDFToWord.title}, cardText=#{home.PDFToWord.desc}, cardLink='pdf-to-word', svgPath='images/file-earmark-word.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-presentation', cardTitle=#{home.PDFToPresentation.title}, cardText=#{home.PDFToPresentation.desc}, cardLink='pdf-to-presentation', svgPath='images/file-earmark-ppt.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='extract-image-scans', cardTitle=#{home.ScannerImageSplit.title}, cardText=#{home.ScannerImageSplit.desc}, cardLink='extract-image-scans', svgPath='images/scanner.svg', tags=#{ScannerImageSplit.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='sign', cardTitle=#{home.sign.title}, cardText=#{home.sign.desc}, cardLink='sign', svgPath='images/sign.svg', tags=#{sign.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='flatten', cardTitle=#{home.flatten.title}, cardText=#{home.flatten.desc}, cardLink='flatten', svgPath='images/flatten.svg', tags=#{flatten.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-text', cardTitle=#{home.PDFToText.title}, cardText=#{home.PDFToText.desc}, cardLink='pdf-to-text', svgPath='images/filetype-txt.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-html', cardTitle=#{home.PDFToHTML.title}, cardText=#{home.PDFToHTML.desc}, cardLink='pdf-to-html', svgPath='images/filetype-html.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-xml', cardTitle=#{home.PDFToXML.title}, cardText=#{home.PDFToXML.desc}, cardLink='pdf-to-xml', svgPath='images/filetype-xml.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='repair', cardTitle=#{home.repair.title}, cardText=#{home.repair.desc}, cardLink='repair', svgPath='images/wrench.svg', tags=#{repair.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='remove-blanks', cardTitle=#{home.removeBlanks.title}, cardText=#{home.removeBlanks.desc}, cardLink='remove-blanks', svgPath='images/blank-file.svg', tags=#{removeBlanks.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='remove-annotations', cardTitle=#{home.removeAnnotations.title}, cardText=#{home.removeAnnotations.desc}, cardLink='remove-annotations', svgPath='images/no-chat.svg', tags=#{removeAnnotations.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='compare', cardTitle=#{home.compare.title}, cardText=#{home.compare.desc}, cardLink='compare', svgPath='images/scales.svg', tags=#{compare.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='extract-image-scans', cardTitle=#{home.ScannerImageSplit.title}, cardText=#{home.ScannerImageSplit.desc}, cardLink='extract-image-scans', svgPath='images/scanner.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='sign', cardTitle=#{home.sign.title}, cardText=#{home.sign.desc}, cardLink='sign', svgPath='images/sign.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='flatten', cardTitle=#{home.flatten.title}, cardText=#{home.flatten.desc}, cardLink='flatten', svgPath='images/flatten.svg')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='repair', cardTitle=#{home.repair.title}, cardText=#{home.repair.desc}, cardLink='repair', svgPath='images/wrench.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='remove-blanks', cardTitle=#{home.removeBlanks.title}, cardText=#{home.removeBlanks.desc}, cardLink='remove-blanks', svgPath='images/blank-file.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='remove-annotations', cardTitle=#{home.removeAnnotations.title}, cardText=#{home.removeAnnotations.desc}, cardLink='remove-annotations', svgPath='images/no-chat.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='compare', cardTitle=#{home.compare.title}, cardText=#{home.compare.desc}, cardLink='compare', svgPath='images/scales.svg')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='cert-sign', cardTitle=#{home.certSign.title}, cardText=#{home.certSign.desc}, cardLink='cert-sign', svgPath='images/award.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='multi-page-layout', cardTitle=#{home.pageLayout.title}, cardText=#{home.pageLayout.desc}, cardLink='multi-page-layout', svgPath='images/page-layout.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='scale-pages', cardTitle=#{home.scalePages.title}, cardText=#{home.scalePages.desc}, cardLink='scale-pages', svgPath='images/scale-pages.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='cert-sign', cardTitle=#{home.certSign.title}, cardText=#{home.certSign.desc}, cardLink='cert-sign', svgPath='images/award.svg', tags=#{certSign.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='multi-page-layout', cardTitle=#{home.pageLayout.title}, cardText=#{home.pageLayout.desc}, cardLink='multi-page-layout', svgPath='images/page-layout.svg', tags=#{pageLayout.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='scale-pages', cardTitle=#{home.scalePages.title}, cardText=#{home.scalePages.desc}, cardLink='scale-pages', svgPath='images/scale-pages.svg', tags=#{scalePages.tags})}"></div>
|
||||
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='auto-rename', cardTitle=#{home.auto-rename.title}, cardText=#{home.auto-rename.desc}, cardLink='auto-rename', svgPath='images/fonts.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='auto-split-pdf', cardTitle=#{home.autoSplitPDF.title}, cardText=#{home.autoSplitPDF.desc}, cardLink='auto-split-pdf', svgPath='images/layout-split.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='sanitize-pdf', cardTitle=#{home.sanitizePdf.title}, cardText=#{home.sanitizePdf.desc}, cardLink='sanitize-pdf', svgPath='images/sanitize.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='auto-rename', cardTitle=#{home.auto-rename.title}, cardText=#{home.auto-rename.desc}, cardLink='auto-rename', svgPath='images/fonts.svg', tags=#{auto-rename.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='auto-split-pdf', cardTitle=#{home.autoSplitPDF.title}, cardText=#{home.autoSplitPDF.desc}, cardLink='auto-split-pdf', svgPath='images/layout-split.svg', tags=#{autoSplitPDF.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='sanitize-pdf', cardTitle=#{home.sanitizePdf.title}, cardText=#{home.sanitizePdf.desc}, cardLink='sanitize-pdf', svgPath='images/sanitize.svg', tags=#{sanitizePdf.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='url-to-pdf', cardTitle=#{home.URLToPDF.title}, cardText=#{home.URLToPDF.desc}, cardLink='url-to-pdf', svgPath='images/url.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='html-to-pdf', cardTitle=#{home.HTMLToPDF.title}, cardText=#{home.HTMLToPDF.desc}, cardLink='html-to-pdf', svgPath='images/html.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='markdown-to-pdf', cardTitle=#{home.MarkdownToPDF.title}, cardText=#{home.MarkdownToPDF.desc}, cardLink='markdown-to-pdf', svgPath='images/markdown.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='get-info-on-pdf', cardTitle=#{home.getPdfInfo.title}, cardText=#{home.getPdfInfo.desc}, cardLink='get-info-on-pdf', svgPath='images/info.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='extract-page', cardTitle=#{home.extractPage.title}, cardText=#{home.extractPage.desc}, cardLink='extract-page', svgPath='images/extract.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-single-page', cardTitle=#{home.PdfToSinglePage.title}, cardText=#{home.PdfToSinglePage.desc}, cardLink='pdf-to-single-page', svgPath='images/single-page.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', svgPath='images/js.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='auto-redact', cardTitle=#{home.autoRedact.title}, cardText=#{home.autoRedact.desc}, cardLink='auto-redact', svgPath='images/eraser-fill.svg')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-csv', cardTitle=#{home.tableExtraxt.title}, cardText=#{home.tableExtraxt.desc}, cardLink='pdf-to-csv', svgPath='images/pdf-csv.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='split-by-size-or-count', cardTitle=#{home.autoSizeSplitPDF.title}, cardText=#{home.autoSizeSplitPDF.desc}, cardLink='split-by-size-or-count', svgPath='images/layout-split.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='overlay-pdf', cardTitle=#{home.overlay-pdfs.title}, cardText=#{home.overlay-pdfs.desc}, cardLink='overlay-pdf', svgPath='images/overlay.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='split-pdf-by-sections', cardTitle=#{home.split-by-sections.title}, cardText=#{home.split-by-sections.desc}, cardLink='split-pdf-by-sections', svgPath='images/layout-split.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='book-to-pdf', cardTitle=#{home.BookToPDF.title}, cardText=#{home.BookToPDF.desc}, cardLink='book-to-pdf', svgPath='images/book.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-book', cardTitle=#{home.PDFToBook.title}, cardText=#{home.PDFToBook.desc}, cardLink='pdf-to-book', svgPath='images/book.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='stamp', cardTitle=#{home.AddStampRequest.title}, cardText=#{home.AddStampRequest.desc}, cardLink='stamp', svgPath='images/stamp.svg')}"></div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div> </div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
<div th:replace="~{fragments/card :: card(id='url-to-pdf', cardTitle=#{home.URLToPDF.title}, cardText=#{home.URLToPDF.desc}, cardLink='url-to-pdf', svgPath='images/url.svg', tags=#{URLToPDF.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='html-to-pdf', cardTitle=#{home.HTMLToPDF.title}, cardText=#{home.HTMLToPDF.desc}, cardLink='html-to-pdf', svgPath='images/html.svg', tags=#{HTMLToPDF.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='markdown-to-pdf', cardTitle=#{home.MarkdownToPDF.title}, cardText=#{home.MarkdownToPDF.desc}, cardLink='markdown-to-pdf', svgPath='images/markdown.svg', tags=#{MarkdownToPDF.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='get-info-on-pdf', cardTitle=#{home.getPdfInfo.title}, cardText=#{home.getPdfInfo.desc}, cardLink='get-info-on-pdf', svgPath='images/info.svg', tags=#{getPdfInfo.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='extract-page', cardTitle=#{home.extractPage.title}, cardText=#{home.extractPage.desc}, cardLink='extract-page', svgPath='images/extract.svg', tags=#{extractPage.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-single-page', cardTitle=#{home.PdfToSinglePage.title}, cardText=#{home.PdfToSinglePage.desc}, cardLink='pdf-to-single-page', svgPath='images/single-page.svg', tags=#{PdfToSinglePage.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', svgPath='images/js.svg', tags=#{showJS.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='auto-redact', cardTitle=#{home.autoRedact.title}, cardText=#{home.autoRedact.desc}, cardLink='auto-redact', svgPath='images/eraser-fill.svg', tags=#{autoRedact.tags})}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-csv', cardTitle=#{home.tableExtraxt.title}, cardText=#{home.tableExtraxt.desc}, cardLink='pdf-to-csv', svgPath='images/pdf-csv.svg', tags=#{tableExtraxt.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='split-by-size-or-count', cardTitle=#{home.autoSizeSplitPDF.title}, cardText=#{home.autoSizeSplitPDF.desc}, cardLink='split-by-size-or-count', svgPath='images/layout-split.svg', tags=#{autoSizeSplitPDF.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='overlay-pdf', cardTitle=#{home.overlay-pdfs.title}, cardText=#{home.overlay-pdfs.desc}, cardLink='overlay-pdf', svgPath='images/overlay.svg', tags=#{overlay-pdfs.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='split-pdf-by-sections', cardTitle=#{home.split-by-sections.title}, cardText=#{home.split-by-sections.desc}, cardLink='split-pdf-by-sections', svgPath='images/layout-split.svg', tags=#{split-by-sections.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='book-to-pdf', cardTitle=#{home.BookToPDF.title}, cardText=#{home.BookToPDF.desc}, cardLink='book-to-pdf', svgPath='images/book.svg', tags=#{BookToPDF.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-book', cardTitle=#{home.PDFToBook.title}, cardText=#{home.PDFToBook.desc}, cardLink='pdf-to-book', svgPath='images/book.svg', tags=#{PDFToBook.tags})}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='stamp', cardTitle=#{home.AddStampRequest.title}, cardText=#{home.AddStampRequest.desc}, cardLink='stamp', svgPath='images/stamp.svg', tags=#{AddStampRequest.tags})}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,56 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}"
|
||||
th:lang-direction="#{language.direction}"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{licenses.title}, header=#{licenses.title})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block
|
||||
th:insert="~{fragments/common :: head(title=#{licenses.title}, header=#{licenses.title})}"></th:block>
|
||||
<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">
|
||||
<h2 th:text="#{licenses.header}">3rd Party licenses</h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:text="#{licenses.module}">Module</th>
|
||||
<th th:text="#{licenses.version}">Version</th>
|
||||
<th th:text="#{licenses.license}">License</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="dep : ${dependencies}">
|
||||
<td><a th:href="${dep.moduleUrl}"
|
||||
th:text="${dep.moduleName}"></a></td>
|
||||
<td th:text="${dep.moduleVersion}"></td>
|
||||
<td><a th:href="${dep.moduleLicenseUrl}"
|
||||
th:text="${dep.moduleLicense}"></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<style>
|
||||
td a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
td a:hover, td a:focus {
|
||||
text-decoration: underline;
|
||||
/* Adds underline on hover/focus for clarity */
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{licenses.header}">3rd Party licenses</h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:text="#{licenses.module}">Module</th>
|
||||
<th th:text="#{licenses.version}">Version</th>
|
||||
<th th:text="#{licenses.license}">License</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="dep : ${dependencies}">
|
||||
<td><a th:href="${dep.moduleUrl}"
|
||||
th:text="${dep.moduleName}"></a></td>
|
||||
<td th:text="${dep.moduleVersion}"></td>
|
||||
<td><a th:href="${dep.moduleLicenseUrl}"
|
||||
th:text="${dep.moduleLicense}"></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,313 +1,175 @@
|
||||
<!doctype html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{login.title}, header=#{login.header})}"></th:block>
|
||||
<link rel="stylesheet" href="css/login.css">
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{login.title}, header=#{login.header})}"></th:block>
|
||||
<script src="js/darkmode.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
<body>
|
||||
<div class="your-container-class"></div>
|
||||
<div class="container-flex">
|
||||
<main class="form-signin text-center">
|
||||
<script>
|
||||
function setInputMode(elementId, mode) {
|
||||
var inputElement = document.getElementById(elementId);
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
if (!inputElement) return; // If the element doesn't exist, exit the function
|
||||
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.form-signin .form-floating:focus-within {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.form-signin input[type="text"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.container-flex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
width: 100%; /* Set width to 100% */
|
||||
align-items: center; /* Center its children horizontally */
|
||||
}
|
||||
.footer-bottom {
|
||||
margin-top: auto;
|
||||
}
|
||||
body.light-mode input:-webkit-autofill,
|
||||
body.light-mode input:-webkit-autofill:hover,
|
||||
body.light-mode input:-webkit-autofill:focus,
|
||||
body.light-mode input:-webkit-autofill:active {
|
||||
-webkit-text-fill-color: #212529; /* Dark font color */
|
||||
-webkit-box-shadow: 0 0 0 1000px #f8f9fa inset; /* Light background color */
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
body.dark-mode input:-webkit-autofill,
|
||||
body.dark-mode input:-webkit-autofill:hover,
|
||||
body.dark-mode input:-webkit-autofill:focus,
|
||||
body.dark-mode input:-webkit-autofill:active {
|
||||
-webkit-text-fill-color: #f8f9fa; /* Light font color */
|
||||
-webkit-box-shadow: 0 0 0 1000px #212529 inset; /* Dark background color */
|
||||
}
|
||||
/* Light Mode */
|
||||
body.light-mode .form-floating > input:focus + label {
|
||||
color: #212529 !important; /* Dark text for light background */
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
body.dark-mode .form-floating > input:focus + label {
|
||||
color: #fff !important; /* Light text for dark background */
|
||||
}
|
||||
|
||||
body.light-mode .form-floating > label {
|
||||
color: #212529 !important; /* Dark text for light background */
|
||||
}
|
||||
|
||||
body.dark-mode .form-floating > label {
|
||||
color: #fff !important; /* Light text for dark background */
|
||||
}
|
||||
|
||||
|
||||
/* Removing default styles for ul and li */
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* Positioning the container of these elements to the top right */
|
||||
.your-container-class {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Styling for the dropdown */
|
||||
.dropdown-menu {
|
||||
min-width: 200px; /* or whatever width you prefer */
|
||||
}
|
||||
|
||||
.navbar-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<div class="your-container-class">
|
||||
</div>
|
||||
<div class="container-flex">
|
||||
<main class="form-signin text-center">
|
||||
|
||||
|
||||
<script>
|
||||
function setInputMode(elementId, mode) {
|
||||
var inputElement = document.getElementById(elementId);
|
||||
|
||||
if (!inputElement) return; // If the element doesn't exist, exit the function
|
||||
|
||||
switch (mode) {
|
||||
case "on":
|
||||
inputElement.classList.add("bg-dark", "text-light");
|
||||
inputElement.classList.remove("bg-light", "text-dark");
|
||||
break;
|
||||
case "off":
|
||||
inputElement.classList.add("bg-light", "text-dark");
|
||||
inputElement.classList.remove("bg-dark", "text-light");
|
||||
break;
|
||||
case "rainbow":
|
||||
// Assuming you have defined some classes for rainbow mode
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
document.addEventListener('modeChanged', function(e) {
|
||||
var mode = e.detail;
|
||||
|
||||
setInputMode("username", mode);
|
||||
setInputMode("password", mode);
|
||||
document.body.classList.remove("light-mode", "dark-mode", "rainbow-mode"); // remove all mode classes first
|
||||
|
||||
switch (mode) {
|
||||
case "on":
|
||||
document.body.classList.add("dark-mode");
|
||||
break;
|
||||
case "off":
|
||||
document.body.classList.add("light-mode");
|
||||
break;
|
||||
case "rainbow":
|
||||
document.body.classList.add("rainbow-mode");
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
const defaultLocale = document.documentElement.lang || 'en_GB';
|
||||
const storedLocale = localStorage.getItem('languageCode') || defaultLocale;
|
||||
|
||||
const currentURL = new URL(window.location.href);
|
||||
const urlParams = currentURL.searchParams;
|
||||
const currentLangParam = urlParams.get('lang') || defaultLocale;
|
||||
|
||||
|
||||
if (defaultLocale !== storedLocale && currentLangParam !== storedLocale) {
|
||||
urlParams.set('lang', storedLocale);
|
||||
currentURL.search = urlParams.toString();
|
||||
window.location.href = currentURL.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
const dropdown = document.getElementById('languageDropdown');
|
||||
const dropdownItems = document.querySelectorAll('.lang_dropdown-item');
|
||||
|
||||
let activeItem;
|
||||
for (let i = 0; i < dropdownItems.length; i++) {
|
||||
const item = dropdownItems[i];
|
||||
item.classList.remove('active');
|
||||
if (item.dataset.bsLanguageCode === storedLocale) {
|
||||
item.classList.add('active');
|
||||
activeItem = item;
|
||||
}
|
||||
item.addEventListener('click', handleDropdownItemClick);
|
||||
}
|
||||
|
||||
if (activeItem) {
|
||||
dropdown.innerHTML = activeItem.innerHTML; // This will set the dropdown button's content to the active language's flag and name
|
||||
}
|
||||
|
||||
// Additional functionality that was in your provided code:
|
||||
|
||||
document.querySelectorAll('.nav-item.dropdown').forEach((element) => {
|
||||
const dropdownMenu = element.querySelector(".dropdown-menu");
|
||||
if (dropdownMenu.id !== 'favoritesDropdown' && dropdownMenu.children.length <= 2 && dropdownMenu.querySelectorAll("hr.dropdown-divider").length === dropdownMenu.children.length) {
|
||||
if (element.previousElementSibling && element.previousElementSibling.classList.contains('nav-item') && element.previousElementSibling.classList.contains('nav-item-separator')) {
|
||||
element.previousElementSibling.remove();
|
||||
switch (mode) {
|
||||
case "on":
|
||||
inputElement.classList.add("bg-dark", "text-light");
|
||||
inputElement.classList.remove("bg-light", "text-dark");
|
||||
break;
|
||||
case "off":
|
||||
inputElement.classList.add("bg-light", "text-dark");
|
||||
inputElement.classList.remove("bg-dark", "text-light");
|
||||
break;
|
||||
case "rainbow":
|
||||
// Assuming you have defined some classes for rainbow mode
|
||||
break;
|
||||
}
|
||||
element.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sort languages by alphabet
|
||||
const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter(child => child.matches('a'));
|
||||
list.sort(function(a, b) {
|
||||
var A = a.textContent.toUpperCase();
|
||||
var B = b.textContent.toUpperCase();
|
||||
return (A < B) ? -1 : (A > B) ? 1 : 0;
|
||||
}).forEach(node => document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').appendChild(node));
|
||||
});
|
||||
document.addEventListener('modeChanged', function(e) {
|
||||
var mode = e.detail;
|
||||
|
||||
function handleDropdownItemClick(event) {
|
||||
event.preventDefault();
|
||||
const languageCode = event.currentTarget.dataset.bsLanguageCode;
|
||||
const dropdown = document.getElementById('languageDropdown');
|
||||
setInputMode("username", mode);
|
||||
setInputMode("password", mode);
|
||||
document.body.classList.remove("light-mode", "dark-mode", "rainbow-mode"); // remove all mode classes first
|
||||
|
||||
if (languageCode) {
|
||||
localStorage.setItem('languageCode', languageCode);
|
||||
const currentLang = document.documentElement.getAttribute('lang');
|
||||
if (currentLang !== languageCode) {
|
||||
console.log("currentLang", currentLang)
|
||||
console.log("languageCode", languageCode)
|
||||
const currentUrl = window.location.href;
|
||||
if (currentUrl.indexOf('?lang=') === -1) {
|
||||
window.location.href = currentUrl + '?lang=' + languageCode;
|
||||
} else {
|
||||
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
|
||||
}
|
||||
}
|
||||
dropdown.innerHTML = event.currentTarget.innerHTML; // Update the dropdown button's content
|
||||
} else {
|
||||
console.error("Language code is not set for this item.");
|
||||
}
|
||||
}
|
||||
switch (mode) {
|
||||
case "on":
|
||||
document.body.classList.add("dark-mode");
|
||||
break;
|
||||
case "off":
|
||||
document.body.classList.add("light-mode");
|
||||
break;
|
||||
case "rainbow":
|
||||
document.body.classList.add("rainbow-mode");
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
const defaultLocale = document.documentElement.lang || 'en_GB';
|
||||
const storedLocale = localStorage.getItem('languageCode') || defaultLocale;
|
||||
|
||||
const currentURL = new URL(window.location.href);
|
||||
const urlParams = currentURL.searchParams;
|
||||
const currentLangParam = urlParams.get('lang') || defaultLocale;
|
||||
|
||||
|
||||
if (defaultLocale !== storedLocale && currentLangParam !== storedLocale) {
|
||||
urlParams.set('lang', storedLocale);
|
||||
currentURL.search = urlParams.toString();
|
||||
window.location.href = currentURL.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
</script>
|
||||
<div th:if="${logoutMessage}" class="alert alert-success"
|
||||
th:text="${logoutMessage}"></div>
|
||||
const dropdown = document.getElementById('languageDropdown');
|
||||
const dropdownItems = document.querySelectorAll('.lang_dropdown-item');
|
||||
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'credsUpdated'}" class="alert alert-success">
|
||||
<span th:text="#{changedCredsMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<form th:action="@{login}" method="post">
|
||||
<img class="mb-4" src="favicon.svg?v=2" alt="" width="144" height="144">
|
||||
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
|
||||
<h2 class="h5 mb-3 fw-normal" th:text="#{login.signinTitle}">Please sign in</h2>
|
||||
let activeItem;
|
||||
for (let i = 0; i < dropdownItems.length; i++) {
|
||||
const item = dropdownItems[i];
|
||||
item.classList.remove('active');
|
||||
if (item.dataset.bsLanguageCode === storedLocale) {
|
||||
item.classList.add('active');
|
||||
activeItem = item;
|
||||
}
|
||||
item.addEventListener('click', handleDropdownItemClick);
|
||||
}
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control bg-dark text-light" id="username" name="username"
|
||||
placeholder="admin"> <label for="username" th:text="#{username}">Username</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control bg-dark text-light" id="password" name="password"
|
||||
placeholder="Password"> <label for="password" th:text="#{password}">Password</label>
|
||||
</div>
|
||||
if (activeItem) {
|
||||
dropdown.innerHTML = activeItem.innerHTML; // This will set the dropdown button's content to the active language's flag and name
|
||||
}
|
||||
|
||||
<div class="checkbox mb-3">
|
||||
<label > <input type="checkbox" value="remember-me">
|
||||
<span th:text="#{login.rememberme}"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button class="w-100 btn btn-lg btn-primary" type="submit" th:text="#{login.signin}">Sign
|
||||
in</button>
|
||||
</form>
|
||||
<div class="mt-3"> <!-- Added a margin-top for spacing -->
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="languageDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
English (GB) <!-- Default language placeholder -->
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="languageDropdown">
|
||||
<!-- Here's where the fragment will be included -->
|
||||
<th:block th:replace="~{fragments/languages :: langs}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// Additional functionality that was in your provided code:
|
||||
|
||||
document.querySelectorAll('.nav-item.dropdown').forEach((element) => {
|
||||
const dropdownMenu = element.querySelector(".dropdown-menu");
|
||||
if (dropdownMenu.id !== 'favoritesDropdown' && dropdownMenu.children.length <= 2 && dropdownMenu.querySelectorAll("hr.dropdown-divider").length === dropdownMenu.children.length) {
|
||||
if (element.previousElementSibling && element.previousElementSibling.classList.contains('nav-item') && element.previousElementSibling.classList.contains('nav-item-separator')) {
|
||||
element.previousElementSibling.remove();
|
||||
}
|
||||
element.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Sort languages by alphabet
|
||||
const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter(child => child.matches('a'));
|
||||
list.sort(function(a, b) {
|
||||
var A = a.textContent.toUpperCase();
|
||||
var B = b.textContent.toUpperCase();
|
||||
return (A < B) ? -1 : (A > B) ? 1 : 0;
|
||||
}).forEach(node => document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').appendChild(node));
|
||||
});
|
||||
|
||||
<div class="text-danger text-center">
|
||||
<div th:if="${error == 'badcredentials'}" th:text="#{login.invalid}">Invalid username or
|
||||
password.</div>
|
||||
<div th:if="${error == 'locked'}" th:text="#{login.locked}">Your account has been locked.
|
||||
</div>
|
||||
</div>
|
||||
function handleDropdownItemClick(event) {
|
||||
event.preventDefault();
|
||||
const languageCode = event.currentTarget.dataset.bsLanguageCode;
|
||||
const dropdown = document.getElementById('languageDropdown');
|
||||
|
||||
</main>
|
||||
if (languageCode) {
|
||||
localStorage.setItem('languageCode', languageCode);
|
||||
const currentLang = document.documentElement.getAttribute('lang');
|
||||
if (currentLang !== languageCode) {
|
||||
console.log("currentLang", currentLang)
|
||||
console.log("languageCode", languageCode)
|
||||
const currentUrl = window.location.href;
|
||||
if (currentUrl.indexOf('?lang=') === -1) {
|
||||
window.location.href = currentUrl + '?lang=' + languageCode;
|
||||
} else {
|
||||
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
|
||||
}
|
||||
}
|
||||
dropdown.innerHTML = event.currentTarget.innerHTML; // Update the dropdown button's content
|
||||
} else {
|
||||
console.error("Language code is not set for this item.");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div th:if="${logoutMessage}" class="alert alert-success" th:text="${logoutMessage}"></div>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'credsUpdated'}" class="alert alert-success">
|
||||
<span th:text="#{changedCredsMessage}">Default message if not found</span>
|
||||
</div>
|
||||
<form th:action="@{login}" method="post">
|
||||
<img class="mb-4" src="favicon.svg?v=2" alt="" width="144" height="144">
|
||||
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
|
||||
<h2 class="h5 mb-3 fw-normal" th:text="#{login.signinTitle}">Please sign in</h2>
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control bg-dark text-light" id="username" name="username" placeholder="admin"> <label for="username" th:text="#{username}">Username</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control bg-dark text-light" id="password" name="password" placeholder="Password"> <label for="password" th:text="#{password}">Password</label>
|
||||
</div>
|
||||
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
<div class="checkbox mb-3">
|
||||
<label > <input type="checkbox" value="remember-me">
|
||||
<span th:text="#{login.rememberme}"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button class="w-100 btn btn-lg btn-primary" type="submit" th:text="#{login.signin}">Sign in</button>
|
||||
</form>
|
||||
<div class="mt-3"> <!-- Added a margin-top for spacing -->
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="languageDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
English (GB) <!-- Default language placeholder -->
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="languageDropdown">
|
||||
<!-- Here's where the fragment will be included -->
|
||||
<th:block th:replace="~{fragments/languages :: langs}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-danger text-center">
|
||||
<div th:if="${error == 'badcredentials'}" th:text="#{login.invalid}">Invalid username or password.</div>
|
||||
<div th:if="${error == 'locked'}" th:text="#{login.locked}">Your account has been locked. </div>
|
||||
</div>
|
||||
</main>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,40 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{merge.title}, header=#{merge.header})}"></th:block>
|
||||
<link rel="stylesheet" href="css/merge.css">
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{merge.title}, header=#{merge.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container" id="dropContainer">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{merge.header}"></h2>
|
||||
<form action="api/v1/general/merge-pdfs" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{multiPdfDropPrompt}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true, accept='application/pdf')}"></div>
|
||||
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<ul id="selectedFiles" class="list-group"></ul>
|
||||
</div>
|
||||
<div class="mb-3 text-center">
|
||||
<button type="button" id="sortByNameBtn" class="btn btn-info" th:text="#{merge.sortByName}"></button>
|
||||
<button type="button" id="sortByDateBtn" class="btn btn-info" th:text="#{merge.sortByDate}"></button>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{merge.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
<link rel="stylesheet" href="css/merge.css">
|
||||
<script src="js/merge.js"></script>
|
||||
</div>
|
||||
<div id="content-wrap">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<br /><br />
|
||||
<div class="container" id="dropContainer">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{merge.header}"></h2>
|
||||
<form action="api/v1/general/merge-pdfs" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{multiPdfDropPrompt}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<ul id="selectedFiles" class="list-group"></ul>
|
||||
</div>
|
||||
<div class="mb-3 text-center">
|
||||
<button type="button" id="sortByNameBtn" class="btn btn-info" th:text="#{merge.sortByName}"></button>
|
||||
<button type="button" id="sortByDateBtn" class="btn btn-info" th:text="#{merge.sortByDate}"></button>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{merge.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
<script src="js/merge.js"></script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,141 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{addImage.title}, header=#{addImage.header})}"></th:block>
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{addImage.title}, header=#{addImage.header})}"></th:block>
|
||||
<script src="js/thirdParty/interact.min.js"></script>
|
||||
<link rel="stylesheet" href="css/add-image.css">
|
||||
</head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{addImage.header}"></h2>
|
||||
<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="#{addImage.header}"></h2>
|
||||
|
||||
<!-- pdf selector -->
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
|
||||
<script>
|
||||
let originalFileName = '';
|
||||
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
originalFileName = file.name.replace(/\.[^/.]+$/, "");
|
||||
const pdfData = await file.arrayBuffer();
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||
await DraggableUtils.renderPage(pdfDoc, 0);
|
||||
<!-- pdf selector -->
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
|
||||
<script>
|
||||
let originalFileName = '';
|
||||
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
originalFileName = file.name.replace(/\.[^/.]+$/, "");
|
||||
const pdfData = await file.arrayBuffer();
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||
await DraggableUtils.renderPage(pdfDoc, 0);
|
||||
|
||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||
el.style.cssText = '';
|
||||
})
|
||||
}
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||
el.style.cssText = "display:none !important";
|
||||
})
|
||||
});
|
||||
</script>
|
||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||
el.style.cssText = '';
|
||||
});
|
||||
}
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||
el.style.cssText = "display:none !important";
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="tab-group show-on-file-selected">
|
||||
<div class="tab-container" th:title="#{addImage.upload}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||
<script>
|
||||
const imageUpload = document.querySelector('input[name=image-upload]');
|
||||
imageUpload.addEventListener('change', e => {
|
||||
if(!e.target.files) {
|
||||
return;
|
||||
}
|
||||
for (const imageFile of e.target.files) {
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(imageFile);
|
||||
reader.onloadend = function (e) {
|
||||
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- draggables box -->
|
||||
<div id="box-drag-container" class="show-on-file-selected">
|
||||
<canvas id="pdf-canvas"></canvas>
|
||||
<script src="js/draggable-utils.js"></script>
|
||||
<div class="draggable-buttons-box ignore-rtl">
|
||||
<button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
|
||||
<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" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto">
|
||||
<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" onclick="document.documentElement.getAttribute('lang-direction')==='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>
|
||||
</div>
|
||||
<style>
|
||||
#box-drag-container {
|
||||
position: relative;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#pdf-canvas {
|
||||
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
|
||||
width: 100%;
|
||||
}
|
||||
.draggable-buttons-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
.draggable-buttons-box > button {
|
||||
z-index: 10;
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
}
|
||||
.draggable-canvas {
|
||||
border: 1px solid red;
|
||||
position: absolute;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
|
||||
<!-- download button -->
|
||||
<div class="margin-auto-parent">
|
||||
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center">Download PDF</button>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById("download-pdf").addEventListener('click', async() => {
|
||||
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
||||
const modifiedPdfBytes = await modifiedPdf.save();
|
||||
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = originalFileName + '_addedImage.pdf';
|
||||
link.click();
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="tab-group show-on-file-selected">
|
||||
<div class="tab-container" th:title="#{addImage.upload}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||
<script>
|
||||
const imageUpload = document.querySelector('input[name=image-upload]');
|
||||
imageUpload.addEventListener('change', e => {
|
||||
if(!e.target.files) {
|
||||
return;
|
||||
}
|
||||
for (const imageFile of e.target.files) {
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(imageFile);
|
||||
reader.onloadend = function (e) {
|
||||
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- draggables box -->
|
||||
<div id="box-drag-container" class="show-on-file-selected">
|
||||
<canvas id="pdf-canvas"></canvas>
|
||||
<script src="js/draggable-utils.js"></script>
|
||||
<div class="draggable-buttons-box ignore-rtl">
|
||||
<button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
|
||||
<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" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto">
|
||||
<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" onclick="document.documentElement.getAttribute('lang-direction')==='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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- download button -->
|
||||
<div class="margin-auto-parent">
|
||||
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center">Download PDF</button>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById("download-pdf").addEventListener('click', async() => {
|
||||
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
||||
const modifiedPdfBytes = await modifiedPdf.save();
|
||||
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = originalFileName + '_addedImage.pdf';
|
||||
link.click();
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,154 +1,137 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}"
|
||||
th:lang-direction="#{language.direction}"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://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;
|
||||
border: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
<th:block
|
||||
th:insert="~{fragments/common :: head(title=#{addPageNumbers.title}, header=#{addPageNumbers.header})}"></th:block>
|
||||
.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;
|
||||
}
|
||||
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{addPageNumbers.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
th:action="@{api/v1/misc/add-page-numbers}">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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"
|
||||
required>
|
||||
<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>
|
||||
<style>
|
||||
.a4container {
|
||||
position: relative;
|
||||
width: 50%;
|
||||
aspect-ratio: 0.707;
|
||||
border: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
background-color: white;
|
||||
}
|
||||
#myForm {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.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%);
|
||||
}
|
||||
.selectedPosition {
|
||||
background-color: #0a0;
|
||||
}
|
||||
|
||||
.pageNumber:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
.selectedPosition.selectedHovered {
|
||||
background-color: #006600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
#myForm {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
<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="#{addPageNumbers.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/add-page-numbers}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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" required>
|
||||
<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 for="position" 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" min="1" max="9" value="8" required />
|
||||
<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');
|
||||
|
||||
.selectedPosition {
|
||||
background-color: #0a0;
|
||||
}
|
||||
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
|
||||
});
|
||||
|
||||
.selectedPosition.selectedHovered {
|
||||
background-color: #006600;
|
||||
}
|
||||
</style>
|
||||
cell.addEventListener('mouseenter', function(e) {
|
||||
if(e.target.classList.contains('selectedPosition')) {
|
||||
e.target.classList.add('selectedHovered');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="position" 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" min="1"
|
||||
max="9" value="8" required />
|
||||
<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>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
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>
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}"
|
||||
th:lang-direction="#{language.direction}"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block
|
||||
th:insert="~{fragments/common :: head(title=#{adjustContrast.title}, header=#{adjustContrast.header})}"></th:block>
|
||||
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{adjustContrast.title}, header=#{adjustContrast.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-12">
|
||||
<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">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-3">
|
||||
<div id="sliders-container" style="display:none;">
|
||||
@@ -46,265 +43,265 @@
|
||||
</div>
|
||||
<style>
|
||||
#flex-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#sliders-container {
|
||||
padding: 0 20px; /* Add some padding to separate sliders from canvas */
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#sliders-container {
|
||||
padding: 0 20px; /* Add some padding to separate sliders from canvas */
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script src="pdfjs/pdf.js"></script>
|
||||
<script>
|
||||
var canvas = document.getElementById('contrast-pdf-canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
var originalImageData = null;
|
||||
var allPages = [];
|
||||
var pdfDoc = null;
|
||||
var pdf = null; // This is the current PDF document
|
||||
<script src="pdfjs/pdf.js"></script>
|
||||
<script>
|
||||
var canvas = document.getElementById('contrast-pdf-canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
var originalImageData = null;
|
||||
var allPages = [];
|
||||
var pdfDoc = null;
|
||||
var pdf = null; // This is the current PDF document
|
||||
|
||||
async function renderPDFAndSaveOriginalImageData(file) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = async function() {
|
||||
var data = new Uint8Array(this.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdf = await pdfjsLib.getDocument({data: data}).promise;
|
||||
async function renderPDFAndSaveOriginalImageData(file) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = async function() {
|
||||
var data = new Uint8Array(this.result);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
pdf = await pdfjsLib.getDocument({data: data}).promise;
|
||||
|
||||
// Get the number of pages in the PDF
|
||||
var numPages = pdf.numPages;
|
||||
allPages = Array.from({length: numPages}, (_, i) => i + 1);
|
||||
// Get the number of pages in the PDF
|
||||
var numPages = pdf.numPages;
|
||||
allPages = Array.from({length: numPages}, (_, i) => i + 1);
|
||||
|
||||
// Create a new PDF document
|
||||
pdfDoc = await PDFLib.PDFDocument.create();
|
||||
// Render the first page in the viewer
|
||||
await renderPageAndAdjustImageProperties(1);
|
||||
document.getElementById("sliders-container").style.display = "block";
|
||||
// Create a new PDF document
|
||||
pdfDoc = await PDFLib.PDFDocument.create();
|
||||
// Render the first page in the viewer
|
||||
await renderPageAndAdjustImageProperties(1);
|
||||
document.getElementById("sliders-container").style.display = "block";
|
||||
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
// This function is now async and returns a promise
|
||||
function renderPageAndAdjustImageProperties(pageNum) {
|
||||
return new Promise(async function(resolve, reject) {
|
||||
var page = await pdf.getPage(pageNum);
|
||||
var scale = 1.5;
|
||||
var viewport = page.getViewport({ scale: scale });
|
||||
// This function is now async and returns a promise
|
||||
function renderPageAndAdjustImageProperties(pageNum) {
|
||||
return new Promise(async function(resolve, reject) {
|
||||
var page = await pdf.getPage(pageNum);
|
||||
var scale = 1.5;
|
||||
var viewport = page.getViewport({ scale: scale });
|
||||
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
var renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
};
|
||||
var renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
var renderTask = page.render(renderContext);
|
||||
renderTask.promise.then(function () {
|
||||
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
adjustImageProperties();
|
||||
resolve();
|
||||
});
|
||||
canvas.classList.add("fixed-shadow-canvas");
|
||||
});
|
||||
}
|
||||
var renderTask = page.render(renderContext);
|
||||
renderTask.promise.then(function () {
|
||||
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
adjustImageProperties();
|
||||
resolve();
|
||||
});
|
||||
canvas.classList.add("fixed-shadow-canvas");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function adjustImageProperties() {
|
||||
var contrast = parseFloat(document.getElementById('contrast-slider').value);
|
||||
var brightness = parseFloat(document.getElementById('brightness-slider').value);
|
||||
var saturation = parseFloat(document.getElementById('saturation-slider').value);
|
||||
function adjustImageProperties() {
|
||||
var contrast = parseFloat(document.getElementById('contrast-slider').value);
|
||||
var brightness = parseFloat(document.getElementById('brightness-slider').value);
|
||||
var saturation = parseFloat(document.getElementById('saturation-slider').value);
|
||||
|
||||
contrast /= 100; // normalize to range [0, 2]
|
||||
brightness /= 100; // normalize to range [0, 2]
|
||||
saturation /= 100; // normalize to range [0, 2]
|
||||
contrast /= 100; // normalize to range [0, 2]
|
||||
brightness /= 100; // normalize to range [0, 2]
|
||||
saturation /= 100; // normalize to range [0, 2]
|
||||
|
||||
if (originalImageData) {
|
||||
var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
|
||||
newImageData.data.set(originalImageData.data);
|
||||
if (originalImageData) {
|
||||
var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
|
||||
newImageData.data.set(originalImageData.data);
|
||||
|
||||
for(var i=0; i<newImageData.data.length; i+=4)
|
||||
{
|
||||
var r = newImageData.data[i];
|
||||
var g = newImageData.data[i+1];
|
||||
var b = newImageData.data[i+2];
|
||||
// Adjust contrast
|
||||
r = adjustContrastForPixel(r, contrast);
|
||||
g = adjustContrastForPixel(g, contrast);
|
||||
b = adjustContrastForPixel(b, contrast);
|
||||
// Adjust brightness
|
||||
r = adjustBrightnessForPixel(r, brightness);
|
||||
g = adjustBrightnessForPixel(g, brightness);
|
||||
b = adjustBrightnessForPixel(b, brightness);
|
||||
// Adjust saturation
|
||||
var rgb = adjustSaturationForPixel(r, g, b, saturation);
|
||||
newImageData.data[i] = rgb[0];
|
||||
newImageData.data[i+1] = rgb[1];
|
||||
newImageData.data[i+2] = rgb[2];
|
||||
}
|
||||
for(var i=0; i<newImageData.data.length; i+=4)
|
||||
{
|
||||
var r = newImageData.data[i];
|
||||
var g = newImageData.data[i+1];
|
||||
var b = newImageData.data[i+2];
|
||||
// Adjust contrast
|
||||
r = adjustContrastForPixel(r, contrast);
|
||||
g = adjustContrastForPixel(g, contrast);
|
||||
b = adjustContrastForPixel(b, contrast);
|
||||
// Adjust brightness
|
||||
r = adjustBrightnessForPixel(r, brightness);
|
||||
g = adjustBrightnessForPixel(g, brightness);
|
||||
b = adjustBrightnessForPixel(b, brightness);
|
||||
// Adjust saturation
|
||||
var rgb = adjustSaturationForPixel(r, g, b, saturation);
|
||||
newImageData.data[i] = rgb[0];
|
||||
newImageData.data[i+1] = rgb[1];
|
||||
newImageData.data[i+2] = rgb[2];
|
||||
}
|
||||
|
||||
context.putImageData(newImageData, 0, 0);
|
||||
}
|
||||
}
|
||||
context.putImageData(newImageData, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function rgbToHsl(r, g, b) {
|
||||
r /= 255, g /= 255, b /= 255;
|
||||
function rgbToHsl(r, g, b) {
|
||||
r /= 255, g /= 255, b /= 255;
|
||||
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h, s, l = (max + min) / 2;
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h, s, l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0; // achromatic
|
||||
} else {
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
if (max === min) {
|
||||
h = s = 0; // achromatic
|
||||
} else {
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
|
||||
switch (max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
switch (max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
|
||||
h /= 6;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return [h, s, l];
|
||||
}
|
||||
return [h, s, l];
|
||||
}
|
||||
|
||||
function hslToRgb(h, s, l) {
|
||||
var r, g, b;
|
||||
function hslToRgb(h, s, l) {
|
||||
var r, g, b;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
var hue2rgb = function hue2rgb(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
var hue2rgb = function hue2rgb(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return [r * 255, g * 255, b * 255];
|
||||
}
|
||||
return [r * 255, g * 255, b * 255];
|
||||
}
|
||||
|
||||
function adjustContrastForPixel(pixel, contrast) {
|
||||
// Normalize to range [-0.5, 0.5]
|
||||
var normalized = pixel / 255 - 0.5;
|
||||
function adjustContrastForPixel(pixel, contrast) {
|
||||
// Normalize to range [-0.5, 0.5]
|
||||
var normalized = pixel / 255 - 0.5;
|
||||
|
||||
// Apply contrast
|
||||
normalized *= contrast;
|
||||
// Apply contrast
|
||||
normalized *= contrast;
|
||||
|
||||
// Denormalize back to [0, 255]
|
||||
return (normalized + 0.5) * 255;
|
||||
}
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
// Denormalize back to [0, 255]
|
||||
return (normalized + 0.5) * 255;
|
||||
}
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
function adjustSaturationForPixel(r, g, b, saturation) {
|
||||
var hsl = rgbToHsl(r, g, b);
|
||||
function adjustSaturationForPixel(r, g, b, saturation) {
|
||||
var hsl = rgbToHsl(r, g, b);
|
||||
|
||||
// Adjust saturation
|
||||
hsl[1] = clamp(hsl[1] * saturation, 0, 1);
|
||||
// Adjust saturation
|
||||
hsl[1] = clamp(hsl[1] * saturation, 0, 1);
|
||||
|
||||
// Convert back to RGB
|
||||
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
|
||||
// Convert back to RGB
|
||||
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
|
||||
|
||||
// Return adjusted RGB values
|
||||
return rgb;
|
||||
}
|
||||
// Return adjusted RGB values
|
||||
return rgb;
|
||||
}
|
||||
|
||||
function adjustBrightnessForPixel(pixel, brightness) {
|
||||
return Math.max(0, Math.min(255, pixel * brightness));
|
||||
}
|
||||
function adjustBrightnessForPixel(pixel, brightness) {
|
||||
return Math.max(0, Math.min(255, pixel * brightness));
|
||||
}
|
||||
|
||||
async function downloadPDF() {
|
||||
for (var i = 0; i < allPages.length; i++) {
|
||||
await renderPageAndAdjustImageProperties(allPages[i]);
|
||||
const pngImageBytes = canvas.toDataURL('image/png');
|
||||
const pngImage = await pdfDoc.embedPng(pngImageBytes);
|
||||
const pngDims = pngImage.scale(1);
|
||||
async function downloadPDF() {
|
||||
for (var i = 0; i < allPages.length; i++) {
|
||||
await renderPageAndAdjustImageProperties(allPages[i]);
|
||||
const pngImageBytes = canvas.toDataURL('image/png');
|
||||
const pngImage = await pdfDoc.embedPng(pngImageBytes);
|
||||
const pngDims = pngImage.scale(1);
|
||||
|
||||
// Create a blank page matching the dimensions of the image
|
||||
const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
|
||||
// Create a blank page matching the dimensions of the image
|
||||
const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
|
||||
|
||||
// Draw the PNG image
|
||||
page.drawImage(pngImage, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: pngDims.width,
|
||||
height: pngDims.height
|
||||
});
|
||||
}
|
||||
// Draw the PNG image
|
||||
page.drawImage(pngImage, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: pngDims.width,
|
||||
height: pngDims.height
|
||||
});
|
||||
}
|
||||
|
||||
// Serialize the PDFDocument to bytes (a Uint8Array)
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
// Serialize the PDFDocument to bytes (a Uint8Array)
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
|
||||
// Create a Blob
|
||||
const blob = new Blob([pdfBytes.buffer], {type: "application/pdf"});
|
||||
// Create a Blob
|
||||
const blob = new Blob([pdfBytes.buffer], {type: "application/pdf"});
|
||||
|
||||
// Create download link
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = URL.createObjectURL(blob);
|
||||
downloadLink.download = "download.pdf";
|
||||
downloadLink.click();
|
||||
// Create download link
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = URL.createObjectURL(blob);
|
||||
downloadLink.download = "download.pdf";
|
||||
downloadLink.click();
|
||||
|
||||
// After download, reset the viewer and clear stored data
|
||||
allPages = []; // Clear the pages
|
||||
originalImageData = null; // Clear the image data
|
||||
// After download, reset the viewer and clear stored data
|
||||
allPages = []; // Clear the pages
|
||||
originalImageData = null; // Clear the image data
|
||||
|
||||
// Go back to page 1 and render it in the viewer
|
||||
if (pdf !== null) {
|
||||
renderPageAndAdjustImageProperties(1);
|
||||
}
|
||||
// Go back to page 1 and render it in the viewer
|
||||
if (pdf !== null) {
|
||||
renderPageAndAdjustImageProperties(1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Event listeners
|
||||
document.getElementById('fileInput-input').addEventListener('change', function(e) {
|
||||
if (e.target.files.length > 0) {
|
||||
renderPDFAndSaveOriginalImageData(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
// Event listeners
|
||||
document.getElementById('fileInput-input').addEventListener('change', function(e) {
|
||||
if (e.target.files.length > 0) {
|
||||
renderPDFAndSaveOriginalImageData(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('contrast-slider').addEventListener('input', function() {
|
||||
document.getElementById('contrast-val').textContent = this.value;
|
||||
adjustImageProperties();
|
||||
});
|
||||
document.getElementById('contrast-slider').addEventListener('input', function() {
|
||||
document.getElementById('contrast-val').textContent = this.value;
|
||||
adjustImageProperties();
|
||||
});
|
||||
|
||||
document.getElementById('brightness-slider').addEventListener('input', function() {
|
||||
document.getElementById('brightness-val').textContent = this.value;
|
||||
adjustImageProperties();
|
||||
});
|
||||
document.getElementById('brightness-slider').addEventListener('input', function() {
|
||||
document.getElementById('brightness-val').textContent = this.value;
|
||||
adjustImageProperties();
|
||||
});
|
||||
|
||||
document.getElementById('saturation-slider').addEventListener('input', function() {
|
||||
document.getElementById('saturation-val').textContent = this.value;
|
||||
adjustImageProperties();
|
||||
});
|
||||
document.getElementById('saturation-slider').addEventListener('input', function() {
|
||||
document.getElementById('saturation-val').textContent = this.value;
|
||||
adjustImageProperties();
|
||||
});
|
||||
|
||||
document.getElementById('download-button').addEventListener('click', function() {
|
||||
downloadPDF();
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
document.getElementById('download-button').addEventListener('click', function() {
|
||||
downloadPDF();
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{autoCrop.title}, header=#{autoCrop.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{autoCrop.title}, header=#{autoCrop.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{autoCrop.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/auto-crop}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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 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="#{autoCrop.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/auto-crop}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{auto-rename.title}, header=#{auto-rename.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{auto-rename.title}, header=#{auto-rename.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{auto-rename.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/auto-rename}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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 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="#{auto-rename.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/auto-rename}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,261 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{changeMetadata.title}, header=#{changeMetadata.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{changeMetadata.title}, header=#{changeMetadata.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{changeMetadata.header}"></h2>
|
||||
|
||||
<form method="post" id="form1" enctype="multipart/form-data" th:action="@{api/v1/misc/update-metadata}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
|
||||
|
||||
<div class="mb-3-inline form-check">
|
||||
<input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll">
|
||||
<label class="ms-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3-inline form-check">
|
||||
<input type="checkbox" class="form-check-input" id="customModeCheckbox">
|
||||
<label class="ms-3" 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 class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
|
||||
<script>
|
||||
|
||||
const deleteAllCheckbox = document.querySelector("#deleteAll");
|
||||
let inputs = document.querySelectorAll("input");
|
||||
const customMetadataDiv = document.getElementById('customMetadata');
|
||||
const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
|
||||
|
||||
deleteAllCheckbox.addEventListener("change", function(event) {
|
||||
inputs.forEach(input => {
|
||||
// If it's the deleteAllCheckbox or any file input, skip
|
||||
if (input === deleteAllCheckbox || input.type === "file") {
|
||||
return;
|
||||
}
|
||||
// Disable or enable based on the checkbox state
|
||||
input.disabled = deleteAllCheckbox.checked;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const customModeCheckbox = document.getElementById('customModeCheckbox');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const addMetadataBtn = document.getElementById("addMetadataBtn");
|
||||
const customMetadataFormContainer = document.getElementById("customMetadataEntries");
|
||||
var count = 1;
|
||||
|
||||
|
||||
|
||||
|
||||
const fileInput = document.querySelector("#fileInput-input");
|
||||
const authorInput = document.querySelector("#author");
|
||||
const creationDateInput = document.querySelector("#creationDate");
|
||||
const creatorInput = document.querySelector("#creator");
|
||||
const keywordsInput = document.querySelector("#keywords");
|
||||
const modificationDateInput = document.querySelector("#modificationDate");
|
||||
const producerInput = document.querySelector("#producer");
|
||||
const subjectInput = document.querySelector("#subject");
|
||||
const titleInput = document.querySelector("#title");
|
||||
const trappedInput = document.querySelector("#trapped");
|
||||
|
||||
var lastPDFFileMeta = null;
|
||||
fileInput.addEventListener("change", async function() {
|
||||
|
||||
|
||||
|
||||
while (otherMetadataEntriesDiv.firstChild) {
|
||||
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||
}
|
||||
while (customMetadataFormContainer.firstChild) {
|
||||
customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const file = this.files[0];
|
||||
var url = URL.createObjectURL(file)
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
const pdf = await pdfjsLib.getDocument(url).promise;
|
||||
const pdfMetadata = await pdf.getMetadata();
|
||||
lastPDFFile = pdfMetadata?.info
|
||||
console.log(pdfMetadata);
|
||||
if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
|
||||
customModeCheckbox.disabled = true;
|
||||
customModeCheckbox.checked = false;
|
||||
} else {
|
||||
customModeCheckbox.disabled = false;
|
||||
}
|
||||
authorInput.value = pdfMetadata?.info?.Author;
|
||||
creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
|
||||
creatorInput.value = pdfMetadata?.info?.Creator;
|
||||
keywordsInput.value = pdfMetadata?.info?.Keywords;
|
||||
modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
|
||||
producerInput.value = pdfMetadata?.info?.Producer;
|
||||
subjectInput.value = pdfMetadata?.info?.Subject;
|
||||
titleInput.value = pdfMetadata?.info?.Title;
|
||||
console.log(pdfMetadata?.info);
|
||||
const trappedValue = pdfMetadata?.info?.Trapped;
|
||||
// Get all options in the select element
|
||||
const options = trappedInput.options;
|
||||
// Loop through all options to find the one with a matching value
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (options[i].value === trappedValue) {
|
||||
options[i].selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
addExtra();
|
||||
});
|
||||
|
||||
addMetadataBtn.addEventListener("click", () => {
|
||||
|
||||
const keyInput = document.createElement("input");
|
||||
keyInput.type = "text";
|
||||
keyInput.placeholder = 'Key';
|
||||
keyInput.className = "form-control";
|
||||
keyInput.name = "customKey" + count;
|
||||
|
||||
const valueInput = document.createElement("input");
|
||||
valueInput.type = "text";
|
||||
valueInput.placeholder = 'Value';
|
||||
valueInput.className = "form-control";
|
||||
valueInput.name = "customValue" + count;
|
||||
count = count + 1;
|
||||
|
||||
const formGroup = document.createElement("div");
|
||||
formGroup.className = "mb-3";
|
||||
formGroup.appendChild(keyInput);
|
||||
formGroup.appendChild(valueInput);
|
||||
|
||||
|
||||
customMetadataFormContainer.appendChild(formGroup);
|
||||
});
|
||||
function convertDateFormat(dateTimeString) {
|
||||
if (!dateTimeString || dateTimeString.length < 17) {
|
||||
return dateTimeString;
|
||||
}
|
||||
|
||||
const year = dateTimeString.substring(2, 6);
|
||||
const month = dateTimeString.substring(6, 8);
|
||||
const day = dateTimeString.substring(8, 10);
|
||||
const hour = dateTimeString.substring(10, 12);
|
||||
const minute = dateTimeString.substring(12, 14);
|
||||
const second = dateTimeString.substring(14, 16);
|
||||
|
||||
return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
|
||||
}
|
||||
|
||||
function addExtra() {
|
||||
const event = document.getElementById("customModeCheckbox");
|
||||
|
||||
|
||||
if (event.checked && lastPDFFile.Custom != null) {
|
||||
customMetadataDiv.style.display = 'block';
|
||||
for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
|
||||
if (key === 'Author' || key === 'CreationDate' || key === 'Creator' || key === 'Keywords' || key === 'ModDate' || key === 'Producer' || key === 'Subject' || key === 'Title' || key === 'Trapped') {
|
||||
continue;
|
||||
}
|
||||
const entryDiv = document.createElement('div');
|
||||
entryDiv.className = 'mb-3';
|
||||
|
||||
|
||||
|
||||
entryDiv.innerHTML = `<div class="mb-3"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
|
||||
otherMetadataEntriesDiv.appendChild(entryDiv);
|
||||
}
|
||||
} else {
|
||||
customMetadataDiv.style.display = 'none';
|
||||
while (otherMetadataEntriesDiv.firstChild) {
|
||||
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
customModeCheckbox.addEventListener('change', (event) => {
|
||||
|
||||
addExtra();
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<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="#{changeMetadata.header}"></h2>
|
||||
<form method="post" id="form1" enctype="multipart/form-data" th:action="@{api/v1/misc/update-metadata}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
|
||||
<div class="mb-3-inline form-check">
|
||||
<input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll">
|
||||
<label class="ms-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
|
||||
</div>
|
||||
<div class="mb-3-inline form-check">
|
||||
<input type="checkbox" class="form-check-input" id="customModeCheckbox">
|
||||
<label class="ms-3" 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 class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
|
||||
<script>
|
||||
const deleteAllCheckbox = document.querySelector("#deleteAll");
|
||||
let inputs = document.querySelectorAll("input");
|
||||
const customMetadataDiv = document.getElementById('customMetadata');
|
||||
const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
|
||||
|
||||
deleteAllCheckbox.addEventListener("change", function(event) {
|
||||
inputs.forEach(input => {
|
||||
// If it's the deleteAllCheckbox or any file input, skip
|
||||
if (input === deleteAllCheckbox || input.type === "file") {
|
||||
return;
|
||||
}
|
||||
// Disable or enable based on the checkbox state
|
||||
input.disabled = deleteAllCheckbox.checked;
|
||||
});
|
||||
});
|
||||
|
||||
const customModeCheckbox = document.getElementById('customModeCheckbox');
|
||||
const addMetadataBtn = document.getElementById("addMetadataBtn");
|
||||
const customMetadataFormContainer = document.getElementById("customMetadataEntries");
|
||||
var count = 1;
|
||||
const fileInput = document.querySelector("#fileInput-input");
|
||||
const authorInput = document.querySelector("#author");
|
||||
const creationDateInput = document.querySelector("#creationDate");
|
||||
const creatorInput = document.querySelector("#creator");
|
||||
const keywordsInput = document.querySelector("#keywords");
|
||||
const modificationDateInput = document.querySelector("#modificationDate");
|
||||
const producerInput = document.querySelector("#producer");
|
||||
const subjectInput = document.querySelector("#subject");
|
||||
const titleInput = document.querySelector("#title");
|
||||
const trappedInput = document.querySelector("#trapped");
|
||||
var lastPDFFileMeta = null;
|
||||
fileInput.addEventListener("change", async function() {
|
||||
while (otherMetadataEntriesDiv.firstChild) {
|
||||
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||
}
|
||||
while (customMetadataFormContainer.firstChild) {
|
||||
customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
|
||||
}
|
||||
const file = this.files[0];
|
||||
var url = URL.createObjectURL(file)
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
const pdf = await pdfjsLib.getDocument(url).promise;
|
||||
const pdfMetadata = await pdf.getMetadata();
|
||||
lastPDFFile = pdfMetadata?.info
|
||||
console.log(pdfMetadata);
|
||||
if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
|
||||
customModeCheckbox.disabled = true;
|
||||
customModeCheckbox.checked = false;
|
||||
} else {
|
||||
customModeCheckbox.disabled = false;
|
||||
}
|
||||
authorInput.value = pdfMetadata?.info?.Author;
|
||||
creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
|
||||
creatorInput.value = pdfMetadata?.info?.Creator;
|
||||
keywordsInput.value = pdfMetadata?.info?.Keywords;
|
||||
modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
|
||||
producerInput.value = pdfMetadata?.info?.Producer;
|
||||
subjectInput.value = pdfMetadata?.info?.Subject;
|
||||
titleInput.value = pdfMetadata?.info?.Title;
|
||||
console.log(pdfMetadata?.info);
|
||||
const trappedValue = pdfMetadata?.info?.Trapped;
|
||||
// Get all options in the select element
|
||||
const options = trappedInput.options;
|
||||
// Loop through all options to find the one with a matching value
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (options[i].value === trappedValue) {
|
||||
options[i].selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
addExtra();
|
||||
});
|
||||
|
||||
addMetadataBtn.addEventListener("click", () => {
|
||||
const keyInput = document.createElement("input");
|
||||
keyInput.type = "text";
|
||||
keyInput.placeholder = 'Key';
|
||||
keyInput.className = "form-control";
|
||||
keyInput.name = "customKey" + count;
|
||||
|
||||
const valueInput = document.createElement("input");
|
||||
valueInput.type = "text";
|
||||
valueInput.placeholder = 'Value';
|
||||
valueInput.className = "form-control";
|
||||
valueInput.name = "customValue" + count;
|
||||
count = count + 1;
|
||||
|
||||
const formGroup = document.createElement("div");
|
||||
formGroup.className = "mb-3";
|
||||
formGroup.appendChild(keyInput);
|
||||
formGroup.appendChild(valueInput);
|
||||
|
||||
customMetadataFormContainer.appendChild(formGroup);
|
||||
});
|
||||
function convertDateFormat(dateTimeString) {
|
||||
if (!dateTimeString || dateTimeString.length < 17) {
|
||||
return dateTimeString;
|
||||
}
|
||||
|
||||
const year = dateTimeString.substring(2, 6);
|
||||
const month = dateTimeString.substring(6, 8);
|
||||
const day = dateTimeString.substring(8, 10);
|
||||
const hour = dateTimeString.substring(10, 12);
|
||||
const minute = dateTimeString.substring(12, 14);
|
||||
const second = dateTimeString.substring(14, 16);
|
||||
|
||||
return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
|
||||
}
|
||||
|
||||
function addExtra() {
|
||||
const event = document.getElementById("customModeCheckbox");
|
||||
if (event.checked && lastPDFFile.Custom != null) {
|
||||
customMetadataDiv.style.display = 'block';
|
||||
for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
|
||||
if (key === 'Author' || key === 'CreationDate' || key === 'Creator' || key === 'Keywords' || key === 'ModDate' || key === 'Producer' || key === 'Subject' || key === 'Title' || key === 'Trapped') {
|
||||
continue;
|
||||
}
|
||||
const entryDiv = document.createElement('div');
|
||||
entryDiv.className = 'mb-3';
|
||||
entryDiv.innerHTML = `<div class="mb-3"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
|
||||
otherMetadataEntriesDiv.appendChild(entryDiv);
|
||||
}
|
||||
} else {
|
||||
customMetadataDiv.style.display = 'none';
|
||||
while (otherMetadataEntriesDiv.firstChild) {
|
||||
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customModeCheckbox.addEventListener('change', (event) => {
|
||||
addExtra();
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,190 +1,187 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://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;
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 400px);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{compare.title}, header=#{compare.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-9">
|
||||
<h2 th:text="#{compare.header}"></h2>
|
||||
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput2', multiple=false, accept='application/pdf', remoteCall='false')}"></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>
|
||||
<style>
|
||||
.result-column {
|
||||
border: 1px solid #ccc;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 400px);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// 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];
|
||||
|
||||
if (!file1 || !file2) {
|
||||
console.error("Please select two PDF files to compare");
|
||||
return;
|
||||
}
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
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("One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.");
|
||||
return;
|
||||
}
|
||||
const diff = (text1, text2) => {
|
||||
const words1 = text1.split(' ');
|
||||
const words2 = text2.split(' ');
|
||||
|
||||
// Create a 2D array to hold our "matrix"
|
||||
const matrix = Array(words1.length + 1).fill(null).map(() => Array(words2.length + 1).fill(0));
|
||||
|
||||
// Perform standard LCS algorithm
|
||||
for (let i = 1; i <= words1.length; i++) {
|
||||
for (let j = 1; j <= words2.length; j++) {
|
||||
if (words1[i - 1] === words2[j - 1]) {
|
||||
matrix[i][j] = matrix[i - 1][j - 1] + 1;
|
||||
} else {
|
||||
matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let i = words1.length;
|
||||
let j = words2.length;
|
||||
const differences = [];
|
||||
|
||||
// Backtrack through the matrix to create the diff
|
||||
while (i > 0 || j > 0) {
|
||||
if (i > 0 && j > 0 && words1[i - 1] === words2[j - 1]) {
|
||||
differences.unshift(['black', words1[i - 1]]);
|
||||
i--;
|
||||
j--;
|
||||
} else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) {
|
||||
differences.unshift(['green', words2[j - 1]]);
|
||||
j--;
|
||||
} else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) {
|
||||
differences.unshift(['red', words1[i - 1]]);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return differences;
|
||||
};
|
||||
|
||||
|
||||
const differences = diff(text1, text2);
|
||||
|
||||
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 it's an addition, show it in green in the second document and transparent in the first
|
||||
if (color === "green") {
|
||||
span1.style.color = "transparent";
|
||||
span1.style.userSelect = "none";
|
||||
span2.style.color = color;
|
||||
}
|
||||
// If it's a deletion, show it in red in the first document and transparent in the second
|
||||
else if (color === "red") {
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
console.log('Differences:', differences);
|
||||
displayDifferences(differences);
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<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">
|
||||
<h2 th:text="#{compare.header}"></h2>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput2', multiple=false, accept='application/pdf', remoteCall='false')}"></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>
|
||||
// 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];
|
||||
|
||||
if (!file1 || !file2) {
|
||||
console.error("Please select two PDF files to compare");
|
||||
return;
|
||||
}
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
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("One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.");
|
||||
return;
|
||||
}
|
||||
const diff = (text1, text2) => {
|
||||
const words1 = text1.split(' ');
|
||||
const words2 = text2.split(' ');
|
||||
|
||||
// Create a 2D array to hold our "matrix"
|
||||
const matrix = Array(words1.length + 1).fill(null).map(() => Array(words2.length + 1).fill(0));
|
||||
|
||||
// Perform standard LCS algorithm
|
||||
for (let i = 1; i <= words1.length; i++) {
|
||||
for (let j = 1; j <= words2.length; j++) {
|
||||
if (words1[i - 1] === words2[j - 1]) {
|
||||
matrix[i][j] = matrix[i - 1][j - 1] + 1;
|
||||
} else {
|
||||
matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let i = words1.length;
|
||||
let j = words2.length;
|
||||
const differences = [];
|
||||
|
||||
// Backtrack through the matrix to create the diff
|
||||
while (i > 0 || j > 0) {
|
||||
if (i > 0 && j > 0 && words1[i - 1] === words2[j - 1]) {
|
||||
differences.unshift(['black', words1[i - 1]]);
|
||||
i--;
|
||||
j--;
|
||||
} else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) {
|
||||
differences.unshift(['green', words2[j - 1]]);
|
||||
j--;
|
||||
} else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) {
|
||||
differences.unshift(['red', words1[i - 1]]);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return differences;
|
||||
};
|
||||
|
||||
const differences = diff(text1, text2);
|
||||
|
||||
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 it's an addition, show it in green in the second document and transparent in the first
|
||||
if (color === "green") {
|
||||
span1.style.color = "transparent";
|
||||
span1.style.userSelect = "none";
|
||||
span2.style.color = color;
|
||||
}
|
||||
// If it's a deletion, show it in red in the first document and transparent in the second
|
||||
else if (color === "red") {
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
console.log('Differences:', differences);
|
||||
displayDifferences(differences);
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,50 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{compress.title}, header=#{compress.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{compress.title}, header=#{compress.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{compress.header}"></h2>
|
||||
<form action="#" th:action="@{api/v1/misc/compress-pdf}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h4 th:text="#{compress.selectText.1}"></h4>
|
||||
<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" selected>2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4" th:text="#{compress.selectText.3}"></option>
|
||||
</select>
|
||||
</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" min="1" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{compress.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
<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="#{compress.header}"></h2>
|
||||
<form action="#" th:action="@{api/v1/misc/compress-pdf}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h4 th:text="#{compress.selectText.1}"></h4>
|
||||
<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" selected>2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4" th:text="#{compress.selectText.3}"></option>
|
||||
</select>
|
||||
</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" min="1" 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:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,54 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{home.ScannerImageSplit.title}, header=#{home.ScannerImageSplit.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{home.ScannerImageSplit.title}, header=#{home.ScannerImageSplit.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{home.ScannerImageSplit.title}"></h2>
|
||||
|
||||
<form id="multiPdfForm" th:action="@{api/v1/misc/extract-image-scans}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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>
|
||||
<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">
|
||||
<h2 th:text="#{home.ScannerImageSplit.title}"></h2>
|
||||
|
||||
<form id="multiPdfForm" th:action="@{api/v1/misc/extract-image-scans}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,36 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.title}, header=#{extractImages.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.title}, header=#{extractImages.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{extractImages.header}"></h2>
|
||||
|
||||
<form id="multiPdfForm" th:action="@{api/v1/misc/extract-images}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{extractImages.selectText}"></label>
|
||||
<select class="form-control" name="format">
|
||||
<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">
|
||||
<h2 th:text="#{extractImages.header}"></h2>
|
||||
<form id="multiPdfForm" th:action="@{api/v1/misc/extract-images}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{extractImages.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{extractImages.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,57 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{flatten.title}, header=#{flatten.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{flatten.title}, header=#{flatten.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{flatten.header}"></h2>
|
||||
<form id="pdfForm" class="mb-3">
|
||||
<div class="custom-file">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{flatten.submit}"></button>
|
||||
<script 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 fetch(origFileUrl).then(res => res.arrayBuffer());
|
||||
const pdfDoc = await PDFDocument.load(formPdfBytes, { ignoreEncryption: true });
|
||||
|
||||
const form = pdfDoc.getForm();
|
||||
form.flatten();
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
|
||||
const fileName = (file.name ? file.name.replace('.pdf', '') : 'pdf') + '_flattened.pdf';
|
||||
|
||||
return { processedData: pdfBlob, fileName };
|
||||
};
|
||||
|
||||
await downloadFilesWithCallback(processFile);
|
||||
});
|
||||
</script>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<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="#{flatten.header}"></h2>
|
||||
<form id="pdfForm" class="mb-3">
|
||||
<div class="custom-file">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{flatten.submit}"></button>
|
||||
<script 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 fetch(origFileUrl).then(res => res.arrayBuffer());
|
||||
const pdfDoc = await PDFDocument.load(formPdfBytes, { ignoreEncryption: true });
|
||||
|
||||
const form = pdfDoc.getForm();
|
||||
form.flatten();
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
|
||||
const fileName = (file.name ? file.name.replace('.pdf', '') : 'pdf') + '_flattened.pdf';
|
||||
|
||||
return { processedData: pdfBlob, fileName };
|
||||
};
|
||||
|
||||
await downloadFilesWithCallback(processFile);
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,254 +1,249 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{ocr.title}, header=#{ocr.header})}"></th:block>
|
||||
<head>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{ocr.title}, header=#{ocr.header})}"></th:block>
|
||||
<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');
|
||||
}
|
||||
}
|
||||
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>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{ocr.header}"></h2>
|
||||
<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', multiple=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 th:each="language, iterStat : ${languages}">
|
||||
<input type="checkbox" th:name="languages" th:value="${language}" required th:id="${'language-' + language}" onchange="handleLangSelection()" />
|
||||
<label class="form-check-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="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="sidecar" id="sidecar" />
|
||||
<label class="form-check-label" for="sidecar" th:text="#{ocr.selectText.2}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="deskew" id="deskew" />
|
||||
<label class="form-check-label" for="deskew" th:text="#{ocr.selectText.3}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="clean" id="clean" />
|
||||
<label class="form-check-label" for="clean" th:text="#{ocr.selectText.4}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="clean-final" id="clean-final" />
|
||||
<label class="form-check-label" for="clean-final" th:text="#{ocr.selectText.5}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="removeImagesAfter" id="removeImagesAfter" />
|
||||
<label class="form-check-label" for="removeImagesAfter" th:text="#{ocr.selectText.11}"></label>
|
||||
</div>
|
||||
|
||||
|
||||
<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>
|
||||
const languageMap = {
|
||||
'afr': 'Afrikaans',
|
||||
'amh': 'Amharic',
|
||||
'ara': 'Arabic',
|
||||
'asm': 'Assamese',
|
||||
'aze': 'Azerbaijani',
|
||||
'aze_cyrl': 'Azerbaijani (Cyrillic)',
|
||||
'bel': 'Belarusian',
|
||||
'ben': 'Bengali',
|
||||
'bod': 'Tibetan',
|
||||
'bos': 'Bosnian',
|
||||
'bre': 'Breton',
|
||||
'bul': 'Bulgarian',
|
||||
'cat': 'Catalan',
|
||||
'ceb': 'Cebuano',
|
||||
'ces': 'Czech',
|
||||
'chi_sim': 'Chinese (Simplified)',
|
||||
'chi_sim_vert': 'Chinese (Simplified, Vertical)',
|
||||
'chi_tra': 'Chinese (Traditional)',
|
||||
'chi_tra_vert': 'Chinese (Traditional, Vertical)',
|
||||
'chr': 'Cherokee',
|
||||
'cos': 'Corsican',
|
||||
'cym': 'Welsh',
|
||||
'dan': 'Danish',
|
||||
'dan_frak': 'Danish (Fraktur)',
|
||||
'deu': 'German',
|
||||
'deu_frak': 'German (Fraktur)',
|
||||
'div': 'Divehi',
|
||||
'dzo': 'Dzongkha',
|
||||
'ell': 'Greek',
|
||||
'eng': 'English',
|
||||
'enm': 'English, Middle (1100-1500)',
|
||||
'epo': 'Esperanto',
|
||||
'equ': 'Math / equation detection module',
|
||||
'est': 'Estonian',
|
||||
'eus': 'Basque',
|
||||
'fao': 'Faroese',
|
||||
'fas': 'Persian',
|
||||
'fil': 'Filipino',
|
||||
'fin': 'Finnish',
|
||||
'fra': 'French',
|
||||
'frk': 'Frankish',
|
||||
'frm': 'French, Middle (ca.1400-1600)',
|
||||
'fry': 'Western Frisian',
|
||||
'gla': 'Scottish Gaelic',
|
||||
'gle': 'Irish',
|
||||
'glg': 'Galician',
|
||||
'grc': 'Ancient Greek',
|
||||
'guj': 'Gujarati',
|
||||
'hat': 'Haitian, Haitian Creole',
|
||||
'heb': 'Hebrew',
|
||||
'hin': 'Hindi',
|
||||
'hrv': 'Croatian',
|
||||
'hun': 'Hungarian',
|
||||
'hye': 'Armenian',
|
||||
'iku': 'Inuktitut',
|
||||
'ind': 'Indonesian',
|
||||
'isl': 'Icelandic',
|
||||
'ita': 'Italian',
|
||||
'ita_old': 'Italian (Old)',
|
||||
'jav': 'Javanese',
|
||||
'jpn': 'Japanese',
|
||||
'jpn_vert': 'Japanese (Vertical)',
|
||||
'kan': 'Kannada',
|
||||
'kat': 'Georgian',
|
||||
'kat_old': 'Georgian (Old)',
|
||||
'kaz': 'Kazakh',
|
||||
'khm': 'Central Khmer',
|
||||
'kir': 'Kirghiz, Kyrgyz',
|
||||
'kmr': 'Northern Kurdish',
|
||||
'kor': 'Korean',
|
||||
'kor_vert': 'Korean (Vertical)',
|
||||
'lao': 'Lao',
|
||||
'lat': 'Latin',
|
||||
'lav': 'Latvian',
|
||||
'lit': 'Lithuanian',
|
||||
'ltz': 'Luxembourgish',
|
||||
'mal': 'Malayalam',
|
||||
'mar': 'Marathi',
|
||||
'mkd': 'Macedonian',
|
||||
'mlt': 'Maltese',
|
||||
'mon': 'Mongolian',
|
||||
'mri': 'Maori',
|
||||
'msa': 'Malay',
|
||||
'mya': 'Burmese',
|
||||
'nep': 'Nepali',
|
||||
'nld': 'Dutch; Flemish',
|
||||
'nor': 'Norwegian',
|
||||
'oci': 'Occitan (post 1500)',
|
||||
'ori': 'Oriya',
|
||||
'osd': 'Orientation and script detection module',
|
||||
'pan': 'Panjabi, Punjabi',
|
||||
'pol': 'Polish',
|
||||
'por': 'Portuguese',
|
||||
'pus': 'Pushto, Pashto',
|
||||
'que': 'Quechua',
|
||||
'ron': 'Romanian, Moldavian, Moldovan',
|
||||
'rus': 'Russian',
|
||||
'san': 'Sanskrit',
|
||||
'sin': 'Sinhala, Sinhalese',
|
||||
'slk': 'Slovak',
|
||||
'slk_frak': 'Slovak (Fraktur)',
|
||||
'slv': 'Slovenian',
|
||||
'snd': 'Sindhi',
|
||||
'spa': 'Spanish',
|
||||
'spa_old': 'Spanish (Old)',
|
||||
'sqi': 'Albanian',
|
||||
'srp': 'Serbian',
|
||||
'srp_latn': 'Serbian (Latin)',
|
||||
'sun': 'Sundanese',
|
||||
'swa': 'Swahili',
|
||||
'swe': 'Swedish',
|
||||
'syr': 'Syriac',
|
||||
'tam': 'Tamil',
|
||||
'tat': 'Tatar',
|
||||
'tel': 'Telugu',
|
||||
'tgk': 'Tajik',
|
||||
'tgl': 'Tagalog',
|
||||
'tha': 'Thai',
|
||||
'tir': 'Tigrinya',
|
||||
'ton': 'Tonga (Tonga Islands)',
|
||||
'tur': 'Turkish',
|
||||
'uig': 'Uighur, Uyghur',
|
||||
'ukr': 'Ukrainian',
|
||||
'urd': 'Urdu',
|
||||
'uzb': 'Uzbek',
|
||||
'uzb_cyrl': 'Uzbek (Cyrillic)',
|
||||
'vie': 'Vietnamese',
|
||||
'yid': 'Yiddish',
|
||||
'yor': 'Yoruba'
|
||||
};
|
||||
|
||||
// Step 2: Function to get the full language name
|
||||
function getFullLanguageName(shortCode) {
|
||||
return languageMap[shortCode] || shortCode;
|
||||
}
|
||||
|
||||
// Step 3: Apply the function to your labels
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const labels = document.querySelectorAll('#languages .form-check-label');
|
||||
labels.forEach(label => {
|
||||
const languageCode = label.getAttribute('for').split('-')[1];
|
||||
label.textContent = getFullLanguageName(languageCode);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<p th:text="#{ocr.credit}"></p>
|
||||
<p th:text="#{ocr.help}"></p>
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md">https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md</a>
|
||||
<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="#{ocr.header}"></h2>
|
||||
<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', multiple=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 th:each="language, iterStat : ${languages}">
|
||||
<input type="checkbox" th:name="languages" th:value="${language}" required th:id="${'language-' + language}" onchange="handleLangSelection()" />
|
||||
<label class="form-check-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="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="sidecar" id="sidecar" />
|
||||
<label class="form-check-label" for="sidecar" th:text="#{ocr.selectText.2}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="deskew" id="deskew" />
|
||||
<label class="form-check-label" for="deskew" th:text="#{ocr.selectText.3}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="clean" id="clean" />
|
||||
<label class="form-check-label" for="clean" th:text="#{ocr.selectText.4}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="clean-final" id="clean-final" />
|
||||
<label class="form-check-label" for="clean-final" th:text="#{ocr.selectText.5}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="removeImagesAfter" id="removeImagesAfter" />
|
||||
<label class="form-check-label" for="removeImagesAfter" th:text="#{ocr.selectText.11}"></label>
|
||||
</div>
|
||||
<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>
|
||||
const languageMap = {
|
||||
'afr': 'Afrikaans',
|
||||
'amh': 'Amharic',
|
||||
'ara': 'Arabic',
|
||||
'asm': 'Assamese',
|
||||
'aze': 'Azerbaijani',
|
||||
'aze_cyrl': 'Azerbaijani (Cyrillic)',
|
||||
'bel': 'Belarusian',
|
||||
'ben': 'Bengali',
|
||||
'bod': 'Tibetan',
|
||||
'bos': 'Bosnian',
|
||||
'bre': 'Breton',
|
||||
'bul': 'Bulgarian',
|
||||
'cat': 'Catalan',
|
||||
'ceb': 'Cebuano',
|
||||
'ces': 'Czech',
|
||||
'chi_sim': 'Chinese (Simplified)',
|
||||
'chi_sim_vert': 'Chinese (Simplified, Vertical)',
|
||||
'chi_tra': 'Chinese (Traditional)',
|
||||
'chi_tra_vert': 'Chinese (Traditional, Vertical)',
|
||||
'chr': 'Cherokee',
|
||||
'cos': 'Corsican',
|
||||
'cym': 'Welsh',
|
||||
'dan': 'Danish',
|
||||
'dan_frak': 'Danish (Fraktur)',
|
||||
'deu': 'German',
|
||||
'deu_frak': 'German (Fraktur)',
|
||||
'div': 'Divehi',
|
||||
'dzo': 'Dzongkha',
|
||||
'ell': 'Greek',
|
||||
'eng': 'English',
|
||||
'enm': 'English, Middle (1100-1500)',
|
||||
'epo': 'Esperanto',
|
||||
'equ': 'Math / equation detection module',
|
||||
'est': 'Estonian',
|
||||
'eus': 'Basque',
|
||||
'fao': 'Faroese',
|
||||
'fas': 'Persian',
|
||||
'fil': 'Filipino',
|
||||
'fin': 'Finnish',
|
||||
'fra': 'French',
|
||||
'frk': 'Frankish',
|
||||
'frm': 'French, Middle (ca.1400-1600)',
|
||||
'fry': 'Western Frisian',
|
||||
'gla': 'Scottish Gaelic',
|
||||
'gle': 'Irish',
|
||||
'glg': 'Galician',
|
||||
'grc': 'Ancient Greek',
|
||||
'guj': 'Gujarati',
|
||||
'hat': 'Haitian, Haitian Creole',
|
||||
'heb': 'Hebrew',
|
||||
'hin': 'Hindi',
|
||||
'hrv': 'Croatian',
|
||||
'hun': 'Hungarian',
|
||||
'hye': 'Armenian',
|
||||
'iku': 'Inuktitut',
|
||||
'ind': 'Indonesian',
|
||||
'isl': 'Icelandic',
|
||||
'ita': 'Italian',
|
||||
'ita_old': 'Italian (Old)',
|
||||
'jav': 'Javanese',
|
||||
'jpn': 'Japanese',
|
||||
'jpn_vert': 'Japanese (Vertical)',
|
||||
'kan': 'Kannada',
|
||||
'kat': 'Georgian',
|
||||
'kat_old': 'Georgian (Old)',
|
||||
'kaz': 'Kazakh',
|
||||
'khm': 'Central Khmer',
|
||||
'kir': 'Kirghiz, Kyrgyz',
|
||||
'kmr': 'Northern Kurdish',
|
||||
'kor': 'Korean',
|
||||
'kor_vert': 'Korean (Vertical)',
|
||||
'lao': 'Lao',
|
||||
'lat': 'Latin',
|
||||
'lav': 'Latvian',
|
||||
'lit': 'Lithuanian',
|
||||
'ltz': 'Luxembourgish',
|
||||
'mal': 'Malayalam',
|
||||
'mar': 'Marathi',
|
||||
'mkd': 'Macedonian',
|
||||
'mlt': 'Maltese',
|
||||
'mon': 'Mongolian',
|
||||
'mri': 'Maori',
|
||||
'msa': 'Malay',
|
||||
'mya': 'Burmese',
|
||||
'nep': 'Nepali',
|
||||
'nld': 'Dutch; Flemish',
|
||||
'nor': 'Norwegian',
|
||||
'oci': 'Occitan (post 1500)',
|
||||
'ori': 'Oriya',
|
||||
'osd': 'Orientation and script detection module',
|
||||
'pan': 'Panjabi, Punjabi',
|
||||
'pol': 'Polish',
|
||||
'por': 'Portuguese',
|
||||
'pus': 'Pushto, Pashto',
|
||||
'que': 'Quechua',
|
||||
'ron': 'Romanian, Moldavian, Moldovan',
|
||||
'rus': 'Russian',
|
||||
'san': 'Sanskrit',
|
||||
'sin': 'Sinhala, Sinhalese',
|
||||
'slk': 'Slovak',
|
||||
'slk_frak': 'Slovak (Fraktur)',
|
||||
'slv': 'Slovenian',
|
||||
'snd': 'Sindhi',
|
||||
'spa': 'Spanish',
|
||||
'spa_old': 'Spanish (Old)',
|
||||
'sqi': 'Albanian',
|
||||
'srp': 'Serbian',
|
||||
'srp_latn': 'Serbian (Latin)',
|
||||
'sun': 'Sundanese',
|
||||
'swa': 'Swahili',
|
||||
'swe': 'Swedish',
|
||||
'syr': 'Syriac',
|
||||
'tam': 'Tamil',
|
||||
'tat': 'Tatar',
|
||||
'tel': 'Telugu',
|
||||
'tgk': 'Tajik',
|
||||
'tgl': 'Tagalog',
|
||||
'tha': 'Thai',
|
||||
'tir': 'Tigrinya',
|
||||
'ton': 'Tonga (Tonga Islands)',
|
||||
'tur': 'Turkish',
|
||||
'uig': 'Uighur, Uyghur',
|
||||
'ukr': 'Ukrainian',
|
||||
'urd': 'Urdu',
|
||||
'uzb': 'Uzbek',
|
||||
'uzb_cyrl': 'Uzbek (Cyrillic)',
|
||||
'vie': 'Vietnamese',
|
||||
'yid': 'Yiddish',
|
||||
'yor': 'Yoruba'
|
||||
};
|
||||
|
||||
// Step 2: Function to get the full language name
|
||||
function getFullLanguageName(shortCode) {
|
||||
return languageMap[shortCode] || shortCode;
|
||||
}
|
||||
|
||||
// Step 3: Apply the function to your labels
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const labels = document.querySelectorAll('#languages .form-check-label');
|
||||
labels.forEach(label => {
|
||||
const languageCode = label.getAttribute('for').split('-')[1];
|
||||
label.textContent = getFullLanguageName(languageCode);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<p th:text="#{ocr.credit}"></p>
|
||||
<p th:text="#{ocr.help}"></p>
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md">https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,64 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{removeAnnotations.title}, header=#{removeAnnotations.header})}"></th:block>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{removeAnnotations.title}, header=#{removeAnnotations.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{removeAnnotations.header}"></h2>
|
||||
<form id="pdfForm" class="mb-3">
|
||||
<div class="custom-file">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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 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="#{removeAnnotations.header}"></h2>
|
||||
<form id="pdfForm" class="mb-3">
|
||||
<div class="custom-file">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<script src="js/local-pdf-input-download.js"></script>
|
||||
<script>
|
||||
document.getElementById('pdfForm').addEventListener('submit', async (e) => {
|
||||
document.getElementById('pdfForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { PDFDocument } = PDFLib;
|
||||
|
||||
const processFile = async (file) => {
|
||||
const origFileUrl = URL.createObjectURL(file);
|
||||
const formPdfBytes = await fetch(origFileUrl).then(res => res.arrayBuffer());
|
||||
const pdfDoc = await PDFDocument.load(formPdfBytes, { ignoreEncryption: true });
|
||||
const origFileUrl = URL.createObjectURL(file);
|
||||
const formPdfBytes = await fetch(origFileUrl).then(res => res.arrayBuffer());
|
||||
const pdfDoc = await PDFDocument.load(formPdfBytes, { ignoreEncryption: true });
|
||||
|
||||
const pages = pdfDoc.getPages();
|
||||
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 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);
|
||||
}
|
||||
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';
|
||||
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 };
|
||||
return { processedData: pdfBlob, fileName };
|
||||
};
|
||||
|
||||
await downloadFilesWithCallback(processFile);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,39 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{removeBlanks.title}, header=#{removeBlanks.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{removeBlanks.title}, header=#{removeBlanks.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{removeBlanks.header}"></h2>
|
||||
|
||||
<form id="multiPdfForm" th:action="@{api/v1/misc/remove-blanks}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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">
|
||||
<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">
|
||||
<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>
|
||||
<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">
|
||||
<h2 th:text="#{removeBlanks.header}"></h2>
|
||||
<form id="multiPdfForm" th:action="@{api/v1/misc/remove-blanks}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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">
|
||||
<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">
|
||||
<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>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{repair.title}, header=#{repair.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{repair.title}, header=#{repair.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{repair.header}"></h2>
|
||||
<form id="multiPdfForm" th:action="@{api/v1/misc/repair}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{repair.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<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="#{repair.header}"></h2>
|
||||
<form id="multiPdfForm" th:action="@{api/v1/misc/repair}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,100 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}"
|
||||
th:lang-direction="#{language.direction}"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<th:block
|
||||
th:insert="~{fragments/common :: head(title=#{showJS.title}, header=#{showJS.header})}"></th:block>
|
||||
<body>
|
||||
<link href="css/prism.css" rel="stylesheet" />
|
||||
<script src="js/thirdParty/prism.js"></script>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-12">
|
||||
<h2 th:text="#{showJS.header}"></h2>
|
||||
<form id="pdfInfoForm" method="post" enctype="multipart/form-data"
|
||||
th:action="@{show-javascript}">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, remoteCall='false', accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary"
|
||||
th:text="#{showJS.submit}"></button>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{showJS.title}, header=#{showJS.header})}"></th:block>
|
||||
<link href="css/prism.css" rel="stylesheet" />
|
||||
<script 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>
|
||||
|
||||
</form>
|
||||
<div class="container mt-5">
|
||||
<!-- Iterate over each main section in the JSON -->
|
||||
<div id="script-content">
|
||||
<!-- JavaScript will populate this section -->
|
||||
</div>
|
||||
<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">
|
||||
<h2 th:text="#{showJS.header}"></h2>
|
||||
<form id="pdfInfoForm" method="post" enctype="multipart/form-data" th:action="@{show-javascript}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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-5">
|
||||
<!-- 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>
|
||||
<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>
|
||||
<script>
|
||||
document.querySelector('#pdfInfoForm').addEventListener('submit', function(event){
|
||||
event.preventDefault();
|
||||
<!-- 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>
|
||||
document.querySelector('#pdfInfoForm').addEventListener('submit', function(event){
|
||||
event.preventDefault();
|
||||
|
||||
// Fetch the formData
|
||||
const formData = new FormData(event.target);
|
||||
// Fetch the formData
|
||||
const formData = new FormData(event.target);
|
||||
|
||||
fetch('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, '>');
|
||||
fetch('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);
|
||||
// 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);
|
||||
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();
|
||||
// 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);
|
||||
// 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>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
// 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>
|
||||
@@ -1,222 +1,167 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}"
|
||||
th:lang-direction="#{language.direction}"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{AddStampRequest.title}, header=#{AddStampRequest.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block
|
||||
th:insert="~{fragments/common :: head(title=#{AddStampRequest.title}, header=#{AddStampRequest.header})}"></th:block>
|
||||
<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="#{AddStampRequest.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/add-stamp}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=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 for="position" 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" min="1" max="9" value="8" required />
|
||||
|
||||
<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()" required>
|
||||
<option value="text">Text</option>
|
||||
<option value="image">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>
|
||||
|
||||
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{AddStampRequest.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
th:action="@{api/v1/misc/add-stamp}">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<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="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="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="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>
|
||||
<style>
|
||||
.a4container {
|
||||
position: relative;
|
||||
width: 50%;
|
||||
aspect-ratio: 0.707;
|
||||
border: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
background-color: white;
|
||||
}
|
||||
<div class="mb-3">
|
||||
<label for="customColor" class="form-label" th:text="#{AddStampRequest.customColor}">Custom Color</label>
|
||||
<input type="color" class="form-control form-control-color" id="customColor" name="customColor" value="#d3d3d3">
|
||||
</div>
|
||||
|
||||
.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>
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="position" 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" min="1"
|
||||
max="9" value="8" required />
|
||||
|
||||
|
||||
|
||||
<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()" required>
|
||||
<option value="text">Text</option>
|
||||
<option value="image">Image</option>
|
||||
</select>
|
||||
<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');
|
||||
|
||||
<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>
|
||||
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
|
||||
});
|
||||
|
||||
<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>
|
||||
cell.addEventListener('mouseenter', function(e) {
|
||||
if(e.target.classList.contains('selectedPosition')) {
|
||||
e.target.classList.add('selectedHovered');
|
||||
}
|
||||
});
|
||||
|
||||
<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>
|
||||
cell.addEventListener('mouseleave', function(e) {
|
||||
if(e.target.classList.contains('selectedPosition')) {
|
||||
e.target.classList.remove('selectedHovered');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
<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>
|
||||
function toggleFileOption() {
|
||||
const stampType = document.getElementById('stampType').value;
|
||||
const stampTextGroup = document.getElementById('stampTextGroup');
|
||||
const stampImageGroup = document.getElementById('stampImageGroup');
|
||||
const alphabetGroup = document.getElementById('alphabetGroup');
|
||||
|
||||
<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>
|
||||
<input type="color" class="form-control form-control-color" id="customColor" name="customColor" value="#d3d3d3">
|
||||
</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 stampType = document.getElementById('stampType').value;
|
||||
const stampTextGroup = document.getElementById('stampTextGroup');
|
||||
const stampImageGroup = document.getElementById('stampImageGroup');
|
||||
const alphabetGroup = document.getElementById('alphabetGroup');
|
||||
|
||||
if (stampType === 'text') {
|
||||
stampTextGroup.style.display = 'block';
|
||||
stampImageGroup.style.display = 'none';
|
||||
alphabetGroup.style.display = 'block';
|
||||
} else if (stampType === 'image') {
|
||||
stampTextGroup.style.display = 'none';
|
||||
stampImageGroup.style.display = 'block';
|
||||
alphabetGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
if (stampType === 'text') {
|
||||
stampTextGroup.style.display = 'block';
|
||||
stampImageGroup.style.display = 'none';
|
||||
alphabetGroup.style.display = 'block';
|
||||
} else if (stampType === 'image') {
|
||||
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>
|
||||
|
||||
@@ -1,43 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageLayout.title}, header=#{pageLayout.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageLayout.title}, header=#{pageLayout.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pageLayout.header}"></h2>
|
||||
<form id="multiPdfForm" th:action="@{api/v1/general/multi-page-layout}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="pagesPerSheet" th:text="#{pageLayout.pagesPerSheet}"></label>
|
||||
<select id="pagesPerSheet" name="pagesPerSheet" required>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="9">9</option>
|
||||
<option value="16">16</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="addBorder" name="addBorder">
|
||||
<label class="form-check-label" for="addBorder" th:text="#{pageLayout.addBorder}"></label>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageLayout.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
<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="#{pageLayout.header}"></h2>
|
||||
<form id="multiPdfForm" th:action="@{api/v1/general/multi-page-layout}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="pagesPerSheet" th:text="#{pageLayout.pagesPerSheet}"></label>
|
||||
<select id="pagesPerSheet" name="pagesPerSheet" required>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="9">9</option>
|
||||
<option value="16">16</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="addBorder" name="addBorder">
|
||||
<label class="form-check-label" for="addBorder" th:text="#{pageLayout.addBorder}"></label>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageLayout.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,233 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{multiTool.title}, header=#{multiTool.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{multiTool.title}, header=#{multiTool.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="image-highlighter"></div>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="multi-tool-container">
|
||||
<div class="row justify-content-center">
|
||||
<h2 th:text="#{multiTool.header}"></h2>
|
||||
<div class="col-md-12" id="pages-container-wrapper">
|
||||
<div id="pages-container">
|
||||
<div class="page-container" th:each="pdf, status: ${pdfList}" th:id="'page-container-' + ${status.index}">
|
||||
<div class="page-number-container">
|
||||
<span th:text="${status.index + 1}"></span>
|
||||
</div>
|
||||
<img th:src="${pdf.imageUrl}" alt="PDF Page">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6" style="text-align: center">
|
||||
<div id="global-buttons-container" class="d-flex align-content-center justify-content-center">
|
||||
<div class="form-group">
|
||||
<label for="filename-input">Filename</label>
|
||||
<input type="text" class="form-control" id="filename-input" placeholder="filename">
|
||||
</div>
|
||||
</div>
|
||||
<div id="global-buttons-container">
|
||||
|
||||
<button class="btn btn-primary" onclick="addPdfs()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16">
|
||||
<path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
|
||||
<button class="btn btn-secondary enable-on-file" onclick="rotateAll(-90)" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-secondary enable-on-file" onclick="rotateAll(90)" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button id="export-button" class="btn btn-primary enable-on-file" onclick="exportPdf()" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-download" viewBox="0 0 16 16">
|
||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="content-wrap">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<br /><br />
|
||||
<div class="multi-tool-container">
|
||||
<div class="row justify-content-center">
|
||||
<h2 th:text="#{multiTool.header}"></h2>
|
||||
<div class="col-md-12" id="pages-container-wrapper">
|
||||
<div id="pages-container">
|
||||
<div class="page-container" th:each="pdf, status: ${pdfList}" th:id="'page-container-' + ${status.index}">
|
||||
<div class="page-number-container">
|
||||
<span th:text="${status.index + 1}"></span>
|
||||
</div>
|
||||
<img th:src="${pdf.imageUrl}" alt="PDF Page">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6" style="text-align: center">
|
||||
<div id="global-buttons-container" class="d-flex align-content-center justify-content-center">
|
||||
<div class="form-group">
|
||||
<label for="filename-input">Filename</label>
|
||||
<input type="text" class="form-control" id="filename-input" placeholder="filename">
|
||||
</div>
|
||||
</div>
|
||||
<div id="global-buttons-container">
|
||||
<button class="btn btn-primary" onclick="addPdfs()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16">
|
||||
<path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary enable-on-file" onclick="rotateAll(-90)" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary enable-on-file" onclick="rotateAll(90)" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="export-button" class="btn btn-primary enable-on-file" onclick="exportPdf()" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-download" viewBox="0 0 16 16">
|
||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div id="drag-container"></div>
|
||||
|
||||
<script type="module">
|
||||
import PdfContainer from './js/multitool/PdfContainer.js';
|
||||
import DragDropManager from "./js/multitool/DragDropManager.js";
|
||||
import scrollDivHorizontally from "./js/multitool/horizontalScroll.js";
|
||||
import ImageHighlighter from "./js/multitool/ImageHighlighter.js";
|
||||
import PdfActionsManager from './js/multitool/PdfActionsManager.js';
|
||||
import FileDragManager from './js/multitool/fileInput.js';
|
||||
// enables drag and drop
|
||||
const dragDropManager = new DragDropManager('drag-container', 'pages-container');
|
||||
// enables image highlight on click
|
||||
const imageHighlighter = new ImageHighlighter('image-highlighter');
|
||||
// enables the default action buttons on each pdf
|
||||
const pdfActionsManager = new PdfActionsManager('pages-container');
|
||||
const fileDragManager = new FileDragManager();
|
||||
import PdfContainer from './js/multitool/PdfContainer.js';
|
||||
import DragDropManager from "./js/multitool/DragDropManager.js";
|
||||
import scrollDivHorizontally from "./js/multitool/horizontalScroll.js";
|
||||
import ImageHighlighter from "./js/multitool/ImageHighlighter.js";
|
||||
import PdfActionsManager from './js/multitool/PdfActionsManager.js';
|
||||
import FileDragManager from './js/multitool/fileInput.js';
|
||||
// enables drag and drop
|
||||
const dragDropManager = new DragDropManager('drag-container', 'pages-container');
|
||||
// enables image highlight on click
|
||||
const imageHighlighter = new ImageHighlighter('image-highlighter');
|
||||
// enables the default action buttons on each pdf
|
||||
const pdfActionsManager = new PdfActionsManager('pages-container');
|
||||
const fileDragManager = new FileDragManager();
|
||||
|
||||
// Scroll the wrapper horizontally
|
||||
scrollDivHorizontally('pages-container-wrapper');
|
||||
// Scroll the wrapper horizontally
|
||||
scrollDivHorizontally('pages-container-wrapper');
|
||||
|
||||
// Automatically exposes rotateAll, addPdfs and exportPdf to the window for the global buttons.
|
||||
const pdfContainer = new PdfContainer(
|
||||
'pages-container',
|
||||
'pages-container-wrapper',
|
||||
[
|
||||
dragDropManager,
|
||||
imageHighlighter,
|
||||
pdfActionsManager,
|
||||
fileDragManager
|
||||
]
|
||||
)
|
||||
// Automatically exposes rotateAll, addPdfs and exportPdf to the window for the global buttons.
|
||||
const pdfContainer = new PdfContainer(
|
||||
'pages-container',
|
||||
'pages-container-wrapper',
|
||||
[
|
||||
dragDropManager,
|
||||
imageHighlighter,
|
||||
pdfActionsManager,
|
||||
fileDragManager
|
||||
]
|
||||
)
|
||||
|
||||
fileDragManager.setCallback(async (files) => pdfContainer.addPdfsFromFiles(files));
|
||||
fileDragManager.setCallback(async (files) => pdfContainer.addPdfsFromFiles(files));
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.multi-tool-container {
|
||||
max-width: 95vw;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#global-buttons-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: start;
|
||||
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
border: 1px solid rgba(0, 0, 0, .25);
|
||||
backdrop-filter: blur(2px);
|
||||
|
||||
top: 10px;
|
||||
z-index: 10;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
#global-buttons-container > * {
|
||||
padding: 0.6rem 0.75rem;
|
||||
}
|
||||
|
||||
#global-buttons-container svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
#export-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#pages-container-wrapper {
|
||||
--background-color: rgba(0, 0, 0, 0.025);
|
||||
--scroll-bar-color: #f1f1f1;
|
||||
--scroll-bar-thumb: #888;
|
||||
--scroll-bar-thumb-hover: #555;
|
||||
background-color: var(--background-color);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 25px;
|
||||
border-radius: 10px;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
min-height: 275px;
|
||||
margin: 0 0 30px 0;
|
||||
}
|
||||
|
||||
#pages-container {
|
||||
margin: auto;
|
||||
gap: 0px;
|
||||
display:flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* width */
|
||||
#pages-container-wrapper::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
#pages-container-wrapper::-webkit-scrollbar-track {
|
||||
background: var(--scroll-bar-color);
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
#pages-container-wrapper::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background: var(--scroll-bar-thumb);
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
#pages-container-wrapper::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scroll-bar-thumb-hover);
|
||||
}
|
||||
|
||||
.page-container {
|
||||
height:250px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column-reverse;
|
||||
aspect-ratio: 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
transition: width 1s linear;
|
||||
|
||||
}
|
||||
|
||||
.page-container img {
|
||||
/* max-width: calc(100% - 15px); */
|
||||
max-height: calc(100% - 15px);
|
||||
max-width: 237px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
translate: -50% -50%;
|
||||
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
|
||||
border-radius: 4px;
|
||||
transition: rotate 0.3s;
|
||||
}
|
||||
|
||||
#add-pdf-button {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right:5px;
|
||||
color:white;
|
||||
background-color: #007bff; /* Primary blue color */
|
||||
padding: 3px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
z-index:2;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,102 +1,96 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<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="#{overlay-pdfs.header}"></h2>
|
||||
<form id="overlayForm" method="post" enctype="multipart/form-data" th:action="@{/api/v1/general/overlay-pdfs}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='overlayFiles', multiple=true, accept='application/pdf')}"></div>
|
||||
|
||||
|
||||
<h2 th:text="#{overlay-pdfs.header}"></h2>
|
||||
<form id="overlayForm" method="post" enctype="multipart/form-data" th:action="@{/api/v1/general/overlay-pdfs}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='overlayFiles', multiple=true, accept='application/pdf')}"></div>
|
||||
|
||||
<label for="overlayMode" th:text="#{overlay-pdfs.mode.label}">Overlay Mode</label>
|
||||
<select id="overlayMode" name="overlayMode" class="form-control">
|
||||
<option value="SequentialOverlay" th:text="#{overlay-pdfs.mode.sequential}">Sequential Overlay</option>
|
||||
<option value="InterleavedOverlay" th:text="#{overlay-pdfs.mode.interleaved}">Interleaved Overlay</option>
|
||||
<option value="FixedRepeatOverlay" th:text="#{overlay-pdfs.mode.fixedRepeat}">Fixed Repeat Overlay</option>
|
||||
</select>
|
||||
<br>
|
||||
<label for="overlayPosition" th:text="#{overlay-pdfs.position.label}">Overlay Position</label>
|
||||
<select id="overlayPosition" name="overlayPosition" class="form-control">
|
||||
<option value="0" th:text="#{overlay-pdfs.position.foreground}">Foreground</option>
|
||||
<option value="1" th:text="#{overlay-pdfs.position.background}">Background</option>
|
||||
</select>
|
||||
<br>
|
||||
<div id="countsContainer" style="display: none;">
|
||||
<label th:text="#{overlay-pdfs.counts.label}">Overlay Counts</label>
|
||||
<!-- Inputs for counts will be dynamically added here -->
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{overlay-pdfs.submit}">Submit</button>
|
||||
</form>
|
||||
|
||||
|
||||
<script>
|
||||
function updateCountsInputs() {
|
||||
const mode = document.getElementById('overlayMode').value;
|
||||
console.log("mode",mode);
|
||||
const countsContainer = document.getElementById('countsContainer');
|
||||
console.log("countsContainer",countsContainer);
|
||||
countsContainer.innerHTML = ''; // Clear previous inputs
|
||||
|
||||
if (mode === 'FixedRepeatOverlay') {
|
||||
const fileInput = document.getElementById('overlayFiles-input');
|
||||
console.log("fileInput",fileInput);
|
||||
if(fileInput){
|
||||
const files = fileInput.files
|
||||
console.log("files",files);
|
||||
if(files) {
|
||||
const fileCount = files.length;
|
||||
|
||||
for (let i = 0; i < fileCount; i++) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.name = 'counts';
|
||||
input.classList.add('form-control');
|
||||
input.placeholder = 'Count for file ' + (i + 1);
|
||||
countsContainer.appendChild(input);
|
||||
}
|
||||
countsContainer.style.display = 'block';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
countsContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
var fileInput = document.getElementById('overlayFiles-input');
|
||||
console.log("fileInput2",fileInput);
|
||||
if (fileInput) {
|
||||
fileInput.addEventListener('change', updateCountsInputs);
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
var overlay = document.getElementById('overlayMode');
|
||||
console.log("overlay2",overlay);
|
||||
if (overlay) {
|
||||
overlay.addEventListener('change', updateCountsInputs);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</div>
|
||||
<label for="overlayMode" th:text="#{overlay-pdfs.mode.label}">Overlay Mode</label>
|
||||
<select id="overlayMode" name="overlayMode" class="form-control">
|
||||
<option value="SequentialOverlay" th:text="#{overlay-pdfs.mode.sequential}">Sequential Overlay</option>
|
||||
<option value="InterleavedOverlay" th:text="#{overlay-pdfs.mode.interleaved}">Interleaved Overlay</option>
|
||||
<option value="FixedRepeatOverlay" th:text="#{overlay-pdfs.mode.fixedRepeat}">Fixed Repeat Overlay</option>
|
||||
</select>
|
||||
<br>
|
||||
<label for="overlayPosition" th:text="#{overlay-pdfs.position.label}">Overlay Position</label>
|
||||
<select id="overlayPosition" name="overlayPosition" class="form-control">
|
||||
<option value="0" th:text="#{overlay-pdfs.position.foreground}">Foreground</option>
|
||||
<option value="1" th:text="#{overlay-pdfs.position.background}">Background</option>
|
||||
</select>
|
||||
<br>
|
||||
<div id="countsContainer" style="display: none;">
|
||||
<label th:text="#{overlay-pdfs.counts.label}">Overlay Counts</label>
|
||||
<!-- Inputs for counts will be dynamically added here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{overlay-pdfs.submit}">Submit</button>
|
||||
</form>
|
||||
<script>
|
||||
function updateCountsInputs() {
|
||||
const mode = document.getElementById('overlayMode').value;
|
||||
console.log("mode",mode);
|
||||
const countsContainer = document.getElementById('countsContainer');
|
||||
console.log("countsContainer",countsContainer);
|
||||
countsContainer.innerHTML = ''; // Clear previous inputs
|
||||
|
||||
if (mode === 'FixedRepeatOverlay') {
|
||||
const fileInput = document.getElementById('overlayFiles-input');
|
||||
console.log("fileInput",fileInput);
|
||||
if(fileInput){
|
||||
const files = fileInput.files
|
||||
console.log("files",files);
|
||||
if(files) {
|
||||
const fileCount = files.length;
|
||||
|
||||
for (let i = 0; i < fileCount; i++) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.name = 'counts';
|
||||
input.classList.add('form-control');
|
||||
input.placeholder = 'Count for file ' + (i + 1);
|
||||
countsContainer.appendChild(input);
|
||||
}
|
||||
countsContainer.style.display = 'block';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
countsContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
var fileInput = document.getElementById('overlayFiles-input');
|
||||
console.log("fileInput2",fileInput);
|
||||
if (fileInput) {
|
||||
fileInput.addEventListener('change', updateCountsInputs);
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
var overlay = document.getElementById('overlayMode');
|
||||
console.log("overlay2",overlay);
|
||||
if (overlay) {
|
||||
overlay.addEventListener('change', updateCountsInputs);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,57 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfOrganiser.title}, header=#{pdfOrganiser.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfOrganiser.title}, header=#{pdfOrganiser.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pdfOrganiser.header}"></h2>
|
||||
<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="#{pdfOrganiser.header}"></h2>
|
||||
|
||||
<form th:action="@{api/v1/general/rearrange-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="customMode">Mode</label>
|
||||
<select class="form-control" id="customMode" name="customMode">
|
||||
<option value="">Custom Page Order</option>
|
||||
<option value="REVERSE_ORDER">Reverse Order</option>
|
||||
<option value="DUPLEX_SORT">Duplex Sort</option>
|
||||
<option value="BOOKLET_SORT">Booklet Sort</option>
|
||||
<option value="SIDE_STITCH_BOOKLET_SORT">Side Stitch Booklet Sort</option>
|
||||
<option value="ODD_EVEN_SPLIT">Odd-Even Split</option>
|
||||
<option value="REMOVE_FIRST">Remove First</option>
|
||||
<option value="REMOVE_LAST">Remove Last</option>
|
||||
<option value="REMOVE_FIRST_AND_LAST">Remove First and Last</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||
<input type="text" class="form-control" id="pageOrder" name="pageNumbers" placeholder="(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('customMode').addEventListener('change', function () {
|
||||
var pageOrderInput = document.getElementById('pageOrder');
|
||||
if (this.value === "") {
|
||||
pageOrderInput.disabled = false;
|
||||
} else {
|
||||
pageOrderInput.disabled = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<form th:action="@{api/v1/general/rearrange-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="customMode">Mode</label>
|
||||
<select class="form-control" id="customMode" name="customMode">
|
||||
<option value="">Custom Page Order</option>
|
||||
<option value="REVERSE_ORDER">Reverse Order</option>
|
||||
<option value="DUPLEX_SORT">Duplex Sort</option>
|
||||
<option value="BOOKLET_SORT">Booklet Sort</option>
|
||||
<option value="SIDE_STITCH_BOOKLET_SORT">Side Stitch Booklet Sort</option>
|
||||
<option value="ODD_EVEN_SPLIT">Odd-Even Split</option>
|
||||
<option value="REMOVE_FIRST">Remove First</option>
|
||||
<option value="REMOVE_LAST">Remove Last</option>
|
||||
<option value="REMOVE_FIRST_AND_LAST">Remove First and Last</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||
<input type="text" class="form-control" id="pageOrder" name="pageNumbers" placeholder="(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)" required>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('customMode').addEventListener('change', function () {
|
||||
var pageOrderInput = document.getElementById('pageOrder');
|
||||
if (this.value === "") {
|
||||
pageOrderInput.disabled = false;
|
||||
} else {
|
||||
pageOrderInput.disabled = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToSinglePage.title}, header=#{pdfToSinglePage.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToSinglePage.title}, header=#{pdfToSinglePage.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pdfToSinglePage.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/general/pdf-to-single-page}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToSinglePage.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<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="#{pdfToSinglePage.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/general/pdf-to-single-page}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToSinglePage.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,254 +1,163 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}"
|
||||
th:lang-direction="#{language.direction}"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block
|
||||
th:insert="~{fragments/common :: head(title=#{pipeline.title}, header=#{pipeline.header})}"></th:block>
|
||||
<style>
|
||||
.btn-margin {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.bordered-box {
|
||||
border: 1px solid #ddd;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.center-element {
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.element-margin {
|
||||
margin: 10px 0;
|
||||
/* Adjust this value to increase/decrease the margin as needed */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script th:inline="javascript">
|
||||
const saveSettings = /*[[#{pipelineOptions.saveSettings}]]*/ '';
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pipeline.title}, header=#{pipeline.header})}"></th:block>
|
||||
<link rel="stylesheet" href="css/pipeline.css" th:if="${currentPage == 'pipeline'}">
|
||||
<script th:inline="javascript">
|
||||
const saveSettings = /*[[#{pipelineOptions.saveSettings}]]*/ '';
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<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">
|
||||
<h1 th:text="#{pipeline.header}"></h1>
|
||||
<h2 th:text="#{WorkInProgess}"></h2>
|
||||
<div class="bordered-box">
|
||||
<div class="text-end text-top">
|
||||
<button id="uploadPipelineBtn" class="btn btn-primary" th:text="#{pipeline.uploadButton}"></button>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#pipelineSettingsModal" th:text="#{pipeline.configureButton}"></button>
|
||||
</div>
|
||||
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="center-element">
|
||||
<div class="element-margin">
|
||||
<select id="pipelineSelect" class="custom-select">
|
||||
<option value="{"name":"Custom","pipeline":[],"_examples":{"outputDir":"{outputFolder}/{folderName}","outputFileName":"{filename}-{pipelineName}-{date}-{time}"},"outputDir":"{outputFolder}","outputFileName":"{filename}"}" th:text="#{pipeline.defaultOption}"></option>
|
||||
<th:block th:each="config : ${pipelineConfigsWithNames}">
|
||||
<option th:value="${config.json}" th:text="${config.name}"></option>
|
||||
</th:block>
|
||||
</select>
|
||||
</div>
|
||||
<div class="element-margin">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true)}"></div>
|
||||
</div>
|
||||
<div class="element-margin">
|
||||
<button class="btn btn-primary" id="submitConfigBtn" th:text="#{pipeline.submitButton}"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 th:text="#{pipeline.header}"></h1>
|
||||
<h2 th:text="#{WorkInProgess}"> </h2>
|
||||
<div class="bordered-box">
|
||||
<div class="text-end text-top">
|
||||
<button id="uploadPipelineBtn" class="btn btn-primary"
|
||||
th:text="#{pipeline.uploadButton}"></button>
|
||||
<button type="button" class="btn btn-primary"
|
||||
data-bs-toggle="modal" data-bs-target="#pipelineSettingsModal"
|
||||
th:text="#{pipeline.configureButton}"></button>
|
||||
<p>Below info is Alpha only, will be removed and hence not translated</p>
|
||||
|
||||
</div>
|
||||
<h3>Current Limitations</h3>
|
||||
|
||||
<div class="center-element">
|
||||
<div class="element-margin">
|
||||
<select id="pipelineSelect" class="custom-select">
|
||||
<option
|
||||
value="{"name":"Custom","pipeline":[],"_examples":{"outputDir":"{outputFolder}/{folderName}","outputFileName":"{filename}-{pipelineName}-{date}-{time}"},"outputDir":"{outputFolder}","outputFileName":"{filename}"}"
|
||||
th:text="#{pipeline.defaultOption}"></option>
|
||||
<ul>
|
||||
<li>Cannot have more than one of the same operation</li>
|
||||
<li>Cannot input additional files via UI</li>
|
||||
<li>All files and operations run in serial mode</li>
|
||||
</ul>
|
||||
|
||||
<h3>How it Works Notes</h3>
|
||||
<ul>
|
||||
<li>Configure the pipeline config file and input files to run files against it</li>
|
||||
<li>For reuse, download the config file and re-upload it when needed, or place it in /pipeline/defaultWebUIConfigs/ to auto-load in the web UI for all users</li>
|
||||
</ul>
|
||||
|
||||
<h3>How to use pre-load configs in web UI</h3>
|
||||
<ul>
|
||||
<li>Download config files</li>
|
||||
<li>For reuse, download the config file and re-upload it when needed, or place it in /pipeline/defaultWebUIConfigs/ to auto-load in the web UI for all users</li>
|
||||
</ul>
|
||||
|
||||
<h3>Todo</h3>
|
||||
<ul>
|
||||
<li>Save to browser/Account</li>
|
||||
<li>offline folder scan mode checks and testing for unique usecases</li>
|
||||
<li>Improve operation config settings UI</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<th:block th:each="config : ${pipelineConfigsWithNames}">
|
||||
<option th:value="${config.json}" th:text="${config.name}"></option>
|
||||
</th:block>
|
||||
</select>
|
||||
</div>
|
||||
<div class="element-margin">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true)}"></div>
|
||||
</div>
|
||||
<div class="element-margin">
|
||||
<button class="btn btn-primary" id="submitConfigBtn"
|
||||
th:text="#{pipeline.submitButton}"></button>
|
||||
<h2>User Guide for Local Directory Scanning and File Processing</h2>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Setting Up Watched Folders:</h3>
|
||||
<p>Create a folder where you want your files to be monitored. This is your 'watched folder'.</p>
|
||||
<p>The default directory for this is <code>./pipeline/watchedFolders/</code></p>
|
||||
<p>Place any directories you want to be scanned into this folder, this folder should contain multiple folders each for their own tasks and pipelines.</p>
|
||||
|
||||
<h3>Configuring Processing with JSON Files:</h3>
|
||||
<p>In each directory you want processed (e.g. <code>./pipeline/watchedFolders/officePrinter</code>), include a JSON configuration file.</p>
|
||||
<p>This JSON file should specify how you want the files in the directory to be handled (e.g., what operations to perform on them) which can be made, configured and downloaded from Stirling-PDF Pipeline interface.</p>
|
||||
|
||||
<p>Below info is Alpha only, will be removed and hence not translated</p>
|
||||
<h3>Current Limitations</h3>
|
||||
<ul>
|
||||
<li>Cannot have more than one of the same operation</li>
|
||||
<li>Cannot input additional files via UI</li>
|
||||
<li>All files and operations run in serial mode</li>
|
||||
</ul>
|
||||
<h3>Automatic Scanning and Processing:</h3>
|
||||
<p>The system automatically checks the watched folder every minute for new directories and files to process.</p>
|
||||
<p>When a directory with a valid JSON configuration file is found, it begins processing the files inside as per the configuration.</p>
|
||||
|
||||
<h3>How it Works Notes</h3>
|
||||
<ul>
|
||||
<li>Configure the pipeline config file and input files to run
|
||||
files against it</li>
|
||||
<li>For reuse, download the config file and re-upload it when
|
||||
needed, or place it in /pipeline/defaultWebUIConfigs/ to
|
||||
auto-load in the web UI for all users</li>
|
||||
</ul>
|
||||
<h3>Processing Steps:</h3>
|
||||
<p>Files in each directory are processed according to the instructions in the JSON file.</p>
|
||||
<p>This might involve file conversions, data filtering, renaming files, etc. If the output of a step is a zip, this zip will be automatically unzipped as it passes to next process.</p>
|
||||
|
||||
<h3>How to use pre-load configs in web UI</h3>
|
||||
<ul>
|
||||
<li>Download config files</li>
|
||||
<li>For reuse, download the config file and re-upload it when
|
||||
needed, or place it in /pipeline/defaultWebUIConfigs/ to
|
||||
auto-load in the web UI for all users</li>
|
||||
</ul>
|
||||
<h3>Results and Output:</h3>
|
||||
<p>After processing, the results are saved in a specified output location. This could be a different folder or location as defined in the JSON file or the default location <code>./pipeline/finishedFolders/</code>.</p>
|
||||
<p>Each processed file is named and organized according to the rules set in the JSON configuration.</p>
|
||||
|
||||
<h3>Todo</h3>
|
||||
<ul>
|
||||
<li>Save to browser/Account</li>
|
||||
<li>offline folder scan mode checks and testing for unique usecases</li>
|
||||
<li>Improve operation config settings UI</li>
|
||||
</ul>
|
||||
<h3>Completion and Cleanup:</h3>
|
||||
<p>Once processing is complete, the original files in the watched folder's directory are removed.</p>
|
||||
<p>You can find the processed files in the designated output location.</p>
|
||||
|
||||
<h3>Error Handling:</h3>
|
||||
<p>If there's an error during processing, the system will not delete the original files, allowing you to check and retry if necessary.</p>
|
||||
|
||||
<h2>User Guide for Local Directory Scanning and File
|
||||
Processing</h2>
|
||||
<h3>User Interaction:</h3>
|
||||
<p>As a user, your main tasks are to set up the watched folders, place directories with files for processing, and create the corresponding JSON configuration files.</p>
|
||||
<p>The system handles the rest, including scanning, processing, and outputting results.</p>
|
||||
|
||||
<h3>Setting Up Watched Folders:</h3>
|
||||
<p>Create a folder where you want your files to be monitored.
|
||||
This is your 'watched folder'.</p>
|
||||
<p>
|
||||
The default directory for this is
|
||||
<code>./pipeline/watchedFolders/</code>
|
||||
</p>
|
||||
<p>Place any directories you want to be scanned into this
|
||||
folder, this folder should contain multiple folders each for their
|
||||
own tasks and pipelines.</p>
|
||||
<!-- The Modal -->
|
||||
<div class="modal" id="pipelineSettingsModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content dark-card">
|
||||
|
||||
<h3>Configuring Processing with JSON Files:</h3>
|
||||
<p>
|
||||
In each directory you want processed (e.g
|
||||
<code>./pipeline/watchedFolders/officePrinter</code>
|
||||
), include a JSON configuration file.
|
||||
</p>
|
||||
<p>This JSON file should specify how you want the files in the
|
||||
directory to be handled (e.g., what operations to perform on them)
|
||||
which can be made, configured and downloaded from Stirling-PDF
|
||||
Pipeline interface.</p>
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" th:text="#{pipelineOptions.header}"></h2>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<h3>Automatic Scanning and Processing:</h3>
|
||||
<p>The system automatically checks the watched folder every
|
||||
minute for new directories and files to process.</p>
|
||||
<p>When a directory with a valid JSON configuration file is
|
||||
found, it begins processing the files inside as per the
|
||||
configuration.</p>
|
||||
<!-- Modal body -->
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="pipelineName" class="form-label" th:text="#{pipelineOptions.pipelineNameLabel}"></label>
|
||||
<input type="text" id="pipelineName" class="form-control" th:placeholder="#{pipelineOptions.pipelineNamePrompt}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="operationsDropdown" th:text="#{pipelineOptions.selectOperation}"></label>
|
||||
<select id="operationsDropdown" class="form-select">
|
||||
<!-- Options will be dynamically populated here -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button id="addOperationBtn" class="btn btn-primary" th:text="#{pipelineOptions.addOperationButton}"></button>
|
||||
</div>
|
||||
<h3 id="pipelineHeader" style="display: none;" th:text="#{pipelineOptions.pipelineHeader}"></h3>
|
||||
|
||||
<h3>Processing Steps:</h3>
|
||||
<p>Files in each directory are processed according to the
|
||||
instructions in the JSON file.</p>
|
||||
<p>This might involve file conversions, data filtering,
|
||||
renaming files, etc. If the output of a step is a zip, this zip
|
||||
will be automatically unzipped as it passes to next process.</p>
|
||||
<ol id="pipelineList" class="list-group">
|
||||
<!-- Pipeline operations will be dynamically populated here -->
|
||||
</ol>
|
||||
<div id="pipelineSettingsContent">
|
||||
<!-- pipelineSettings will be dynamically populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Results and Output:</h3>
|
||||
<p>
|
||||
After processing, the results are saved in a specified output
|
||||
location. This could be a different folder or location as defined
|
||||
in the JSON file or the default location
|
||||
<code>./pipeline/finishedFolders/</code>
|
||||
.
|
||||
</p>
|
||||
<p>Each processed file is named and organized according to the
|
||||
rules set in the JSON configuration.</p>
|
||||
<!-- Modal footer -->
|
||||
<div class="modal-footer">
|
||||
<button id="savePipelineBtn" class="btn btn-success" th:text="#{pipelineOptions.saveButton}"></button>
|
||||
<button id="validateButton" class="btn btn-success" th:text="#{pipelineOptions.validateButton}"></button>
|
||||
|
||||
<h3>Completion and Cleanup:</h3>
|
||||
<p>Once processing is complete, the original files in the
|
||||
watched folder's directory are removed.</p>
|
||||
<p>You can find the processed files in the designated output
|
||||
location.</p>
|
||||
|
||||
<h3>Error Handling:</h3>
|
||||
<p>If there's an error during processing, the system will not
|
||||
delete the original files, allowing you to check and retry if
|
||||
necessary.</p>
|
||||
|
||||
<h3>User Interaction:</h3>
|
||||
<p>As a user, your main tasks are to set up the watched
|
||||
folders, place directories with files for processing, and create
|
||||
the corresponding JSON configuration files.</p>
|
||||
<p>The system handles the rest, including scanning, processing,
|
||||
and outputting results.</p>
|
||||
|
||||
|
||||
<!-- The Modal -->
|
||||
<div class="modal" id="pipelineSettingsModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content dark-card">
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" th:text="#{pipelineOptions.header}"></h2>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<!-- Modal body -->
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="pipelineName" class="form-label"
|
||||
th:text="#{pipelineOptions.pipelineNameLabel}"></label> <input
|
||||
type="text" id="pipelineName" class="form-control"
|
||||
th:placeholder="#{pipelineOptions.pipelineNamePrompt}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="operationsDropdown" th:text="#{pipelineOptions.selectOperation}"></label>
|
||||
<select id="operationsDropdown" class="form-select">
|
||||
<!-- Options will be dynamically populated here -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button id="addOperationBtn" class="btn btn-primary"
|
||||
th:text="#{pipelineOptions.addOperationButton}"></button>
|
||||
|
||||
</div>
|
||||
<h3 id="pipelineHeader" style="display: none;"
|
||||
th:text="#{pipelineOptions.pipelineHeader}"></h3>
|
||||
|
||||
<ol id="pipelineList" class="list-group">
|
||||
<!-- Pipeline operations will be dynamically populated here -->
|
||||
</ol>
|
||||
<div id="pipelineSettingsContent">
|
||||
<!-- pipelineSettings will be dynamically populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal footer -->
|
||||
<div class="modal-footer">
|
||||
<button id="savePipelineBtn" class="btn btn-success"
|
||||
th:text="#{pipelineOptions.saveButton}"></button>
|
||||
<button id="validateButton" class="btn btn-success"
|
||||
th:text="#{pipelineOptions.validateButton}"></button>
|
||||
|
||||
<div class="btn-group">
|
||||
<input type="file" id="uploadPipelineInput" accept=".json"
|
||||
style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/pipeline.js"></script>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<div class="btn-group">
|
||||
<input type="file" id="uploadPipelineInput" accept=".json" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/pipeline.js"></script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageRemover.title}, header=#{pageRemover.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageRemover.title}, header=#{pageRemover.header})}"></th:block>
|
||||
<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">
|
||||
<h2 th:text="#{pageRemover.header}"></h2>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pageRemover.header}"></h2>
|
||||
|
||||
<form th:action="@{api/v1/general/remove-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="pageNumbers" th:text="#{pageRemover.pagesToDelete}"></label>
|
||||
<input type="text" class="form-control" id="fileInput" name="pageNumbers" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
|
||||
</form>
|
||||
<form th:action="@{api/v1/general/remove-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="pageNumbers" th:text="#{pageRemover.pagesToDelete}"></label>
|
||||
<input type="text" class="form-control" id="fileInput" name="pageNumbers" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('fileInput').addEventListener('input', function(){
|
||||
this.value =this.value.replace(/\s+/g, '');;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
<script>
|
||||
document.getElementById('fileInput').addEventListener('input', function(){
|
||||
this.value =this.value.replace(/\s+/g, '');;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,132 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{rotate.title}, header=#{rotate.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{rotate.title}, header=#{rotate.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{rotate.header}"></h2>
|
||||
<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="#{rotate.header}"></h2>
|
||||
|
||||
<form action="#" th:action="@{api/v1/general/rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<input type="hidden" id="angleInput" name="angle" value="0">
|
||||
<form action="#" th:action="@{api/v1/general/rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<input type="hidden" id="angleInput" name="angle" value="0">
|
||||
|
||||
<div id="editSection" style="display: none">
|
||||
<div class="previewContainer">
|
||||
<img id="pdf-preview" />
|
||||
</div>
|
||||
|
||||
<div class="buttonContainer">
|
||||
<button type="button" class="btn btn-secondary" onclick="rotate(-90)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{rotate.submit}"></button>
|
||||
<button type="button" class="btn btn-secondary" onclick="rotate(90)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="editSection" style="display: none">
|
||||
<div class="previewContainer">
|
||||
<img id="pdf-preview">
|
||||
</div>
|
||||
|
||||
<div class="buttonContainer">
|
||||
<button type="button" class="btn btn-secondary" onclick="rotate(-90)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{rotate.submit}"></button>
|
||||
<button type="button" class="btn btn-secondary" onclick="rotate(90)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const angleInput = document.getElementById("angleInput");
|
||||
const fileInput = document.getElementById("fileInput-input");
|
||||
const preview = document.getElementById("pdf-preview");
|
||||
fileInput.addEventListener("change", async function() {
|
||||
console.log("loading pdf");
|
||||
const angleInput = document.getElementById("angleInput");
|
||||
const fileInput = document.getElementById("fileInput-input");
|
||||
const preview = document.getElementById("pdf-preview");
|
||||
fileInput.addEventListener("change", async function() {
|
||||
console.log("loading pdf");
|
||||
|
||||
document.querySelector("#editSection").style.display = "";
|
||||
document.querySelector("#editSection").style.display = "";
|
||||
|
||||
var url = URL.createObjectURL(fileInput.files[0])
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
const pdf = await pdfjsLib.getDocument(url).promise;
|
||||
const page = await pdf.getPage(1);
|
||||
var url = URL.createObjectURL(fileInput.files[0])
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
const pdf = await pdfjsLib.getDocument(url).promise;
|
||||
const page = await pdf.getPage(1);
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const canvas = document.createElement("canvas");
|
||||
|
||||
// set the canvas size to the size of the page
|
||||
if (page.rotate == 90 || page.rotate == 270) {
|
||||
canvas.width = page.view[3];
|
||||
canvas.height = page.view[2];
|
||||
} else {
|
||||
canvas.width = page.view[2];
|
||||
canvas.height = page.view[3];
|
||||
}
|
||||
// set the canvas size to the size of the page
|
||||
if (page.rotate == 90 || page.rotate == 270) {
|
||||
canvas.width = page.view[3];
|
||||
canvas.height = page.view[2];
|
||||
} else {
|
||||
canvas.width = page.view[2];
|
||||
canvas.height = page.view[3];
|
||||
}
|
||||
|
||||
// render the page onto the canvas
|
||||
var renderContext = {
|
||||
canvasContext: canvas.getContext("2d"),
|
||||
viewport: page.getViewport({ scale: 1 })
|
||||
};
|
||||
// render the page onto the canvas
|
||||
var renderContext = {
|
||||
canvasContext: canvas.getContext("2d"),
|
||||
viewport: page.getViewport({ scale: 1 })
|
||||
};
|
||||
|
||||
await page.render(renderContext).promise;
|
||||
preview.src = canvas.toDataURL();
|
||||
});
|
||||
await page.render(renderContext).promise;
|
||||
preview.src = canvas.toDataURL();
|
||||
});
|
||||
|
||||
function rotate(deg) {
|
||||
var lastTransform = preview.style.rotate;
|
||||
if (!lastTransform) {
|
||||
lastTransform = "0";
|
||||
}
|
||||
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
|
||||
const newAngle = lastAngle + deg;
|
||||
function rotate(deg) {
|
||||
var lastTransform = preview.style.rotate;
|
||||
if (!lastTransform) {
|
||||
lastTransform = "0";
|
||||
}
|
||||
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
|
||||
const newAngle = lastAngle + deg;
|
||||
|
||||
preview.style.rotate = newAngle + "deg";
|
||||
angleInput.value = newAngle;
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#pdf-preview {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
max-width: calc(100% - 30px);
|
||||
max-height: calc(100% - 30px);
|
||||
box-shadow: 0 0 4px rgba(100, 100, 100, .25);
|
||||
transition: rotate .3s;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
translate: -50% -50%;
|
||||
}
|
||||
|
||||
.previewContainer {
|
||||
aspect-ratio: 1;
|
||||
width: 100%;
|
||||
border: 1px solid rgba(0, 0, 0, .125);
|
||||
border-radius: 0.25rem;
|
||||
margin: 1rem 0;
|
||||
padding: 15px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
</style>
|
||||
preview.style.rotate = newAngle + "deg";
|
||||
angleInput.value = newAngle;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,47 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{scalePages.title}, header=#{scalePages.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{scalePages.title}, header=#{scalePages.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{scalePages.header}"></h2>
|
||||
<form id="scalePagesFrom" th:action="@{api/v1/general/scale-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="pageSize" th:text="#{scalePages.pageSize}"></label>
|
||||
<select id="pageSize" name="pageSize" required>
|
||||
<option value="A0">A0</option>
|
||||
<option value="A1">A1</option>
|
||||
<option value="A2">A2</option>
|
||||
<option value="A3">A3</option>
|
||||
<option value="A4" selected>A4</option>
|
||||
<option value="A5">A5</option>
|
||||
<option value="A6">A6</option>
|
||||
<option value="LETTER">Letter</option>
|
||||
<option value="LEGAL">Legal</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="scaleFactor" th:text="#{scalePages.scaleFactor}"></label>
|
||||
<input type="number" id="scaleFactor" name="scaleFactor" step="any" min="0" value="1">
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{scalePages.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
<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="#{scalePages.header}"></h2>
|
||||
<form id="scalePagesFrom" th:action="@{api/v1/general/scale-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="pageSize" th:text="#{scalePages.pageSize}"></label>
|
||||
<select id="pageSize" name="pageSize" required>
|
||||
<option value="A0">A0</option>
|
||||
<option value="A1">A1</option>
|
||||
<option value="A2">A2</option>
|
||||
<option value="A3">A3</option>
|
||||
<option value="A4" selected>A4</option>
|
||||
<option value="A5">A5</option>
|
||||
<option value="A6">A6</option>
|
||||
<option value="LETTER">Letter</option>
|
||||
<option value="LEGAL">Legal</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="scaleFactor" th:text="#{scalePages.scaleFactor}"></label>
|
||||
<input type="number" id="scaleFactor" name="scaleFactor" step="any" min="0" value="1">
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{scalePages.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,86 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{addPassword.title}, header=#{addPassword.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{addPassword.title}, header=#{addPassword.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{addPassword.header}"></h2>
|
||||
|
||||
<form action="api/v1/security/add-password" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.14}"></label> <input type="password" class="form-control" id="ownerPassword" name="ownerPassword">
|
||||
<small class="form-text text-muted" th:text="#{addPassword.selectText.15}"></small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password">
|
||||
<small class="form-text text-muted" th:text="#{addPassword.selectText.16}"></small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" id="keyLength" name="keyLength">
|
||||
<option value="40">40</option>
|
||||
<option value="128">128</option>
|
||||
<option value="256">256</option>
|
||||
</select> <small class="form-text text-muted" th:text="#{addPassword.selectText.4}"></small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.5}"></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||
<label class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
|
||||
<label class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
|
||||
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
|
||||
<label class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
|
||||
<label class="form-check-label" for="canModify" th:text="#{addPassword.selectText.10}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
|
||||
<label class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
|
||||
<label class="form-check-label" for="canPrint" th:text="#{addPassword.selectText.12}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
|
||||
<label class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{addPassword.submit}"></button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<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="#{addPassword.header}"></h2>
|
||||
<form action="api/v1/security/add-password" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.14}"></label> <input type="password" class="form-control" id="ownerPassword" name="ownerPassword">
|
||||
<small class="form-text text-muted" th:text="#{addPassword.selectText.15}"></small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password">
|
||||
<small class="form-text text-muted" th:text="#{addPassword.selectText.16}"></small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" id="keyLength" name="keyLength">
|
||||
<option value="40">40</option>
|
||||
<option value="128">128</option>
|
||||
<option value="256">256</option>
|
||||
</select>
|
||||
<small class="form-text text-muted" th:text="#{addPassword.selectText.4}"></small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.5}"></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||
<label class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
|
||||
<label class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
|
||||
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
|
||||
<label class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
|
||||
<label class="form-check-label" for="canModify" th:text="#{addPassword.selectText.10}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
|
||||
<label class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
|
||||
<label class="form-check-label" for="canPrint" th:text="#{addPassword.selectText.12}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
|
||||
<label class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{addPassword.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,144 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{watermark.title}, header=#{watermark.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{watermark.title}, header=#{watermark.header})}"></th:block>
|
||||
|
||||
<body onload="toggleFileOption()">
|
||||
<body onload="toggleFileOption()">
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{watermark.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" action="api/v1/security/add-watermark">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{watermark.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}">
|
||||
<input type="file" id="fileInput" name="fileInput" class="form-control-file" accept="application/pdf" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label th:text="#{watermark.selectText.8}"></label>
|
||||
<select class="form-control" id="watermarkType" name="watermarkType" onchange="toggleFileOption()">
|
||||
<option value="text">Text</option>
|
||||
<option value="image">Image</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="alphabetGroup" class="mb-3">
|
||||
<label for="fontSize" th:text="#{alphabet} + ':'"></label>
|
||||
<select class="form-control" name="alphabet" id="alphabet-select">
|
||||
<option value="roman">Roman</option>
|
||||
<option value="arabic">العربية</option>
|
||||
<option value="japanese">日本語</option>
|
||||
<option value="korean">한국어</option>
|
||||
<option value="chinese">简体中文</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="watermarkTextGroup" class="mb-3">
|
||||
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||
</div>
|
||||
|
||||
<div id="watermarkImageGroup" class="mb-3" style="display: none;">
|
||||
<label for="watermarkImage" th:text="#{watermark.selectText.9}"></label>
|
||||
<input type="file" id="watermarkImage" name="watermarkImage" class="form-control-file" accept="image/*" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="fontSize" th:text="#{watermark.selectText.3}"></label>
|
||||
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="opacity" th:text="#{watermark.selectText.7}"></label>
|
||||
<input type="text" id="opacity" name="opacityText" class="form-control" value="50" onblur="updateOpacityValue()" />
|
||||
<input type="hidden" id="opacityReal" name="opacity" value="0.5">
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
const opacityInput = document.getElementById('opacity');
|
||||
const opacityRealInput = document.getElementById('opacityReal');
|
||||
|
||||
const updateOpacityValue = () => {
|
||||
let percentageValue = parseFloat(opacityInput.value.replace('%', ''));
|
||||
if (isNaN(percentageValue)) {
|
||||
percentageValue = 0;
|
||||
}
|
||||
percentageValue = Math.min(Math.max(percentageValue, 0), 100);
|
||||
opacityInput.value = `${percentageValue}`;
|
||||
opacityRealInput.value = (percentageValue / 100).toFixed(2);
|
||||
};
|
||||
|
||||
const appendPercentageSymbol = () => {
|
||||
if (!opacityInput.value.endsWith('%')) {
|
||||
opacityInput.value += '%';
|
||||
}
|
||||
};
|
||||
|
||||
opacityInput.addEventListener('focus', () => {
|
||||
opacityInput.value = opacityInput.value.replace('%', '');
|
||||
});
|
||||
opacityInput.addEventListener('blur', () => {
|
||||
updateOpacityValue();
|
||||
appendPercentageSymbol();
|
||||
});
|
||||
|
||||
// Set initial values
|
||||
updateOpacityValue();
|
||||
appendPercentageSymbol();
|
||||
</script>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="rotation" th:text="#{watermark.selectText.4}"></label>
|
||||
<input type="text" id="rotation" name="rotation" class="form-control" value="45" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
|
||||
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
|
||||
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50" />
|
||||
</div>
|
||||
<div class="mb-3 text-center">
|
||||
<input type="submit" id="submitBtn" th:value="#{watermark.submit}" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function toggleFileOption() {
|
||||
const watermarkType = document.getElementById('watermarkType').value;
|
||||
const watermarkTextGroup = document.getElementById('watermarkTextGroup');
|
||||
const watermarkImageGroup = document.getElementById('watermarkImageGroup');
|
||||
const alphabetGroup = document.getElementById('alphabetGroup'); // This is the new addition
|
||||
const watermarkText = document.getElementById('watermarkText');
|
||||
const watermarkImage = document.getElementById('watermarkImage');
|
||||
|
||||
if (watermarkType === 'text') {
|
||||
watermarkTextGroup.style.display = 'block';
|
||||
watermarkText.required = true;
|
||||
watermarkImageGroup.style.display = 'none';
|
||||
watermarkImage.required = false;
|
||||
alphabetGroup.style.display = 'block';
|
||||
} else if (watermarkType === 'image') {
|
||||
watermarkTextGroup.style.display = 'none';
|
||||
watermarkText.required = false;
|
||||
watermarkImageGroup.style.display = 'block';
|
||||
watermarkImage.required = true;
|
||||
alphabetGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<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="#{watermark.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" action="api/v1/security/add-watermark">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{watermark.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}">
|
||||
<input type="file" id="fileInput" name="fileInput" class="form-control-file" accept="application/pdf" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label th:text="#{watermark.selectText.8}"></label>
|
||||
<select class="form-control" id="watermarkType" name="watermarkType" onchange="toggleFileOption()">
|
||||
<option value="text">Text</option>
|
||||
<option value="image">Image</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="alphabetGroup" class="mb-3">
|
||||
<label for="fontSize" th:text="#{alphabet} + ':'"></label>
|
||||
<select class="form-control" name="alphabet" id="alphabet-select">
|
||||
<option value="roman">Roman</option>
|
||||
<option value="arabic">العربية</option>
|
||||
<option value="japanese">日本語</option>
|
||||
<option value="korean">한국어</option>
|
||||
<option value="chinese">简体中文</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="watermarkTextGroup" class="mb-3">
|
||||
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||
</div>
|
||||
|
||||
<div id="watermarkImageGroup" class="mb-3" style="display: none;">
|
||||
<label for="watermarkImage" th:text="#{watermark.selectText.9}"></label>
|
||||
<input type="file" id="watermarkImage" name="watermarkImage" class="form-control-file" accept="image/*" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="fontSize" th:text="#{watermark.selectText.3}"></label>
|
||||
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="opacity" th:text="#{watermark.selectText.7}"></label>
|
||||
<input type="text" id="opacity" name="opacityText" class="form-control" value="50" onblur="updateOpacityValue()" />
|
||||
<input type="hidden" id="opacityReal" name="opacity" value="0.5">
|
||||
</div>
|
||||
<script>
|
||||
const opacityInput = document.getElementById('opacity');
|
||||
const opacityRealInput = document.getElementById('opacityReal');
|
||||
|
||||
const updateOpacityValue = () => {
|
||||
let percentageValue = parseFloat(opacityInput.value.replace('%', ''));
|
||||
if (isNaN(percentageValue)) {
|
||||
percentageValue = 0;
|
||||
}
|
||||
percentageValue = Math.min(Math.max(percentageValue, 0), 100);
|
||||
opacityInput.value = `${percentageValue}`;
|
||||
opacityRealInput.value = (percentageValue / 100).toFixed(2);
|
||||
};
|
||||
|
||||
const appendPercentageSymbol = () => {
|
||||
if (!opacityInput.value.endsWith('%')) {
|
||||
opacityInput.value += '%';
|
||||
}
|
||||
};
|
||||
|
||||
opacityInput.addEventListener('focus', () => {
|
||||
opacityInput.value = opacityInput.value.replace('%', '');
|
||||
});
|
||||
opacityInput.addEventListener('blur', () => {
|
||||
updateOpacityValue();
|
||||
appendPercentageSymbol();
|
||||
});
|
||||
|
||||
// Set initial values
|
||||
updateOpacityValue();
|
||||
appendPercentageSymbol();
|
||||
</script>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="rotation" th:text="#{watermark.selectText.4}"></label>
|
||||
<input type="text" id="rotation" name="rotation" class="form-control" value="45" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
|
||||
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
|
||||
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50" />
|
||||
</div>
|
||||
<div class="mb-3 text-center">
|
||||
<input type="submit" id="submitBtn" th:value="#{watermark.submit}" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function toggleFileOption() {
|
||||
const watermarkType = document.getElementById('watermarkType').value;
|
||||
const watermarkTextGroup = document.getElementById('watermarkTextGroup');
|
||||
const watermarkImageGroup = document.getElementById('watermarkImageGroup');
|
||||
const alphabetGroup = document.getElementById('alphabetGroup'); // This is the new addition
|
||||
const watermarkText = document.getElementById('watermarkText');
|
||||
const watermarkImage = document.getElementById('watermarkImage');
|
||||
|
||||
if (watermarkType === 'text') {
|
||||
watermarkTextGroup.style.display = 'block';
|
||||
watermarkText.required = true;
|
||||
watermarkImageGroup.style.display = 'none';
|
||||
watermarkImage.required = false;
|
||||
alphabetGroup.style.display = 'block';
|
||||
} else if (watermarkType === 'image') {
|
||||
watermarkTextGroup.style.display = 'none';
|
||||
watermarkText.required = false;
|
||||
watermarkImageGroup.style.display = 'block';
|
||||
watermarkImage.required = true;
|
||||
alphabetGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,84 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title}, header=#{autoRedact.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title}, header=#{autoRedact.header})}"></th:block>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{autoRedact.header}"></h2>
|
||||
<form action="api/v1/security/auto-redact" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control" id="fileInput" name="fileInput" required accept="application/pdf">
|
||||
</div>
|
||||
<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">
|
||||
<h2 th:text="#{autoRedact.header}"></h2>
|
||||
<form action="api/v1/security/auto-redact" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control" id="fileInput" name="fileInput" required accept="application/pdf">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="listOfText" class="form-label" th:text="#{autoRedact.textsToRedactLabel}"></label>
|
||||
<textarea class="form-control" id="listOfText" name="listOfText" rows="4" required th:placeholder="#{autoRedact.textsToRedactPlaceholder}"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="listOfText" class="form-label" th:text="#{autoRedact.textsToRedactLabel}"></label>
|
||||
<textarea class="form-control" id="listOfText" name="listOfText" rows="4" required th:placeholder="#{autoRedact.textsToRedactPlaceholder}"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="defaultColor" class="form-label" th:text="#{autoRedact.colorLabel}">Color</label>
|
||||
<select class="form-control" id="defaultColor" name="defaultColor" onchange="handleColorChange(this.value)">
|
||||
<option value="#000000" th:text="#{black}">Black</option>
|
||||
<option value="#FFFFFF" th:text="#{white}">White</option>
|
||||
<option value="#FF0000" th:text="#{red}">Red</option>
|
||||
<option value="#00FF00" th:text="#{green}">Green</option>
|
||||
<option value="#0000FF" th:text="#{blue}">Blue</option>
|
||||
<option value="custom" th:text="#{custom}">Custom...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="defaultColor" class="form-label" th:text="#{autoRedact.colorLabel}">Color</label>
|
||||
<select class="form-control" id="defaultColor" name="defaultColor" onchange="handleColorChange(this.value)">
|
||||
<option value="#000000" th:text="#{black}">Black</option>
|
||||
<option value="#FFFFFF" th:text="#{white}">White</option>
|
||||
<option value="#FF0000" th:text="#{red}">Red</option>
|
||||
<option value="#00FF00" th:text="#{green}">Green</option>
|
||||
<option value="#0000FF" th:text="#{blue}">Blue</option>
|
||||
<option value="custom" th:text="#{custom}">Custom...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Custom Color Input -->
|
||||
<div class="mb-3" id="customColorContainer" style="display: none;">
|
||||
<label for="customColor" class="form-label" th:text="#{autoRedact.colorLabel}">Custom Color</label>
|
||||
<input type="text" class="form-control" id="customColor" name="redactColor" placeholder="#FF00FF">
|
||||
</div>
|
||||
<!-- Custom Color Input -->
|
||||
<div class="mb-3" id="customColorContainer" style="display: none;">
|
||||
<label for="customColor" class="form-label" th:text="#{autoRedact.colorLabel}">Custom Color</label>
|
||||
<input type="text" class="form-control" id="customColor" name="redactColor" placeholder="#FF00FF">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function handleColorChange(selectedValue) {
|
||||
if (selectedValue === "custom") {
|
||||
document.getElementById('customColorContainer').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('customColorContainer').style.display = 'none';
|
||||
document.getElementById('customColor').value = selectedValue;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function handleColorChange(selectedValue) {
|
||||
if (selectedValue === "custom") {
|
||||
document.getElementById('customColorContainer').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('customColorContainer').style.display = 'none';
|
||||
document.getElementById('customColor').value = selectedValue;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="useRegex" name="useRegex">
|
||||
<label class="form-check-label" for="useRegex" th:text="#{autoRedact.useRegexLabel}"></label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="wholeWordSearch" name="wholeWordSearch">
|
||||
<label class="form-check-label" for="wholeWordSearch" th:text="#{autoRedact.wholeWordSearchLabel}"></label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="customPadding" class="form-label" th:text="#{autoRedact.customPaddingLabel}"></label>
|
||||
<input type="number" step="0.1" class="form-control" id="customPadding" name="customPadding" value="0.1">
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="useRegex" name="useRegex">
|
||||
<label class="form-check-label" for="useRegex" th:text="#{autoRedact.useRegexLabel}"></label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="convertPDFToImage" name="convertPDFToImage" checked>
|
||||
<label class="form-check-label" for="convertPDFToImage" th:text="#{autoRedact.convertPDFToImageLabel}"></label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="wholeWordSearch" name="wholeWordSearch">
|
||||
<label class="form-check-label" for="wholeWordSearch" th:text="#{autoRedact.wholeWordSearchLabel}"></label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="customPadding" class="form-label" th:text="#{autoRedact.customPaddingLabel}"></label>
|
||||
<input type="number" step="0.1" class="form-control" id="customPadding" name="customPadding" value="0.1">
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="convertPDFToImage" name="convertPDFToImage" checked>
|
||||
<label class="form-check-label" for="convertPDFToImage" th:text="#{autoRedact.convertPDFToImageLabel}"></label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" th:text="#{autoRedact.submitButton}"></button>
|
||||
</form>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{autoRedact.submitButton}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,113 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{certSign.title}, header=#{certSign.header})}"></th:block>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{certSign.title}, header=#{certSign.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{certSign.header}"></h2>
|
||||
<form action="api/v1/security/cert-sign" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectPDF}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<!-- Tell users to use keytool to generate JKS for other formats -->
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.jksNote}"></label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="certType" th:text="#{certSign.certType}"></label> <select class="form-control" id="certType" name="certType">
|
||||
<option value="" th:text="#{selectFillter}"></option>
|
||||
<option value="PEM">PEM</option>
|
||||
<option value="PKCS12">PKCS12</option>
|
||||
<option value="JKS">JKS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="pemGroup" style="display: none;">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectKey}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='privateKeyFile', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectCert}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='certFile', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3" id="p12Group" style="display: none;">
|
||||
<label th:text="#{certSign.selectP12}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='p12File', notRequired=true, multiple=false, accept='.p12,.pfx')}"></div>
|
||||
</div>
|
||||
<div class="mb-3" id="jksGroup" style="display: none;">
|
||||
<label th:text="#{certSign.selectJKS}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='jksFile', notRequired=true, multiple=false, accept='.jks,.keystore')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.password}"></label> <input type="password" class="form-control" id="password" name="password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label><input type="checkbox" id="showSignature" name="showSignature" th:text="#{certSign.showSig}"></label>
|
||||
</div>
|
||||
<div id="signatureDetails" style="display: none;">
|
||||
<div class="mb-3">
|
||||
<label for="reason" th:text="#{certSign.reason}"></label> <input type="text" class="form-control" id="reason" name="reason">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="location" th:text="#{certSign.location}"></label> <input type="text" class="form-control" id="location" name="location">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="name" th:text="#{certSign.name}"></label> <input type="text" class="form-control" id="name" name="name">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pageNumber" th:text="#{pageNum}"></label> <input type="number" class="form-control" id="pageNumber" name="pageNumber" min="1" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{certSign.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<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="#{certSign.header}"></h2>
|
||||
<form action="api/v1/security/cert-sign" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectPDF}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<!-- Tell users to use keytool to generate JKS for other formats -->
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.jksNote}"></label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="certType" th:text="#{certSign.certType}"></label> <select class="form-control" id="certType" name="certType">
|
||||
<option value="" th:text="#{selectFillter}"></option>
|
||||
<option value="PEM">PEM</option>
|
||||
<option value="PKCS12">PKCS12</option>
|
||||
<option value="JKS">JKS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="pemGroup" style="display: none;">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectKey}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='privateKeyFile', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectCert}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='certFile', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3" id="p12Group" style="display: none;">
|
||||
<label th:text="#{certSign.selectP12}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='p12File', notRequired=true, multiple=false, accept='.p12,.pfx')}"></div>
|
||||
</div>
|
||||
<div class="mb-3" id="jksGroup" style="display: none;">
|
||||
<label th:text="#{certSign.selectJKS}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='jksFile', notRequired=true, multiple=false, accept='.jks,.keystore')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.password}"></label> <input type="password" class="form-control" id="password" name="password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label><input type="checkbox" id="showSignature" name="showSignature" th:text="#{certSign.showSig}"></label>
|
||||
</div>
|
||||
<div id="signatureDetails" style="display: none;">
|
||||
<div class="mb-3">
|
||||
<label for="reason" th:text="#{certSign.reason}"></label> <input type="text" class="form-control" id="reason" name="reason">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="location" th:text="#{certSign.location}"></label> <input type="text" class="form-control" id="location" name="location">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="name" th:text="#{certSign.name}"></label> <input type="text" class="form-control" id="name" name="name">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pageNumber" th:text="#{pageNum}"></label> <input type="number" class="form-control" id="pageNumber" name="pageNumber" min="1" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{certSign.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document
|
||||
.getElementById('certType')
|
||||
.addEventListener(
|
||||
'change',
|
||||
function() {
|
||||
var pemGroup = document.getElementById('pemGroup');
|
||||
var p12Group = document.getElementById('p12Group');
|
||||
var jksGroup = document.getElementById('jksGroup');
|
||||
var valueToGroupMap = {
|
||||
'PEM': pemGroup,
|
||||
'PKCS12': p12Group,
|
||||
'JKS': jksGroup
|
||||
};
|
||||
for (var key in valueToGroupMap) {
|
||||
valueToGroupMap[key].style.display = (this.value === key) ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
document.getElementById('certType').addEventListener('change', function() {
|
||||
var pemGroup = document.getElementById('pemGroup');
|
||||
var p12Group = document.getElementById('p12Group');
|
||||
var jksGroup = document.getElementById('jksGroup');
|
||||
var valueToGroupMap = {
|
||||
'PEM': pemGroup,
|
||||
'PKCS12': p12Group,
|
||||
'JKS': jksGroup
|
||||
};
|
||||
for (var key in valueToGroupMap) {
|
||||
valueToGroupMap[key].style.display = (this.value === key) ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById('showSignature')
|
||||
.addEventListener(
|
||||
'change',
|
||||
function() {
|
||||
var signatureDetails = document.getElementById('signatureDetails');
|
||||
if (this.checked) {
|
||||
signatureDetails.style.display = 'block';
|
||||
} else {
|
||||
signatureDetails.style.display = 'none';
|
||||
}
|
||||
});
|
||||
document.getElementById('showSignature').addEventListener('change', function() {
|
||||
var signatureDetails = document.getElementById('signatureDetails');
|
||||
if (this.checked) {
|
||||
signatureDetails.style.display = 'block';
|
||||
} else {
|
||||
signatureDetails.style.display = 'none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,71 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{permissions.title}, header=#{permissions.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{permissions.title}, header=#{permissions.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{permissions.header}"></h2>
|
||||
<p th:text="#{permissions.warning}"></p>
|
||||
<form action="api/v1/security/add-password" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{permissions.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{permissions.selectText.2}"></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||
<label class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
|
||||
<label class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
|
||||
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
|
||||
<label class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
|
||||
<label class="form-check-label" for="canModify" th:text="#{permissions.selectText.7}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
|
||||
<label class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
|
||||
<label class="form-check-label" for="canPrint" th:text="#{permissions.selectText.9}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
|
||||
<label class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{permissions.submit}"></button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<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="#{permissions.header}"></h2>
|
||||
<p th:text="#{permissions.warning}"></p>
|
||||
<form action="api/v1/security/add-password" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{permissions.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{permissions.selectText.2}"></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||
<label class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
|
||||
<label class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
|
||||
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
|
||||
<label class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
|
||||
<label class="form-check-label" for="canModify" th:text="#{permissions.selectText.7}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
|
||||
<label class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
|
||||
<label class="form-check-label" for="canPrint" th:text="#{permissions.selectText.9}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
|
||||
<label class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{permissions.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,165 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}"
|
||||
th:lang-direction="#{language.direction}"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<th:block
|
||||
th:insert="~{fragments/common :: head(title=#{getPdfInfo.title}, header=#{getPdfInfo.header})}"></th:block>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{getPdfInfo.header}"></h2>
|
||||
<form id="pdfInfoForm" method="post" enctype="multipart/form-data"
|
||||
th:action="@{api/v1/security/get-info-on-pdf}">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, remoteCall='false', accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary"
|
||||
th:text="#{getPdfInfo.submit}"></button>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{getPdfInfo.title}, header=#{getPdfInfo.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
</form>
|
||||
<div class="container mt-5">
|
||||
<!-- Iterate over each main section in the JSON -->
|
||||
<div id="json-content">
|
||||
<!-- JavaScript will populate this section -->
|
||||
</div>
|
||||
<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">
|
||||
<h2 th:text="#{getPdfInfo.header}"></h2>
|
||||
<form id="pdfInfoForm" method="post" enctype="multipart/form-data" th:action="@{api/v1/security/get-info-on-pdf}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, remoteCall='false', accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{getPdfInfo.submit}"></button>
|
||||
</form>
|
||||
<div class="container mt-5">
|
||||
<!-- Iterate over each main section in the JSON -->
|
||||
<div id="json-content">
|
||||
<!-- JavaScript will populate this section -->
|
||||
</div>
|
||||
|
||||
<!-- Button to download the JSON -->
|
||||
<a href="#" id="downloadJson" class="btn btn-primary mt-3"
|
||||
style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download
|
||||
JSON</a>
|
||||
</div>
|
||||
<script>
|
||||
<!-- Button to download the JSON -->
|
||||
<a href="#" id="downloadJson" class="btn btn-primary mt-3" style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download JSON</a>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.target);
|
||||
|
||||
const formData = new FormData(event.target);
|
||||
fetch('api/v1/security/get-info-on-pdf', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => response.json()).then(data => {
|
||||
displayJsonData(data);
|
||||
setDownloadLink(data);
|
||||
document.getElementById("downloadJson").style.display = "block";
|
||||
}).catch(error => console.error('Error:', error));
|
||||
});
|
||||
|
||||
fetch('api/v1/security/get-info-on-pdf', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
displayJsonData(data);
|
||||
setDownloadLink(data);
|
||||
document.getElementById("downloadJson").style.display = "block";
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
});
|
||||
function displayJsonData(jsonData) {
|
||||
const jsonContent = document.getElementById('json-content');
|
||||
|
||||
function displayJsonData(jsonData) {
|
||||
const jsonContent = document.getElementById('json-content');
|
||||
while (jsonContent.firstChild) {
|
||||
jsonContent.removeChild(jsonContent.firstChild);
|
||||
}
|
||||
|
||||
while (jsonContent.firstChild) {
|
||||
jsonContent.removeChild(jsonContent.firstChild);
|
||||
}
|
||||
for (const key in jsonData) {
|
||||
const sectionElem = createJsonSection(key, jsonData[key]);
|
||||
jsonContent.appendChild(sectionElem);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in jsonData) {
|
||||
const sectionElem = createJsonSection(key, jsonData[key]);
|
||||
jsonContent.appendChild(sectionElem);
|
||||
}
|
||||
}
|
||||
function setDownloadLink(jsonData) {
|
||||
const downloadLink = document.getElementById('downloadJson');
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData, null, 4));
|
||||
downloadLink.setAttribute("href", dataStr);
|
||||
downloadLink.setAttribute("download", "data.json");
|
||||
}
|
||||
|
||||
function setDownloadLink(jsonData) {
|
||||
const downloadLink = document.getElementById('downloadJson');
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData, null, 4));
|
||||
downloadLink.setAttribute("href", dataStr);
|
||||
downloadLink.setAttribute("download", "data.json");
|
||||
}
|
||||
function createJsonSection(key, value, depth = 0) {
|
||||
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card mb-3';
|
||||
|
||||
function createJsonSection(key, value, depth = 0) {
|
||||
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card mb-3';
|
||||
const header = document.createElement('div');
|
||||
header.className = 'card-header';
|
||||
header.id = `${safeKey}-heading-${depth}`;
|
||||
const h5Elem = document.createElement('h5');
|
||||
h5Elem.className = 'mb-0';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'card-header';
|
||||
header.id = `${safeKey}-heading-${depth}`;
|
||||
const h5Elem = document.createElement('h5');
|
||||
h5Elem.className = 'mb-0';
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||
h5Elem.appendChild(buttonElem);
|
||||
} else if (value && typeof value === 'object') {
|
||||
if (Array.isArray(value) && value.length === 0) {
|
||||
h5Elem.textContent = `${key}: Empty array`;
|
||||
} else if (!Array.isArray(value) && Object.keys(value).length === 0) {
|
||||
h5Elem.textContent = `${key}: Empty object`;
|
||||
} else {
|
||||
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||
h5Elem.appendChild(buttonElem);
|
||||
}
|
||||
} else {
|
||||
h5Elem.textContent = `${key}: ${String(value)}`;
|
||||
}
|
||||
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||
h5Elem.appendChild(buttonElem);
|
||||
} else if (value && typeof value === 'object') {
|
||||
if (Array.isArray(value) && value.length === 0) {
|
||||
h5Elem.textContent = `${key}: Empty array`;
|
||||
} else if (!Array.isArray(value) && Object.keys(value).length === 0) {
|
||||
h5Elem.textContent = `${key}: Empty object`;
|
||||
} else {
|
||||
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||
h5Elem.appendChild(buttonElem);
|
||||
}
|
||||
} else {
|
||||
h5Elem.textContent = `${key}: ${String(value)}`;
|
||||
}
|
||||
header.appendChild(h5Elem);
|
||||
card.appendChild(header);
|
||||
|
||||
header.appendChild(h5Elem);
|
||||
card.appendChild(header);
|
||||
const content = document.createElement('div');
|
||||
content.id = `${safeKey}-content-${depth}`;
|
||||
content.className = 'collapse';
|
||||
content.setAttribute('aria-labelledby', `${safeKey}-heading-${depth}`);
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.id = `${safeKey}-content-${depth}`;
|
||||
content.className = 'collapse';
|
||||
content.setAttribute('aria-labelledby', `${safeKey}-heading-${depth}`);
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
const preElem = document.createElement('pre');
|
||||
preElem.textContent = value; // Not escaping since we're using textContent
|
||||
body.appendChild(preElem);
|
||||
content.appendChild(body);
|
||||
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
for (const subKey in value) {
|
||||
const subElem = createJsonSection(subKey, value[subKey], depth + 1);
|
||||
body.appendChild(subElem);
|
||||
}
|
||||
content.appendChild(body);
|
||||
} else if (value && typeof value === 'object' && Array.isArray(value)) {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
value.forEach((val, index) => {
|
||||
const subElem = createJsonSection(`${key}[${index}]`, val, depth + 1);
|
||||
body.appendChild(subElem);
|
||||
});
|
||||
content.appendChild(body);
|
||||
}
|
||||
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
const preElem = document.createElement('pre');
|
||||
preElem.textContent = value; // Not escaping since we're using textContent
|
||||
body.appendChild(preElem);
|
||||
content.appendChild(body);
|
||||
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
for (const subKey in value) {
|
||||
const subElem = createJsonSection(subKey, value[subKey], depth + 1);
|
||||
body.appendChild(subElem);
|
||||
}
|
||||
content.appendChild(body);
|
||||
} else if (value && typeof value === 'object' && Array.isArray(value)) {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
value.forEach((val, index) => {
|
||||
const subElem = createJsonSection(`${key}[${index}]`, val, depth + 1);
|
||||
body.appendChild(subElem);
|
||||
});
|
||||
content.appendChild(body);
|
||||
}
|
||||
|
||||
card.appendChild(content);
|
||||
return card;
|
||||
}
|
||||
|
||||
|
||||
function createButtonElement(key, safeKey, depth) {
|
||||
const buttonElem = document.createElement('button');
|
||||
buttonElem.className = 'btn btn-link';
|
||||
buttonElem.type = 'button';
|
||||
buttonElem.dataset.bsToggle = "collapse";
|
||||
buttonElem.dataset.bsTarget = `#${safeKey}-content-${depth}`;
|
||||
buttonElem.setAttribute('aria-expanded', 'true');
|
||||
buttonElem.setAttribute('aria-controls', `${safeKey}-content-${depth}`);
|
||||
buttonElem.textContent = key;
|
||||
return buttonElem;
|
||||
}
|
||||
const collapsibleElems = document.querySelectorAll('[data-bs-toggle="collapse"]');
|
||||
collapsibleElems.forEach(elem => {
|
||||
new bootstrap.Collapse(elem);
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
card.appendChild(content);
|
||||
return card;
|
||||
}
|
||||
function createButtonElement(key, safeKey, depth) {
|
||||
const buttonElem = document.createElement('button');
|
||||
buttonElem.className = 'btn btn-link';
|
||||
buttonElem.type = 'button';
|
||||
buttonElem.dataset.bsToggle = "collapse";
|
||||
buttonElem.dataset.bsTarget = `#${safeKey}-content-${depth}`;
|
||||
buttonElem.setAttribute('aria-expanded', 'true');
|
||||
buttonElem.setAttribute('aria-controls', `${safeKey}-content-${depth}`);
|
||||
buttonElem.textContent = key;
|
||||
return buttonElem;
|
||||
}
|
||||
const collapsibleElems = document.querySelectorAll('[data-bs-toggle="collapse"]');
|
||||
collapsibleElems.forEach(elem => {
|
||||
new bootstrap.Collapse(elem);
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,37 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{removePassword.title}, header=#{removePassword.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{removePassword.title}, header=#{removePassword.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{removePassword.header}"></h2>
|
||||
|
||||
<form action="api/v1/security/remove-password" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{removePassword.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{removePassword.selectText.2}"></label>
|
||||
<input type="password" class="form-control" id="password" name="password">
|
||||
</div>
|
||||
<br />
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{removePassword.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<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="#{removePassword.header}"></h2>
|
||||
<form action="api/v1/security/remove-password" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{removePassword.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{removePassword.selectText.2}"></label>
|
||||
<input type="password" class="form-control" id="password" name="password">
|
||||
</div>
|
||||
<br>
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{removePassword.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{remove-watermark.title}, header=#{remove-watermark.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{remove-watermark.title}, header=#{remove-watermark.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{remove-watermark.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" action="api/v1/security/remove-watermark">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{remove-watermark.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="watermarkText" th:text="#{remove-watermark.selectText.2}"></label>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||
</div>
|
||||
<div class="mb-3 text-center">
|
||||
<input type="submit" id="submitBtn" th:value="#{remove-watermark.submit}" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<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="#{remove-watermark.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" action="api/v1/security/remove-watermark">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{remove-watermark.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="watermarkText" th:text="#{remove-watermark.selectText.2}"></label>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||
</div>
|
||||
<div class="mb-3 text-center">
|
||||
<input type="submit" id="submitBtn" th:value="#{remove-watermark.submit}" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,53 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{sanitizePDF.title}, header=#{sanitizePDF.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{sanitizePDF.title}, header=#{sanitizePDF.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{sanitizePDF.header}"></h2>
|
||||
|
||||
<form action="api/v1/security/sanitize-pdf" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeJavaScript" name="removeJavaScript" checked>
|
||||
<label class="form-check-label" for="removeJavaScript" th:text="#{sanitizePDF.selectText.1}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeEmbeddedFiles" name="removeEmbeddedFiles" checked>
|
||||
<label class="form-check-label" for="removeEmbeddedFiles" th:text="#{sanitizePDF.selectText.2}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeMetadata" name="removeMetadata" checked>
|
||||
<label class="form-check-label" for="removeMetadata" th:text="#{sanitizePDF.selectText.3}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeLinks" name="removeLinks">
|
||||
<label class="form-check-label" for="removeLinks" th:text="#{sanitizePDF.selectText.4}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeFonts" name="removeFonts">
|
||||
<label class="form-check-label" for="removeFonts" th:text="#{sanitizePDF.selectText.5}"></label>
|
||||
</div>
|
||||
<br />
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{sanitizePDF.submit}"></button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<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="#{sanitizePDF.header}"></h2>
|
||||
<form action="api/v1/security/sanitize-pdf" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeJavaScript" name="removeJavaScript" checked>
|
||||
<label class="form-check-label" for="removeJavaScript" th:text="#{sanitizePDF.selectText.1}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeEmbeddedFiles" name="removeEmbeddedFiles" checked>
|
||||
<label class="form-check-label" for="removeEmbeddedFiles" th:text="#{sanitizePDF.selectText.2}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeMetadata" name="removeMetadata" checked>
|
||||
<label class="form-check-label" for="removeMetadata" th:text="#{sanitizePDF.selectText.3}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeLinks" name="removeLinks">
|
||||
<label class="form-check-label" for="removeLinks" th:text="#{sanitizePDF.selectText.4}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="removeFonts" name="removeFonts">
|
||||
<label class="form-check-label" for="removeFonts" th:text="#{sanitizePDF.selectText.5}"></label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{sanitizePDF.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,333 +1,276 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{sign.title}, header=#{sign.header})}"></th:block>
|
||||
<link rel="stylesheet" href="css/sign.css">
|
||||
<th:block th:each="font : ${fonts}">
|
||||
<style th:inline="text">
|
||||
@font-face {
|
||||
font-family: "[[${font.name}]]";
|
||||
src: url('fonts/[[${font.name}]].[[${font.extension}]]') format('[[${font.type}]]');
|
||||
}
|
||||
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{sign.title}, header=#{sign.header})}"></th:block>
|
||||
#font-select option[value="[[${font.name}]]"] {
|
||||
font-family: "[[${font.name}]]", cursive;
|
||||
}
|
||||
#font-select option[value='/*[[${font.name}]]*/'] {
|
||||
font-family: '/*[[${font.name}]]*/', cursive;
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
<script src="js/thirdParty/signature_pad.umd.min.js"></script>
|
||||
<script src="js/thirdParty/interact.min.js"></script>
|
||||
</head>
|
||||
|
||||
</head>
|
||||
<th:block th:each="font : ${fonts}">
|
||||
<style th:inline="text">
|
||||
@font-face {
|
||||
font-family: "[[${font.name}]]";
|
||||
src: url('fonts/[[${font.name}]].[[${font.extension}]]') format('[[${font.type}]]');
|
||||
}
|
||||
|
||||
#font-select option[value="[[${font.name}]]"] {
|
||||
font-family: "[[${font.name}]]", cursive;
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
|
||||
<style>
|
||||
select#font-select, select#font-select option {
|
||||
height: 60px; /* Adjust as needed */
|
||||
font-size: 30px; /* Adjust as needed */
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{sign.header}"></h2>
|
||||
<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="#{sign.header}"></h2>
|
||||
|
||||
<!-- pdf selector -->
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
|
||||
<script>
|
||||
let originalFileName = '';
|
||||
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
originalFileName = file.name.replace(/\.[^/.]+$/, "");
|
||||
const pdfData = await file.arrayBuffer();
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||
await DraggableUtils.renderPage(pdfDoc, 0);
|
||||
<!-- pdf selector -->
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
|
||||
<script>
|
||||
let originalFileName = '';
|
||||
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
originalFileName = file.name.replace(/\.[^/.]+$/, "");
|
||||
const pdfData = await file.arrayBuffer();
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
|
||||
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||
await DraggableUtils.renderPage(pdfDoc, 0);
|
||||
|
||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||
el.style.cssText = '';
|
||||
})
|
||||
}
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||
el.style.cssText = "display:none !important";
|
||||
})
|
||||
});
|
||||
</script>
|
||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||
el.style.cssText = '';
|
||||
})
|
||||
}
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||
el.style.cssText = "display:none !important";
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<div class="tab-group show-on-file-selected">
|
||||
<div class="tab-container" th:title="#{sign.upload}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||
<script>
|
||||
const imageUpload = document.querySelector('input[name=image-upload]');
|
||||
imageUpload.addEventListener('change', e => {
|
||||
if(!e.target.files) {
|
||||
return;
|
||||
}
|
||||
for (const imageFile of e.target.files) {
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(imageFile);
|
||||
reader.onloadend = function (e) {
|
||||
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="tab-container drawing-pad-container" th:title="#{sign.draw}">
|
||||
<canvas id="drawing-pad-canvas"></canvas>
|
||||
<br>
|
||||
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()" th:text="#{sign.clear}"></button>
|
||||
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()" th:text="#{sign.add}"></button>
|
||||
<script>
|
||||
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
|
||||
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
||||
minWidth: 1,
|
||||
maxWidth: 2,
|
||||
penColor: 'black',
|
||||
});
|
||||
function addDraggableFromPad() {
|
||||
if (signaturePad.isEmpty()) return;
|
||||
const startTime = Date.now();
|
||||
const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas)
|
||||
console.log(Date.now() - startTime);
|
||||
DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
|
||||
}
|
||||
function getCroppedCanvasDataUrl(canvas) {
|
||||
// code is from: https://github.com/szimek/signature_pad/issues/49#issuecomment-1104035775
|
||||
let originalCtx = canvas.getContext('2d');
|
||||
|
||||
<div class="tab-group show-on-file-selected">
|
||||
<div class="tab-container" th:title="#{sign.upload}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||
<script>
|
||||
const imageUpload = document.querySelector('input[name=image-upload]');
|
||||
imageUpload.addEventListener('change', e => {
|
||||
if(!e.target.files) {
|
||||
return;
|
||||
}
|
||||
for (const imageFile of e.target.files) {
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(imageFile);
|
||||
reader.onloadend = function (e) {
|
||||
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="tab-container drawing-pad-container" th:title="#{sign.draw}">
|
||||
<canvas id="drawing-pad-canvas"></canvas>
|
||||
<br>
|
||||
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()" th:text="#{sign.clear}"></button>
|
||||
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()" th:text="#{sign.add}"></button>
|
||||
<script>
|
||||
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
|
||||
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
||||
minWidth: 1,
|
||||
maxWidth: 2,
|
||||
penColor: 'black',
|
||||
});
|
||||
function addDraggableFromPad() {
|
||||
if (signaturePad.isEmpty()) return;
|
||||
const startTime = Date.now();
|
||||
const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas)
|
||||
console.log(Date.now() - startTime);
|
||||
DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
|
||||
}
|
||||
function getCroppedCanvasDataUrl(canvas) {
|
||||
// code is from: https://github.com/szimek/signature_pad/issues/49#issuecomment-1104035775
|
||||
let originalCtx = canvas.getContext('2d');
|
||||
let originalWidth = canvas.width;
|
||||
let originalHeight = canvas.height;
|
||||
let imageData = originalCtx.getImageData(0,0, originalWidth, originalHeight);
|
||||
|
||||
let originalWidth = canvas.width;
|
||||
let originalHeight = canvas.height;
|
||||
let imageData = originalCtx.getImageData(0,0, originalWidth, originalHeight);
|
||||
let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;
|
||||
|
||||
let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;
|
||||
for (y = 0; y < originalHeight; y++) {
|
||||
for (x = 0; x < originalWidth; x++) {
|
||||
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
||||
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
|
||||
if (currentPixelAlphaValue > 0) {
|
||||
if (minX > x) minX = x;
|
||||
if (maxX < x) maxX = x;
|
||||
if (minY > y) minY = y;
|
||||
if (maxY < y) maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (y = 0; y < originalHeight; y++) {
|
||||
for (x = 0; x < originalWidth; x++) {
|
||||
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
||||
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
|
||||
if (currentPixelAlphaValue > 0) {
|
||||
if (minX > x) minX = x;
|
||||
if (maxX < x) maxX = x;
|
||||
if (minY > y) minY = y;
|
||||
if (maxY < y) maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
let croppedWidth = maxX - minX;
|
||||
let croppedHeight = maxY - minY;
|
||||
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
||||
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
||||
|
||||
let croppedWidth = maxX - minX;
|
||||
let croppedHeight = maxY - minY;
|
||||
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
||||
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
||||
let croppedCanvas = document.createElement('canvas'),
|
||||
croppedCtx = croppedCanvas.getContext('2d');
|
||||
|
||||
let croppedCanvas = document.createElement('canvas'),
|
||||
croppedCtx = croppedCanvas.getContext('2d');
|
||||
croppedCanvas.width = croppedWidth;
|
||||
croppedCanvas.height = croppedHeight;
|
||||
croppedCtx.putImageData(cuttedImageData, 0, 0);
|
||||
|
||||
croppedCanvas.width = croppedWidth;
|
||||
croppedCanvas.height = croppedHeight;
|
||||
croppedCtx.putImageData(cuttedImageData, 0, 0);
|
||||
return croppedCanvas.toDataURL();
|
||||
}
|
||||
function resizeCanvas() {
|
||||
// When zoomed out to less than 100%, for some very strange reason,
|
||||
// some browsers report devicePixelRatio as less than 1
|
||||
// and only part of the canvas is cleared then.
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
var additionalFactor = 10;
|
||||
|
||||
return croppedCanvas.toDataURL();
|
||||
}
|
||||
function resizeCanvas() {
|
||||
// When zoomed out to less than 100%, for some very strange reason,
|
||||
// some browsers report devicePixelRatio as less than 1
|
||||
// and only part of the canvas is cleared then.
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
var additionalFactor = 10;
|
||||
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
|
||||
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
|
||||
signaturePadCanvas.getContext("2d").scale(ratio * additionalFactor, ratio * additionalFactor);
|
||||
|
||||
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
|
||||
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
|
||||
signaturePadCanvas.getContext("2d").scale(ratio * additionalFactor, ratio * additionalFactor);
|
||||
|
||||
|
||||
// This library does not listen for canvas changes, so after the canvas is automatically
|
||||
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
|
||||
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
|
||||
// that the state of this library is consistent with visual state of the canvas, you
|
||||
// have to clear it manually.
|
||||
signaturePad.clear();
|
||||
}
|
||||
new IntersectionObserver((entries, observer) => {
|
||||
if (entries.some(entry => entry.intersectionRatio > 0)) {
|
||||
resizeCanvas();
|
||||
}
|
||||
}).observe(signaturePadCanvas);
|
||||
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
||||
</script>
|
||||
<style>
|
||||
.drawing-pad-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#drawing-pad-canvas {
|
||||
background: rgba(125,125,125,0.2);
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<div class="tab-container" th:title="#{sign.text}">
|
||||
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
|
||||
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
|
||||
<label th:text="#{font}"></label>
|
||||
<select class="form-control" name="font" id="font-select">
|
||||
<option th:each="font : ${fonts}"
|
||||
th:value="${font.name}"
|
||||
th:text="${font.name}"
|
||||
th:class="${font.name.toLowerCase()+'-font'}">
|
||||
</option>
|
||||
</select>
|
||||
<div class="margin-auto-parent">
|
||||
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center" onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
|
||||
</div>
|
||||
<script>
|
||||
function addDraggableFromText() {
|
||||
const sigText = document.getElementById('sigText').value;
|
||||
const font = document.querySelector('select[name=font]').value;
|
||||
const fontSize = 100;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.font = `${fontSize}px ${font}`;
|
||||
const textWidth = ctx.measureText(sigText).width;
|
||||
const textHeight = fontSize;
|
||||
|
||||
let paragraphs = sigText.split(/\r?\n/);
|
||||
|
||||
canvas.width = textWidth;
|
||||
canvas.height = paragraphs.length * textHeight*1.35; //for tails
|
||||
ctx.font = `${fontSize}px ${font}`;
|
||||
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
let y = 0;
|
||||
|
||||
paragraphs.forEach(paragraph => {
|
||||
ctx.fillText(paragraph, 0, y);
|
||||
y += fontSize;
|
||||
});
|
||||
|
||||
const dataURL = canvas.toDataURL();
|
||||
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
const sigTextInput = document.getElementById('sigText');
|
||||
const fontSelect = document.getElementById('font-select');
|
||||
|
||||
const updateOptionTexts = () => {
|
||||
Array.from(fontSelect.options).forEach(option => {
|
||||
const fontName = option.value.replace(/-regular$/i, '');
|
||||
option.text = sigTextInput.value || fontName;
|
||||
});
|
||||
}
|
||||
|
||||
sigTextInput.addEventListener('input', updateOptionTexts);
|
||||
|
||||
fontSelect.addEventListener('change', (e) => {
|
||||
e.target.style.fontFamily = e.target.value;
|
||||
updateOptionTexts();
|
||||
});
|
||||
|
||||
// Manually trigger the change event
|
||||
fontSelect.dispatchEvent(new Event('change'));
|
||||
</script>
|
||||
|
||||
|
||||
<th:block th:each="font : ${fonts}">
|
||||
<style th:inline="text">
|
||||
#font-select option[value='/*[[${font.name}]]*/'] {
|
||||
font-family: '/*[[${font.name}]]*/', cursive;
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- draggables box -->
|
||||
<div id="box-drag-container" class="show-on-file-selected">
|
||||
<canvas id="pdf-canvas"></canvas>
|
||||
<script src="js/draggable-utils.js"></script>
|
||||
<div class="draggable-buttons-box ignore-rtl">
|
||||
<button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
|
||||
<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" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto">
|
||||
<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" onclick="document.documentElement.getAttribute('lang-direction')==='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>
|
||||
</div>
|
||||
<style>
|
||||
#box-drag-container {
|
||||
position: relative;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.draggable-buttons-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
.draggable-buttons-box > button {
|
||||
z-index: 10;
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
}
|
||||
.draggable-canvas {
|
||||
border: 1px solid red;
|
||||
position: absolute;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
|
||||
<!-- download button -->
|
||||
<div class="margin-auto-parent">
|
||||
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center">Download PDF</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById("download-pdf").addEventListener('click', async() => {
|
||||
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
||||
const modifiedPdfBytes = await modifiedPdf.save();
|
||||
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = originalFileName + '_signed.pdf';
|
||||
link.click();
|
||||
});
|
||||
</script>
|
||||
// This library does not listen for canvas changes, so after the canvas is automatically
|
||||
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
|
||||
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
|
||||
// that the state of this library is consistent with visual state of the canvas, you
|
||||
// have to clear it manually.
|
||||
signaturePad.clear();
|
||||
}
|
||||
new IntersectionObserver((entries, observer) => {
|
||||
if (entries.some(entry => entry.intersectionRatio > 0)) {
|
||||
resizeCanvas();
|
||||
}
|
||||
}).observe(signaturePadCanvas);
|
||||
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
||||
</script>
|
||||
</div>
|
||||
<div class="tab-container" th:title="#{sign.text}">
|
||||
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
|
||||
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
|
||||
<label th:text="#{font}"></label>
|
||||
<select class="form-control" name="font" id="font-select">
|
||||
<option th:each="font : ${fonts}"
|
||||
th:value="${font.name}"
|
||||
th:text="${font.name}"
|
||||
th:class="${font.name.toLowerCase()+'-font'}">
|
||||
</option>
|
||||
</select>
|
||||
<div class="margin-auto-parent">
|
||||
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center" onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
|
||||
</div>
|
||||
<script>
|
||||
function addDraggableFromText() {
|
||||
const sigText = document.getElementById('sigText').value;
|
||||
const font = document.querySelector('select[name=font]').value;
|
||||
const fontSize = 100;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.font = `${fontSize}px ${font}`;
|
||||
const textWidth = ctx.measureText(sigText).width;
|
||||
const textHeight = fontSize;
|
||||
|
||||
let paragraphs = sigText.split(/\r?\n/);
|
||||
|
||||
canvas.width = textWidth;
|
||||
canvas.height = paragraphs.length * textHeight*1.35; //for tails
|
||||
ctx.font = `${fontSize}px ${font}`;
|
||||
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
let y = 0;
|
||||
|
||||
paragraphs.forEach(paragraph => {
|
||||
ctx.fillText(paragraph, 0, y);
|
||||
y += fontSize;
|
||||
});
|
||||
|
||||
const dataURL = canvas.toDataURL();
|
||||
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
const sigTextInput = document.getElementById('sigText');
|
||||
const fontSelect = document.getElementById('font-select');
|
||||
|
||||
const updateOptionTexts = () => {
|
||||
Array.from(fontSelect.options).forEach(option => {
|
||||
const fontName = option.value.replace(/-regular$/i, '');
|
||||
option.text = sigTextInput.value || fontName;
|
||||
});
|
||||
}
|
||||
|
||||
sigTextInput.addEventListener('input', updateOptionTexts);
|
||||
|
||||
fontSelect.addEventListener('change', (e) => {
|
||||
e.target.style.fontFamily = e.target.value;
|
||||
updateOptionTexts();
|
||||
});
|
||||
|
||||
// Manually trigger the change event
|
||||
fontSelect.dispatchEvent(new Event('change'));
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- draggables box -->
|
||||
<div id="box-drag-container" class="show-on-file-selected">
|
||||
<canvas id="pdf-canvas"></canvas>
|
||||
<script src="js/draggable-utils.js"></script>
|
||||
<div class="draggable-buttons-box ignore-rtl">
|
||||
<button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
|
||||
<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" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto">
|
||||
<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" onclick="document.documentElement.getAttribute('lang-direction')==='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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- download button -->
|
||||
<div class="margin-auto-parent">
|
||||
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center">Download PDF</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById("download-pdf").addEventListener('click', async() => {
|
||||
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
||||
const modifiedPdfBytes = await modifiedPdf.save();
|
||||
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = originalFileName + '_signed.pdf';
|
||||
link.click();
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,43 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{split-by-size-or-count.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{/api/v1/general/split-by-size-or-count}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<label for="splitType" th:text="#{split-by-size-or-count.type.label}">Split Type</label>
|
||||
<select id="splitType" name="splitType" class="form-control">
|
||||
<option value="0" th:text="#{split-by-size-or-count.type.size}">Size</option>
|
||||
<option value="1" th:text="#{split-by-size-or-count.type.pageCount}">Page Count</option>
|
||||
<option value="2" th:text="#{split-by-size-or-count.type.docCount}">Document Count</option>
|
||||
</select>
|
||||
<br>
|
||||
|
||||
<label for="splitValue" th:text="#{split-by-size-or-count.value.label}">Split Value</label>
|
||||
<input type="text" id="splitValue" name="splitValue" class="form-control" required th:placeholder="#{split-by-size-or-count.value.placeholder}">
|
||||
<br>
|
||||
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split-by-size-or-count.submit}">Submit</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<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="#{split-by-size-or-count.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{/api/v1/general/split-by-size-or-count}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<label for="splitType" th:text="#{split-by-size-or-count.type.label}">Split Type</label>
|
||||
<select id="splitType" name="splitType" class="form-control">
|
||||
<option value="0" th:text="#{split-by-size-or-count.type.size}">Size</option>
|
||||
<option value="1" th:text="#{split-by-size-or-count.type.pageCount}">Page Count</option>
|
||||
<option value="2" th:text="#{split-by-size-or-count.type.docCount}">Document Count</option>
|
||||
</select>
|
||||
<br>
|
||||
<label for="splitValue" th:text="#{split-by-size-or-count.value.label}">Split Value</label>
|
||||
<input type="text" id="splitValue" name="splitValue" class="form-control" required th:placeholder="#{split-by-size-or-count.value.placeholder}">
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split-by-size-or-count.submit}">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,95 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split-by-sections.title}, header=#{split-by-sections.header})}"></th:block>
|
||||
<link rel="stylesheet" href="css/split-pdf-by-sections.css">
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split-by-sections.title}, header=#{split-by-sections.header})}"></th:block>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{split-by-sections.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{/api/v1/general/split-pdf-by-sections}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<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="#{split-by-sections.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{/api/v1/general/split-pdf-by-sections}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<label for="horizontalDivisions" th:text="#{split-by-sections.horizontal.label}">Horizontal Divisions</label>
|
||||
<input type="number" id="horizontalDivisions" name="horizontalDivisions" class="form-control" min="0" max="300" value="0" required th:placeholder="#{split-by-sections.horizontal.placeholder}">
|
||||
<br>
|
||||
<label for="verticalDivisions" th:text="#{split-by-sections.vertical.label}">Vertical Divisions</label>
|
||||
<input type="number" id="verticalDivisions" name="verticalDivisions" class="form-control" min="0" max="300" required value="1" th:placeholder="#{split-by-sections.vertical.placeholder}">
|
||||
<br>
|
||||
<div id="pdfVisualAid" class="pdf-visual-aid"></div>
|
||||
<script>
|
||||
function updateVisualAid() {
|
||||
const horizontalDivisions = document.getElementById('horizontalDivisions').value;
|
||||
const verticalDivisions = document.getElementById('verticalDivisions').value;
|
||||
const aid = document.getElementById('pdfVisualAid');
|
||||
|
||||
<label for="horizontalDivisions" th:text="#{split-by-sections.horizontal.label}">Horizontal Divisions</label>
|
||||
<input type="number" id="horizontalDivisions" name="horizontalDivisions" class="form-control" min="0" max="300" value="0" required th:placeholder="#{split-by-sections.horizontal.placeholder}">
|
||||
<br>
|
||||
if(horizontalDivisions > 300)
|
||||
horizontalDivisions = 300
|
||||
if(verticalDivisions > 300)
|
||||
verticalDivisions = 300
|
||||
// Clear existing lines
|
||||
aid.innerHTML = '';
|
||||
|
||||
<label for="verticalDivisions" th:text="#{split-by-sections.vertical.label}">Vertical Divisions</label>
|
||||
<input type="number" id="verticalDivisions" name="verticalDivisions" class="form-control" min="0" max="300" required value="1" th:placeholder="#{split-by-sections.vertical.placeholder}">
|
||||
<br>
|
||||
<style>
|
||||
.pdf-visual-aid {
|
||||
width: 150px; /* Adjust as needed */
|
||||
height: 200px; /* Adjust as needed */
|
||||
border: 1px solid black; /* Represents the PDF page */
|
||||
position: relative;
|
||||
}
|
||||
.line {
|
||||
position: absolute;
|
||||
background-color: red; /* Line color */
|
||||
}
|
||||
// Add horizontal lines
|
||||
for (let i = 0; i < horizontalDivisions; i++) {
|
||||
const line = document.createElement('div');
|
||||
line.classList.add('line');
|
||||
line.style.width = '100%';
|
||||
line.style.height = '1px';
|
||||
line.style.top = `${((i + 1) / (parseInt(horizontalDivisions) + 1)) * 100}%`;
|
||||
aid.appendChild(line);
|
||||
}
|
||||
|
||||
</style>
|
||||
// Add vertical lines
|
||||
for (let i = 0; i < verticalDivisions; i++) {
|
||||
const line = document.createElement('div');
|
||||
line.classList.add('line');
|
||||
line.style.height = '100%';
|
||||
line.style.width = '1px';
|
||||
line.style.left = `${((i + 1) / (parseInt(verticalDivisions) + 1)) * 100}%`;
|
||||
aid.appendChild(line);
|
||||
}
|
||||
}
|
||||
|
||||
<div id="pdfVisualAid" class="pdf-visual-aid"></div>
|
||||
<Script>
|
||||
function updateVisualAid() {
|
||||
const horizontalDivisions = document.getElementById('horizontalDivisions').value;
|
||||
const verticalDivisions = document.getElementById('verticalDivisions').value;
|
||||
const aid = document.getElementById('pdfVisualAid');
|
||||
// Event listeners
|
||||
document.getElementById('horizontalDivisions').addEventListener('input', updateVisualAid);
|
||||
document.getElementById('verticalDivisions').addEventListener('input', updateVisualAid);
|
||||
|
||||
if(horizontalDivisions > 300)
|
||||
horizontalDivisions = 300
|
||||
if(verticalDivisions > 300)
|
||||
verticalDivisions = 300
|
||||
// Clear existing lines
|
||||
aid.innerHTML = '';
|
||||
|
||||
// Add horizontal lines
|
||||
for (let i = 0; i < horizontalDivisions; i++) {
|
||||
const line = document.createElement('div');
|
||||
line.classList.add('line');
|
||||
line.style.width = '100%';
|
||||
line.style.height = '1px';
|
||||
line.style.top = `${((i + 1) / (parseInt(horizontalDivisions) + 1)) * 100}%`;
|
||||
aid.appendChild(line);
|
||||
}
|
||||
|
||||
// Add vertical lines
|
||||
for (let i = 0; i < verticalDivisions; i++) {
|
||||
const line = document.createElement('div');
|
||||
line.classList.add('line');
|
||||
line.style.height = '100%';
|
||||
line.style.width = '1px';
|
||||
line.style.left = `${((i + 1) / (parseInt(verticalDivisions) + 1)) * 100}%`;
|
||||
aid.appendChild(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.getElementById('horizontalDivisions').addEventListener('input', updateVisualAid);
|
||||
document.getElementById('verticalDivisions').addEventListener('input', updateVisualAid);
|
||||
|
||||
// Initial draw
|
||||
updateVisualAid();
|
||||
|
||||
</Script>
|
||||
</br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split-by-sections.submit}">Submit</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
// Initial draw
|
||||
updateVisualAid();
|
||||
</script>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split-by-sections.submit}">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,43 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split.title}, header=#{split.header})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split.title}, header=#{split.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h1 th:text="#{split.header}"></h1>
|
||||
<p th:text="#{split.desc.1}"></p>
|
||||
<p th:text="#{split.desc.2}"></p>
|
||||
<p th:text="#{split.desc.3}"></p>
|
||||
<p th:text="#{split.desc.4}"></p>
|
||||
<p th:text="#{split.desc.5}"></p>
|
||||
<p th:text="#{split.desc.6}"></p>
|
||||
<p th:text="#{split.desc.7}"></p>
|
||||
<p th:text="#{split.desc.8}"></p>
|
||||
<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">
|
||||
<h1 th:text="#{split.header}"></h1>
|
||||
<p th:text="#{split.desc.1}"></p>
|
||||
<p th:text="#{split.desc.2}"></p>
|
||||
<p th:text="#{split.desc.3}"></p>
|
||||
<p th:text="#{split.desc.4}"></p>
|
||||
<p th:text="#{split.desc.5}"></p>
|
||||
<p th:text="#{split.desc.6}"></p>
|
||||
<p th:text="#{split.desc.7}"></p>
|
||||
<p th:text="#{split.desc.8}"></p>
|
||||
|
||||
<form th:action="@{api/v1/general/split-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<form th:action="@{api/v1/general/split-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="pages" th:text="#{split.splitPages}"></label>
|
||||
<input type="text" class="form-control" id="pageNumbers" name="pageNumbers" placeholder="1,3,5-10" required>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pages" th:text="#{split.splitPages}"></label>
|
||||
<input type="text" class="form-control" id="pageNumbers" name="pageNumbers" placeholder="1,3,5-10" required>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user