2023-07-30 21:56:09 +01:00
<!DOCTYPE html>
2024-05-22 21:48:23 +01:00
< html th:lang = "${#locale.language}" th:dir = "#{language.direction}" th:data-language = "${#locale.toString()}" xmlns:th = "https://www.thymeleaf.org" >
2024-02-16 22:49:06 +01:00
< head >
< th:block th:insert = "~{fragments/common :: head(title=#{getPdfInfo.title}, header=#{getPdfInfo.header})}" > < / th:block >
< / head >
< body >
< div id = "page-container" >
< div id = "content-wrap" >
< th:block th:insert = "~{fragments/navbar.html :: navbar}" > < / th:block >
2024-03-21 21:58:01 +01:00
< br > < br >
2024-02-16 22:49:06 +01:00
< div class = "container" >
< div class = "row justify-content-center" >
2025-06-03 17:48:17 +01:00
< div class = "col-md-7 bg-card" >
2024-05-05 15:19:53 +04:00
< 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 >
< / div >
2024-06-15 23:07:09 +02:00
< form id = "pdfInfoForm" method = "post" enctype = "multipart/form-data" th:action = "@{'/api/v1/security/get-info-on-pdf'}" >
2024-08-23 10:17:50 +02:00
< div th:replace = "~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, remoteCall='false', accept='application/pdf')}" > < / div >
2024-09-19 00:10:40 +05:30
2024-02-16 22:49:06 +01:00
< button type = "submit" id = "submitBtn" class = "btn btn-primary" th:text = "#{getPdfInfo.submit}" > < / button >
< / form >
2024-09-19 00:10:40 +05:30
< div class = "container mt-0" >
2025-06-03 17:48:17 +01:00
<!-- 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 >
2024-02-16 22:49:06 +01:00
<!-- Iterate over each main section in the JSON -->
< div id = "json-content" >
<!-- JavaScript will populate this section -->
< / div >
<!-- Button to download the JSON -->
< a href = "#" id = "downloadJson" class = "btn btn-primary mt-3" style = "display: none;" th:text = "#{getPdfInfo.downloadJson}" > Download JSON< / a >
< / div >
2024-11-11 23:12:57 +00:00
< script th:src = "@{'/js/fetch-utils.js'}" > < / script >
2025-06-03 17:48:17 +01:00
< 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";
2024-10-30 12:46:44 +00:00
2024-02-16 22:49:06 +01:00
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
event.preventDefault();
2025-06-03 17:48:17 +01:00
// 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';
2024-02-16 22:49:06 +01:00
const formData = new FormData(event.target);
2024-10-30 12:46:44 +00:00
fetchWithCsrf('api/v1/security/get-info-on-pdf', {
2024-02-16 22:49:06 +01:00
method: 'POST',
body: formData
}).then(response => response.json()).then(data => {
2025-06-03 17:48:17 +01:00
// Populate and display the enhanced PDF summary
populateSummarySection(data);
2024-02-16 22:49:06 +01:00
displayJsonData(data);
setDownloadLink(data);
document.getElementById("downloadJson").style.display = "block";
}).catch(error => console.error('Error:', error));
2025-06-03 17:48:17 +01:00
// 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(' ');
}
2024-02-16 22:49:06 +01:00
});
function displayJsonData(jsonData) {
const jsonContent = document.getElementById('json-content');
while (jsonContent.firstChild) {
jsonContent.removeChild(jsonContent.firstChild);
}
for (const key in jsonData) {
const sectionElem = createJsonSection(key, jsonData[key]);
jsonContent.appendChild(sectionElem);
}
}
function setDownloadLink(jsonData) {
const downloadLink = document.getElementById('downloadJson');
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData, null, 4));
downloadLink.setAttribute("href", dataStr);
downloadLink.setAttribute("download", "data.json");
}
function createJsonSection(key, value, depth = 0) {
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
const card = document.createElement('div');
card.className = 'card mb-3';
const header = document.createElement('div');
header.className = 'card-header';
header.id = `${safeKey}-heading-${depth}`;
const h5Elem = document.createElement('h5');
2025-06-03 17:48:17 +01:00
h5Elem.className = 'mb-0 d-flex align-items-center';
2024-02-16 22:49:06 +01:00
2025-06-03 17:48:17 +01:00
// Create the main content (button or text)
2024-02-16 22:49:06 +01:00
if (key === 'XMPMetadata' & & typeof value === "string") {
const buttonElem = createButtonElement(key, safeKey, depth);
h5Elem.appendChild(buttonElem);
} else if (value & & typeof value === 'object') {
if (Array.isArray(value) & & value.length === 0) {
h5Elem.textContent = `${key}: Empty array`;
} else if (!Array.isArray(value) & & Object.keys(value).length === 0) {
h5Elem.textContent = `${key}: Empty object`;
} else {
const buttonElem = createButtonElement(key, safeKey, depth);
h5Elem.appendChild(buttonElem);
}
} else {
h5Elem.textContent = `${key}: ${String(value)}`;
}
2025-06-03 17:48:17 +01:00
// Info buttons removed as requested
2024-02-16 22:49:06 +01:00
header.appendChild(h5Elem);
card.appendChild(header);
const content = document.createElement('div');
content.id = `${safeKey}-content-${depth}`;
content.className = 'collapse';
content.setAttribute('aria-labelledby', `${safeKey}-heading-${depth}`);
if (key === 'XMPMetadata' & & typeof value === "string") {
const body = document.createElement('div');
body.className = 'card-body';
const preElem = document.createElement('pre');
preElem.textContent = value; // Not escaping since we're using textContent
body.appendChild(preElem);
content.appendChild(body);
} else if (value & & typeof value === 'object' & & !Array.isArray(value)) {
const body = document.createElement('div');
body.className = 'card-body';
for (const subKey in value) {
const subElem = createJsonSection(subKey, value[subKey], depth + 1);
body.appendChild(subElem);
}
content.appendChild(body);
} else if (value & & typeof value === 'object' & & Array.isArray(value)) {
const body = document.createElement('div');
body.className = 'card-body';
value.forEach((val, index) => {
const subElem = createJsonSection(`${key}[${index}]`, val, depth + 1);
body.appendChild(subElem);
});
content.appendChild(body);
}
card.appendChild(content);
return card;
}
function createButtonElement(key, safeKey, depth) {
const buttonElem = document.createElement('button');
buttonElem.className = 'btn btn-link';
buttonElem.type = 'button';
buttonElem.dataset.bsToggle = "collapse";
buttonElem.dataset.bsTarget = `#${safeKey}-content-${depth}`;
buttonElem.setAttribute('aria-expanded', 'true');
buttonElem.setAttribute('aria-controls', `${safeKey}-content-${depth}`);
buttonElem.textContent = key;
return buttonElem;
}
const collapsibleElems = document.querySelectorAll('[data-bs-toggle="collapse"]');
collapsibleElems.forEach(elem => {
new bootstrap.Collapse(elem);
});
< / script >
< / div >
< / div >
< / div >
< / div >
< th:block th:insert = "~{fragments/footer.html :: footer}" > < / th:block >
< / div >
< / body >
2024-11-11 23:12:57 +00:00
< / html >