Fix: Thymeleaf syntax (/*[[...]]*/) (#2659)

# Description

Please provide a summary of the changes, including relevant motivation
and context.

Closes #(issue_number)

## Checklist

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [x] My changes generate no new warnings
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
This commit is contained in:
Ludy 2025-01-12 01:18:35 +01:00 committed by GitHub
parent b2da426cc1
commit 76cbf94fdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 183 additions and 186 deletions

View File

@ -1,10 +1,10 @@
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
var cacheInputs = localStorage.getItem("cacheInputs") || "disabled"; var cacheInputs = localStorage.getItem("cacheInputs") || "disabled";
if (cacheInputs !== "enabled") { if (cacheInputs !== "enabled") {
return; // Stop execution if caching is not enabled return; // Stop execution if caching is not enabled
} }
// Function to generate a key based on the form's action attribute // Function to generate a key based on the form's action attribute
function generateStorageKey(form) { function generateStorageKey(form) {
const action = form.getAttribute('action'); const action = form.getAttribute('action');
@ -24,11 +24,11 @@ document.addEventListener("DOMContentLoaded", function() {
for (let i = 0; i < elements.length; i++) { for (let i = 0; i < elements.length; i++) {
const element = elements[i]; const element = elements[i];
// Skip elements without names, passwords, files, hidden fields, and submit/reset buttons // Skip elements without names, passwords, files, hidden fields, and submit/reset buttons
if (!element.name || if (!element.name ||
element.type === 'password' || element.type === 'password' ||
element.type === 'file' || element.type === 'file' ||
//element.type === 'hidden' || //element.type === 'hidden' ||
element.type === 'submit' || element.type === 'submit' ||
element.type === 'reset') { element.type === 'reset') {
continue; continue;
} }

View File

@ -18,7 +18,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Find all forms and add CSRF token // Find all forms and add CSRF token
const forms = document.querySelectorAll('form'); const forms = document.querySelectorAll('form');
const csrfToken = decodeCsrfToken(getCsrfToken()); const csrfToken = decodeCsrfToken(getCsrfToken());
// Only proceed if we have a cookie-based token // Only proceed if we have a cookie-based token
if (csrfToken) { if (csrfToken) {
forms.forEach(form => { forms.forEach(form => {
@ -34,4 +34,4 @@ document.addEventListener('DOMContentLoaded', function() {
form.appendChild(csrfInput); form.appendChild(csrfInput);
}); });
} }
}); });

View File

@ -102,4 +102,4 @@ document.addEventListener("DOMContentLoaded", function () {
toggleDarkMode(); toggleDarkMode();
}); });
} }
}); });

View File

