mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-12 10:35:03 +00:00
merging changes from main
This commit is contained in:
parent
a681d039f0
commit
a5479e76d7
1
.gitignore
vendored
1
.gitignore
vendored
@ -197,4 +197,3 @@ id_ed25519.pub
|
||||
|
||||
# node_modules
|
||||
node_modules/
|
||||
*.mjs
|
||||
|
1
common/.gitignore
vendored
1
common/.gitignore
vendored
@ -194,4 +194,3 @@ id_ed25519.pub
|
||||
|
||||
# node_modules
|
||||
node_modules/
|
||||
*.mjs
|
||||
|
1
proprietary/.gitignore
vendored
1
proprietary/.gitignore
vendored
@ -194,4 +194,3 @@ id_ed25519.pub
|
||||
|
||||
# node_modules
|
||||
node_modules/
|
||||
*.mjs
|
||||
|
5
stirling-pdf/.gitignore
vendored
5
stirling-pdf/.gitignore
vendored
@ -123,8 +123,8 @@ SwaggerDoc.json
|
||||
*.tar.gz
|
||||
*.rar
|
||||
*.db
|
||||
/build
|
||||
/stirling-pdf/build/
|
||||
/build/*
|
||||
/stirling-pdf/build/*
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
@ -194,4 +194,3 @@ id_ed25519.pub
|
||||
|
||||
# node_modules
|
||||
node_modules/
|
||||
*.mjs
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,328 +0,0 @@
|
||||
<th:block th:fragment="head">
|
||||
<!-- 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">
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" th:href="@{'/apple-touch-icon.png'}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" th:href="@{'/favicon-32x32.png'}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" th:href="@{'/favicon-16x16.png'}">
|
||||
<link rel="manifest" th:href="@{'/site.webmanifest'}" crossorigin="use-credentials">
|
||||
<link rel="mask-icon" th:href="@{'/safari-pinned-tab.svg'}" color="#ca2b2a">
|
||||
<link rel="shortcut icon" th:href="@{'/favicon.ico'}">
|
||||
<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">
|
||||
|
||||
<script>
|
||||
window.stirlingPDF = window.stirlingPDF || {};
|
||||
</script>
|
||||
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
||||
<!-- jQuery -->
|
||||
<script th:src="@{'/js/thirdParty/jquery.min.js'}"></script>
|
||||
<script th:src="@{'/js/thirdParty/jquery.validate.min.js'}"></script>
|
||||
<script th:src="@{'/js/thirdParty/jszip.min.js'}" th:if="${currentPage != 'home'}"></script>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<script th:src="@{'/js/thirdParty/popper.min.js'}"></script>
|
||||
<script th:src="@{'/js/thirdParty/bootstrap.min.js'}"></script>
|
||||
|
||||
<link rel="stylesheet" th:href="@{'/css/bootstrap.min.css'}">
|
||||
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" th:href="@{'/css/bootstrap-icons.min.css'}">
|
||||
|
||||
<!-- Pixel, doesn't collect any PII-->
|
||||
<img th:if="${!@disablePixel}" referrerpolicy="no-referrer-when-downgrade"
|
||||
th:src="'https://pixel.stirlingpdf.com/a.png?x-pxid=4f5fa02f-a065-4efb-bb2c-24509a4b6b92'
|
||||
+ '&machineType=' + ${@machineType}
|
||||
+ '&appVersion=' + ${@appVersion}
|
||||
+ '&licenseType=' + ${@license}
|
||||
+ '&loginEnabled=' + ${@loginEnabled}"
|
||||
style="position: absolute; visibility: hidden;" />
|
||||
|
||||
<!-- Custom -->
|
||||
<link rel="stylesheet" th:href="@{'/css/general.css'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/theme/theme.css'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/theme/componentes.css'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/theme/theme.light.css'}" id="light-mode-styles">
|
||||
<link rel="stylesheet" th:href="@{'/css/theme/theme.dark.css'}" id="dark-mode-styles">
|
||||
<link rel="stylesheet" th:href="@{'/css/rainbow-mode.css'}" id="rainbow-mode-styles" disabled>
|
||||
<link rel="stylesheet" th:href="@{'/css/tab-container.css'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/navbar.css'}">
|
||||
|
||||
<link rel="stylesheet" th:href="@{'/css/error.css'}" th:if="${error}">
|
||||
|
||||
<link rel="stylesheet" th:href="@{'/css/home.css'}" th:if="${currentPage == 'home'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/home-legacy.css'}" th:if="${currentPage == 'home-legacy'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/account.css'}" th:if="${currentPage == 'account'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/licenses.css'}" th:if="${currentPage == 'licenses'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/multi-tool.css'}" th:if="${currentPage == 'multi-tool'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/rotate-pdf.css'}" th:if="${currentPage == 'rotate-pdf'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/stamp.css'}" th:if="${currentPage == 'stamp'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/fileSelect.css'}" th:if="${currentPage != 'home'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/footer.css'}">
|
||||
|
||||
<link rel="preload" th:href="@{'/fonts/google-symbol.woff2'}" as="font" type="font/woff2" crossorigin="anonymous">
|
||||
<link rel="stylesheet" th:href="@{'/css/cookieconsent.css'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/cookieconsentCustomisation.css'}">
|
||||
<script th:src="@{'/js/thirdParty/fontfaceobserver.standalone.js'}"></script>
|
||||
|
||||
<!-- Google MD Icons -->
|
||||
<link rel="stylesheet" th:href="@{'/css/theme/font.css'}">
|
||||
|
||||
<!-- Help Modal -->
|
||||
<link rel="stylesheet" th:href="@{'/css/errorBanner.css'}" th:if="${currentPage != 'home'}">
|
||||
|
||||
<script th:src="@{'/js/cacheFormInputs.js'}" th:if="${currentPage != 'home'}"></script>
|
||||
<script th:src="@{'/js/tab-container.js'}"></script>
|
||||
<script th:src="@{'/js/darkmode.js'}"></script>
|
||||
<script th:src="@{'/js/csrf.js'}"></script>
|
||||
<script th:inline="javascript">
|
||||
|
||||
function UpdatePosthogConsent(){
|
||||
if(typeof(posthog) == "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
window.CookieConsent.acceptedCategory('analytics')?
|
||||
posthog.opt_in_capturing() : posthog.opt_out_capturing();
|
||||
}
|
||||
const stirlingPDFLabel = /*[[${@StirlingPDFLabel}]]*/ '';
|
||||
const analyticsEnabled = /*[[${@analyticsEnabled}]]*/ false;
|
||||
|
||||
const cookieBannerPopUpTitle = /*[[#{cookieBanner.popUp.title}]]*/ "How we use Cookies";
|
||||
const cookieBannerPopUpDescription1 = /*[[#{cookieBanner.popUp.description.1}]]*/ "";
|
||||
const cookieBannerPopUpDescription2 = /*[[#{cookieBanner.popUp.description.2}]]*/ "";
|
||||
const cookieBannerPopUpAcceptAllBtn = /*[[#{cookieBanner.popUp.acceptAllBtn}]]*/ "";
|
||||
const cookieBannerPopUpAcceptNecessaryBtn = /*[[#{cookieBanner.popUp.acceptNecessaryBtn}]]*/ "";
|
||||
const cookieBannerPopUpShowPreferencesBtn = /*[[#{cookieBanner.popUp.showPreferencesBtn}]]*/ "";
|
||||
const cookieBannerPreferencesModalTitle = /*[[#{cookieBanner.preferencesModal.title}]]*/ "";
|
||||
const cookieBannerPreferencesModalAcceptAllBtn = /*[[#{cookieBanner.preferencesModal.acceptAllBtn}]]*/ "";
|
||||
const cookieBannerPreferencesModalAcceptNecessaryBtn = /*[[#{cookieBanner.preferencesModal.acceptNecessaryBtn}]]*/ "";
|
||||
const cookieBannerPreferencesModalSavePreferencesBtn = /*[[#{cookieBanner.preferencesModal.savePreferencesBtn}]]*/ "";
|
||||
const cookieBannerPreferencesModalCloseIconLabel = /*[[#{cookieBanner.preferencesModal.closeIconLabel}]]*/ "";
|
||||
const cookieBannerPreferencesModalServiceCounterLabel = /*[[#{cookieBanner.preferencesModal.serviceCounterLabel}]]*/ "";
|
||||
const cookieBannerPreferencesModalSubtitle = /*[[#{cookieBanner.preferencesModal.subtitle}]]*/ "";
|
||||
const cookieBannerPreferencesModalDescription1 = /*[[#{cookieBanner.preferencesModal.description.1}]]*/ "";
|
||||
const cookieBannerPreferencesModalDescription2= /*[[#{cookieBanner.preferencesModal.description.2}]]*/ "";
|
||||
const cookieBannerPreferencesModalDescription3 = /*[[#{cookieBanner.preferencesModal.description.3}]]*/ "";
|
||||
const cookieBannerPreferencesModalNecessaryTitle1 = /*[[#{cookieBanner.preferencesModal.necessary.title.1}]]*/ "";
|
||||
const cookieBannerPreferencesModalNecessaryTitle2 = /*[[#{cookieBanner.preferencesModal.necessary.title.2}]]*/ "";
|
||||
const cookieBannerPreferencesModalNecessaryDescription = /*[[#{cookieBanner.preferencesModal.necessary.description}]]*/ "";
|
||||
const cookieBannerPreferencesModalAnalyticsTitle = /*[[#{cookieBanner.preferencesModal.analytics.title}]]*/ "";
|
||||
const cookieBannerPreferencesModalAnalyticsDescription = /*[[#{cookieBanner.preferencesModal.analytics.description}]]*/ "";
|
||||
|
||||
if (analyticsEnabled) {
|
||||
!function (t, e) {
|
||||
var o, n, p, r;
|
||||
e.__SV || (window.posthog = e, e._i = [], e.init = function (i, s, a) {
|
||||
function g(t, e) {
|
||||
var o = e.split(".");
|
||||
2 == o.length && (t = t[o[0]], e = o[1]), t[e] = function () {
|
||||
t.push([e].concat(Array.prototype.slice.call(arguments, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
(p = t.createElement("script")).type = "text/javascript", p.async = !0, p.src = s.api_host + "/static/array.js", (r = t.getElementsByTagName("script")[0]).parentNode.insertBefore(p, r);
|
||||
var u = e;
|
||||
for (void 0 !== a ? u = e[a] = [] : a = "posthog", u.people = u.people || [], u.toString = function (t) {
|
||||
var e = "posthog";
|
||||
return "posthog" !== a && (e += "." + a), t || (e += " (stub)"), e
|
||||
}, u.people.toString = function () {
|
||||
return u.toString(1) + ".people (stub)"
|
||||
}, o = "capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId".split(" "), n = 0; n < o.length; n++) g(u, o[n]);
|
||||
e._i.push([i, s, a])
|
||||
}, e.__SV = 1)
|
||||
}(document, window.posthog || []);
|
||||
posthog.init('phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq', {
|
||||
api_host: 'https://eu.i.posthog.com',
|
||||
persistence: 'localStorage',
|
||||
person_profiles: 'always',
|
||||
autocapture: false,
|
||||
mask_all_text: true,
|
||||
mask_all_element_attributes: true,
|
||||
opt_out_capturing_by_default: true
|
||||
})
|
||||
const baseUrl = window.location.hostname;
|
||||
posthog.register_once({
|
||||
'hostname': baseUrl,
|
||||
'UUID': /*[[${@UUID}]]*/ '',
|
||||
'app_version': /*[[${@appVersion}]]*/ '',
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("cc:onConsent", UpdatePosthogConsent);
|
||||
window.addEventListener("cc:onChange", UpdatePosthogConsent);
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
<th:block th:fragment="game">
|
||||
<dialog id="game-container-wrapper" class="game-container-wrapper" data-bs-modal>
|
||||
<script th:inline="javascript">
|
||||
console.log("loaded game");
|
||||
$(document).ready(function () {
|
||||
|
||||
// Find the file input within the form
|
||||
var fileInput = $('input[type="file"]');
|
||||
|
||||
// Find the closest enclosing form of the file input
|
||||
var form = fileInput.closest('form');
|
||||
|
||||
// Find the submit button within the form
|
||||
var submitButton = form.find('button[type="submit"], input[type="submit"]');
|
||||
|
||||
const boredWaitingText = /*[[#{bored}]]*/ 'Bored Waiting?';
|
||||
const downloadCompleteText = /*[[#{downloadComplete}]]*/ 'Download Complete';
|
||||
window.downloadCompleteText = downloadCompleteText;
|
||||
// Create the 'show-game-btn' button
|
||||
var gameButton = $('<button type="button" class="btn btn-primary" id="show-game-btn" style="display:none;">' + boredWaitingText + '</button>');
|
||||
|
||||
// Insert the 'show-game-btn' just above the submit button
|
||||
submitButton.before(gameButton);
|
||||
|
||||
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 th:src="@{'/favicon.svg'}" class="player" id="player" alt="favicon">
|
||||
</div>
|
||||
<link rel="stylesheet" th:href="@{'/css/game.css'}">
|
||||
</dialog>
|
||||
</th:block>
|
||||
|
||||
<th:block th:fragment="fileSelector(name, multipleInputsForSingleRequest)"
|
||||
th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: true, disableMultipleFiles=${disableMultipleFiles} ?: false, showUploads=${showUploads} ?: true, notRequired=${notRequired} ?: false">
|
||||
<script th:inline="javascript">
|
||||
(function () {
|
||||
window.stirlingPDF.pdfPasswordPrompt = /*[[#{error.pdfPassword}]]*/ '';
|
||||
window.stirlingPDF.multipleInputsForSingleRequest = /*[[${multipleInputsForSingleRequest}]]*/ false;
|
||||
window.stirlingPDF.disableMultipleFiles = /*[[${disableMultipleFiles}]]*/ false;
|
||||
window.stirlingPDF.remoteCall = /*[[${remoteCall}]]*/ true;
|
||||
window.stirlingPDF.sessionExpired = /*[[#{session.expired}]]*/ '';
|
||||
window.stirlingPDF.refreshPage = /*[[#{session.refreshPage}]]*/ 'Refresh Page';
|
||||
window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
|
||||
window.stirlingPDF.uploadLimitReadable = /*[[${@uploadLimitService.getReadableUploadLimit()}]]*/ 'Unlimited';
|
||||
window.stirlingPDF.uploadLimit = /*[[${@uploadLimitService.getUploadLimit()}]]*/ 0;
|
||||
window.stirlingPDF.uploadLimitExceededSingular = /*[[#{uploadLimitExceededSingular}]]*/ 'is too large. Maximum allowed size is';
|
||||
window.stirlingPDF.uploadLimitExceededPlural = /*[[#{uploadLimitExceededPlural}]]*/ 'are too large. Maximum allowed size is';
|
||||
})();
|
||||
</script>
|
||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||
<script th:src="@{'/js/downloader.js'}"></script>
|
||||
<script>
|
||||
window.decrypt = {
|
||||
passwordPrompt: '[[#{decrypt.passwordPrompt}]]',
|
||||
cancelled: '[[#{decrypt.cancelled}]]',
|
||||
noPassword: '[[#{decrypt.noPassword}]]',
|
||||
invalidPassword: '[[#{decrypt.invalidPassword}]]',
|
||||
invalidPasswordHeader: '[[#{decrypt.invalidPasswordHeader}]]',
|
||||
unexpectedError: '[[#{decrypt.unexpectedError}]]',
|
||||
serverError: '[[#{decrypt.serverError}]]',
|
||||
success: '[[#{decrypt.success}]]',
|
||||
};
|
||||
window.fileInput = {
|
||||
dragAndDropPDF: '[[#{fileChooser.dragAndDropPDF}]]',
|
||||
dragAndDropImage: '[[#{fileChooser.dragAndDropImage}]]',
|
||||
extractPDF: '[[#{fileChooser.extractPDF}]]',
|
||||
loading: '[[#{loading}]]'
|
||||
};</script>
|
||||
<div class="custom-file-chooser mb-3"
|
||||
|
||||
th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-element-container-id=${name+'-input-container'}, data-bs-show-uploads=${showUploads}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}, data-bs-no-file-selected=#{noFileSelected}">
|
||||
<div class="mb-3 d-flex flex-column justify-content-center align-items-center flex-wrap input-container"
|
||||
th:name="${name}+'-input'" th:id="${name}+'-input-container'" th:data-text="#{fileChooser.hoveredDragAndDrop}">
|
||||
<label class="file-input-btn d-none">
|
||||
<input type="file" class="form-control"
|
||||
th:name="${name}"
|
||||
th:id="${name}+'-input'"
|
||||
th:accept="${accept == null ? '*/*': ((accept == '*/*') ? accept : (accept + ',.zip'))}"
|
||||
th:attr="multiple=${!disableMultipleFiles}"
|
||||
th:required="${notRequired} ? null : 'required'">
|
||||
Browse
|
||||
</label>
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<div class="d-flex justify-content-start align-items-center" id="fileInputText">
|
||||
<div th:text="#{fileChooser.click}" style="margin-right: 5px"></div>
|
||||
<div th:text="#{fileChooser.or}" style="margin-right: 5px"></div>
|
||||
<div th:text="#{fileChooser.dragAndDrop}" id="dragAndDrop"></div>
|
||||
</div>
|
||||
<hr th:if="${@GoogleDriveEnabled == true}" class="horizontal-divider" >
|
||||
</div>
|
||||
<div th:if="${@GoogleDriveEnabled == true}" th:id="${name}+'-google-drive-button'" class="google-drive-button" th:attr="data-name=${name}, data-multiple=${!disableMultipleFiles}, data-accept=${accept}" >
|
||||
<img th:src="@{'/images/google-drive.svg'}" alt="google drive">
|
||||
</div>
|
||||
</div>
|
||||
<div class="selected-files flex-wrap"></div>
|
||||
<div class="text-muted small mt-0 text-end w-100" th:if="${@uploadLimitService.getUploadLimit() != 0}">
|
||||
<span th:text="#{uploadLimit}">Maximum file size: </span>
|
||||
<span th:text="${@uploadLimitService.getReadableUploadLimit()}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progressBarContainer" style="display: none; position: relative;">
|
||||
<div class="progress" style="height: 1rem;">
|
||||
<div class="progressBar 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>
|
||||
<script th:src="@{'/js/fileInput.js'}" type="module"></script>
|
||||
|
||||
<div th:if="${@GoogleDriveEnabled == true}" >
|
||||
<script type="text/javascript" th:src="@{'/js/googleFilePicker.js'}"></script>
|
||||
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
|
||||
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
|
||||
|
||||
<script th:inline="javascript">
|
||||
window.stirlingPDF.GoogleDriveClientId = /*[[${@GoogleDriveConfig.getClientId()}]]*/ null;
|
||||
window.stirlingPDF.GoogleDriveApiKey = /*[[${@GoogleDriveConfig.getApiKey()}]]*/ null;
|
||||
window.stirlingPDF.GoogleDriveAppId = /*[[${@GoogleDriveConfig.getAppId()}]]*/ null;
|
||||
</script>
|
||||
</div>
|
||||
</th:block>
|
@ -1,284 +0,0 @@
|
||||
<th:block th:fragment="navElements">
|
||||
<div id="groupOrganize" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.organize})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}">
|
||||
</div>
|
||||
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'add_to_photos', 'home.merge.title', 'home.merge.desc', 'merge.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdfs', 'cut', 'home.split.title', 'home.split.desc', 'split.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('rotate-pdf', 'rotate_right', 'home.rotate.title', 'home.rotate.desc', 'rotate.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('crop', 'crop', 'home.crop.title', 'home.crop.desc', 'crop.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-organizer', 'format_list_bulleted', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc', 'pdfOrganiser.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-pages', 'delete', 'home.removePages.title', 'home.removePages.desc', 'removePages.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('multi-page-layout', 'dashboard', 'home.pageLayout.title', 'home.pageLayout.desc', 'pageLayout.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('scale-pages', 'fullscreen', 'home.scalePages.title', 'home.scalePages.desc', 'scalePages.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('extract-page', 'upload', 'home.extractPage.title', 'home.extractPage.desc', 'extractPage.tags', 'organize')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-single-page', 'looks_one', 'home.PdfToSinglePage.title', 'home.PdfToSinglePage.desc', 'PdfToSinglePage.tags', 'organize')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="groupConvertTo" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('img-to-pdf', 'picture_as_pdf', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('file-to-pdf', 'draft', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('url-to-pdf', 'link', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('html-to-pdf', 'html', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('eml-to-pdf', 'email', 'home.EMLToPDF.title', 'home.EMLToPDF.desc', 'EMLToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="groupConvertFrom" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-img', 'photo_library', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-pdfa', 'picture_as_pdf', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc', 'pdfToPDFA.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-word', 'description', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-presentation', 'slideshow', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc', 'PDFToPresentation.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-text', 'text_fields', 'home.PDFToText.title', 'home.PDFToText.desc', 'PDFToText.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-html', 'html', 'home.PDFToHTML.title', 'home.PDFToHTML.desc', 'PDFToHTML.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-xml', 'code', 'home.PDFToXML.title', 'home.PDFToXML.desc', 'PDFToXML.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-markdown', 'markdown_copy', 'home.PDFToMarkdown.title', 'home.PDFToMarkdown.desc', 'PDFToMarkdown.tags', 'convert')}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="convertGroup" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('img-to-pdf', 'picture_as_pdf', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('file-to-pdf', 'draft', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('url-to-pdf', 'link', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('html-to-pdf', 'html', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
</div>
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-img', 'photo_library', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-pdfa', 'picture_as_pdf', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc', 'pdfToPDFA.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-word', 'description', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-presentation', 'slideshow', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc', 'PDFToPresentation.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-text', 'text_fields', 'home.PDFToText.title', 'home.PDFToText.desc', 'PDFToText.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-html', 'html', 'home.PDFToHTML.title', 'home.PDFToHTML.desc', 'PDFToHTML.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-xml', 'code', 'home.PDFToXML.title', 'home.PDFToXML.desc', 'PDFToXML.tags', 'convert')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags', 'convert')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="groupSecurity" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.security})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('add-password', 'lock', 'home.addPassword.title', 'home.addPassword.desc', 'addPassword.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-password', 'lock_open_right', 'home.removePassword.title', 'home.removePassword.desc', 'removePassword.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('change-permissions', 'encrypted', 'home.permissions.title', 'home.permissions.desc', 'permissions.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('sign', 'signature', 'home.sign.title', 'home.sign.desc', 'sign.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('cert-sign', 'workspace_premium', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags', 'security')}">
|
||||
</div>
|
||||
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('validate-signature', 'verified', 'home.validateSignature.title', 'home.validateSignature.desc', 'validateSignature.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-cert-sign', 'remove_moderator', 'home.removeCertSign.title', 'home.removeCertSign.desc', 'removeCertSign.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('sanitize-pdf', 'sanitizer', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('auto-redact', '/images/redact-auto.svg#icon-redact-auto', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('redact', '/images/redact-manual.svg#icon-redact-manual', 'home.redact.title', 'home.redact.desc', 'redact.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('stamp', 'approval', 'home.AddStampRequest.title', 'home.AddStampRequest.desc', 'AddStampRequest.tags', 'security')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('add-watermark', 'water_drop', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags', 'security')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="groupView" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.edit})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('view-pdf', 'menu_book', 'home.viewPdf.title', 'home.viewPdf.desc', 'viewPdf.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('add-page-numbers', '123', 'home.add-page-numbers.title', 'home.add-page-numbers.desc', 'add-page-numbers.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('add-image', 'text_fields', 'home.addImage.title', 'home.addImage.desc', 'addImage.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('change-metadata', 'assignment', 'home.changeMetadata.title', 'home.changeMetadata.desc', 'changeMetadata.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('ocr-pdf', 'quick_reference_all', 'home.ocr.title', 'home.ocr.desc', 'ocr.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('extract-images', 'photo_library', 'home.extractImages.title', 'home.extractImages.desc', 'extractImages.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('flatten', 'layers_clear', 'home.flatten.title', 'home.flatten.desc', 'flatten.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-blanks', 'scan_delete', 'home.removeBlanks.title', 'home.removeBlanks.desc', 'removeBlanks.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-annotations', 'thread_unread', 'home.removeAnnotations.title', 'home.removeAnnotations.desc', 'removeAnnotations.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('compare', 'compare', 'home.compare.title', 'home.compare.desc', 'compare.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('get-info-on-pdf', 'info', 'home.getPdfInfo.title', 'home.getPdfInfo.desc', 'getPdfInfo.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-image-pdf', 'remove_selection', 'home.removeImagePdf.title', 'home.removeImagePdf.desc', 'removeImagePdf.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('replace-and-invert-color-pdf', 'format_color_fill', 'home.replaceColorPdf.title', 'home.replaceColorPdf.desc', 'replaceColorPdf.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('unlock-pdf-forms', 'preview_off', 'home.unlockPDFForms.title', 'home.unlockPDFForms.desc', 'unlockPDFForms.tags', 'other')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="groupAdvanced" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.advance})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pipeline', 'family_history', 'home.pipeline.title', 'home.pipeline.desc', 'pipeline.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('adjust-contrast', 'palette', 'home.adjust-contrast.title', 'home.adjust-contrast.desc', 'adjust-contrast.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPdfs.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('extract-image-scans', 'scanner', 'home.ScannerImageSplit.title', 'home.ScannerImageSplit.desc', 'ScannerImageSplit.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('repair', 'build', 'home.repair.title', 'home.repair.desc', 'repair.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('auto-rename', '/images/rename.svg#icon-rename', 'home.auto-rename.title', 'home.auto-rename.desc', 'auto-rename.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('auto-split-pdf', '/images/split-auto.svg#icon-split-auto', 'home.autoSplitPDF.title', 'home.autoSplitPDF.desc', 'autoSplitPDF.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('show-javascript', 'javascript', 'home.showJS.title', 'home.showJS.desc', 'showJS.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('split-by-size-or-count', '/images/split-size.svg#icon-split-size', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('overlay-pdf', 'layers', 'home.overlay-pdfs.title', 'home.overlay-pdfs.desc', 'overlay-pdfs.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('split-pdf-by-sections', 'grid_on', 'home.split-by-sections.title', 'home.split-by-sections.desc', 'split-by-sections.tags', 'advance')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('split-pdf-by-chapters', '/images/split-chapters.svg#icon-split-chapters', 'home.splitPdfByChapters.title', 'home.splitPdfByChapters.desc', 'splitPdfByChapters.tags', 'advance')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
@ -14,21 +14,21 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final List<String> ALLOWED_PARAMS =
|
||||
Arrays.asList(
|
||||
"lang",
|
||||
"endpoint",
|
||||
"endpoints",
|
||||
"logout",
|
||||
"error",
|
||||
"errorOAuth",
|
||||
"file",
|
||||
"messageType",
|
||||
"infoMessage");
|
||||
Arrays.asList(
|
||||
"lang",
|
||||
"endpoint",
|
||||
"endpoints",
|
||||
"logout",
|
||||
"error",
|
||||
"errorOAuth",
|
||||
"file",
|
||||
"messageType",
|
||||
"infoMessage");
|
||||
|
||||
@Override
|
||||
public boolean preHandle(
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null && !queryString.isEmpty()) {
|
||||
String requestURI = request.getRequestURI();
|
||||
@ -69,15 +69,15 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public void postHandle(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler,
|
||||
ModelAndView modelAndView) {}
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler,
|
||||
ModelAndView modelAndView) {}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler,
|
||||
Exception ex) {}
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler,
|
||||
Exception ex) {}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package stirling.software.SPDF.service;
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -13,9 +13,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class EndpointConfigurationService {
|
||||
@Slf4j
|
||||
public class EndpointConfiguration {
|
||||
|
||||
private static final String REMOVE_BLANKS = "remove-blanks";
|
||||
private final ApplicationProperties applicationProperties;
|
||||
@ -23,7 +23,7 @@ public class EndpointConfigurationService {
|
||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||
private final boolean runningProOrHigher;
|
||||
|
||||
public EndpointConfigurationService(
|
||||
public EndpointConfiguration(
|
||||
ApplicationProperties applicationProperties,
|
||||
@Qualifier("runningProOrHigher") boolean runningProOrHigher) {
|
||||
this.applicationProperties = applicationProperties;
|
@ -39,14 +39,14 @@ public class EndpointInspector implements ApplicationListener<ContextRefreshedEv
|
||||
private void discoverEndpoints() {
|
||||
try {
|
||||
Map<String, RequestMappingHandlerMapping> mappings =
|
||||
applicationContext.getBeansOfType(RequestMappingHandlerMapping.class);
|
||||
applicationContext.getBeansOfType(RequestMappingHandlerMapping.class);
|
||||
|
||||
for (Map.Entry<String, RequestMappingHandlerMapping> entry : mappings.entrySet()) {
|
||||
RequestMappingHandlerMapping mapping = entry.getValue();
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
|
||||
|
||||
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerEntry :
|
||||
handlerMethods.entrySet()) {
|
||||
handlerMethods.entrySet()) {
|
||||
RequestMappingInfo mappingInfo = handlerEntry.getKey();
|
||||
HandlerMethod handlerMethod = handlerEntry.getValue();
|
||||
|
||||
@ -105,7 +105,7 @@ public class EndpointInspector implements ApplicationListener<ContextRefreshedEv
|
||||
String infoString = mappingInfo.toString();
|
||||
if (infoString.contains("{")) {
|
||||
String patternsSection =
|
||||
infoString.substring(infoString.indexOf("{") + 1, infoString.indexOf("}"));
|
||||
infoString.substring(infoString.indexOf("{") + 1, infoString.indexOf("}"));
|
||||
|
||||
for (String pattern : patternsSection.split(",")) {
|
||||
pattern = pattern.trim();
|
||||
|
@ -8,19 +8,18 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.service.EndpointConfigurationService;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class EndpointInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final EndpointConfigurationService endpointConfigurationService;
|
||||
private final EndpointConfiguration endpointConfiguration;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
String requestURI = request.getRequestURI();
|
||||
boolean isEnabled;
|
||||
|
||||
@ -38,10 +37,10 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
||||
}
|
||||
|
||||
log.debug("Request endpoint: {}", requestEndpoint);
|
||||
isEnabled = endpointConfigurationService.isEndpointEnabled(requestEndpoint);
|
||||
isEnabled = endpointConfiguration.isEndpointEnabled(requestEndpoint);
|
||||
log.debug("Is endpoint enabled: {}", isEnabled);
|
||||
} else {
|
||||
isEnabled = endpointConfigurationService.isEndpointEnabled(requestURI);
|
||||
isEnabled = endpointConfiguration.isEndpointEnabled(requestURI);
|
||||
}
|
||||
|
||||
if (!isEnabled) {
|
||||
|
@ -13,36 +13,35 @@ import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.SPDF.service.EndpointConfigurationService;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class ExternalAppDepConfig {
|
||||
|
||||
private final EndpointConfigurationService endpointConfigurationService;
|
||||
private final EndpointConfiguration endpointConfiguration;
|
||||
|
||||
private final String weasyprintPath;
|
||||
private final String unoconvPath;
|
||||
private final Map<String, List<String>> commandToGroupMapping;
|
||||
|
||||
public ExternalAppDepConfig(
|
||||
EndpointConfigurationService endpointConfigurationService, RuntimePathConfig runtimePathConfig) {
|
||||
this.endpointConfigurationService = endpointConfigurationService;
|
||||
EndpointConfiguration endpointConfiguration, RuntimePathConfig runtimePathConfig) {
|
||||
this.endpointConfiguration = endpointConfiguration;
|
||||
weasyprintPath = runtimePathConfig.getWeasyPrintPath();
|
||||
unoconvPath = runtimePathConfig.getUnoConvertPath();
|
||||
|
||||
commandToGroupMapping =
|
||||
new HashMap<>() {
|
||||
new HashMap<>() {
|
||||
|
||||
{
|
||||
put("soffice", List.of("LibreOffice"));
|
||||
put(weasyprintPath, List.of("Weasyprint"));
|
||||
put("pdftohtml", List.of("Pdftohtml"));
|
||||
put(unoconvPath, List.of("Unoconvert"));
|
||||
put("qpdf", List.of("qpdf"));
|
||||
put("tesseract", List.of("tesseract"));
|
||||
}
|
||||
};
|
||||
{
|
||||
put("soffice", List.of("LibreOffice"));
|
||||
put(weasyprintPath, List.of("Weasyprint"));
|
||||
put("pdftohtml", List.of("Pdftohtml"));
|
||||
put(unoconvPath, List.of("Unoconvert"));
|
||||
put("qpdf", List.of("qpdf"));
|
||||
put("tesseract", List.of("tesseract"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isCommandAvailable(String command) {
|
||||
@ -63,9 +62,9 @@ public class ExternalAppDepConfig {
|
||||
}
|
||||
|
||||
private List<String> getAffectedFeatures(String group) {
|
||||
return endpointConfigurationService.getEndpointsForGroup(group).stream()
|
||||
.map(endpoint -> formatEndpointAsFeature(endpoint))
|
||||
.toList();
|
||||
return endpointConfiguration.getEndpointsForGroup(group).stream()
|
||||
.map(endpoint -> formatEndpointAsFeature(endpoint))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String formatEndpointAsFeature(String endpoint) {
|
||||
@ -73,8 +72,8 @@ public class ExternalAppDepConfig {
|
||||
String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
|
||||
// Split into words and capitalize each word
|
||||
return Arrays.stream(feature.split("\\s+"))
|
||||
.map(word -> capitalizeWord(word))
|
||||
.collect(Collectors.joining(" "));
|
||||
.map(word -> capitalizeWord(word))
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
private String capitalizeWord(String word) {
|
||||
@ -94,14 +93,14 @@ public class ExternalAppDepConfig {
|
||||
if (affectedGroups != null) {
|
||||
for (String group : affectedGroups) {
|
||||
List<String> affectedFeatures = getAffectedFeatures(group);
|
||||
endpointConfigurationService.disableGroup(group);
|
||||
endpointConfiguration.disableGroup(group);
|
||||
log.warn(
|
||||
"Missing dependency: {} - Disabling group: {} (Affected features: {})",
|
||||
command,
|
||||
group,
|
||||
affectedFeatures != null && !affectedFeatures.isEmpty()
|
||||
? String.join(", ", affectedFeatures)
|
||||
: "unknown");
|
||||
"Missing dependency: {} - Disabling group: {} (Affected features: {})",
|
||||
command,
|
||||
group,
|
||||
affectedFeatures != null && !affectedFeatures.isEmpty()
|
||||
? String.join(", ", affectedFeatures)
|
||||
: "unknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,12 +120,12 @@ public class ExternalAppDepConfig {
|
||||
if (!pythonAvailable) {
|
||||
List<String> pythonFeatures = getAffectedFeatures("Python");
|
||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
||||
endpointConfigurationService.disableGroup("Python");
|
||||
endpointConfigurationService.disableGroup("OpenCV");
|
||||
endpointConfiguration.disableGroup("Python");
|
||||
endpointConfiguration.disableGroup("OpenCV");
|
||||
log.warn(
|
||||
"Missing dependency: Python - Disabling Python features: {} and OpenCV features: {}",
|
||||
String.join(", ", pythonFeatures),
|
||||
String.join(", ", openCVFeatures));
|
||||
"Missing dependency: Python - Disabling Python features: {} and OpenCV features: {}",
|
||||
String.join(", ", pythonFeatures),
|
||||
String.join(", ", openCVFeatures));
|
||||
} else {
|
||||
// If Python is available, check for OpenCV
|
||||
try {
|
||||
@ -140,20 +139,20 @@ public class ExternalAppDepConfig {
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode != 0) {
|
||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
||||
endpointConfigurationService.disableGroup("OpenCV");
|
||||
endpointConfiguration.disableGroup("OpenCV");
|
||||
log.warn(
|
||||
"OpenCV not available in Python - Disabling OpenCV features: {}",
|
||||
String.join(", ", openCVFeatures));
|
||||
"OpenCV not available in Python - Disabling OpenCV features: {}",
|
||||
String.join(", ", openCVFeatures));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
||||
endpointConfigurationService.disableGroup("OpenCV");
|
||||
endpointConfiguration.disableGroup("OpenCV");
|
||||
log.warn(
|
||||
"Error checking OpenCV: {} - Disabling OpenCV features: {}",
|
||||
e.getMessage(),
|
||||
String.join(", ", openCVFeatures));
|
||||
"Error checking OpenCV: {} - Disabling OpenCV features: {}",
|
||||
e.getMessage(),
|
||||
String.join(", ", openCVFeatures));
|
||||
}
|
||||
}
|
||||
endpointConfigurationService.logDisabledEndpointsSummary();
|
||||
endpointConfiguration.logDisabledEndpointsSummary();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
// package stirling.software.SPDF.config.fingerprint;
|
||||
//
|
||||
// import java.security.MessageDigest;
|
||||
// import java.security.NoSuchAlgorithmException;
|
||||
//
|
||||
// import org.springframework.stereotype.Component;
|
||||
//
|
||||
// import jakarta.servlet.http.HttpServletRequest;
|
||||
//
|
||||
// @Component
|
||||
// public class FingerprintGenerator {
|
||||
//
|
||||
// public String generateFingerprint(HttpServletRequest request) {
|
||||
// if (request == null) {
|
||||
// return "";
|
||||
// }
|
||||
// StringBuilder fingerprintBuilder = new StringBuilder();
|
||||
//
|
||||
// // Add IP address
|
||||
// fingerprintBuilder.append(request.getRemoteAddr());
|
||||
//
|
||||
// // Add X-Forwarded-For header if present (for clients behind proxies)
|
||||
// String forwardedFor = request.getHeader("X-Forwarded-For");
|
||||
// if (forwardedFor != null) {
|
||||
// fingerprintBuilder.append(forwardedFor);
|
||||
// }
|
||||
//
|
||||
// // Add User-Agent
|
||||
// String userAgent = request.getHeader("User-Agent");
|
||||
// if (userAgent != null) {
|
||||
// fingerprintBuilder.append(userAgent);
|
||||
// }
|
||||
//
|
||||
// // Add Accept-Language header
|
||||
// String acceptLanguage = request.getHeader("Accept-Language");
|
||||
// if (acceptLanguage != null) {
|
||||
// fingerprintBuilder.append(acceptLanguage);
|
||||
// }
|
||||
//
|
||||
// // Add Accept header
|
||||
// String accept = request.getHeader("Accept");
|
||||
// if (accept != null) {
|
||||
// fingerprintBuilder.append(accept);
|
||||
// }
|
||||
//
|
||||
// // Add Connection header
|
||||
// String connection = request.getHeader("Connection");
|
||||
// if (connection != null) {
|
||||
// fingerprintBuilder.append(connection);
|
||||
// }
|
||||
//
|
||||
// // Add server port
|
||||
// fingerprintBuilder.append(request.getServerPort());
|
||||
//
|
||||
// // Add secure flag
|
||||
// fingerprintBuilder.append(request.isSecure());
|
||||
//
|
||||
// // Generate a hash of the fingerprint
|
||||
// return generateHash(fingerprintBuilder.toString());
|
||||
// }
|
||||
//
|
||||
// private String generateHash(String input) {
|
||||
// try {
|
||||
// MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
// byte[] hash = digest.digest(input.getBytes());
|
||||
// StringBuilder hexString = new StringBuilder();
|
||||
// for (byte b : hash) {
|
||||
// String hex = Integer.toHexString(0xff & b);
|
||||
// if (hex.length() == 1) hexString.append('0');
|
||||
// hexString.append(hex);
|
||||
// }
|
||||
// return hexString.toString();
|
||||
// } catch (NoSuchAlgorithmException e) {
|
||||
// throw new RuntimeException("Failed to generate fingerprint hash", e);
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -16,7 +16,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import stirling.software.SPDF.service.EndpointConfigurationService;
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
import stirling.software.common.configuration.InstallationPathConfig;
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
@ -29,16 +29,16 @@ import stirling.software.common.util.GeneralUtils;
|
||||
public class SettingsController {
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
private final EndpointConfigurationService endpointConfigurationService;
|
||||
private final EndpointConfiguration endpointConfiguration;
|
||||
|
||||
@PostMapping("/update-enable-analytics")
|
||||
@Hidden
|
||||
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
||||
if (applicationProperties.getSystem().getEnableAnalytics() != null) {
|
||||
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
||||
.body(
|
||||
"Setting has already been set, To adjust please edit "
|
||||
+ InstallationPathConfig.getSettingsPath());
|
||||
.body(
|
||||
"Setting has already been set, To adjust please edit "
|
||||
+ InstallationPathConfig.getSettingsPath());
|
||||
}
|
||||
GeneralUtils.saveKeyToSettings("system.enableAnalytics", enabled);
|
||||
applicationProperties.getSystem().setEnableAnalytics(enabled);
|
||||
@ -48,6 +48,6 @@ public class SettingsController {
|
||||
@GetMapping("/get-endpoints-status")
|
||||
@Hidden
|
||||
public ResponseEntity<Map<String, Boolean>> getDisabledEndpoints() {
|
||||
return ResponseEntity.ok(endpointConfigurationService.getEndpointStatuses());
|
||||
return ResponseEntity.ok(endpointConfiguration.getEndpointStatuses());
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -17,14 +20,17 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
@ -38,24 +44,13 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
import stirling.software.SPDF.service.EndpointConfigurationService;
|
||||
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
@ -68,9 +63,9 @@ public class CompressController {
|
||||
|
||||
public CompressController(
|
||||
CustomPDFDocumentFactory pdfDocumentFactory,
|
||||
EndpointConfigurationService endpointConfigurationService) {
|
||||
EndpointConfiguration endpointConfiguration) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.qpdfEnabled = endpointConfigurationService.isGroupEnabled("qpdf");
|
||||
this.qpdfEnabled = endpointConfiguration.isGroupEnabled("qpdf");
|
||||
}
|
||||
|
||||
@Data
|
||||
|
18511
stirling-pdf/src/main/resources/static/pdfjs-legacy/js/viewer.mjs
vendored
Normal file
18511
stirling-pdf/src/main/resources/static/pdfjs-legacy/js/viewer.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
24320
stirling-pdf/src/main/resources/static/pdfjs-legacy/pdf.mjs
vendored
Normal file
24320
stirling-pdf/src/main/resources/static/pdfjs-legacy/pdf.mjs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3796
stirling-pdf/src/main/resources/static/pdfjs-legacy/pdf.sandbox.mjs
vendored
Normal file
3796
stirling-pdf/src/main/resources/static/pdfjs-legacy/pdf.sandbox.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
61184
stirling-pdf/src/main/resources/static/pdfjs-legacy/pdf.worker.mjs
vendored
Normal file
61184
stirling-pdf/src/main/resources/static/pdfjs-legacy/pdf.worker.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -22,13 +22,13 @@
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToText.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option th:if="${@endpointConfigurationService.isEndpointEnabled('pdf-to-rtf')}" value="rtf">RTF</option>
|
||||
<option th:if="${@endpointConfiguration.isEndpointEnabled('pdf-to-rtf')}" value="rtf">RTF</option>
|
||||
<option value="txt">TXT</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
|
||||
</form>
|
||||
<p th:if="${@endpointConfigurationService.isEndpointEnabled('pdf-to-rtf')}" class="mt-3" th:text="#{PDFToText.credit}"></p>
|
||||
<p th:if="${@endpointConfiguration.isEndpointEnabled('pdf-to-rtf')}" class="mt-3" th:text="#{PDFToText.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div th:fragment="card" class="feature-card hidden" th:id="${id}" th:if="${@endpointConfigurationService.isEndpointEnabled(cardLink)} "
|
||||
<div th:fragment="card" class="feature-card hidden" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)} "
|
||||
th:data-bs-tags="${tags}"
|
||||
th:data-bs-link="@{${endpoint}}">
|
||||
<a th:href="${cardLink}">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('ca_CA', 'Català')}"></div>
|
||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('zh_CN', '简体中文')}"></div>
|
||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('zh_TW', '繁體中文')}"></div>
|
||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('zh_BO', 'བོད་ཡིག')}"></div>
|
||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('bo_CN', 'བོད་ཡིག')}"></div>
|
||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('az_AZ', 'Azərbaycan Dili')}"></div>
|
||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('da_DK', 'Dansk')}"></div>
|
||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('de_DE', 'Deutsch')}"></div>
|
||||
|
@ -59,6 +59,9 @@
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('eml-to-pdf', 'email', 'home.EMLToPDF.title', 'home.EMLToPDF.desc', 'EMLToPDF.tags', 'convertto')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="groupConvertFrom" class="feature-group">
|
||||
@ -264,6 +267,7 @@
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('show-javascript', 'javascript', 'home.showJS.title', 'home.showJS.desc', 'showJS.tags', 'advance')}">
|
||||
</div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry('fake-scan', 'scanner', 'fakeScan.title', 'fakeScan.description', 'fakeScan.tags', 'advance')}"></div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('split-by-size-or-count', '/images/split-size.svg#icon-split-size', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags', 'advance')}">
|
||||
</div>
|
||||
|
@ -57,7 +57,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="nav-item" th:if="${@endpointConfigurationService.isEndpointEnabled('multi-tool')}">
|
||||
<li class="nav-item" th:if="${@endpointConfiguration.isEndpointEnabled('multi-tool')}">
|
||||
<a class="nav-link" href="#" th:href="@{'/multi-tool'}"
|
||||
th:classappend="${currentPage}=='multi-tool' ? 'active' : ''" th:title="#{home.multiTool.desc}">
|
||||
<span class="material-symbols-rounded">
|
||||
@ -67,7 +67,7 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" th:if="${@endpointConfigurationService.isEndpointEnabled('pipeline')}">
|
||||
<li class="nav-item" th:if="${@endpointConfiguration.isEndpointEnabled('pipeline')}">
|
||||
<a class="nav-link" href="#" th:href="@{'/pipeline'}"
|
||||
th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}">
|
||||
<span class="material-symbols-rounded">
|
||||
@ -77,7 +77,7 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" th:if="${@endpointConfigurationService.isEndpointEnabled('compress-pdf')}">
|
||||
<li class="nav-item" th:if="${@endpointConfiguration.isEndpointEnabled('compress-pdf')}">
|
||||
<a class="nav-link" href="#" title="#{home.compressPdfs.title}" th:href="@{'/compress-pdf'}"
|
||||
th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:title="#{home.compressPdfs.desc}">
|
||||
<span class="material-symbols-rounded">
|
||||
|
@ -1,18 +1,18 @@
|
||||
<th:block th:fragment="navbarEntry(endpoint, toolIcon, titleKey, descKey, tagKey, toolGroup)"
|
||||
th:if="${@endpointConfigurationService.isEndpointEnabled(endpoint)}">
|
||||
th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}">
|
||||
<a th:id="@{${endpoint}}" class="dropdown-item" style="position:relative" th:href="@{${endpoint}}"
|
||||
th:data-bs-link="@{${endpoint}}"
|
||||
th:classappend="${endpoint.equals(currentPage)} ? ${toolGroup} + ' active' : '' + ${toolGroup}"
|
||||
th:data-bs-tags="#{${tagKey}}" th:data-bs-title='#{${titleKey}}'>
|
||||
th:data-bs-link="@{${endpoint}}"
|
||||
th:classappend="${endpoint.equals(currentPage)} ? ${toolGroup} + ' active' : '' + ${toolGroup}"
|
||||
th:data-bs-tags="#{${tagKey}}" th:data-bs-title='#{${titleKey}}'>
|
||||
<div style="min-height:2.7rem; align-items: center;display: flex;" th:title="#{${descKey}}" class="icon" alt="icon"
|
||||
th:class="@{${toolGroup}}">
|
||||
th:class="@{${toolGroup}}">
|
||||
<span class="material-symbols-rounded nav-icon" th:text="@{${toolIcon}}" style=" align-items:center; display: flex; justify-content: center; height:2.7rem; width:2.7rem"></span>
|
||||
<span class="icon-text" th:text="#{${titleKey}}"></span>
|
||||
</div>
|
||||
<span class="material-symbols-rounded favorite-icon" style="display: none;" th:data-endpoint="@{${endpoint}}"
|
||||
th:onclick="'addToFavorites(\'' + @{${endpoint}} + '\')'">
|
||||
th:onclick="'addToFavorites(\'' + @{${endpoint}} + '\')'">
|
||||
add
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</th:block>
|
||||
</th:block>
|
@ -1,5 +1,5 @@
|
||||
<th:block th:fragment="navbarEntry(endpoint, toolIcon, titleKey, descKey, tagKey, toolGroup)"
|
||||
th:if="${@endpointConfigurationService.isEndpointEnabled(endpoint)}">
|
||||
th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}">
|
||||
<a th:id="@{${endpoint}}" class="dropdown-item" style="position:relative" th:href="@{${endpoint}}"
|
||||
th:data-bs-link="@{${endpoint}}"
|
||||
th:classappend="${endpoint.equals(currentPage)} ? ${toolGroup} + ' active' : '' + ${toolGroup}"
|
||||
|
@ -1,243 +1,235 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
|
||||
xmlns:th="https://www.thymeleaf.org">
|
||||
xmlns:th="https://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<div style="transform-origin: top;"
|
||||
id="scale-wrap">
|
||||
<br class="d-md-none">
|
||||
<!-- Features -->
|
||||
<script th:src="@{'/js/homecard.js'}"></script>
|
||||
<div style="
|
||||
<div id="page-container">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<div style="transform-origin: top;"
|
||||
id="scale-wrap">
|
||||
<br class="d-md-none">
|
||||
<!-- Features -->
|
||||
<script th:src="@{'/js/homecard.js'}"></script>
|
||||
<div style="
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;"
|
||||
>
|
||||
<div>
|
||||
<br>
|
||||
<div style="justify-content: center; display: flex;">
|
||||
<div style="margin:0 3rem">
|
||||
<div>
|
||||
<div
|
||||
style="display:flex; flex-direction: column; justify-content: center; width:100%; margin-bottom:1rem">
|
||||
<div style="width:fit-content; margin: 0 auto; padding: 0 3rem">
|
||||
<p class="lead fs-4"
|
||||
th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}">
|
||||
</p>
|
||||
</div>
|
||||
<div id="groupRecent" style="width: fit-content; margin: 0 auto">
|
||||
<div
|
||||
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.recent})}">
|
||||
>
|
||||
<div>
|
||||
<br>
|
||||
<div style="justify-content: center; display: flex;">
|
||||
<div style="margin:0 3rem">
|
||||
<div>
|
||||
<div
|
||||
style="display:flex; flex-direction: column; justify-content: center; width:100%; margin-bottom:1rem">
|
||||
<div style="width:fit-content; margin: 0 auto; padding: 0 3rem">
|
||||
<p class="lead fs-4"
|
||||
th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}">
|
||||
</p>
|
||||
</div>
|
||||
<div class="recent-features">
|
||||
<div class="newfeature"
|
||||
th:insert="~{fragments/navbarEntryCustom :: navbarEntry('redact', '/images/redact-manual.svg#icon-redact-manual', 'home.redact.title', 'home.redact.desc', 'redact.tags', 'security')}">
|
||||
<div id="groupRecent" style="width: fit-content; margin: 0 auto">
|
||||
<div
|
||||
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.recent})}">
|
||||
</div>
|
||||
<div class="newfeature"
|
||||
th:insert="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}">
|
||||
</div>
|
||||
<div class="newfeature"
|
||||
th:insert="~{fragments/navbarEntry :: navbarEntry('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPDFs.tags', 'advance')}">
|
||||
<div class="recent-features">
|
||||
<div class="newfeature"
|
||||
th:insert="~{fragments/navbarEntryCustom :: navbarEntry('redact', '/images/redact-manual.svg#icon-redact-manual', 'home.redact.title', 'home.redact.desc', 'redact.tags', 'security')}">
|
||||
</div>
|
||||
<div class="newfeature"
|
||||
th:insert="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}">
|
||||
</div>
|
||||
<div class="newfeature"
|
||||
th:insert="~{fragments/navbarEntry :: navbarEntry('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPDFs.tags', 'advance')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<span class="material-symbols-rounded search-icon">
|
||||
</div>
|
||||
<span class="material-symbols-rounded search-icon">
|
||||
search
|
||||
</span>
|
||||
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}" autofocus>
|
||||
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}" autofocus>
|
||||
|
||||
<div style="display: flex; column-gap: 3rem; flex-wrap: wrap; margin-left:1rem">
|
||||
<div
|
||||
style="height:2.5rem; display: flex; align-items: center; cursor: pointer; justify-content: center;">
|
||||
<label for="sort-options" th:text="#{home.sortBy}">Sort by:</label>
|
||||
<select id="sort-options" style="border:none;">
|
||||
<option value="alphabetical" th:text="#{home.alphabetical}"></option>
|
||||
<!-- <option value="personal">Your most used</option> -->
|
||||
<option value="global" th:text="#{home.globalPopularity}"></option>
|
||||
<!-- <option value="server">Popularity in organisation</option> -->
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; align-items: center; flex-wrap: wrap; align-content: flex-start; width: fit-content; max-width: 100%; gap:2rem; justify-content: center;">
|
||||
<div th:title="#{home.setFavorites}" style="display: flex; align-items: center; cursor: pointer;"
|
||||
onclick="toggleFavoritesMode()">
|
||||
<div style="display: flex; column-gap: 3rem; flex-wrap: wrap; margin-left:1rem">
|
||||
<div
|
||||
style="height:2.5rem; display: flex; align-items: center; cursor: pointer; justify-content: center;">
|
||||
<label for="sort-options" th:text="#{home.sortBy}">Sort by:</label>
|
||||
<select id="sort-options" style="border:none;">
|
||||
<option value="alphabetical" th:text="#{home.alphabetical}"> </option>
|
||||
<!-- <option value="personal">Your most used</option> -->
|
||||
<option value="global" th:text="#{home.globalPopularity}"></option>
|
||||
<!-- <option value="server">Popularity in organisation</option> -->
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; align-items: center; flex-wrap: wrap; align-content: flex-start; width: fit-content; max-width: 100%; gap:2rem; justify-content: center;">
|
||||
<div th:title="#{home.setFavorites}" style="display: flex; align-items: center; cursor: pointer;"
|
||||
onclick="toggleFavoritesMode()">
|
||||
<span class="material-symbols-rounded toggle-favourites"
|
||||
style="font-size: 2rem; margin-left: 0.2rem;">
|
||||
style="font-size: 2rem; margin-left: 0.2rem;">
|
||||
star
|
||||
</span>
|
||||
</div>
|
||||
<div onclick="toggleFavoritesView()" th:title="#{home.hideFavorites}" id="favouritesVisibility"
|
||||
style="display: flex; align-items: center; cursor: pointer;">
|
||||
</div>
|
||||
<div onclick="toggleFavoritesView()" th:title="#{home.hideFavorites}" id="favouritesVisibility"
|
||||
style="display: flex; align-items: center; cursor: pointer;">
|
||||
<span id="toggle-favourites-icon" class="material-symbols-rounded toggle-favourites"
|
||||
style="font-size: 2rem; margin-left: 0.2rem;">
|
||||
style="font-size: 2rem; margin-left: 0.2rem;">
|
||||
visibility
|
||||
</span>
|
||||
</div>
|
||||
<a th:if="${@shouldShow}" href="https://github.com/Stirling-Tools/Stirling-PDF/releases"
|
||||
target="_blank" id="update-link" rel="noopener" th:title="#{settings.update}"
|
||||
style="text-decoration: none; color: inherit; cursor: pointer; display: flex; align-items: center;">
|
||||
</div>
|
||||
<a th:if="${@shouldShow}" href="https://github.com/Stirling-Tools/Stirling-PDF/releases"
|
||||
target="_blank" id="update-link" rel="noopener" th:title="#{settings.update}"
|
||||
style="text-decoration: none; color: inherit; cursor: pointer; display: flex; align-items: center;">
|
||||
<span class="material-symbols-rounded toggle-favourites"
|
||||
style="font-size: 2rem; margin-left: 0.2rem;">
|
||||
style="font-size: 2rem; margin-left: 0.2rem;">
|
||||
update
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
<div class="features-container" style=" border-top: 1px;
|
||||
<div>
|
||||
</div>
|
||||
<div class="features-container" style=" border-top: 1px;
|
||||
border-top-style: solid;
|
||||
border-color: var(--md-nav-color-on-seperator);
|
||||
margin-top: 1rem;
|
||||
">
|
||||
<div class="feature-rows">
|
||||
<div id="groupFavorites" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.favorite})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
<div class="feature-rows">
|
||||
<div id="groupFavorites" class="feature-group">
|
||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.favorite})}">
|
||||
</div>
|
||||
<div class="nav-group-container">
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/navElements.html :: navElements}"></th:block>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/navElements.html :: navElements}"></th:block>
|
||||
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Survey Modal -->
|
||||
<div class="modal fade" id="surveyModal" tabindex="-1" role="dialog" aria-labelledby="surveyModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="surveyModalLabel" th:text="#{survey.title}">Stirling-PDF Survey</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p th:text="#{survey.meeting.1}">If you're using Stirling PDF at work, we'd love to speak to you. We're offering free
|
||||
technical support in exchange for a 15 minute user discovery session.</p>
|
||||
<p th:text="#{survey.meeting.2}">This is a chance to:</p>
|
||||
<p><span>🛠️</span><span th:text="#{survey.meeting.3}">Get help with deployment, integrations, or troubleshooting</span>
|
||||
</p>
|
||||
<p><span>📢</span><span th:text="#{survey.meeting.4}">Provide direct feedback on performance, edge cases, and feature gaps</span>
|
||||
</p>
|
||||
<p><span>🔍</span><span th:text="#{survey.meeting.5}">Help us refine Stirling PDF for real-world enterprise use</span>
|
||||
</p>
|
||||
<p th:text="#{survey.meeting.6}">If you're interested, you can book time with our team directly.</p>
|
||||
<p th:text="#{survey.meeting.7}">Looking forward to digging into your use cases and making Stirling PDF even
|
||||
better!</p>
|
||||
<a href="https://calendly.com/d/crsr-tz6-487" target="_blank" class="btn btn-primary" id="takeSurvey2"
|
||||
th:text="#{survey.meeting.button}">Book meeting</a>
|
||||
</br>
|
||||
</br>
|
||||
<p th:text="#{survey.meeting.notInterested}">Not a business and/or interested in a meeting?</p>
|
||||
|
||||
<p th:text="#{survey.please}">Please consider taking our survey!</p>
|
||||
<a href="https://stirlingpdf.info/s/cm28y3niq000o56dv7liv8wsu" target="_blank" class="btn btn-primary"
|
||||
id="takeSurvey" th:text="#{survey.button}">Take Survey</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" id="dontShowAgain">
|
||||
<label for="dontShowAgain" th:text="#{survey.dontShowAgain}">Don't show again</label>
|
||||
<!-- Survey Modal -->
|
||||
<div class="modal fade" id="surveyModal" tabindex="-1" role="dialog" aria-labelledby="surveyModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="surveyModalLabel" th:text="#{survey.title}">Stirling-PDF Survey</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p th:text="#{survey.meeting.1}">If you're using Stirling PDF at work, we'd love to speak to you. We're offering free technical support in exchange for a 15 minute user discovery session.</p>
|
||||
<p th:text="#{survey.meeting.2}">This is a chance to:</p>
|
||||
<p><span>🛠️</span><span th:text="#{survey.meeting.3}">Get help with deployment, integrations, or troubleshooting</span></p>
|
||||
<p><span>📢</span><span th:text="#{survey.meeting.4}">Provide direct feedback on performance, edge cases, and feature gaps</span></p>
|
||||
<p><span>🔍</span><span th:text="#{survey.meeting.5}">Help us refine Stirling PDF for real-world enterprise use</span></p>
|
||||
<p th:text="#{survey.meeting.6}">If you're interested, you can book time with our team directly.</p>
|
||||
<p th:text="#{survey.meeting.7}">Looking forward to digging into your use cases and making Stirling PDF even better!</p>
|
||||
<a href="https://calendly.com/d/crsr-tz6-487" target="_blank" class="btn btn-primary" id="takeSurvey2" th:text="#{survey.meeting.button}">Book meeting</a>
|
||||
</br>
|
||||
</br>
|
||||
<p th:text="#{survey.meeting.notInterested}">Not a business and/or interested in a meeting?</p>
|
||||
|
||||
<p th:text="#{survey.please}">Please consider taking our survey!</p>
|
||||
<a href="https://stirlingpdf.info/s/cm28y3niq000o56dv7liv8wsu" target="_blank" class="btn btn-primary"
|
||||
id="takeSurvey" th:text="#{survey.button}">Take Survey</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" id="dontShowAgain">
|
||||
<label for="dontShowAgain" th:text="#{survey.dontShowAgain}">Don't show again</label>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}">Close</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}">Close</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Analytics Modal -->
|
||||
<div class="modal fade" id="analyticsModal" tabindex="-1" role="dialog" aria-labelledby="analyticsModalLabel"
|
||||
aria-hidden="true" th:if="${@analyticsPrompt}">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="analyticsModalLabel" th:text="#{analytics.title}">Do you want make Stirling PDF
|
||||
better?</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p th:text="#{analytics.paragraph1}">Stirling PDF has opt in analytics to help us improve the product. We do
|
||||
not track any personal information or file contents.</p>
|
||||
<p th:text="#{analytics.paragraph2}">Please consider enabling analytics to help Stirling-PDF grow and to allow
|
||||
us to understand our users better.</p>
|
||||
<p th:text="#{analytics.settings}">You can change the settings for analytics in the config/settings.yml file
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="setAnalytics(false)"
|
||||
th:text="#{analytics.disable}">Disable analytics
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" th:text="#{analytics.enable}"
|
||||
onclick="setAnalytics(true)">Enable analytics
|
||||
</button>
|
||||
<!-- Analytics Modal -->
|
||||
<div class="modal fade" id="analyticsModal" tabindex="-1" role="dialog" aria-labelledby="analyticsModalLabel"
|
||||
aria-hidden="true" th:if="${@analyticsPrompt}">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="analyticsModalLabel" th:text="#{analytics.title}">Do you want make Stirling PDF
|
||||
better?</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p th:text="#{analytics.paragraph1}">Stirling PDF has opt in analytics to help us improve the product. We do
|
||||
not track any personal information or file contents.</p>
|
||||
<p th:text="#{analytics.paragraph2}">Please consider enabling analytics to help Stirling-PDF grow and to allow
|
||||
us to understand our users better.</p>
|
||||
<p th:text="#{analytics.settings}">You can change the settings for analytics in the config/settings.yml file
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="setAnalytics(false)"
|
||||
th:text="#{analytics.disable}">Disable analytics</button>
|
||||
<button type="button" class="btn btn-primary" th:text="#{analytics.enable}"
|
||||
onclick="setAnalytics(true)">Enable analytics</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.favorite-icon {
|
||||
cursor: pointer;
|
||||
width: 0rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
<style>
|
||||
.favorite-icon {
|
||||
cursor: pointer;
|
||||
width: 0rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.toggle-favourites {
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle-favourites {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-favourites.active {
|
||||
color: gold;
|
||||
}
|
||||
</style>
|
||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
window.analyticsPromptBoolean = /*[[${@analyticsPrompt}]]*/ false;
|
||||
/*]]>*/
|
||||
|
||||
window.showSurvey = /*[[${showSurveyFromDocker}]]*/ true
|
||||
</script>
|
||||
<script th:src="@{'/js/pages/home.js'}" th:inline="javascript"></script>
|
||||
<script>
|
||||
function applyScale() {
|
||||
const baseWidth = 1440;
|
||||
const baseHeight = 1000;
|
||||
const scaleX = window.innerWidth / baseWidth;
|
||||
const scaleY = window.innerHeight / baseHeight;
|
||||
const scale = Math.max(0.9, Math.min(scaleX, scaleY)); // keep aspect ratio, honor minScale
|
||||
const ui = document.getElementById('scale-wrap');
|
||||
ui.style.transform = `scale(${scale*0.75})`;
|
||||
}
|
||||
|
||||
window.addEventListener('resize', applyScale);
|
||||
window.addEventListener('load', applyScale);
|
||||
</script>
|
||||
.toggle-favourites.active {
|
||||
color: gold;
|
||||
}
|
||||
</style>
|
||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
window.analyticsPromptBoolean = /*[[${@analyticsPrompt}]]*/ false;
|
||||
/*]]>*/
|
||||
|
||||
window.showSurvey = /*[[${showSurveyFromDocker}]]*/ true
|
||||
</script>
|
||||
<script th:src="@{'/js/pages/home.js'}" th:inline="javascript"></script>
|
||||
<script>
|
||||
function applyScale() {
|
||||
const baseWidth = 1440;
|
||||
const baseHeight = 1000;
|
||||
const scaleX = window.innerWidth / baseWidth;
|
||||
const scaleY = window.innerHeight / baseHeight;
|
||||
const scale = Math.max(0.9, Math.min(scaleX, scaleY)); // keep aspect ratio, honor minScale
|
||||
const ui = document.getElementById('scale-wrap');
|
||||
ui.style.transform = `scale(${scale*0.75})`;
|
||||
}
|
||||
|
||||
window.addEventListener('resize', applyScale);
|
||||
window.addEventListener('load', applyScale);
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{fakeScan.title}, header=#{fakeScan.header})}"></th:block>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{fakeScan.title}, header=#{fakeScan.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -12,12 +12,85 @@
|
||||
<br><br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{fakeScan.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/fake-scan'}">
|
||||
<div class="col-md-6 bg-card">
|
||||
<div class="tool-header">
|
||||
<span class="material-symbols-rounded tool-header-icon advance">scanner</span>
|
||||
<span class="tool-header-text" th:text="#{fakeScan.title}"></span>
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" id="uploadForm" th:action="@{'/api/v1/misc/fake-scan'}">
|
||||
<input type="hidden" name="advancedEnabled" id="advancedEnabled" value="false">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{fakeScan.submit}"></button>
|
||||
<div class="mb-3">
|
||||
<label for="quality" class="form-label" th:text="#{fakeScan.quality}"></label>
|
||||
<select class="form-select" id="quality" name="quality">
|
||||
<option value="low" th:text="#{fakeScan.quality.low}"></option>
|
||||
<option value="medium" th:text="#{fakeScan.quality.medium}"></option>
|
||||
<option value="high" th:text="#{fakeScan.quality.high}" selected></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rotation" class="form-label" th:text="#{fakeScan.rotation}"></label>
|
||||
<select class="form-select" id="rotation" name="rotation">
|
||||
<option value="none" th:text="#{fakeScan.rotation.none}"></option>
|
||||
<option value="slight" th:text="#{fakeScan.rotation.slight}" selected></option>
|
||||
<option value="moderate" th:text="#{fakeScan.rotation.moderate}"></option>
|
||||
<option value="severe" th:text="#{fakeScan.rotation.severe}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="advancedSettingsToggle">
|
||||
<label class="form-check-label" for="advancedSettingsToggle" th:text="#{fakeScan.advancedSettings}"></label>
|
||||
</div>
|
||||
<div id="advancedSettings" style="display:none; border:1px solid #eee; padding:1em; border-radius:8px; margin-bottom:1em;">
|
||||
<div class="mb-3">
|
||||
<label for="colorspace" class="form-label" th:text="#{fakeScan.colorspace}"></label>
|
||||
<select class="form-select" id="colorspace" name="colorspace">
|
||||
<option value="grayscale" th:text="#{fakeScan.colorspace.grayscale}" selected></option>
|
||||
<option value="color" th:text="#{fakeScan.colorspace.color}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="border" class="form-label" th:text="#{fakeScan.border}"></label>
|
||||
<input type="number" class="form-control" id="border" name="border" min="0" max="100" value="20">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rotate" class="form-label" th:text="#{fakeScan.rotate}"></label>
|
||||
<input type="number" class="form-control" id="rotate" name="rotate" min="-15" max="15" value="0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rotateVariance" class="form-label" th:text="#{fakeScan.rotateVariance}"></label>
|
||||
<input type="number" class="form-control" id="rotateVariance" name="rotateVariance" min="0" max="10" value="2">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="brightness" class="form-label" th:text="#{fakeScan.brightness}"></label>
|
||||
<input type="range" class="form-range" id="brightness" name="brightness" min="0.5" max="1.5" step="0.01" value="1.0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="contrast" class="form-label" th:text="#{fakeScan.contrast}"></label>
|
||||
<input type="range" class="form-range" id="contrast" name="contrast" min="0.5" max="1.5" step="0.01" value="1.0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="blur" class="form-label" th:text="#{fakeScan.blur}"></label>
|
||||
<input type="range" class="form-range" id="blur" name="blur" min="0" max="5" step="0.1" value="1.0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="noise" class="form-label" th:text="#{fakeScan.noise}"></label>
|
||||
<input type="range" class="form-range" id="noise" name="noise" min="0" max="20" step="0.1" value="8.0">
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="yellowish" name="yellowish">
|
||||
<label class="form-check-label" for="yellowish" th:text="#{fakeScan.yellowish}"></label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="resolution" class="form-label" th:text="#{fakeScan.resolution}"></label>
|
||||
<input type="number" class="form-control" id="resolution" name="resolution" min="72" max="600" value="300">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 text-left">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{fakeScan.submit}" id="submitBtn"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -25,5 +98,66 @@
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
||||
<script th:inline="javascript">
|
||||
// Show/hide advanced settings
|
||||
document.getElementById('advancedSettingsToggle').addEventListener('change', function() {
|
||||
document.getElementById('advancedSettings').style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Form submission handling
|
||||
const form = document.getElementById('uploadForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
// If advanced settings are not enabled, remove advanced fields
|
||||
if (!document.getElementById('advancedSettingsToggle').checked) {
|
||||
const formData = new FormData(form);
|
||||
formData.delete('colorspace');
|
||||
formData.delete('border');
|
||||
formData.delete('rotate');
|
||||
formData.delete('rotateVariance');
|
||||
formData.delete('brightness');
|
||||
formData.delete('contrast');
|
||||
formData.delete('blur');
|
||||
formData.delete('noise');
|
||||
formData.delete('yellowish');
|
||||
formData.delete('resolution');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize advanced settings state
|
||||
const advancedSettingsToggle = document.getElementById('advancedSettingsToggle');
|
||||
const advancedSettings = document.getElementById('advancedSettings');
|
||||
if (advancedSettingsToggle && advancedSettings) {
|
||||
// Helper: enable/disable all fields in advanced settings
|
||||
function setAdvancedFieldsEnabled(enabled) {
|
||||
const fields = advancedSettings.querySelectorAll('input, select');
|
||||
fields.forEach(field => {
|
||||
field.disabled = !enabled;
|
||||
// If enabling and value is empty, set to default
|
||||
if (enabled && (field.value === '' || field.value == null)) {
|
||||
if (field.type === 'number' || field.type === 'range') {
|
||||
field.value = field.defaultValue;
|
||||
} else if (field.type === 'checkbox') {
|
||||
field.checked = field.defaultChecked;
|
||||
} else if (field.tagName === 'SELECT') {
|
||||
field.value = field.querySelector('option[selected]')?.value || field.options[0].value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set initial state
|
||||
setAdvancedFieldsEnabled(advancedSettingsToggle.checked);
|
||||
advancedSettings.style.display = advancedSettingsToggle.checked ? 'block' : 'none';
|
||||
document.getElementById('advancedEnabled').value = advancedSettingsToggle.checked ? 'true' : 'false';
|
||||
// Add change listener
|
||||
advancedSettingsToggle.addEventListener('change', function() {
|
||||
advancedSettings.style.display = this.checked ? 'block' : 'none';
|
||||
setAdvancedFieldsEnabled(this.checked);
|
||||
document.getElementById('advancedEnabled').value = this.checked ? 'true' : 'false';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -11,7 +11,7 @@
|
||||
<br><br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 bg-card">
|
||||
<div class="col-md-7 bg-card">
|
||||
<div class="tool-header">
|
||||
<span class="material-symbols-rounded tool-header-icon other">info</span>
|
||||
<span class="tool-header-text" th:text="#{getPdfInfo.header}"></span>
|
||||
@ -22,6 +22,82 @@
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{getPdfInfo.submit}"></button>
|
||||
</form>
|
||||
<div class="container mt-0">
|
||||
<!-- PDF Summary section -->
|
||||
<div id="pdf-summary" class="card mt-3 mb-3" style="display: none;">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" id="summary-heading">PDF Summary</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Quick overview of key details -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 id="summary-basic-info-heading">Basic Information</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><strong>Pages:</strong> <span id="summary-pages">-</span></li>
|
||||
<li><strong>File Size:</strong> <span id="summary-size">-</span></li>
|
||||
<li><strong>PDF Version:</strong> <span id="summary-version">-</span></li>
|
||||
<li><strong>Language:</strong> <span id="summary-language">-</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 id="summary-doc-info-heading">Document Information</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><strong>Title:</strong> <span id="summary-title">-</span></li>
|
||||
<li><strong>Author:</strong> <span id="summary-author">-</span></li>
|
||||
<li><strong>Created:</strong> <span id="summary-created">-</span></li>
|
||||
<li><strong>Modified:</strong> <span id="summary-modified">-</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security section -->
|
||||
<div class="mt-4 mb-3">
|
||||
<h6 id="summary-security-heading">Security Status</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div id="encryption-status" class="card mb-2 h-100">
|
||||
<div class="card-body p-2 d-flex align-items-center">
|
||||
<span id="encryption-icon" class="me-2"><i class="bi bi-lock"></i></span>
|
||||
<span id="encryption-text" class="small">Encryption: Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div id="permissions-status" class="card mb-2 h-100">
|
||||
<div class="card-body p-2 d-flex align-items-center">
|
||||
<span id="permissions-icon" class="me-2"><i class="bi bi-shield"></i></span>
|
||||
<span id="permissions-text" class="small">Permissions: Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div id="compliance-status" class="card mb-2 h-100">
|
||||
<div class="card-body p-2 d-flex align-items-center">
|
||||
<span id="compliance-icon" class="me-2"><i class="bi bi-check-circle"></i></span>
|
||||
<span id="compliance-text" class="small">Compliance: Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detailed alerts -->
|
||||
<div id="summary-alerts" class="mt-3">
|
||||
<!-- Will be populated with detailed alerts for encryption, permissions, etc. -->
|
||||
</div>
|
||||
|
||||
<!-- Descriptive note about PDF characteristics -->
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">PDF Overview</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p id="summary-text" class="mb-0"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Iterate over each main section in the JSON -->
|
||||
<div id="json-content">
|
||||
<!-- JavaScript will populate this section -->
|
||||
@ -31,10 +107,48 @@
|
||||
<a href="#" id="downloadJson" class="btn btn-primary mt-3" style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download JSON</a>
|
||||
</div>
|
||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
||||
<script>
|
||||
<script th:inline="javascript">
|
||||
// Pre-load message translations
|
||||
const getPdfInfoSummary = /*[[#{getPdfInfo.summary}]]*/ "PDF Summary";
|
||||
const getPdfInfoSummaryEncrypted = /*[[#{getPdfInfo.summary.encrypted}]]*/ "This PDF is encrypted so may face issues with some applications";
|
||||
const getPdfInfoSummaryPermissions = /*[[#{getPdfInfo.summary.permissions}]]*/ "This PDF has {0} restricted permissions which may limit what you can do with it";
|
||||
const getPdfInfoSummaryCompliance = /*[[#{getPdfInfo.summary.compliance}]]*/ "This PDF complies with the {0} standard, meaning it is suitable for {1}";
|
||||
const getPdfInfoSummaryBasicInfo = /*[[#{getPdfInfo.summary.basicInfo}]]*/ "Basic Information";
|
||||
const getPdfInfoSummaryDocInfo = /*[[#{getPdfInfo.summary.docInfo}]]*/ "Document Information";
|
||||
const getPdfInfoSummarySecuritySection = /*[[#{getPdfInfo.summary.security.section}]]*/ "Security Status";
|
||||
const getPdfInfoSummaryEncryptedAlert = /*[[#{getPdfInfo.summary.encrypted.alert}]]*/ "Encrypted PDF - This document is password protected";
|
||||
const getPdfInfoSummaryNotEncryptedAlert = /*[[#{getPdfInfo.summary.not.encrypted.alert}]]*/ "Unencrypted PDF - No password protection";
|
||||
const getPdfInfoSummaryPermissionsAlert = /*[[#{getPdfInfo.summary.permissions.alert}]]*/ "Restricted Permissions - {0} actions are not allowed";
|
||||
const getPdfInfoSummaryAllPermissionsAlert = /*[[#{getPdfInfo.summary.all.permissions.alert}]]*/ "All Permissions Allowed";
|
||||
const getPdfInfoSummaryComplianceAlert = /*[[#{getPdfInfo.summary.compliance.alert}]]*/ "{0} Compliant";
|
||||
const getPdfInfoSummaryNoComplianceAlert = /*[[#{getPdfInfo.summary.no.compliance.alert}]]*/ "No Compliance Standards";
|
||||
|
||||
// Update the summary headings
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('summary-heading').textContent = getPdfInfoSummary;
|
||||
document.getElementById('summary-basic-info-heading').textContent = getPdfInfoSummaryBasicInfo;
|
||||
document.getElementById('summary-doc-info-heading').textContent = getPdfInfoSummaryDocInfo;
|
||||
document.getElementById('summary-security-heading').textContent = getPdfInfoSummarySecuritySection;
|
||||
});
|
||||
|
||||
// Pre-load section descriptions
|
||||
const getPdfInfoSectionBasicInfo = /*[[#{getPdfInfo.section.BasicInfo}]]*/ "Basic Information about the PDF document including file size, page count, and language";
|
||||
const getPdfInfoSectionMetadata = /*[[#{getPdfInfo.section.Metadata}]]*/ "Document metadata including title, author, creation date and other document properties";
|
||||
const getPdfInfoSectionDocumentInfo = /*[[#{getPdfInfo.section.DocumentInfo}]]*/ "Technical details about the PDF document structure and version";
|
||||
const getPdfInfoSectionCompliancy = /*[[#{getPdfInfo.section.Compliancy}]]*/ "PDF standards compliance information (PDF/A, PDF/X, etc.)";
|
||||
const getPdfInfoSectionEncryption = /*[[#{getPdfInfo.section.Encryption}]]*/ "Security and encryption details of the document";
|
||||
const getPdfInfoSectionPermissions = /*[[#{getPdfInfo.section.Permissions}]]*/ "Document permission settings that control what actions can be performed";
|
||||
const getPdfInfoSectionOther = /*[[#{getPdfInfo.section.Other}]]*/ "Additional document components like bookmarks, layers, and embedded files";
|
||||
const getPdfInfoSectionFormFields = /*[[#{getPdfInfo.section.FormFields}]]*/ "Interactive form fields present in the document";
|
||||
const getPdfInfoSectionPerPageInfo = /*[[#{getPdfInfo.section.PerPageInfo}]]*/ "Detailed information about each page in the document";
|
||||
|
||||
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Clear previous results when submitting a new form
|
||||
document.getElementById('json-content').innerHTML = '';
|
||||
document.getElementById('pdf-summary').style.display = 'none';
|
||||
document.getElementById('downloadJson').style.display = 'none';
|
||||
|
||||
const formData = new FormData(event.target);
|
||||
|
||||
@ -42,10 +156,330 @@
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => response.json()).then(data => {
|
||||
// Populate and display the enhanced PDF summary
|
||||
populateSummarySection(data);
|
||||
|
||||
displayJsonData(data);
|
||||
setDownloadLink(data);
|
||||
document.getElementById("downloadJson").style.display = "block";
|
||||
}).catch(error => console.error('Error:', error));
|
||||
|
||||
// Function to reset all summary elements to default state
|
||||
function resetSummaryElements() {
|
||||
// Reset basic information fields
|
||||
document.getElementById('summary-pages').textContent = '-';
|
||||
document.getElementById('summary-size').textContent = '-';
|
||||
document.getElementById('summary-version').textContent = '-';
|
||||
document.getElementById('summary-language').textContent = '-';
|
||||
|
||||
// Reset document information fields
|
||||
document.getElementById('summary-title').textContent = '-';
|
||||
document.getElementById('summary-author').textContent = '-';
|
||||
document.getElementById('summary-created').textContent = '-';
|
||||
document.getElementById('summary-modified').textContent = '-';
|
||||
|
||||
// Reset security status cards
|
||||
const cards = ['encryption-status', 'permissions-status', 'compliance-status'];
|
||||
cards.forEach(id => {
|
||||
const card = document.getElementById(id);
|
||||
// Remove all classes except the base ones
|
||||
card.className = 'card mb-2 h-100';
|
||||
});
|
||||
|
||||
// Reset status text and icons
|
||||
document.getElementById('encryption-icon').innerHTML = '<i class="bi bi-lock"></i>';
|
||||
document.getElementById('encryption-text').textContent = 'Encryption: Unknown';
|
||||
|
||||
document.getElementById('permissions-icon').innerHTML = '<i class="bi bi-shield"></i>';
|
||||
document.getElementById('permissions-text').textContent = 'Permissions: Unknown';
|
||||
|
||||
document.getElementById('compliance-icon').innerHTML = '<i class="bi bi-check-circle"></i>';
|
||||
document.getElementById('compliance-text').textContent = 'Compliance: Unknown';
|
||||
|
||||
// Clear alerts container
|
||||
document.getElementById('summary-alerts').innerHTML = '';
|
||||
|
||||
// Reset summary text
|
||||
document.getElementById('summary-text').innerHTML = '';
|
||||
}
|
||||
|
||||
// Function to populate the enhanced summary section
|
||||
function populateSummarySection(data) {
|
||||
// Reset all elements first
|
||||
resetSummaryElements();
|
||||
|
||||
// Get basic information
|
||||
if (data.BasicInfo) {
|
||||
document.getElementById('summary-pages').textContent = data.BasicInfo["Number of pages"] || "-";
|
||||
|
||||
// Format file size nicely
|
||||
let fileSize = data.BasicInfo["FileSizeInBytes"];
|
||||
if (fileSize) {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(fileSize) / Math.log(1024));
|
||||
fileSize = (fileSize / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
|
||||
document.getElementById('summary-size').textContent = fileSize;
|
||||
}
|
||||
|
||||
document.getElementById('summary-language').textContent = data.BasicInfo["Language"] || "-";
|
||||
}
|
||||
|
||||
// Get document information
|
||||
if (data.DocumentInfo) {
|
||||
document.getElementById('summary-version').textContent = data.DocumentInfo["PDF version"] || "-";
|
||||
}
|
||||
|
||||
// Get metadata
|
||||
if (data.Metadata) {
|
||||
document.getElementById('summary-title').textContent = data.Metadata["Title"] || "-";
|
||||
document.getElementById('summary-author').textContent = data.Metadata["Author"] || "-";
|
||||
document.getElementById('summary-created').textContent = data.Metadata["CreationDate"] || "-";
|
||||
document.getElementById('summary-modified').textContent = data.Metadata["ModificationDate"] || "-";
|
||||
}
|
||||
|
||||
// Update security status cards
|
||||
|
||||
// Encryption status
|
||||
const encryptionStatusCard = document.getElementById('encryption-status');
|
||||
const encryptionIcon = document.getElementById('encryption-icon');
|
||||
const encryptionText = document.getElementById('encryption-text');
|
||||
|
||||
if (data.Encryption && data.Encryption.IsEncrypted) {
|
||||
encryptionIcon.innerHTML = '<i class="bi bi-lock-fill"></i>';
|
||||
encryptionText.textContent = getPdfInfoSummaryEncryptedAlert;
|
||||
} else {
|
||||
encryptionIcon.innerHTML = '<i class="bi bi-unlock-fill"></i>';
|
||||
encryptionText.textContent = getPdfInfoSummaryNotEncryptedAlert;
|
||||
}
|
||||
|
||||
// Permissions status
|
||||
const permissionsStatusCard = document.getElementById('permissions-status');
|
||||
const permissionsIcon = document.getElementById('permissions-icon');
|
||||
const permissionsText = document.getElementById('permissions-text');
|
||||
|
||||
let restrictedPermissions = [];
|
||||
if (data.Permissions) {
|
||||
for (const [permission, state] of Object.entries(data.Permissions)) {
|
||||
if (state === "Not Allowed") {
|
||||
restrictedPermissions.push(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (restrictedPermissions.length > 0) {
|
||||
permissionsIcon.innerHTML = '<i class="bi bi-shield-lock-fill"></i>';
|
||||
const formattedAlert = getPdfInfoSummaryPermissionsAlert.replace('{0}', restrictedPermissions.length);
|
||||
permissionsText.textContent = formattedAlert;
|
||||
} else {
|
||||
permissionsIcon.innerHTML = '<i class="bi bi-shield-check"></i>';
|
||||
permissionsText.textContent = getPdfInfoSummaryAllPermissionsAlert;
|
||||
}
|
||||
|
||||
// Compliance status
|
||||
const complianceStatusCard = document.getElementById('compliance-status');
|
||||
const complianceIcon = document.getElementById('compliance-icon');
|
||||
const complianceText = document.getElementById('compliance-text');
|
||||
|
||||
let hasCompliance = false;
|
||||
let compliantStandards = [];
|
||||
|
||||
if (data.Compliancy) {
|
||||
for (const [standard, compliant] of Object.entries(data.Compliancy)) {
|
||||
if (compliant === true) {
|
||||
hasCompliance = true;
|
||||
const standardName = standard.replace("Is", "").replace("Compliant", "");
|
||||
compliantStandards.push(standardName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCompliance) {
|
||||
complianceIcon.innerHTML = '<i class="bi bi-check-circle-fill"></i>';
|
||||
const formattedAlert = getPdfInfoSummaryComplianceAlert.replace('{0}', compliantStandards.join(', '));
|
||||
complianceText.textContent = formattedAlert;
|
||||
} else {
|
||||
complianceIcon.innerHTML = '<i class="bi bi-dash-circle"></i>';
|
||||
complianceText.textContent = getPdfInfoSummaryNoComplianceAlert;
|
||||
}
|
||||
|
||||
// Create detailed characteristic alerts
|
||||
const alertsContainer = document.getElementById('summary-alerts');
|
||||
|
||||
// Clear previous alerts
|
||||
alertsContainer.innerHTML = '';
|
||||
|
||||
// Create a single comprehensive security details section
|
||||
let hasSummaryInfo = false;
|
||||
|
||||
// Create a consolidated security details card if there are security details worth highlighting
|
||||
if ((data.Encryption && data.Encryption.IsEncrypted) ||
|
||||
restrictedPermissions.length > 0 ||
|
||||
hasCompliance) {
|
||||
|
||||
const securityDetailsCard = document.createElement('div');
|
||||
securityDetailsCard.className = 'card mt-3 mb-3';
|
||||
|
||||
const cardHeader = document.createElement('div');
|
||||
cardHeader.className = 'card-header';
|
||||
cardHeader.innerHTML = '<h6 class="mb-0">Detailed Security Information</h6>';
|
||||
securityDetailsCard.appendChild(cardHeader);
|
||||
|
||||
const cardBody = document.createElement('div');
|
||||
cardBody.className = 'card-body';
|
||||
|
||||
// Add detailed encryption info
|
||||
if (data.Encryption && data.Encryption.IsEncrypted) {
|
||||
const encryptionDiv = document.createElement('div');
|
||||
encryptionDiv.className = 'mb-3';
|
||||
encryptionDiv.innerHTML = '<h6>Encryption Details:</h6>';
|
||||
|
||||
const encryptionList = document.createElement('ul');
|
||||
encryptionList.className = 'list-unstyled';
|
||||
|
||||
if (data.Encryption.EncryptionAlgorithm) {
|
||||
encryptionList.innerHTML += `<li><strong>Algorithm:</strong> ${data.Encryption.EncryptionAlgorithm}</li>`;
|
||||
}
|
||||
if (data.Encryption.KeyLength) {
|
||||
encryptionList.innerHTML += `<li><strong>Key Length:</strong> ${data.Encryption.KeyLength} bits</li>`;
|
||||
}
|
||||
|
||||
encryptionDiv.appendChild(encryptionList);
|
||||
cardBody.appendChild(encryptionDiv);
|
||||
hasSummaryInfo = true;
|
||||
}
|
||||
|
||||
// Add detailed permissions info
|
||||
if (restrictedPermissions.length > 0) {
|
||||
const permissionsDiv = document.createElement('div');
|
||||
permissionsDiv.className = 'mb-3';
|
||||
permissionsDiv.innerHTML = '<h6>Restricted Permissions:</h6>';
|
||||
|
||||
const permissionsList = document.createElement('ul');
|
||||
restrictedPermissions.forEach(perm => {
|
||||
permissionsList.innerHTML += `<li>${perm}</li>`;
|
||||
});
|
||||
|
||||
permissionsDiv.appendChild(permissionsList);
|
||||
cardBody.appendChild(permissionsDiv);
|
||||
hasSummaryInfo = true;
|
||||
}
|
||||
|
||||
// Add detailed compliance info
|
||||
if (hasCompliance) {
|
||||
const complianceDiv = document.createElement('div');
|
||||
complianceDiv.className = 'mb-3';
|
||||
complianceDiv.innerHTML = '<h6>Standards Compliance:</h6>';
|
||||
|
||||
const complianceList = document.createElement('ul');
|
||||
complianceList.className = 'list-unstyled';
|
||||
|
||||
compliantStandards.forEach(standard => {
|
||||
let standardDescription = '';
|
||||
|
||||
// Add brief descriptions for standards
|
||||
if (standard === "PDF/A") {
|
||||
standardDescription = 'ISO standard for long-term document archiving';
|
||||
} else if (standard === "PDF/X") {
|
||||
standardDescription = 'ISO standard for printing and graphic arts exchange';
|
||||
} else if (standard === "PDF/UA") {
|
||||
standardDescription = 'ISO standard for universal accessibility';
|
||||
} else if (standard === "PDF/E") {
|
||||
standardDescription = 'ISO standard for engineering documents';
|
||||
} else if (standard === "PDF/VT") {
|
||||
standardDescription = 'ISO standard for variable and transactional printing';
|
||||
}
|
||||
|
||||
complianceList.innerHTML += `<li><strong>${standard}:</strong> ${standardDescription}</li>`;
|
||||
});
|
||||
|
||||
complianceDiv.appendChild(complianceList);
|
||||
cardBody.appendChild(complianceDiv);
|
||||
hasSummaryInfo = true;
|
||||
}
|
||||
|
||||
securityDetailsCard.appendChild(cardBody);
|
||||
|
||||
if (hasSummaryInfo) {
|
||||
alertsContainer.appendChild(securityDetailsCard);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a general document summary
|
||||
const summaryTextElement = document.getElementById('summary-text');
|
||||
|
||||
// Create a general summary for the document
|
||||
let generalSummary = `This is a ${data.BasicInfo["Number of pages"] || "multi"}-page PDF`;
|
||||
|
||||
if (data.Metadata && data.Metadata["Title"]) {
|
||||
generalSummary += ` titled "${data.Metadata["Title"]}"`;
|
||||
}
|
||||
|
||||
if (data.Metadata && data.Metadata["Author"]) {
|
||||
generalSummary += ` created by ${data.Metadata["Author"]}`;
|
||||
}
|
||||
|
||||
if (data.DocumentInfo && data.DocumentInfo["PDF version"]) {
|
||||
generalSummary += ` (PDF version ${data.DocumentInfo["PDF version"]})`;
|
||||
}
|
||||
|
||||
// Add security information to the general summary if relevant
|
||||
if (data.Encryption && data.Encryption.IsEncrypted) {
|
||||
generalSummary += '. The document is password protected';
|
||||
|
||||
if (data.Encryption.EncryptionAlgorithm) {
|
||||
generalSummary += ` using ${data.Encryption.EncryptionAlgorithm}`;
|
||||
|
||||
if (data.Encryption.KeyLength) {
|
||||
generalSummary += ` (${data.Encryption.KeyLength} bit)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (restrictedPermissions.length > 0) {
|
||||
generalSummary += `. It has ${restrictedPermissions.length} restricted permissions`;
|
||||
}
|
||||
|
||||
// Add compliance standards if available
|
||||
if (hasCompliance && compliantStandards.length > 0) {
|
||||
generalSummary += `. This document complies with the ${compliantStandards.join(', ')} PDF standard${compliantStandards.length > 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
generalSummary += '.';
|
||||
|
||||
// Remove SummaryData from JSON to avoid duplication
|
||||
if (data.SummaryData) {
|
||||
delete data.SummaryData;
|
||||
}
|
||||
|
||||
summaryTextElement.innerHTML = generalSummary;
|
||||
|
||||
// Display the summary section
|
||||
document.getElementById('pdf-summary').style.display = 'block';
|
||||
}
|
||||
|
||||
function generateSummaryFromData(summaryData) {
|
||||
let summary = [];
|
||||
|
||||
// Handle encryption information
|
||||
if (summaryData.encrypted) {
|
||||
summary.push(getPdfInfoSummaryEncrypted);
|
||||
}
|
||||
|
||||
// Handle permissions information
|
||||
if (summaryData.restrictedPermissions && summaryData.restrictedPermissions.length > 0) {
|
||||
const formattedPermissionsText = getPdfInfoSummaryPermissions.replace('{0}', summaryData.restrictedPermissionsCount);
|
||||
summary.push(formattedPermissionsText);
|
||||
}
|
||||
|
||||
// Handle standard compliance information
|
||||
if (summaryData.standardCompliance) {
|
||||
const formattedComplianceText = getPdfInfoSummaryCompliance
|
||||
.replace('{0}', summaryData.standardCompliance);
|
||||
summary.push(formattedComplianceText);
|
||||
}
|
||||
|
||||
return summary.join(' ');
|
||||
}
|
||||
});
|
||||
|
||||
function displayJsonData(jsonData) {
|
||||
@ -77,8 +511,9 @@
|
||||
header.className = 'card-header';
|
||||
header.id = `${safeKey}-heading-${depth}`;
|
||||
const h5Elem = document.createElement('h5');
|
||||
h5Elem.className = 'mb-0';
|
||||
h5Elem.className = 'mb-0 d-flex align-items-center';
|
||||
|
||||
// Create the main content (button or text)
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||
h5Elem.appendChild(buttonElem);
|
||||
@ -94,6 +529,8 @@
|
||||
} else {
|
||||
h5Elem.textContent = `${key}: ${String(value)}`;
|
||||
}
|
||||
|
||||
// Info buttons removed as requested
|
||||
|
||||
header.appendChild(h5Elem);
|
||||
card.appendChild(header);
|
||||
|
Loading…
x
Reference in New Issue
Block a user