@ -4,7 +4,7 @@ window.fetchWithCsrf = async function(url, options = {}) {
.split('; ') .split('; ')
.find(row => row.startsWith('XSRF-TOKEN=')) .find(row => row.startsWith('XSRF-TOKEN='))
?.split('=')[1]; ?.split('=')[1];
if (cookieValue) { if (cookieValue) {
return cookieValue; return cookieValue;
} }
@ -14,10 +14,10 @@ window.fetchWithCsrf = async function(url, options = {}) {
// Create a new options object to avoid modifying the passed object // Create a new options object to avoid modifying the passed object
const fetchOptions = { ...options }; const fetchOptions = { ...options };
// Ensure headers object exists // Ensure headers object exists
fetchOptions.headers = { ...options.headers }; fetchOptions.headers = { ...options.headers };
// Add CSRF token if available // Add CSRF token if available
const csrfToken = getCsrfToken(); const csrfToken = getCsrfToken();
if (csrfToken) { if (csrfToken) {
@ -25,4 +25,4 @@ window.fetchWithCsrf = async function(url, options = {}) {
} }
return fetch(url, fetchOptions); return fetch(url, fetchOptions);
} }

View File

@ -32,8 +32,8 @@ function initializeGame() {
const BASE_SPAWN_INTERVAL_MS = 1250; // milliseconds before a new enemy spawns const BASE_SPAWN_INTERVAL_MS = 1250; // milliseconds before a new enemy spawns
const LEVEL_INCREASE_FACTOR_MS = 25; // milliseconds to decrease the spawn interval per level const LEVEL_INCREASE_FACTOR_MS = 25; // milliseconds to decrease the spawn interval per level
const MAX_SPAWN_RATE_REDUCTION_MS = 800; // Max milliseconds from the base spawn interval const MAX_SPAWN_RATE_REDUCTION_MS = 800; // Max milliseconds from the base spawn interval
let keysPressed = {}; let keysPressed = {};
const pdfs = []; const pdfs = [];
const projectiles = []; const projectiles = [];
@ -42,7 +42,7 @@ function initializeGame() {
let pdfSpeed = BASE_PDF_SPEED; let pdfSpeed = BASE_PDF_SPEED;
let gameOver = false; let gameOver = false;
function handleKeys() { function handleKeys() {
if (keysPressed["ArrowLeft"]) { if (keysPressed["ArrowLeft"]) {
playerX -= PLAYER_MOVE_SPEED; playerX -= PLAYER_MOVE_SPEED;
@ -72,7 +72,7 @@ function initializeGame() {
function onKeyUp(event) { function onKeyUp(event) {
keysPressed[event.key] = false; keysPressed[event.key] = false;
} }
document.removeEventListener("keydown", onKeydown); document.removeEventListener("keydown", onKeydown);
document.removeEventListener("keyup", onKeyUp); document.removeEventListener("keyup", onKeyUp);
document.addEventListener("keydown", onKeydown); document.addEventListener("keydown", onKeydown);
@ -243,7 +243,7 @@ function initializeGame() {
let spawnPdfTimeout; let spawnPdfTimeout;
function spawnPdfInterval() { function spawnPdfInterval() {
if (gameOver || paused) { if (gameOver || paused) {

View File

@ -39,4 +39,3 @@ document.getElementById("cacheInputs").addEventListener("change", function () {
cacheInputs = this.checked ? "enabled" : "disabled"; cacheInputs = this.checked ? "enabled" : "disabled";
localStorage.setItem("cacheInputs", cacheInputs); localStorage.setItem("cacheInputs", cacheInputs);
}); });

View File

@ -308,37 +308,36 @@
document.getElementById('syncToAccount').addEventListener('click', async function() { document.getElementById('syncToAccount').addEventListener('click', async function() {
/*<![CDATA[*/ /*<![CDATA[*/
const urlUpdateUserSettings = /*[[@{/api/v1/user/updateUserSettings}]]*/ "/api/v1/user/updateUserSettings"; const urlUpdateUserSettings = /*[[@{/api/v1/user/updateUserSettings}]]*/ "/api/v1/user/updateUserSettings";
/*]]>*/ /*]]>*/
let settings = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if(key !== 'debug' && key !== '0' && key !== '1' && !key.includes('pdfjs') && !key.includes('posthog') && !key.includes('pageViews')) {
settings[key] = localStorage.getItem(key);
}
}
try {
const response = await window.fetchWithCsrf(urlUpdateUserSettings, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(settings)
});
if (response.ok) {
location.reload();
} else {
alert('Error syncing settings to account');
}
} catch (error) {
console.error('Error:', error);
alert('Error syncing settings to account');
}
});
let settings = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if(key !== 'debug' && key !== '0' && key !== '1' && !key.includes('pdfjs') && !key.includes('posthog') && !key.includes('pageViews')) {
settings[key] = localStorage.getItem(key);
}
}
try {
const response = await window.fetchWithCsrf(urlUpdateUserSettings, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(settings)
});
if (response.ok) {
location.reload();
} else {
alert('Error syncing settings to account');
}
} catch (error) {
console.error('Error:', error);
alert('Error syncing settings to account');
}
});
}); });
</script> </script>
<div class="mb-3 mt-4 text-center"> <div class="mb-3 mt-4 text-center">

View File

@ -16,8 +16,8 @@
<!-- powered by section --> <!-- powered by section -->
<div class="footer-powered-by"> <div class="footer-powered-by">
<span th:text="#{poweredBy}"></span> <span th:text="#{poweredBy}"></span>
<a href="https://stirlingpdf.com" class="stirling-link">Stirling PDF</a> <a href="https://stirlingpdf.com" class="stirling-link">Stirling PDF</a>
</div> </div>
</div> </div>
</footer> </footer>

View File

@ -12,87 +12,87 @@
<div class="container-flex"> <div class="container-flex">
<main class="form-signin"> <main class="form-signin">
<script th:inline="javascript"> <script th:inline="javascript">
const redirectAttempts = parseInt(localStorage.getItem('ssoRedirectAttempts') || '0'); const redirectAttempts = parseInt(localStorage.getItem('ssoRedirectAttempts') || '0');
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const hasRedirectError = urlParams.has('error'); const hasRedirectError = urlParams.has('error');
const hasLogout = urlParams.has('logout'); const hasLogout = urlParams.has('logout');
const hasMessage = urlParams.has('message'); const hasMessage = urlParams.has('message');
const MAX_REDIRECT_ATTEMPTS = 3; const MAX_REDIRECT_ATTEMPTS = 3;
document.addEventListener('modeChanged', function(e) { document.addEventListener('modeChanged', function(e) {
var mode = e.detail; var mode = e.detail;
document.body.classList.remove("light-mode", "dark-mode", "rainbow-mode"); // remove all mode classes first document.body.classList.remove("light-mode", "dark-mode", "rainbow-mode"); // remove all mode classes first
switch (mode) { switch (mode) {
case "on": case "on":
document.body.classList.add("dark-mode"); document.body.classList.add("dark-mode");
break; break;
case "off": case "off":
document.body.classList.add("light-mode"); document.body.classList.add("light-mode");
break; break;
case "rainbow": case "rainbow":
document.body.classList.add("rainbow-mode"); document.body.classList.add("rainbow-mode");
break; break;
} }
}); });
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const runningEE = [[${@runningEE}]]; const runningEE = /*[[${@runningEE}]]*/ false;
const SSOAutoLogin = [[${@SSOAutoLogin}]]; const SSOAutoLogin = /*[[${@SSOAutoLogin}]]*/ false;
const loginMethod = [[${loginMethod}]]; const loginMethod = /*[[${loginMethod}]]*/ 'normal';
const providerList = [[${providerlist}]]; const providerList = /*[[${providerlist}]]*/ {};
const shouldAutoRedirect = !hasRedirectError && const shouldAutoRedirect = !hasRedirectError &&
!hasLogout && !hasLogout &&
!hasMessage && !hasMessage &&
redirectAttempts < MAX_REDIRECT_ATTEMPTS && redirectAttempts < MAX_REDIRECT_ATTEMPTS &&
loginMethod !== 'normal' && runningEE && SSOAutoLogin; loginMethod !== 'normal' && runningEE && SSOAutoLogin;
console.log('Should redirect:', shouldAutoRedirect, { console.log('Should redirect:', shouldAutoRedirect, {
'No error': !hasRedirectError, 'No error': !hasRedirectError,
'No logout': !hasLogout, 'No logout': !hasLogout,
'No message': !hasMessage, 'No message': !hasMessage,
'Under max attempts': redirectAttempts < MAX_REDIRECT_ATTEMPTS, 'Under max attempts': redirectAttempts < MAX_REDIRECT_ATTEMPTS,
'Is OAuth2': loginMethod === 'oauth2' 'Is only OAuth2': loginMethod === 'oauth2'
}); });
if (shouldAutoRedirect && providerList && Object.keys(providerList).length > 0) { if (shouldAutoRedirect && providerList && Object.keys(providerList).length > 0) {
localStorage.setItem('ssoRedirectAttempts', redirectAttempts + 1); localStorage.setItem('ssoRedirectAttempts', redirectAttempts + 1);
const firstProvider = Object.keys(providerList)[0]; const firstProvider = Object.keys(providerList)[0];
window.location.href = firstProvider; window.location.href = firstProvider;
} }
// Reset redirect attempts if successful login or after 1 hour // Reset redirect attempts if successful login or after 1 hour
const lastAttemptTime = parseInt(localStorage.getItem('lastRedirectAttempt') || '0'); const lastAttemptTime = parseInt(localStorage.getItem('lastRedirectAttempt') || '0');
if (Date.now() - lastAttemptTime > 3600000) { // 1 hour if (Date.now() - lastAttemptTime > 3600000) { // 1 hour
localStorage.setItem('ssoRedirectAttempts', '0'); localStorage.setItem('ssoRedirectAttempts', '0');
} }
localStorage.setItem('lastRedirectAttempt', Date.now().toString()); localStorage.setItem('lastRedirectAttempt', Date.now().toString());
const defaultLocale = getStoredOrDefaultLocale(); const defaultLocale = getStoredOrDefaultLocale();
checkUserLanguage(defaultLocale); checkUserLanguage(defaultLocale);
const dropdownItems = document.querySelectorAll('.lang_dropdown-item'); const dropdownItems = document.querySelectorAll('.lang_dropdown-item');
let activeItem; let activeItem;
for (let i = 0; i < dropdownItems.length; i++) { for (let i = 0; i < dropdownItems.length; i++) {
const item = dropdownItems[i]; const item = dropdownItems[i];
item.classList.remove('active'); item.classList.remove('active');
if (item.dataset.bsLanguageCode === defaultLocale) { if (item.dataset.bsLanguageCode === defaultLocale) {
item.classList.add('active'); item.classList.add('active');
activeItem = item; activeItem = item;
} }
item.addEventListener('click', handleDropdownItemClick); item.addEventListener('click', handleDropdownItemClick);
} }
const dropdown = document.getElementById('languageDropdown'); const dropdown = document.getElementById('languageDropdown');
if (activeItem) { if (activeItem) {
dropdown.innerHTML = activeItem.innerHTML; // This will set the dropdown button's content to the active language's flag and name dropdown.innerHTML = activeItem.innerHTML; // This will set the dropdown button's content to the active language's flag and name
} }
}); });
</script> </script>
<div class="text-center"> <div class="text-center">
<img class="my-4" th:src="@{'/favicon.svg'}" alt="favicon" width="144" height="144"> <img class="my-4" th:src="@{'/favicon.svg'}" alt="favicon" width="144" height="144">

View File

@ -16,15 +16,15 @@
<span class="material-symbols-rounded tool-header-icon history">update</span> <span class="material-symbols-rounded tool-header-icon history">update</span>
<span class="tool-header-text" th:text="#{releases.header}">Release Notes</span> <span class="tool-header-text" th:text="#{releases.header}">Release Notes</span>
</div> </div>
<div class="alert alert-info" role="alert"> <div class="alert alert-info" role="alert">
<strong th:text="#{releases.current.version}">Current Installed Version</strong>: <strong th:text="#{releases.current.version}">Current Installed Version</strong>:
<span id="currentVersion" th:text="${@appVersion}"></span> <span id="currentVersion" th:text="${@appVersion}"></span>
</div> </div>
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
<span th:text="#{releases.note}">All release notes are only available in English</span> <span th:text="#{releases.note}">All release notes are only available in English</span>
</div> </div>
<div id="loading" class="text-center my-4"> <div id="loading" class="text-center my-4">
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">
@ -51,42 +51,42 @@
.release-notes-container { .release-notes-container {
margin-top: 2rem; margin-top: 2rem;
} }
.release-card { .release-card {
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
border-radius: 0.25rem; border-radius: 0.25rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
padding: 1rem; padding: 1rem;
} }
.release-card.current-version { .release-card.current-version {
border-color: #28a745; border-color: #28a745;
background-color: rgba(40, 167, 69, 0.05); background-color: rgba(40, 167, 69, 0.05);
} }
.release-header { .release-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.release-header h3 { .release-header h3 {
margin: 0; margin: 0;
display: flex; display: flex;
gap: 1rem; gap: 1rem;
align-items: center; align-items: center;
} }
.version { .version {
font-weight: bold; font-weight: bold;
} }
.release-date { .release-date {
color: #6c757d; color: #6c757d;
font-size: 0.9em; font-size: 0.9em;
} }
.release-body { .release-body {
font-size: 0.9rem; font-size: 0.9rem;
white-space: pre-wrap; white-space: pre-wrap;
@ -105,17 +105,17 @@
<script th:inline="javascript"> <script th:inline="javascript">
/*<![CDATA[*/ /*<![CDATA[*/
// Get the current version from the appVersion bean // Get the current version from the appVersion bean
const appVersion = [[${@appVersion}]]; const appVersion = /*[[${@appVersion}]]*/ '';
// GitHub API configuration // GitHub API configuration
const REPO_OWNER = 'Stirling-Tools'; const REPO_OWNER = 'Stirling-Tools';
const REPO_NAME = 'Stirling-PDF'; const REPO_NAME = 'Stirling-PDF';
const GITHUB_API = 'https://api.github.com/repos/' + REPO_OWNER + '/' + REPO_NAME; const GITHUB_API = 'https://api.github.com/repos/' + REPO_OWNER + '/' + REPO_NAME;
const GITHUB_URL = 'https://github.com/' + REPO_OWNER + '/' + REPO_NAME; const GITHUB_URL = 'https://github.com/' + REPO_OWNER + '/' + REPO_NAME;
const MAX_RELEASES = 8; const MAX_RELEASES = 8;
// Secure element creation helper // Secure element creation helper
function createElement(tag, attributes = {}, children = []) { function createElement(tag, attributes = {}, children = []) {
const element = document.createElement(tag); const element = document.createElement(tag);
@ -134,7 +134,6 @@
return element; return element;
} }
const ALLOWED_TAGS = { const ALLOWED_TAGS = {
'a': ['href', 'target', 'rel', 'class'], 'a': ['href', 'target', 'rel', 'class'],
'img': ['src', 'alt', 'width', 'height', 'style'], 'img': ['src', 'alt', 'width', 'height', 'style'],
@ -149,7 +148,7 @@
try { try {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html'); const doc = parser.parseFromString(htmlString, 'text/html');
function sanitizeNode(node) { function sanitizeNode(node) {
// Safety check for null/undefined // Safety check for null/undefined
if (!node) return null; if (!node) return null;
@ -162,7 +161,7 @@
// Handle element nodes // Handle element nodes
if (node.nodeType === Node.ELEMENT_NODE) { if (node.nodeType === Node.ELEMENT_NODE) {
const tagName = node.tagName.toLowerCase(); const tagName = node.tagName.toLowerCase();
// Check if tag is allowed // Check if tag is allowed
if (!ALLOWED_TAGS[tagName]) { if (!ALLOWED_TAGS[tagName]) {
return document.createTextNode(node.textContent); return document.createTextNode(node.textContent);
@ -170,7 +169,7 @@
// Create new element // Create new element
const cleanElement = document.createElement(tagName); const cleanElement = document.createElement(tagName);
// Copy allowed attributes // Copy allowed attributes
const allowedAttributes = ALLOWED_TAGS[tagName]; const allowedAttributes = ALLOWED_TAGS[tagName];
Array.from(node.attributes).forEach(attr => { Array.from(node.attributes).forEach(attr => {
@ -244,7 +243,7 @@
let lastIndex = 0; let lastIndex = 0;
const result = document.createElement('span'); const result = document.createElement('span');
const urlRegex = new RegExp('https://github\\.com/' + REPO_OWNER + '/' + REPO_NAME + '/(?:issues|pull)/(\\d+)', 'g'); const urlRegex = new RegExp('https://github\\.com/' + REPO_OWNER + '/' + REPO_NAME + '/(?:issues|pull)/(\\d+)', 'g');
while ((match = urlRegex.exec(text)) !== null) { while ((match = urlRegex.exec(text)) !== null) {
// Add text before the match // Add text before the match
if (match.index > lastIndex) { if (match.index > lastIndex) {
@ -259,7 +258,7 @@
link.target = '_blank'; link.target = '_blank';
link.rel = 'noopener noreferrer'; link.rel = 'noopener noreferrer';
result.appendChild(link); result.appendChild(link);
lastIndex = match.index + match[0].length; lastIndex = match.index + match[0].length;
} }
@ -274,15 +273,15 @@
// Update formatText function to handle processGitHubReferences properly // Update formatText function to handle processGitHubReferences properly
function formatText(text) { function formatText(text) {
const container = document.createElement('div'); const container = document.createElement('div');
// Split the text into lines // Split the text into lines
const textWithoutComments = text.replace(/<!--[\s\S]*?-->/g, ''); const textWithoutComments = text.replace(/<!--[\s\S]*?-->/g, '');
const lines = textWithoutComments.split('\n'); const lines = textWithoutComments.split('\n');
let currentList = null; let currentList = null;
lines.forEach(line => { lines.forEach(line => {
const trimmedLine = line.trim(); const trimmedLine = line.trim();
// Skip empty lines but add spacing // Skip empty lines but add spacing
if (!trimmedLine) { if (!trimmedLine) {
if (currentList) { if (currentList) {
@ -292,14 +291,14 @@ function formatText(text) {
container.appendChild(document.createElement('br')); container.appendChild(document.createElement('br'));
return; return;
} }
// Check if the line is HTML // Check if the line is HTML
if (trimmedLine.startsWith('<') && trimmedLine.endsWith('>')) { if (trimmedLine.startsWith('<') && trimmedLine.endsWith('>')) {
if (currentList) { if (currentList) {
container.appendChild(currentList); container.appendChild(currentList);
currentList = null; currentList = null;
} }
const safeElement = createSafeElement(trimmedLine); const safeElement = createSafeElement(trimmedLine);
if (safeElement) { if (safeElement) {
container.appendChild(safeElement); container.appendChild(safeElement);
@ -309,7 +308,7 @@ function formatText(text) {
} }
return; return;
} }
// Check for headers // Check for headers
const headerMatch = trimmedLine.match(/^(#{1,3})\s+(.+)$/); const headerMatch = trimmedLine.match(/^(#{1,3})\s+(.+)$/);
if (headerMatch) { if (headerMatch) {
@ -326,40 +325,40 @@ function formatText(text) {
container.appendChild(header); container.appendChild(header);
return; return;
} }
// Check for bullet points // Check for bullet points
const bulletMatch = trimmedLine.match(/^[-*]\s+(.+)$/); const bulletMatch = trimmedLine.match(/^[-*]\s+(.+)$/);
if (bulletMatch) { if (bulletMatch) {
if (!currentList) { if (!currentList) {
currentList = document.createElement('ul'); currentList = document.createElement('ul');
} }
const listContent = bulletMatch[1]; const listContent = bulletMatch[1];
const listItem = document.createElement('li'); const listItem = document.createElement('li');
// Process GitHub references in list items // Process GitHub references in list items
listItem.appendChild(processGitHubReferences(listContent)); listItem.appendChild(processGitHubReferences(listContent));
currentList.appendChild(listItem); currentList.appendChild(listItem);
return; return;
} }
// If we reach here and have a list, append it // If we reach here and have a list, append it
if (currentList) { if (currentList) {
container.appendChild(currentList); container.appendChild(currentList);
currentList = null; currentList = null;
} }
// Handle regular paragraph // Handle regular paragraph
const paragraph = document.createElement('p'); const paragraph = document.createElement('p');
paragraph.appendChild(processGitHubReferences(trimmedLine)); paragraph.appendChild(processGitHubReferences(trimmedLine));
container.appendChild(paragraph); container.appendChild(paragraph);
}); });
// Append any remaining list // Append any remaining list
if (currentList) { if (currentList) {
container.appendChild(currentList); container.appendChild(currentList);
} }
return container; return container;
} }
@ -368,7 +367,7 @@ function compareVersions(v1, v2) {
const normalize = v => v.replace(/^v/, ''); const normalize = v => v.replace(/^v/, '');
const v1Parts = normalize(v1).split('.').map(Number); const v1Parts = normalize(v1).split('.').map(Number);
const v2Parts = normalize(v2).split('.').map(Number); const v2Parts = normalize(v2).split('.').map(Number);
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
const v1Part = v1Parts[i] || 0; const v1Part = v1Parts[i] || 0;
const v2Part = v2Parts[i] || 0; const v2Part = v2Parts[i] || 0;
@ -386,7 +385,7 @@ async function loadReleases() {
try { try {
loading.classList.remove('d-none'); loading.classList.remove('d-none');
errorMessage.classList.add('d-none'); errorMessage.classList.add('d-none');
// Clear container safely // Clear container safely
while (container.firstChild) { while (container.firstChild) {
container.removeChild(container.firstChild); container.removeChild(container.firstChild);
@ -400,8 +399,8 @@ async function loadReleases() {
releases.sort((a, b) => compareVersions(b.tag_name, a.tag_name)); releases.sort((a, b) => compareVersions(b.tag_name, a.tag_name));
// Find index of current version // Find index of current version
const currentVersionIndex = releases.findIndex(release => const currentVersionIndex = releases.findIndex(release =>
compareVersions(release.tag_name, 'v' + appVersion) === 0 || compareVersions(release.tag_name, 'v' + appVersion) === 0 ||
compareVersions(release.tag_name, appVersion) === 0 compareVersions(release.tag_name, appVersion) === 0
); );
@ -425,20 +424,20 @@ async function loadReleases() {
relevantReleases.forEach((release, index) => { relevantReleases.forEach((release, index) => {
const isCurrentVersion = index === 0; // First release in the array is current version const isCurrentVersion = index === 0; // First release in the array is current version
const releaseCard = createElement('div', { const releaseCard = createElement('div', {
class: `release-card ${isCurrentVersion ? 'current-version' : ''}` class: `release-card ${isCurrentVersion ? 'current-version' : ''}`
}); });
const header = createElement('div', { class: 'release-header' }); const header = createElement('div', { class: 'release-header' });
const h3 = createElement('h3', {}, [ const h3 = createElement('h3', {}, [
createElement('span', { class: 'version' }, [release.tag_name]), createElement('span', { class: 'version' }, [release.tag_name]),
createElement('span', { class: 'release-date' }, [ createElement('span', { class: 'release-date' }, [
new Date(release.created_at).toLocaleDateString() new Date(release.created_at).toLocaleDateString()
]) ])
]); ]);
header.appendChild(h3); header.appendChild(h3);
if (isCurrentVersion) { if (isCurrentVersion) {
@ -451,7 +450,7 @@ async function loadReleases() {
const body = createElement('div', { class: 'release-body' }); const body = createElement('div', { class: 'release-body' });
body.appendChild(formatText(release.body || 'No release notes available.')); body.appendChild(formatText(release.body || 'No release notes available.'));
releaseCard.appendChild(body); releaseCard.appendChild(body);
container.appendChild(releaseCard); container.appendChild(releaseCard);
}); });
@ -467,7 +466,7 @@ async function loadReleases() {
// Load releases when the page loads // Load releases when the page loads
document.addEventListener('DOMContentLoaded', loadReleases); document.addEventListener('DOMContentLoaded', loadReleases);
/*]]>*/ /*]]>*/
</script> </script>
</body> </body>

View File

@ -21,9 +21,9 @@
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, remoteCall='false', accept='application/pdf')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, remoteCall='false', accept='application/pdf')}"></div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label th:text="#{validateSignature.selectCustomCert}" ></label> <label th:text="#{validateSignature.selectCustomCert}" ></label>
<div th:replace="~{fragments/common :: fileSelector(name='certFile', multipleInputsForSingleRequest=false, notRequired=true, remoteCall='false', accept='.cer,.crt,.pem')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='certFile', multipleInputsForSingleRequest=false, notRequired=true, remoteCall='false', accept='.cer,.crt,.pem')}"></div>
</div> </div>
<div class="mb-3 text-left"> <div class="mb-3 text-left">
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{validateSignature.submit}"></button> <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{validateSignature.submit}"></button>
</div> </div>

View File

@ -29,7 +29,7 @@ public class SPDFApplicationTest {
@Mock @Mock
private ApplicationProperties applicationProperties; private ApplicationProperties applicationProperties;
@InjectMocks @InjectMocks
private SPDFApplication SPDFApplication; private SPDFApplication SPDFApplication;

View File

@ -103,4 +103,4 @@ class DatabaseConfigTest {
assertThrows(UnsupportedProviderException.class, () -> databaseConfig.dataSource()); assertThrows(UnsupportedProviderException.class, () -> databaseConfig.dataSource());
} }
} }

View File

@ -98,4 +98,4 @@ class RearrangePagesPDFControllerTest {
assertNotNull(newPageOrder, "Returning null instead of page order list"); assertNotNull(newPageOrder, "Returning null instead of page order list");
assertEquals(Arrays.stream(expectedPageOrder.split(",")).map(Integer::parseInt).toList(), newPageOrder, "Page order doesn't match"); assertEquals(Arrays.stream(expectedPageOrder.split(",")).map(Integer::parseInt).toList(), newPageOrder, "Page order doesn't match");
} }
} }