From 2bacb4dc810c053e3c1a85238f623415a48783c9 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:19:23 +0000 Subject: [PATCH] cleanups (#5795) Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> --- .../main/resources/static/js/DecryptFiles.js | 364 --- .../resources/static/js/cacheFormInputs.js | 84 - .../main/resources/static/js/compare/diff.js | 2106 ----------------- .../resources/static/js/compare/pdfWorker.js | 213 -- app/core/src/main/resources/static/js/csrf.js | 37 - .../src/main/resources/static/js/darkmode.js | 105 - .../src/main/resources/static/js/download.js | 27 - .../main/resources/static/js/downloader.js | 791 ------- .../resources/static/js/draggable-utils.js | 631 ----- .../main/resources/static/js/errorBanner.js | 50 - .../main/resources/static/js/favourites.js | 144 -- .../main/resources/static/js/fetch-utils.js | 67 - .../resources/static/js/file-icon-factory.js | 76 - .../main/resources/static/js/file-utils.js | 31 - .../src/main/resources/static/js/fileInput.js | 625 ----- app/core/src/main/resources/static/js/game.js | 276 --- .../main/resources/static/js/githubVersion.js | 390 --- .../resources/static/js/googleFilePicker.js | 158 -- .../resources/static/js/homecard-legacy.js | 266 --- .../src/main/resources/static/js/homecard.js | 249 -- .../src/main/resources/static/js/jwt-init.js | 44 - .../resources/static/js/languageSelection.js | 74 - .../static/js/local-pdf-input-download.js | 47 - .../src/main/resources/static/js/merge.js | 222 -- .../static/js/multitool/DragDropManager.js | 209 -- .../static/js/multitool/ImageHighlighter.js | 47 - .../static/js/multitool/PdfActionsManager.js | 294 --- .../static/js/multitool/PdfContainer.js | 1160 --------- .../static/js/multitool/UndoManager.js | 65 - .../static/js/multitool/commands/add-page.js | 84 - .../static/js/multitool/commands/command.js | 65 - .../multitool/commands/commands-sequence.js | 28 - .../js/multitool/commands/delete-page.js | 92 - .../js/multitool/commands/duplicate-page.js | 63 - .../static/js/multitool/commands/move-page.js | 80 - .../js/multitool/commands/page-break.js | 105 - .../static/js/multitool/commands/remove.js | 112 - .../static/js/multitool/commands/rotate.js | 81 - .../static/js/multitool/commands/select.js | 47 - .../static/js/multitool/commands/split.js | 89 - .../src/main/resources/static/js/navbar.js | 163 -- .../resources/static/js/pages/add-image.js | 85 - .../static/js/pages/adjust-contrast.js | 262 -- .../static/js/pages/change-metadata.js | 161 -- .../main/resources/static/js/pages/crop.js | 195 -- .../static/js/pages/edit-table-of-contents.js | 802 ------- .../main/resources/static/js/pages/home.js | 200 -- .../resources/static/js/pages/pdf-to-csv.js | 159 -- .../main/resources/static/js/pages/sign.js | 415 ---- .../src/main/resources/static/js/pipeline.js | 768 ------ .../src/main/resources/static/js/redact.js | 1090 --------- .../src/main/resources/static/js/search.js | 143 -- .../src/main/resources/static/js/settings.js | 41 - .../static/js/sign/signature-canvas.js | 137 -- .../main/resources/static/js/tab-container.js | 37 - .../static/js/thirdParty/bootstrap.min.js | 7 - .../static/js/thirdParty/bootstrap.min.js.map | 1 - .../static/js/thirdParty/chart.umd.min.js | 20 - .../js/thirdParty/cookieconsent-config.js | 80 - .../thirdParty/fontfaceobserver.standalone.js | 8 - .../static/js/thirdParty/interact.min.js | 3 - .../static/js/thirdParty/interact.min.js.map | 1 - .../static/js/thirdParty/jquery.min.js | 2 - .../js/thirdParty/jquery.validate.min.js | 4 - .../static/js/thirdParty/jszip.min.js | 13 - .../static/js/thirdParty/pdf-lib.min.js | 16 - .../static/js/thirdParty/pdf-lib.min.js.map | 1 - .../static/js/thirdParty/popper.min.js | 6 - .../static/js/thirdParty/popper.min.js.map | 1 - .../resources/static/js/thirdParty/prism.js | 5 - .../js/thirdParty/signature_pad.umd.min.js | 6 - .../thirdParty/signature_pad.umd.min.js.map | 1 - .../src/main/resources/static/js/usage.js | 363 --- app/core/src/main/resources/static/js/uuid.js | 9 - 74 files changed, 14903 deletions(-) delete mode 100644 app/core/src/main/resources/static/js/DecryptFiles.js delete mode 100644 app/core/src/main/resources/static/js/cacheFormInputs.js delete mode 100644 app/core/src/main/resources/static/js/compare/diff.js delete mode 100644 app/core/src/main/resources/static/js/compare/pdfWorker.js delete mode 100644 app/core/src/main/resources/static/js/csrf.js delete mode 100644 app/core/src/main/resources/static/js/darkmode.js delete mode 100644 app/core/src/main/resources/static/js/download.js delete mode 100644 app/core/src/main/resources/static/js/downloader.js delete mode 100644 app/core/src/main/resources/static/js/draggable-utils.js delete mode 100644 app/core/src/main/resources/static/js/errorBanner.js delete mode 100644 app/core/src/main/resources/static/js/favourites.js delete mode 100644 app/core/src/main/resources/static/js/fetch-utils.js delete mode 100644 app/core/src/main/resources/static/js/file-icon-factory.js delete mode 100644 app/core/src/main/resources/static/js/file-utils.js delete mode 100644 app/core/src/main/resources/static/js/fileInput.js delete mode 100644 app/core/src/main/resources/static/js/game.js delete mode 100644 app/core/src/main/resources/static/js/githubVersion.js delete mode 100644 app/core/src/main/resources/static/js/googleFilePicker.js delete mode 100644 app/core/src/main/resources/static/js/homecard-legacy.js delete mode 100644 app/core/src/main/resources/static/js/homecard.js delete mode 100644 app/core/src/main/resources/static/js/jwt-init.js delete mode 100644 app/core/src/main/resources/static/js/languageSelection.js delete mode 100644 app/core/src/main/resources/static/js/local-pdf-input-download.js delete mode 100644 app/core/src/main/resources/static/js/merge.js delete mode 100644 app/core/src/main/resources/static/js/multitool/DragDropManager.js delete mode 100644 app/core/src/main/resources/static/js/multitool/ImageHighlighter.js delete mode 100644 app/core/src/main/resources/static/js/multitool/PdfActionsManager.js delete mode 100644 app/core/src/main/resources/static/js/multitool/PdfContainer.js delete mode 100644 app/core/src/main/resources/static/js/multitool/UndoManager.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/add-page.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/command.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/commands-sequence.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/delete-page.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/duplicate-page.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/move-page.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/page-break.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/remove.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/rotate.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/select.js delete mode 100644 app/core/src/main/resources/static/js/multitool/commands/split.js delete mode 100644 app/core/src/main/resources/static/js/navbar.js delete mode 100644 app/core/src/main/resources/static/js/pages/add-image.js delete mode 100644 app/core/src/main/resources/static/js/pages/adjust-contrast.js delete mode 100644 app/core/src/main/resources/static/js/pages/change-metadata.js delete mode 100644 app/core/src/main/resources/static/js/pages/crop.js delete mode 100644 app/core/src/main/resources/static/js/pages/edit-table-of-contents.js delete mode 100644 app/core/src/main/resources/static/js/pages/home.js delete mode 100644 app/core/src/main/resources/static/js/pages/pdf-to-csv.js delete mode 100644 app/core/src/main/resources/static/js/pages/sign.js delete mode 100644 app/core/src/main/resources/static/js/pipeline.js delete mode 100644 app/core/src/main/resources/static/js/redact.js delete mode 100644 app/core/src/main/resources/static/js/search.js delete mode 100644 app/core/src/main/resources/static/js/settings.js delete mode 100644 app/core/src/main/resources/static/js/sign/signature-canvas.js delete mode 100644 app/core/src/main/resources/static/js/tab-container.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/bootstrap.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/bootstrap.min.js.map delete mode 100644 app/core/src/main/resources/static/js/thirdParty/chart.umd.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/cookieconsent-config.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/fontfaceobserver.standalone.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/interact.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/interact.min.js.map delete mode 100644 app/core/src/main/resources/static/js/thirdParty/jquery.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/jquery.validate.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/jszip.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/pdf-lib.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/pdf-lib.min.js.map delete mode 100644 app/core/src/main/resources/static/js/thirdParty/popper.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/popper.min.js.map delete mode 100644 app/core/src/main/resources/static/js/thirdParty/prism.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/signature_pad.umd.min.js delete mode 100644 app/core/src/main/resources/static/js/thirdParty/signature_pad.umd.min.js.map delete mode 100644 app/core/src/main/resources/static/js/usage.js delete mode 100644 app/core/src/main/resources/static/js/uuid.js diff --git a/app/core/src/main/resources/static/js/DecryptFiles.js b/app/core/src/main/resources/static/js/DecryptFiles.js deleted file mode 100644 index b19fb6e12..000000000 --- a/app/core/src/main/resources/static/js/DecryptFiles.js +++ /dev/null @@ -1,364 +0,0 @@ -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -function formatProblemDetailsJson(input) { - try { - const obj = typeof input === 'string' ? JSON.parse(input) : input; - const preferredOrder = [ - 'errorCode', - 'title', - 'status', - 'type', - 'detail', - 'instance', - 'path', - 'timestamp', - 'hints', - 'actionRequired' - ]; - - const ordered = {}; - preferredOrder.forEach((key) => { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - ordered[key] = obj[key]; - } - }); - - Object.keys(obj).forEach((key) => { - if (!Object.prototype.hasOwnProperty.call(ordered, key)) { - ordered[key] = obj[key]; - } - }); - - return JSON.stringify(ordered, null, 2); - } catch (err) { - if (typeof input === 'string') return input; - try { - return JSON.stringify(input, null, 2); - } catch (jsonErr) { - return String(input); - } - } -} - -function formatUserFriendlyError(json) { - if (!json || typeof json !== 'object') { - return typeof json === 'string' ? json : ''; - } - - const lines = []; - const title = json.title || json.error || ''; - const detail = json.detail || json.message || ''; - - const primaryLine = title && detail ? `${title}: ${detail}` : title || detail; - - if (primaryLine) { - lines.push(primaryLine); - } - - if (json.errorCode) { - lines.push(''); - lines.push(`Error Code: ${json.errorCode}`); - } - - const detailAlreadyIncluded = detail && primaryLine && primaryLine.includes(detail); - if (detail && !detailAlreadyIncluded) { - lines.push(''); - lines.push(detail); - } - - if (json.hints && Array.isArray(json.hints) && json.hints.length > 0) { - lines.push(''); - lines.push('How to fix:'); - json.hints.forEach((hint, index) => { - lines.push(` ${index + 1}. ${hint}`); - }); - } - - if (json.actionRequired) { - lines.push(''); - lines.push(json.actionRequired); - } - - if (json.supportId) { - lines.push(''); - lines.push(`Support ID: ${json.supportId}`); - } - - return lines - .filter((line, index, arr) => { - if (line !== '') return true; - if (index === 0 || index === arr.length - 1) return false; - return arr[index - 1] !== ''; - }) - .join('\n'); -} - -function buildPdfPasswordProblemDetail(fileName) { - const stirling = window.stirlingPDF || {}; - const detailTemplate = stirling.pdfPasswordDetail || 'The PDF Document is passworded and either the password was not provided or was incorrect'; - const title = stirling.pdfPasswordTitle || 'PDF Password Required'; - const hints = [ - stirling.pdfPasswordHint1, - stirling.pdfPasswordHint2, - stirling.pdfPasswordHint3, - stirling.pdfPasswordHint4, - stirling.pdfPasswordHint5, - stirling.pdfPasswordHint6 - ].filter((hint) => typeof hint === 'string' && hint.trim().length > 0); - const actionRequired = stirling.pdfPasswordAction || 'Provide the owner/permissions password, not just the document open password.'; - - return { - errorCode: 'E004', - title, - detail: detailTemplate.replace('{0}', fileName), - type: '/errors/pdf-password', - path: '/api/v1/security/remove-password', - hints, - actionRequired - }; -} - -function buildCorruptedPdfProblemDetail(fileName) { - const stirling = window.stirlingPDF || {}; - const detailTemplate = stirling.pdfCorruptedMessage || 'The PDF file "{0}" appears to be corrupted or has an invalid structure.'; - const hints = [ - stirling.pdfCorruptedHint1, - stirling.pdfCorruptedHint2, - stirling.pdfCorruptedHint3 - ].filter((hint) => typeof hint === 'string' && hint.trim().length > 0); - const actionRequired = stirling.pdfCorruptedAction || stirling.tryRepairMessage || ''; - - return { - errorCode: 'E001', - title: stirling.pdfCorruptedTitle || 'PDF File Corrupted', - detail: detailTemplate.replace('{0}', fileName), - type: '/errors/pdf-corrupted', - hints, - actionRequired - }; -} - -export class DecryptFile { - - constructor(){ - this.decryptWorker = null - } - - async decryptFile(file, requiresPassword) { - - try { - async function getCsrfToken() { - const cookieValue = document.cookie - .split('; ') - .find((row) => row.startsWith('XSRF-TOKEN=')) - ?.split('=')[1]; - - if (cookieValue) { - return cookieValue; - } - - const csrfElement = document.querySelector('input[name="_csrf"]'); - return csrfElement ? csrfElement.value : null; - } - const csrfToken = await getCsrfToken(); - const formData = new FormData(); - formData.append('fileInput', file); - if (requiresPassword) { - const password = prompt(`${window.decrypt.passwordPrompt}`); - - if (password === null) { - // User cancelled - console.error(`Password prompt cancelled for PDF: ${file.name}`); - return null; // No file to return - } - - if (!password) { - // No password provided - console.error(`No password provided for encrypted PDF: ${file.name}`); - const problemDetail = buildPdfPasswordProblemDetail(file.name); - this.showProblemDetail(problemDetail); - return null; // No file to return - } - - formData.append('password', password); - } - // Send decryption request - const response = await fetchWithCsrf('/api/v1/security/remove-password', { - method: 'POST', - body: formData, - }); - - if (!response.ok) { - const contentType = response.headers.get('content-type') || ''; - if (contentType.includes('application/json') || contentType.includes('application/problem+json')) { - const errorJson = await response.json(); - this.showProblemDetail(errorJson); - } else { - const errorText = await response.text(); - console.error(`${window.decrypt.invalidPassword} ${errorText}`); - const fallbackProblem = buildPdfPasswordProblemDetail(file.name); - if (errorText && errorText.trim().length > 0) { - fallbackProblem.detail = errorText.trim(); - } - this.showProblemDetail(fallbackProblem); - } - return null; // No file to return - } - - this.removeErrorBanner(); - const decryptedBlob = await response.blob(); - return new File([decryptedBlob], file.name, { - type: 'application/pdf', - }); - } catch (error) { - // Handle network or unexpected errors - console.error(`Failed to decrypt PDF: ${file.name}`, error); - const fallbackDetail = - (error && error.message) || - window.decrypt.unexpectedError || - 'There was an error processing the file. Please try again.'; - - const unexpectedProblem = { - title: (window.stirlingPDF && window.stirlingPDF.errorUnexpectedTitle) || 'Unexpected Error', - detail: fallbackDetail, - }; - - if (window.decrypt.serverError) { - unexpectedProblem.hints = [ - window.decrypt.serverError.replace('{0}', file.name), - ]; - } - - this.showProblemDetail(unexpectedProblem); - return null; // No file to return - } - } - - async checkFileEncrypted(file) { - try { - if (file.type !== 'application/pdf') { - return {isEncrypted: false, requiresPassword: false}; - } - - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - - const arrayBuffer = await file.arrayBuffer(); - const arrayBufferForPdfLib = arrayBuffer.slice(0); - var loadingTask; - - if(this.decryptWorker == null){ - loadingTask = pdfjsLib.getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: arrayBuffer, - }); - this.decryptWorker = loadingTask._worker - - }else { - loadingTask = pdfjsLib.getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: arrayBuffer, - worker: this.decryptWorker - }); - } - - await loadingTask.promise; - - try { - //Uses PDFLib.PDFDocument to check if unpassworded but encrypted - const pdfDoc = await PDFLib.PDFDocument.load(arrayBufferForPdfLib); - return {isEncrypted: false, requiresPassword: false}; - } catch (error) { - if (error.message.includes('Input document to `PDFDocument.load` is encrypted')) { - return {isEncrypted: true, requiresPassword: false}; - } - console.error('Error checking encryption:', error); - throw new Error('Failed to determine if the file is encrypted.'); - } - } catch (error) { - if (error.name === 'PasswordException') { - if (error.code === pdfjsLib.PasswordResponses.NEED_PASSWORD) { - return {isEncrypted: true, requiresPassword: true}; - } else if (error.code === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) { - return {isEncrypted: true, requiresPassword: false}; - } - } else if (error.name === 'InvalidPDFException' || - (error.message && error.message.includes('Invalid PDF structure'))) { - // Handle corrupted PDF files - console.error('Corrupted PDF detected:', error); - if (window.stirlingPDF.currentPage !== 'repair') { - const corruptedProblem = buildCorruptedPdfProblemDetail(file.name); - this.showProblemDetail(corruptedProblem); - } else { - console.log('Suppressing corrupted PDF warning banner on repair page'); - } - throw new Error('PDF file is corrupted.'); - } - - console.error('Error checking encryption:', error); - throw new Error('Failed to determine if the file is encrypted.'); - } - } - - showProblemDetail(problemDetail) { - const errorContainer = document.getElementById('errorContainer'); - if (!errorContainer) { - console.error('Error container not found'); - return; - } - - errorContainer.style.display = 'block'; - - const heading = errorContainer.querySelector('.alert-heading'); - const messageEl = errorContainer.querySelector('p'); - const traceEl = document.querySelector('#traceContent'); - - const fallbackHeading = (window.stirlingPDF && window.stirlingPDF.error) || 'Error'; - - if (heading) { - heading.textContent = - (problemDetail && typeof problemDetail === 'object' && problemDetail.title) || - fallbackHeading; - } - - if (messageEl) { - messageEl.style.whiteSpace = 'pre-wrap'; - messageEl.textContent = - typeof problemDetail === 'object' - ? formatUserFriendlyError(problemDetail) - : String(problemDetail || ''); - } - - if (traceEl) { - traceEl.textContent = - typeof problemDetail === 'object' ? formatProblemDetailsJson(problemDetail) : ''; - } - } - - removeErrorBanner() { - const errorContainer = document.getElementById('errorContainer'); - if (!errorContainer) { - return; - } - - errorContainer.style.display = 'none'; - - const heading = errorContainer.querySelector('.alert-heading'); - if (heading) { - heading.textContent = (window.stirlingPDF && window.stirlingPDF.error) || 'Error'; - } - - const messageEl = errorContainer.querySelector('p'); - if (messageEl) { - messageEl.textContent = ''; - } - - const traceEl = document.querySelector('#traceContent'); - if (traceEl) { - traceEl.textContent = ''; - } - } -} diff --git a/app/core/src/main/resources/static/js/cacheFormInputs.js b/app/core/src/main/resources/static/js/cacheFormInputs.js deleted file mode 100644 index 64760bf41..000000000 --- a/app/core/src/main/resources/static/js/cacheFormInputs.js +++ /dev/null @@ -1,84 +0,0 @@ -document.addEventListener("DOMContentLoaded", function() { - - var cacheInputs = localStorage.getItem("cacheInputs") || "disabled"; - if (cacheInputs !== "enabled") { - return; // Stop execution if caching is not enabled - } - - // Function to generate a key based on the form's action attribute - function generateStorageKey(form) { - const action = form.getAttribute('action'); - if (!action || action.length < 3) { - return null; // Not a valid action, return null to skip processing - } - return 'formData_' + encodeURIComponent(action); - } - - // Function to save form data to localStorage - function saveFormData(form) { - const formKey = generateStorageKey(form); - if (!formKey) return; // Skip if no valid key - - const formData = {}; - const elements = form.elements; - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - // Skip elements without names, passwords, files, hidden fields, and submit/reset buttons - if (!element.name || - element.type === 'password' || - element.type === 'file' || - //element.type === 'hidden' || - element.type === 'submit' || - element.type === 'reset') { - continue; - } - // Handle checkboxes: store only if checked - if (element.type === 'checkbox') { - if (element.checked) { - formData[element.name] = element.value; - } else { - continue; // Skip unchecked boxes - } - } else { - // Skip saving empty values - if (element.value === "" || element.value == null) { - continue; - } - formData[element.name] = element.value; - } - } - if (Object.keys(formData).length > 0) { - localStorage.setItem(formKey, JSON.stringify(formData)); - } - } - - // Function to load form data from localStorage - function loadFormData(form) { - const formKey = generateStorageKey(form); - if (!formKey) return; // Skip if no valid key - - const savedData = localStorage.getItem(formKey); - if (savedData) { - const formData = JSON.parse(savedData); - for (const key in formData) { - if (formData.hasOwnProperty(key) && form.elements[key]) { - const element = form.elements[key]; - if (element.type === 'checkbox') { - element.checked = true; - } else { - element.value = formData[key]; - } - } - } - } - } - - // Attach event listeners and load data for all forms - const forms = document.querySelectorAll('form'); - forms.forEach(form => { - form.addEventListener('submit', function(event) { - saveFormData(form); - }); - loadFormData(form); - }); -}); diff --git a/app/core/src/main/resources/static/js/compare/diff.js b/app/core/src/main/resources/static/js/compare/diff.js deleted file mode 100644 index 2c2c33344..000000000 --- a/app/core/src/main/resources/static/js/compare/diff.js +++ /dev/null @@ -1,2106 +0,0 @@ -/*! - - diff v7.0.0 - -BSD 3-Clause License - -Copyright (c) 2009-2015, Kevin Decker -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -@license -*/ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define(['exports'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Diff = {})); -})(this, (function (exports) { 'use strict'; - - function Diff() {} - Diff.prototype = { - diff: function diff(oldString, newString) { - var _options$timeout; - var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - var callback = options.callback; - if (typeof options === 'function') { - callback = options; - options = {}; - } - var self = this; - function done(value) { - value = self.postProcess(value, options); - if (callback) { - setTimeout(function () { - callback(value); - }, 0); - return true; - } else { - return value; - } - } - - // Allow subclasses to massage the input prior to running - oldString = this.castInput(oldString, options); - newString = this.castInput(newString, options); - oldString = this.removeEmpty(this.tokenize(oldString, options)); - newString = this.removeEmpty(this.tokenize(newString, options)); - var newLen = newString.length, - oldLen = oldString.length; - var editLength = 1; - var maxEditLength = newLen + oldLen; - if (options.maxEditLength != null) { - maxEditLength = Math.min(maxEditLength, options.maxEditLength); - } - var maxExecutionTime = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : Infinity; - var abortAfterTimestamp = Date.now() + maxExecutionTime; - var bestPath = [{ - oldPos: -1, - lastComponent: undefined - }]; - - // Seed editLength = 0, i.e. the content starts with the same values - var newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options); - if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { - // Identity per the equality and tokenizer - return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken)); - } - - // Once we hit the right edge of the edit graph on some diagonal k, we can - // definitely reach the end of the edit graph in no more than k edits, so - // there's no point in considering any moves to diagonal k+1 any more (from - // which we're guaranteed to need at least k+1 more edits). - // Similarly, once we've reached the bottom of the edit graph, there's no - // point considering moves to lower diagonals. - // We record this fact by setting minDiagonalToConsider and - // maxDiagonalToConsider to some finite value once we've hit the edge of - // the edit graph. - // This optimization is not faithful to the original algorithm presented in - // Myers's paper, which instead pointlessly extends D-paths off the end of - // the edit graph - see page 7 of Myers's paper which notes this point - // explicitly and illustrates it with a diagram. This has major performance - // implications for some common scenarios. For instance, to compute a diff - // where the new text simply appends d characters on the end of the - // original text of length n, the true Myers algorithm will take O(n+d^2) - // time while this optimization needs only O(n+d) time. - var minDiagonalToConsider = -Infinity, - maxDiagonalToConsider = Infinity; - - // Main worker method. checks all permutations of a given edit length for acceptance. - function execEditLength() { - for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) { - var basePath = void 0; - var removePath = bestPath[diagonalPath - 1], - addPath = bestPath[diagonalPath + 1]; - if (removePath) { - // No one else is going to attempt to use this value, clear it - bestPath[diagonalPath - 1] = undefined; - } - var canAdd = false; - if (addPath) { - // what newPos will be after we do an insertion: - var addPathNewPos = addPath.oldPos - diagonalPath; - canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen; - } - var canRemove = removePath && removePath.oldPos + 1 < oldLen; - if (!canAdd && !canRemove) { - // If this path is a terminal then prune - bestPath[diagonalPath] = undefined; - continue; - } - - // Select the diagonal that we want to branch from. We select the prior - // path whose position in the old string is the farthest from the origin - // and does not pass the bounds of the diff graph - if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) { - basePath = self.addToPath(addPath, true, false, 0, options); - } else { - basePath = self.addToPath(removePath, false, true, 1, options); - } - newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options); - if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { - // If we have hit the end of both strings, then we are done - return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken)); - } else { - bestPath[diagonalPath] = basePath; - if (basePath.oldPos + 1 >= oldLen) { - maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1); - } - if (newPos + 1 >= newLen) { - minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1); - } - } - } - editLength++; - } - - // Performs the length of edit iteration. Is a bit fugly as this has to support the - // sync and async mode which is never fun. Loops over execEditLength until a value - // is produced, or until the edit length exceeds options.maxEditLength (if given), - // in which case it will return undefined. - if (callback) { - (function exec() { - setTimeout(function () { - if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) { - return callback(); - } - if (!execEditLength()) { - exec(); - } - }, 0); - })(); - } else { - while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) { - var ret = execEditLength(); - if (ret) { - return ret; - } - } - } - }, - addToPath: function addToPath(path, added, removed, oldPosInc, options) { - var last = path.lastComponent; - if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { - return { - oldPos: path.oldPos + oldPosInc, - lastComponent: { - count: last.count + 1, - added: added, - removed: removed, - previousComponent: last.previousComponent - } - }; - } else { - return { - oldPos: path.oldPos + oldPosInc, - lastComponent: { - count: 1, - added: added, - removed: removed, - previousComponent: last - } - }; - } - }, - extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath, options) { - var newLen = newString.length, - oldLen = oldString.length, - oldPos = basePath.oldPos, - newPos = oldPos - diagonalPath, - commonCount = 0; - while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) { - newPos++; - oldPos++; - commonCount++; - if (options.oneChangePerToken) { - basePath.lastComponent = { - count: 1, - previousComponent: basePath.lastComponent, - added: false, - removed: false - }; - } - } - if (commonCount && !options.oneChangePerToken) { - basePath.lastComponent = { - count: commonCount, - previousComponent: basePath.lastComponent, - added: false, - removed: false - }; - } - basePath.oldPos = oldPos; - return newPos; - }, - equals: function equals(left, right, options) { - if (options.comparator) { - return options.comparator(left, right); - } else { - return left === right || options.ignoreCase && left.toLowerCase() === right.toLowerCase(); - } - }, - removeEmpty: function removeEmpty(array) { - var ret = []; - for (var i = 0; i < array.length; i++) { - if (array[i]) { - ret.push(array[i]); - } - } - return ret; - }, - castInput: function castInput(value) { - return value; - }, - tokenize: function tokenize(value) { - return Array.from(value); - }, - join: function join(chars) { - return chars.join(''); - }, - postProcess: function postProcess(changeObjects) { - return changeObjects; - } - }; - function buildValues(diff, lastComponent, newString, oldString, useLongestToken) { - // First we convert our linked list of components in reverse order to an - // array in the right order: - var components = []; - var nextComponent; - while (lastComponent) { - components.push(lastComponent); - nextComponent = lastComponent.previousComponent; - delete lastComponent.previousComponent; - lastComponent = nextComponent; - } - components.reverse(); - var componentPos = 0, - componentLen = components.length, - newPos = 0, - oldPos = 0; - for (; componentPos < componentLen; componentPos++) { - var component = components[componentPos]; - if (!component.removed) { - if (!component.added && useLongestToken) { - var value = newString.slice(newPos, newPos + component.count); - value = value.map(function (value, i) { - var oldValue = oldString[oldPos + i]; - return oldValue.length > value.length ? oldValue : value; - }); - component.value = diff.join(value); - } else { - component.value = diff.join(newString.slice(newPos, newPos + component.count)); - } - newPos += component.count; - - // Common case - if (!component.added) { - oldPos += component.count; - } - } else { - component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); - oldPos += component.count; - } - } - return components; - } - - var characterDiff = new Diff(); - function diffChars(oldStr, newStr, options) { - return characterDiff.diff(oldStr, newStr, options); - } - - function longestCommonPrefix(str1, str2) { - var i; - for (i = 0; i < str1.length && i < str2.length; i++) { - if (str1[i] != str2[i]) { - return str1.slice(0, i); - } - } - return str1.slice(0, i); - } - function longestCommonSuffix(str1, str2) { - var i; - - // Unlike longestCommonPrefix, we need a special case to handle all scenarios - // where we return the empty string since str1.slice(-0) will return the - // entire string. - if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) { - return ''; - } - for (i = 0; i < str1.length && i < str2.length; i++) { - if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) { - return str1.slice(-i); - } - } - return str1.slice(-i); - } - function replacePrefix(string, oldPrefix, newPrefix) { - if (string.slice(0, oldPrefix.length) != oldPrefix) { - throw Error("string ".concat(JSON.stringify(string), " doesn't start with prefix ").concat(JSON.stringify(oldPrefix), "; this is a bug")); - } - return newPrefix + string.slice(oldPrefix.length); - } - function replaceSuffix(string, oldSuffix, newSuffix) { - if (!oldSuffix) { - return string + newSuffix; - } - if (string.slice(-oldSuffix.length) != oldSuffix) { - throw Error("string ".concat(JSON.stringify(string), " doesn't end with suffix ").concat(JSON.stringify(oldSuffix), "; this is a bug")); - } - return string.slice(0, -oldSuffix.length) + newSuffix; - } - function removePrefix(string, oldPrefix) { - return replacePrefix(string, oldPrefix, ''); - } - function removeSuffix(string, oldSuffix) { - return replaceSuffix(string, oldSuffix, ''); - } - function maximumOverlap(string1, string2) { - return string2.slice(0, overlapCount(string1, string2)); - } - - // Nicked from https://stackoverflow.com/a/60422853/1709587 - function overlapCount(a, b) { - // Deal with cases where the strings differ in length - var startA = 0; - if (a.length > b.length) { - startA = a.length - b.length; - } - var endB = b.length; - if (a.length < b.length) { - endB = a.length; - } - // Create a back-reference for each index - // that should be followed in case of a mismatch. - // We only need B to make these references: - var map = Array(endB); - var k = 0; // Index that lags behind j - map[0] = 0; - for (var j = 1; j < endB; j++) { - if (b[j] == b[k]) { - map[j] = map[k]; // skip over the same character (optional optimisation) - } else { - map[j] = k; - } - while (k > 0 && b[j] != b[k]) { - k = map[k]; - } - if (b[j] == b[k]) { - k++; - } - } - // Phase 2: use these references while iterating over A - k = 0; - for (var i = startA; i < a.length; i++) { - while (k > 0 && a[i] != b[k]) { - k = map[k]; - } - if (a[i] == b[k]) { - k++; - } - } - return k; - } - - /** - * Returns true if the string consistently uses Windows line endings. - */ - function hasOnlyWinLineEndings(string) { - return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/); - } - - /** - * Returns true if the string consistently uses Unix line endings. - */ - function hasOnlyUnixLineEndings(string) { - return !string.includes('\r\n') && string.includes('\n'); - } - - // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode - // - // Ranges and exceptions: - // Latin-1 Supplement, 0080–00FF - // - U+00D7 × Multiplication sign - // - U+00F7 ÷ Division sign - // Latin Extended-A, 0100–017F - // Latin Extended-B, 0180–024F - // IPA Extensions, 0250–02AF - // Spacing Modifier Letters, 02B0–02FF - // - U+02C7 ˇ ˇ Caron - // - U+02D8 ˘ ˘ Breve - // - U+02D9 ˙ ˙ Dot Above - // - U+02DA ˚ ˚ Ring Above - // - U+02DB ˛ ˛ Ogonek - // - U+02DC ˜ ˜ Small Tilde - // - U+02DD ˝ ˝ Double Acute Accent - // Latin Extended Additional, 1E00–1EFF - var extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}"; - - // Each token is one of the following: - // - A punctuation mark plus the surrounding whitespace - // - A word plus the surrounding whitespace - // - Pure whitespace (but only in the special case where this the entire text - // is just whitespace) - // - // We have to include surrounding whitespace in the tokens because the two - // alternative approaches produce horribly broken results: - // * If we just discard the whitespace, we can't fully reproduce the original - // text from the sequence of tokens and any attempt to render the diff will - // get the whitespace wrong. - // * If we have separate tokens for whitespace, then in a typical text every - // second token will be a single space character. But this often results in - // the optimal diff between two texts being a perverse one that preserves - // the spaces between words but deletes and reinserts actual common words. - // See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640 - // for an example. - // - // Keeping the surrounding whitespace of course has implications for .equals - // and .join, not just .tokenize. - - // This regex does NOT fully implement the tokenization rules described above. - // Instead, it gives runs of whitespace their own "token". The tokenize method - // then handles stitching whitespace tokens onto adjacent word or punctuation - // tokens. - var tokenizeIncludingWhitespace = new RegExp("[".concat(extendedWordChars, "]+|\\s+|[^").concat(extendedWordChars, "]"), 'ug'); - var wordDiff = new Diff(); - wordDiff.equals = function (left, right, options) { - if (options.ignoreCase) { - left = left.toLowerCase(); - right = right.toLowerCase(); - } - return left.trim() === right.trim(); - }; - wordDiff.tokenize = function (value) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var parts; - if (options.intlSegmenter) { - if (options.intlSegmenter.resolvedOptions().granularity != 'word') { - throw new Error('The segmenter passed must have a granularity of "word"'); - } - parts = Array.from(options.intlSegmenter.segment(value), function (segment) { - return segment.segment; - }); - } else { - parts = value.match(tokenizeIncludingWhitespace) || []; - } - var tokens = []; - var prevPart = null; - parts.forEach(function (part) { - if (/\s/.test(part)) { - if (prevPart == null) { - tokens.push(part); - } else { - tokens.push(tokens.pop() + part); - } - } else if (/\s/.test(prevPart)) { - if (tokens[tokens.length - 1] == prevPart) { - tokens.push(tokens.pop() + part); - } else { - tokens.push(prevPart + part); - } - } else { - tokens.push(part); - } - prevPart = part; - }); - return tokens; - }; - wordDiff.join = function (tokens) { - // Tokens being joined here will always have appeared consecutively in the - // same text, so we can simply strip off the leading whitespace from all the - // tokens except the first (and except any whitespace-only tokens - but such - // a token will always be the first and only token anyway) and then join them - // and the whitespace around words and punctuation will end up correct. - return tokens.map(function (token, i) { - if (i == 0) { - return token; - } else { - return token.replace(/^\s+/, ''); - } - }).join(''); - }; - wordDiff.postProcess = function (changes, options) { - if (!changes || options.oneChangePerToken) { - return changes; - } - var lastKeep = null; - // Change objects representing any insertion or deletion since the last - // "keep" change object. There can be at most one of each. - var insertion = null; - var deletion = null; - changes.forEach(function (change) { - if (change.added) { - insertion = change; - } else if (change.removed) { - deletion = change; - } else { - if (insertion || deletion) { - // May be false at start of text - dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change); - } - lastKeep = change; - insertion = null; - deletion = null; - } - }); - if (insertion || deletion) { - dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null); - } - return changes; - }; - function diffWords(oldStr, newStr, options) { - // This option has never been documented and never will be (it's clearer to - // just call `diffWordsWithSpace` directly if you need that behavior), but - // has existed in jsdiff for a long time, so we retain support for it here - // for the sake of backwards compatibility. - if ((options === null || options === void 0 ? void 0 : options.ignoreWhitespace) != null && !options.ignoreWhitespace) { - return diffWordsWithSpace(oldStr, newStr, options); - } - return wordDiff.diff(oldStr, newStr, options); - } - function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) { - // Before returning, we tidy up the leading and trailing whitespace of the - // change objects to eliminate cases where trailing whitespace in one object - // is repeated as leading whitespace in the next. - // Below are examples of the outcomes we want here to explain the code. - // I=insert, K=keep, D=delete - // 1. diffing 'foo bar baz' vs 'foo baz' - // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz' - // After cleanup, we want: K:'foo ' D:'bar ' K:'baz' - // - // 2. Diffing 'foo bar baz' vs 'foo qux baz' - // Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz' - // After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz' - // - // 3. Diffing 'foo\nbar baz' vs 'foo baz' - // Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz' - // After cleanup, we want K'foo' D:'\nbar' K:' baz' - // - // 4. Diffing 'foo baz' vs 'foo\nbar baz' - // Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz' - // After cleanup, we ideally want K'foo' I:'\nbar' K:' baz' - // but don't actually manage this currently (the pre-cleanup change - // objects don't contain enough information to make it possible). - // - // 5. Diffing 'foo bar baz' vs 'foo baz' - // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz' - // After cleanup, we want K:'foo ' D:' bar ' K:'baz' - // - // Our handling is unavoidably imperfect in the case where there's a single - // indel between keeps and the whitespace has changed. For instance, consider - // diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change - // object to represent the insertion of the space character (which isn't even - // a token), we have no way to avoid losing information about the texts' - // original whitespace in the result we return. Still, we do our best to - // output something that will look sensible if we e.g. print it with - // insertions in green and deletions in red. - - // Between two "keep" change objects (or before the first or after the last - // change object), we can have either: - // * A "delete" followed by an "insert" - // * Just an "insert" - // * Just a "delete" - // We handle the three cases separately. - if (deletion && insertion) { - var oldWsPrefix = deletion.value.match(/^\s*/)[0]; - var oldWsSuffix = deletion.value.match(/\s*$/)[0]; - var newWsPrefix = insertion.value.match(/^\s*/)[0]; - var newWsSuffix = insertion.value.match(/\s*$/)[0]; - if (startKeep) { - var commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix); - startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix); - deletion.value = removePrefix(deletion.value, commonWsPrefix); - insertion.value = removePrefix(insertion.value, commonWsPrefix); - } - if (endKeep) { - var commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix); - endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix); - deletion.value = removeSuffix(deletion.value, commonWsSuffix); - insertion.value = removeSuffix(insertion.value, commonWsSuffix); - } - } else if (insertion) { - // The whitespaces all reflect what was in the new text rather than - // the old, so we essentially have no information about whitespace - // insertion or deletion. We just want to dedupe the whitespace. - // We do that by having each change object keep its trailing - // whitespace and deleting duplicate leading whitespace where - // present. - if (startKeep) { - insertion.value = insertion.value.replace(/^\s*/, ''); - } - if (endKeep) { - endKeep.value = endKeep.value.replace(/^\s*/, ''); - } - // otherwise we've got a deletion and no insertion - } else if (startKeep && endKeep) { - var newWsFull = endKeep.value.match(/^\s*/)[0], - delWsStart = deletion.value.match(/^\s*/)[0], - delWsEnd = deletion.value.match(/\s*$/)[0]; - - // Any whitespace that comes straight after startKeep in both the old and - // new texts, assign to startKeep and remove from the deletion. - var newWsStart = longestCommonPrefix(newWsFull, delWsStart); - deletion.value = removePrefix(deletion.value, newWsStart); - - // Any whitespace that comes straight before endKeep in both the old and - // new texts, and hasn't already been assigned to startKeep, assign to - // endKeep and remove from the deletion. - var newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd); - deletion.value = removeSuffix(deletion.value, newWsEnd); - endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd); - - // If there's any whitespace from the new text that HASN'T already been - // assigned, assign it to the start: - startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length)); - } else if (endKeep) { - // We are at the start of the text. Preserve all the whitespace on - // endKeep, and just remove whitespace from the end of deletion to the - // extent that it overlaps with the start of endKeep. - var endKeepWsPrefix = endKeep.value.match(/^\s*/)[0]; - var deletionWsSuffix = deletion.value.match(/\s*$/)[0]; - var overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix); - deletion.value = removeSuffix(deletion.value, overlap); - } else if (startKeep) { - // We are at the END of the text. Preserve all the whitespace on - // startKeep, and just remove whitespace from the start of deletion to - // the extent that it overlaps with the end of startKeep. - var startKeepWsSuffix = startKeep.value.match(/\s*$/)[0]; - var deletionWsPrefix = deletion.value.match(/^\s*/)[0]; - var _overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix); - deletion.value = removePrefix(deletion.value, _overlap); - } - } - var wordWithSpaceDiff = new Diff(); - wordWithSpaceDiff.tokenize = function (value) { - // Slightly different to the tokenizeIncludingWhitespace regex used above in - // that this one treats each individual newline as a distinct tokens, rather - // than merging them into other surrounding whitespace. This was requested - // in https://github.com/kpdecker/jsdiff/issues/180 & - // https://github.com/kpdecker/jsdiff/issues/211 - var regex = new RegExp("(\\r?\\n)|[".concat(extendedWordChars, "]+|[^\\S\\n\\r]+|[^").concat(extendedWordChars, "]"), 'ug'); - return value.match(regex) || []; - }; - function diffWordsWithSpace(oldStr, newStr, options) { - return wordWithSpaceDiff.diff(oldStr, newStr, options); - } - - function generateOptions(options, defaults) { - if (typeof options === 'function') { - defaults.callback = options; - } else if (options) { - for (var name in options) { - /* istanbul ignore else */ - if (options.hasOwnProperty(name)) { - defaults[name] = options[name]; - } - } - } - return defaults; - } - - var lineDiff = new Diff(); - lineDiff.tokenize = function (value, options) { - if (options.stripTrailingCr) { - // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior - value = value.replace(/\r\n/g, '\n'); - } - var retLines = [], - linesAndNewlines = value.split(/(\n|\r\n)/); - - // Ignore the final empty token that occurs if the string ends with a new line - if (!linesAndNewlines[linesAndNewlines.length - 1]) { - linesAndNewlines.pop(); - } - - // Merge the content and line separators into single tokens - for (var i = 0; i < linesAndNewlines.length; i++) { - var line = linesAndNewlines[i]; - if (i % 2 && !options.newlineIsToken) { - retLines[retLines.length - 1] += line; - } else { - retLines.push(line); - } - } - return retLines; - }; - lineDiff.equals = function (left, right, options) { - // If we're ignoring whitespace, we need to normalise lines by stripping - // whitespace before checking equality. (This has an annoying interaction - // with newlineIsToken that requires special handling: if newlines get their - // own token, then we DON'T want to trim the *newline* tokens down to empty - // strings, since this would cause us to treat whitespace-only line content - // as equal to a separator between lines, which would be weird and - // inconsistent with the documented behavior of the options.) - if (options.ignoreWhitespace) { - if (!options.newlineIsToken || !left.includes('\n')) { - left = left.trim(); - } - if (!options.newlineIsToken || !right.includes('\n')) { - right = right.trim(); - } - } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { - if (left.endsWith('\n')) { - left = left.slice(0, -1); - } - if (right.endsWith('\n')) { - right = right.slice(0, -1); - } - } - return Diff.prototype.equals.call(this, left, right, options); - }; - function diffLines(oldStr, newStr, callback) { - return lineDiff.diff(oldStr, newStr, callback); - } - - // Kept for backwards compatibility. This is a rather arbitrary wrapper method - // that just calls `diffLines` with `ignoreWhitespace: true`. It's confusing to - // have two ways to do exactly the same thing in the API, so we no longer - // document this one (library users should explicitly use `diffLines` with - // `ignoreWhitespace: true` instead) but we keep it around to maintain - // compatibility with code that used old versions. - function diffTrimmedLines(oldStr, newStr, callback) { - var options = generateOptions(callback, { - ignoreWhitespace: true - }); - return lineDiff.diff(oldStr, newStr, options); - } - - var sentenceDiff = new Diff(); - sentenceDiff.tokenize = function (value) { - return value.split(/(\S.+?[.!?])(?=\s+|$)/); - }; - function diffSentences(oldStr, newStr, callback) { - return sentenceDiff.diff(oldStr, newStr, callback); - } - - var cssDiff = new Diff(); - cssDiff.tokenize = function (value) { - return value.split(/([{}:;,]|\s+)/); - }; - function diffCss(oldStr, newStr, callback) { - return cssDiff.diff(oldStr, newStr, callback); - } - - function ownKeys(e, r) { - var t = Object.keys(e); - if (Object.getOwnPropertySymbols) { - var o = Object.getOwnPropertySymbols(e); - r && (o = o.filter(function (r) { - return Object.getOwnPropertyDescriptor(e, r).enumerable; - })), t.push.apply(t, o); - } - return t; - } - function _objectSpread2(e) { - for (var r = 1; r < arguments.length; r++) { - var t = null != arguments[r] ? arguments[r] : {}; - r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { - _defineProperty(e, r, t[r]); - }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { - Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); - }); - } - return e; - } - function _toPrimitive(t, r) { - if ("object" != typeof t || !t) return t; - var e = t[Symbol.toPrimitive]; - if (void 0 !== e) { - var i = e.call(t, r || "default"); - if ("object" != typeof i) return i; - throw new TypeError("@@toPrimitive must return a primitive value."); - } - return ("string" === r ? String : Number)(t); - } - function _toPropertyKey(t) { - var i = _toPrimitive(t, "string"); - return "symbol" == typeof i ? i : i + ""; - } - function _typeof(o) { - "@babel/helpers - typeof"; - - return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { - return typeof o; - } : function (o) { - return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; - }, _typeof(o); - } - function _defineProperty(obj, key, value) { - key = _toPropertyKey(key); - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } else { - obj[key] = value; - } - return obj; - } - function _toConsumableArray(arr) { - return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); - } - function _arrayWithoutHoles(arr) { - if (Array.isArray(arr)) return _arrayLikeToArray(arr); - } - function _iterableToArray(iter) { - if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); - } - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); - } - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - return arr2; - } - function _nonIterableSpread() { - throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); - } - - var jsonDiff = new Diff(); - // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a - // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: - jsonDiff.useLongestToken = true; - jsonDiff.tokenize = lineDiff.tokenize; - jsonDiff.castInput = function (value, options) { - var undefinedReplacement = options.undefinedReplacement, - _options$stringifyRep = options.stringifyReplacer, - stringifyReplacer = _options$stringifyRep === void 0 ? function (k, v) { - return typeof v === 'undefined' ? undefinedReplacement : v; - } : _options$stringifyRep; - return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); - }; - jsonDiff.equals = function (left, right, options) { - return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); - }; - function diffJson(oldObj, newObj, options) { - return jsonDiff.diff(oldObj, newObj, options); - } - - // This function handles the presence of circular references by bailing out when encountering an - // object that is already on the "stack" of items being processed. Accepts an optional replacer - function canonicalize(obj, stack, replacementStack, replacer, key) { - stack = stack || []; - replacementStack = replacementStack || []; - if (replacer) { - obj = replacer(key, obj); - } - var i; - for (i = 0; i < stack.length; i += 1) { - if (stack[i] === obj) { - return replacementStack[i]; - } - } - var canonicalizedObj; - if ('[object Array]' === Object.prototype.toString.call(obj)) { - stack.push(obj); - canonicalizedObj = new Array(obj.length); - replacementStack.push(canonicalizedObj); - for (i = 0; i < obj.length; i += 1) { - canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); - } - stack.pop(); - replacementStack.pop(); - return canonicalizedObj; - } - if (obj && obj.toJSON) { - obj = obj.toJSON(); - } - if (_typeof(obj) === 'object' && obj !== null) { - stack.push(obj); - canonicalizedObj = {}; - replacementStack.push(canonicalizedObj); - var sortedKeys = [], - _key; - for (_key in obj) { - /* istanbul ignore else */ - if (Object.prototype.hasOwnProperty.call(obj, _key)) { - sortedKeys.push(_key); - } - } - sortedKeys.sort(); - for (i = 0; i < sortedKeys.length; i += 1) { - _key = sortedKeys[i]; - canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); - } - stack.pop(); - replacementStack.pop(); - } else { - canonicalizedObj = obj; - } - return canonicalizedObj; - } - - var arrayDiff = new Diff(); - arrayDiff.tokenize = function (value) { - return value.slice(); - }; - arrayDiff.join = arrayDiff.removeEmpty = function (value) { - return value; - }; - function diffArrays(oldArr, newArr, callback) { - return arrayDiff.diff(oldArr, newArr, callback); - } - - function unixToWin(patch) { - if (Array.isArray(patch)) { - return patch.map(unixToWin); - } - return _objectSpread2(_objectSpread2({}, patch), {}, { - hunks: patch.hunks.map(function (hunk) { - return _objectSpread2(_objectSpread2({}, hunk), {}, { - lines: hunk.lines.map(function (line, i) { - var _hunk$lines; - return line.startsWith('\\') || line.endsWith('\r') || (_hunk$lines = hunk.lines[i + 1]) !== null && _hunk$lines !== void 0 && _hunk$lines.startsWith('\\') ? line : line + '\r'; - }) - }); - }) - }); - } - function winToUnix(patch) { - if (Array.isArray(patch)) { - return patch.map(winToUnix); - } - return _objectSpread2(_objectSpread2({}, patch), {}, { - hunks: patch.hunks.map(function (hunk) { - return _objectSpread2(_objectSpread2({}, hunk), {}, { - lines: hunk.lines.map(function (line) { - return line.endsWith('\r') ? line.substring(0, line.length - 1) : line; - }) - }); - }) - }); - } - - /** - * Returns true if the patch consistently uses Unix line endings (or only involves one line and has - * no line endings). - */ - function isUnix(patch) { - if (!Array.isArray(patch)) { - patch = [patch]; - } - return !patch.some(function (index) { - return index.hunks.some(function (hunk) { - return hunk.lines.some(function (line) { - return !line.startsWith('\\') && line.endsWith('\r'); - }); - }); - }); - } - - /** - * Returns true if the patch uses Windows line endings and only Windows line endings. - */ - function isWin(patch) { - if (!Array.isArray(patch)) { - patch = [patch]; - } - return patch.some(function (index) { - return index.hunks.some(function (hunk) { - return hunk.lines.some(function (line) { - return line.endsWith('\r'); - }); - }); - }) && patch.every(function (index) { - return index.hunks.every(function (hunk) { - return hunk.lines.every(function (line, i) { - var _hunk$lines2; - return line.startsWith('\\') || line.endsWith('\r') || ((_hunk$lines2 = hunk.lines[i + 1]) === null || _hunk$lines2 === void 0 ? void 0 : _hunk$lines2.startsWith('\\')); - }); - }); - }); - } - - function parsePatch(uniDiff) { - var diffstr = uniDiff.split(/\n/), - list = [], - i = 0; - function parseIndex() { - var index = {}; - list.push(index); - - // Parse diff metadata - while (i < diffstr.length) { - var line = diffstr[i]; - - // File header found, end parsing diff metadata - if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) { - break; - } - - // Diff index - var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line); - if (header) { - index.index = header[1]; - } - i++; - } - - // Parse file headers if they are defined. Unified diff requires them, but - // there's no technical issues to have an isolated hunk without file header - parseFileHeader(index); - parseFileHeader(index); - - // Parse hunks - index.hunks = []; - while (i < diffstr.length) { - var _line = diffstr[i]; - if (/^(Index:\s|diff\s|\-\-\-\s|\+\+\+\s|===================================================================)/.test(_line)) { - break; - } else if (/^@@/.test(_line)) { - index.hunks.push(parseHunk()); - } else if (_line) { - throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line)); - } else { - i++; - } - } - } - - // Parses the --- and +++ headers, if none are found, no lines - // are consumed. - function parseFileHeader(index) { - var fileHeader = /^(---|\+\+\+)\s+(.*)\r?$/.exec(diffstr[i]); - if (fileHeader) { - var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; - var data = fileHeader[2].split('\t', 2); - var fileName = data[0].replace(/\\\\/g, '\\'); - if (/^".*"$/.test(fileName)) { - fileName = fileName.substr(1, fileName.length - 2); - } - index[keyPrefix + 'FileName'] = fileName; - index[keyPrefix + 'Header'] = (data[1] || '').trim(); - i++; - } - } - - // Parses a hunk - // This assumes that we are at the start of a hunk. - function parseHunk() { - var chunkHeaderIndex = i, - chunkHeaderLine = diffstr[i++], - chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); - var hunk = { - oldStart: +chunkHeader[1], - oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2], - newStart: +chunkHeader[3], - newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4], - lines: [] - }; - - // Unified Diff Format quirk: If the chunk size is 0, - // the first number is one lower than one would expect. - // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 - if (hunk.oldLines === 0) { - hunk.oldStart += 1; - } - if (hunk.newLines === 0) { - hunk.newStart += 1; - } - var addCount = 0, - removeCount = 0; - for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || (_diffstr$i = diffstr[i]) !== null && _diffstr$i !== void 0 && _diffstr$i.startsWith('\\')); i++) { - var _diffstr$i; - var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0]; - if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { - hunk.lines.push(diffstr[i]); - if (operation === '+') { - addCount++; - } else if (operation === '-') { - removeCount++; - } else if (operation === ' ') { - addCount++; - removeCount++; - } - } else { - throw new Error("Hunk at line ".concat(chunkHeaderIndex + 1, " contained invalid line ").concat(diffstr[i])); - } - } - - // Handle the empty block count case - if (!addCount && hunk.newLines === 1) { - hunk.newLines = 0; - } - if (!removeCount && hunk.oldLines === 1) { - hunk.oldLines = 0; - } - - // Perform sanity checking - if (addCount !== hunk.newLines) { - throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); - } - if (removeCount !== hunk.oldLines) { - throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); - } - return hunk; - } - while (i < diffstr.length) { - parseIndex(); - } - return list; - } - - // Iterator that traverses in the range of [min, max], stepping - // by distance from a given start position. I.e. for [0, 4], with - // start of 2, this will iterate 2, 3, 1, 4, 0. - function distanceIterator (start, minLine, maxLine) { - var wantForward = true, - backwardExhausted = false, - forwardExhausted = false, - localOffset = 1; - return function iterator() { - if (wantForward && !forwardExhausted) { - if (backwardExhausted) { - localOffset++; - } else { - wantForward = false; - } - - // Check if trying to fit beyond text length, and if not, check it fits - // after offset location (or desired location on first iteration) - if (start + localOffset <= maxLine) { - return start + localOffset; - } - forwardExhausted = true; - } - if (!backwardExhausted) { - if (!forwardExhausted) { - wantForward = true; - } - - // Check if trying to fit before text beginning, and if not, check it fits - // before offset location - if (minLine <= start - localOffset) { - return start - localOffset++; - } - backwardExhausted = true; - return iterator(); - } - - // We tried to fit hunk before text beginning and beyond text length, then - // hunk can't fit on the text. Return undefined - }; - } - - function applyPatch(source, uniDiff) { - var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - if (typeof uniDiff === 'string') { - uniDiff = parsePatch(uniDiff); - } - if (Array.isArray(uniDiff)) { - if (uniDiff.length > 1) { - throw new Error('applyPatch only works with a single input.'); - } - uniDiff = uniDiff[0]; - } - if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) { - if (hasOnlyWinLineEndings(source) && isUnix(uniDiff)) { - uniDiff = unixToWin(uniDiff); - } else if (hasOnlyUnixLineEndings(source) && isWin(uniDiff)) { - uniDiff = winToUnix(uniDiff); - } - } - - // Apply the diff to the input - var lines = source.split('\n'), - hunks = uniDiff.hunks, - compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) { - return line === patchContent; - }, - fuzzFactor = options.fuzzFactor || 0, - minLine = 0; - if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) { - throw new Error('fuzzFactor must be a non-negative integer'); - } - - // Special case for empty patch. - if (!hunks.length) { - return source; - } - - // Before anything else, handle EOFNL insertion/removal. If the patch tells us to make a change - // to the EOFNL that is redundant/impossible - i.e. to remove a newline that's not there, or add a - // newline that already exists - then we either return false and fail to apply the patch (if - // fuzzFactor is 0) or simply ignore the problem and do nothing (if fuzzFactor is >0). - // If we do need to remove/add a newline at EOF, this will always be in the final hunk: - var prevLine = '', - removeEOFNL = false, - addEOFNL = false; - for (var i = 0; i < hunks[hunks.length - 1].lines.length; i++) { - var line = hunks[hunks.length - 1].lines[i]; - if (line[0] == '\\') { - if (prevLine[0] == '+') { - removeEOFNL = true; - } else if (prevLine[0] == '-') { - addEOFNL = true; - } - } - prevLine = line; - } - if (removeEOFNL) { - if (addEOFNL) { - // This means the final line gets changed but doesn't have a trailing newline in either the - // original or patched version. In that case, we do nothing if fuzzFactor > 0, and if - // fuzzFactor is 0, we simply validate that the source file has no trailing newline. - if (!fuzzFactor && lines[lines.length - 1] == '') { - return false; - } - } else if (lines[lines.length - 1] == '') { - lines.pop(); - } else if (!fuzzFactor) { - return false; - } - } else if (addEOFNL) { - if (lines[lines.length - 1] != '') { - lines.push(''); - } else if (!fuzzFactor) { - return false; - } - } - - /** - * Checks if the hunk can be made to fit at the provided location with at most `maxErrors` - * insertions, substitutions, or deletions, while ensuring also that: - * - lines deleted in the hunk match exactly, and - * - wherever an insertion operation or block of insertion operations appears in the hunk, the - * immediately preceding and following lines of context match exactly - * - * `toPos` should be set such that lines[toPos] is meant to match hunkLines[0]. - * - * If the hunk can be applied, returns an object with properties `oldLineLastI` and - * `replacementLines`. Otherwise, returns null. - */ - function applyHunk(hunkLines, toPos, maxErrors) { - var hunkLinesI = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; - var lastContextLineMatched = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; - var patchedLines = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : []; - var patchedLinesLength = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 0; - var nConsecutiveOldContextLines = 0; - var nextContextLineMustMatch = false; - for (; hunkLinesI < hunkLines.length; hunkLinesI++) { - var hunkLine = hunkLines[hunkLinesI], - operation = hunkLine.length > 0 ? hunkLine[0] : ' ', - content = hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine; - if (operation === '-') { - if (compareLine(toPos + 1, lines[toPos], operation, content)) { - toPos++; - nConsecutiveOldContextLines = 0; - } else { - if (!maxErrors || lines[toPos] == null) { - return null; - } - patchedLines[patchedLinesLength] = lines[toPos]; - return applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1); - } - } - if (operation === '+') { - if (!lastContextLineMatched) { - return null; - } - patchedLines[patchedLinesLength] = content; - patchedLinesLength++; - nConsecutiveOldContextLines = 0; - nextContextLineMustMatch = true; - } - if (operation === ' ') { - nConsecutiveOldContextLines++; - patchedLines[patchedLinesLength] = lines[toPos]; - if (compareLine(toPos + 1, lines[toPos], operation, content)) { - patchedLinesLength++; - lastContextLineMatched = true; - nextContextLineMustMatch = false; - toPos++; - } else { - if (nextContextLineMustMatch || !maxErrors) { - return null; - } - - // Consider 3 possibilities in sequence: - // 1. lines contains a *substitution* not included in the patch context, or - // 2. lines contains an *insertion* not included in the patch context, or - // 3. lines contains a *deletion* not included in the patch context - // The first two options are of course only possible if the line from lines is non-null - - // i.e. only option 3 is possible if we've overrun the end of the old file. - return lines[toPos] && (applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength + 1) || applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1)) || applyHunk(hunkLines, toPos, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength); - } - } - } - - // Before returning, trim any unmodified context lines off the end of patchedLines and reduce - // toPos (and thus oldLineLastI) accordingly. This allows later hunks to be applied to a region - // that starts in this hunk's trailing context. - patchedLinesLength -= nConsecutiveOldContextLines; - toPos -= nConsecutiveOldContextLines; - patchedLines.length = patchedLinesLength; - return { - patchedLines: patchedLines, - oldLineLastI: toPos - 1 - }; - } - var resultLines = []; - - // Search best fit offsets for each hunk based on the previous ones - var prevHunkOffset = 0; - for (var _i = 0; _i < hunks.length; _i++) { - var hunk = hunks[_i]; - var hunkResult = void 0; - var maxLine = lines.length - hunk.oldLines + fuzzFactor; - var toPos = void 0; - for (var maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) { - toPos = hunk.oldStart + prevHunkOffset - 1; - var iterator = distanceIterator(toPos, minLine, maxLine); - for (; toPos !== undefined; toPos = iterator()) { - hunkResult = applyHunk(hunk.lines, toPos, maxErrors); - if (hunkResult) { - break; - } - } - if (hunkResult) { - break; - } - } - if (!hunkResult) { - return false; - } - - // Copy everything from the end of where we applied the last hunk to the start of this hunk - for (var _i2 = minLine; _i2 < toPos; _i2++) { - resultLines.push(lines[_i2]); - } - - // Add the lines produced by applying the hunk: - for (var _i3 = 0; _i3 < hunkResult.patchedLines.length; _i3++) { - var _line = hunkResult.patchedLines[_i3]; - resultLines.push(_line); - } - - // Set lower text limit to end of the current hunk, so next ones don't try - // to fit over already patched text - minLine = hunkResult.oldLineLastI + 1; - - // Note the offset between where the patch said the hunk should've applied and where we - // applied it, so we can adjust future hunks accordingly: - prevHunkOffset = toPos + 1 - hunk.oldStart; - } - - // Copy over the rest of the lines from the old text - for (var _i4 = minLine; _i4 < lines.length; _i4++) { - resultLines.push(lines[_i4]); - } - return resultLines.join('\n'); - } - - // Wrapper that supports multiple file patches via callbacks. - function applyPatches(uniDiff, options) { - if (typeof uniDiff === 'string') { - uniDiff = parsePatch(uniDiff); - } - var currentIndex = 0; - function processIndex() { - var index = uniDiff[currentIndex++]; - if (!index) { - return options.complete(); - } - options.loadFile(index, function (err, data) { - if (err) { - return options.complete(err); - } - var updatedContent = applyPatch(data, index, options); - options.patched(index, updatedContent, function (err) { - if (err) { - return options.complete(err); - } - processIndex(); - }); - }); - } - processIndex(); - } - - function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { - if (!options) { - options = {}; - } - if (typeof options === 'function') { - options = { - callback: options - }; - } - if (typeof options.context === 'undefined') { - options.context = 4; - } - if (options.newlineIsToken) { - throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions'); - } - if (!options.callback) { - return diffLinesResultToPatch(diffLines(oldStr, newStr, options)); - } else { - var _options = options, - _callback = _options.callback; - diffLines(oldStr, newStr, _objectSpread2(_objectSpread2({}, options), {}, { - callback: function callback(diff) { - var patch = diffLinesResultToPatch(diff); - _callback(patch); - } - })); - } - function diffLinesResultToPatch(diff) { - // STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays - // of lines containing trailing newline characters. We'll tidy up later... - - if (!diff) { - return; - } - diff.push({ - value: '', - lines: [] - }); // Append an empty value to make cleanup easier - - function contextLines(lines) { - return lines.map(function (entry) { - return ' ' + entry; - }); - } - var hunks = []; - var oldRangeStart = 0, - newRangeStart = 0, - curRange = [], - oldLine = 1, - newLine = 1; - var _loop = function _loop() { - var current = diff[i], - lines = current.lines || splitLines(current.value); - current.lines = lines; - if (current.added || current.removed) { - var _curRange; - // If we have previous context, start with that - if (!oldRangeStart) { - var prev = diff[i - 1]; - oldRangeStart = oldLine; - newRangeStart = newLine; - if (prev) { - curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; - oldRangeStart -= curRange.length; - newRangeStart -= curRange.length; - } - } - - // Output our changes - (_curRange = curRange).push.apply(_curRange, _toConsumableArray(lines.map(function (entry) { - return (current.added ? '+' : '-') + entry; - }))); - - // Track the updated file position - if (current.added) { - newLine += lines.length; - } else { - oldLine += lines.length; - } - } else { - // Identical context lines. Track line changes - if (oldRangeStart) { - // Close out any changes that have been output (or join overlapping) - if (lines.length <= options.context * 2 && i < diff.length - 2) { - var _curRange2; - // Overlapping - (_curRange2 = curRange).push.apply(_curRange2, _toConsumableArray(contextLines(lines))); - } else { - var _curRange3; - // end the range and output - var contextSize = Math.min(lines.length, options.context); - (_curRange3 = curRange).push.apply(_curRange3, _toConsumableArray(contextLines(lines.slice(0, contextSize)))); - var _hunk = { - oldStart: oldRangeStart, - oldLines: oldLine - oldRangeStart + contextSize, - newStart: newRangeStart, - newLines: newLine - newRangeStart + contextSize, - lines: curRange - }; - hunks.push(_hunk); - oldRangeStart = 0; - newRangeStart = 0; - curRange = []; - } - } - oldLine += lines.length; - newLine += lines.length; - } - }; - for (var i = 0; i < diff.length; i++) { - _loop(); - } - - // Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add - // "\ No newline at end of file". - for (var _i = 0, _hunks = hunks; _i < _hunks.length; _i++) { - var hunk = _hunks[_i]; - for (var _i2 = 0; _i2 < hunk.lines.length; _i2++) { - if (hunk.lines[_i2].endsWith('\n')) { - hunk.lines[_i2] = hunk.lines[_i2].slice(0, -1); - } else { - hunk.lines.splice(_i2 + 1, 0, '\\ No newline at end of file'); - _i2++; // Skip the line we just added, then continue iterating - } - } - } - return { - oldFileName: oldFileName, - newFileName: newFileName, - oldHeader: oldHeader, - newHeader: newHeader, - hunks: hunks - }; - } - } - function formatPatch(diff) { - if (Array.isArray(diff)) { - return diff.map(formatPatch).join('\n'); - } - var ret = []; - if (diff.oldFileName == diff.newFileName) { - ret.push('Index: ' + diff.oldFileName); - } - ret.push('==================================================================='); - ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); - ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); - for (var i = 0; i < diff.hunks.length; i++) { - var hunk = diff.hunks[i]; - // Unified Diff Format quirk: If the chunk size is 0, - // the first number is one lower than one would expect. - // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 - if (hunk.oldLines === 0) { - hunk.oldStart -= 1; - } - if (hunk.newLines === 0) { - hunk.newStart -= 1; - } - ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@'); - ret.push.apply(ret, hunk.lines); - } - return ret.join('\n') + '\n'; - } - function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { - var _options2; - if (typeof options === 'function') { - options = { - callback: options - }; - } - if (!((_options2 = options) !== null && _options2 !== void 0 && _options2.callback)) { - var patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); - if (!patchObj) { - return; - } - return formatPatch(patchObj); - } else { - var _options3 = options, - _callback2 = _options3.callback; - structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, _objectSpread2(_objectSpread2({}, options), {}, { - callback: function callback(patchObj) { - if (!patchObj) { - _callback2(); - } else { - _callback2(formatPatch(patchObj)); - } - } - })); - } - } - function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { - return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); - } - - /** - * Split `text` into an array of lines, including the trailing newline character (where present) - */ - function splitLines(text) { - var hasTrailingNl = text.endsWith('\n'); - var result = text.split('\n').map(function (line) { - return line + '\n'; - }); - if (hasTrailingNl) { - result.pop(); - } else { - result.push(result.pop().slice(0, -1)); - } - return result; - } - - function arrayEqual(a, b) { - if (a.length !== b.length) { - return false; - } - return arrayStartsWith(a, b); - } - function arrayStartsWith(array, start) { - if (start.length > array.length) { - return false; - } - for (var i = 0; i < start.length; i++) { - if (start[i] !== array[i]) { - return false; - } - } - return true; - } - - function calcLineCount(hunk) { - var _calcOldNewLineCount = calcOldNewLineCount(hunk.lines), - oldLines = _calcOldNewLineCount.oldLines, - newLines = _calcOldNewLineCount.newLines; - if (oldLines !== undefined) { - hunk.oldLines = oldLines; - } else { - delete hunk.oldLines; - } - if (newLines !== undefined) { - hunk.newLines = newLines; - } else { - delete hunk.newLines; - } - } - function merge(mine, theirs, base) { - mine = loadPatch(mine, base); - theirs = loadPatch(theirs, base); - var ret = {}; - - // For index we just let it pass through as it doesn't have any necessary meaning. - // Leaving sanity checks on this to the API consumer that may know more about the - // meaning in their own context. - if (mine.index || theirs.index) { - ret.index = mine.index || theirs.index; - } - if (mine.newFileName || theirs.newFileName) { - if (!fileNameChanged(mine)) { - // No header or no change in ours, use theirs (and ours if theirs does not exist) - ret.oldFileName = theirs.oldFileName || mine.oldFileName; - ret.newFileName = theirs.newFileName || mine.newFileName; - ret.oldHeader = theirs.oldHeader || mine.oldHeader; - ret.newHeader = theirs.newHeader || mine.newHeader; - } else if (!fileNameChanged(theirs)) { - // No header or no change in theirs, use ours - ret.oldFileName = mine.oldFileName; - ret.newFileName = mine.newFileName; - ret.oldHeader = mine.oldHeader; - ret.newHeader = mine.newHeader; - } else { - // Both changed... figure it out - ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName); - ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName); - ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader); - ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader); - } - } - ret.hunks = []; - var mineIndex = 0, - theirsIndex = 0, - mineOffset = 0, - theirsOffset = 0; - while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) { - var mineCurrent = mine.hunks[mineIndex] || { - oldStart: Infinity - }, - theirsCurrent = theirs.hunks[theirsIndex] || { - oldStart: Infinity - }; - if (hunkBefore(mineCurrent, theirsCurrent)) { - // This patch does not overlap with any of the others, yay. - ret.hunks.push(cloneHunk(mineCurrent, mineOffset)); - mineIndex++; - theirsOffset += mineCurrent.newLines - mineCurrent.oldLines; - } else if (hunkBefore(theirsCurrent, mineCurrent)) { - // This patch does not overlap with any of the others, yay. - ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset)); - theirsIndex++; - mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines; - } else { - // Overlap, merge as best we can - var mergedHunk = { - oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart), - oldLines: 0, - newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset), - newLines: 0, - lines: [] - }; - mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines); - theirsIndex++; - mineIndex++; - ret.hunks.push(mergedHunk); - } - } - return ret; - } - function loadPatch(param, base) { - if (typeof param === 'string') { - if (/^@@/m.test(param) || /^Index:/m.test(param)) { - return parsePatch(param)[0]; - } - if (!base) { - throw new Error('Must provide a base reference or pass in a patch'); - } - return structuredPatch(undefined, undefined, base, param); - } - return param; - } - function fileNameChanged(patch) { - return patch.newFileName && patch.newFileName !== patch.oldFileName; - } - function selectField(index, mine, theirs) { - if (mine === theirs) { - return mine; - } else { - index.conflict = true; - return { - mine: mine, - theirs: theirs - }; - } - } - function hunkBefore(test, check) { - return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart; - } - function cloneHunk(hunk, offset) { - return { - oldStart: hunk.oldStart, - oldLines: hunk.oldLines, - newStart: hunk.newStart + offset, - newLines: hunk.newLines, - lines: hunk.lines - }; - } - function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) { - // This will generally result in a conflicted hunk, but there are cases where the context - // is the only overlap where we can successfully merge the content here. - var mine = { - offset: mineOffset, - lines: mineLines, - index: 0 - }, - their = { - offset: theirOffset, - lines: theirLines, - index: 0 - }; - - // Handle any leading content - insertLeading(hunk, mine, their); - insertLeading(hunk, their, mine); - - // Now in the overlap content. Scan through and select the best changes from each. - while (mine.index < mine.lines.length && their.index < their.lines.length) { - var mineCurrent = mine.lines[mine.index], - theirCurrent = their.lines[their.index]; - if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) { - // Both modified ... - mutualChange(hunk, mine, their); - } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') { - var _hunk$lines; - // Mine inserted - (_hunk$lines = hunk.lines).push.apply(_hunk$lines, _toConsumableArray(collectChange(mine))); - } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') { - var _hunk$lines2; - // Theirs inserted - (_hunk$lines2 = hunk.lines).push.apply(_hunk$lines2, _toConsumableArray(collectChange(their))); - } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') { - // Mine removed or edited - removal(hunk, mine, their); - } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') { - // Their removed or edited - removal(hunk, their, mine, true); - } else if (mineCurrent === theirCurrent) { - // Context identity - hunk.lines.push(mineCurrent); - mine.index++; - their.index++; - } else { - // Context mismatch - conflict(hunk, collectChange(mine), collectChange(their)); - } - } - - // Now push anything that may be remaining - insertTrailing(hunk, mine); - insertTrailing(hunk, their); - calcLineCount(hunk); - } - function mutualChange(hunk, mine, their) { - var myChanges = collectChange(mine), - theirChanges = collectChange(their); - if (allRemoves(myChanges) && allRemoves(theirChanges)) { - // Special case for remove changes that are supersets of one another - if (arrayStartsWith(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) { - var _hunk$lines3; - (_hunk$lines3 = hunk.lines).push.apply(_hunk$lines3, _toConsumableArray(myChanges)); - return; - } else if (arrayStartsWith(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) { - var _hunk$lines4; - (_hunk$lines4 = hunk.lines).push.apply(_hunk$lines4, _toConsumableArray(theirChanges)); - return; - } - } else if (arrayEqual(myChanges, theirChanges)) { - var _hunk$lines5; - (_hunk$lines5 = hunk.lines).push.apply(_hunk$lines5, _toConsumableArray(myChanges)); - return; - } - conflict(hunk, myChanges, theirChanges); - } - function removal(hunk, mine, their, swap) { - var myChanges = collectChange(mine), - theirChanges = collectContext(their, myChanges); - if (theirChanges.merged) { - var _hunk$lines6; - (_hunk$lines6 = hunk.lines).push.apply(_hunk$lines6, _toConsumableArray(theirChanges.merged)); - } else { - conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges); - } - } - function conflict(hunk, mine, their) { - hunk.conflict = true; - hunk.lines.push({ - conflict: true, - mine: mine, - theirs: their - }); - } - function insertLeading(hunk, insert, their) { - while (insert.offset < their.offset && insert.index < insert.lines.length) { - var line = insert.lines[insert.index++]; - hunk.lines.push(line); - insert.offset++; - } - } - function insertTrailing(hunk, insert) { - while (insert.index < insert.lines.length) { - var line = insert.lines[insert.index++]; - hunk.lines.push(line); - } - } - function collectChange(state) { - var ret = [], - operation = state.lines[state.index][0]; - while (state.index < state.lines.length) { - var line = state.lines[state.index]; - - // Group additions that are immediately after subtractions and treat them as one "atomic" modify change. - if (operation === '-' && line[0] === '+') { - operation = '+'; - } - if (operation === line[0]) { - ret.push(line); - state.index++; - } else { - break; - } - } - return ret; - } - function collectContext(state, matchChanges) { - var changes = [], - merged = [], - matchIndex = 0, - contextChanges = false, - conflicted = false; - while (matchIndex < matchChanges.length && state.index < state.lines.length) { - var change = state.lines[state.index], - match = matchChanges[matchIndex]; - - // Once we've hit our add, then we are done - if (match[0] === '+') { - break; - } - contextChanges = contextChanges || change[0] !== ' '; - merged.push(match); - matchIndex++; - - // Consume any additions in the other block as a conflict to attempt - // to pull in the remaining context after this - if (change[0] === '+') { - conflicted = true; - while (change[0] === '+') { - changes.push(change); - change = state.lines[++state.index]; - } - } - if (match.substr(1) === change.substr(1)) { - changes.push(change); - state.index++; - } else { - conflicted = true; - } - } - if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) { - conflicted = true; - } - if (conflicted) { - return changes; - } - while (matchIndex < matchChanges.length) { - merged.push(matchChanges[matchIndex++]); - } - return { - merged: merged, - changes: changes - }; - } - function allRemoves(changes) { - return changes.reduce(function (prev, change) { - return prev && change[0] === '-'; - }, true); - } - function skipRemoveSuperset(state, removeChanges, delta) { - for (var i = 0; i < delta; i++) { - var changeContent = removeChanges[removeChanges.length - delta + i].substr(1); - if (state.lines[state.index + i] !== ' ' + changeContent) { - return false; - } - } - state.index += delta; - return true; - } - function calcOldNewLineCount(lines) { - var oldLines = 0; - var newLines = 0; - lines.forEach(function (line) { - if (typeof line !== 'string') { - var myCount = calcOldNewLineCount(line.mine); - var theirCount = calcOldNewLineCount(line.theirs); - if (oldLines !== undefined) { - if (myCount.oldLines === theirCount.oldLines) { - oldLines += myCount.oldLines; - } else { - oldLines = undefined; - } - } - if (newLines !== undefined) { - if (myCount.newLines === theirCount.newLines) { - newLines += myCount.newLines; - } else { - newLines = undefined; - } - } - } else { - if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) { - newLines++; - } - if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) { - oldLines++; - } - } - }); - return { - oldLines: oldLines, - newLines: newLines - }; - } - - function reversePatch(structuredPatch) { - if (Array.isArray(structuredPatch)) { - return structuredPatch.map(reversePatch).reverse(); - } - return _objectSpread2(_objectSpread2({}, structuredPatch), {}, { - oldFileName: structuredPatch.newFileName, - oldHeader: structuredPatch.newHeader, - newFileName: structuredPatch.oldFileName, - newHeader: structuredPatch.oldHeader, - hunks: structuredPatch.hunks.map(function (hunk) { - return { - oldLines: hunk.newLines, - oldStart: hunk.newStart, - newLines: hunk.oldLines, - newStart: hunk.oldStart, - lines: hunk.lines.map(function (l) { - if (l.startsWith('-')) { - return "+".concat(l.slice(1)); - } - if (l.startsWith('+')) { - return "-".concat(l.slice(1)); - } - return l; - }) - }; - }) - }); - } - - // See: http://code.google.com/p/google-diff-match-patch/wiki/API - function convertChangesToDMP(changes) { - var ret = [], - change, - operation; - for (var i = 0; i < changes.length; i++) { - change = changes[i]; - if (change.added) { - operation = 1; - } else if (change.removed) { - operation = -1; - } else { - operation = 0; - } - ret.push([operation, change.value]); - } - return ret; - } - - function convertChangesToXML(changes) { - var ret = []; - for (var i = 0; i < changes.length; i++) { - var change = changes[i]; - if (change.added) { - ret.push(''); - } else if (change.removed) { - ret.push(''); - } - ret.push(escapeHTML(change.value)); - if (change.added) { - ret.push(''); - } else if (change.removed) { - ret.push(''); - } - } - return ret.join(''); - } - function escapeHTML(s) { - var n = s; - n = n.replace(/&/g, '&'); - n = n.replace(//g, '>'); - n = n.replace(/"/g, '"'); - return n; - } - - exports.Diff = Diff; - exports.applyPatch = applyPatch; - exports.applyPatches = applyPatches; - exports.canonicalize = canonicalize; - exports.convertChangesToDMP = convertChangesToDMP; - exports.convertChangesToXML = convertChangesToXML; - exports.createPatch = createPatch; - exports.createTwoFilesPatch = createTwoFilesPatch; - exports.diffArrays = diffArrays; - exports.diffChars = diffChars; - exports.diffCss = diffCss; - exports.diffJson = diffJson; - exports.diffLines = diffLines; - exports.diffSentences = diffSentences; - exports.diffTrimmedLines = diffTrimmedLines; - exports.diffWords = diffWords; - exports.diffWordsWithSpace = diffWordsWithSpace; - exports.formatPatch = formatPatch; - exports.merge = merge; - exports.parsePatch = parsePatch; - exports.reversePatch = reversePatch; - exports.structuredPatch = structuredPatch; - -})); diff --git a/app/core/src/main/resources/static/js/compare/pdfWorker.js b/app/core/src/main/resources/static/js/compare/pdfWorker.js deleted file mode 100644 index 86fb66383..000000000 --- a/app/core/src/main/resources/static/js/compare/pdfWorker.js +++ /dev/null @@ -1,213 +0,0 @@ -importScripts('./diff.js'); - -let complexMessage = 'One or both of the provided documents are large files, accuracy of comparison may be reduced'; -let largeFilesMessage = 'One or Both of the provided documents are too large to process'; - -// Early: Listener for SET messages (before onmessage) -self.addEventListener('message', (event) => { - if (event.data.type === 'SET_COMPLEX_MESSAGE') { - complexMessage = event.data.message; - } else if (event.data.type === 'SET_TOO_LARGE_MESSAGE') { - largeFilesMessage = event.data.message; - } -}); - -self.onmessage = async function (e) { - const data = e.data; - if (data.type !== 'COMPARE') { - console.log('Worker ignored non-COMPARE message'); - return; - } - - const { text1, text2, color1, color2 } = data; - console.log('Received text for comparison:', { lengths: { text1: text1.length, text2: text2.length } }); // Safe Log - - const startTime = performance.now(); - - // Safe Trim - if (!text1 || !text2 || text1.trim() === "" || text2.trim() === "") { - self.postMessage({ status: 'error', message: 'One or both of the texts are empty.' }); - return; - } - - // Robust Word-Split (handles spaces/punctuation better) - const words1 = text1.trim().split(/\s+/).filter(w => w.length > 0); - const words2 = text2.trim().split(/\s+/).filter(w => w.length > 0); - - const MAX_WORD_COUNT = 150000; - const COMPLEX_WORD_COUNT = 50000; - const BATCH_SIZE = 5000; // Define a suitable batch size for processing - const OVERLAP_SIZE = 200; // Number of words to overlap - bigger increases accuracy but affects performance - - const isComplex = words1.length > COMPLEX_WORD_COUNT || words2.length > COMPLEX_WORD_COUNT; - const isTooLarge = words1.length > MAX_WORD_COUNT || words2.length > MAX_WORD_COUNT; - - if (isTooLarge) { - self.postMessage({ status: 'error', message: largeFilesMessage }); - return; - } - - if (isComplex) { - self.postMessage({ status: 'warning', message: complexMessage }); - } - - // Diff based on size - let differences; - if (isComplex) { - differences = await staggeredBatchDiff(words1, words2, color1 || '#ff0000', color2 || '#008000', BATCH_SIZE, OVERLAP_SIZE); - } else { - differences = diff(words1, words2, color1 || '#ff0000', color2 || '#008000'); - } - - console.log(`Diff took ${performance.now() - startTime} ms for ${words1.length + words2.length} words`); - self.postMessage({ status: 'success', differences }); -}; - -// Splits text into smaller batches to run through diff checking algorithms. overlaps the batches to help ensure -async function staggeredBatchDiff(words1, words2, color1, color2, batchSize, overlapSize) { - const differences = []; - const totalWords1 = words1.length; - const totalWords2 = words2.length; - - let previousEnd1 = 0; // Track where the last batch ended in words1 - let previousEnd2 = 0; // Track where the last batch ended in words2 - - // Track processed indices to dedupe overlaps - const processed1 = new Set(); - const processed2 = new Set(); - - while (previousEnd1 < totalWords1 || previousEnd2 < totalWords2) { - // Define the next chunk boundaries - const start1 = previousEnd1; - const end1 = Math.min(start1 + batchSize, totalWords1); - - const start2 = previousEnd2; - const end2 = Math.min(start2 + batchSize, totalWords2); - - // Adaptive: If many diffs, smaller batch (max 3x downscale) - const recentDiffs = differences.slice(-100).filter(([c]) => c !== 'black').length; - // If difference is too high decrease batch size for more granular check - const dynamicBatchSize = Math.max(batchSize / Math.min(8, 1 + recentDiffs / 50), batchSize / 8); - - const extendedEnd1 = Math.min(end1 + dynamicBatchSize, totalWords1); - const extendedEnd2 = Math.min(end2 + dynamicBatchSize, totalWords2); - - const batchWords1 = words1.slice(start1, extendedEnd1); - const batchWords2 = words2.slice(start2, extendedEnd2); - - // Include overlap from the previous chunk - const overlapStart1 = Math.max(0, previousEnd1 - overlapSize); - const overlapStart2 = Math.max(0, previousEnd2 - overlapSize); - const overlapWords1 = previousEnd1 > 0 ? words1.slice(overlapStart1, previousEnd1) : []; - const overlapWords2 = previousEnd2 > 0 ? words2.slice(overlapStart2, previousEnd2) : []; - - - // Combine overlaps and current batches for comparison - const combinedWords1 = [...overlapWords1, ...batchWords1]; - const combinedWords2 = [...overlapWords2, ...batchWords2]; - - // Perform the diff on the combined words - const batchDifferences = diff(combinedWords1, combinedWords2, color1, color2); - - const combinedIndices1 = []; - for (let i = overlapStart1; i < previousEnd1; i++) { - combinedIndices1.push(i); - } - for (let i = start1; i < extendedEnd1; i++) { - combinedIndices1.push(i); - } - - const combinedIndices2 = []; - for (let i = overlapStart2; i < previousEnd2; i++) { - combinedIndices2.push(i); - } - for (let i = start2; i < extendedEnd2; i++) { - combinedIndices2.push(i); - } - - let pointer1 = 0; - let pointer2 = 0; - - const filteredBatch = []; - batchDifferences.forEach(([color, word]) => { - if (color === color1) { - const globalIndex1 = combinedIndices1[pointer1]; - if (globalIndex1 === undefined || !processed1.has(globalIndex1)) { - filteredBatch.push([color, word]); - } - if (globalIndex1 !== undefined) { - processed1.add(globalIndex1); - } - pointer1++; - } else if (color === color2) { - const globalIndex2 = combinedIndices2[pointer2]; - if (globalIndex2 === undefined || !processed2.has(globalIndex2)) { - filteredBatch.push([color, word]); - } - if (globalIndex2 !== undefined) { - processed2.add(globalIndex2); - } - pointer2++; - } else { - const globalIndex1 = combinedIndices1[pointer1]; - const globalIndex2 = combinedIndices2[pointer2]; - const alreadyProcessed = (globalIndex1 !== undefined && processed1.has(globalIndex1)) && (globalIndex2 !== undefined && processed2.has(globalIndex2)); - if (!alreadyProcessed) { - filteredBatch.push([color, word]); - } - if (globalIndex1 !== undefined) { - processed1.add(globalIndex1); - } - if (globalIndex2 !== undefined) { - processed2.add(globalIndex2); - } - pointer1++; - pointer2++; - } - }); - - differences.push(...filteredBatch); - - // Mark as processed - for (let k = start1; k < end1; k++) processed1.add(k); - for (let k = start2; k < end2; k++) processed2.add(k); - - previousEnd1 = end1; - previousEnd2 = end2; - - // Yield for async (avoids blocking) - await new Promise(resolve => setTimeout(resolve, 0)); - } - - return differences; -} - -// Standard diff function for small text comparisons -function diff(words1, words2, color1, color2) { - console.log(`Diff: ${words1.length} vs ${words2.length} words`); - const oldStr = words1.join(' '); // As string for diff.js - const newStr = words2.join(' '); - // Static method: No 'new' needed, avoids constructor error - const changes = Diff.diffWords(oldStr, newStr, { ignoreWhitespace: true }); - - // Map changes to [color, word] format (change.value and added/removed) - const differences = []; - changes.forEach(change => { - const value = change.value; - const op = change.added ? 1 : change.removed ? -1 : 0; - - // Split value into words and process - const words = value.split(/\s+/).filter(w => w.length > 0); - words.forEach(word => { - if (op === 0) { // Equal - differences.push(['black', word]); - } else if (op === 1) { // Insert - differences.push([color2, word]); - } else if (op === -1) { // Delete - differences.push([color1, word]); - } - }); - }); - - return differences; -} diff --git a/app/core/src/main/resources/static/js/csrf.js b/app/core/src/main/resources/static/js/csrf.js deleted file mode 100644 index 20b8b00f1..000000000 --- a/app/core/src/main/resources/static/js/csrf.js +++ /dev/null @@ -1,37 +0,0 @@ -document.addEventListener('DOMContentLoaded', function() { - // Get CSRF token from cookie - const getCsrfToken = () => { - return document.cookie - .split('; ') - .find(row => row.startsWith('XSRF-TOKEN=')) - ?.split('=')[1]; - }; - - // Function to decode the URI-encoded cookie value - const decodeCsrfToken = (token) => { - if (token) { - return decodeURIComponent(token); - } - return null; - }; - - // Find all forms and add CSRF token - const forms = document.querySelectorAll('form'); - const csrfToken = decodeCsrfToken(getCsrfToken()); - - // Only proceed if we have a cookie-based token - if (csrfToken) { - forms.forEach(form => { - // Only now remove existing CSRF input fields since we have a new token - const existingCsrfInputs = form.querySelectorAll('input[name="_csrf"]'); - existingCsrfInputs.forEach(input => input.remove()); - - // Create and add new CSRF input field - const csrfInput = document.createElement('input'); - csrfInput.type = 'hidden'; - csrfInput.name = '_csrf'; - csrfInput.value = csrfToken; - form.appendChild(csrfInput); - }); - } -}); diff --git a/app/core/src/main/resources/static/js/darkmode.js b/app/core/src/main/resources/static/js/darkmode.js deleted file mode 100644 index abfe737c9..000000000 --- a/app/core/src/main/resources/static/js/darkmode.js +++ /dev/null @@ -1,105 +0,0 @@ -var toggleCount = 0; -var lastToggleTime = Date.now(); - -var elements = { - lightModeStyles: null, - darkModeStyles: null, - rainbowModeStyles: null, - darkModeIcon: null, - searchBar: null, - formControls: null, - navIcons: null, - navDropdownMenus: null, -}; - -function getElements() { - elements.lightModeStyles = document.getElementById("light-mode-styles"); - elements.darkModeStyles = document.getElementById("dark-mode-styles"); - elements.rainbowModeStyles = document.getElementById("rainbow-mode-styles"); - elements.darkModeIcon = document.getElementById("dark-mode-icon"); - elements.searchBar = document.getElementById("searchBar"); - elements.formControls = document.querySelectorAll(".form-control"); - elements.navDropdownMenus = document.querySelectorAll(".dropdown-menu"); -} -function setMode(mode) { - var event = new CustomEvent("modeChanged", { detail: mode }); - document.dispatchEvent(event); - - if (elements && elements.lightModeStyles) { - elements.lightModeStyles.disabled = mode !== "off"; - } - if (elements && elements.darkModeStyles) { - elements.darkModeStyles.disabled = mode !== "on"; - } - if (elements && elements.rainbowModeStyles) { - elements.rainbowModeStyles.disabled = mode !== "rainbow"; - } - - var jumbotron = document.getElementById("jumbotron"); - - if (mode === "on") { - if (elements && elements.darkModeIcon) { - elements.darkModeIcon.textContent = "dark_mode"; - } - var tables = document.querySelectorAll(".table"); - tables.forEach((table) => { - table.classList.add("table-dark"); - }); - } else if (mode === "off") { - if (elements && elements.darkModeIcon) { - elements.darkModeIcon.textContent = "light_mode"; - } - var tables = document.querySelectorAll(".table-dark"); - tables.forEach((table) => { - table.classList.remove("table-dark"); - }); - } else if (mode === "rainbow") { - if (elements && elements.darkModeIcon) { - elements.darkModeIcon.textContent = "looks"; - } - } -} - -function toggleDarkMode() { - var currentTime = Date.now(); - if (currentTime - lastToggleTime < 1000) { - toggleCount++; - } else { - toggleCount = 1; - } - lastToggleTime = currentTime; - - document.body.classList.add("transition-theme"); - - if (toggleCount >= 18) { - localStorage.setItem("dark-mode", "rainbow"); - setMode("rainbow"); - } else if (localStorage.getItem("dark-mode") == "on") { - localStorage.setItem("dark-mode", "off"); - setMode("off"); - } else { - localStorage.setItem("dark-mode", "on"); - setMode("on"); - } -} - -document.addEventListener("DOMContentLoaded", function () { - getElements(); - - var currentMode = localStorage.getItem("dark-mode"); - if (currentMode === "on" || currentMode === "off" || currentMode === "rainbow") { - setMode(currentMode); - } else if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { - setMode("on"); - } else { - setMode("off"); - } - - var darkModeToggle = document.getElementById("dark-mode-toggle"); - if (darkModeToggle !== null) { - darkModeToggle.addEventListener("click", function (event) { - event.preventDefault(); - toggleDarkMode(); - }); - } -}); diff --git a/app/core/src/main/resources/static/js/download.js b/app/core/src/main/resources/static/js/download.js deleted file mode 100644 index 8eed99f9a..000000000 --- a/app/core/src/main/resources/static/js/download.js +++ /dev/null @@ -1,27 +0,0 @@ -document.getElementById('download-pdf').addEventListener('click', async () => { - const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument(); - let decryptedFile = modifiedPdf; - let isEncrypted = false; - let requiresPassword = false; - await this.decryptFile - .checkFileEncrypted(decryptedFile) - .then((result) => { - isEncrypted = result.isEncrypted; - requiresPassword = result.requiresPassword; - }) - .catch((error) => { - console.error(error); - }); - if (decryptedFile.type === 'application/pdf' && isEncrypted) { - decryptedFile = await this.decryptFile.decryptFile(decryptedFile, requiresPassword); - if (!decryptedFile) { - throw new Error('File decryption failed.'); - } - } - const modifiedPdfBytes = await modifiedPdf.save(); - const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'}); - const link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.download = originalFileName + '_signed.pdf'; - link.click(); -}); diff --git a/app/core/src/main/resources/static/js/downloader.js b/app/core/src/main/resources/static/js/downloader.js deleted file mode 100644 index 2afdb2f75..000000000 --- a/app/core/src/main/resources/static/js/downloader.js +++ /dev/null @@ -1,791 +0,0 @@ -(function () { - if (window.isDownloadScriptInitialized) return; // Prevent re-execution - window.isDownloadScriptInitialized = true; - - const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', - }; - - // Global PDF processing count tracking for survey system - window.incrementPdfProcessingCount = function() { - let pdfProcessingCount = parseInt(localStorage.getItem('pdfProcessingCount') || '0'); - pdfProcessingCount++; - localStorage.setItem('pdfProcessingCount', pdfProcessingCount.toString()); - }; - - const { - pdfPasswordPrompt, - multipleInputsForSingleRequest, - disableMultipleFiles, - remoteCall, - sessionExpired, - refreshPage, - error, - } = window.stirlingPDF; - - // Format Problem Details JSON with consistent key order and pretty-printing - function formatProblemDetailsJson(input) { - try { - const obj = typeof input === 'string' ? JSON.parse(input) : input; - const preferredOrder = [ - 'errorCode', - 'title', - 'status', - 'type', - 'detail', - 'instance', - 'path', - 'timestamp', - 'hints', - 'actionRequired' - ]; - - const out = {}; - // Place preferred keys first if present - preferredOrder.forEach((k) => { - if (Object.prototype.hasOwnProperty.call(obj, k)) { - out[k] = obj[k]; - } - }); - // Append remaining keys preserving their original order - Object.keys(obj).forEach((k) => { - if (!Object.prototype.hasOwnProperty.call(out, k)) { - out[k] = obj[k]; - } - }); - return JSON.stringify(out, null, 2); - } catch (e) { - // Fallback: if it's already a string, return as-is; otherwise pretty-print best effort - if (typeof input === 'string') return input; - try { - return JSON.stringify(input, null, 2); - } catch { - return String(input); - } - } - } - - function showErrorBanner(message, stackTrace) { - const errorContainer = document.getElementById('errorContainer'); - if (!errorContainer) { - console.error('Error container not found'); - return; - } - errorContainer.style.display = 'block'; // Display the banner - const heading = errorContainer.querySelector('.alert-heading'); - const messageEl = errorContainer.querySelector('p'); - const traceEl = document.querySelector('#traceContent'); - - if (heading) heading.textContent = error; - if (messageEl) { - messageEl.style.whiteSpace = 'pre-wrap'; - messageEl.textContent = message; - } - - // Format stack trace: if it looks like JSON, pretty-print with consistent key order; otherwise clean it up - if (traceEl) { - if (stackTrace) { - // Check if stackTrace is already JSON formatted - if (stackTrace.trim().startsWith('{') || stackTrace.trim().startsWith('[')) { - traceEl.textContent = formatProblemDetailsJson(stackTrace); - } else { - // Filter out unhelpful stack traces (internal browser/library paths) - // Only show if it contains meaningful error info - const lines = stackTrace.split('\n'); - const meaningfulLines = lines.filter(line => - !line.includes('pdfjs-legacy') && - !line.includes('pdf.worker') && - !line.includes('pdf.mjs') && - line.trim().length > 0 - ); - traceEl.textContent = meaningfulLines.length > 0 ? meaningfulLines.join('\n') : 'No additional trace information available'; - } - } else { - traceEl.textContent = ''; - } - } - } - - function showSessionExpiredPrompt() { - const errorContainer = document.getElementById('errorContainer'); - errorContainer.style.display = 'block'; - errorContainer.querySelector('.alert-heading').textContent = sessionExpired; - errorContainer.querySelector('p').textContent = sessionExpired; - document.querySelector('#traceContent').textContent = ''; - - // Optional: Add a refresh button - const refreshButton = document.createElement('button'); - refreshButton.textContent = refreshPage; - refreshButton.className = 'btn btn-primary mt-3'; - refreshButton.onclick = () => location.reload(); - errorContainer.appendChild(refreshButton); - } - - let firstErrorOccurred = false; - - $(document).ready(function () { - $('form').submit(async function (event) { - event.preventDefault(); - firstErrorOccurred = false; - const url = this.action; - let files = $('#fileInput-input')[0].files; - const uploadLimit = window.stirlingPDF?.uploadLimit ?? 0; - if (uploadLimit > 0) { - const oversizedFiles = Array.from(files).filter(f => f.size > uploadLimit); - if (oversizedFiles.length > 0) { - const names = oversizedFiles.map(f => `"${f.name}"`).join(', '); - if (names.length === 1) { - alert(`${names} ${window.stirlingPDF.uploadLimitExceededSingular} ${window.stirlingPDF.uploadLimitReadable}.`); - } else { - alert(`${names} ${window.stirlingPDF.uploadLimitExceededPlural} ${window.stirlingPDF.uploadLimitReadable}.`); - } - files = Array.from(files).filter(f => f.size <= uploadLimit); - if (files.length === 0) return; - } - } - const formData = new FormData(this); - const submitButton = document.getElementById('submitBtn'); - const showGameBtn = document.getElementById('show-game-btn'); - const originalButtonText = submitButton.textContent; - var boredWaiting = localStorage.getItem('boredWaiting') || 'disabled'; - - if (showGameBtn) { - showGameBtn.style.display = 'none'; - } - - // Log fileOrder for debugging - const fileOrderValue = formData.get('fileOrder'); - if (fileOrderValue) { - console.log('FormData fileOrder:', fileOrderValue); - } - - // Remove empty file entries - for (let [key, value] of formData.entries()) { - if (value instanceof File && !value.name) { - formData.delete(key); - } - } - const override = $('#override').val() || ''; - console.log(override); - - // Set a timeout to show the game button if operation takes more than 5 seconds - const timeoutId = setTimeout(() => { - if (boredWaiting === 'enabled' && showGameBtn) { - showGameBtn.style.display = 'block'; - showGameBtn.parentNode.insertBefore(document.createElement('br'), showGameBtn.nextSibling); - } - }, 5000); - - try { - if (!url.includes('remove-password')) { - // Check if any PDF files are encrypted and handle decryption if necessary - const decryptedFiles = await checkAndDecryptFiles(url, files); - files = decryptedFiles; - } - - submitButton.textContent = 'Processing...'; - submitButton.disabled = true; - - if (remoteCall === true) { - if (override === 'multi' || (!multipleInputsForSingleRequest && files.length > 1 && override !== 'single')) { - await submitMultiPdfForm(url, files, this); - } else { - await handleSingleDownload(url, formData); - } - } - - //clearFileInput(); - clearTimeout(timeoutId); - if (showGameBtn) { - showGameBtn.style.display = 'none'; - showGameBtn.style.marginTop = ''; - } - submitButton.textContent = originalButtonText; - submitButton.disabled = false; - - // After process finishes, check for boredWaiting and gameDialog open status - const gameDialog = document.getElementById('game-container-wrapper'); - if (boredWaiting === 'enabled' && gameDialog && gameDialog.open) { - // Display a green banner at the bottom of the screen saying "Download complete" - let downloadCompleteText = 'Download Complete'; - if (window.downloadCompleteText) { - downloadCompleteText = window.downloadCompleteText; - } - $('body').append( - '
' + - downloadCompleteText + - '
' - ); - setTimeout(function () { - $('#download-complete-banner').fadeOut('slow', function () { - $(this).remove(); // Remove the banner after fading out - }); - }, 5000); // Banner will fade out after 5 seconds - } - } catch (error) { - clearTimeout(timeoutId); - if(showGameBtn){ - showGameBtn.style.display = 'none'; - } - submitButton.textContent = originalButtonText; - submitButton.disabled = false; - handleDownloadError(error); - console.error(error); - } - }); - }); - - async function getPDFPageCount(file) { - try { - const arrayBuffer = await file.arrayBuffer(); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - const pdf = await pdfjsLib - .getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: arrayBuffer, - }) - .promise; - return pdf.numPages; - } catch (error) { - console.error('Error getting PDF page count:', error); - return null; - } - } - - async function checkAndDecryptFiles(url, files) { - const decryptedFiles = []; - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - - // Extract the base URL - const baseUrl = new URL(url); - let removePasswordUrl = `${baseUrl.origin}`; - - // Check if there's a path before /api/ - const apiIndex = baseUrl.pathname.indexOf('/api/'); - if (apiIndex > 0) { - removePasswordUrl += baseUrl.pathname.substring(0, apiIndex); - } - - // Append the new endpoint - removePasswordUrl += '/api/v1/security/remove-password'; - - console.log(`Remove password URL: ${removePasswordUrl}`); - - for (const file of files) { - console.log(`Processing file: ${file.name}`); - if (file.type !== 'application/pdf') { - console.log(`Skipping non-PDF file: ${file.name}`); - decryptedFiles.push(file); - continue; - } - try { - const arrayBuffer = await file.arrayBuffer(); - const loadingTask = pdfjsLib.getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: arrayBuffer, - }); - - console.log(`Attempting to load PDF: ${file.name}`); - const pdf = await loadingTask.promise; - console.log(`File is not encrypted: ${file.name}`); - decryptedFiles.push(file); // If no error, file is not encrypted - } catch (error) { - if (error.name === 'PasswordException' && error.code === 1) { - console.log(`PDF requires password: ${file.name}`, error); - console.log(`Attempting to remove password from PDF: ${file.name} with password.`); - const password = prompt(`${window.decrypt.passwordPrompt}`); - - if (!password) { - console.error(`No password provided for encrypted PDF: ${file.name}`); - - // Create a Problem Detail object matching the server's E004 response using localized strings - const passwordDetailTemplate = - window.stirlingPDF?.pdfPasswordDetail || - `The PDF file "${file.name}" requires a password to proceed.`; - const hints = [ - window.stirlingPDF?.pdfPasswordHint1, - window.stirlingPDF?.pdfPasswordHint2, - window.stirlingPDF?.pdfPasswordHint3, - window.stirlingPDF?.pdfPasswordHint4, - window.stirlingPDF?.pdfPasswordHint5, - window.stirlingPDF?.pdfPasswordHint6 - ].filter(Boolean); - const noProblemDetail = { - errorCode: 'E004', - title: window.stirlingPDF?.pdfPasswordTitle || 'PDF Password Required', - detail: passwordDetailTemplate.includes('{0}') - ? passwordDetailTemplate.replace('{0}', file.name) - : passwordDetailTemplate, - hints, - actionRequired: - window.stirlingPDF?.pdfPasswordAction || - 'Provide the owner/permissions password, not just the document open password.' - }; - - const bannerMessage = formatUserFriendlyError(noProblemDetail); - const debugInfo = formatProblemDetailsJson(noProblemDetail); - showErrorBanner(bannerMessage, debugInfo); - - const err = new Error(noProblemDetail.detail); - err.alreadyHandled = true; - throw err; - } - - try { - // Prepare FormData for the decryption request - const formData = new FormData(); - formData.append('fileInput', file); - formData.append('password', password); - - // Use handleSingleDownload to send the request - const decryptionResult = await fetchWithCsrf(removePasswordUrl, {method: 'POST', body: formData}); - - // Check if we got an error response (RFC 7807 Problem Details) - if (!decryptionResult.ok) { - const contentType = decryptionResult.headers.get('content-type'); - if (contentType && (contentType.includes('application/json') || contentType.includes('application/problem+json'))) { - // Parse the RFC 7807 error response - const errorJson = await decryptionResult.json(); - const formattedError = formatUserFriendlyError(errorJson); - const debugInfo = formatProblemDetailsJson(errorJson); - const title = errorJson.title || 'Decryption Failed'; - const detail = errorJson.detail || 'Failed to decrypt PDF'; - const bannerMessage = formattedError || `${title}: ${detail}`; - showErrorBanner(bannerMessage, debugInfo); - const err = new Error(detail); - err.alreadyHandled = true; // Mark error as already handled - throw err; - } else { - throw new Error('Decryption failed: Invalid server response'); - } - } - - if (decryptionResult && decryptionResult.blob) { - const decryptedBlob = await decryptionResult.blob(); - const decryptedFile = new File([decryptedBlob], file.name, {type: 'application/pdf'}); - - decryptedFiles.push(decryptedFile); - console.log(`Successfully decrypted PDF: ${file.name}`); - } else { - throw new Error('Decryption failed: No valid response from server'); - } - } catch (decryptError) { - console.error(`Failed to decrypt PDF: ${file.name}`, decryptError); - // Error banner already shown above with formatted hints/actions - throw decryptError; - } - } else if (error.name === 'InvalidPDFException' || - (error.message && error.message.includes('Invalid PDF structure'))) { - // Handle corrupted PDF files - console.log(`Corrupted PDF detected: ${file.name}`, error); - if (window.stirlingPDF.currentPage !== 'repair') { - // Create a formatted error message using properties from language files - const errorMessage = window.stirlingPDF.pdfCorruptedMessage.replace('{0}', file.name); - const hints = [ - window.stirlingPDF.pdfCorruptedHint1, - window.stirlingPDF.pdfCorruptedHint2, - window.stirlingPDF.pdfCorruptedHint3 - ].filter((hint) => typeof hint === 'string' && hint.trim().length > 0); - const action = window.stirlingPDF.pdfCorruptedAction || window.stirlingPDF.tryRepairMessage; - - const problemDetails = { - title: window.stirlingPDF.pdfCorruptedTitle || window.stirlingPDF.error || 'Error', - detail: errorMessage - }; - - if (hints.length > 0) { - problemDetails.hints = hints; - } - - if (action) { - problemDetails.actionRequired = action; - } - - const bannerMessage = formatUserFriendlyError(problemDetails); - const debugInfo = formatProblemDetailsJson(problemDetails); - - showErrorBanner(bannerMessage, debugInfo); - // Mark error as already handled to prevent double display - error.alreadyHandled = true; - } else { - // On repair page, suppress banner; user already knows and is repairing - console.log('Suppressing corrupted PDF banner on repair page'); - } - throw error; - } else { - console.log(`Error loading PDF: ${file.name}`, error); - throw error; - } - } - } - return decryptedFiles; - } - - async function handleSingleDownload(url, formData, isMulti = false, isZip = false) { - const startTime = performance.now(); - const file = formData.get('fileInput'); - let success = false; - let errorMessage = null; - - try { - const response = await window.fetchWithCsrf(url, {method: 'POST', body: formData}); - const contentType = response.headers.get('content-type'); - - if (!response.ok) { - errorMessage = response.status; - // Check for JSON error responses first (including RFC 7807 Problem Details) - if (contentType && (contentType.includes('application/json') || contentType.includes('application/problem+json'))) { - console.error('Throwing error banner, response was not okay'); - await handleJsonResponse(response); - // Return early - error banner already shown by handleJsonResponse - // Don't throw to avoid double error display - return null; - } - // Only show session expired for 401 without JSON body (actual auth failure) - if (response.status === 401) { - showSessionExpiredPrompt(); - return; - } - // For non-JSON errors, try to extract error message from response body - try { - const errorText = await response.text(); - if (errorText && errorText.trim().length > 0) { - showErrorBanner(`HTTP ${response.status}`, errorText); - // Return early - error already shown - return null; - } - } catch (textError) { - // If we can't read the response body, show generic error - const errorMsg = `HTTP ${response.status} - ${response.statusText || 'Request failed'}`; - showErrorBanner('Error', errorMsg); - return null; - } - } - - const contentDisposition = response.headers.get('Content-Disposition'); - let filename = getFilenameFromContentDisposition(contentDisposition); - - const blob = await response.blob(); - success = true; - - if (contentType.includes('application/pdf') || contentType.includes('image/')) { - //clearFileInput(); - return handleResponse(blob, filename, !isMulti, isZip); - } else { - //clearFileInput(); - return handleResponse(blob, filename, false, isZip); - } - } catch (error) { - success = false; - errorMessage = error.message; - console.error('Error in handleSingleDownload:', error); - throw error; - } finally { - const processingTime = performance.now() - startTime; - - // Capture analytics - const pageCount = file && file.type === 'application/pdf' ? await getPDFPageCount(file) : null; - if (analyticsEnabled) { - posthog.capture('file_processing', { - success: success, - file_type: file ? file.type || 'unknown' : 'unknown', - file_size: file ? file.size : 0, - processing_time: processingTime, - error_message: errorMessage, - pdf_pages: pageCount, - }); - } - - // Increment PDF processing count for survey tracking - if (success && typeof window.incrementPdfProcessingCount === 'function') { - window.incrementPdfProcessingCount(); - } - } - } - - function getFilenameFromContentDisposition(contentDisposition) { - let filename; - - if (contentDisposition && contentDisposition.indexOf('attachment') !== -1) { - filename = decodeURIComponent(contentDisposition.split('filename=')[1].replace(/"/g, '')).trim(); - } else { - // If the Content-Disposition header is not present or does not contain the filename, use a default filename - filename = 'download'; - } - - return filename; - } - - /** - * Format error details in a user-friendly way - * Extracts key information and presents hints/actions prominently - */ - function formatUserFriendlyError(json) { - if (!json || typeof json !== 'object') { - return typeof json === 'string' ? json : ''; - } - - const lines = []; - const title = json.title || json.error || ''; - const detail = json.detail || json.message || ''; - - const primaryLine = title && detail - ? `${title}: ${detail}` - : title || detail; - - if (primaryLine) { - lines.push(primaryLine); - } - - if (json.errorCode) { - lines.push(''); - lines.push(`Error Code: ${json.errorCode}`); - } - - const detailAlreadyIncluded = detail && primaryLine && primaryLine.includes(detail); - - if (detail && !detailAlreadyIncluded) { - lines.push(''); - lines.push(detail); - } - - if (json.hints && Array.isArray(json.hints) && json.hints.length > 0) { - lines.push(''); - lines.push('How to fix:'); - json.hints.forEach((hint, index) => { - lines.push(` ${index + 1}. ${hint}`); - }); - } - - if (json.actionRequired) { - lines.push(''); - lines.push(json.actionRequired); - } - - if (json.supportId) { - lines.push(''); - lines.push(`Support ID: ${json.supportId}`); - } - - return lines - .filter((line, index, arr) => { - if (line !== '') return true; - if (index === 0 || index === arr.length - 1) return false; - return arr[index - 1] !== ''; - }) - .join('\n'); - } - - async function handleJsonResponse(response) { - const json = await response.json(); - - // Format the full JSON response for display in stack trace with errorCode first - const formattedJson = formatProblemDetailsJson(json); - - // Check for PDF password errors using RFC 7807 fields - const isPdfPasswordError = - json.type === '/errors/pdf-password' || - json.errorCode === 'E004' || - (json.detail && ( - json.detail.toLowerCase().includes('pdf document is passworded') || - json.detail.toLowerCase().includes('password is incorrect') || - json.detail.toLowerCase().includes('password was not provided') || - json.detail.toLowerCase().includes('pdf contains an encryption dictionary') - )); - - const fallbackTitle = json.title || json.error || 'Error'; - const fallbackDetail = json.detail || json.message || ''; - const fallbackMessage = fallbackDetail ? `${fallbackTitle}: ${fallbackDetail}` : fallbackTitle; - const bannerMessage = formatUserFriendlyError(json) || fallbackMessage; - - if (isPdfPasswordError) { - showErrorBanner(bannerMessage, formattedJson); - - // Show alert only once for user attention - if (!firstErrorOccurred) { - firstErrorOccurred = true; - const detail = json.detail || 'The PDF document requires a password to open.'; - alert(pdfPasswordPrompt + '\n\n' + detail); - } - } else { - // Show user-friendly error, fallback to full JSON for debugging - showErrorBanner(bannerMessage, formattedJson); - } - } - - async function handleResponse(blob, filename, considerViewOptions = false, isZip = false) { - if (!blob) return; - const downloadOption = localStorage.getItem('downloadOption'); - if (considerViewOptions) { - if (downloadOption === 'sameWindow') { - const url = URL.createObjectURL(blob); - window.location.href = url; - return; - } else if (downloadOption === 'newWindow') { - const url = URL.createObjectURL(blob); - window.open(url, '_blank'); - return; - } - } - if (!isZip) { - downloadFile(blob, filename); - } - return {filename, blob}; - } - - function handleDownloadError(error) { - // Skip if error was already handled and displayed - if (error.alreadyHandled) { - return; - } - const errorMessage = error.message; - showErrorBanner(errorMessage); - } - - let urls = []; // An array to hold all the URLs - - function downloadFile(blob, filename) { - if (!(blob instanceof Blob)) { - console.error('Invalid blob passed to downloadFile function'); - return; - } - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - a.click(); - urls.push(url); // Store the URL so it doesn't get garbage collected too soon - - return {filename, blob}; - } - - async function submitMultiPdfForm(url, files, form) { - const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4; - const zipFiles = files.length > zipThreshold; - let jszip = null; - // Add Space below Progress Bar before Showing - $('.progressBarContainer').after($('
')); - $('.progressBarContainer').show(); - // Initialize the progress bar - - let progressBar = $('.progressBar'); - progressBar.css('width', '0%'); - progressBar.attr('aria-valuenow', 0); - progressBar.attr('aria-valuemax', files.length); - - if (zipFiles) { - jszip = new JSZip(); - } - - // Get the form with the method attribute set to POST - let postForm = document.querySelector('form[method="POST"]'); - - // Get existing form data - let formData; - if (form) { - formData = new FormData(form); - } else if (postForm) { - formData = new FormData($(postForm)[0]); // Convert the form to a jQuery object and get the raw DOM element - } else { - console.log('No form with POST method found.'); - } - //Remove file to reuse parameters for other runs - formData.delete('fileInput'); - // Remove empty file entries - for (let [key, value] of formData.entries()) { - if (value instanceof File && !value.name) { - formData.delete(key); - } - } - const CONCURRENCY_LIMIT = 8; - const chunks = []; - for (let i = 0; i < Array.from(files).length; i += CONCURRENCY_LIMIT) { - chunks.push(Array.from(files).slice(i, i + CONCURRENCY_LIMIT)); - } - - for (const chunk of chunks) { - const promises = chunk.map(async (file) => { - let fileFormData = new FormData(); - fileFormData.append('fileInput', file); - for (let [key, value] of fileFormData.entries()) { - console.log(key, value); - } // Add other form data - for (let pair of formData.entries()) { - fileFormData.append(pair[0], pair[1]); - console.log(pair[0] + ', ' + pair[1]); - } - - try { - const downloadDetails = await handleSingleDownload(url, fileFormData, true, zipFiles); - console.log(downloadDetails); - // If downloadDetails is null, error was already shown, skip processing - if (downloadDetails) { - if (zipFiles) { - jszip.file(downloadDetails.filename, downloadDetails.blob); - } else { - //downloadFile(downloadDetails.blob, downloadDetails.filename); - } - updateProgressBar(progressBar, Array.from(files).length); - } - } catch (error) { - handleDownloadError(error); - console.error(error); - } - }); - await Promise.all(promises); - } - - if (zipFiles) { - try { - const content = await jszip.generateAsync({type: 'blob'}); - downloadFile(content, 'files.zip'); - } catch (error) { - console.error('Error generating ZIP file: ' + error); - } - } - progressBar.css('width', '100%'); - progressBar.attr('aria-valuenow', Array.from(files).length); - setTimeout(() => { - progressBar.closest('.progressBarContainer').hide(); - progressBar.css('width', '0%'); - progressBar.attr('aria-valuenow', 0); - }, 1000); - } - - function updateProgressBar(progressBar, files) { - let progress = (progressBar.attr('aria-valuenow') / files.length) * 100 + 100 / files.length; - progressBar.css('width', progress + '%'); - progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1); - } - window.addEventListener('unload', () => { - for (const url of urls) { - URL.revokeObjectURL(url); - } - }); - - // Clear file input after job - function clearFileInput() { - let pathname = document.location.pathname; - if (pathname != '/merge-pdfs') { - let formElement = document.querySelector('#fileInput-input'); - formElement.value = ''; - let editSectionElement = document.querySelector('#editSection'); - if (editSectionElement) { - editSectionElement.style.display = 'none'; - } - let cropPdfCanvas = document.querySelector('#cropPdfCanvas'); - let overlayCanvas = document.querySelector('#overlayCanvas'); - if (cropPdfCanvas && overlayCanvas) { - cropPdfCanvas.width = 0; - cropPdfCanvas.height = 0; - - overlayCanvas.width = 0; - overlayCanvas.height = 0; - } - } else { - console.log("Disabled for 'Merge'"); - } - } -})(); diff --git a/app/core/src/main/resources/static/js/draggable-utils.js b/app/core/src/main/resources/static/js/draggable-utils.js deleted file mode 100644 index 2e9769eed..000000000 --- a/app/core/src/main/resources/static/js/draggable-utils.js +++ /dev/null @@ -1,631 +0,0 @@ -const DraggableUtils = { - boxDragContainer: document.getElementById('box-drag-container'), - pdfCanvas: document.getElementById('pdf-canvas'), - nextId: 0, - pdfDoc: null, - pageIndex: 0, - elementAllPages: [], - documentsMap: new Map(), - lastInteracted: null, - padding: 15, - maintainRatioEnabled: true, - init() { - interact('.draggable-canvas') - .draggable({ - listeners: { - start(event) { - const target = event.target; - x = parseFloat(target.getAttribute('data-bs-x')); - y = parseFloat(target.getAttribute('data-bs-y')); - }, - move: (event) => { - const target = event.target; - - // Retrieve position attributes - let x = parseFloat(target.getAttribute('data-bs-x')) || 0; - let y = parseFloat(target.getAttribute('data-bs-y')) || 0; - const angle = parseFloat(target.getAttribute('data-angle')) || 0; - - // Update position based on drag movement - x += event.dx; - y += event.dy; - - // Apply translation to the parent container (bounding box) - target.style.transform = `translate(${x}px, ${y}px)`; - - // Preserve rotation on the inner canvas - const canvas = target.querySelector('.display-canvas'); - - const canvasWidth = parseFloat(canvas.style.width); - const canvasHeight = parseFloat(canvas.style.height); - - const cosAngle = Math.abs(Math.cos(angle)); - const sinAngle = Math.abs(Math.sin(angle)); - - const rotatedWidth = canvasWidth * cosAngle + canvasHeight * sinAngle; - const rotatedHeight = canvasWidth * sinAngle + canvasHeight * cosAngle; - - const offsetX = (rotatedWidth - canvasWidth) / 2; - const offsetY = (rotatedHeight - canvasHeight) / 2; - - canvas.style.transform = `translate(${offsetX}px, ${offsetY}px) rotate(${angle}rad)`; - - // Update attributes for persistence - target.setAttribute('data-bs-x', x); - target.setAttribute('data-bs-y', y); - - // Set the last interacted element - this.lastInteracted = target; - }, - }, - }) - .resizable({ - edges: { left: true, right: true, bottom: true, top: true }, - listeners: { - start: (event) => { - const target = event.target; - x = parseFloat(target.getAttribute('data-bs-x')) || 0; - y = parseFloat(target.getAttribute('data-bs-y')) || 0; - }, - move: (event) => { - const target = event.target; - - const MAX_CHANGE = 60; - - let width = event.rect.width - 2 * this.padding; - let height = event.rect.height - 2 * this.padding; - - const canvas = target.querySelector('.display-canvas'); - if (canvas) { - const originalWidth = parseFloat(canvas.style.width) || canvas.width; - const originalHeight = parseFloat(canvas.style.height) || canvas.height; - const angle = parseFloat(target.getAttribute('data-angle')) || 0; - - const aspectRatio = originalWidth / originalHeight; - - if (!event.ctrlKey && this.maintainRatioEnabled) { - if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) { - height = width / aspectRatio; - } else { - width = height * aspectRatio; - } - } - - const widthChange = width - originalWidth; - const heightChange = height - originalHeight; - - if (Math.abs(widthChange) > MAX_CHANGE || Math.abs(heightChange) > MAX_CHANGE) { - const scale = MAX_CHANGE / Math.max(Math.abs(widthChange), Math.abs(heightChange)); - width = originalWidth + widthChange * scale; - height = originalHeight + heightChange * scale; - } - - const cosAngle = Math.abs(Math.cos(angle)); - const sinAngle = Math.abs(Math.sin(angle)); - const boundingWidth = width * cosAngle + height * sinAngle; - const boundingHeight = width * sinAngle + height * cosAngle; - - if (event.edges.left) { - const dx = event.deltaRect.left; - x += dx; - } - if (event.edges.top) { - const dy = event.deltaRect.top; - y += dy; - } - - target.style.transform = `translate(${x}px, ${y}px)`; - target.style.width = `${boundingWidth + 2 * this.padding}px`; - target.style.height = `${boundingHeight + 2 * this.padding}px`; - - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - canvas.style.transform = `translate(${(boundingWidth - width) / 2}px, ${(boundingHeight - height) / 2 - }px) rotate(${angle}rad)`; - - target.setAttribute('data-bs-x', x); - target.setAttribute('data-bs-y', y); - - this.lastInteracted = target; - } - }, - }, - modifiers: [ - interact.modifiers.restrictSize({ - min: { width: 50, height: 50 }, - }), - ], - inertia: true, - }); - - //Arrow key Support for Add-Image and Sign pages - if (window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) { - window.addEventListener('keydown', (event) => { - //Check for last interacted element - if (!this.lastInteracted) { - return; - } - // Get the currently selected element - const target = this.lastInteracted; - - // Step size relatively to the elements size - const stepX = target.offsetWidth * 0.05; - const stepY = target.offsetHeight * 0.05; - - // Get the current x and y coordinates - let x = parseFloat(target.getAttribute('data-bs-x')) || 0; - let y = parseFloat(target.getAttribute('data-bs-y')) || 0; - - // Check which key was pressed and update the coordinates accordingly - switch (event.key) { - case 'ArrowUp': - y -= stepY; - event.preventDefault(); // Prevent the default action - break; - case 'ArrowDown': - y += stepY; - event.preventDefault(); - break; - case 'ArrowLeft': - x -= stepX; - event.preventDefault(); - break; - case 'ArrowRight': - x += stepX; - event.preventDefault(); - break; - default: - return; // Listen only to arrow keys - } - - // Update position - const angle = parseFloat(target.getAttribute('data-angle')) || 0; - target.style.transform = `translate(${x}px, ${y}px) rotate(${angle}rad)`; - target.setAttribute('data-bs-x', x); - target.setAttribute('data-bs-y', y); - - DraggableUtils.onInteraction(target); - }); - } - }, - onInteraction(target) { - this.lastInteracted = target; - // this.boxDragContainer.appendChild(target); - // target.appendChild(target.querySelector(".display-canvas")); - }, - createDraggableCanvasFromUrl(dataUrl) { - return new Promise((resolve) => { - const canvasContainer = document.createElement('div'); - const createdCanvas = document.createElement('canvas'); // Inner canvas - const padding = this.padding; - - canvasContainer.id = `draggable-canvas-${this.nextId++}`; - canvasContainer.classList.add('draggable-canvas'); - createdCanvas.classList.add('display-canvas'); - - canvasContainer.style.position = 'absolute'; - canvasContainer.style.padding = `${padding}px`; - canvasContainer.style.overflow = 'hidden'; - - let x = 0, - y = 30, - angle = 0; - canvasContainer.style.transform = `translate(${x}px, ${y}px)`; - canvasContainer.setAttribute('data-bs-x', x); - canvasContainer.setAttribute('data-bs-y', y); - canvasContainer.setAttribute('data-angle', angle); - - canvasContainer.addEventListener('click', () => { - this.lastInteracted = canvasContainer; - this.showRotationControls(canvasContainer); - }); - canvasContainer.appendChild(createdCanvas); - this.boxDragContainer.appendChild(canvasContainer); - - const myImage = new Image(); - myImage.src = dataUrl; - myImage.onload = () => { - const context = createdCanvas.getContext('2d'); - - createdCanvas.width = myImage.width; - createdCanvas.height = myImage.height; - - const imgAspect = myImage.width / myImage.height; - const containerWidth = this.boxDragContainer.offsetWidth; - const containerHeight = this.boxDragContainer.offsetHeight; - - let scaleMultiplier = Math.min(containerWidth / myImage.width, containerHeight / myImage.height); - const scaleFactor = 0.5; - - const newWidth = myImage.width * scaleMultiplier * scaleFactor; - const newHeight = myImage.height * scaleMultiplier * scaleFactor; - - // Calculate initial bounding box size - const cosAngle = Math.abs(Math.cos(angle)); - const sinAngle = Math.abs(Math.sin(angle)); - const boundingWidth = newWidth * cosAngle + newHeight * sinAngle; - const boundingHeight = newWidth * sinAngle + newHeight * cosAngle; - - createdCanvas.style.width = `${newWidth}px`; - createdCanvas.style.height = `${newHeight}px`; - - canvasContainer.style.width = `${boundingWidth + 2 * padding}px`; - canvasContainer.style.height = `${boundingHeight + 2 * padding}px`; - - context.imageSmoothingEnabled = true; - context.imageSmoothingQuality = 'high'; - context.drawImage(myImage, 0, 0, myImage.width, myImage.height); - this.showRotationControls(canvasContainer); - this.lastInteracted = canvasContainer; - - resolve(canvasContainer); - }; - - myImage.onerror = () => { - console.error('Failed to load the image.'); - resolve(null); - }; - }); - }, - toggleMaintainRatio() { - this.maintainRatioEnabled = !this.maintainRatioEnabled; - const button = document.getElementById('ratioToggleBtn'); - if (this.maintainRatioEnabled) { - button.classList.remove('btn-danger'); - button.classList.add('btn-outline-secondary'); - } else { - button.classList.remove('btn-outline-secondary'); - button.classList.add('btn-danger'); - } - }, - - deleteAllDraggableCanvases() { - this.boxDragContainer.querySelectorAll('.draggable-canvas').forEach((el) => el.remove()); - }, - async addAllPagesDraggableCanvas(element) { - if (element) { - let currentPage = this.pageIndex; - if (!this.elementAllPages.includes(element)) { - this.elementAllPages.push(element); - element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)'; - let newElement = { - element: element, - offsetWidth: element.width, - offsetHeight: element.height, - }; - - let pagesMap = this.documentsMap.get(this.pdfDoc); - - if (!pagesMap) { - pagesMap = {}; - this.documentsMap.set(this.pdfDoc, pagesMap); - } - let page = this.pageIndex; - - for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) { - if (pagesMap[`${pageIndex}-offsetWidth`]) { - if (!pagesMap[pageIndex].includes(newElement)) { - pagesMap[pageIndex].push(newElement); - } - } else { - pagesMap[pageIndex] = []; - pagesMap[pageIndex].push(newElement); - pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`]; - pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`]; - } - await this.goToPage(pageIndex); - } - } else { - const index = this.elementAllPages.indexOf(element); - if (index !== -1) { - this.elementAllPages.splice(index, 1); - } - element.style.filter = ''; - let pagesMap = this.documentsMap.get(this.pdfDoc); - - if (!pagesMap) { - pagesMap = {}; - this.documentsMap.set(this.pdfDoc, pagesMap); - } - for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) { - if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) { - const pageElements = pagesMap[pageIndex]; - pageElements.forEach((elementPage) => { - const elementIndex = pageElements.findIndex((elementPage) => elementPage['element'].id === element.id); - if (elementIndex !== -1) { - pageElements.splice(elementIndex, 1); - } - }); - } - await this.goToPage(pageIndex); - } - } - await this.goToPage(currentPage); - } - }, - deleteDraggableCanvas(element) { - if (element) { - //Check if deleted element is the last interacted - if (this.lastInteracted === element) { - // If it is, set lastInteracted to null - this.lastInteracted = null; - } - element.remove(); - } - }, - getLastInteracted() { - return this.lastInteracted; - }, - showRotationControls(element) { - const rotationControls = document.getElementById('rotation-controls'); - const rotationInput = document.getElementById('rotation-input'); - rotationControls.style.display = 'flex'; - rotationInput.value = Math.round((parseFloat(element.getAttribute('data-angle')) * 180) / Math.PI); - rotationInput.addEventListener('input', this.handleRotationInputChange); - }, - hideRotationControls() { - const rotationControls = document.getElementById('rotation-controls'); - const rotationInput = document.getElementById('rotation-input'); - rotationControls.style.display = 'none'; - rotationInput.addEventListener('input', this.handleRotationInputChange); - }, - applyRotationToElement(element, degrees) { - const radians = degrees * (Math.PI / 180); // Convert degrees to radians - - // Get current position - const x = parseFloat(element.getAttribute('data-bs-x')) || 0; - const y = parseFloat(element.getAttribute('data-bs-y')) || 0; - - // Get the inner canvas (image) - const canvas = element.querySelector('.display-canvas'); - if (canvas) { - const originalWidth = parseFloat(canvas.style.width); - const originalHeight = parseFloat(canvas.style.height); - const padding = this.padding; // Access the padding value - - // Calculate rotated bounding box dimensions - const cosAngle = Math.abs(Math.cos(radians)); - const sinAngle = Math.abs(Math.sin(radians)); - const boundingWidth = originalWidth * cosAngle + originalHeight * sinAngle + 2 * padding; - const boundingHeight = originalWidth * sinAngle + originalHeight * cosAngle + 2 * padding; - - // Update parent container to fit the rotated bounding box - element.style.width = `${boundingWidth}px`; - element.style.height = `${boundingHeight}px`; - - // Center the canvas within the bounding box, accounting for padding - const offsetX = (boundingWidth - originalWidth) / 2 - padding; - const offsetY = (boundingHeight - originalHeight) / 2 - padding; - - canvas.style.transform = `translate(${offsetX}px, ${offsetY}px) rotate(${radians}rad)`; - } - - // Keep the bounding box positioned properly - element.style.transform = `translate(${x}px, ${y}px)`; - element.setAttribute('data-angle', radians); - }, - handleRotationInputChange() { - const rotationInput = document.getElementById('rotation-input'); - const degrees = parseFloat(rotationInput.value) || 0; - DraggableUtils.applyRotationToElement(DraggableUtils.lastInteracted, degrees); - }, - storePageContents() { - var pagesMap = this.documentsMap.get(this.pdfDoc); - if (!pagesMap) { - pagesMap = {}; - } - - const elements = [...this.boxDragContainer.querySelectorAll('.draggable-canvas')]; - const draggablesData = elements.map((el) => { - return { - element: el, - offsetWidth: el.offsetWidth, - offsetHeight: el.offsetHeight, - }; - }); - elements.forEach((el) => this.boxDragContainer.removeChild(el)); - - pagesMap[this.pageIndex] = draggablesData; - pagesMap[this.pageIndex + '-offsetWidth'] = this.pdfCanvas.offsetWidth; - pagesMap[this.pageIndex + '-offsetHeight'] = this.pdfCanvas.offsetHeight; - - this.documentsMap.set(this.pdfDoc, pagesMap); - }, - loadPageContents() { - var pagesMap = this.documentsMap.get(this.pdfDoc); - this.deleteAllDraggableCanvases(); - if (!pagesMap) { - return; - } - - const draggablesData = pagesMap[this.pageIndex]; - if (draggablesData && Array.isArray(draggablesData)) { - draggablesData.forEach((draggableData) => this.boxDragContainer.appendChild(draggableData.element)); - } - - this.documentsMap.set(this.pdfDoc, pagesMap); - }, - - async renderPage(pdfDocument, pageIdx) { - this.pdfDoc = pdfDocument ? pdfDocument : this.pdfDoc; - this.pageIndex = pageIdx; - - // persist - const page = await this.pdfDoc.getPage(this.pageIndex + 1); - - // set the canvas size to the size of the page - if (page.rotate == 90 || page.rotate == 270) { - this.pdfCanvas.width = page.view[3]; - this.pdfCanvas.height = page.view[2]; - } else { - this.pdfCanvas.width = page.view[2]; - this.pdfCanvas.height = page.view[3]; - } - - // render the page onto the canvas - var renderContext = { - canvasContext: this.pdfCanvas.getContext('2d'), - viewport: page.getViewport({ scale: 1 }), - }; - await page.render(renderContext).promise; - - //return pdfCanvas.toDataURL(); - }, - - async goToPage(pageIndex) { - this.storePageContents(); - await this.renderPage(this.pdfDoc, pageIndex); - this.loadPageContents(); - }, - - async incrementPage() { - if (this.pageIndex < this.pdfDoc.numPages - 1) { - this.storePageContents(); - await this.renderPage(this.pdfDoc, this.pageIndex + 1); - this.loadPageContents(); - } - }, - async decrementPage() { - if (this.pageIndex > 0) { - this.storePageContents(); - await this.renderPage(this.pdfDoc, this.pageIndex - 1); - this.loadPageContents(); - } - }, - async getOverlayedPdfDocument() { - const pdfBytes = await this.pdfDoc.getData(); - const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { - ignoreEncryption: true, - }); - this.storePageContents(); - - const pagesMap = this.documentsMap.get(this.pdfDoc); - - for (let pageIdx in pagesMap) { - if (pageIdx.includes('offset')) { - continue; - } - - const page = pdfDocModified.getPage(parseInt(pageIdx)); - let draggablesData = pagesMap[pageIdx]; - - const offsetWidth = pagesMap[pageIdx + '-offsetWidth']; - const offsetHeight = pagesMap[pageIdx + '-offsetHeight']; - - for (const draggableData of draggablesData) { - // Embed the draggable canvas - const draggableElement = draggableData.element.querySelector('.display-canvas'); - const response = await fetch(draggableElement.toDataURL()); - const draggableImgBytes = await response.arrayBuffer(); - const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes); - - // Extract transformation data - const transform = draggableData.element.style.transform || ''; - const translateRegex = /translate\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/; - - const translateMatch = transform.match(translateRegex); - - const translateX = translateMatch ? parseFloat(translateMatch[1]) : 0; - const translateY = translateMatch ? parseFloat(translateMatch[2]) : 0; - - const childTransform = draggableElement.style.transform || ''; - const childTranslateMatch = childTransform.match(translateRegex); - - const childOffsetX = childTranslateMatch ? parseFloat(childTranslateMatch[1]) : 0; - const childOffsetY = childTranslateMatch ? parseFloat(childTranslateMatch[2]) : 0; - - const rotateAngle = parseFloat(draggableData.element.getAttribute('data-angle')) || 0; - - const draggablePositionPixels = { - x: translateX + childOffsetX + this.padding + 2, - y: translateY + childOffsetY + this.padding + 2, - width: parseFloat(draggableElement.style.width), - height: parseFloat(draggableElement.style.height), - angle: rotateAngle, // Store rotation - }; - - const pageRotation = page.getRotation(); - - // Normalize page rotation angle - let normalizedAngle = pageRotation.angle % 360; - if (normalizedAngle < 0) { - normalizedAngle += 360; - } - - // Determine the viewed page dimensions based on the normalized rotation angle - let viewedPageWidth = (normalizedAngle === 90 || normalizedAngle === 270) ? page.getHeight() : page.getWidth(); - let viewedPageHeight = (normalizedAngle === 90 || normalizedAngle === 270) ? page.getWidth() : page.getHeight(); - - const draggablePositionRelative = { - x: draggablePositionPixels.x / offsetWidth, - y: draggablePositionPixels.y / offsetHeight, - width: draggablePositionPixels.width / offsetWidth, - height: draggablePositionPixels.height / offsetHeight, - angle: draggablePositionPixels.angle, - }; - - const draggablePositionPdf = { - x: draggablePositionRelative.x * viewedPageWidth, - y: draggablePositionRelative.y * viewedPageHeight, - width: draggablePositionRelative.width * viewedPageWidth, - height: draggablePositionRelative.height * viewedPageHeight, - }; - - // Calculate position based on normalized page rotation - let x = draggablePositionPdf.x; - let y = viewedPageHeight - draggablePositionPdf.y - draggablePositionPdf.height; - - if (normalizedAngle === 90) { - x = draggablePositionPdf.y; - y = draggablePositionPdf.x; - } else if (normalizedAngle === 180) { - x = viewedPageWidth - draggablePositionPdf.x - draggablePositionPdf.width; - y = draggablePositionPdf.y; - } else if (normalizedAngle === 270) { - x = viewedPageHeight - draggablePositionPdf.y - draggablePositionPdf.height; - y = viewedPageWidth - draggablePositionPdf.x - draggablePositionPdf.width; - } - - // Convert rotation angle to radians - let pageRotationInRadians = PDFLib.degreesToRadians(normalizedAngle); - const rotationInRadians = pageRotationInRadians - draggablePositionPixels.angle; - - // Calculate the center of the image - const imageCenterX = x + draggablePositionPdf.width / 2; - const imageCenterY = y + draggablePositionPdf.height / 2; - - // Apply transformations to rotate the image about its center - page.pushOperators( - PDFLib.pushGraphicsState(), - PDFLib.concatTransformationMatrix(1, 0, 0, 1, imageCenterX, imageCenterY), // Translate to center - PDFLib.concatTransformationMatrix( - Math.cos(rotationInRadians), - Math.sin(rotationInRadians), - -Math.sin(rotationInRadians), - Math.cos(rotationInRadians), - 0, - 0 - ), // Rotate - PDFLib.concatTransformationMatrix(1, 0, 0, 1, -imageCenterX, -imageCenterY) // Translate back - ); - - page.drawImage(pdfImageObject, { - x: x, - y: y, - width: draggablePositionPdf.width, - height: draggablePositionPdf.height, - }); - - // Restore the graphics state - page.pushOperators(PDFLib.popGraphicsState()); - } - } - - this.loadPageContents(); - return pdfDocModified; - }, -}; - -document.addEventListener('DOMContentLoaded', () => { - DraggableUtils.init(); -}); diff --git a/app/core/src/main/resources/static/js/errorBanner.js b/app/core/src/main/resources/static/js/errorBanner.js deleted file mode 100644 index 528fe8f27..000000000 --- a/app/core/src/main/resources/static/js/errorBanner.js +++ /dev/null @@ -1,50 +0,0 @@ -var traceVisible = false; - -function toggletrace() { - var traceDiv = document.getElementById("trace"); - if (!traceVisible) { - traceDiv.style.maxHeight = "100vh"; - traceVisible = true; - } else { - traceDiv.style.maxHeight = "0px"; - traceVisible = false; - } - adjustContainerHeight(); -} - -function copytrace() { - var flip = false; - if (!traceVisible) { - toggletrace(); - flip = true; - } - var traceContent = document.getElementById("traceContent"); - var range = document.createRange(); - range.selectNode(traceContent); - window.getSelection().removeAllRanges(); - window.getSelection().addRange(range); - document.execCommand("copy"); - window.getSelection().removeAllRanges(); - if (flip) { - toggletrace(); - } -} - -function dismissError() { - var errorContainer = document.getElementById("errorContainer"); - errorContainer.style.display = "none"; - errorContainer.style.height = "0"; -} - -function adjustContainerHeight() { - var errorContainer = document.getElementById("errorContainer"); - var traceDiv = document.getElementById("trace"); - if (traceVisible) { - errorContainer.style.height = errorContainer.scrollHeight - traceDiv.scrollHeight + traceDiv.offsetHeight + "px"; - } else { - errorContainer.style.height = "auto"; - } -} -function showHelp() { - $("#helpModal").modal("show"); -} diff --git a/app/core/src/main/resources/static/js/favourites.js b/app/core/src/main/resources/static/js/favourites.js deleted file mode 100644 index 169cf53d9..000000000 --- a/app/core/src/main/resources/static/js/favourites.js +++ /dev/null @@ -1,144 +0,0 @@ -function updateFavoritesDropdown() { - const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || []; - - for (var i = 0; i < localStorage.length; i++) { - var key = localStorage.key(i); - var value = localStorage.getItem(key); - - if (value === 'favorite') { - const index = favoritesList.indexOf(key); - if (index === -1) { - favoritesList.push(key); - localStorage.removeItem(key); - console.log(`Added to favorites: ${key}`); - } - } - } - - var dropdown = document.querySelector('#favoritesDropdown'); - - if (!dropdown) { - console.error('Dropdown element with ID "favoritesDropdown" not found!'); - return; - } - dropdown.innerHTML = ''; - - var hasFavorites = false; - var addedFeatures = new Set(); - - for (var i = 0; i < favoritesList.length; i++) { - var value = favoritesList[i]; - if (value) { - var navbarEntry = document.querySelector(`a[data-bs-link='${value}']`); - if (navbarEntry) { - var featureName = navbarEntry.textContent.trim(); - - if (!addedFeatures.has(featureName)) { - var dropdownItem = document.createElement('div'); - dropdownItem.className = 'dropdown-item d-flex justify-content-between align-items-center'; - - // Create a wrapper for the original content - var contentWrapper = document.createElement('div'); - contentWrapper.className = 'd-flex align-items-center flex-grow-1'; - contentWrapper.style.textDecoration = 'none'; - contentWrapper.style.color = 'inherit'; - - // Clone the original content - var divElement = navbarEntry.querySelector('div'); - if (divElement) { - var originalContent = divElement.cloneNode(true); - contentWrapper.appendChild(originalContent); - } else { - // Fallback: create content manually if div is not found - var fallbackContent = document.createElement('div'); - fallbackContent.innerHTML = navbarEntry.innerHTML; - contentWrapper.appendChild(fallbackContent); - } - - // Create the remove button - var removeButton = document.createElement('button'); - removeButton.className = 'btn btn-sm btn-link p-0 ml-2'; - removeButton.innerHTML = 'close'; - removeButton.onclick = function (itemKey, event) { - event.preventDefault(); - event.stopPropagation(); - addToFavorites(itemKey); - updateFavoritesDropdown(); - }.bind(null, value); - - // Add click event to the content wrapper - contentWrapper.onclick = function (itemHref, event) { - event.preventDefault(); - window.location.href = itemHref; - }.bind(null, navbarEntry.href); - - dropdownItem.appendChild(contentWrapper); - dropdownItem.appendChild(removeButton); - dropdown.appendChild(dropdownItem); - hasFavorites = true; - addedFeatures.add(featureName); - } - } - } else { - console.warn(`Navbar entry not found for : ${value}`); - } - } - - if (!hasFavorites) { - var defaultItem = document.createElement('a'); - defaultItem.className = 'dropdown-item'; - defaultItem.textContent = noFavourites || 'No favorites added'; - dropdown.appendChild(defaultItem); - } -} - -function updateFavoriteIcons() { - const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || []; - - // Select all favorite icons - document.querySelectorAll('.favorite-icon').forEach((icon) => { - const endpoint = icon.getAttribute('data-endpoint'); - const parent = icon.closest('.dropdown-item'); - - // Determine if the icon belongs to groupRecent or groupFavorites - const isInGroupRecent = parent?.closest('#groupRecent') !== null; - const isInGroupFavorites = parent?.closest('#groupFavorites') !== null; - - if (isInGroupRecent) { - icon.style.display = 'none'; - } else if (isInGroupFavorites) { - icon.textContent = 'close_small'; - icon.style.color = 'palevioletred'; - } else { - icon.textContent = favoritesList.includes(endpoint) ? 'close_small' : 'add'; - icon.className = favoritesList.includes(endpoint) - ? 'material-symbols-rounded favorite-icon close-icon' - : 'material-symbols-rounded favorite-icon add-icon'; - } - }); -} - -function addToFavorites(entryId) { - if (entryId) { - const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || []; - const index = favoritesList.indexOf(entryId); - - if (index === -1) { - favoritesList.push(entryId); - console.log(`Added to favorites: ${entryId}`); - } else { - favoritesList.splice(index, 1); - console.log(`Removed from favorites: ${entryId}`); - } - - localStorage.setItem('favoritesList', JSON.stringify(favoritesList)); - updateFavoritesDropdown(); - updateFavoriteIcons(); - const currentPath = window.location.pathname; - if (currentPath.includes('home-legacy')) { - syncFavoritesLegacy(); - } else { - initializeCards(); - } - } -} diff --git a/app/core/src/main/resources/static/js/fetch-utils.js b/app/core/src/main/resources/static/js/fetch-utils.js deleted file mode 100644 index 2cccbd19d..000000000 --- a/app/core/src/main/resources/static/js/fetch-utils.js +++ /dev/null @@ -1,67 +0,0 @@ -// Authentication utility for cookie-based JWT -window.JWTManager = { - - // Logout - clear cookies and redirect to login - logout: function() { - - // Clear JWT cookie manually (fallback) - document.cookie = 'stirling_jwt=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=None; Secure'; - - // Perform logout request to clear server-side session - fetch('/logout', { - method: 'POST', - credentials: 'include' - }).then(response => { - if (response.redirected) { - window.location.href = response.url; - } else { - window.location.href = '/login?logout=true'; - } - }).catch(() => { - // If logout fails, let server handle it - window.location.href = '/logout'; - }); - } -}; - -window.fetchWithCsrf = async function(url, options = {}) { - function getCsrfToken() { - const cookieValue = document.cookie - .split('; ') - .find(row => row.startsWith('XSRF-TOKEN=')) - ?.split('=')[1]; - - if (cookieValue) { - return cookieValue; - } - const csrfElement = document.querySelector('input[name="_csrf"]'); - return csrfElement ? csrfElement.value : null; - } - - // Create a new options object to avoid modifying the passed object - const fetchOptions = { ...options }; - - // Ensure headers object exists - fetchOptions.headers = { ...options.headers }; - - // Add CSRF token if available - const csrfToken = getCsrfToken(); - if (csrfToken) { - fetchOptions.headers['X-XSRF-TOKEN'] = csrfToken; - } - - // Always include credentials to send JWT cookies - fetchOptions.credentials = 'include'; - - // Make the request - const response = await fetch(url, fetchOptions); - - // Handle 401 responses (unauthorized) - if (response.status === 401) { - console.warn('Authentication failed, redirecting to login'); - window.JWTManager.logout(); - return response; - } - - return response; -} diff --git a/app/core/src/main/resources/static/js/file-icon-factory.js b/app/core/src/main/resources/static/js/file-icon-factory.js deleted file mode 100644 index 645ae323e..000000000 --- a/app/core/src/main/resources/static/js/file-icon-factory.js +++ /dev/null @@ -1,76 +0,0 @@ -class FileIconFactory { - static createFileIcon(fileExtension) { - let ext = fileExtension.toLowerCase(); - switch (ext) { - case "pdf": - return this.createPDFIcon(); - case "csv": - return this.createCSVIcon(); - case "xls": - case "xlsx": - return this.createXLSXIcon(); - case "jpe": - case "jpg": - case "jpeg": - case "gif": - case "png": - case "bmp": - case "ico": - case "svg": - case "svgz": - case "tif": - case "tiff": - case "ai": - case "drw": - case "pct": - case "psp": - case "xcf": - case "psd": - case "raw": - case "webp": - case "heic": - return this.createImageIcon(); - default: - return this.createUnknownFileIcon(); - } - } - - static createPDFIcon() { - return ` - - - - `; - } - - static createImageIcon() { - return ``; - } - - static createCSVIcon() { - return ` - - - - `; - } - - static createXLSXIcon() { - return ` - - - - - `; - } - - static createUnknownFileIcon() { - return ` - - - - `; - } -} - -export default FileIconFactory; diff --git a/app/core/src/main/resources/static/js/file-utils.js b/app/core/src/main/resources/static/js/file-utils.js deleted file mode 100644 index 98772cb2f..000000000 --- a/app/core/src/main/resources/static/js/file-utils.js +++ /dev/null @@ -1,31 +0,0 @@ -class FileUtils { - static extractFileExtension(filename) { - if (!filename || filename.trim().length <= 0) return ""; - let trimmedName = filename.trim(); - return trimmedName.substring(trimmedName.lastIndexOf(".") + 1); - } - - static transformFileSize(size) { - if (!size) return `0Bs`; - let oneKB = 1024; - let oneMB = oneKB * 1024; - let oneGB = oneMB * 1024; - let oneTB = oneGB * 1024; - - if (size < oneKB) return `${this._toFixed(size)}Bs`; - else if (oneKB <= size && size < oneMB) return `${this._toFixed(size / oneKB)}KBs`; - else if (oneMB <= size && size < oneGB) return `${this._toFixed(size / oneMB)}MBs`; - else if (oneGB <= size && size < oneTB) return `${this._toFixed(size / oneGB)}GBs`; - else return `${this._toFixed(size / oneTB)}TBs`; - } - - static _toFixed(val, digits = 1) { - // Return value without ending 0s after decimal point - // Example: if res == 145.0 then return 145, else if 145.x (where x != 0) return 145.x - let res = val.toFixed(digits); - let resRounded = (res|0); - return res == resRounded ? resRounded : res; - } -} - -export default FileUtils; diff --git a/app/core/src/main/resources/static/js/fileInput.js b/app/core/src/main/resources/static/js/fileInput.js deleted file mode 100644 index 07ec80882..000000000 --- a/app/core/src/main/resources/static/js/fileInput.js +++ /dev/null @@ -1,625 +0,0 @@ -import FileIconFactory from './file-icon-factory.js'; -import FileUtils from './file-utils.js'; -import UUID from './uuid.js'; -import { DecryptFile } from './DecryptFiles.js'; - -let isScriptExecuted = false; -if (!isScriptExecuted) { - isScriptExecuted = true; - document.addEventListener('DOMContentLoaded', function () { - document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput); - }); -} -let hasDroppedImage = false; - -const zipTypes = [ - 'application/zip', - 'multipart/x-zip', - 'application/zip-compressed', - 'application/x-zip-compressed', -]; - -const mimeTypes = { - "png": "image/png", - "jpg": "image/jpeg", - "jpeg": "image/jpeg", - "gif": "image/gif", - "bmp": "image/bmp", - "svg": "image/svg+xml", - "pdf": "application/pdf", -}; - -const isMultiToolPage = () => window.location.pathname?.includes('multi-tool'); - -const isSvgFile = (file) => { - if (!file) return false; - const type = (file.type || '').toLowerCase(); - if (type === 'image/svg+xml') { - return true; - } - const name = (file.name || '').toLowerCase(); - return name.endsWith('.svg'); -}; - -function filterSvgFiles(files) { - if (!Array.isArray(files) || !isMultiToolPage()) { - return { allowed: files ?? [], rejected: [] }; - } - - const allowed = []; - const rejected = []; - - files.forEach((file) => { - if (isSvgFile(file)) { - rejected.push(file); - } else { - allowed.push(file); - } - }); - - return { allowed, rejected }; -} - -function showSvgWarning(rejectedFiles = []) { - if (!rejectedFiles.length) return; - - const message = window.multiTool?.svgNotSupported || - 'SVG files are not supported in Multi Tool and were ignored.'; - const rejectedNames = rejectedFiles - .map((file) => file?.name) - .filter(Boolean) - .join(', '); - - if (rejectedNames) { - alert(`${message}\n${rejectedNames}`); - } else { - alert(message); - } -} - -function setupFileInput(chooser) { - const elementId = chooser.getAttribute('data-bs-element-id'); - const filesSelected = chooser.getAttribute('data-bs-files-selected'); - const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt'); - const inputContainerId = chooser.getAttribute('data-bs-element-container-id'); - const showUploads = chooser.getAttribute('data-bs-show-uploads') === "true"; - const name = chooser.getAttribute('data-bs-unique-id') - const noFileSelectedPrompt = chooser.getAttribute('data-bs-no-file-selected'); - - let inputContainer = document.getElementById(inputContainerId); - const input = document.getElementById(elementId); - - if (inputContainer.id === 'pdf-upload-input-container') { - inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropPDF; - } else if (inputContainer.id === 'image-upload-input-container') { - inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropImage; - } else if (inputContainer.id === 'attachments-input-container') { - inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.addAttachments; - } - let allFiles = []; - let overlay; - let dragCounter = 0; - - input.addEventListener('reset', (e) => { - allFiles = []; - input.value = null; - }); - - inputContainer.addEventListener('click', (e) => { - let inputBtn = document.getElementById(elementId); - inputBtn.click(); - }); - - // Handle form validation if the input is left empty - input.addEventListener("invalid", (e) => { - e.preventDefault(); - alert(noFileSelectedPrompt); - }); - - const dragenterListener = function () { - dragCounter++; - if (!overlay) { - // Show overlay by removing display: none from pseudo elements (::before and ::after) - inputContainer.style.setProperty('--overlay-display', "''"); - overlay = true; - } - }; - - const dragleaveListener = function () { - dragCounter--; - if (dragCounter === 0) { - hideOverlay(); - } - }; - - function hideOverlay() { - if (!overlay) return; - inputContainer.style.setProperty('--overlay-display', 'none'); - overlay = false; - } - - const googleDriveFileListener = function (e) { - const googleDriveFiles = e.detail; - - const fileInput = document.getElementById(elementId); - if (fileInput?.hasAttribute('multiple')) { - pushFileListTo(googleDriveFiles, allFiles); - } else if (fileInput) { - allFiles = [googleDriveFiles[0]]; - } - - const dataTransfer = new DataTransfer(); - allFiles.forEach((file) => dataTransfer.items.add(file)); - fileInput.files = dataTransfer.files; - fileInput.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { source: 'drag-drop' } })); - } - - const dropListener = function (e) { - e.preventDefault(); - // Drag and Drop shall only affect the target file chooser - if (e.target !== inputContainer) { - hideOverlay(); - dragCounter = 0; - return; - } - - const dt = e.dataTransfer; - const files = dt.files; - - const fileInput = document.getElementById(elementId); - if (fileInput?.hasAttribute('multiple')) { - pushFileListTo(files, allFiles); - } else if (fileInput) { - allFiles = [files[0]]; - } - - const dataTransfer = new DataTransfer(); - allFiles.forEach((file) => dataTransfer.items.add(file)); - - fileInput.files = dataTransfer.files; - - hideOverlay(); - - dragCounter = 0; - - fileInput.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { source: 'drag-drop' } })); - }; - - function pushFileListTo(fileList, container) { - for (let file of fileList) { - container.push(file); - } - } - - ['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => { - document.body.addEventListener(eventName, preventDefaults, false); - }); - - function preventDefaults(e) { - e.preventDefault(); - e.stopPropagation(); - } - - document.body.addEventListener('dragenter', dragenterListener); - document.body.addEventListener('dragleave', dragleaveListener); - document.body.addEventListener('drop', dropListener); - document.body.addEventListener(name + 'GoogleDriveDrivePicked', googleDriveFileListener); - - $('#' + elementId).on('change', async function (e) { - let element = e.target; - const isDragAndDrop = e.detail?.source == 'drag-drop'; - - if (element instanceof HTMLInputElement && element.hasAttribute('multiple')) { - allFiles = isDragAndDrop ? allFiles : [...allFiles, ...element.files]; - } else { - allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]); - } - - const originalText = inputContainer.querySelector('#fileInputText').innerHTML; - - inputContainer.querySelector('#fileInputText').innerHTML = window.fileInput.loading; - - async function checkZipFile() { - const hasZipFiles = allFiles.some(file => ((typeof(file.type) != undefined) && zipTypes.includes(file.type))); - - // Only change to extractPDF message if we actually have zip files - if (hasZipFiles) { - inputContainer.querySelector('#fileInputText').innerHTML = window.fileInput.extractPDF; - } - - const promises = allFiles.map(async (file, index) => { - try { - if (zipTypes.includes(file.type)) { - await extractZipFiles(file, element.accept); - allFiles.splice(index, 1); - } - } catch (error) { - console.error(`Error extracting ZIP file (${file.name}):`, error); - allFiles.splice(index, 1); - } - }); - - await Promise.all(promises); - } - - const decryptFile = new DecryptFile(); - - await checkZipFile(); - - const { allowed: nonSvgFiles, rejected: rejectedSvgFiles } = filterSvgFiles(allFiles); - if (rejectedSvgFiles.length > 0) { - showSvgWarning(rejectedSvgFiles); - allFiles = nonSvgFiles; - - const updatedTransfer = toDataTransfer(allFiles); - element.files = updatedTransfer.files; - if (allFiles.length === 0) { - element.value = ''; - } - - if (allFiles.length === 0) { - inputContainer.querySelector('#fileInputText').innerHTML = originalText; - showOrHideSelectedFilesContainer(allFiles); - return; - } - } - - const uploadLimit = window.stirlingPDF?.uploadLimit ?? 0; - if (uploadLimit > 0) { - const oversizedFiles = allFiles.filter(f => f.size > uploadLimit); - if (oversizedFiles.length > 0) { - const names = oversizedFiles.map(f => `"${f.name}"`).join(', '); - if (names.length === 1) { - alert(`${names} ${window.stirlingPDF.uploadLimitExceededSingular} ${window.stirlingPDF.uploadLimitReadable}.`); - } else { - alert(`${names} ${window.stirlingPDF.uploadLimitExceededPlural} ${window.stirlingPDF.uploadLimitReadable}.`); - } - allFiles = allFiles.filter(f => f.size <= uploadLimit); - const dataTransfer = new DataTransfer(); - allFiles.forEach(f => dataTransfer.items.add(f)); - input.files = dataTransfer.files; - - if (allFiles.length === 0) { - inputContainer.querySelector('#fileInputText').innerHTML = originalText; - return; - } - } - } - - allFiles = await Promise.all( - allFiles.map(async (file) => { - let decryptedFile = file; - - try { - const { isEncrypted, requiresPassword } = await decryptFile.checkFileEncrypted(file); - if (file.type === 'application/pdf' && isEncrypted && - !window.location.pathname.includes('remove-password')) { - decryptedFile = await decryptFile.decryptFile(file, requiresPassword); - if (!decryptedFile) throw new Error('File decryption failed.'); - } - decryptedFile.uniqueId = UUID.uuidv4(); - return decryptedFile; - - } catch (error) { - console.error(`Error decrypting file: ${file.name}`, error); - - // Check if this is a PDF corruption error - if (error.message && error.message.includes('PDF file is corrupted')) { - // The error banner is already shown by DecryptFiles.js, just continue with the file - console.warn(`Continuing with corrupted PDF file: ${file.name}`); - } - - if (!file.uniqueId) file.uniqueId = UUID.uuidv4(); - return file; - } - }) - ); - - inputContainer.querySelector('#fileInputText').innerHTML = originalText; - if (!isDragAndDrop) { - let dataTransfer = toDataTransfer(allFiles); - element.files = dataTransfer.files; - } - - handleFileInputChange(this); - this.dispatchEvent(new CustomEvent('file-input-change', { bubbles: true, detail: { elementId, allFiles } })); - }); - - function toDataTransfer(files) { - let dataTransfer = new DataTransfer(); - files.forEach((file) => dataTransfer.items.add(file)); - return dataTransfer; - } - - async function extractZipFiles(zipFile, acceptedFileType) { - const jszip = new JSZip(); - var counter = 0; - - // do an overall count, then proceed to make the pdf files - await jszip.loadAsync(zipFile) - .then(function (zip) { - - zip.forEach(function (relativePath, zipEntry) { - counter+=1; - }) - } - ) - - if (counter >= 1000) { - throw Error("Maximum file reached"); - } - - return jszip.loadAsync(zipFile) - .then(function (zip) { - var extractionPromises = []; - - zip.forEach(function (relativePath, zipEntry) { - const promise = zipEntry.async('blob').then(function (content) { - // Assuming that folders have size zero - if (content.size > 0) { - const extension = zipEntry.name.split('.').pop().toLowerCase(); - const mimeType = mimeTypes[extension] || 'application/octet-stream'; - - // Check if we're accepting ONLY ZIP files (in which case extract everything) - // or if the file type matches the accepted type - if (zipTypes.includes(acceptedFileType) || - acceptedFileType === '*/*' || - (mimeType && (mimeType.startsWith(acceptedFileType.split('/')[0]) || acceptedFileType === mimeType))) { - var file = new File([content], zipEntry.name, { type: mimeType }); - file.uniqueId = UUID.uuidv4(); - allFiles.push(file); - } else { - console.log(`File ${zipEntry.name} skipped. MIME type (${mimeType}) does not match accepted type (${acceptedFileType})`); - } - } - }); - - extractionPromises.push(promise); - }); - - return Promise.all(extractionPromises); - }) - .catch(function (err) { - console.error("Error extracting ZIP file:", err); - throw err; - }); - } - - function handleFileInputChange(inputElement) { - - const files = allFiles; - - showOrHideSelectedFilesContainer(files); - - const filesInfo = files.map((f) => { - - const url = URL.createObjectURL(f); - - return { - name: f.name, - size: f.size, - uniqueId: f.uniqueId, - type: f.type, - url: url, - }; - }); - - const selectedFilesContainer = $(inputContainer).siblings('.selected-files'); - selectedFilesContainer.empty(); - filesInfo.forEach((info) => { - let fileContainerClasses = 'small-file-container d-flex flex-column justify-content-center align-items-center'; - - let fileContainer = document.createElement('div'); - $(fileContainer).addClass(fileContainerClasses); - $(fileContainer).attr('id', info.uniqueId); - - let fileIconContainer = document.createElement('div'); - const isDragAndDropEnabled = - window.location.pathname.includes('add-image') || window.location.pathname.includes('sign'); - - // add image thumbnail to it - if (info.type.startsWith('image/')) { - let imgPreview = document.createElement('img'); - imgPreview.src = info.url; - imgPreview.alt = 'Preview'; - imgPreview.style.width = '50px'; - imgPreview.style.height = '50px'; - imgPreview.style.objectFit = 'cover'; - $(fileIconContainer).append(imgPreview); - - if (isDragAndDropEnabled) { - let dragIcon = document.createElement('div'); - dragIcon.classList.add('drag-icon'); - dragIcon.innerHTML = - ''; - fileContainer.appendChild(dragIcon); - - $(fileContainer).attr('draggable', 'true'); - $(fileContainer).on('dragstart', (e) => { - e.originalEvent.dataTransfer.setData('fileUrl', info.url); - e.originalEvent.dataTransfer.setData('uniqueId', info.uniqueId); - e.originalEvent.dataTransfer.setDragImage(imgPreview, imgPreview.width / 2, imgPreview.height / 2); - }); - enableImagePreviewOnClick(fileIconContainer); - } else { - $(fileContainer).removeAttr('draggable'); - } - } else { - fileIconContainer = createFileIconContainer(info); - } - - let fileInfoContainer = createFileInfoContainer(info); - - if (!isDragAndDropEnabled) { - let removeBtn = document.createElement('div'); - removeBtn.classList.add('remove-selected-file'); - - let removeBtnIconHTML = ``; - $(removeBtn).append(removeBtnIconHTML); - $(removeBtn).attr('data-file-id', info.uniqueId).click(removeFileListener); - $(fileContainer).append(removeBtn); - } - $(fileContainer).append(fileIconContainer, fileInfoContainer); - - selectedFilesContainer.append(fileContainer); - }); - const pageContainers = $('#box-drag-container'); - pageContainers.off('dragover').on('dragover', (e) => { - e.preventDefault(); - }); - - pageContainers.off('drop').on('drop', (e) => { - e.preventDefault(); - const fileUrl = e.originalEvent.dataTransfer.getData('fileUrl'); - - if (fileUrl) { - const existingImages = $(e.target).find(`img[src="${fileUrl}"]`); - if (existingImages.length === 0) { - DraggableUtils.createDraggableCanvasFromUrl(fileUrl); - } - } - const overlayElement = chooser.querySelector('.drag-drop-overlay'); - if (overlayElement) { - overlayElement.style.display = 'none'; - } - hasDroppedImage = true; - }); - - showOrHideSelectedFilesContainer(files); - } - - function showOrHideSelectedFilesContainer(files) { - if (showUploads && files && files.length > 0) { - chooser.style.setProperty('--selected-files-display', 'flex'); - } else { - chooser.style.setProperty('--selected-files-display', 'none'); - } - const isDragAndDropEnabled = - (window.location.pathname.includes('add-image') || window.location.pathname.includes('sign')) && - files.some((file) => file.type.startsWith('image/')); - - if (!isDragAndDropEnabled) return; - - const selectedFilesContainer = chooser.querySelector('.selected-files'); - - let overlayElement = chooser.querySelector('.drag-drop-overlay'); - if (!overlayElement) { - selectedFilesContainer.style.position = 'relative'; - overlayElement = document.createElement('div'); - overlayElement.classList.add('draggable-image-overlay'); - - overlayElement.innerHTML = 'Drag images to add them to the page'; - selectedFilesContainer.appendChild(overlayElement); - } - if (hasDroppedImage) overlayElement.style.display = files && files.length > 0 ? 'flex' : 'none'; - - selectedFilesContainer.addEventListener('mouseenter', () => { - overlayElement.style.display = 'none'; - }); - - selectedFilesContainer.addEventListener('mouseleave', () => { - if (!hasDroppedImage) overlayElement.style.display = files && files.length > 0 ? 'flex' : 'none'; - }); - } - - function removeFileListener(e) { - const fileId = e.target.getAttribute('data-file-id'); - - let inputElement = document.getElementById(elementId); - removeFileById(fileId, inputElement); - - showOrHideSelectedFilesContainer(allFiles); - - inputElement.dispatchEvent(new CustomEvent('file-input-change', { bubbles: true })); - } - - function removeFileById(fileId, inputElement) { - let fileContainer = document.getElementById(fileId); - fileContainer.remove(); - - allFiles = allFiles.filter((v) => v.uniqueId != fileId); - let dataTransfer = toDataTransfer(allFiles); - - if (inputElement) inputElement.files = dataTransfer.files; - } - - function createFileIconContainer(info) { - let fileIconContainer = document.createElement('div'); - fileIconContainer.classList.add('file-icon'); - - // Add icon based on the extension - let fileExtension = FileUtils.extractFileExtension(info.name); - let fileIcon = FileIconFactory.createFileIcon(fileExtension); - - $(fileIconContainer).append(fileIcon); - return fileIconContainer; - } - - function createFileInfoContainer(info) { - let fileInfoContainer = document.createElement('div'); - let fileInfoContainerClasses = 'file-info d-flex flex-column align-items-center justify-content-center'; - - $(fileInfoContainer).addClass(fileInfoContainerClasses); - - $(fileInfoContainer).append(`
${info.name}
`); - let fileSizeWithUnits = FileUtils.transformFileSize(info.size); - $(fileInfoContainer).append(`
${fileSizeWithUnits}
`); - return fileInfoContainer; - } - - //Listen for event of file being removed and the filter it out of the allFiles array - document.addEventListener('fileRemoved', function (e) { - const fileId = e.detail; - let inputElement = document.getElementById(elementId); - removeFileById(fileId, inputElement); - showOrHideSelectedFilesContainer(allFiles); - }); - function enableImagePreviewOnClick(container) { - const imagePreviewModal = document.getElementById('imagePreviewModal') || createImagePreviewModal(); - - container.querySelectorAll('img').forEach((img) => { - if (!img.hasPreviewListener) { - img.addEventListener('mouseup', function () { - const imgElement = imagePreviewModal.querySelector('img'); - imgElement.src = this.src; - imagePreviewModal.style.display = 'flex'; - }); - img.hasPreviewListener = true; - } - }); - - function createImagePreviewModal() { - const modal = document.createElement('div'); - modal.id = 'imagePreviewModal'; - modal.style.position = 'fixed'; - modal.style.top = '0'; - modal.style.left = '0'; - modal.style.width = '100vw'; - modal.style.height = '100vh'; - modal.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; - modal.style.display = 'none'; - modal.style.justifyContent = 'center'; - modal.style.alignItems = 'center'; - modal.style.zIndex = '2000'; - - const imgElement = document.createElement('img'); - imgElement.style.maxWidth = '90%'; - imgElement.style.maxHeight = '90%'; - - modal.appendChild(imgElement); - document.body.appendChild(modal); - - modal.addEventListener('click', () => { - modal.style.display = 'none'; - }); - - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && modal.style.display === 'flex') { - modal.style.display = 'none'; - } - }); - - return modal; - } - } -} diff --git a/app/core/src/main/resources/static/js/game.js b/app/core/src/main/resources/static/js/game.js deleted file mode 100644 index df04c5dd5..000000000 --- a/app/core/src/main/resources/static/js/game.js +++ /dev/null @@ -1,276 +0,0 @@ -function initializeGame() { - const gameContainer = document.getElementById("game-container"); - const player = document.getElementById("player"); - - let playerSize = gameContainer.clientWidth * 0.0625; // 5% of container width - player.style.width = playerSize + "px"; - player.style.height = playerSize + "px"; - - let playerX = gameContainer.clientWidth / 2 - playerSize / 2; - let playerY = gameContainer.clientHeight * 0.1; - const scoreElement = document.getElementById("score"); - const levelElement = document.getElementById("level"); - const livesElement = document.getElementById("lives"); - const highScoreElement = document.getElementById("high-score"); - - let pdfSize = gameContainer.clientWidth * 0.0625; // 5% of container width - let projectileWidth = gameContainer.clientWidth * 0.00625; // 0.00625; // 0.5% of container width - let projectileHeight = gameContainer.clientHeight * 0.01667; // 1% of container height - - let paused = false; - - const fireRate = 200; // Time between shots in milliseconds - let lastProjectileTime = 0; - let lives = 3; - - let highScore = localStorage.getItem("highScore") ? parseInt(localStorage.getItem("highScore")) : 0; - updateHighScore(); - - const PLAYER_MOVE_SPEED = 5; - const BASE_PDF_SPEED = 1; - const LEVEL_INCREASE_PDF_SPEED = 0.2; - 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 MAX_SPAWN_RATE_REDUCTION_MS = 800; // Max milliseconds from the base spawn interval - - - let keysPressed = {}; - const pdfs = []; - const projectiles = []; - let score = 0; - let level = 1; - let pdfSpeed = BASE_PDF_SPEED; - let gameOver = false; - - - function handleKeys() { - if (keysPressed["ArrowLeft"]) { - playerX -= PLAYER_MOVE_SPEED; - playerX = Math.max(0, playerX) - } - if (keysPressed["ArrowRight"]) { - playerX += PLAYER_MOVE_SPEED; - playerX = Math.min(gameContainer.clientWidth - playerSize, playerX); - } - if (keysPressed[" "] && !gameOver) { - const currentTime = new Date().getTime(); - if (currentTime - lastProjectileTime >= fireRate) { - shootProjectile(); - lastProjectileTime = currentTime; - } - } - updatePlayerPosition(); - } - - function onKeydown(event) { - if (event.key === " ") { - event.preventDefault(); - } - keysPressed[event.key] = true; - handleKeys(); - } - function onKeyUp(event) { - keysPressed[event.key] = false; - } - - document.removeEventListener("keydown", onKeydown); - document.removeEventListener("keyup", onKeyUp); - document.addEventListener("keydown", onKeydown); - document.addEventListener("keyup", onKeyUp); - - function updatePlayerPosition() { - player.style.left = playerX + "px"; - player.style.bottom = playerY + "px"; - } - - function updateLives() { - livesElement.textContent = "Lives: " + lives; - } - - function updateHighScore() { - highScoreElement.textContent = "High Score: " + highScore; - } - - function shootProjectile() { - const projectile = document.createElement("div"); - projectile.classList.add("projectile"); - projectile.style.backgroundColor = "black"; - projectile.style.width = projectileWidth + "px"; - projectile.style.height = projectileHeight + "px"; - projectile.style.left = playerX + playerSize / 2 - projectileWidth / 2 + "px"; - projectile.style.top = gameContainer.clientHeight - playerY - playerSize + "px"; - gameContainer.appendChild(projectile); - projectiles.push(projectile); - } - - function spawnPdf() { - const pdf = document.createElement("img"); - pdf.src = "images/file-earmark-pdf.svg"; - pdf.classList.add("pdf"); - pdf.style.width = pdfSize + "px"; - pdf.style.height = pdfSize + "px"; - pdf.style.left = Math.floor(Math.random() * (gameContainer.clientWidth - (2*pdfSize))) + pdfSize + "px"; - pdf.style.top = "0px"; - gameContainer.appendChild(pdf); - pdfs.push(pdf); - } - - function resetEnemies() { - pdfs.forEach((pdf) => gameContainer.removeChild(pdf)); - pdfs.length = 0; - } - - function updateGame() { - if (gameOver || paused) return; - - handleKeys(); - for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) { - const pdf = pdfs[pdfIndex]; - const pdfY = parseFloat(pdf.style.top) + pdfSpeed; - if (pdfY + 50 > gameContainer.clientHeight) { - gameContainer.removeChild(pdf); - pdfs.splice(pdfIndex, 1); - - // Deduct 2 points when a PDF gets past the player - score -= 0; - updateScore(); - - // Decrease lives and check if game over - lives--; - updateLives(); - if (lives <= 0) { - endGame(); - return; - } - } else { - pdf.style.top = pdfY + "px"; - - // Check for collision with player - if (collisionDetected(player, pdf)) { - lives--; - updateLives(); - resetEnemies(); - if (lives <= 0) { - endGame(); - return; - } - } - } - } - - projectiles.forEach((projectile, projectileIndex) => { - const projectileY = parseInt(projectile.style.top) - 10; - if (projectileY < 0) { - gameContainer.removeChild(projectile); - projectiles.splice(projectileIndex, 1); - } else { - projectile.style.top = projectileY + "px"; - } - - for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) { - const pdf = pdfs[pdfIndex]; - if (collisionDetected(projectile, pdf)) { - gameContainer.removeChild(pdf); - gameContainer.removeChild(projectile); - pdfs.splice(pdfIndex, 1); - projectiles.splice(projectileIndex, 1); - score = score + 10; - updateScore(); - break; - } - } - }); - - setTimeout(updateGame, 1000 / 60); - } - - function resetGame() { - playerX = gameContainer.clientWidth / 2; - playerY = 50; - updatePlayerPosition(); - - pdfs.forEach((pdf) => gameContainer.removeChild(pdf)); - projectiles.forEach((projectile) => gameContainer.removeChild(projectile)); - - pdfs.length = 0; - projectiles.length = 0; - - score = 0; - level = 1; - lives = 3; - - gameOver = false; - - updateScore(); - updateLives(); - levelElement.textContent = "Level: " + level; - pdfSpeed = BASE_PDF_SPEED; - clearTimeout(spawnPdfTimeout); // Clear the existing spawnPdfTimeout - setTimeout(updateGame, 1000 / 60); - spawnPdfInterval(); - } - - function updateScore() { - scoreElement.textContent = "Score: " + score; - checkLevelUp(); - } - - function checkLevelUp() { - const newLevel = Math.floor(score / 100) + 1; - if (newLevel > level) { - level = newLevel; - levelElement.textContent = "Level: " + level; - pdfSpeed += LEVEL_INCREASE_PDF_SPEED; - } - } - - function collisionDetected(a, b) { - const rectA = a.getBoundingClientRect(); - const rectB = b.getBoundingClientRect(); - return rectA.left < rectB.right && rectA.right > rectB.left && rectA.top < rectB.bottom && rectA.bottom > rectB.top; - } - - function endGame() { - gameOver = true; - if (score > highScore) { - highScore = score; - localStorage.setItem("highScore", highScore); - updateHighScore(); - } - alert("Game Over! Your final score is: " + score); - document.getElementById("game-container-wrapper").close(); - } - - let spawnPdfTimeout; - - - - function spawnPdfInterval() { - if (gameOver || paused) { - clearTimeout(spawnPdfTimeout); - return; - } - spawnPdf(); - let spawnRateReduction = Math.min(level * LEVEL_INCREASE_FACTOR_MS, MAX_SPAWN_RATE_REDUCTION_MS); - let spawnRate = BASE_SPAWN_INTERVAL_MS - spawnRateReduction; - spawnPdfTimeout = setTimeout(spawnPdfInterval, spawnRate); - } - - updatePlayerPosition(); - updateGame(); - spawnPdfInterval(); - - document.addEventListener("visibilitychange", function () { - if (document.hidden) { - paused = true; - } else { - paused = false; - updateGame(); - spawnPdfInterval(); - } - }); - - window.resetGame = resetGame; -} - -window.initializeGame = initializeGame; diff --git a/app/core/src/main/resources/static/js/githubVersion.js b/app/core/src/main/resources/static/js/githubVersion.js deleted file mode 100644 index 5dee33238..000000000 --- a/app/core/src/main/resources/static/js/githubVersion.js +++ /dev/null @@ -1,390 +0,0 @@ -function compareVersions(version1, version2) { - const v1 = version1.split("."); - const v2 = version2.split("."); - - for (let i = 0; i < v1.length || i < v2.length; i++) { - const n1 = parseInt(v1[i]) || 0; - const n2 = parseInt(v2[i]) || 0; - - if (n1 > n2) { - return 1; - } else if (n1 < n2) { - return -1; - } - } - - return 0; -} - -function getDownloadUrl() { - // Only show download for non-Docker installations - if (machineType === 'Docker' || machineType === 'Kubernetes') { - return null; - } - - const baseUrl = 'https://files.stirlingpdf.com/'; - - // Determine file based on machine type and security - if (machineType === 'Server-jar') { - return baseUrl + (activeSecurity ? 'Stirling-PDF-with-login.jar' : 'Stirling-PDF.jar'); - } - - // Client installations - if (machineType.startsWith('Client-')) { - const os = machineType.replace('Client-', ''); // win, mac, unix - const type = activeSecurity ? '-server-security' : '-server'; - - if (os === 'unix') { - return baseUrl + os + type + '.jar'; - } else if (os === 'win') { - return baseUrl + os + '-installer.exe'; - } else if (os === 'mac') { - return baseUrl + os + '-installer.dmg'; - } - } - - return null; -} - -// Function to get translated priority text -function getTranslatedPriority(priority) { - switch(priority?.toLowerCase()) { - case 'urgent': return updatePriorityUrgent; - case 'normal': return updatePriorityNormal; - case 'minor': return updatePriorityMinor; - case 'low': return updatePriorityLow; - default: return priority?.toUpperCase() || 'NORMAL'; - } -} - -async function getUpdateSummary() { - // Map Java License enum to API types - let type = 'normal'; - if (licenseType === 'PRO') { - type = 'pro'; - } else if (licenseType === 'ENTERPRISE') { - type = 'enterprise'; - } - const url = `https://supabase.stirling.com/functions/v1/updates?from=${currentVersion}&type=${type}&login=${activeSecurity}&summary=true`; - console.log("Fetching update summary from:", url); - try { - const response = await fetch(url); - console.log("Response status:", response.status); - if (response.status === 200) { - const data = await response.json(); - return data; - } else { - console.error("Failed to fetch update summary from Supabase:", response.status); - return null; - } - } catch (error) { - console.error("Failed to fetch update summary from Supabase:", error); - return null; - } -} - -async function getFullUpdateInfo() { - // Map Java License enum to API types - let type = 'normal'; - if (licenseType === 'PRO') { - type = 'pro'; - } else if (licenseType === 'ENTERPRISE') { - type = 'enterprise'; - } - const url = `https://supabase.stirling.com/functions/v1/updates?from=${currentVersion}&type=${type}&login=${activeSecurity}&summary=false`; - console.log("Fetching full update info from:", url); - try { - const response = await fetch(url); - console.log("Full update response status:", response.status); - if (response.status === 200) { - const data = await response.json(); - return data; - } else { - console.error("Failed to fetch full update info from Supabase:", response.status); - return null; - } - } catch (error) { - console.error("Failed to fetch full update info from Supabase:", error); - return null; - } -} - -async function getCurrentVersionFromBypass() { - const url = "https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/master/build.gradle"; - try { - const response = await fetch(url); - if (response.status === 200) { - const text = await response.text(); - const versionRegex = /version\s*=\s*['"](\d+\.\d+\.\d+)['"]/; - const match = versionRegex.exec(text); - if (match) { - return match[1]; - } - } - throw new Error("Version number not found"); - } catch (error) { - console.error("Failed to fetch latest version from build.gradle:", error); - return ""; // Return an empty string if the fetch fails - } -} - -async function checkForUpdate() { - // Initialize the update button as hidden - var updateBtn = document.getElementById("update-btn") || null; - var updateLink = document.getElementById("update-link") || null; - var updateLinkLegacy = document.getElementById("update-link-legacy") || null; - if (updateBtn !== null) { - updateBtn.style.display = "none"; - updateBtn.classList.remove("btn-danger", "btn-warning", "btn-outline-primary"); - } - if (updateLink !== null) { - updateLink.style.display = "none"; - } - if (updateLinkLegacy !== null) { - console.log("hidden!"); - if (!updateLinkLegacy.classList.contains("visually-hidden")) { - updateLinkLegacy.classList.add("visually-hidden"); - } - } - - const updateSummary = await getUpdateSummary(); - if (!updateSummary) { - console.log("No update summary available"); - return; - } - - console.log("updateSummary=", updateSummary); - console.log("currentVersion=" + currentVersion); - console.log("latestVersion=" + updateSummary.latest_version); - - if (updateSummary.latest_version && compareVersions(updateSummary.latest_version, currentVersion) > 0) { - const priority = updateSummary.max_priority || 'normal'; - - if (updateBtn != null) { - // Style button based on priority - if (priority === 'urgent') { - updateBtn.classList.add("btn-danger"); - updateBtn.innerHTML = urgentUpdateAvailable; - } else if (priority === 'normal') { - updateBtn.classList.add("btn-warning"); - updateBtn.innerHTML = updateAvailableText; - } else { - updateBtn.classList.add("btn-outline-primary"); - updateBtn.innerHTML = updateAvailableText; - } - - // Store summary for initial display - updateBtn.setAttribute('data-update-summary', JSON.stringify(updateSummary)); - updateBtn.style.display = "block"; - - // Add click handler for update details modal - updateBtn.onclick = function(e) { - e.preventDefault(); - showUpdateModal(); - }; - } - if (updateLink !== null) { - document.getElementById("update-link").style.display = "flex"; - } - if (updateLinkLegacy !== null) { - document.getElementById("app-update").innerHTML = updateAvailable.replace("{0}", '' + currentVersion + '').replace("{1}", '' + updateSummary.latest_version + ''); - if (updateLinkLegacy.classList.contains("visually-hidden")) { - updateLinkLegacy.classList.remove("visually-hidden"); - } - } - console.log("visible"); - } else { - if (updateLinkLegacy !== null) { - if (!updateLinkLegacy.classList.contains("visually-hidden")) { - updateLinkLegacy.classList.add("visually-hidden"); - } - } - console.log("hidden"); - } -} - -async function showUpdateModal() { - // Close settings modal if open - const settingsModal = bootstrap.Modal.getInstance(document.getElementById('settingsModal')); - if (settingsModal) { - settingsModal.hide(); - } - - // Get summary data from button - const updateBtn = document.getElementById("update-btn"); - const summaryData = JSON.parse(updateBtn.getAttribute('data-update-summary')); - - // Utility function to escape HTML special characters - function escapeHtml(str) { - if (typeof str !== 'string') return str; - return str - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(/\//g, '/'); - } - - // Create initial modal with loading state - const initialModalHtml = ` - - `; - - // Remove existing modal if present - const existingModal = document.getElementById('updateModal'); - if (existingModal) { - existingModal.remove(); - } - - // Add modal to body - document.body.insertAdjacentHTML('beforeend', initialModalHtml); - - // Show modal - const modal = new bootstrap.Modal(document.getElementById('updateModal')); - modal.show(); - - // Fetch full update info - const fullUpdateInfo = await getFullUpdateInfo(); - - // Update modal with full information - const modalBody = document.getElementById('updateModalBody'); - if (fullUpdateInfo && fullUpdateInfo.new_versions) { - const storedMode = localStorage.getItem("dark-mode"); - const isDarkMode = storedMode === "on" || - (storedMode === null && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches); - const darkClasses = isDarkMode ? { - accordionItem: 'bg-dark border-secondary text-light', - accordionButton: 'bg-dark text-light border-secondary', - accordionBody: 'bg-dark text-light' - } : { - accordionItem: '', - accordionButton: '', - accordionBody: '' - }; - - const detailedVersionsHtml = ` -
-
${updateAvailableUpdates}
-
- ${fullUpdateInfo.new_versions.map((version, index) => ` -
-

- -

-
-
-
${version.announcement.title}
-

${version.announcement.message}

- ${version.compatibility.breaking_changes ? ` - - ` : ''} -
-
-
- `).join('')} -
-
- `; - - // Remove loading spinner and add detailed info - const spinner = document.getElementById('loadingSpinner'); - if (spinner) { - spinner.parentElement.remove(); - } - modalBody.insertAdjacentHTML('beforeend', detailedVersionsHtml); - - } else { - // Remove loading spinner if failed to load - const spinner = document.getElementById('loadingSpinner'); - if (spinner) { - spinner.parentElement.innerHTML = `

${updateUnableToLoadDetails}

`; - } - } -} - -document.addEventListener("DOMContentLoaded", (event) => { - checkForUpdate(); -}); diff --git a/app/core/src/main/resources/static/js/googleFilePicker.js b/app/core/src/main/resources/static/js/googleFilePicker.js deleted file mode 100644 index 833cf950b..000000000 --- a/app/core/src/main/resources/static/js/googleFilePicker.js +++ /dev/null @@ -1,158 +0,0 @@ -const SCOPES = "https://www.googleapis.com/auth/drive.readonly"; -const SESSION_STORAGE_ID = "googleDrivePickerAccessToken"; - -let tokenClient; -let accessToken = sessionStorage.getItem(SESSION_STORAGE_ID); - -let isScriptExecuted = false; -if (!isScriptExecuted) { - isScriptExecuted = true; - document.addEventListener("DOMContentLoaded", function () { - document.querySelectorAll(".google-drive-button").forEach(setupGoogleDrivePicker); - }); -} - -function gisLoaded() { - tokenClient = google.accounts.oauth2.initTokenClient({ - client_id: window.stirlingPDF.GoogleDriveClientId, - scope: SCOPES, - callback: "", // defined later - }); -} - -// add more as needed. -// Google picker is limited on what mimeTypes are supported -// Wild card are not supported -const expandableMimeTypes = { - "image/*" : ["image/jpeg", "image/png","image/svg+xml" ] -} - -function fileInputToGooglePickerMimeTypes(accept) { - - if(accept == null || accept == "" || accept.includes("*/*")){ - - // Setting null will accept all supported mimetypes - return null; - } - - let mimeTypes = []; - accept.split(',').forEach(part => { - if(!(part in expandableMimeTypes)){ - mimeTypes.push(part); - return; - } - - expandableMimeTypes[part].forEach(mimeType => { - mimeTypes.push(mimeType); - }); - }); - - const mimeString = mimeTypes.join(",").replace(/\s+/g, ''); - console.log([accept, "became", mimeString]); - return mimeString; -} - -/** - * Callback after api.js is loaded. - */ -function gapiLoaded() { - gapi.load("client:picker", initializePicker); -} - -/** - * Callback after the API client is loaded. Loads the - * discovery doc to initialize the API. - */ -async function initializePicker() { - await gapi.client.load("https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"); -} - -function setupGoogleDrivePicker(picker) { - - const name = picker.getAttribute('data-name'); - const accept = picker.getAttribute('data-accept'); - const multiple = picker.getAttribute('data-multiple') === "true"; - const mimeTypes = fileInputToGooglePickerMimeTypes(accept); - - picker.addEventListener("click", onGoogleDriveButtonClick); - - function onGoogleDriveButtonClick(e) { - e.stopPropagation(); - - tokenClient.callback = (response) => { - if (response.error !== undefined) { - throw response; - } - accessToken = response.access_token; - sessionStorage.setItem(SESSION_STORAGE_ID, accessToken); - createGooglePicker(); - }; - - tokenClient.requestAccessToken({ prompt: accessToken === null ? "consent" : "" }); - } - - /** - * Sign out the user upon button click. - */ - function signOut() { - if (accessToken) { - sessionStorage.removeItem(SESSION_STORAGE_ID); - google.accounts.oauth2.revoke(accessToken); - accessToken = null; - } - } - - function createGooglePicker() { - let builder = new google.picker.PickerBuilder() - .setDeveloperKey(window.stirlingPDF.GoogleDriveApiKey) - .setAppId(window.stirlingPDF.GoogleDriveAppId) - .setOAuthToken(accessToken) - .addView( - new google.picker.DocsView() - .setIncludeFolders(true) - .setMimeTypes(mimeTypes) - ) - .addView( - new google.picker.DocsView() - .setIncludeFolders(true) - .setEnableDrives(true) - .setMimeTypes(mimeTypes) - ) - .setCallback(pickerCallback); - - if(multiple) { - builder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED); - } - const picker = builder.build(); - - picker.setVisible(true); - } - - /** - * Displays the file details of the user's selection. - * @param {object} data - Containers the user selection from the picker - */ - async function pickerCallback(data) { - if (data.action === google.picker.Action.PICKED) { - const files = await Promise.all( - data[google.picker.Response.DOCUMENTS].map(async (pickedFile) => { - const fileId = pickedFile[google.picker.Document.ID]; - console.log(fileId); - const res = await gapi.client.drive.files.get({ - fileId: fileId, - alt: "media", - }); - - let file = new File([new Uint8Array(res.body.length).map((_, i) => res.body.charCodeAt(i))], pickedFile.name, { - type: pickedFile.mimeType, - lastModified: pickedFile.lastModified, - endings: pickedFile.endings, - }); - return file; - }) - ); - - document.body.dispatchEvent(new CustomEvent(name+"GoogleDriveDrivePicked", { detail: files })); - } - } -} diff --git a/app/core/src/main/resources/static/js/homecard-legacy.js b/app/core/src/main/resources/static/js/homecard-legacy.js deleted file mode 100644 index a43453f1c..000000000 --- a/app/core/src/main/resources/static/js/homecard-legacy.js +++ /dev/null @@ -1,266 +0,0 @@ -function filterCardsLegacy() { - var input = document.getElementById('searchBar'); - var filter = input.value.toUpperCase(); - - let featureGroups = document.querySelectorAll('.feature-group-legacy'); - const collapsedGroups = getCollapsedGroups(); - - for (const featureGroup of featureGroups) { - var cards = featureGroup.querySelectorAll('.feature-card'); - - let groupMatchesFilter = false; - for (var i = 0; i < cards.length; i++) { - var card = cards[i]; - var title = card.querySelector('h5.card-title').innerText; - var text = card.querySelector('p.card-text').innerText; - - // Get the navbar tags associated with the card - var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`); - var navbarTags = navbarItem ? navbarItem.getAttribute('data-bs-tags') : ''; - - var content = title + ' ' + text + ' ' + navbarTags; - - if (content.toUpperCase().indexOf(filter) > -1) { - card.style.display = ''; - groupMatchesFilter = true; - } else { - card.style.display = 'none'; - } - } - - if (!groupMatchesFilter) { - featureGroup.style.display = 'none'; - } else { - featureGroup.style.display = ''; - resetOrTemporarilyExpandGroup(featureGroup, filter, collapsedGroups); - } - } -} - -function getCollapsedGroups() { - return localStorage.getItem('collapsedGroups') ? JSON.parse(localStorage.getItem('collapsedGroups')) : []; -} - -function resetOrTemporarilyExpandGroup(featureGroup, filterKeywords = '', collapsedGroups = []) { - const shouldResetCollapse = filterKeywords.trim() === ''; - if (shouldResetCollapse) { - // Resetting the group's expand/collapse to its original state (as in collapsed groups) - const isCollapsed = collapsedGroups.indexOf(featureGroup.id) != -1; - expandCollapseToggle(featureGroup, !isCollapsed); - } else { - // Temporarily expands feature group without affecting the actual/stored collapsed groups - featureGroup.classList.remove('collapsed'); - featureGroup.querySelector('.header-expand-button').classList.remove('collapsed'); - } -} - -function updateFavoritesSectionLegacy() { - const favoritesContainer = document.getElementById('groupFavorites').querySelector('.feature-group-container'); - favoritesContainer.innerHTML = ''; - const cards = Array.from(document.querySelectorAll('.feature-card:not(.duplicate)')); - const addedCardIds = new Set(); - let favoritesAmount = 0; - - cards.forEach((card) => { - const favouritesList = JSON.parse(localStorage.getItem('favoritesList') || '[]'); - - if (favouritesList.includes(card.id) && !addedCardIds.has(card.id)) { - const duplicate = card.cloneNode(true); - duplicate.classList.add('duplicate'); - favoritesContainer.appendChild(duplicate); - addedCardIds.add(card.id); - favoritesAmount++; - } - }); - - if (favoritesAmount === 0) { - document.getElementById('groupFavorites').style.display = 'none'; - } else { - document.getElementById('groupFavorites').style.display = 'flex'; - } - reorderCards(favoritesContainer); -} - -function syncFavoritesLegacy() { - const cards = Array.from(document.querySelectorAll('.feature-card')); - cards.forEach((card) => { - const isFavorite = localStorage.getItem(card.id) === 'favorite'; - const starIcon = card.querySelector('.favorite-icon span.material-symbols-rounded'); - if (starIcon) { - if (isFavorite) { - starIcon.classList.remove('no-fill'); - starIcon.classList.add('fill'); - card.classList.add('favorite'); - } else { - starIcon.classList.remove('fill'); - starIcon.classList.add('no-fill'); - card.classList.remove('favorite'); - } - } - }); - updateFavoritesSectionLegacy(); - updateFavoritesDropdown(); - filterCardsLegacy(); -} - -function reorderCards(container) { - var cards = Array.from(container.querySelectorAll('.feature-card')); - cards.forEach(function (card) { - container.removeChild(card); - }); - cards.sort(function (a, b) { - var aIsFavorite = localStorage.getItem(a.id) === 'favorite'; - var bIsFavorite = localStorage.getItem(b.id) === 'favorite'; - if (a.id === 'update-link') { - return -1; - } - if (b.id === 'update-link') { - return 1; - } - - if (aIsFavorite && !bIsFavorite) { - return -1; - } else if (!aIsFavorite && bIsFavorite) { - return 1; - } else { - return a.id > b.id; - } - }); - cards.forEach(function (card) { - container.appendChild(card); - }); -} - -function reorderAllCards() { - const containers = Array.from(document.querySelectorAll('.feature-group-container')); - containers.forEach(function (container) { - reorderCards(container); - }); -} - -function initializeCardsLegacy() { - reorderAllCards(); - updateFavoritesSectionLegacy(); - updateFavoritesDropdown(); - filterCardsLegacy(); -} - -function showFavoritesOnly() { - const groups = Array.from(document.querySelectorAll('.feature-group-legacy')); - if (localStorage.getItem('favoritesOnly') === 'true') { - groups.forEach((group) => { - if (group.id !== 'groupFavorites') { - group.style.display = 'none'; - } - }); - } else { - groups.forEach((group) => { - if (group.id !== 'groupFavorites') { - group.style.display = 'flex'; - } - }); - } -} - -function toggleFavoritesOnly() { - if (localStorage.getItem('favoritesOnly') === 'true') { - localStorage.setItem('favoritesOnly', 'false'); - } else { - localStorage.setItem('favoritesOnly', 'true'); - } - showFavoritesOnly(); -} - -// Expands a feature group on true, collapses it on false and toggles state on null. -function expandCollapseToggle(group, expand = null) { - if (expand === null) { - group.classList.toggle('collapsed'); - group.querySelector('.header-expand-button').classList.toggle('collapsed'); - } else if (expand) { - group.classList.remove('collapsed'); - group.querySelector('.header-expand-button').classList.remove('collapsed'); - } else { - group.classList.add('collapsed'); - group.querySelector('.header-expand-button').classList.add('collapsed'); - } - - const collapsed = localStorage.getItem('collapsedGroups') ? JSON.parse(localStorage.getItem('collapsedGroups')) : []; - const groupIndex = collapsed.indexOf(group.id); - - if (group.classList.contains('collapsed')) { - if (groupIndex === -1) { - collapsed.push(group.id); - } - } else { - if (groupIndex !== -1) { - collapsed.splice(groupIndex, 1); - } - } - - localStorage.setItem('collapsedGroups', JSON.stringify(collapsed)); -} - -function expandCollapseAll(expandAll) { - const groups = Array.from(document.querySelectorAll('.feature-group-legacy')); - groups.forEach((group) => { - expandCollapseToggle(group, expandAll); - }); -} - -window.onload = function () { - initializeCardsLegacy(); - syncFavoritesLegacy(); // Ensure everything is in sync on page load -}; - -document.addEventListener('DOMContentLoaded', function () { - const materialIcons = new FontFaceObserver('Material Symbols Rounded'); - - materialIcons - .load() - .then(() => { - document.querySelectorAll('.feature-card.hidden').forEach((el) => { - el.classList.remove('hidden'); - }); - }) - .catch(() => { - console.error('Material Symbols Rounded font failed to load.'); - }); - - Array.from(document.querySelectorAll('.feature-group-header-legacy')).forEach((header) => { - const parent = header.parentNode; - const container = header.parentNode.querySelector('.feature-group-container'); - if (parent.id !== 'groupFavorites') { - // container.style.maxHeight = container.scrollHeight + 'px'; - } - header.onclick = () => { - expandCollapseToggle(parent); - }; - }); - - const collapsed = localStorage.getItem('collapsedGroups') ? JSON.parse(localStorage.getItem('collapsedGroups')) : []; - const groupsArray = Array.from(document.querySelectorAll('.feature-group-legacy')); - - groupsArray.forEach((group) => { - if (collapsed.indexOf(group.id) !== -1) { - expandCollapseToggle(group, false); - } - }); - - // Necessary in order to not fire the transition animation on page load, which looks wrong. - // The timeout isn't doing anything visible to the user, so it's not making the page load look slower. - setTimeout(() => { - groupsArray.forEach((group) => { - const container = group.querySelector('.feature-group-container'); - container.classList.add('animated-group'); - }); - }, 500); - - Array.from(document.querySelectorAll('.feature-group-header')).forEach((header) => { - const parent = header.parentNode; - header.onclick = () => { - expandCollapseToggle(parent); - }; - }); - - showFavoritesOnly(); -}); diff --git a/app/core/src/main/resources/static/js/homecard.js b/app/core/src/main/resources/static/js/homecard.js deleted file mode 100644 index 22c0caaf5..000000000 --- a/app/core/src/main/resources/static/js/homecard.js +++ /dev/null @@ -1,249 +0,0 @@ -function filterCards() { - var input = document.getElementById('searchBar'); - var filter = input.value.toUpperCase().trim(); - - // Split the input filter into individual words for multi-word matching - var filterWords = filter.split(/[\s,;.\-]+/); - - let featureGroups = document.querySelectorAll('.feature-group'); - for (const featureGroup of featureGroups) { - var cards = featureGroup.querySelectorAll('.dropdown-item'); - - let groupMatchesFilter = false; - for (var i = 0; i < cards.length; i++) { - var card = cards[i]; - var title = card.getAttribute('title') || ''; - - // Get the navbar tags associated with the card - var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`); - var navbarTags = navbarItem ? navbarItem.getAttribute('data-bs-tags') : ''; - navbarTags = navbarItem ? navbarTags + ',' + navbarItem.getAttribute('data-bs-title') + ',' + navbarItem.children[0].getAttribute('data-title') : navbarTags; - - var content = (title + ' ' + navbarTags).toUpperCase(); - - // Check if all words in the filter match the content - var matches = filterWords.every((word) => content.includes(word)); - - if (matches) { - card.style.display = ''; - groupMatchesFilter = true; - } else { - card.style.display = 'none'; - } - } - - if (!groupMatchesFilter) { - featureGroup.style.display = 'none'; - } else { - featureGroup.style.display = ''; - } - } -} - -function updateFavoritesSection() { - const favoritesContainer = document.getElementById('groupFavorites').querySelector('.nav-group-container'); - favoritesContainer.innerHTML = ''; - let favoritesAmount = 0; - const favouritesList = JSON.parse(localStorage.getItem('favoritesList') || '[]'); - const isFavoritesView = JSON.parse(localStorage.getItem('favoritesView') || 'false'); - - favouritesList.forEach((value) => { - var navbarEntry = document.querySelector(`a[data-bs-link='${value}']`); - if (navbarEntry) { - const duplicate = navbarEntry.cloneNode(true); - favoritesContainer.appendChild(duplicate); - } - favoritesAmount++; - }); - - if (favoritesAmount === 0 || !isFavoritesView) { - document.getElementById('groupFavorites').style.display = 'none'; - } else { - document.getElementById('groupFavorites').style.display = 'flex'; - } - reorderCards(favoritesContainer); - //favoritesContainer.style.maxHeight = favoritesContainer.scrollHeight + 'px'; -} - -function reorderCards(container) { - var cards = Array.from(container.querySelectorAll('.dropdown-item')); - cards.forEach(function (card) { - container.removeChild(card); - }); - cards.sort(function (a, b) { - var aIsFavorite = localStorage.getItem(a.id) === 'favorite'; - var bIsFavorite = localStorage.getItem(b.id) === 'favorite'; - - if (aIsFavorite && !bIsFavorite) { - return -1; - } else if (!aIsFavorite && bIsFavorite) { - return 1; - } else { - return a.id > b.id; - } - }); - cards.forEach(function (card) { - container.appendChild(card); - }); -} - -function initializeCards() { - updateFavoritesSection(); - updateFavoritesView(); - updateFavoritesDropdown(); - filterCards(); -} - -function updateFavoritesView() { - const isFavoritesView = JSON.parse(localStorage.getItem('favoritesView') || 'false'); - const iconElement = document.getElementById('toggle-favourites-icon'); - const favoritesGroup = document.querySelector('#groupFavorites'); - const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || []; - - if (isFavoritesView && favoritesList.length > 0) { - document.getElementById('favouritesVisibility').style.display = 'flex'; - favoritesGroup.style.display = 'flex'; - } else { - if (favoritesList.length > 0) { - document.getElementById('favouritesVisibility').style.display = 'flex'; - favoritesGroup.style.display = 'none'; - } else { - document.getElementById('favouritesVisibility').style.display = 'none'; - } - } -} - -function toggleFavoritesMode() { - const favoritesMode = !document.querySelector('.toggle-favourites').classList.contains('active'); - document.querySelector('.toggle-favourites').classList.toggle('active', favoritesMode); - - document.querySelectorAll('.favorite-icon').forEach((icon) => { - const endpoint = icon.getAttribute('data-endpoint'); - const parent = icon.closest('.dropdown-item'); - const isInGroupRecent = parent.closest('#groupRecent') !== null; - const isInGroupFavorites = parent.closest('#groupFavorites') !== null; - - if (isInGroupRecent) { - icon.style.display = 'none'; - } else if (isInGroupFavorites) { - icon.style.display = favoritesMode ? 'inline-block' : 'none'; - icon.textContent = 'close_small'; - } else { - icon.style.display = favoritesMode ? 'inline-block' : 'none'; - - const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || []; - icon.textContent = favoritesList.includes(endpoint) ? 'close_small' : 'add'; - } - }); - - document.querySelectorAll('.dropdown-item').forEach((link) => { - if (favoritesMode) { - link.dataset.originalHref = link.getAttribute('href'); - link.setAttribute('href', '#'); - link.classList.add('no-hover'); - } else { - link.setAttribute('href', link.dataset.originalHref || '#'); - link.classList.remove('no-hover'); - } - }); - - const isFavoritesView = JSON.parse(localStorage.getItem('favoritesView') || 'false'); - if (favoritesMode && !isFavoritesView) { - toggleFavoritesView(); - } -} - -function toggleFavoritesView() { - const isFavoritesView = JSON.parse(localStorage.getItem('favoritesView') || 'false'); - localStorage.setItem('favoritesView', !isFavoritesView); - updateFavoritesView(); -} -window.onload = function () { - initializeCards(); -}; - -function sortNavElements(criteria) { - document.querySelectorAll('.nav-group-container').forEach((container) => { - const items = Array.from(container.children); - - items.sort((a, b) => { - if (criteria === 'alphabetical') { - const titleA = a.querySelector('.icon-text')?.textContent.trim().toLowerCase() || ''; - const titleB = b.querySelector('.icon-text')?.textContent.trim().toLowerCase() || ''; - return titleA.localeCompare(titleB); - } else if (criteria === 'global') { - const popularityA = parseInt(a.dataset.popularity, 10) || 1000; - const popularityB = parseInt(b.dataset.popularity, 10) || 1000; - return popularityA - popularityB; - } - return 0; - }); - container.innerHTML = ''; - items.forEach((item) => container.appendChild(item)); - }); -} - -async function fetchPopularityData(url) { - const response = await fetch(url); - if (!response.ok) { - const errorText = await response.text().catch(() => ''); - const errorMsg = errorText || response.statusText || 'Request failed'; - throw new Error(`HTTP ${response.status}: ${errorMsg}`); - } - return await response.text(); -} - -function applyPopularityData(popularityData) { - document.querySelectorAll('.dropdown-item').forEach((item) => { - const endpoint = item.getAttribute('data-bs-link'); - const popularity = popularityData['/' + endpoint]; - if (endpoint && popularity !== undefined) { - item.setAttribute('data-popularity', popularity); - } - }); - const currentSort = localStorage.getItem('homepageSort') || 'alphabetical'; - const sortDropdown = document.getElementById('sort-options'); - if (sortDropdown) { - sortDropdown.value = currentSort; - ``; - } - sortNavElements(currentSort); -} -document.addEventListener('DOMContentLoaded', async function () { - const sortDropdown = document.getElementById('sort-options'); - if (sortDropdown) { - sortDropdown.addEventListener('change', (event) => { - const selectedOption = event.target.value; - localStorage.setItem('homepageSort', selectedOption); - - sortNavElements(selectedOption); - }); - } - try { - const response = await fetch('./files/popularity.txt'); - if (!response.ok) { - const errorText = await response.text().catch(() => ''); - const errorMsg = errorText || response.statusText || 'Request failed'; - throw new Error(`HTTP ${response.status}: ${errorMsg}`); - } - const popularityData = await response.json(); - applyPopularityData(popularityData); - } catch (error) { - console.error('Error loading popularity data:', error); - } - - const materialIcons = new FontFaceObserver('Material Symbols Rounded'); - - materialIcons - .load() - .then(() => { - document.querySelectorAll('.dropdown-item.hidden').forEach((el) => { - el.classList.remove('hidden'); - }); - }) - .catch(() => { - console.error('Material Symbols Rounded font failed to load.'); - }); - - -}); diff --git a/app/core/src/main/resources/static/js/jwt-init.js b/app/core/src/main/resources/static/js/jwt-init.js deleted file mode 100644 index 35b736fd6..000000000 --- a/app/core/src/main/resources/static/js/jwt-init.js +++ /dev/null @@ -1,44 +0,0 @@ -// JWT Authentication Management Script -// This script handles cookie-based JWT authentication and page access control - -(function() { - // Clean up JWT token from URL parameters after OAuth/Login flows - function cleanupTokenFromUrl() { - const urlParams = new URLSearchParams(window.location.search); - const hasToken = urlParams.get('jwt') || urlParams.get('token'); - if (hasToken) { - // Clean up URL by removing token parameter - // Token should now be set as cookie by server - urlParams.delete('jwt'); - urlParams.delete('token'); - const newUrl = window.location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : ''); - window.history.replaceState({}, '', newUrl); - } - } - - // Initialize JWT handling when page loads - function initializeJWT() { - // Clean up any JWT tokens from URL (OAuth flow) - cleanupTokenFromUrl(); - - // Authentication is handled server-side - // If user is not authenticated, server will redirect to login - console.log('JWT initialization complete - authentication handled server-side'); - } - - // No form enhancement needed for cookie-based JWT - // Cookies are automatically sent with form submissions - function enhanceFormSubmissions() { - // Cookie-based JWT is automatically included in form submissions - // No additional processing needed - } - - // Initialize when DOM is ready - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - initializeJWT(); - }); - } else { - initializeJWT(); - } -})(); diff --git a/app/core/src/main/resources/static/js/languageSelection.js b/app/core/src/main/resources/static/js/languageSelection.js deleted file mode 100644 index 7d4b8bc0c..000000000 --- a/app/core/src/main/resources/static/js/languageSelection.js +++ /dev/null @@ -1,74 +0,0 @@ -function getStoredOrDefaultLocale() { - const storedLocale = localStorage.getItem('languageCode'); - return storedLocale || getDetailedLanguageCode(); -} - -function setLanguageForDropdown(dropdownClass) { - const storedLocale = getStoredOrDefaultLocale(); - const dropdownItems = document.querySelectorAll(dropdownClass); - - dropdownItems.forEach((item) => { - item.classList.toggle('active', item.dataset.bsLanguageCode === storedLocale); - item.removeEventListener('click', handleDropdownItemClick); - item.addEventListener('click', handleDropdownItemClick); - }); -} - -function updateUrlWithLanguage(languageCode) { - const currentURL = new URL(window.location.href); - currentURL.searchParams.set('lang', languageCode); - window.location.href = currentURL.toString(); -} - -function handleDropdownItemClick(event) { - event.preventDefault(); - const languageCode = event.currentTarget.dataset.bsLanguageCode; - if (languageCode) { - localStorage.setItem('languageCode', languageCode); - updateUrlWithLanguage(languageCode); - } else { - console.error('Language code is not set for this item.'); - } -} - -function checkUserLanguage(defaultLocale) { - if ( - !localStorage.getItem('languageCode') || - document.documentElement.getAttribute('data-language') != defaultLocale - ) { - localStorage.setItem('languageCode', defaultLocale); - updateUrlWithLanguage(defaultLocale); - } -} - -function initLanguageSettings() { - document.addEventListener('DOMContentLoaded', function () { - setLanguageForDropdown('.lang_dropdown-item'); - - const defaultLocale = getStoredOrDefaultLocale(); - checkUserLanguage(defaultLocale); - - const dropdownItems = document.querySelectorAll('.lang_dropdown-item'); - dropdownItems.forEach((item) => { - item.classList.toggle('active', item.dataset.bsLanguageCode === defaultLocale); - }); - }); -} - -function sortLanguageDropdown() { - document.addEventListener('DOMContentLoaded', function () { - const dropdownMenu = document.getElementById('languageSelection'); - if (dropdownMenu) { - const items = Array.from(dropdownMenu.children).filter((child) => child.querySelector('a')); - items - .sort((wrapperA, wrapperB) => { - const a = wrapperA.querySelector('a'); - const b = wrapperB.querySelector('a'); - return a.dataset.bsLanguageCode.localeCompare(b.dataset.bsLanguageCode); - }) - .forEach((node) => dropdownMenu.appendChild(node)); - } - }); -} - -sortLanguageDropdown(); diff --git a/app/core/src/main/resources/static/js/local-pdf-input-download.js b/app/core/src/main/resources/static/js/local-pdf-input-download.js deleted file mode 100644 index 447938847..000000000 --- a/app/core/src/main/resources/static/js/local-pdf-input-download.js +++ /dev/null @@ -1,47 +0,0 @@ -async function downloadFilesWithCallback(processFileCallback) { - const fileInput = document.querySelector('input[type="file"]'); - const files = fileInput.files; - - const zipThreshold = 4; - const zipFiles = files.length > zipThreshold; - - let jszip = null; - if (zipFiles) { - jszip = new JSZip(); - } - - const promises = Array.from(files).map(async (file) => { - const { processedData, fileName } = await processFileCallback(file); - - if (zipFiles) { - jszip.file(fileName, processedData); - } else { - const url = URL.createObjectURL(processedData); - const downloadOption = localStorage.getItem("downloadOption"); - - if (downloadOption === "sameWindow") { - window.location.href = url; - } else if (downloadOption === "newWindow") { - window.open(url, "_blank"); - } else { - const downloadLink = document.createElement("a"); - downloadLink.href = url; - downloadLink.download = fileName; - downloadLink.click(); - } - } - }); - - await Promise.all(promises); - - if (zipFiles) { - const content = await jszip.generateAsync({ type: "blob" }); - const url = URL.createObjectURL(content); - const a = document.createElement("a"); - a.href = url; - a.download = "files.zip"; - document.body.appendChild(a); - a.click(); - a.remove(); - } -} diff --git a/app/core/src/main/resources/static/js/merge.js b/app/core/src/main/resources/static/js/merge.js deleted file mode 100644 index af5037c27..000000000 --- a/app/core/src/main/resources/static/js/merge.js +++ /dev/null @@ -1,222 +0,0 @@ -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -let currentSort = { - field: null, - descending: false, -}; - -document.getElementById("fileInput-input").addEventListener("file-input-change", function () { - var files = this.files; - displayFiles(files); -}); - -/** - * @param {FileList} files - */ -async function displayFiles(files) { - const list = document.getElementById("selectedFiles"); - - while (list.firstChild) { - list.removeChild(list.firstChild); - } - - for (let i = 0; i < files.length; i++) { - const pageCount = await getPDFPageCount(files[i]); - const pageLabel = pageCount === 1 ? pageTranslation : pagesTranslation; - - // Create list item - const item = document.createElement("li"); - item.className = "list-group-item"; - - // Create filename div and set textContent to sanitize - const fileNameDiv = document.createElement("div"); - fileNameDiv.className = "filename"; - fileNameDiv.setAttribute("data-file-id", files[i].uniqueId); - fileNameDiv.textContent = files[i].name; - - // Create page info div and set textContent to sanitize - const pageInfoDiv = document.createElement("div"); - pageInfoDiv.className = "page-info"; - const pageCountSpan = document.createElement("span"); - pageCountSpan.className = "page-count"; - pageCountSpan.textContent = `${pageCount} ${pageLabel}`; - pageInfoDiv.appendChild(pageCountSpan); - - // Create arrows div with buttons - const arrowsDiv = document.createElement("div"); - arrowsDiv.className = "arrows d-flex"; - - const moveUpButton = document.createElement("button"); - moveUpButton.className = "btn btn-secondary move-up"; - moveUpButton.innerHTML = ""; - - const moveDownButton = document.createElement("button"); - moveDownButton.className = "btn btn-secondary move-down"; - moveDownButton.innerHTML = ""; - - const removeButton = document.createElement("button"); - removeButton.className = "btn btn-danger remove-file"; - removeButton.innerHTML = "×"; - - arrowsDiv.append(moveUpButton, moveDownButton, removeButton); - - // Append elements to item and then to list - const itemContainer = document.createElement("div"); - itemContainer.className = "d-flex justify-content-between align-items-center w-100"; - itemContainer.append(fileNameDiv, pageInfoDiv, arrowsDiv); - - item.appendChild(itemContainer); - list.appendChild(item); - } - - attachMoveButtons(); -} - - -async function getPDFPageCount(file) { - const blobUrl = URL.createObjectURL(file); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - const pdf = await pdfjsLib - .getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - url: blobUrl, - }) - .promise; - URL.revokeObjectURL(blobUrl); - return pdf.numPages; -} - -function attachMoveButtons() { - var moveUpButtons = document.querySelectorAll(".move-up"); - for (var i = 0; i < moveUpButtons.length; i++) { - moveUpButtons[i].addEventListener("click", function (event) { - event.preventDefault(); - var parent = this.closest(".list-group-item"); - var grandParent = parent.parentNode; - if (parent.previousElementSibling) { - grandParent.insertBefore(parent, parent.previousElementSibling); - updateFiles(); - } - }); - } - - var moveDownButtons = document.querySelectorAll(".move-down"); - for (var i = 0; i < moveDownButtons.length; i++) { - moveDownButtons[i].addEventListener("click", function (event) { - event.preventDefault(); - var parent = this.closest(".list-group-item"); - var grandParent = parent.parentNode; - if (parent.nextElementSibling) { - grandParent.insertBefore(parent.nextElementSibling, parent); - updateFiles(); - } - }); - } - - var removeButtons = document.querySelectorAll(".remove-file"); - for (var i = 0; i < removeButtons.length; i++) { - removeButtons[i].addEventListener("click", function (event) { - event.preventDefault(); - var parent = this.closest(".list-group-item"); - //Get name of removed file - let filenameNode = parent.querySelector(".filename"); - var fileName = filenameNode.innerText; - const fileId = filenameNode.getAttribute("data-file-id"); - parent.remove(); - updateFiles(); - //Dispatch a custom event with the name of the removed file - var event = new CustomEvent("fileRemoved", { detail: fileId }); - document.dispatchEvent(event); - }); - } -} - -document.getElementById("sortByNameBtn").addEventListener("click", async function () { - if (currentSort.field === "name" && !currentSort.descending) { - currentSort.descending = true; - await sortFiles((a, b) => b.name.localeCompare(a.name)); - } else { - currentSort.field = "name"; - currentSort.descending = false; - await sortFiles((a, b) => a.name.localeCompare(b.name)); - } -}); - -document.getElementById("sortByDateBtn").addEventListener("click", async function () { - if (currentSort.field === "lastModified" && !currentSort.descending) { - currentSort.descending = true; - await sortFiles((a, b) => b.lastModified - a.lastModified); - } else { - currentSort.field = "lastModified"; - currentSort.descending = false; - await sortFiles((a, b) => a.lastModified - b.lastModified); - } -}); - -async function sortFiles(comparator) { - // Convert FileList to array and sort - const sortedFilesArray = Array.from(document.getElementById("fileInput-input").files).sort(comparator); - - // Refresh displayed list (wait for it to complete since it's async) - await displayFiles(sortedFilesArray); - - // Update the file input and fileOrder based on the current display order - // This ensures consistency between display and file input - updateFiles(); -} - -function updateFiles() { - var dataTransfer = new DataTransfer(); - var liElements = document.querySelectorAll("#selectedFiles li"); - const files = document.getElementById("fileInput-input").files; - - console.log("updateFiles: found", liElements.length, "LI elements and", files.length, "files"); - - for (var i = 0; i < liElements.length; i++) { - var fileNameFromList = liElements[i].querySelector(".filename").innerText; - var found = false; - for (var j = 0; j < files.length; j++) { - var file = files[j]; - if (file.name === fileNameFromList) { - dataTransfer.items.add(file); - found = true; - break; - } - } - if (!found) { - console.warn("updateFiles: Could not find file:", fileNameFromList); - } - } - - document.getElementById("fileInput-input").files = dataTransfer.files; - console.log("updateFiles: Updated file input with", dataTransfer.files.length, "files"); - - // Also populate hidden fileOrder to preserve visible order - const order = Array.from(liElements) - .map((li) => li.querySelector(".filename").innerText) - .join("\n"); - const orderInput = document.getElementById("fileOrder"); - if (orderInput) { - orderInput.value = order; - console.log("Updated fileOrder:", order); - } -} - -document.querySelector("#resetFileInputBtn").addEventListener("click", ()=>{ - let formElement = document.querySelector("#fileInput-input"); - formElement.value = ''; - clearLiElements(); - updateFiles(); - -}); - -function clearLiElements(){ - let listGroupItemNodeList = document.querySelectorAll(".list-group-item"); - for (let i = 0; i < listGroupItemNodeList.length; i++) { - listGroupItemNodeList[i].remove(); - }; - } diff --git a/app/core/src/main/resources/static/js/multitool/DragDropManager.js b/app/core/src/main/resources/static/js/multitool/DragDropManager.js deleted file mode 100644 index 7cbe97ab0..000000000 --- a/app/core/src/main/resources/static/js/multitool/DragDropManager.js +++ /dev/null @@ -1,209 +0,0 @@ -class DragDropManager { - constructor(id, wrapperId) { - this.dragContainer = document.getElementById(id); - this.pageDirection = document.documentElement.getAttribute('dir'); - this.wrapper = document.getElementById(wrapperId); - this.pageDragging = false; - this.hoveredEl = undefined; - this.draggedImageEl = undefined; - this.draggedEl = undefined; - this.selectedPageElements = []; // Store selected pages for multi-page mode - this.elementTimeouts = new Map(); - - // Add CSS dynamically - const styleElement = document.createElement('link'); - styleElement.rel = 'stylesheet'; - styleElement.href = 'css/dragdrop.css'; - document.head.appendChild(styleElement); - - // Create the endpoint element - const div = document.createElement('div'); - div.classList.add('page-container'); - div.classList.add('drag-manager_endpoint'); - div.innerHTML = ` - - - `; - this.endInsertionElement = div; - - // Bind methods - this.startDraggingPage = this.startDraggingPage.bind(this); - this.onDragEl = this.onDragEl.bind(this); - this.stopDraggingPage = this.stopDraggingPage.bind(this); - - this.adapt(div); - } - - startDraggingPage(div) { - if (window.selectPage) { - // Multi-page drag logic - this.selectedPageElements = window.selectedPages - .map((index) => { - const pageEl = Array.from(this.wrapper.childNodes)[index]; - if (pageEl) { - pageEl.initialTransform = pageEl.style.transform || 'translate(0px, 0px)'; - pageEl.classList.add('drag-manager_dragging'); - } - return pageEl; - }) - .filter(Boolean); - - if (this.selectedPageElements.length === 0) return; - - this.pageDragging = true; - this.draggedImageEl = document.createElement('div'); - this.draggedImageEl.classList.add('multidrag'); - this.draggedImageEl.textContent = `${this.selectedPageElements.length} ${window.translations.dragDropMessage}`; - this.draggedImageEl.style.visibility = 'hidden'; - - this.dragContainer.appendChild(this.draggedImageEl); - } else { - // Single-page drag logic - this.pageDragging = true; - this.draggedEl = div; - const img = div.querySelector('img'); - div.classList.add('drag-manager_dragging'); - div.classList.remove('moved-element', 'remove'); - const imgEl = document.createElement('img'); - imgEl.classList.add('dragged-img'); - imgEl.src = img.src; - imgEl.style.visibility = 'hidden'; - imgEl.style.transform = `rotate(${img.style.rotate === '' ? '0deg' : img.style.rotate}) translate(-50%, -50%)`; - this.draggedImageEl = imgEl; - this.dragContainer.appendChild(imgEl); - } - - // Common setup for both modes - window.addEventListener('mouseup', this.stopDraggingPage); - window.addEventListener('mousemove', this.onDragEl); - this.wrapper.classList.add('drag-manager_dragging-container'); - this.wrapper.appendChild(this.endInsertionElement); - } - - onDragEl(mouseEvent) { - const { clientX, clientY } = mouseEvent; - if (this.draggedImageEl) { - this.draggedImageEl.style.visibility = 'visible'; - this.draggedImageEl.style.left = `${clientX}px`; - this.draggedImageEl.style.top = `${clientY}px`; - } - } - - stopDraggingPage() { - window.removeEventListener('mousemove', this.onDragEl); - this.wrapper.classList.remove('drag-manager_dragging-container'); - this.wrapper.removeChild(this.endInsertionElement); - window.removeEventListener('mouseup', this.stopDraggingPage); - - if (this.draggedImageEl) { - this.dragContainer.removeChild(this.draggedImageEl); - this.draggedImageEl = undefined; - } - - if (window.selectPage) { - // Multi-page drop logic - if ( - !this.hoveredEl || - !this.hoveredEl.classList.contains('page-container') || - this.selectedPageElements.includes(this.hoveredEl) - ) { - this.selectedPageElements.forEach((pageEl) => { - pageEl.style.transform = pageEl.initialTransform || 'translate(0px, 0px)'; - pageEl.classList.remove('drag-manager_dragging'); - }); - } else { - this.selectedPageElements.forEach((pageEl) => { - pageEl.classList.remove('drag-manager_dragging'); - }); - - this.movePageTo( - this.selectedPageElements, - this.hoveredEl === this.endInsertionElement - ? null - : this.hoveredEl); - - this.selectedPageElements.forEach((pageEl) => { - // Handle timeout for the current element - this.handleTimeoutForElement(pageEl); - }); - } - this.selectedPageElements = []; - window.resetPages(); - } else { - // Single-page drop logic - if ( - !this.hoveredEl || - !this.hoveredEl.classList.contains('page-container') || - this.hoveredEl === this.draggedEl - ) { - this.draggedEl.style.transform = this.draggedEl.initialTransform || 'translate(0px, 0px)'; - this.draggedEl.classList.remove('drag-manager_dragging'); - return; - } - - this.draggedEl.classList.remove('drag-manager_dragging'); - - if (this.hoveredEl === this.endInsertionElement) { - this.movePageTo(this.draggedEl); - } else { - this.movePageTo(this.draggedEl, this.hoveredEl); - } - - // Handle timeout for the current element - this.handleTimeoutForElement(this.draggedEl); - } - - this.pageDragging = false; - } - - // Helper function to manage independent timeouts - handleTimeoutForElement(element) { - // Clear existing timeout if present - if (this.elementTimeouts.has(element)) { - clearTimeout(this.elementTimeouts.get(element)); - } - - // Add the moved-element class and set a new timeout - element.classList.remove('remove'); - element.classList.add('moved-element'); - - const timeoutId = setTimeout(() => { - element.classList.add('remove'); - this.elementTimeouts.delete(element); // Cleanup the timeout map - }, 2000); - - // Store the timeout ID for this element - this.elementTimeouts.set(element, timeoutId); - } - - setActions({ movePageTo }) { - this.movePageTo = movePageTo; - } - - adapt(div) { - const onDragStart = (e) => { - e.preventDefault(); - this.startDraggingPage(div); - }; - - const onMouseEnter = () => { - if (this.pageDragging) { - this.hoveredEl = div; - div.classList.add('drag-manager_draghover'); - } - }; - - const onMouseLeave = () => { - this.hoveredEl = undefined; - div.classList.remove('drag-manager_draghover'); - }; - - div.addEventListener('dragstart', onDragStart); - div.addEventListener('mouseenter', onMouseEnter); - div.addEventListener('mouseleave', onMouseLeave); - - return div; - } -} - -export default DragDropManager; diff --git a/app/core/src/main/resources/static/js/multitool/ImageHighlighter.js b/app/core/src/main/resources/static/js/multitool/ImageHighlighter.js deleted file mode 100644 index 4b914ea9d..000000000 --- a/app/core/src/main/resources/static/js/multitool/ImageHighlighter.js +++ /dev/null @@ -1,47 +0,0 @@ -class ImageHighlighter { - imageHighlighter; - constructor(id) { - this.imageHighlighter = document.getElementById(id); - this.imageHighlightCallback = this.imageHighlightCallback.bind(this); - - var styleElement = document.createElement("link"); - styleElement.rel = "stylesheet"; - styleElement.href = "css/imageHighlighter.css"; - - document.head.appendChild(styleElement); - - this.imageHighlighter.onclick = () => { - this.imageHighlighter.childNodes.forEach((child) => { - child.classList.add("remove"); - setTimeout(() => { - this.imageHighlighter.removeChild(child); - }, 100); - }); - }; - } - - imageHighlightCallback(highlightEvent) { - var bigImg = document.createElement("img"); - bigImg.onclick = (imageClickEvent) => { - // This prevents the highlighter's onClick from closing the image when clicking - // on the image instead of next to it. - imageClickEvent.preventDefault(); - imageClickEvent.stopPropagation(); - }; - bigImg.src = highlightEvent.target.src; - bigImg.style.rotate = highlightEvent.target.style.rotate; - this.imageHighlighter.appendChild(bigImg); - } - - setActions() { - // not needed in this case - } - - adapt(div) { - const img = div.querySelector(".page-image"); - img.addEventListener("click", this.imageHighlightCallback); - return div; - } -} - -export default ImageHighlighter; diff --git a/app/core/src/main/resources/static/js/multitool/PdfActionsManager.js b/app/core/src/main/resources/static/js/multitool/PdfActionsManager.js deleted file mode 100644 index 14cb31006..000000000 --- a/app/core/src/main/resources/static/js/multitool/PdfActionsManager.js +++ /dev/null @@ -1,294 +0,0 @@ -import { DeletePageCommand } from "./commands/delete-page.js"; -import { DuplicatePageCommand } from "./commands/duplicate-page.js"; -import { SelectPageCommand } from "./commands/select.js"; -import { SplitFileCommand } from "./commands/split.js"; -import { UndoManager } from "./UndoManager.js"; - -class PdfActionsManager { - pageDirection; - pagesContainer; - static selectedPages = []; // Static property shared across all instances - undoManager; - - constructor(id, undoManager) { - this.pagesContainer = document.getElementById(id); - this.pageDirection = document.documentElement.getAttribute("dir"); - - this.undoManager = undoManager || new UndoManager(); - - var styleElement = document.createElement("link"); - styleElement.rel = "stylesheet"; - styleElement.href = "css/pdfActions.css"; - - document.head.appendChild(styleElement); - } - - getPageContainer(element) { - var container = element; - while (!container.classList.contains("page-container")) { - container = container.parentNode; - } - return container; - } - - moveUpButtonCallback(e) { - var imgContainer = this.getPageContainer(e.target); - - const sibling = imgContainer.previousSibling; - if (sibling) { - this.movePageTo(imgContainer, sibling, true); - } - } - - moveDownButtonCallback(e) { - var imgContainer = this.getPageContainer(e.target); - const sibling = imgContainer.nextSibling; - if (sibling) { - this.movePageTo( - imgContainer, - sibling.nextSibling, - true - ); - } - } - - rotateCCWButtonCallback(e) { - var imgContainer = this.getPageContainer(e.target); - const img = imgContainer.querySelector("img"); - - let rotateCommand = this.rotateElement(img, -90); - this._pushUndoClearRedo(rotateCommand); - } - - rotateCWButtonCallback(e) { - var imgContainer = this.getPageContainer(e.target); - const img = imgContainer.querySelector("img"); - - let rotateCommand = this.rotateElement(img, 90); - this._pushUndoClearRedo(rotateCommand); - } - - deletePageButtonCallback(e) { - let imgContainer = this.getPageContainer(e.target); - let deletePageCommand = new DeletePageCommand( - imgContainer, - this.pagesContainer - ); - deletePageCommand.execute(); - - this._pushUndoClearRedo(deletePageCommand); - } - - duplicatePageButtonCallback(e) { - let imgContainer = this.getPageContainer(e.target); - let duplicatePageCommand = new DuplicatePageCommand( - imgContainer, - this.duplicatePage, - this.pagesContainer - ); - duplicatePageCommand.execute(); - - this._pushUndoClearRedo(duplicatePageCommand); - } - - insertFileButtonCallback(e) { - var imgContainer = this.getPageContainer(e.target); - this.addFiles(imgContainer); - } - - insertFileBlankButtonCallback(e) { - var imgContainer = this.getPageContainer(e.target); - this.addFiles(imgContainer, true); - } - - splitFileButtonCallback(e) { - var imgContainer = this.getPageContainer(e.target); - - let splitFileCommand = new SplitFileCommand(imgContainer, "split-before"); - splitFileCommand.execute(); - - this._pushUndoClearRedo(splitFileCommand); - } - - _pushUndoClearRedo(command) { - this.undoManager.pushUndoClearRedo(command); - } - - setActions({ movePageTo, addFiles, rotateElement, duplicatePage }) { - this.movePageTo = movePageTo; - this.addFiles = addFiles; - this.rotateElement = rotateElement; - this.duplicatePage = duplicatePage; - - this.moveUpButtonCallback = this.moveUpButtonCallback.bind(this); - this.moveDownButtonCallback = this.moveDownButtonCallback.bind(this); - this.rotateCCWButtonCallback = this.rotateCCWButtonCallback.bind(this); - this.rotateCWButtonCallback = this.rotateCWButtonCallback.bind(this); - this.deletePageButtonCallback = this.deletePageButtonCallback.bind(this); - this.insertFileButtonCallback = this.insertFileButtonCallback.bind(this); - this.insertFileBlankButtonCallback = this.insertFileBlankButtonCallback.bind(this); - this.splitFileButtonCallback = this.splitFileButtonCallback.bind(this); - this.duplicatePageButtonCallback = this.duplicatePageButtonCallback.bind(this); - } - - - adapt(div) { - div.classList.add("pdf-actions_container"); - const leftDirection = this.pageDirection === "rtl" ? "right" : "left"; - const rightDirection = this.pageDirection === "rtl" ? "left" : "right"; - const buttonContainer = document.createElement("div"); - - buttonContainer.classList.add("btn-group", "pdf-actions_button-container", "hide-on-drag"); - - const moveUp = document.createElement("button"); - moveUp.classList.add("pdf-actions_move-left-button", "btn", "btn-secondary"); - moveUp.setAttribute('title', window.translations.moveLeft); - moveUp.innerHTML = `arrow_${leftDirection}_alt`; - moveUp.onclick = this.moveUpButtonCallback; - buttonContainer.appendChild(moveUp); - - const moveDown = document.createElement("button"); - moveDown.classList.add("pdf-actions_move-right-button", "btn", "btn-secondary"); - moveDown.setAttribute('title', window.translations.moveRight); - moveDown.innerHTML = `arrow_${rightDirection}_alt`; - moveDown.onclick = this.moveDownButtonCallback; - buttonContainer.appendChild(moveDown); - - - const rotateCCW = document.createElement("button"); - rotateCCW.classList.add("btn", "btn-secondary"); - rotateCCW.setAttribute('title', window.translations.rotateLeft); - rotateCCW.innerHTML = `rotate_left`; - rotateCCW.onclick = this.rotateCCWButtonCallback; - buttonContainer.appendChild(rotateCCW); - - const rotateCW = document.createElement("button"); - rotateCW.classList.add("btn", "btn-secondary"); - rotateCW.setAttribute('title', window.translations.rotateRight); - rotateCW.innerHTML = `rotate_right`; - rotateCW.onclick = this.rotateCWButtonCallback; - buttonContainer.appendChild(rotateCW); - - const duplicatePage = document.createElement("button"); - duplicatePage.classList.add("btn", "btn-secondary"); - duplicatePage.setAttribute('title', window.translations.duplicate); - duplicatePage.innerHTML = `control_point_duplicate`; - duplicatePage.onclick = this.duplicatePageButtonCallback; - buttonContainer.appendChild(duplicatePage); - - const deletePage = document.createElement("button"); - deletePage.classList.add("btn", "btn-danger"); - deletePage.setAttribute('title', window.translations.delete); - deletePage.innerHTML = `delete`; - deletePage.onclick = this.deletePageButtonCallback; - buttonContainer.appendChild(deletePage); - - div.appendChild(buttonContainer); - - //enerate checkbox to select individual pages - const selectCheckbox = document.createElement("input"); - selectCheckbox.type = "checkbox"; - selectCheckbox.classList.add("pdf-actions_checkbox", "form-check-input"); - selectCheckbox.id = `selectPageCheckbox`; - selectCheckbox.checked = window.selectAll; - - div.appendChild(selectCheckbox); - - //only show whenpage select mode is active - if (!window.selectPage) { - selectCheckbox.classList.add("hidden"); - } else { - selectCheckbox.classList.remove("hidden"); - } - - selectCheckbox.onchange = () => { - const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1; - let selectPageCommand = new SelectPageCommand(pageNumber, selectCheckbox); - selectPageCommand.execute(); - }; - - const insertFileButtonContainer = document.createElement("div"); - - insertFileButtonContainer.classList.add( - "pdf-actions_insert-file-button-container", - leftDirection, - `align-center-${leftDirection}`, - ); - - const insertFileButton = document.createElement("button"); - insertFileButton.classList.add("btn", "btn-primary"); - insertFileButton.setAttribute('title', window.translations.addFile); - insertFileButton.innerHTML = `add`; - insertFileButton.onclick = this.insertFileButtonCallback; - insertFileButtonContainer.appendChild(insertFileButton); - - const splitFileButton = document.createElement("button"); - splitFileButton.classList.add("btn", "btn-primary"); - splitFileButton.setAttribute('title', window.translations.split); - splitFileButton.innerHTML = `cut`; - splitFileButton.onclick = this.splitFileButtonCallback; - insertFileButtonContainer.appendChild(splitFileButton); - - const insertFileBlankButton = document.createElement("button"); - insertFileBlankButton.classList.add("btn", "btn-primary"); - insertFileBlankButton.setAttribute('title', window.translations.insertPageBreak); - insertFileBlankButton.innerHTML = `insert_page_break`; - insertFileBlankButton.onclick = this.insertFileBlankButtonCallback; - insertFileButtonContainer.appendChild(insertFileBlankButton); - - div.appendChild(insertFileButtonContainer); - - // add this button to every element, but only show it on the last one :D - const insertFileButtonRightContainer = document.createElement("div"); - insertFileButtonRightContainer.classList.add( - "pdf-actions_insert-file-button-container", - rightDirection, - `align-center-${rightDirection}`, - ); - - const insertFileButtonRight = document.createElement("button"); - insertFileButtonRight.classList.add("btn", "btn-primary"); - insertFileButtonRight.innerHTML = `add`; - insertFileButtonRight.onclick = () => addFiles(); - insertFileButtonRightContainer.appendChild(insertFileButtonRight); - - div.appendChild(insertFileButtonRightContainer); - - const adaptPageNumber = (pageNumber, div) => { - const pageNumberElement = document.createElement("span"); - pageNumberElement.classList.add("page-number"); - pageNumberElement.textContent = pageNumber; - - div.insertBefore(pageNumberElement, div.firstChild); - }; - - div.addEventListener("mouseenter", () => { - window.updatePageNumbersAndCheckboxes(); - const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1; - adaptPageNumber(pageNumber, div); - const checkbox = document.getElementById(`selectPageCheckbox-${pageNumber}`); - if (checkbox && !window.selectPage) { - checkbox.classList.remove("hidden"); - } - }); - - div.addEventListener("mouseleave", () => { - const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1; - const pageNumberElement = div.querySelector(".page-number"); - if (pageNumberElement) { - div.removeChild(pageNumberElement); - } - const checkbox = document.getElementById(`selectPageCheckbox-${pageNumber}`); - if (checkbox && !window.selectPage) { - checkbox.classList.add("hidden"); - } - }); - - document.addEventListener("selectedPagesUpdated", () => { - window.updateSelectedPagesDisplay(); - }); - return div; - } -} - -export default PdfActionsManager; diff --git a/app/core/src/main/resources/static/js/multitool/PdfContainer.js b/app/core/src/main/resources/static/js/multitool/PdfContainer.js deleted file mode 100644 index 866fd69fa..000000000 --- a/app/core/src/main/resources/static/js/multitool/PdfContainer.js +++ /dev/null @@ -1,1160 +0,0 @@ -import { MovePageCommand } from './commands/move-page.js'; -import { RemoveSelectedCommand } from './commands/remove.js'; -import { RotateAllCommand, RotateElementCommand } from './commands/rotate.js'; -import { SplitAllCommand } from './commands/split.js'; -import { UndoManager } from './UndoManager.js'; -import { PageBreakCommand } from './commands/page-break.js'; -import { AddFilesCommand } from './commands/add-page.js'; -import { DecryptFile } from '../DecryptFiles.js'; -import { CommandSequence } from './commands/commands-sequence.js'; - -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -const isSvgFile = (file) => { - if (!file) return false; - const type = (file.type || '').toLowerCase(); - if (type === 'image/svg+xml') { - return true; - } - const name = (file.name || '').toLowerCase(); - return name.endsWith('.svg'); -}; - -const partitionSvgFiles = (files = []) => { - const allowed = []; - const rejected = []; - - files.forEach((file) => { - if (isSvgFile(file)) { - rejected.push(file); - } else { - allowed.push(file); - } - }); - - return { allowed, rejected }; -}; - -const notifySvgUnsupported = (files = []) => { - if (!files.length) return; - if (!window.location.pathname?.includes('multi-tool')) return; - - const message = window.multiTool?.svgNotSupported || - 'SVG files are not supported in Multi Tool and were ignored.'; - const names = files - .map((file) => file?.name) - .filter(Boolean) - .join(', '); - - if (names) { - alert(`${message}\n${names}`); - } else { - alert(message); - } -}; - -class PdfContainer { - fileName; - pagesContainer; - pagesContainerWrapper; - pdfAdapters; - downloadLink; - undoManager; - - constructor(id, wrapperId, pdfAdapters, undoManager) { - this.pagesContainer = document.getElementById(id); - this.pagesContainerWrapper = document.getElementById(wrapperId); - this.downloadLink = null; - this.movePageTo = this.movePageTo.bind(this); - this.addFiles = this.addFiles.bind(this); - this.addFilesFromFiles = this.addFilesFromFiles.bind(this); - this.rotateElement = this.rotateElement.bind(this); - this.rotateAll = this.rotateAll.bind(this); - this.exportPdf = this.exportPdf.bind(this); - this.updateFilename = this.updateFilename.bind(this); - this.setDownloadAttribute = this.setDownloadAttribute.bind(this); - this.preventIllegalChars = this.preventIllegalChars.bind(this); - this.addImageFile = this.addImageFile.bind(this); - this.duplicatePage = this.duplicatePage.bind(this); - this.nameAndArchiveFiles = this.nameAndArchiveFiles.bind(this); - this.splitPDF = this.splitPDF.bind(this); - this.splitAll = this.splitAll.bind(this); - this.deleteSelected = this.deleteSelected.bind(this); - this.selectAll = this.selectAll.bind(this); - this.deselectAll = this.deselectAll.bind(this); - this.updateSelectedPagesDisplay = this.updateSelectedPagesDisplay.bind(this); - this.toggleSelectPageVisibility = this.toggleSelectPageVisibility.bind(this); - this.updatePagesFromCSV = this.updatePagesFromCSV.bind(this); - this.addFilesBlankAll = this.addFilesBlankAll.bind(this); - this.removeAllElements = this.removeAllElements.bind(this); - this.resetPages = this.resetPages.bind(this); - - this.decryptFile = new DecryptFile(); - - this.undoManager = undoManager || new UndoManager(); - - this.pdfAdapters = pdfAdapters; - - this.pdfAdapters.forEach((adapter) => { - adapter.setActions({ - movePageTo: this.movePageTo, - addFiles: this.addFiles, - rotateElement: this.rotateElement, - updateFilename: this.updateFilename, - deleteSelected: this.deleteSelected, - duplicatePage: this.duplicatePage, - }); - }); - - window.addFiles = this.addFiles; - window.exportPdf = this.exportPdf; - window.rotateAll = this.rotateAll; - window.splitAll = this.splitAll; - window.deleteSelected = this.deleteSelected; - window.selectAll = this.selectAll; - window.deselectAll = this.deselectAll; - window.updateSelectedPagesDisplay = this.updateSelectedPagesDisplay; - window.toggleSelectPageVisibility = this.toggleSelectPageVisibility; - window.updatePagesFromCSV = this.updatePagesFromCSV; - window.updateSelectedPagesDisplay = this.updateSelectedPagesDisplay; - window.updatePageNumbersAndCheckboxes = this.updatePageNumbersAndCheckboxes; - window.addFilesBlankAll = this.addFilesBlankAll; - window.removeAllElements = this.removeAllElements; - window.resetPages = this.resetPages; - - let undoBtn = document.getElementById('undo-btn'); - let redoBtn = document.getElementById('redo-btn'); - - document.addEventListener('undo-manager-update', (e) => { - let canUndo = e.detail.canUndo; - let canRedo = e.detail.canRedo; - - undoBtn.disabled = !canUndo; - redoBtn.disabled = !canRedo; - }); - - window.undo = () => { - if (undoManager.canUndo()) undoManager.undo(); - else { - undoBtn.disabled = !undoManager.canUndo(); - redoBtn.disabled = !undoManager.canRedo(); - } - }; - - window.redo = () => { - if (undoManager.canRedo()) undoManager.redo(); - else { - undoBtn.disabled = !undoManager.canUndo(); - redoBtn.disabled = !undoManager.canRedo(); - } - }; - - const filenameInput = document.getElementById('filename-input'); - const downloadBtn = document.getElementById('export-button'); - - filenameInput.onkeyup = this.updateFilename; - filenameInput.onkeydown = this.preventIllegalChars; - filenameInput.disabled = false; - filenameInput.innerText = ''; - downloadBtn.disabled = true; - } - - movePagesTo(startElements, endElement, scrollTo = false) { - let commands = []; - startElements.forEach((page) => { - let command = new MovePageCommand( - page, - endElement, - this.pagesContainer, - this.pagesContainerWrapper, - scrollTo - ) - command.execute(); - commands.push(command); - }) - - let commandSequence = new CommandSequence(commands); - this.undoManager.pushUndoClearRedo(commandSequence); - return commandSequence; - } - - showButton(button, show) { - button.classList.toggle('hidden', !show); - } - - movePageTo(startElements, endElement, scrollTo = false) { - - if (Array.isArray(startElements)){ - return this.movePagesTo(startElements, endElement, scrollTo = false); - } - - let movePageCommand = new MovePageCommand( - startElements, - endElement, - this.pagesContainer, - this.pagesContainerWrapper, - scrollTo - ); - - movePageCommand.execute(); - this.undoManager.pushUndoClearRedo(movePageCommand); - return movePageCommand; - } - - /** - * Adds files or a single blank page (when blank=true) near an anchor element. - * @param {HTMLElement|null} element - Anchor element (insert before its nextSibling). - * @param {boolean} [blank=false] - When true, insert a single blank page. - */ - async addFiles(element, blank = false) { - // Choose the action: real file picker or blank page generator. - const action = blank - ? async (nextSiblingElement) => { - // Create exactly one blank page and return the created elements array. - const pages = await this.addFilesBlank(nextSiblingElement, []); - return pages; // array of inserted elements - } - : this.addFilesAction.bind(this); - - const addFilesCommand = new AddFilesCommand( - element, - window.selectedPages, - action, - this.pagesContainer - ); - - await addFilesCommand.execute(); - this.undoManager.pushUndoClearRedo(addFilesCommand); - window.tooltipSetup(); - } - - async addFilesAction(nextSiblingElement) { - let pages = []; - return new Promise((resolve) => { - var input = document.createElement('input'); - input.type = 'file'; - input.multiple = true; - input.setAttribute('accept', 'application/pdf,image/*'); - - input.onchange = async (e) => { - const files = e.target.files; - if (files.length > 0) { - const { - pages: updatedPages, - acceptedFileCount, - } = await this.addFilesFromFiles(files, nextSiblingElement, pages); - - pages = updatedPages; - - if (acceptedFileCount > 0) { - this.updateFilename(); - } - - if (window.selectPage && acceptedFileCount > 0) { - this.showButton(document.getElementById('select-pages-container'), true); - } - } - resolve(pages); - }; - - input.click(); - }); - } - - async handleDroppedFiles(files, nextSiblingElement = null) { - if (files.length > 0) { - const { - pages, - acceptedFileCount, - } = await this.addFilesFromFiles(files, nextSiblingElement, []); - - if (acceptedFileCount > 0) { - this.updateFilename(); - - if (window.selectPage) { - this.showButton(document.getElementById('select-pages-container'), true); - } - } - - return pages; - } - } - - async addFilesFromFiles(files, nextSiblingElement, pages) { - this.fileName = files[0].name; - const fileArray = Array.from(files || []); - const { allowed: allowedFiles, rejected: rejectedSvgFiles } = partitionSvgFiles(fileArray); - - if (allowedFiles.length > 0) { - this.fileName = allowedFiles[0].name || 'untitled'; - } - - let acceptedFileCount = 0; - - for (let i = 0; i < allowedFiles.length; i++) { - const file = allowedFiles[i]; - const startTime = Date.now(); - let processingTime, - errorMessage = null, - pageCount = 0; - - try { - let decryptedFile = file; - let isEncrypted = false; - let requiresPassword = false; - await this.decryptFile - .checkFileEncrypted(decryptedFile) - .then((result) => { - isEncrypted = result.isEncrypted; - requiresPassword = result.requiresPassword; - }) - .catch((error) => { - console.error(error); - }); - if (decryptedFile.type === 'application/pdf' && isEncrypted) { - decryptedFile = await this.decryptFile.decryptFile(decryptedFile, requiresPassword); - if (!decryptedFile) { - throw new Error('File decryption failed.'); - } - } - - if (decryptedFile.type === 'application/pdf') { - const { renderer, pdfDocument } = await this.loadFile(decryptedFile); - pageCount = renderer.pageCount || 0; - pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages); - } else if (decryptedFile.type.startsWith('image/')) { - pages = await this.addImageFile(decryptedFile, nextSiblingElement, pages); - } - - processingTime = Date.now() - startTime; - this.captureFileProcessingEvent(true, decryptedFile, processingTime, null, pageCount); - acceptedFileCount++; - } catch (error) { - processingTime = Date.now() - startTime; - errorMessage = error.message || 'Unknown error'; - this.captureFileProcessingEvent(false, file, processingTime, errorMessage, pageCount); - - if (isSvgFile(file)) { - rejectedSvgFiles.push(file); - } - } - } - - if (rejectedSvgFiles.length > 0) { - notifySvgUnsupported(rejectedSvgFiles); - } - - document.querySelectorAll('.enable-on-file').forEach((element) => { - element.disabled = false; - }); - - return { pages, acceptedFileCount }; - } - - captureFileProcessingEvent(success, file, processingTime, errorMessage, pageCount) { - try { - if (analyticsEnabled) { - posthog.capture('file_processing', { - success, - file_type: file?.type || 'unknown', - file_size: file?.size || 0, - processing_time: processingTime, - error_message: errorMessage, - pdf_pages: pageCount, - }); - } - - // Increment PDF processing count for survey tracking - if (success && typeof window.incrementPdfProcessingCount === 'function') { - window.incrementPdfProcessingCount(); - } - } catch { } - } - - async addFilesBlank(nextSiblingElement, pages) { - let doc = await PDFLib.PDFDocument.create(); - let docBytes = await doc.save(); - - const url = URL.createObjectURL(new Blob([docBytes], { type: 'application/pdf' })); - - const renderer = await this.toRenderer(url); - pages = await this.addPdfFile(renderer, doc, nextSiblingElement, pages); - return pages; - } - - rotateElement(element, deg) { - let rotateCommand = new RotateElementCommand(element, deg); - rotateCommand.execute(); - - return rotateCommand; - } - - async addPdfFile(renderer, pdfDocument, nextSiblingElement, pages) { - for (var i = 0; i < renderer.pageCount; i++) { - const div = document.createElement('div'); - - div.classList.add('page-container'); - div.id = 'page-container-' + (i + 1); - var img = document.createElement('img'); - img.classList.add('page-image'); - const imageSrc = await renderer.renderPage(i); - img.src = imageSrc; - img.pageIdx = i; - img.rend = renderer; - img.doc = pdfDocument; - div.appendChild(img); - - this.pdfAdapters.forEach((adapter) => { - adapter.adapt?.(div); - }); - - if (nextSiblingElement) { - this.pagesContainer.insertBefore(div, nextSiblingElement); - } else { - this.pagesContainer.appendChild(div); - } - - pages.push(div); - } - - return pages; - } - - async addImageFile(file, nextSiblingElement, pages) { - // Ensure the provided file is a safe image type to prevent DOM XSS when - // rendering user-supplied content. Reject SVG and non-image files that could - // contain executable scripts. - if (!(file instanceof File) || !file.type.startsWith('image/') || file.type === 'image/svg+xml') { - throw new Error('Invalid image file'); - } - const div = document.createElement('div'); - div.classList.add('page-container'); - - const img = document.createElement('img'); - img.classList.add('page-image'); - const objectUrl = URL.createObjectURL(file); - img.src = objectUrl; - img.onload = () => URL.revokeObjectURL(objectUrl); - div.appendChild(img); - - this.pdfAdapters.forEach((adapter) => { - adapter.adapt?.(div); - }); - if (nextSiblingElement) { - this.pagesContainer.insertBefore(div, nextSiblingElement); - } else { - this.pagesContainer.appendChild(div); - } - pages.push(div); - return pages; - } - - duplicatePage(element) { - const clone = document.createElement('div'); - clone.classList.add('page-container'); - - const originalImg = element.querySelector('img'); - const img = document.createElement('img'); - img.classList.add('page-image'); - img.src = originalImg.src; - img.pageIdx = originalImg.pageIdx; - img.rend = originalImg.rend; - img.doc = originalImg.doc; - img.style.rotate = originalImg.style.rotate; - clone.appendChild(img); - - this.pdfAdapters.forEach((adapter) => { - adapter?.adapt?.(clone); - }); - - const nextSibling = element.nextSibling; - this.pagesContainer.insertBefore(clone, nextSibling); - this.updatePageNumbersAndCheckboxes(); - return clone; - } - - async loadFile(file) { - var objectUrl = URL.createObjectURL(file); - var pdfDocument = await this.toPdfLib(objectUrl); - var renderer = await this.toRenderer(objectUrl); - return { renderer, pdfDocument }; - } - - async toRenderer(objectUrl) { - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - const pdf = await pdfjsLib.getDocument({ - url: objectUrl, - ...PDFJS_DEFAULT_OPTIONS, - }).promise; - return { - document: pdf, - pageCount: pdf.numPages, - renderPage: async function (pageIdx) { - const page = await this.document.getPage(pageIdx + 1); - - const canvas = document.createElement('canvas'); - - // set the canvas size to the size of the page - if (page.rotate == 90 || page.rotate == 270) { - canvas.width = page.view[3]; - canvas.height = page.view[2]; - } else { - canvas.width = page.view[2]; - canvas.height = page.view[3]; - } - - // render the page onto the canvas - var renderContext = { - canvasContext: canvas.getContext('2d'), - viewport: page.getViewport({ scale: 1 }), - }; - - await page.render(renderContext).promise; - return canvas.toDataURL(); - }, - }; - } - - async toPdfLib(objectUrl) { - const existingPdfBytes = await fetch(objectUrl).then((res) => res.arrayBuffer()); - const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { - ignoreEncryption: true, - }); - return pdfDoc; - } - - rotateAll(deg) { - let elementsToRotate = []; - for (let i = 0; i < this.pagesContainer.childNodes.length; i++) { - const child = this.pagesContainer.children[i]; - if (!child) continue; - - const pageIndex = i + 1; - //if in page select mode is active rotate only selected pages - if (window.selectPage && !window.selectedPages.includes(pageIndex)) continue; - - const img = child.querySelector('img'); - if (!img) continue; - - elementsToRotate.push(img); - } - - let rotateAllCommand = new RotateAllCommand(elementsToRotate, deg); - rotateAllCommand.execute(); - - this.undoManager.pushUndoClearRedo(rotateAllCommand); - } - - removeAllElements() { - let pageContainerNodeList = document.querySelectorAll('.page-container'); - for (var i = 0; i < pageContainerNodeList.length; i++) { - pageContainerNodeList[i].remove(); - } - document.querySelectorAll('.enable-on-file').forEach((element) => { - element.disabled = true; - }); - } - - deleteSelected() { - window.selectedPages.sort((a, b) => a - b); - let removeSelectedCommand = new RemoveSelectedCommand( - this.pagesContainer, - window.selectedPages, - this.updatePageNumbersAndCheckboxes - ); - removeSelectedCommand.execute(); - this.undoManager.pushUndoClearRedo(removeSelectedCommand); - } - - selectAll() { - const checkboxes = document.querySelectorAll('.pdf-actions_checkbox'); - const selectIcon = document.getElementById('select-All-Container'); - const deselectIcon = document.getElementById('deselect-All-Container'); - - this.showButton(selectIcon, false); - this.showButton(deselectIcon, true); - - checkboxes.forEach((checkbox) => { - checkbox.checked = true; - - const pageNumber = Array.from(checkbox.parentNode.parentNode.children).indexOf(checkbox.parentNode) + 1; - - if (!window.selectedPages.includes(pageNumber)) { - window.selectedPages.push(pageNumber); - } - }); - - this.updateSelectedPagesDisplay(); - } - - deselectAll() { - const checkboxes = document.querySelectorAll('.pdf-actions_checkbox'); - const selectIcon = document.getElementById('select-All-Container'); - const deselectIcon = document.getElementById('deselect-All-Container'); - - this.showButton(selectIcon, true); - this.showButton(deselectIcon, false); - - checkboxes.forEach((checkbox) => { - checkbox.checked = false; - - const pageNumber = Array.from(checkbox.parentNode.parentNode.children).indexOf(checkbox.parentNode) + 1; - - const index = window.selectedPages.indexOf(pageNumber); - if (index !== -1) { - window.selectedPages.splice(index, 1); - } - }); - - this.updateSelectedPagesDisplay(); - } - - parseCSVInput(csvInput, maxPageIndex) { - const pages = new Set(); - - csvInput.split(',').forEach((item) => { - const range = item.split('-').map((p) => parseInt(p.trim())); - if (range.length === 2) { - const [start, end] = range; - for (let i = start; i <= end && i <= maxPageIndex; i++) { - if (i > 0) { - // Ensure the page number is greater than 0 - pages.add(i); - } - } - } else if (range.length === 1 && Number.isInteger(range[0])) { - const page = range[0]; - if (page > 0 && page <= maxPageIndex) { - // Ensure page is within valid range - pages.add(page); - } - } - }); - - return Array.from(pages).sort((a, b) => a - b); - } - - updatePagesFromCSV() { - const csvInput = document.getElementById('csv-input').value; - - const allPages = this.pagesContainer.querySelectorAll('.page-container'); - const maxPageIndex = allPages.length; - - window.selectedPages = this.parseCSVInput(csvInput, maxPageIndex); - - this.updateSelectedPagesDisplay(); - - const allCheckboxes = document.querySelectorAll('.pdf-actions_checkbox'); - allCheckboxes.forEach((checkbox) => { - const page = parseInt(checkbox.getAttribute('data-page-number')); - checkbox.checked = window.selectedPages.includes(page); - }); - } - - formatSelectedPages(pages) { - if (pages.length === 0) return ''; - - pages.sort((a, b) => a - b); // Sort the page numbers in ascending order - const ranges = []; - let start = pages[0]; - let end = start; - - for (let i = 1; i < pages.length; i++) { - if (pages[i] === end + 1) { - // Consecutive page, update end - end = pages[i]; - } else { - // Non-consecutive page, finalize current range - ranges.push(start === end ? `${start}` : `${start}-${end}`); - start = pages[i]; - end = start; - } - } - // Add the last range - ranges.push(start === end ? `${start}` : `${start}-${end}`); - - return ranges.join(', '); - } - - updateSelectedPagesDisplay() { - const selectedPagesList = document.getElementById('selected-pages-list'); - const selectedPagesInput = document.getElementById('csv-input'); - selectedPagesList.innerHTML = ''; // Clear the list - window.selectedPages.sort((a, b) => a - b); - window.selectedPages.forEach((page) => { - const pageItem = document.createElement('div'); - pageItem.className = 'page-item'; - - const pageNumber = document.createElement('span'); - const pagelabel = /*[[#{multiTool.page}]]*/ 'Page'; - pageNumber.className = 'selected-page-number'; - pageNumber.innerText = `${pagelabel} ${page}`; - pageItem.appendChild(pageNumber); - - const removeBtn = document.createElement('span'); - removeBtn.className = 'remove-btn'; - removeBtn.innerHTML = '✕'; - - // Remove page from selected pages list and update display and checkbox - removeBtn.onclick = () => { - window.selectedPages = window.selectedPages.filter((p) => p !== page); - this.updateSelectedPagesDisplay(); - - const checkbox = document.getElementById(`selectPageCheckbox-${page}`); - if (checkbox) { - checkbox.checked = false; - } - }; - - pageItem.appendChild(removeBtn); - selectedPagesList.appendChild(pageItem); - }); - - // Update the input field with the formatted page list - selectedPagesInput.value = this.formatSelectedPages(window.selectedPages); - - const selectIcon = document.getElementById('select-All-Container'); - const deselectIcon = document.getElementById('deselect-All-Container'); - - if (window.selectPage) { // Check if selectPage mode is active - console.log("Page Select on. Showing buttons"); - //Check if no pages are selected - if (window.selectedPages.length === 0) { - this.showButton(selectIcon, true); - this.showButton(deselectIcon, false); - } else { - this.showButton(deselectIcon, true); - } - - //Check if all pages are selected - const allCheckboxes = document.querySelectorAll('.pdf-actions_checkbox'); - const allSelected = Array.from(allCheckboxes).every((checkbox) => checkbox.checked); - if (allSelected) { - this.showButton(selectIcon, false); - this.showButton(deselectIcon, true); - } else { - this.showButton(selectIcon, true); - } - } else { - console.log("Page Select off. Hiding buttons"); - this.showButton(selectIcon, false); - this.showButton(deselectIcon, false); - } - } - - parsePageRanges(ranges) { - const pages = new Set(); - - ranges.split(',').forEach((range) => { - const [start, end] = range.split('-').map(Number); - if (end) { - for (let i = start; i <= end; i++) { - pages.add(i); - } - } else { - pages.add(start); - } - }); - - return Array.from(pages).sort((a, b) => a - b); - } - - async addFilesBlankAll() { - const allPages = this.pagesContainer.querySelectorAll('.page-container'); - - let pageBreakCommand = new PageBreakCommand( - allPages, - window.selectPage, - window.selectedPages, - this.addFilesBlank.bind(this), - this.pagesContainer - ); - - await pageBreakCommand.execute(); - - this.undoManager.pushUndoClearRedo(pageBreakCommand); - } - - splitAll() { - const allPages = this.pagesContainer.querySelectorAll('.page-container'); - let splitAllCommand = new SplitAllCommand(allPages, window.selectPage, window.selectedPages, 'split-before'); - splitAllCommand.execute(); - - this.undoManager.pushUndoClearRedo(splitAllCommand); - } - - async splitPDF(baseDocBytes, splitters) { - const baseDocument = await PDFLib.PDFDocument.load(baseDocBytes); - const pageNum = baseDocument.getPages().length; - - splitters.sort((a, b) => a - b); // We'll sort the separator indexes just in case querySelectorAll does something funny. - splitters.push(pageNum); // We'll also add a faux separator at the end in order to get the pages after the last separator. - - const splitDocuments = []; - for (const splitterPosition of splitters) { - const subDocument = await PDFLib.PDFDocument.create(); - - const splitterIndex = splitters.indexOf(splitterPosition); - - let firstPage = splitterIndex === 0 ? 0 : splitters[splitterIndex - 1]; - - const pageIndices = Array.from({ length: splitterPosition - firstPage }, (value, key) => firstPage + key); - - const copiedPages = await subDocument.copyPages(baseDocument, pageIndices); - - copiedPages.forEach((copiedPage) => { - subDocument.addPage(copiedPage); - }); - - const subDocumentBytes = await subDocument.save(); - - splitDocuments.push(subDocumentBytes); - } - - return splitDocuments; - } - - async nameAndArchiveFiles(pdfBytesArray, baseNameString) { - const zip = new JSZip(); - - for (let i = 0; i < pdfBytesArray.length; i++) { - const documentBlob = new Blob([pdfBytesArray[i]], { - type: 'application/pdf', - }); - zip.file(baseNameString + '-' + String(i + 1).padStart(1 + Math.floor(Math.log10(pdfBytesArray.length)), "0") + '.pdf', documentBlob); - } - - return zip; - } - - async exportPdf(selected) { - const pdfDoc = await PDFLib.PDFDocument.create(); - const pageContainers = this.pagesContainer.querySelectorAll('.page-container'); // Select all .page-container elements - - const docPageMap = new Map(); - - pageContainers.forEach((container, index) => { - if (selected && !window.selectedPages.includes(index + 1)) { - return; - } - - const img = container.querySelector('img'); - if (!img?.doc) { - return; - } - - let entry = docPageMap.get(img.doc); - if (!entry) { - entry = { indices: [], copiedPages: [], cursor: 0 }; - docPageMap.set(img.doc, entry); - } - - entry.indices.push(img.pageIdx); - }); - - for (const [doc, entry] of docPageMap.entries()) { - entry.copiedPages = await pdfDoc.copyPages(doc, entry.indices); - } - - for (var i = 0; i < pageContainers.length; i++) { - if (!selected || window.selectedPages.includes(i + 1)) { - const img = pageContainers[i].querySelector('img'); // Find the img element within each .page-container - if (!img) continue; - let page; - if (img.doc) { - const entry = docPageMap.get(img.doc); - page = entry.copiedPages[entry.cursor++]; - pdfDoc.addPage(page); - } else { - page = pdfDoc.addPage([img.naturalWidth, img.naturalHeight]); - - // NEU: Bildbytes robust ermitteln (Canvas für blob:, fetch für http/https) - const { bytes: imageBytes, forcedType } = await bytesFromImageElement(img); - const uint8Array = new Uint8Array(imageBytes); - const imageType = forcedType || detectImageType(uint8Array); - - let image; - switch (imageType) { - case 'PNG': - image = await pdfDoc.embedPng(imageBytes); - break; - case 'JPEG': - image = await pdfDoc.embedJpg(imageBytes); - break; - case 'TIFF': - image = await pdfDoc.embedTiff(imageBytes); - break; - case 'GIF': - console.warn(`Unsupported image type: ${imageType}`); - continue; // Skip this image - default: - console.warn(`Unsupported image type: ${imageType}`); - continue; // Skip this image - } - page.drawImage(image, { - x: 0, - y: 0, - width: img.naturalWidth, - height: img.naturalHeight, - }); - } - const rotation = img.style.rotate; - if (rotation) { - const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, '')); - page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle)); - } - } - } - pdfDoc.setCreator(stirlingPDFLabel); - pdfDoc.setProducer(stirlingPDFLabel); - - const pdfBytes = await pdfDoc.save(); - const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' }); - - const filenameInput = document.getElementById('filename-input'); - - let inputArr = filenameInput.value.split('.'); - - if (inputArr !== null && inputArr !== undefined && inputArr.length > 0) { - inputArr = inputArr.filter((n) => n); // remove all empty strings, nulls or undefined - - if (inputArr.length > 1) { - inputArr.pop(); // remove right part after last dot - } - - filenameInput.value = inputArr.join(''); - this.fileName = filenameInput.value; - } - - const separators = this.pagesContainer.querySelectorAll('.split-before'); - if (separators.length !== 0) { - // Split the pdf if there are separators. - const baseName = this.fileName ? this.fileName : 'managed'; - - const pagesArray = Array.from(this.pagesContainer.children); - const splitters = []; - separators.forEach((page) => { - const pageIndex = pagesArray.indexOf(page); - if (pageIndex !== 0) { - splitters.push(pageIndex); - } - }); - - const splitDocuments = await this.splitPDF(pdfBytes, splitters); - const archivedDocuments = await this.nameAndArchiveFiles(splitDocuments, baseName); - - const self = this; - archivedDocuments.generateAsync({ type: 'base64' }).then(function (base64) { - const url = 'data:application/zip;base64,' + base64; - self.downloadLink = document.createElement('a'); - self.downloadLink.href = url; - self.downloadLink.setAttribute('download', baseName + '.zip'); - self.downloadLink.setAttribute('target', '_blank'); - self.downloadLink.click(); - }); - } else { - // Continue normally if there are no separators - - const url = URL.createObjectURL(pdfBlob); - const downloadOption = localStorage.getItem('downloadOption'); - - if (!filenameInput.value.includes('.pdf')) { - filenameInput.value = filenameInput.value + '.pdf'; - this.fileName = filenameInput.value; - } - - if (downloadOption === 'sameWindow') { - // Open the file in the same window - window.location.href = url; - } else if (downloadOption === 'newWindow') { - // Open the file in a new window - window.open(url, '_blank'); - } else { - // Download the file - this.downloadLink = document.createElement('a'); - this.downloadLink.id = 'download-link'; - this.downloadLink.href = url; - // downloadLink.download = this.fileName ? this.fileName : 'managed.pdf'; - // downloadLink.download = this.fileName; - this.downloadLink.setAttribute('download', this.fileName ? this.fileName : 'managed.pdf'); - this.downloadLink.setAttribute('target', '_blank'); - this.downloadLink.onclick = this.setDownloadAttribute; - this.downloadLink.click(); - } - } - } - - resetPages() { - const pageContainers = this.pagesContainer.querySelectorAll('.page-container'); - - pageContainers.forEach((container, index) => { - container.id = 'page-container-' + (index + 1); - }); - - const checkboxes = document.querySelectorAll('.pdf-actions_checkbox'); - const selectIcon = document.getElementById('select-All-Container'); - const deselectIcon = document.getElementById('deselect-All-Container'); - - checkboxes.forEach((checkbox) => { - const pageNumber = Array.from(checkbox.parentNode.parentNode.children).indexOf(checkbox.parentNode) + 1; - - const index = window.selectedPages.indexOf(pageNumber); - if (index !== -1) { - window.selectedPages.splice(index, 1); - } - }); - window.toggleSelectPageVisibility(); - } - - setDownloadAttribute() { - this.downloadLink.setAttribute('download', this.fileName ? this.fileName : 'managed.pdf'); - } - - updateFilename(fileName = '') { - const filenameInput = document.getElementById('filename-input'); - const pagesContainer = document.getElementById('pages-container'); - const downloadBtn = document.getElementById('export-button'); - - downloadBtn.disabled = pagesContainer.childElementCount === 0; - - if (!this.fileName) { - this.fileName = fileName; - } - - if (!filenameInput.value) { - filenameInput.value = this.fileName; - } - } - - preventIllegalChars(e) { - // const filenameInput = document.getElementById('filename-input'); - // - // filenameInput.value = filenameInput.value.replace('.pdf', ''); - // - // // prevent . - // if (filenameInput.value.includes('.')) { - // filenameInput.value.replace('.',''); - // } - } - - toggleSelectPageVisibility() { - window.selectPage = !window.selectPage; - const checkboxes = document.querySelectorAll('.pdf-actions_checkbox'); - checkboxes.forEach((checkbox) => { - checkbox.classList.toggle('hidden', !window.selectPage); - }); - const deleteButton = document.getElementById('delete-button'); - deleteButton.classList.toggle('hidden', !window.selectPage); - const selectedPages = document.getElementById('selected-pages-display'); - selectedPages.classList.toggle('hidden', !window.selectPage); - - if(!window.selectPage) - { - this.showButton(document.getElementById('deselect-All-Container'), false); - this.showButton(document.getElementById('select-All-Container'), false); - - // Uncheck all checkboxes and clear selected pages - const allCheckboxes = document.querySelectorAll('.pdf-actions_checkbox'); - allCheckboxes.forEach((checkbox) => { - checkbox.checked = false; - }); - window.selectedPages = []; - this.updateSelectedPagesDisplay(); - } - else{ - const allCheckboxes = document.querySelectorAll('.pdf-actions_checkbox'); - const allSelected = Array.from(allCheckboxes).every((checkbox) => checkbox.checked); - if (!allSelected) { - this.showButton(document.getElementById('select-All-Container'), true); - } - - if (window.selectedPages.length > 0) { - this.showButton(document.getElementById('deselect-All-Container'), true); - } - } - - const exportSelected = document.getElementById('export-selected-button'); - exportSelected.classList.toggle('hidden', !window.selectPage); - const selectPagesButton = document.getElementById('select-pages-button'); - selectPagesButton.style.opacity = window.selectPage ? '1' : '0.5'; - - if (window.selectPage) { - this.updatePageNumbersAndCheckboxes(); - } - } - - updatePageNumbersAndCheckboxes() { - const pageDivs = document.querySelectorAll('.pdf-actions_container'); - - pageDivs.forEach((div, index) => { - const pageNumber = index + 1; - const checkbox = div.querySelector('.pdf-actions_checkbox'); - checkbox.id = `selectPageCheckbox-${pageNumber}`; - checkbox.setAttribute('data-page-number', pageNumber); - checkbox.checked = window.selectedPages.includes(pageNumber); - }); - } -} - -async function bytesFromImageElement(img) { - // Handle Blob URLs without using fetch() - if (img.src.startsWith('blob:')) { - const canvas = document.createElement('canvas'); - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = canvas.getContext('2d', { willReadFrequently: true }); - ctx.drawImage(img, 0, 0); - const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png')); - if (!blob) throw new Error('Canvas toBlob() failed'); - const buf = await blob.arrayBuffer(); - return { bytes: buf, forcedType: 'PNG' }; // Canvas always generates PNG - } - - // Fetch http(s)/data:-URLs normally (if necessary) - const res = await fetch(img.src, { cache: 'no-store' }); - if (!res.ok) throw new Error(`HTTP ${res.status} beim Laden von ${img.src}`); - const buf = await res.arrayBuffer(); - - // Use Content-Type as a hint (optional) - let forcedType = null; - const ct = res.headers.get('content-type') || ''; - if (ct.includes('png')) forcedType = 'PNG'; - else if (ct.includes('jpeg') || ct.includes('jpg')) forcedType = 'JPEG'; - else if (ct.includes('tiff')) forcedType = 'TIFF'; - else if (ct.includes('gif')) forcedType = 'GIF'; - - return { bytes: buf, forcedType }; -} - -function detectImageType(uint8Array) { - // Check for PNG signature - if (uint8Array[0] === 137 && uint8Array[1] === 80 && uint8Array[2] === 78 && uint8Array[3] === 71) { - return 'PNG'; - } - - // Check for JPEG signature - if (uint8Array[0] === 255 && uint8Array[1] === 216 && uint8Array[2] === 255) { - return 'JPEG'; - } - - // Check for TIFF signature (little-endian and big-endian) - if ( - (uint8Array[0] === 73 && uint8Array[1] === 73 && uint8Array[2] === 42 && uint8Array[3] === 0) || - (uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42) - ) { - return 'TIFF'; - } - - // Check for GIF signature - if (uint8Array[0] === 71 && uint8Array[1] === 73 && uint8Array[2] === 70) { - return 'GIF'; - } - - return 'UNKNOWN'; -} - -export default PdfContainer; diff --git a/app/core/src/main/resources/static/js/multitool/UndoManager.js b/app/core/src/main/resources/static/js/multitool/UndoManager.js deleted file mode 100644 index 39202f3f6..000000000 --- a/app/core/src/main/resources/static/js/multitool/UndoManager.js +++ /dev/null @@ -1,65 +0,0 @@ -export class UndoManager { - _undoStack; - _redoStack; - - constructor() { - this._undoStack = []; - this._redoStack = []; - } - - pushUndo(command) { - this._undoStack.push(command); - this._dispatchStateChange(); - } - - pushRedo(command) { - this._redoStack.push(command); - this._dispatchStateChange(); - } - - pushUndoClearRedo(command) { - this._undoStack.push(command); - this._redoStack = []; - this._dispatchStateChange(); - } - - undo() { - if (!this.canUndo()) return; - - let cmd = this._undoStack.pop(); - cmd.undo(); - - this._redoStack.push(cmd); - this._dispatchStateChange(); - } - - canUndo() { - return this._undoStack && this._undoStack.length > 0; - } - - redo() { - if (!this.canRedo()) return; - - let cmd = this._redoStack.pop(); - cmd.redo(); - - this._undoStack.push(cmd); - this._dispatchStateChange(); - } - - canRedo() { - return this._redoStack && this._redoStack.length > 0; - } - - _dispatchStateChange() { - document.dispatchEvent( - new CustomEvent("undo-manager-update", { - bubbles: true, - detail: { - canUndo: this.canUndo(), - canRedo: this.canRedo(), - }, - }) - ); - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/add-page.js b/app/core/src/main/resources/static/js/multitool/commands/add-page.js deleted file mode 100644 index 09965042d..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/add-page.js +++ /dev/null @@ -1,84 +0,0 @@ -import { CommandWithAnchors } from './command.js'; - -export class AddFilesCommand extends CommandWithAnchors { - /** - * @param {HTMLElement|null} element - Anchor element (optional, forwarded to addFilesAction) - * @param {number[]} selectedPages - * @param {Function} addFilesAction - async (nextSiblingElement|false) => HTMLElement[]|HTMLElement|null - * @param {HTMLElement} pagesContainer - */ - constructor(element, selectedPages, addFilesAction, pagesContainer) { - super(); - this.element = element; - this.selectedPages = selectedPages; - this.addFilesAction = addFilesAction; - this.pagesContainer = pagesContainer; - - /** @type {HTMLElement[]} */ - this.addedElements = []; - - /** - * Anchors captured on undo to support redo reinsertion. - * @type {{ el: HTMLElement, nextSibling: ChildNode|null, index: number }[]} - */ - this._anchors = []; - } - - async execute() { - const undoBtn = document.getElementById('undo-btn'); - if (undoBtn) undoBtn.disabled = true; - - const result = await this.addFilesAction(this.element || false); - if (Array.isArray(result)) { - this.addedElements = result; - } else if (result) { - this.addedElements = [result]; - } else { - this.addedElements = []; - } - - // Capture anchors right after insertion so redo does not depend on undo. - this._anchors = this.addedElements.map((el) => this.captureAnchor(el, this.pagesContainer)); - - if (undoBtn) undoBtn.disabled = false; - } - - undo() { - this._anchors = []; - - for (const el of this.addedElements) { - this._anchors.push(this.captureAnchor(el, this.pagesContainer)); - this.pagesContainer.removeChild(el); - } - - if (this.pagesContainer.childElementCount === 0) { - const filenameInput = document.getElementById('filename-input'); - const downloadBtn = document.getElementById('export-button'); - if (filenameInput) { - filenameInput.disabled = true; - filenameInput.value = ''; - } - if (downloadBtn) { - downloadBtn.disabled = true; - } - } - } - - redo() { - if (!this.addedElements.length) return; - // If the elements are already in the DOM (no prior undo), do nothing. - const alreadyInDom = - this.addedElements[0].parentNode === this.pagesContainer; - if (alreadyInDom) return; - - // Use pre-captured anchors (from execute) or fall back to capturing now. - const anchors = (this._anchors && this._anchors.length) - ? this._anchors - : this.addedElements.map((el) => - this.captureAnchor(el, this.pagesContainer)); - - for (const anchor of anchors) { - this.insertWithAnchor(this.pagesContainer, anchor); - } - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/command.js b/app/core/src/main/resources/static/js/multitool/commands/command.js deleted file mode 100644 index e6bc9b11c..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/command.js +++ /dev/null @@ -1,65 +0,0 @@ -export class Command { - execute() {} - undo() {} - redo() {} -} - -/** - * Base class that provides anchor capture and reinsertion helpers - * to avoid storing custom state on DOM nodes. - */ -export class CommandWithAnchors extends Command { - constructor() { - super(); - /** @type {{ el: HTMLElement, nextSibling: ChildNode|null, index: number }[]} */ - this._anchors = []; - } - - /** - * Returns the child index of an element in a container. - * @param {HTMLElement} container - * @param {HTMLElement} el - * @returns {number} - */ - _indexOf(container, el) { - return Array.prototype.indexOf.call(container.children, el); - } - - /** - * Captures an anchor for later reinsertion. - * @param {HTMLElement} el - * @param {HTMLElement} container - * @returns {{ el: HTMLElement, nextSibling: ChildNode|null, index: number }} - */ - captureAnchor(el, container) { - return { - el, - nextSibling: el.nextSibling, - index: this._indexOf(container, el), - }; - } - - /** - * Reinserts an element using a previously captured anchor. - * Prefers stored nextSibling when still valid; otherwise falls back to index. - * @param {HTMLElement} container - * @param {{ el: HTMLElement, nextSibling: ChildNode|null, index: number }} anchor - */ - insertWithAnchor(container, anchor) { - const { el, nextSibling, index } = anchor; - const nextValid = nextSibling && nextSibling.parentNode === container; - - let ref = null; - if (nextValid) { - ref = nextSibling; - } else if ( - Number.isInteger(index) && - index >= 0 && - index < container.children.length - ) { - ref = container.children[index] || null; - } - - container.insertBefore(el, ref || null); - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/commands-sequence.js b/app/core/src/main/resources/static/js/multitool/commands/commands-sequence.js deleted file mode 100644 index ef2f93c21..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/commands-sequence.js +++ /dev/null @@ -1,28 +0,0 @@ -import { Command } from './command.js'; - -/** - * Composes multiple commands into a single atomic operation. - * Executes in order; undo in reverse order. - */ -export class CommandSequence extends Command { - /** @param {Command[]} commands - Commands to be executed/undone/redone as a group. */ - constructor(commands) { - super(); - this.commands = commands; - } - - /** Execute: run each command in order. */ - execute() { - this.commands.forEach((command) => command.execute()); - } - - /** Undo: undo in reverse order. */ - undo() { - this.commands.slice().reverse().forEach((command) => command.undo()); - } - - /** Redo: simply execute again. */ - redo() { - this.execute(); - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/delete-page.js b/app/core/src/main/resources/static/js/multitool/commands/delete-page.js deleted file mode 100644 index a52b4e962..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/delete-page.js +++ /dev/null @@ -1,92 +0,0 @@ -import { Command } from "./command.js"; - -/** - * Removes a page from the container and restores it on undo. - */ -export class DeletePageCommand extends Command { - /** - * @param {HTMLElement} element - Page container to delete. - * @param {HTMLElement} pagesContainer - Parent container holding all pages. - */ - constructor(element, pagesContainer) { - super(); - - this.element = element; - this.pagesContainer = pagesContainer; - - /** @type {ChildNode|null} */ - this.nextSibling = null; - - const filenameInput = document.getElementById("filename-input"); - /** @type {string} */ - this.filenameInputValue = filenameInput ? filenameInput.value : ""; - - const filenameParagraph = document.getElementById("filename"); - /** @type {string} */ - this.filenameParagraphText = filenameParagraph ? filenameParagraph.innerText : ""; - } - - /** Execute: remove the page and update empty-state UI if needed. */ - execute() { - this.nextSibling = this.element.nextSibling; - - this.pagesContainer.removeChild(this.element); - if (this.pagesContainer.childElementCount === 0) { - const filenameInput = document.getElementById("filename-input"); - const downloadBtn = document.getElementById("export-button"); - - if (filenameInput) { - filenameInput.disabled = true; - filenameInput.value = ""; - } - if (downloadBtn) { - downloadBtn.disabled = true; - } - } - } - - /** Undo: reinsert the page at its original position. */ - undo() { - const node = /** @type {ChildNode|null} */ (this.nextSibling); - if (node) this.pagesContainer.insertBefore(this.element, node); - else this.pagesContainer.appendChild(this.element); - - const pageNumberElement = this.element.querySelector(".page-number"); - if (pageNumberElement) { - this.element.removeChild(pageNumberElement); - } - - const filenameInput = document.getElementById("filename-input"); - const downloadBtn = document.getElementById("export-button"); - - if (filenameInput) { - filenameInput.disabled = false; - filenameInput.value = this.filenameInputValue; - } - if (downloadBtn) { - downloadBtn.disabled = false; - } - } - - /** Redo: remove again and maintain empty-state UI. */ - redo() { - const pageNumberElement = this.element.querySelector(".page-number"); - if (pageNumberElement) { - this.element.removeChild(pageNumberElement); - } - - this.pagesContainer.removeChild(this.element); - if (this.pagesContainer.childElementCount === 0) { - const filenameInput = document.getElementById("filename-input"); - const downloadBtn = document.getElementById("export-button"); - - if (filenameInput) { - filenameInput.disabled = true; - filenameInput.value = ""; - } - if (downloadBtn) { - downloadBtn.disabled = true; - } - } - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/duplicate-page.js b/app/core/src/main/resources/static/js/multitool/commands/duplicate-page.js deleted file mode 100644 index 8071bc9b2..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/duplicate-page.js +++ /dev/null @@ -1,63 +0,0 @@ -import { CommandWithAnchors } from './command.js'; - -export class DuplicatePageCommand extends CommandWithAnchors { - /** - * @param {HTMLElement} element - The page element to duplicate. - * @param {Function} duplicatePageAction - (element) => HTMLElement (new clone already inserted) - * @param {HTMLElement} pagesContainer - */ - constructor(element, duplicatePageAction, pagesContainer) { - super(); - this.element = element; - this.duplicatePageAction = duplicatePageAction; - this.pagesContainer = pagesContainer; - - /** @type {HTMLElement|null} */ - this.newElement = null; - - /** @type {{ el: HTMLElement, nextSibling: ChildNode|null, index: number }|null} */ - this._anchor = null; - } - - execute() { - // Create and insert a duplicate next to the original - this.newElement = this.duplicatePageAction(this.element); - } - - undo() { - if (!this.newElement) return; - - // Capture anchor before removal so redo can reinsert at the same position - this._anchor = this.captureAnchor(this.newElement, this.pagesContainer); - - this.pagesContainer.removeChild(this.newElement); - - if (this.pagesContainer.childElementCount === 0) { - const filenameInput = document.getElementById('filename-input'); - const downloadBtn = document.getElementById('export-button'); - if (filenameInput) { - filenameInput.disabled = true; - filenameInput.value = ''; - } - if (downloadBtn) { - downloadBtn.disabled = true; - } - } - - window.updatePageNumbersAndCheckboxes?.(); - } - - redo() { - if (!this.newElement) { - this.execute(); - return; - } - if (this._anchor) { - this.insertWithAnchor(this.pagesContainer, this._anchor); - } else { - // Fallback: insert relative to the original element - this.pagesContainer.insertBefore(this.newElement, this.element.nextSibling || null); - } - window.updatePageNumbersAndCheckboxes?.(); - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/move-page.js b/app/core/src/main/resources/static/js/multitool/commands/move-page.js deleted file mode 100644 index d4436050f..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/move-page.js +++ /dev/null @@ -1,80 +0,0 @@ -import { Command } from './command.js'; - -/** - * Moves a page (or multiple pages, via PdfContainer wrapper) inside the container. - */ -export class MovePageCommand extends Command { - /** - * @param {HTMLElement} startElement - Dragged page container. - * @param {HTMLElement|null} endElement - Destination reference; insert before this node. Null = append. - * @param {HTMLElement} pagesContainer - Parent container with all pages. - * @param {HTMLElement} pagesContainerWrapper - Scrollable wrapper element. - * @param {boolean} [scrollTo=false] - Whether to apply a subtle scroll after move. - */ - constructor(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo = false) { - super(); - - this.pagesContainer = pagesContainer; - const childArray = Array.from(this.pagesContainer.childNodes); - - /** @type {number} */ - this.startIndex = childArray.indexOf(startElement); - /** @type {number} */ - this.endIndex = childArray.indexOf(endElement); - - this.startElement = startElement; - this.endElement = endElement; - - this.scrollTo = scrollTo; - this.pagesContainerWrapper = pagesContainerWrapper; - } - - /** Execute: perform DOM move and optional scroll. */ - execute() { - // Remove stale page number badge if present (Firefox sometimes misses the event) - const pageNumberElement = this.startElement.querySelector('.page-number'); - if (pageNumberElement) { - this.startElement.removeChild(pageNumberElement); - } - - this.pagesContainer.removeChild(this.startElement); - if (!this.endElement) { - this.pagesContainer.append(this.startElement); - } else { - this.pagesContainer.insertBefore(this.startElement, this.endElement); - } - - if (this.scrollTo) { - const { width } = this.startElement.getBoundingClientRect(); - const vector = this.endIndex !== -1 && this.startIndex > this.endIndex ? 0 - width : width; - - this.pagesContainerWrapper.scroll({ - left: this.pagesContainerWrapper.scrollLeft + vector, - }); - } - } - - /** Undo: restore original order and optional scroll back. */ - undo() { - if (this.startElement) { - this.pagesContainer.removeChild(this.startElement); - const previousNeighbour = Array.from(this.pagesContainer.childNodes)[this.startIndex]; - previousNeighbour?.insertAdjacentElement('beforebegin', this.startElement) - ?? this.pagesContainer.append(this.startElement); - } - - if (this.scrollTo) { - const { width } = this.startElement.getBoundingClientRect(); - const vector = this.endIndex === -1 || this.startIndex <= this.endIndex ? 0 - width : width; - - this.pagesContainerWrapper.scroll({ - left: this.pagesContainerWrapper.scrollLeft - vector, - }); - } - } - - /** Redo: same as execute. */ - redo() { - this.execute(); - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/page-break.js b/app/core/src/main/resources/static/js/multitool/commands/page-break.js deleted file mode 100644 index 0f910ec49..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/page-break.js +++ /dev/null @@ -1,105 +0,0 @@ -import { CommandWithAnchors } from './command.js'; - -export class PageBreakCommand extends CommandWithAnchors { - /** - * @param {HTMLElement[]} elements - * @param {boolean} isSelectedInWindow - * @param {number[]} selectedPages - 0-based indices of selected pages - * @param {Function} pageBreakCallback - async (element, addedSoFar) => HTMLElement[]|HTMLElement|null - * @param {HTMLElement} pagesContainer - */ - constructor(elements, isSelectedInWindow, selectedPages, pageBreakCallback, pagesContainer) { - super(); - this.elements = elements; - this.isSelectedInWindow = isSelectedInWindow; - this.selectedPages = selectedPages; - this.pageBreakCallback = pageBreakCallback; - this.pagesContainer = pagesContainer; - - /** @type {HTMLElement[]} */ - this.addedElements = []; - - /** - * Anchors captured on undo to support redo reinsertion. - * @type {{ el: HTMLElement, nextSibling: ChildNode|null, index: number }[]} - */ - this._anchors = []; - - // Keep content snapshot if needed for future enhancements - this.originalStates = Array.from(elements, (element) => ({ - element, - hasContent: element.innerHTML.trim() !== '', - })); - } - - async execute() { - const undoBtn = document.getElementById('undo-btn'); - if (undoBtn) undoBtn.disabled = true; - - for (const [index, element] of this.elements.entries()) { - const withinSelection = !this.isSelectedInWindow || this.selectedPages.includes(index); - if (!withinSelection) continue; - - if (index !== 0) { - const result = await this.pageBreakCallback(element, this.addedElements); - if (!Array.isArray(this.addedElements)) { - this.addedElements = []; - } - - if (Array.isArray(result)) { - this.addedElements.push(...result); - } else if (result) { - this.addedElements.push(result); - } - } - } - - // Capture anchors right after insertion so redo does not depend on undo. - this._anchors = this.addedElements.map((el) => this.captureAnchor(el, this.pagesContainer)); - - if (undoBtn) undoBtn.disabled = false; - } - - undo() { - this._anchors = []; - - for (const el of this.addedElements) { - this._anchors.push(this.captureAnchor(el, this.pagesContainer)); - this.pagesContainer.removeChild(el); - } - - if (this.pagesContainer.childElementCount === 0) { - const filenameInput = document.getElementById('filename-input'); - const filenameParagraph = document.getElementById('filename'); - const downloadBtn = document.getElementById('export-button'); - - if (filenameInput) { - filenameInput.disabled = true; - filenameInput.value = ''; - } - if (filenameParagraph) { - filenameParagraph.innerText = ''; - } - if (downloadBtn) { - downloadBtn.disabled = true; - } - } - } - - redo() { - // If elements are already present (no prior undo), do nothing. - if (!this.addedElements.length) return; - const alreadyInDom = - this.addedElements[0].parentNode === this.pagesContainer; - if (alreadyInDom) return; - - // Use pre-captured anchors (from execute) or fall back to current ones. - const anchors = (this._anchors && this._anchors.length) - ? this._anchors - : this.addedElements.map((el) => this.captureAnchor(el, this.pagesContainer)); - - for (const anchor of anchors) { - this.insertWithAnchor(this.pagesContainer, anchor); - } - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/remove.js b/app/core/src/main/resources/static/js/multitool/commands/remove.js deleted file mode 100644 index e7c6bb7ca..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/remove.js +++ /dev/null @@ -1,112 +0,0 @@ -import { Command } from "./command.js"; - -/** - * Deletes a set of selected pages and restores them on undo. - */ -export class RemoveSelectedCommand extends Command { - /** - * @param {HTMLElement} pagesContainer - Parent container. - * @param {number[]} selectedPages - 1-based page numbers to remove. - * @param {Function} updatePageNumbersAndCheckboxes - Callback to refresh UI state. - */ - constructor(pagesContainer, selectedPages, updatePageNumbersAndCheckboxes) { - super(); - this.pagesContainer = pagesContainer; - this.selectedPages = selectedPages; - - /** @type {{idx:number, childNode:HTMLElement}[]} */ - this.deletedChildren = []; - - this.updatePageNumbersAndCheckboxes = updatePageNumbersAndCheckboxes || (() => { - const pageDivs = document.querySelectorAll(".pdf-actions_container"); - pageDivs.forEach((div, index) => { - const pageNumber = index + 1; - const checkbox = div.querySelector(".pdf-actions_checkbox"); - if (checkbox) { - checkbox.id = `selectPageCheckbox-${pageNumber}`; - checkbox.setAttribute("data-page-number", pageNumber); - checkbox.checked = window.selectedPages.includes(pageNumber); - } - }); - }); - - const filenameInput = document.getElementById("filename-input"); - const filenameParagraph = document.getElementById("filename"); - - /** @type {string} */ - this.originalFilenameInputValue = filenameInput ? filenameInput.value : ""; - /** @type {string|undefined} */ - this.originalFilenameParagraphText = filenameParagraph?.innerText; - } - - /** Execute: remove selected pages and update empty state. */ - execute() { - let deletions = 0; - - this.selectedPages.forEach((pageIndex) => { - const adjustedIndex = pageIndex - 1 - deletions; - const child = this.pagesContainer.children[adjustedIndex]; - if (child) { - this.pagesContainer.removeChild(child); - deletions++; - - this.deletedChildren.push({ - idx: adjustedIndex, - childNode: child, - }); - } - }); - - if (this.pagesContainer.childElementCount === 0) { - const filenameInput = document.getElementById("filename-input"); - const filenameParagraph = document.getElementById("filename"); - const downloadBtn = document.getElementById("export-button"); - - if (filenameInput) filenameInput.disabled = true; - if (filenameInput) filenameInput.value = ""; - if (filenameParagraph) filenameParagraph.innerText = ""; - if (downloadBtn) downloadBtn.disabled = true; - } - - window.selectedPages = []; - this.updatePageNumbersAndCheckboxes(); - document.dispatchEvent(new Event("selectedPagesUpdated")); - } - - /** Undo: restore all removed nodes at their original indices. */ - undo() { - while (this.deletedChildren.length > 0) { - const deletedChild = this.deletedChildren.pop(); - if (this.pagesContainer.children.length <= deletedChild.idx) { - this.pagesContainer.appendChild(deletedChild.childNode); - } else { - this.pagesContainer.insertBefore( - deletedChild.childNode, - this.pagesContainer.children[deletedChild.idx] - ); - } - } - - if (this.pagesContainer.childElementCount > 0) { - const filenameInput = document.getElementById("filename-input"); - const filenameParagraph = document.getElementById("filename"); - const downloadBtn = document.getElementById("export-button"); - - if (filenameInput) filenameInput.disabled = false; - if (filenameInput) filenameInput.value = this.originalFilenameInputValue; - if (filenameParagraph && this.originalFilenameParagraphText !== undefined) { - filenameParagraph.innerText = this.originalFilenameParagraphText; - } - if (downloadBtn) downloadBtn.disabled = false; - } - - window.selectedPages = this.selectedPages; - this.updatePageNumbersAndCheckboxes(); - document.dispatchEvent(new Event("selectedPagesUpdated")); - } - - /** Redo mirrors execute. */ - redo() { - this.execute(); - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/rotate.js b/app/core/src/main/resources/static/js/multitool/commands/rotate.js deleted file mode 100644 index e3236fe81..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/rotate.js +++ /dev/null @@ -1,81 +0,0 @@ -import { Command } from "./command.js"; - -/** - * Rotates a single image element by a relative degree. - */ -export class RotateElementCommand extends Command { - /** - * @param {HTMLElement} element - The element to rotate. - * @param {number|string} degree - Relative degrees to add (e.g., 90 or "-90"). - */ - constructor(element, degree) { - super(); - this.element = element; - this.degree = degree; - } - - /** Execute: apply rotation. */ - execute() { - let lastTransform = this.element.style.rotate || "0"; - const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); - const newAngle = lastAngle + parseInt(this.degree); - - this.element.style.rotate = newAngle + "deg"; - } - - /** Undo: revert by subtracting the same delta. */ - undo() { - let lastTransform = this.element.style.rotate || "0"; - const currentAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); - const undoAngle = currentAngle + -parseInt(this.degree); - - this.element.style.rotate = undoAngle + "deg"; - } - - /** Redo mirrors execute. */ - redo() { - this.execute(); - } -} - -/** - * Rotates a set of image elements by a relative degree. - */ -export class RotateAllCommand extends Command { - /** - * @param {HTMLElement[]} elements - Image elements to rotate. - * @param {number} degree - Relative degrees to add for all. - */ - constructor(elements, degree) { - super(); - this.elements = elements; - this.degree = degree; - } - - /** Execute: apply rotation to all. */ - execute() { - for (const element of this.elements) { - let lastTransform = element.style.rotate || "0"; - const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); - const newAngle = lastAngle + this.degree; - - element.style.rotate = newAngle + "deg"; - } - } - - /** Undo: revert rotation for all. */ - undo() { - for (const element of this.elements) { - let lastTransform = element.style.rotate || "0"; - const currentAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); - const undoAngle = currentAngle + -this.degree; - - element.style.rotate = undoAngle + "deg"; - } - } - - /** Redo mirrors execute. */ - redo() { - this.execute(); - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/select.js b/app/core/src/main/resources/static/js/multitool/commands/select.js deleted file mode 100644 index 61199cba1..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/select.js +++ /dev/null @@ -1,47 +0,0 @@ -import { Command } from "./command.js"; - -/** - * Toggles selection state of a single page via its checkbox. - */ -export class SelectPageCommand extends Command { - /** - * @param {number} pageNumber - 1-based page number. - * @param {HTMLInputElement} checkbox - Checkbox linked to the page. - */ - constructor(pageNumber, checkbox) { - super(); - this.pageNumber = pageNumber; - this.selectCheckbox = checkbox; - } - - /** Execute: apply current checkbox state to global selection. */ - execute() { - if (this.selectCheckbox.checked) { - window.selectedPages.push(this.pageNumber); - } else { - const index = window.selectedPages.indexOf(this.pageNumber); - if (index !== -1) window.selectedPages.splice(index, 1); - } - - if (window.selectedPages.length > 0 && !window.selectPage) { - window.toggleSelectPageVisibility(); - } - if (window.selectedPages.length === 0 && window.selectPage) { - window.toggleSelectPageVisibility(); - } - - window.updateSelectedPagesDisplay(); - } - - /** Undo: invert checkbox and apply same logic as execute. */ - undo() { - this.selectCheckbox.checked = !this.selectCheckbox.checked; - this.execute(); - } - - /** Redo: invert again then execute. */ - redo() { - this.selectCheckbox.checked = !this.selectCheckbox.checked; - this.execute(); - } -} diff --git a/app/core/src/main/resources/static/js/multitool/commands/split.js b/app/core/src/main/resources/static/js/multitool/commands/split.js deleted file mode 100644 index a4fad6ffb..000000000 --- a/app/core/src/main/resources/static/js/multitool/commands/split.js +++ /dev/null @@ -1,89 +0,0 @@ -import { Command } from "./command.js"; - -/** - * Toggles a split class on a single page element. - */ -export class SplitFileCommand extends Command { - /** - * @param {HTMLElement} element - Target page container. - * @param {string} splitClass - CSS class to toggle for split markers. - */ - constructor(element, splitClass) { - super(); - this.element = element; - this.splitClass = splitClass; - } - - /** Execute: toggle split class. */ - execute() { - this.element.classList.toggle(this.splitClass); - } - - /** Undo: toggle split class back. */ - undo() { - this.element.classList.toggle(this.splitClass); - } - - /** Redo: same as execute. */ - redo() { - this.execute(); - } -} - -/** - * Toggles split class across a set of elements, optionally limited by selection. - */ -export class SplitAllCommand extends Command { - /** - * @param {NodeListOf|HTMLElement[]} elements - All page containers. - * @param {boolean} isSelectedInWindow - Whether multi-select mode is active. - * @param {number[]} selectedPages - 0-based indices of selected pages (when active). - * @param {string} splitClass - CSS class used as split marker. - */ - constructor(elements, isSelectedInWindow, selectedPages, splitClass) { - super(); - this.elements = elements; - this.isSelectedInWindow = isSelectedInWindow; - this.selectedPages = selectedPages; - this.splitClass = splitClass; - } - - /** Execute: toggle split for all or selected pages. */ - execute() { - if (!this.isSelectedInWindow) { - const hasSplit = this._hasSplit(); - (this.elements || []).forEach((page) => { - if (hasSplit) { - page.classList.remove(this.splitClass); - } else { - page.classList.add(this.splitClass); - } - }); - return; - } - - this.elements.forEach((page, index) => { - if (!this.selectedPages.includes(index)) return; - page.classList.toggle(this.splitClass); - }); - } - - /** @returns {boolean} true if any element currently has the split class. */ - _hasSplit() { - if (!this.elements || this.elements.length === 0) return false; - for (const node of this.elements) { - if (node.classList.contains(this.splitClass)) return true; - } - return false; - } - - /** Undo mirrors execute logic. */ - undo() { - this.execute(); - } - - /** Redo mirrors execute logic. */ - redo() { - this.execute(); - } -} diff --git a/app/core/src/main/resources/static/js/navbar.js b/app/core/src/main/resources/static/js/navbar.js deleted file mode 100644 index 4f1c8a5f7..000000000 --- a/app/core/src/main/resources/static/js/navbar.js +++ /dev/null @@ -1,163 +0,0 @@ -function toolsManager() { - const convertToPDF = document.querySelector('#groupConvertTo'); - const convertFromPDF = document.querySelector('#groupConvertFrom'); - - if (convertToPDF && convertFromPDF) { - const itemsTo = Array.from(convertToPDF.querySelectorAll('.dropdown-item')).filter( - (item) => !item.querySelector('hr.dropdown-divider') - ); - const itemsFrom = Array.from(convertFromPDF.querySelectorAll('.dropdown-item')).filter( - (item) => !item.querySelector('hr.dropdown-divider') - ); - - const totalItems = itemsTo.length + itemsFrom.length; - - if (totalItems > 12) { - document.querySelectorAll('#convertGroup').forEach((element) => (element.style.display = 'none')); - document.querySelectorAll('#groupConvertTo').forEach((element) => (element.style.display = 'flex')); - document.querySelectorAll('#groupConvertFrom').forEach((element) => (element.style.display = 'flex')); - } else { - document.querySelectorAll('#convertGroup').forEach((element) => (element.style.display = 'flex')); - document.querySelectorAll('#groupConvertTo').forEach((element) => (element.style.display = 'none')); - document.querySelectorAll('#groupConvertFrom').forEach((element) => (element.style.display = 'none')); - } - } - - document.querySelectorAll('.navbar-item').forEach((element) => { - if (!element.closest('#stacked')) { - const dropdownItems = element.querySelectorAll('.dropdown-item'); - const items = Array.from(dropdownItems).filter((item) => !item.querySelector('hr.dropdown-divider')); - - if (items.length === 0) { - if ( - element.previousElementSibling && - element.previousElementSibling.classList.contains('navbar-item') && - element.previousElementSibling.classList.contains('nav-item-separator') - ) { - element.previousElementSibling.remove(); - } - element.remove(); - } - } - }); -} - -function setupDropdowns() { - const dropdowns = document.querySelectorAll('.navbar-nav > .nav-item.dropdown'); - - dropdowns.forEach((dropdown) => { - const toggle = dropdown.querySelector('[data-bs-toggle="dropdown"]'); - if (!toggle) return; - - // Skip search dropdown, it has its own logic - if (toggle.id === 'searchDropdown') { - return; - } - - dropdown.addEventListener('show.bs.dropdown', () => { - // Find all other open dropdowns and hide them - const openDropdowns = document.querySelectorAll('.navbar-nav .dropdown-menu.show'); - openDropdowns.forEach((menu) => { - const parentDropdown = menu.closest('.dropdown'); - if (parentDropdown && parentDropdown !== dropdown) { - const parentToggle = parentDropdown.querySelector('[data-bs-toggle="dropdown"]'); - if (parentToggle) { - // Get or create Bootstrap dropdown instance - let instance = bootstrap.Dropdown.getInstance(parentToggle); - if (!instance) { - instance = new bootstrap.Dropdown(parentToggle); - } - instance.hide(); - } - } - }); - }); - }); -} - -function tooltipSetup() { - // initialize global tooltip element or get reference - let customTooltip = document.getElementById("customTooltip"); - if (!customTooltip) { - customTooltip = document.createElement("div"); - customTooltip.id = "customTooltip"; - customTooltip.className = "btn-tooltip"; - document.body.appendChild(customTooltip); - } - - function updateTooltipPosition(event, text) { - if (window.innerWidth >= 1200) { - customTooltip.textContent = text; - customTooltip.style.display = "block"; - customTooltip.style.left = `${event.pageX + 10}px`; - customTooltip.style.top = `${event.pageY + 10}px`; - } - } - - function hideTooltip() { - customTooltip.style.display = "none"; - } - - // find uninitialized tooltips and set up event listeners - const tooltipElements = document.querySelectorAll("[title]"); - - tooltipElements.forEach((element) => { - const tooltipText = element.getAttribute("title"); - element.removeAttribute("title"); - element.setAttribute("data-title", tooltipText); // no UI effect, just for reference - - element.addEventListener("mouseenter", (event) => updateTooltipPosition(event, tooltipText)); - element.addEventListener("mousemove", (event) => updateTooltipPosition(event, tooltipText)); - element.addEventListener("mouseleave", hideTooltip); - - // in case UI moves and mouseleave is not triggered, the tooltip is re-added when the mouse is moved over the element - element.addEventListener("click", hideTooltip); - }); -}; -window.tooltipSetup = tooltipSetup; - -// Override the bootstrap dropdown styles for mobile -function fixNavbarDropdownStyles() { - if (window.innerWidth < 1200) { - document.querySelectorAll('.navbar .dropdown-menu').forEach(function(menu) { - menu.style.transform = 'none'; - menu.style.transformOrigin = 'none'; - menu.style.left = '0'; - menu.style.right = '0'; - menu.style.maxWidth = '95vw'; - menu.style.width = '100vw'; - menu.style.marginBottom = '0'; - }); - } else { - document.querySelectorAll('.navbar .dropdown-menu').forEach(function(menu) { - menu.style.transform = ''; - menu.style.transformOrigin = ''; - menu.style.left = ''; - menu.style.right = ''; - menu.style.maxWidth = ''; - menu.style.width = ''; - menu.style.marginBottom = ''; - }); - } -} - -document.addEventListener('DOMContentLoaded', () => { - tooltipSetup(); - setupDropdowns(); - fixNavbarDropdownStyles(); - // Setup logout button functionality - const logoutButton = document.querySelector('a[href="/logout"]'); - if (logoutButton) { - logoutButton.addEventListener('click', function(event) { - event.preventDefault(); - if (window.JWTManager) { - window.JWTManager.logout(); - } else { - // Fallback if JWTManager is not available - window.location.href = '/logout'; - } - }); - } - -}); -window.addEventListener('resize', fixNavbarDropdownStyles); diff --git a/app/core/src/main/resources/static/js/pages/add-image.js b/app/core/src/main/resources/static/js/pages/add-image.js deleted file mode 100644 index 6a40145b7..000000000 --- a/app/core/src/main/resources/static/js/pages/add-image.js +++ /dev/null @@ -1,85 +0,0 @@ -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -window.goToFirstOrLastPage = goToFirstOrLastPage; - -document.getElementById('download-pdf').addEventListener('click', async () => { - const downloadButton = document.getElementById('download-pdf'); - const originalContent = downloadButton.innerHTML; - - downloadButton.disabled = true; - downloadButton.innerHTML = ` - - `; - - try { - const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument(); - const modifiedPdfBytes = await modifiedPdf.save(); - const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' }); - const link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.download = originalFileName + '_addedImage.pdf'; - link.click(); - } finally { - downloadButton.disabled = false; - downloadButton.innerHTML = originalContent; - } -}); -let originalFileName = ''; -document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => { - const fileInput = event.target; - fileInput.addEventListener('file-input-change', async (e) => { - const { allFiles } = e.detail; - if (allFiles && allFiles.length > 0) { - const file = allFiles[0]; - originalFileName = file.name.replace(/\.[^/.]+$/, ''); - const pdfData = await file.arrayBuffer(); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - const pdfDoc = await pdfjsLib.getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: pdfData, - }).promise; - await DraggableUtils.renderPage(pdfDoc, 0); - - document.querySelectorAll('.show-on-file-selected').forEach((el) => { - el.style.cssText = ''; - }); - } - }); -}); -document.addEventListener('DOMContentLoaded', () => { - document.querySelectorAll('.show-on-file-selected').forEach((el) => { - el.style.cssText = 'display:none !important'; - }); - document.addEventListener('keydown', (e) => { - if (e.key === 'Delete') { - DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted()); - } - }); -}); - -const imageUpload = document.querySelector('input[name=image-upload]'); -imageUpload.addEventListener('change', (e) => { - if (!e.target.files) { - return; - } - for (const imageFile of e.target.files) { - var reader = new FileReader(); - reader.readAsDataURL(imageFile); - reader.onloadend = function (e) { - DraggableUtils.createDraggableCanvasFromUrl(e.target.result); - }; - } -}); - -async function goToFirstOrLastPage(page) { - if (page) { - const lastPage = DraggableUtils.pdfDoc.numPages; - await DraggableUtils.goToPage(lastPage - 1); - } else { - await DraggableUtils.goToPage(0); - } -} diff --git a/app/core/src/main/resources/static/js/pages/adjust-contrast.js b/app/core/src/main/resources/static/js/pages/adjust-contrast.js deleted file mode 100644 index 506af1f8e..000000000 --- a/app/core/src/main/resources/static/js/pages/adjust-contrast.js +++ /dev/null @@ -1,262 +0,0 @@ -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -var canvas = document.getElementById('contrast-pdf-canvas'); -var context = canvas.getContext('2d'); -var originalImageData = null; -var allPages = []; -var pdfDoc = null; -var pdf = null; // This is the current PDF document - -async function renderPDFAndSaveOriginalImageData(file) { - var fileReader = new FileReader(); - fileReader.onload = async function () { - var data = new Uint8Array(this.result); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - pdf = await pdfjsLib.getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: data, - }).promise; - - // Get the number of pages in the PDF - var numPages = pdf.numPages; - allPages = Array.from({length: numPages}, (_, i) => i + 1); - - // Create a new PDF document - pdfDoc = await PDFLib.PDFDocument.create(); - // Render the first page in the viewer - await renderPageAndAdjustImageProperties(1); - document.getElementById('sliders-container').style.display = 'block'; - }; - fileReader.readAsArrayBuffer(file); -} - -// This function is now async and returns a promise -function renderPageAndAdjustImageProperties(pageNum) { - return new Promise(async function (resolve, reject) { - var page = await pdf.getPage(pageNum); - var scale = 1.5; - var viewport = page.getViewport({scale: scale}); - - canvas.height = viewport.height; - canvas.width = viewport.width; - - var renderContext = { - canvasContext: context, - viewport: viewport, - }; - - var renderTask = page.render(renderContext); - renderTask.promise.then(function () { - originalImageData = context.getImageData(0, 0, canvas.width, canvas.height); - adjustImageProperties(); - resolve(); - }); - canvas.classList.add('fixed-shadow-canvas'); - }); -} - -function adjustImageProperties() { - var contrast = parseFloat(document.getElementById('contrast-slider').value); - var brightness = parseFloat(document.getElementById('brightness-slider').value); - var saturation = parseFloat(document.getElementById('saturation-slider').value); - - contrast /= 100; // normalize to range [0, 2] - brightness /= 100; // normalize to range [0, 2] - saturation /= 100; // normalize to range [0, 2] - - if (originalImageData) { - var newImageData = context.createImageData(originalImageData.width, originalImageData.height); - newImageData.data.set(originalImageData.data); - - for (var i = 0; i < newImageData.data.length; i += 4) { - var r = newImageData.data[i]; - var g = newImageData.data[i + 1]; - var b = newImageData.data[i + 2]; - // Adjust contrast - r = adjustContrastForPixel(r, contrast); - g = adjustContrastForPixel(g, contrast); - b = adjustContrastForPixel(b, contrast); - // Adjust brightness - r = adjustBrightnessForPixel(r, brightness); - g = adjustBrightnessForPixel(g, brightness); - b = adjustBrightnessForPixel(b, brightness); - // Adjust saturation - var rgb = adjustSaturationForPixel(r, g, b, saturation); - newImageData.data[i] = rgb[0]; - newImageData.data[i + 1] = rgb[1]; - newImageData.data[i + 2] = rgb[2]; - } - context.putImageData(newImageData, 0, 0); - } -} - -function rgbToHsl(r, g, b) { - (r /= 255), (g /= 255), (b /= 255); - - var max = Math.max(r, g, b), - min = Math.min(r, g, b); - var h, - s, - l = (max + min) / 2; - - if (max === min) { - h = s = 0; // achromatic - } else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - } - - h /= 6; - } - - return [h, s, l]; -} - -function hslToRgb(h, s, l) { - var r, g, b; - - if (s === 0) { - r = g = b = l; // achromatic - } else { - var hue2rgb = function hue2rgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - }; - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - - return [r * 255, g * 255, b * 255]; -} - -function adjustContrastForPixel(pixel, contrast) { - // Normalize to range [-0.5, 0.5] - var normalized = pixel / 255 - 0.5; - - // Apply contrast - normalized *= contrast; - - // Denormalize back to [0, 255] - return (normalized + 0.5) * 255; -} - -function clamp(value, min, max) { - return Math.min(Math.max(value, min), max); -} - -function adjustSaturationForPixel(r, g, b, saturation) { - var hsl = rgbToHsl(r, g, b); - - // Adjust saturation - hsl[1] = clamp(hsl[1] * saturation, 0, 1); - - // Convert back to RGB - var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]); - - // Return adjusted RGB values - return rgb; -} - -function adjustBrightnessForPixel(pixel, brightness) { - return Math.max(0, Math.min(255, pixel * brightness)); -} -let inputFileName = ''; -async function downloadPDF() { - for (var i = 0; i < allPages.length; i++) { - await renderPageAndAdjustImageProperties(allPages[i]); - const pngImageBytes = canvas.toDataURL('image/png'); - const pngImage = await pdfDoc.embedPng(pngImageBytes); - const pngDims = pngImage.scale(1); - - // Create a blank page matching the dimensions of the image - const page = pdfDoc.addPage([pngDims.width, pngDims.height]); - - // Draw the PNG image - page.drawImage(pngImage, { - x: 0, - y: 0, - width: pngDims.width, - height: pngDims.height, - }); - } - - // Serialize the PDFDocument to bytes (a Uint8Array) - const pdfBytes = await pdfDoc.save(); - - // Create a Blob - const blob = new Blob([pdfBytes.buffer], {type: 'application/pdf'}); - - // Create download link - const downloadLink = document.createElement('a'); - downloadLink.href = URL.createObjectURL(blob); - let newFileName = inputFileName ? inputFileName.replace('.pdf', '') : 'download'; - newFileName += '_adjusted_color.pdf'; - - downloadLink.download = newFileName; - downloadLink.click(); - - // After download, reset the viewer and clear stored data - allPages = []; // Clear the pages - originalImageData = null; // Clear the image data - - // Go back to page 1 and render it in the viewer - if (pdf !== null) { - renderPageAndAdjustImageProperties(1); - } -} - -// Event listeners -document.getElementById('fileInput-input').addEventListener('change', function (e) { - const fileInput = e.target; - fileInput.addEventListener('file-input-change', async (e) => { - const {allFiles} = e.detail; - if (allFiles && allFiles.length > 0) { - const file = allFiles[0]; - inputFileName = file.name; - renderPDFAndSaveOriginalImageData(file); - } - }); -}); - -document.getElementById('contrast-slider').addEventListener('input', function () { - document.getElementById('contrast-val').textContent = this.value; - adjustImageProperties(); -}); - -document.getElementById('brightness-slider').addEventListener('input', function () { - document.getElementById('brightness-val').textContent = this.value; - adjustImageProperties(); -}); - -document.getElementById('saturation-slider').addEventListener('input', function () { - document.getElementById('saturation-val').textContent = this.value; - adjustImageProperties(); -}); - -document.getElementById('download-button').addEventListener('click', function () { - downloadPDF(); -}); diff --git a/app/core/src/main/resources/static/js/pages/change-metadata.js b/app/core/src/main/resources/static/js/pages/change-metadata.js deleted file mode 100644 index 25d8565a0..000000000 --- a/app/core/src/main/resources/static/js/pages/change-metadata.js +++ /dev/null @@ -1,161 +0,0 @@ -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -const deleteAllCheckbox = document.querySelector('#deleteAll'); -let inputs = document.querySelectorAll('input'); -const customMetadataDiv = document.getElementById('customMetadata'); -const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries'); - -deleteAllCheckbox.addEventListener('change', function (event) { - inputs.forEach((input) => { - // If it's the deleteAllCheckbox or any file input, skip - if (input === deleteAllCheckbox || input.type === 'file') { - return; - } - // Disable or enable based on the checkbox state - input.disabled = deleteAllCheckbox.checked; - }); -}); - -const customModeCheckbox = document.getElementById('customModeCheckbox'); -const addMetadataBtn = document.getElementById('addMetadataBtn'); -const customMetadataFormContainer = document.getElementById('customMetadataEntries'); -var count = 1; -const fileInput = document.querySelector('#fileInput-input'); -const authorInput = document.querySelector('#author'); -const creationDateInput = document.querySelector('#creationDate'); -const creatorInput = document.querySelector('#creator'); -const keywordsInput = document.querySelector('#keywords'); -const modificationDateInput = document.querySelector('#modificationDate'); -const producerInput = document.querySelector('#producer'); -const subjectInput = document.querySelector('#subject'); -const titleInput = document.querySelector('#title'); -const trappedInput = document.querySelector('#trapped'); -var lastPDFFileMeta = null; -var lastPDFFile = null; - -fileInput.addEventListener('change', async function () { - fileInput.addEventListener('file-input-change', async (e) => { - const {allFiles} = e.detail; - if (allFiles && allFiles.length > 0) { - const file = allFiles[0]; - while (otherMetadataEntriesDiv.firstChild) { - otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild); - } - while (customMetadataFormContainer.firstChild) { - customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild); - } - var url = URL.createObjectURL(file); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - const pdf = await pdfjsLib - .getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - url: url, - }) - .promise; - const pdfMetadata = await pdf.getMetadata(); - lastPDFFile = pdfMetadata?.info; - console.log(pdfMetadata); - if (!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) { - customModeCheckbox.disabled = true; - customModeCheckbox.checked = false; - } else { - customModeCheckbox.disabled = false; - } - authorInput.value = pdfMetadata?.info?.Author; - creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate); - creatorInput.value = pdfMetadata?.info?.Creator; - keywordsInput.value = pdfMetadata?.info?.Keywords; - modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate); - producerInput.value = pdfMetadata?.info?.Producer; - subjectInput.value = pdfMetadata?.info?.Subject; - titleInput.value = pdfMetadata?.info?.Title; - console.log(pdfMetadata?.info); - const trappedValue = pdfMetadata?.info?.Trapped; - // Get all options in the select element - const options = trappedInput.options; - // Loop through all options to find the one with a matching value - for (let i = 0; i < options.length; i++) { - if (options[i].value === trappedValue) { - options[i].selected = true; - break; - } - } - addExtra(); - } - }); -}); - -addMetadataBtn.addEventListener('click', () => { - const keyInput = document.createElement('input'); - keyInput.type = 'text'; - keyInput.placeholder = 'Key'; - keyInput.className = 'form-control'; - keyInput.name = `allRequestParams[customKey${count}]`; - - const valueInput = document.createElement('input'); - valueInput.type = 'text'; - valueInput.placeholder = 'Value'; - valueInput.className = 'form-control'; - valueInput.name = `allRequestParams[customValue${count}]`; - count = count + 1; - - const formGroup = document.createElement('div'); - formGroup.className = 'mb-3'; - formGroup.appendChild(keyInput); - formGroup.appendChild(valueInput); - - customMetadataFormContainer.appendChild(formGroup); -}); -function convertDateFormat(dateTimeString) { - if (!dateTimeString || dateTimeString.length < 17) { - return dateTimeString; - } - - const year = dateTimeString.substring(2, 6); - const month = dateTimeString.substring(6, 8); - const day = dateTimeString.substring(8, 10); - const hour = dateTimeString.substring(10, 12); - const minute = dateTimeString.substring(12, 14); - const second = dateTimeString.substring(14, 16); - - return year + '/' + month + '/' + day + ' ' + hour + ':' + minute + ':' + second; -} - -function addExtra() { - const event = document.getElementById('customModeCheckbox'); - if (event.checked && lastPDFFile.Custom != null) { - customMetadataDiv.style.display = 'block'; - for (const [key, value] of Object.entries(lastPDFFile.Custom)) { - if ( - key === 'Author' || - key === 'CreationDate' || - key === 'Creator' || - key === 'Keywords' || - key === 'ModDate' || - key === 'Producer' || - key === 'Subject' || - key === 'Title' || - key === 'Trapped' - ) { - continue; - } - const entryDiv = document.createElement('div'); - entryDiv.className = 'mb-3'; - entryDiv.innerHTML = `
`; - otherMetadataEntriesDiv.appendChild(entryDiv); - } - } else { - customMetadataDiv.style.display = 'none'; - while (otherMetadataEntriesDiv.firstChild) { - otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild); - } - } -} - -customModeCheckbox.addEventListener('change', (event) => { - addExtra(); -}); diff --git a/app/core/src/main/resources/static/js/pages/crop.js b/app/core/src/main/resources/static/js/pages/crop.js deleted file mode 100644 index f6af3e5e2..000000000 --- a/app/core/src/main/resources/static/js/pages/crop.js +++ /dev/null @@ -1,195 +0,0 @@ -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -let pdfCanvas = document.getElementById('cropPdfCanvas'); -let overlayCanvas = document.getElementById('overlayCanvas'); -let canvasesContainer = document.getElementById('canvasesContainer'); -canvasesContainer.style.display = 'none'; - -let context = pdfCanvas.getContext('2d'); -let overlayContext = overlayCanvas.getContext('2d'); - -overlayCanvas.width = pdfCanvas.width; -overlayCanvas.height = pdfCanvas.height; - -let isDrawing = false; // New flag to check if drawing is ongoing - -let cropForm = document.getElementById('cropForm'); -let fileInput = document.getElementById('fileInput-input'); -let xInput = document.getElementById('x'); -let yInput = document.getElementById('y'); -let widthInput = document.getElementById('width'); -let heightInput = document.getElementById('height'); - -let pdfDoc = null; -let currentPage = 1; -let totalPages = 0; - -let startX = 0; -let startY = 0; -let rectWidth = 0; -let rectHeight = 0; - -let pageScale = 1; // The scale which the pdf page renders -let timeId = null; // timeout id for resizing canvases event -let currentRenderTask = null; // Track current PDF render task to cancel if needed - -function renderPageFromFile(file) { - if (file.type === 'application/pdf') { - // Cancel any ongoing render task when loading a new file - if (currentRenderTask) { - currentRenderTask.cancel(); - currentRenderTask = null; - } - - let reader = new FileReader(); - reader.onload = function (ev) { - let typedArray = new Uint8Array(reader.result); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - pdfjsLib - .getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: typedArray, - }) - .promise.then(function (pdf) { - pdfDoc = pdf; - totalPages = pdf.numPages; - renderPage(currentPage); - }); - }; - reader.readAsArrayBuffer(file); - } -} - -window.addEventListener('resize', function () { - clearTimeout(timeId); - - timeId = setTimeout(function () { - if (!pdfDoc) return; // Only resize if we have a PDF loaded - let canvasesContainer = document.getElementById('canvasesContainer'); - let containerRect = canvasesContainer.getBoundingClientRect(); - - context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); - - overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); - - // Re-render with new container size - renderPage(currentPage); - }, 1000); -}); - -fileInput.addEventListener('file-input-change', async (e) => { - if (!e.detail) return; // Guard against null detail - const {allFiles} = e.detail; - if (allFiles && allFiles.length > 0) { - canvasesContainer.style.display = 'block'; // set for visual purposes - - // Wait for the layout to be updated before rendering - setTimeout(() => { - let file = allFiles[0]; - renderPageFromFile(file); - }, 100); - } -}); - -cropForm.addEventListener('submit', function (e) { - if (xInput.value == '' && yInput.value == '' && widthInput.value == '' && heightInput.value == '') { - // Set coordinates for the entire PDF surface - let currentContainerRect = canvasesContainer.getBoundingClientRect(); - xInput.value = 0; - yInput.value = 0; - widthInput.value = currentContainerRect.width; - heightInput.value = currentContainerRect.height; - } -}); - -overlayCanvas.addEventListener('mousedown', function (e) { - // Clear previously drawn rectangle on the main canvas - context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); - renderPage(currentPage); // Re-render the PDF - - // Clear the overlay canvas to ensure old drawings are removed - overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); - - startX = e.offsetX; - startY = e.offsetY; - isDrawing = true; -}); - -overlayCanvas.addEventListener('mousemove', function (e) { - if (!isDrawing) return; - overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear previous rectangle - - rectWidth = e.offsetX - startX; - rectHeight = e.offsetY - startY; - overlayContext.strokeStyle = 'red'; - overlayContext.strokeRect(startX, startY, rectWidth, rectHeight); -}); - -overlayCanvas.addEventListener('mouseup', function (e) { - isDrawing = false; - - rectWidth = e.offsetX - startX; - rectHeight = e.offsetY - startY; - - let flippedY = pdfCanvas.height - e.offsetY; - - xInput.value = startX / pageScale; - yInput.value = flippedY / pageScale; - widthInput.value = rectWidth / pageScale; - heightInput.value = rectHeight / pageScale; - - // Draw the final rectangle on the main canvas - context.strokeStyle = 'red'; - context.strokeRect(startX, startY, rectWidth, rectHeight); - - overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear the overlay -}); - -function renderPage(pageNumber) { - // Cancel any ongoing render task - if (currentRenderTask) { - currentRenderTask.cancel(); - currentRenderTask = null; - } - - pdfDoc.getPage(pageNumber).then(function (page) { - let canvasesContainer = document.getElementById('canvasesContainer'); - let containerRect = canvasesContainer.getBoundingClientRect(); - - pageScale = containerRect.width / page.getViewport({scale: 1}).width; // The new scale - - // Normalize rotation to 0, 90, 180, or 270 degrees - let normalizedRotation = ((page.rotate % 360) + 360) % 360; - let viewport = page.getViewport({scale: pageScale, rotation: normalizedRotation}); - - // Don't set container width, let CSS handle it - canvasesContainer.style.height = viewport.height + 'px'; - - pdfCanvas.width = viewport.width; - pdfCanvas.height = viewport.height; - - overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas - overlayCanvas.height = viewport.height; - - context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); - - context.fillStyle = 'white'; - context.fillRect(0, 0, pdfCanvas.width, pdfCanvas.height); - - let renderContext = {canvasContext: context, viewport: viewport}; - currentRenderTask = page.render(renderContext); - currentRenderTask.promise.then(function() { - currentRenderTask = null; - pdfCanvas.classList.add('shadow-canvas'); - }).catch(function(error) { - if (error.name !== 'RenderingCancelledException') { - console.error('PDF rendering error:', error); - } - currentRenderTask = null; - }); - }); -} diff --git a/app/core/src/main/resources/static/js/pages/edit-table-of-contents.js b/app/core/src/main/resources/static/js/pages/edit-table-of-contents.js deleted file mode 100644 index 9b8cb5cdd..000000000 --- a/app/core/src/main/resources/static/js/pages/edit-table-of-contents.js +++ /dev/null @@ -1,802 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - const bookmarksContainer = document.getElementById("bookmarks-container"); - const errorMessageContainer = document.getElementById("error-message-container"); - const addBookmarkBtn = document.getElementById("addBookmarkBtn"); - const bookmarkDataInput = document.getElementById("bookmarkData"); - let bookmarks = []; - let counter = 0; // Used for generating unique IDs - - // callback function on file input change to extract bookmarks from PDF - async function getBookmarkDataFromPdf(event) { - if (!event.target.files || event.target.files.length === 0) { - return; - } - - const formData = new FormData(); - formData.append("file", event.target.files[0]); - - try { - // Call the API to extract bookmarks using fetchWithCsrf for CSRF protection - const response = await fetchWithCsrf("/api/v1/general/extract-bookmarks", { - method: "POST", - body: formData, - }); - - if (!response.ok) { - throw new Error(`Failed to fetch API: ${response.status} ${response.statusText}`); - } - - const extractedBookmarks = await response.json(); - return extractedBookmarks; - } catch (error) { - throw new Error("Error extracting bookmark data:", error); - } - } - - // callback function on file input change to extract bookmarks from JSON - async function getBookmarkDataFromJson(event) { - if (!event.target.files || event.target.files.length === 0) { - return; - } - - const file = event.target.files[0]; - - try { - const fileText = await file.text(); - const jsonData = JSON.parse(fileText); - return jsonData; - } catch (error) { - throw new Error(`Error extracting bookmark data: error while reading or parsing JSON file: ${error.message}`); - } - } - - // display new bookmark data given by a callback function that loads or fetches the data - async function loadBookmarks(getBookmarkDataCallback) { - // reset bookmarks - bookmarks = []; - updateBookmarksUI(); - showLoadingIndicator(); - - try { - // Get new bookmarks from the callback - const newBookmarks = await getBookmarkDataCallback(); - - // Convert extracted bookmarks to our format with IDs - if (newBookmarks && newBookmarks.length > 0) { - bookmarks = newBookmarks.map(convertExtractedBookmark); - } - } catch (error) { - bookmarks = []; - throw new Error(`Error loading bookmarks: ${error}`); - } finally { - removeLoadingIndicator(); - updateBookmarksUI(); - } - } - - // Add event listener to the file input to extract existing bookmarks - document.getElementById("fileInput-input").addEventListener("change", async function (event) { - try { - await loadBookmarks(async function () { - return getBookmarkDataFromPdf(event); - }); - } catch { - showErrorMessage("Failed to extract bookmarks. You can still create new ones."); - } - }); - - function showLoadingIndicator() { - const loadingEl = document.createElement("div"); - loadingEl.className = "alert alert-info"; - loadingEl.textContent = "Loading bookmarks from PDF..."; - loadingEl.id = "loading-bookmarks"; - errorMessageContainer.innerHTML = ""; - bookmarksContainer.innerHTML = ""; - bookmarksContainer.appendChild(loadingEl); - } - - function removeLoadingIndicator() { - const loadingEl = document.getElementById("loading-bookmarks"); - if (loadingEl) { - loadingEl.remove(); - } - } - - function showErrorMessage(message) { - const errorEl = document.createElement("div"); - errorEl.className = "alert alert-danger"; - errorEl.textContent = message; - errorMessageContainer.appendChild(errorEl); - } - - function showEmptyState() { - const emptyStateEl = document.createElement("div"); - emptyStateEl.className = "empty-bookmarks mb-3"; - emptyStateEl.innerHTML = ` - bookmark_add -
No bookmarks found
-

This PDF doesn't have any bookmarks yet. Add your first bookmark to get started.

- - `; - - // Add event listener to the "Add First Bookmark" button - emptyStateEl.querySelector(".btn-add-first-bookmark").addEventListener("click", function () { - addBookmark(null, "New Bookmark", 1); - emptyStateEl.remove(); - }); - - bookmarksContainer.appendChild(emptyStateEl); - } - - // Function to convert extracted bookmarks to our format with IDs - function convertExtractedBookmark(bookmark) { - counter++; - const result = { - id: Date.now() + counter, // Generate a unique ID - title: bookmark.title || "Untitled Bookmark", - pageNumber: bookmark.pageNumber || 1, - children: [], - expanded: false, // All bookmarks start collapsed for better visibility - }; - - // Convert children recursively - if (bookmark.children && bookmark.children.length > 0) { - result.children = bookmark.children.map((child) => { - return convertExtractedBookmark(child); - }); - } - - return result; - } - - // Add bookmark button click handler - addBookmarkBtn.addEventListener("click", function (e) { - e.preventDefault(); - addBookmark(); - }); - - // Add form submit handler to update JSON data - document.getElementById("editTocForm").addEventListener("submit", function () { - updateBookmarkData(); - }); - - function addBookmark(parent = null, title = "", pageNumber = 1) { - counter++; - const newBookmark = { - id: Date.now() + counter, - title: title || "New Bookmark", - pageNumber: pageNumber || 1, - children: [], - expanded: false, // New bookmarks start collapsed - }; - - if (parent === null) { - bookmarks.push(newBookmark); - } else { - const parentBookmark = findBookmark(bookmarks, parent); - if (parentBookmark) { - parentBookmark.children.push(newBookmark); - parentBookmark.expanded = true; // Auto-expand the parent when adding a child - } else { - // Add to root level if parent not found - bookmarks.push(newBookmark); - } - } - - updateBookmarksUI(); - - // After updating UI, find and focus the new bookmark's title field - setTimeout(() => { - const newElement = document.querySelector(`[data-id="${newBookmark.id}"]`); - if (newElement) { - const titleInput = newElement.querySelector(".bookmark-title"); - if (titleInput) { - titleInput.focus(); - titleInput.select(); - } - // Scroll to the new element - newElement.scrollIntoView({ behavior: "smooth", block: "center" }); - } - }, 50); - } - - function findBookmark(bookmarkArray, id) { - for (const bookmark of bookmarkArray) { - if (bookmark.id === id) { - return bookmark; - } - if (bookmark.children.length > 0) { - const found = findBookmark(bookmark.children, id); - if (found) return found; - } - } - return null; - } - - // Find the parent bookmark of a given bookmark by ID - function findParentBookmark(bookmarkArray, id, parent = null) { - for (const bookmark of bookmarkArray) { - if (bookmark.id === id) { - return parent; // Return the parent ID (or null if top-level) - } - - if (bookmark.children.length > 0) { - const found = findParentBookmark(bookmark.children, id, bookmark.id); - if (found !== undefined) return found; - } - } - return undefined; // Not found at this level - } - - function removeBookmark(id) { - // Remove from top level - const index = bookmarks.findIndex((b) => b.id === id); - if (index !== -1) { - bookmarks.splice(index, 1); - updateBookmarksUI(); - return; - } - - // Remove from children - function removeFromChildren(bookmarkArray, id) { - for (const bookmark of bookmarkArray) { - const childIndex = bookmark.children.findIndex((b) => b.id === id); - if (childIndex !== -1) { - bookmark.children.splice(childIndex, 1); - return true; - } - if (removeFromChildren(bookmark.children, id)) { - return true; - } - } - return false; - } - - if (removeFromChildren(bookmarks, id)) { - updateBookmarksUI(); - } - - // If no bookmarks left, show empty state - if (bookmarks.length === 0) { - showEmptyState(); - } - } - - function toggleBookmarkExpanded(id) { - const bookmark = findBookmark(bookmarks, id); - if (bookmark) { - bookmark.expanded = !bookmark.expanded; - updateBookmarksUI(); - } - } - - function updateBookmarkData() { - // Create a clean version without the IDs for submission - const cleanBookmarks = bookmarks.map(cleanBookmark); - bookmarkDataInput.value = JSON.stringify(cleanBookmarks); - } - - function cleanBookmark(bookmark) { - return { - title: bookmark.title, - pageNumber: bookmark.pageNumber, - children: bookmark.children.map(cleanBookmark), - }; - } - - function updateBookmarksUI() { - if (!bookmarksContainer) { - return; - } - - // Only clear the container if there are no error messages or loading indicators - if (!document.querySelector("#bookmarks-container .alert")) { - bookmarksContainer.innerHTML = ""; - } - - // Check if there are bookmarks to display - if (bookmarks.length === 0 && !document.querySelector(".empty-bookmarks")) { - showEmptyState(); - } else { - // Remove empty state if it exists and there are bookmarks - const emptyState = document.querySelector(".empty-bookmarks"); - if (emptyState && bookmarks.length > 0) { - emptyState.remove(); - } - - // Create bookmark elements - bookmarks.forEach((bookmark) => { - const bookmarkElement = createBookmarkElement(bookmark); - bookmarksContainer.appendChild(bookmarkElement); - }); - } - - updateBookmarkData(); - - // Initialize tooltips for dynamically added elements - window.tooltipSetup(); - } - - // Create the main bookmark element with collapsible interface - function createBookmarkElement(bookmark, level = 0) { - const bookmarkEl = document.createElement("div"); - bookmarkEl.className = "bookmark-item"; - bookmarkEl.dataset.id = bookmark.id; - bookmarkEl.dataset.level = level; - - // Create the header (always visible part) - const header = createBookmarkHeader(bookmark, level); - bookmarkEl.appendChild(header); - - // Create the content (collapsible part) - const content = document.createElement("div"); - content.className = "bookmark-content"; - if (!bookmark.expanded) { - content.style.display = "none"; - } - - // Main input row - const inputRow = createInputRow(bookmark); - content.appendChild(inputRow); - bookmarkEl.appendChild(content); - - // Add children container if has children and expanded - if (bookmark.children && bookmark.children.length > 0) { - const childrenContainer = createChildrenContainer(bookmark, level); - if (bookmark.expanded) { - bookmarkEl.appendChild(childrenContainer); - } - } - - return bookmarkEl; - } - - // Create the header that's always visible - function createBookmarkHeader(bookmark, level) { - const header = document.createElement("div"); - header.className = "bookmark-header"; - if (!bookmark.expanded) { - header.classList.add("collapsed"); - } - - // Left side of header with expand/collapse and info - const headerLeft = document.createElement("div"); - headerLeft.className = "d-flex align-items-center"; - - // Toggle expand/collapse icon with child count - const toggleContainer = document.createElement("div"); - toggleContainer.className = "d-flex align-items-center"; - toggleContainer.style.marginRight = "8px"; - - // Only show toggle if has children - if (bookmark.children && bookmark.children.length > 0) { - // Create toggle icon - const toggleIcon = document.createElement("span"); - toggleIcon.className = "material-symbols-rounded toggle-icon me-1"; - toggleIcon.textContent = "expand_more"; - toggleIcon.style.cursor = "pointer"; - toggleContainer.appendChild(toggleIcon); - - // Add child count indicator - const childCount = document.createElement("span"); - childCount.className = "badge rounded-pill"; - // Use theme-appropriate badge color - const isDarkMode = document.documentElement.getAttribute("data-bs-theme") === "dark"; - childCount.classList.add(isDarkMode ? "bg-info" : "bg-secondary"); - childCount.style.fontSize = "0.7rem"; - childCount.style.padding = "0.2em 0.5em"; - childCount.textContent = bookmark.children.length; - childCount.title = `${bookmark.children.length} child bookmark${bookmark.children.length > 1 ? "s" : ""}`; - toggleContainer.appendChild(childCount); - } else { - // Add spacer if no children - const spacer = document.createElement("span"); - spacer.style.width = "24px"; - spacer.style.display = "inline-block"; - toggleContainer.appendChild(spacer); - } - - headerLeft.appendChild(toggleContainer); - - // Level indicator for nested items - if (level > 0) { - // Add relationship indicator visual line - const relationshipIndicator = document.createElement("div"); - relationshipIndicator.className = "bookmark-relationship-indicator"; - - const line = document.createElement("div"); - line.className = "relationship-line"; - relationshipIndicator.appendChild(line); - - const arrow = document.createElement("div"); - arrow.className = "relationship-arrow"; - relationshipIndicator.appendChild(arrow); - - header.appendChild(relationshipIndicator); - - // Text indicator - const levelIndicator = document.createElement("span"); - levelIndicator.className = "bookmark-level-indicator"; - levelIndicator.textContent = `Child`; - headerLeft.appendChild(levelIndicator); - } - - // Title preview - const titlePreview = document.createElement("span"); - titlePreview.className = "bookmark-title-preview"; - titlePreview.textContent = bookmark.title; - headerLeft.appendChild(titlePreview); - - // Page number preview - const pagePreview = document.createElement("span"); - pagePreview.className = "bookmark-page-preview"; - pagePreview.textContent = `Page ${bookmark.pageNumber}`; - headerLeft.appendChild(pagePreview); - - // Right side of header with action buttons - const headerRight = document.createElement("div"); - headerRight.className = "bookmark-actions-header"; - - // Quick add buttons with clear visual distinction - using Stirling-PDF's tooltip system - const quickAddChildButton = createButton("subdirectory_arrow_right", "btn-add-child", "Add child bookmark", function (e) { - e.preventDefault(); - e.stopPropagation(); - addBookmark(bookmark.id); - }); - - const quickAddSiblingButton = createButton("add", "btn-add-sibling", "Add sibling bookmark", function (e) { - e.preventDefault(); - e.stopPropagation(); - - // Find parent of current bookmark - const parentId = findParentBookmark(bookmarks, bookmark.id); - addBookmark(parentId, "", bookmark.pageNumber); // Same level as current bookmark - }); - - // Quick remove button - const quickRemoveButton = createButton("delete", "btn-outline-danger", "Remove bookmark", function (e) { - e.preventDefault(); - e.stopPropagation(); - - if ( - confirm( - "Are you sure you want to remove this bookmark" + (bookmark.children.length > 0 ? " and all its children?" : "?") - ) - ) { - removeBookmark(bookmark.id); - } - }); - - headerRight.appendChild(quickAddChildButton); - headerRight.appendChild(quickAddSiblingButton); - headerRight.appendChild(quickRemoveButton); - - // Assemble header - header.appendChild(headerLeft); - header.appendChild(headerRight); - - // Add click handler for expansion toggle - header.addEventListener("click", function (e) { - // Only toggle if not clicking on buttons - if (!e.target.closest("button")) { - toggleBookmarkExpanded(bookmark.id); - } - }); - - return header; - } - - function createInputRow(bookmark) { - const row = document.createElement("div"); - row.className = "row"; - - // Title input - row.appendChild(createTitleInputElement(bookmark)); - - // Page input - row.appendChild(createPageInputElement(bookmark)); - - return row; - } - - function createTitleInputElement(bookmark) { - const titleCol = document.createElement("div"); - titleCol.className = "col-md-8"; - - const titleGroup = document.createElement("div"); - titleGroup.className = "mb-3"; - - const titleLabel = document.createElement("label"); - titleLabel.textContent = "Title"; - titleLabel.className = "form-label"; - - const titleInput = document.createElement("input"); - titleInput.type = "text"; - titleInput.className = "form-control bookmark-title"; - titleInput.value = bookmark.title; - titleInput.addEventListener("input", function () { - bookmark.title = this.value; - updateBookmarkData(); - - // Also update the preview in the header - const header = titleInput.closest(".bookmark-item").querySelector(".bookmark-title-preview"); - if (header) { - header.textContent = this.value; - } - }); - - titleGroup.appendChild(titleLabel); - titleGroup.appendChild(titleInput); - titleCol.appendChild(titleGroup); - - return titleCol; - } - - function createPageInputElement(bookmark) { - const pageCol = document.createElement("div"); - pageCol.className = "col-md-4"; - - const pageGroup = document.createElement("div"); - pageGroup.className = "mb-3"; - - const pageLabel = document.createElement("label"); - pageLabel.textContent = "Page"; - pageLabel.className = "form-label"; - - const pageInput = document.createElement("input"); - pageInput.type = "number"; - pageInput.className = "form-control bookmark-page"; - pageInput.value = bookmark.pageNumber; - pageInput.min = 1; - pageInput.addEventListener("input", function () { - bookmark.pageNumber = parseInt(this.value) || 1; - updateBookmarkData(); - - // Also update the preview in the header - const header = pageInput.closest(".bookmark-item").querySelector(".bookmark-page-preview"); - if (header) { - header.textContent = `Page ${bookmark.pageNumber}`; - } - }); - - pageGroup.appendChild(pageLabel); - pageGroup.appendChild(pageInput); - pageCol.appendChild(pageGroup); - - return pageCol; - } - - function createButton(icon, className, title, clickHandler) { - const button = document.createElement("button"); - button.type = "button"; - button.className = `btn ${className} btn-bookmark-action`; - button.innerHTML = `${icon}`; - button.title = title; - - button.addEventListener("click", clickHandler); - return button; - } - - function createChildrenContainer(bookmark, level) { - const childrenContainer = document.createElement("div"); - childrenContainer.className = "bookmark-children"; - - bookmark.children.forEach((child) => { - childrenContainer.appendChild(createBookmarkElement(child, level + 1)); - }); - - return childrenContainer; - } - - // Update the add bookmark button appearance with clear visual cue - addBookmarkBtn.innerHTML = 'add Add Top-level Bookmark'; - addBookmarkBtn.className = "btn btn-primary btn-add-bookmark top-level"; - addBookmarkBtn.title = "Add a new top-level bookmark"; - - // Add icon to empty state button as well - const updateEmptyStateButton = function () { - const emptyStateBtn = document.querySelector(".btn-add-first-bookmark"); - if (emptyStateBtn) { - emptyStateBtn.innerHTML = 'add Add First Bookmark'; - emptyStateBtn.title = "Add first bookmark"; - - // Initialize tooltips for the empty state button - window.tooltipSetup(); - } - }; - - // Initialize with an empty state if no bookmarks - if (bookmarks.length === 0) { - showEmptyState(); - updateEmptyStateButton(); - } - - // Add bookmarks Import/Export functionality - - // Import/Export button references - const importDefaultBtn = document.getElementById("importDefaultBtn"); - const exportDefaultBtn = document.getElementById("exportDefaultBtn"); - const importUploadJsonFileInput = document.getElementById("importUploadJsonFileInput"); - const importPasteFromClipboardBtn = document.getElementById("importPasteFromClipboardBtn"); - const exportDownloadJsonFileBtn = document.getElementById("exportDownloadJsonFileBtn"); - const exportCopyToClipboardBtn = document.getElementById("exportCopyToClipboardBtn"); - - // display import/export from/to clipboard buttons if supported - if (navigator.clipboard && navigator.clipboard.readText) { - importPasteFromClipboardBtn.parentElement.classList.remove("d-none"); - } - if (navigator.clipboard && navigator.clipboard.writeText) { - exportCopyToClipboardBtn.parentElement.classList.remove("d-none"); - } - - function flashButtonSuccess(button) { - const originalClass = button.className; - - button.classList.remove("btn-outline-primary"); - button.classList.add("btn-success", "success-flash"); - - setTimeout(() => { - button.className = originalClass; - }, 1000); - } - - // Import handlers - async function handleJsonFileInputChange(event) { - try { - await loadBookmarks(async function () { - return getBookmarkDataFromJson(event); - }); - flashButtonSuccess(importDefaultBtn); - } catch (error) { - console.error(`Failed to import bookmarks from JSON file: ${error.message}`); - } - } - - async function importBookmarksFromClipboard() { - console.log("Importing bookmarks from clipboard..."); - - try { - await loadBookmarks(async function () { - const clipboardText = await navigator.clipboard.readText(); - if (!clipboardText) return []; - - return JSON.parse(clipboardText); - }); - flashButtonSuccess(importDefaultBtn); - } catch (error) { - console.error(`Failed to import bookmarks from clipboard: ${error.message}`); - } - } - - async function handleBookmarksPasteFromClipboard(event) { - // do not override normal paste behavior on input fields - if (event.target.tagName.toLowerCase() === "input") return; - - try { - await loadBookmarks(async function () { - const clipboardText = event.clipboardData?.getData("text/plain"); - if (!clipboardText) return []; - - return JSON.parse(clipboardText); - }); - flashButtonSuccess(importDefaultBtn); - } catch (error) { - console.error(`Failed to import bookmarks from clipboard (ctrl-v): ${error.message}`); - } - } - - // Export handlers - async function exportBookmarksToJson() { - console.log("Exporting bookmarks to JSON..."); - - try { - const bookmarkData = bookmarkDataInput.value; - const blob = new Blob([bookmarkData], { type: "application/json" }); - const url = URL.createObjectURL(blob); - - const a = document.createElement("a"); - a.href = url; - a.download = "bookmarks.json"; - document.body.appendChild(a); - a.click(); - - document.body.removeChild(a); - URL.revokeObjectURL(url); - flashButtonSuccess(exportDefaultBtn); - } catch (error) { - console.error(`Failed to export bookmarks to JSON: ${error.message}`); - } - } - - async function exportBookmarksToClipboard() { - const bookmarkData = bookmarkDataInput.value; - try { - await navigator.clipboard.writeText(bookmarkData); - flashButtonSuccess(exportDefaultBtn); - } catch (error) { - console.error(`Failed to export bookmarks to clipboard: ${error.message}`); - } - } - - async function handleBookmarksCopyToClipboard(event) { - // do not override normal copy behavior on input fields - if (event.target.tagName.toLowerCase() === "input") return; - - const bookmarkData = bookmarkDataInput.value; - - try { - event.clipboardData.setData("text/plain", bookmarkData); - event.preventDefault(); - flashButtonSuccess(exportDefaultBtn); - } catch (error) { - console.error(`Failed to export bookmarks to clipboard (ctrl-c): ${error.message}`); - } - } - - // register event listeners for import/export functions - importUploadJsonFileInput.addEventListener("change", handleJsonFileInputChange); - importPasteFromClipboardBtn.addEventListener("click", importBookmarksFromClipboard); - exportDownloadJsonFileBtn.addEventListener("click", exportBookmarksToJson); - exportCopyToClipboardBtn.addEventListener("click", exportBookmarksToClipboard); - document.body.addEventListener("copy", handleBookmarksCopyToClipboard); - document.body.addEventListener("paste", handleBookmarksPasteFromClipboard); - // set default actions - // importDefaultBtn is already handled by being a label for the file input - exportDefaultBtn.addEventListener("click", exportBookmarksToJson); - - // Listen for theme changes to update badge colors - const observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - if (mutation.attributeName === "data-bs-theme") { - const isDarkMode = document.documentElement.getAttribute("data-bs-theme") === "dark"; - document.querySelectorAll(".badge").forEach((badge) => { - badge.classList.remove("bg-secondary", "bg-info"); - badge.classList.add(isDarkMode ? "bg-info" : "bg-secondary"); - }); - } - }); - }); - - observer.observe(document.documentElement, { attributes: true }); - - // Add visual enhancement to clearly show the top-level/child relationship - document.addEventListener("mouseover", function (e) { - // When hovering over add buttons, highlight their relationship targets - const button = e.target.closest(".btn-add-child, .btn-add-sibling"); - if (button) { - if (button.classList.contains("btn-add-child")) { - // Highlight parent-child relationship - const bookmarkItem = button.closest(".bookmark-item"); - if (bookmarkItem) { - bookmarkItem.style.boxShadow = "0 0 0 2px var(--btn-add-child-border, #198754)"; - } - } else if (button.classList.contains("btn-add-sibling")) { - // Highlight sibling relationship - const bookmarkItem = button.closest(".bookmark-item"); - if (bookmarkItem) { - // Find siblings - const parent = bookmarkItem.parentElement; - const siblings = parent.querySelectorAll(":scope > .bookmark-item"); - siblings.forEach((sibling) => { - if (sibling !== bookmarkItem) { - sibling.style.boxShadow = "0 0 0 2px var(--btn-add-sibling-border, #0d6efd)"; - } - }); - } - } - } - }); - - document.addEventListener("mouseout", function (e) { - // Remove highlights when not hovering - const button = e.target.closest(".btn-add-child, .btn-add-sibling"); - if (button) { - // Remove all highlights - document.querySelectorAll(".bookmark-item").forEach((item) => { - item.style.boxShadow = ""; - }); - } - }); -}); diff --git a/app/core/src/main/resources/static/js/pages/home.js b/app/core/src/main/resources/static/js/pages/home.js deleted file mode 100644 index a2673ef6c..000000000 --- a/app/core/src/main/resources/static/js/pages/home.js +++ /dev/null @@ -1,200 +0,0 @@ -/* 0) { - cookieBanner.style.display = "none"; - } else if (retryCount < 20) { - retryCount++; - setTimeout(hideCookieBanner, 100); - } -} -hideCookieBanner(); - } -}); -/*]]>*/function setAnalytics(enabled) { - fetchWithCsrf('api/v1/settings/update-enable-analytics', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(enabled), - }) - .then((response) => { - if (response.status === 200) { - console.log('Analytics setting updated successfully'); - bootstrap.Modal.getInstance(document.getElementById('analyticsModal')).hide(); - - if (typeof CookieConsent !== "undefined") { - if (enabled) { - CookieConsent.acceptCategory(['analytics']); - } else { - CookieConsent.acceptCategory([]); - } - } - - } else if (response.status === 208) { - console.log('Analytics setting has already been set. Please edit /config/settings.yml to change it.', response); - alert('Analytics setting has already been set. Please edit /config/settings.yml to change it.'); - } else { - throw new Error('Unexpected response status: ' + response.status); - } - }) - .catch((error) => { - console.error('Error updating analytics setting:', error); - alert('An error occurred while updating the analytics setting. Please try again.'); - }); -} - -updateFavoriteIcons(); -const contentPath = /*[[${@contextPath}]]*/ ''; - -const defaultView = localStorage.getItem('defaultView') || 'home'; // Default to "home" -if (defaultView === 'home-legacy') { - window.location.href = contentPath + 'home-legacy'; // Redirect to legacy view -} - -document.addEventListener('DOMContentLoaded', function () { - const surveyVersion = '3.0'; - const modal = new bootstrap.Modal(document.getElementById('surveyModal')); - const dontShowAgain = document.getElementById('dontShowAgain'); - const takeSurveyButton = document.getElementById('takeSurvey'); - - const pdfProcessingThresholds = [8, 15, 22, 35, 50, 75, 100, 150]; - - // Check if survey version changed and reset PDF processing count if it did - const storedVersion = localStorage.getItem('surveyVersion'); - if (storedVersion && storedVersion !== surveyVersion) { - localStorage.setItem('pdfProcessingCount', '0'); - localStorage.setItem('surveyVersion', surveyVersion); - } - - let pdfProcessingCount = parseInt(localStorage.getItem('pdfProcessingCount') || '0'); - - function shouldShowSurvey() { - if(!window.showSurvey) { - return false; - } - - if (localStorage.getItem('dontShowSurvey') === 'true' || localStorage.getItem('surveyTaken') === 'true') { - return false; - } - - // If survey version changed and we hit a threshold, show the survey - if (localStorage.getItem('surveyVersion') !== surveyVersion && pdfProcessingThresholds.includes(pdfProcessingCount)) { - return true; - } - - return pdfProcessingThresholds.includes(pdfProcessingCount); - } - - if (shouldShowSurvey()) { - modal.show(); - } - - dontShowAgain.addEventListener('change', function () { - if (this.checked) { - localStorage.setItem('dontShowSurvey', 'true'); - localStorage.setItem('surveyVersion', surveyVersion); - } else { - localStorage.removeItem('dontShowSurvey'); - localStorage.removeItem('surveyVersion'); - } - }); -if (takeSurveyButton) { - takeSurveyButton.addEventListener('click', function () { - localStorage.setItem('surveyTaken', 'true'); - localStorage.setItem('surveyVersion', surveyVersion); - modal.hide(); - }); -} - if (localStorage.getItem('dontShowSurvey')) { - modal.hide(); - } - - if (window.location.pathname === '/') { - const navItem = document.getElementById('navItemToHide'); - if (navItem) { - navItem.style.display = 'none'; - } - } - updateFavoritesDropdown(); -}); -function setAsDefault(value) { - localStorage.setItem('defaultView', value); - console.log(`Default view set to: ${value}`); -} - -function adjustVisibleElements() { - const container = document.querySelector('.recent-features'); - if(!container) return; - const subElements = Array.from(container.children); - - let totalWidth = 0; - - subElements.forEach((element) => { - totalWidth += 12 * parseFloat(getComputedStyle(document.documentElement).fontSize); - - if (totalWidth > window.innerWidth) { - element.style.display = 'none'; - } else { - element.style.display = 'block'; - } - }); -} - -function adjustContainerAlignment() { - document.querySelectorAll('.features-container').forEach((parent) => { - parent.querySelectorAll('.feature-rows').forEach((container) => { - const containerWidth = parent.offsetWidth; - if (containerWidth < 32 * parseFloat(getComputedStyle(document.documentElement).fontSize)) { - container.classList.add('single-column'); - } else { - container.classList.remove('single-column'); - } - }); - }); -} -function toolsManager() { - const convertToPDF = document.querySelector('#groupConvertTo'); - const convertFromPDF = document.querySelector('#groupConvertFrom'); - - if (convertToPDF && convertFromPDF) { - const itemsTo = Array.from(convertToPDF.querySelectorAll('.dropdown-item')).filter( - (item) => !item.querySelector('hr.dropdown-divider') - ); - const itemsFrom = Array.from(convertFromPDF.querySelectorAll('.dropdown-item')).filter( - (item) => !item.querySelector('hr.dropdown-divider') - ); - - const totalItems = itemsTo.length + itemsFrom.length; - - if (totalItems > 12) { - document.querySelectorAll('#convertGroup').forEach((element) => element.remove()); - document.querySelectorAll('#groupConvertTo').forEach((element) => (element.style.display = 'flex')); - document.querySelectorAll('#groupConvertFrom').forEach((element) => (element.style.display = 'flex')); - } else { - document.querySelectorAll('#convertGroup').forEach((element) => (element.style.display = 'flex')); - document.querySelectorAll('#groupConvertTo').forEach((element) => element.remove()); - document.querySelectorAll('#groupConvertFrom').forEach((element) => element.remove()); - } - } -} -document.addEventListener('DOMContentLoaded', function () { - toolsManager(); -}); - -window.addEventListener('load', () => { - adjustContainerAlignment(); - adjustVisibleElements(); -}); -window.addEventListener('resize', () => { - adjustContainerAlignment(); - adjustVisibleElements(); -}); diff --git a/app/core/src/main/resources/static/js/pages/pdf-to-csv.js b/app/core/src/main/resources/static/js/pages/pdf-to-csv.js deleted file mode 100644 index 1c3cc0124..000000000 --- a/app/core/src/main/resources/static/js/pages/pdf-to-csv.js +++ /dev/null @@ -1,159 +0,0 @@ -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -let pdfCanvas = document.getElementById('cropPdfCanvas'); -let overlayCanvas = document.getElementById('overlayCanvas'); -let canvasesContainer = document.getElementById('canvasesContainer'); -canvasesContainer.style.display = 'none'; -// let paginationBtnContainer = ; - -let context = pdfCanvas.getContext('2d'); -let overlayContext = overlayCanvas.getContext('2d'); - -let btn1Object = document.getElementById('previous-page-btn'); -let btn2Object = document.getElementById('next-page-btn'); -overlayCanvas.width = pdfCanvas.width; -overlayCanvas.height = pdfCanvas.height; - -let fileInput = document.getElementById('fileInput-input'); - -let file; - -let pdfDoc = null; -let pageNumbers = document.getElementById('pageNumbers'); -let currentPage = 1; -let totalPages = 0; - -let startX = 0; -let startY = 0; -let rectWidth = 0; -let rectHeight = 0; - -let timeId = null; // timeout id for resizing canvases event - -btn1Object.addEventListener('click', function (e) { - if (currentPage !== 1) { - currentPage = currentPage - 1; - pageNumbers.value = currentPage; - - if (file.type === 'application/pdf') { - let reader = new FileReader(); - reader.onload = function (ev) { - let typedArray = new Uint8Array(reader.result); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - pdfjsLib - .getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: typedArray, - }) - .promise.then(function (pdf) { - pdfDoc = pdf; - totalPages = pdf.numPages; - renderPage(currentPage); - }); - }; - reader.readAsArrayBuffer(file); - } - } -}); - -btn2Object.addEventListener('click', function (e) { - if (currentPage !== totalPages) { - currentPage = currentPage + 1; - pageNumbers.value = currentPage; - - if (file.type === 'application/pdf') { - let reader = new FileReader(); - reader.onload = function (ev) { - let typedArray = new Uint8Array(reader.result); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - pdfjsLib - .getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: typedArray, - }) - .promise.then(function (pdf) { - pdfDoc = pdf; - totalPages = pdf.numPages; - renderPage(currentPage); - }); - }; - reader.readAsArrayBuffer(file); - } - } -}); - -function renderPageFromFile(file) { - if (file.type === 'application/pdf') { - let reader = new FileReader(); - reader.onload = function (ev) { - let typedArray = new Uint8Array(reader.result); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - pdfjsLib - .getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: typedArray, - }) - .promise.then(function (pdf) { - pdfDoc = pdf; - totalPages = pdf.numPages; - renderPage(currentPage); - }); - pageNumbers.value = currentPage; - }; - reader.readAsArrayBuffer(file); - document.getElementById('pagination-button-container').style.display = 'flex'; - document.getElementById('instruction-text').style.display = 'block'; - } -} - -window.addEventListener('resize', function () { - clearTimeout(timeId); - timeId = setTimeout(function () { - if (fileInput.files.length == 0) return; - let canvasesContainer = document.getElementById('canvasesContainer'); - let containerRect = canvasesContainer.getBoundingClientRect(); - - context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); - - overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); - - pdfCanvas.width = containerRect.width; - pdfCanvas.height = containerRect.height; - - overlayCanvas.width = containerRect.width; - overlayCanvas.height = containerRect.height; - - let file = fileInput.files[0]; - renderPageFromFile(file); - }, 1000); -}); - -fileInput.addEventListener('change', function (e) { - fileInput.addEventListener('file-input-change', async (e) => { - const {allFiles} = e.detail; - if (allFiles && allFiles.length > 0) { - canvasesContainer.style.display = 'block'; // set for visual purposes - file = e.target.files[0]; - renderPageFromFile(file); - } - }); -}); - -function renderPage(pageNumber) { - pdfDoc.getPage(pageNumber).then(function (page) { - let viewport = page.getViewport({scale: 1.0}); - pdfCanvas.width = viewport.width; - pdfCanvas.height = viewport.height; - - overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas - overlayCanvas.height = viewport.height; - - let renderContext = {canvasContext: context, viewport: viewport}; - page.render(renderContext); - pdfCanvas.classList.add('shadow-canvas'); - }); -} diff --git a/app/core/src/main/resources/static/js/pages/sign.js b/app/core/src/main/resources/static/js/pages/sign.js deleted file mode 100644 index 48a15c780..000000000 --- a/app/core/src/main/resources/static/js/pages/sign.js +++ /dev/null @@ -1,415 +0,0 @@ -const PDFJS_DEFAULT_OPTIONS = { - cMapUrl: pdfjsPath + 'cmaps/', - cMapPacked: true, - standardFontDataUrl: pdfjsPath + 'standard_fonts/', -}; - -window.toggleSignatureView = toggleSignatureView; -window.previewSignature = previewSignature; -window.addSignatureFromPreview = addSignatureFromPreview; -window.addDraggableFromPad = addDraggableFromPad; -window.addDraggableFromText = addDraggableFromText; -window.goToFirstOrLastPage = goToFirstOrLastPage; - -let currentPreviewSrc = null; - -function getSelectedSignatureColor() { - const textPicker = document.getElementById('signature-color-text'); - const drawPicker = document.getElementById('signature-color'); - return (textPicker && textPicker.value) || (drawPicker && drawPicker.value) || '#000000'; -} - -function toggleSignatureView() { - const gridView = document.getElementById("gridView"); - const listView = document.getElementById("listView"); - const gridText = document.querySelector(".grid-view-text"); - const listText = document.querySelector(".list-view-text"); - - if (gridView.style.display !== "none") { - gridView.style.display = "none"; - listView.style.display = "block"; - gridText.style.display = "none"; - listText.style.display = "inline"; - } else { - gridView.style.display = "block"; - listView.style.display = "none"; - gridText.style.display = "inline"; - listText.style.display = "none"; - } -} - -function previewSignature(element) { - const src = element.dataset.src; - currentPreviewSrc = src; - - const filename = element.querySelector(".signature-list-name").textContent; - - const previewImage = document.getElementById("previewImage"); - const previewFileName = document.getElementById("previewFileName"); - - previewImage.src = src; - previewFileName.textContent = filename; - - const modal = new bootstrap.Modal( - document.getElementById("signaturePreview") - ); - modal.show(); -} - -function addSignatureFromPreview() { - if (currentPreviewSrc) { - DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc); - bootstrap.Modal.getInstance( - document.getElementById("signaturePreview") - ).hide(); - } -} - -let originalFileName = ""; -document - .querySelector("input[name=pdf-upload]") - .addEventListener("change", async (event) => { - const fileInput = event.target; - fileInput.addEventListener("file-input-change", async (e) => { - const { allFiles } = e.detail; - if (allFiles && allFiles.length > 0) { - const file = allFiles[0]; - originalFileName = file.name.replace(/\.[^/.]+$/, ""); - const pdfData = await file.arrayBuffer(); - pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath + 'pdf.worker.mjs'; - const pdfDoc = await pdfjsLib.getDocument({ - ...PDFJS_DEFAULT_OPTIONS, - data: pdfData, - }).promise; - await DraggableUtils.renderPage(pdfDoc, 0); - - document.querySelectorAll(".show-on-file-selected").forEach((el) => { - el.style.cssText = ""; - }); - } - }); - }); - -document.addEventListener("DOMContentLoaded", () => { - document.querySelectorAll(".show-on-file-selected").forEach((el) => { - el.style.cssText = "display:none !important"; - }); - document - .querySelectorAll(".small-file-container-saved img ") - .forEach((img) => { - img.addEventListener("dragstart", (e) => { - e.dataTransfer.setData("fileUrl", img.src); - }); - }); - document.addEventListener("keydown", (e) => { - if (e.key === "Delete") { - DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted()); - } - }); - - addCustomSelect(); -}); - -function addCustomSelect() { - let customSelectElementContainer = - document.getElementById("signFontSelection"); - let originalSelectElement = - customSelectElementContainer.querySelector("select"); - - let optionsCount = originalSelectElement.length; - - let selectedItem = createAndStyleSelectedItem(); - - customSelectElementContainer.appendChild(selectedItem); - - let customSelectionsOptionsContainer = createCustomOptionsContainer(); - createAndAddCustomOptions(); - customSelectElementContainer.appendChild(customSelectionsOptionsContainer); - - selectedItem.addEventListener("click", function (e) { - /* When the select box is clicked, close any other select boxes, - and open/close the current select box: */ - e.stopPropagation(); - closeAllSelect(this); - this.nextSibling.classList.toggle("select-hide"); - this.classList.toggle("select-arrow-active"); - }); - - function createAndAddCustomOptions() { - for (let j = 0; j < optionsCount; j++) { - /* For each option in the original select element, - create a new DIV that will act as an option item: */ - let customOptionItem = createAndStyleCustomOption(j); - - customOptionItem.addEventListener("click", onCustomOptionClick); - customSelectionsOptionsContainer.appendChild(customOptionItem); - } - } - - function createCustomOptionsContainer() { - let customSelectionsOptionsContainer = document.createElement("DIV"); - customSelectionsOptionsContainer.setAttribute( - "class", - "select-items select-hide" - ); - return customSelectionsOptionsContainer; - } - - function createAndStyleSelectedItem() { - let selectedItem = document.createElement("DIV"); - selectedItem.setAttribute("class", "select-selected"); - selectedItem.innerHTML = - originalSelectElement.options[ - originalSelectElement.selectedIndex - ].innerHTML; - - selectedItem.style.fontFamily = window.getComputedStyle( - originalSelectElement.options[originalSelectElement.selectedIndex] - ).fontFamily; - return selectedItem; - } - - function onCustomOptionClick(e) { - /* When an item is clicked, update the original select box, - and the selected item: */ - let selectElement = - this.parentNode.parentNode.getElementsByTagName("select")[0]; - let optionsCount = selectElement.length; - let currentlySelectedCustomOption = this.parentNode.previousSibling; - for (let i = 0; i < optionsCount; i++) { - if (selectElement.options[i].innerHTML == this.innerHTML) { - selectElement.selectedIndex = i; - currentlySelectedCustomOption.innerHTML = this.innerHTML; - currentlySelectedCustomOption.style.fontFamily = this.style.fontFamily; - - let previouslySelectedOption = - this.parentNode.getElementsByClassName("same-as-selected"); - - if (previouslySelectedOption && previouslySelectedOption.length > 0) - previouslySelectedOption[0].classList.remove("same-as-selected"); - - this.classList.add("same-as-selected"); - break; - } - } - currentlySelectedCustomOption.click(); - } - - function createAndStyleCustomOption(j) { - let customOptionItem = document.createElement("DIV"); - customOptionItem.innerHTML = originalSelectElement.options[j].innerHTML; - customOptionItem.classList.add(originalSelectElement.options[j].className); - customOptionItem.style.fontFamily = window.getComputedStyle( - originalSelectElement.options[j] - ).fontFamily; - - if (j == originalSelectElement.selectedIndex) - customOptionItem.classList.add("same-as-selected"); - return customOptionItem; - } - - function closeAllSelect(element) { - /* A function that will close all select boxes in the document, - except the current select box: */ - let allSelectedOptions = document.getElementsByClassName("select-selected"); - let allSelectedOptionsCount = allSelectedOptions.length; - let indicesOfContainersToHide = []; - for (let i = 0; i < allSelectedOptionsCount; i++) { - if (element == allSelectedOptions[i]) { - indicesOfContainersToHide.push(i); - } else { - allSelectedOptions[i].classList.remove("select-arrow-active"); - } - } - - hideOptionsContainers(indicesOfContainersToHide); - } - - /* If the user clicks anywhere outside the select box, - then close all select boxes: */ - document.addEventListener("click", closeAllSelect); - - function hideOptionsContainers(containersIndices) { - let allOptionsContainers = document.getElementsByClassName("select-items"); - let allSelectionListsContainerCount = allOptionsContainers.length; - for (let i = 0; i < allSelectionListsContainerCount; i++) { - if (containersIndices.indexOf(i)) { - allOptionsContainers[i].classList.add("select-hide"); - } - } - } -} - -const imageUpload = document.querySelector("input[name=image-upload]"); -imageUpload.addEventListener("change", (e) => { - if (!e.target.files) return; - for (const imageFile of e.target.files) { - var reader = new FileReader(); - reader.readAsDataURL(imageFile); - reader.onloadend = function (e) { - DraggableUtils.createDraggableCanvasFromUrl(e.target.result); - }; - } -}); - -const signaturePadCanvas = document.getElementById("drawing-pad-canvas"); -const signaturePad = new SignaturePad(signaturePadCanvas, { - minWidth: 1, - maxWidth: 2, - penColor: "#000000", -}); - -// Keep pad color in sync if draw picker exists -(function initPadColorSync() { - const drawPicker = document.getElementById('signature-color'); - if (!drawPicker) return; - if (drawPicker.value) signaturePad.penColor = drawPicker.value; - drawPicker.addEventListener('input', () => { - signaturePad.penColor = drawPicker.value || '#000000'; - }); -})(); - -function addDraggableFromPad() { - if (signaturePad.isEmpty()) return; - const startTime = Date.now(); - const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas); - console.log(Date.now() - startTime); - DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl); -} - -function getCroppedCanvasDataUrl(canvas) { - let originalCtx = canvas.getContext("2d"); - let originalWidth = canvas.width; - let originalHeight = canvas.height; - let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight); - - let minX = originalWidth + 1, - maxX = -1, - minY = originalHeight + 1, - maxY = -1, - x = 0, - y = 0, - currentPixelColorValueIndex; - - for (y = 0; y < originalHeight; y++) { - for (x = 0; x < originalWidth; x++) { - currentPixelColorValueIndex = (y * originalWidth + x) * 4; - let currentPixelAlphaValue = - imageData.data[currentPixelColorValueIndex + 3]; - if (currentPixelAlphaValue > 0) { - if (minX > x) minX = x; - if (maxX < x) maxX = x; - if (minY > y) minY = y; - if (maxY < y) maxY = y; - } - } - } - - let croppedWidth = maxX - minX; - let croppedHeight = maxY - minY; - if (croppedWidth < 0 || croppedHeight < 0) return null; - let cuttedImageData = originalCtx.getImageData( - minX, - minY, - croppedWidth, - croppedHeight - ); - - let croppedCanvas = document.createElement("canvas"), - croppedCtx = croppedCanvas.getContext("2d"); - - croppedCanvas.width = croppedWidth; - croppedCanvas.height = croppedHeight; - croppedCtx.putImageData(cuttedImageData, 0, 0); - - return croppedCanvas.toDataURL(); -} - -function resizeCanvas() { - var ratio = Math.max(window.devicePixelRatio || 1, 1); - var additionalFactor = 10; - - signaturePadCanvas.width = - signaturePadCanvas.offsetWidth * ratio * additionalFactor; - signaturePadCanvas.height = - signaturePadCanvas.offsetHeight * ratio * additionalFactor; - signaturePadCanvas - .getContext("2d") - .scale(ratio * additionalFactor, ratio * additionalFactor); - - signaturePad.clear(); -} - -new IntersectionObserver((entries, observer) => { - if (entries.some((entry) => entry.intersectionRatio > 0)) { - resizeCanvas(); - } -}).observe(signaturePadCanvas); - -new ResizeObserver(resizeCanvas).observe(signaturePadCanvas); - -function addDraggableFromText() { - const sigText = document.getElementById("sigText").value; - const font = document.querySelector("select[name=font]").value; - const fontSize = 100; - const color = getSelectedSignatureColor(); - - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - ctx.font = `${fontSize}px ${font}`; - const textWidth = ctx.measureText(sigText).width; - const textHeight = fontSize; - - let paragraphs = sigText.split(/\r?\n/); - - canvas.width = textWidth; - canvas.height = paragraphs.length * textHeight * 1.35; // for tails - ctx.font = `${fontSize}px ${font}`; - ctx.fillStyle = color; - - ctx.textBaseline = "top"; - - let y = 0; - - paragraphs.forEach((paragraph) => { - ctx.fillText(paragraph, 0, y); - y += fontSize; - }); - - const dataURL = canvas.toDataURL(); - DraggableUtils.createDraggableCanvasFromUrl(dataURL); -} - -async function goToFirstOrLastPage(page) { - if (page) { - const lastPage = DraggableUtils.pdfDoc.numPages; - await DraggableUtils.goToPage(lastPage - 1); - } else { - await DraggableUtils.goToPage(0); - } -} - -document.getElementById("download-pdf").addEventListener("click", async () => { - const downloadButton = document.getElementById("download-pdf"); - const originalContent = downloadButton.innerHTML; - - downloadButton.disabled = true; - downloadButton.innerHTML = ` - - `; - - try { - const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument(); - const modifiedPdfBytes = await modifiedPdf.save(); - const blob = new Blob([modifiedPdfBytes], { type: "application/pdf" }); - const link = document.createElement("a"); - link.href = URL.createObjectURL(blob); - link.download = originalFileName + "_signed.pdf"; - link.click(); - } catch (error) { - console.error("Error downloading PDF:", error); - } finally { - downloadButton.disabled = false; - downloadButton.innerHTML = originalContent; - } -}); diff --git a/app/core/src/main/resources/static/js/pipeline.js b/app/core/src/main/resources/static/js/pipeline.js deleted file mode 100644 index c44779c1f..000000000 --- a/app/core/src/main/resources/static/js/pipeline.js +++ /dev/null @@ -1,768 +0,0 @@ -document.getElementById("validateButton").addEventListener("click", function (event) { - event.preventDefault(); - validatePipeline(); -}); -function validatePipeline() { - let pipelineListItems = document.getElementById("pipelineList").children; - let isValid = true; - let containsAddPassword = false; - for (let i = 0; i < pipelineListItems.length - 1; i++) { - let currentOperation = pipelineListItems[i].querySelector(".operationName").textContent; - let nextOperation = pipelineListItems[i + 1].querySelector(".operationName").textContent; - if (currentOperation === "/add-password") { - containsAddPassword = true; - } - - let currentOperationDescription = apiDocs[currentOperation]?.post?.description || ""; - let nextOperationDescription = apiDocs[nextOperation]?.post?.description || ""; - - // Strip off 'ZIP-' prefix - currentOperationDescription = currentOperationDescription.replace("ZIP-", ""); - nextOperationDescription = nextOperationDescription.replace("ZIP-", ""); - - let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || ""; - let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || ""; - - // Splitting in case of multiple possible output/input - let currentOperationOutputArr = currentOperationOutput.split("/"); - let nextOperationInputArr = nextOperationInput.split("/"); - - if (currentOperationOutput !== "ANY" && nextOperationInput !== "ANY") { - let intersection = currentOperationOutputArr.filter((value) => nextOperationInputArr.includes(value)); - console.log(`Intersection: ${intersection}`); - - if (intersection.length === 0) { - updateValidateButton(false); - isValid = false; - console.log( - `Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`, - ); - alert( - `Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`, - ); - break; - } - } - } - if ( - containsAddPassword && - pipelineListItems[pipelineListItems.length - 1].querySelector(".operationName").textContent !== "/add-password" - ) { - updateValidateButton(false); - alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.'); - return false; - } - if (isValid) { - console.log("Pipeline is valid"); - // Continue with the pipeline operation - } else { - console.error("Pipeline is not valid"); - // Stop operation, maybe display an error to the user - } - updateValidateButton(isValid); - return isValid; -} - -function updateValidateButton(isValid) { - var validateButton = document.getElementById("validateButton"); - if (isValid) { - validateButton.classList.remove("btn-danger"); - validateButton.classList.add("btn-success"); - } else { - validateButton.classList.remove("btn-success"); - validateButton.classList.add("btn-danger"); - } -} - -document.getElementById("submitConfigBtn").addEventListener("click", function () { - if (validatePipeline() === false) { - return; - } - let selectedOperation = document.getElementById("operationsDropdown").value; - - var pipelineName = document.getElementById("pipelineName").value; - - - let pipelineList = document.getElementById("pipelineList").children; - let pipelineConfig = { - name: pipelineName, - pipeline: [], - _examples: { - outputDir: "{outputFolder}/{folderName}", - outputFileName: "{filename}-{pipelineName}-{date}-{time}", - }, - outputDir: "httpWebRequest", - outputFileName: "{filename}", - }; - - for (let i = 0; i < pipelineList.length; i++) { - let operationName = pipelineList[i].querySelector(".operationName").textContent; - let parameters = operationSettings[operationName] || {}; - pipelineConfig.pipeline.push({ - operation: operationName, - parameters: parameters, - }); - } - - let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2); - let formData = new FormData(); - - let fileInput = document.getElementById("fileInput-input"); - let files = fileInput.files; - - for (let i = 0; i < files.length; i++) { - console.log("files[i]", files[i].name); - formData.append("fileInput", files[i], files[i].name); - } - - console.log("pipelineConfigJson", pipelineConfigJson); - formData.append("json", pipelineConfigJson); - console.log("formData", formData); - - fetchWithCsrf("api/v1/pipeline/handleData", { - method: "POST", - body: formData, - }) - .then((response) => { - // Save the response to use it later - const responseToUseLater = response; - - return response.blob().then((blob) => { - let url = window.URL.createObjectURL(blob); - let a = document.createElement("a"); - a.href = url; - - // Use responseToUseLater instead of response - const contentDisposition = responseToUseLater.headers.get("Content-Disposition"); - let filename = "download"; - if (contentDisposition && contentDisposition.indexOf("attachment") !== -1) { - filename = decodeURIComponent(contentDisposition.split("filename=")[1].replace(/"/g, "")).trim(); - } - a.download = filename; - - document.body.appendChild(a); - a.click(); - a.remove(); - }); - }) - .catch((error) => { - console.error("Error:", error); - }); -}); - -let apiDocs = {}; -let apiSchemas = {}; -let operationSettings = {}; -let operationStatus = {}; - -fetchWithCsrf("v1/api-docs") - .then((response) => response.json()) - .then((data) => { - apiDocs = data.paths; - apiSchemas = data.components.schemas; - return fetchWithCsrf("api/v1/settings/get-endpoints-status") - .then((response) => response.json()) - .then((data) => { - operationStatus = data; - }) - .catch((error) => { - console.error("Error:", error); - }); - }) - .then(() => { - const ignoreOperations = ["/api/v1/pipeline/handleData", "/api/v1/pipeline/operationToIgnore"]; // Add the operations you want to ignore here - let operationsDropdown = document.getElementById("operationsDropdown"); - operationsDropdown.innerHTML = ""; - - let operationsByTag = {}; - - // Group operations by tags - Object.keys(apiDocs).forEach((operationPath) => { - let operation = apiDocs[operationPath].post; - if (!operation || !operation.description) { - console.log(operationPath); - } - //!operation.description.includes("Type:MISO") - if (operation && !ignoreOperations.includes(operationPath)) { - let operationTag = operation.tags[0]; // This assumes each operation has exactly one tag - if (!operationsByTag[operationTag]) { - operationsByTag[operationTag] = []; - } - operationsByTag[operationTag].push(operationPath); - } - }); - - // Sort operations within each tag alphabetically - Object.keys(operationsByTag).forEach((tag) => { - operationsByTag[tag].sort(); - }); - - // Specify the order of tags - let tagOrder = ["General", "Security", "Convert", "Misc", "Filter"]; - - // Create dropdown options - tagOrder.forEach((tag) => { - if (operationsByTag[tag]) { - let group = document.createElement("optgroup"); - group.label = tag; - - operationsByTag[tag].forEach((operationPath) => { - let option = document.createElement("option"); - - let operationPathDisplay = operationPath; - operationPathDisplay = operationPath.replace(new RegExp("api/v1/" + tag.toLowerCase() + "/", "i"), ""); - - if (operationPath.includes("/convert")) { - operationPathDisplay = operationPathDisplay.replace(/^\//, "").replaceAll("/", " to "); - } else { - operationPathDisplay = operationPathDisplay.replace(/\//g, ""); // Remove slashes - } - operationPathDisplay = operationPathDisplay.replaceAll(" ", "-"); - option.textContent = operationPathDisplay; - - if (!(operationPathDisplay in operationStatus)) { - option.value = operationPath; // Keep the value with slashes for querying - group.appendChild(option); - } - }); - - operationsDropdown.appendChild(group); - } - }); - }) - .catch((error) => { - console.error("Error:", error); - }); - -document.getElementById('deletePipelineBtn').addEventListener('click', function(event) { - event.preventDefault(); - let pipelineName = document.getElementById('pipelineName').value; - - if (confirm(deletePipelineText + pipelineName)) { - removePipelineFromUI(pipelineName); - let key = "#Pipeline-" + pipelineName; - if (localStorage.getItem(key)) { - localStorage.removeItem(key); - } - let pipelineSelect = document.getElementById("pipelineSelect"); - let modal = document.getElementById('pipelineSettingsModal'); - if (modal.style.display !== 'none') { - $('#pipelineSettingsModal').modal('hide'); - } - - if (pipelineSelect.options.length > 0) { - pipelineSelect.selectedIndex = 0; - pipelineSelect.dispatchEvent(new Event('change')); - } - } -}); - -function removePipelineFromUI(pipelineName) { - let pipelineSelect = document.getElementById("pipelineSelect"); - for (let i = 0; i < pipelineSelect.options.length; i++) { - console.log(pipelineSelect.options[i]) - console.log("list " + pipelineSelect.options[i].innerText + " vs " + pipelineName) - if (pipelineSelect.options[i].innerText === pipelineName) { - pipelineSelect.remove(i); - break; - } - } -} - - -document.getElementById("addOperationBtn").addEventListener("click", function () { - let selectedOperation = document.getElementById("operationsDropdown").value; - let pipelineList = document.getElementById("pipelineList"); - - let listItem = document.createElement("li"); - listItem.className = "list-group-item"; - let hasSettings = false; - if (apiDocs[selectedOperation] && apiDocs[selectedOperation].post) { - const postMethod = apiDocs[selectedOperation].post; - - // Check if parameters exist - if (postMethod.parameters && postMethod.parameters.length > 0) { - hasSettings = true; - } else if (postMethod.requestBody && postMethod.requestBody.content["multipart/form-data"]) { - // Extract the reference key - const refKey = postMethod.requestBody.content["multipart/form-data"].schema["$ref"].split("/").pop(); - // Check if the referenced schema exists and has properties more than just its input file - if (apiSchemas[refKey]) { - const properties = apiSchemas[refKey].properties; - const propertyKeys = Object.keys(properties); - - // Check if there's more than one property or if there's exactly one property and its format is not 'binary' - if (propertyKeys.length > 1 || (propertyKeys.length === 1 && properties[propertyKeys[0]].format !== "binary")) { - hasSettings = true; - } - } - } - } - - let containerDiv = document.createElement("div"); - containerDiv.className = "d-flex justify-content-between align-items-center w-100"; - - let operationNameDiv = document.createElement("div"); - operationNameDiv.className = "operationName"; - operationNameDiv.textContent = selectedOperation; - containerDiv.appendChild(operationNameDiv); - - let arrowsDiv = document.createElement("div"); - arrowsDiv.className = "arrows d-flex"; - - let moveUpButton = document.createElement("button"); - moveUpButton.className = "btn btn-secondary move-up ms-1"; - moveUpButton.innerHTML = 'arrow_upward'; - arrowsDiv.appendChild(moveUpButton); - - let moveDownButton = document.createElement("button"); - moveDownButton.className = "btn btn-secondary move-down ms-1"; - moveDownButton.innerHTML = 'arrow_downward'; - arrowsDiv.appendChild(moveDownButton); - - let settingsButton = document.createElement("button"); - settingsButton.className = `btn ${hasSettings ? "btn-warning" : "btn-secondary"} pipelineSettings ms-1`; - if (!hasSettings) { - settingsButton.disabled = true; - } - settingsButton.innerHTML = 'settings'; - arrowsDiv.appendChild(settingsButton); - - let removeButton = document.createElement("button"); - removeButton.className = "btn btn-danger remove ms-1"; - removeButton.innerHTML = 'close'; - arrowsDiv.appendChild(removeButton); - - containerDiv.appendChild(arrowsDiv); - listItem.appendChild(containerDiv); - - pipelineList.appendChild(listItem); - - listItem.querySelector(".move-up").addEventListener("click", function (event) { - event.preventDefault(); - if (listItem.previousElementSibling) { - pipelineList.insertBefore(listItem, listItem.previousElementSibling); - updateConfigInDropdown(); - } - }); - - listItem.querySelector(".move-down").addEventListener("click", function (event) { - event.preventDefault(); - if (listItem.nextElementSibling) { - pipelineList.insertBefore(listItem.nextElementSibling, listItem); - updateConfigInDropdown(); - } - }); - - listItem.querySelector(".remove").addEventListener("click", function (event) { - event.preventDefault(); - pipelineList.removeChild(listItem); - hideOrShowPipelineHeader(); - updateConfigInDropdown(); - }); - - listItem.querySelector(".pipelineSettings").addEventListener("click", function (event) { - event.preventDefault(); - showpipelineSettingsModal(selectedOperation); - hideOrShowPipelineHeader(); - }); - - function showpipelineSettingsModal(operation) { - let pipelineSettingsModal = document.getElementById("pipelineSettingsModal"); - let pipelineSettingsContent = document.getElementById("pipelineSettingsContent"); - let operationData = apiDocs[operation].post.parameters || []; - - // Resolve the $ref reference to get actual schema properties - let refKey = apiDocs[operation].post.requestBody.content["multipart/form-data"].schema["$ref"].split("/").pop(); - let requestBodyData = apiSchemas[refKey].properties || {}; - - // Combine operationData and requestBodyData into a single array - operationData = operationData.concat( - Object.keys(requestBodyData).map((key) => ({ - name: key, - schema: requestBodyData[key], - })), - ); - - pipelineSettingsContent.innerHTML = ""; - - operationData.forEach((parameter) => { - // If the parameter name is 'fileInput', return early to skip the rest of this iteration - if (parameter.name === "fileInput") return; - - let parameterDiv = document.createElement("div"); - parameterDiv.className = "mb-3"; - - let parameterLabel = document.createElement("label"); - parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `; - parameterLabel.title = parameter.schema.description; - parameterLabel.setAttribute("for", parameter.name); - parameterDiv.appendChild(parameterLabel); - - let defaultValue = parameter.schema.example; - if (defaultValue === undefined) defaultValue = parameter.schema.default; - - let parameterInput; - - // check if enum exists in schema - if (parameter.schema.enum) { - // if enum exists, create a select element - parameterInput = document.createElement("select"); - parameterInput.className = "form-control"; - - // iterate over each enum value and create an option for it - parameter.schema.enum.forEach((value) => { - let option = document.createElement("option"); - option.value = value; - option.text = value; - parameterInput.appendChild(option); - }); - } else { - // switch-case statement for handling non-enum types - switch (parameter.schema.type) { - case "string": - if (parameter.schema.format === "binary") { - // This is a file input - - //parameterInput = document.createElement('input'); - //parameterInput.type = 'file'; - //parameterInput.className = "form-control"; - - parameterInput = document.createElement("input"); - parameterInput.type = "text"; - parameterInput.className = "form-control"; - parameterInput.value = "FileInputPathToBeInputtedManuallyForOffline"; - } else { - parameterInput = document.createElement("input"); - parameterInput.type = "text"; - parameterInput.className = "form-control"; - if (defaultValue !== undefined) parameterInput.value = defaultValue; - } - break; - case "number": - case "integer": - parameterInput = document.createElement("input"); - parameterInput.type = "number"; - parameterInput.className = "form-control"; - if (defaultValue !== undefined) parameterInput.value = defaultValue; - break; - case "boolean": - parameterInput = document.createElement("input"); - parameterInput.type = "checkbox"; - if (defaultValue === true) parameterInput.checked = true; - break; - case "array": - // If parameter.schema.format === 'binary' is to be checked, it should be checked here - parameterInput = document.createElement("textarea"); - parameterInput.placeholder = 'Enter a JSON formatted array, e.g., ["item1", "item2", "item3"]'; - parameterInput.className = "form-control"; - break; - case "object": - parameterInput = document.createElement("textarea"); - parameterInput.placeholder = 'Enter a JSON formatted object, e.g., {"key": "value"} If this is a fileInput, it is not currently supported'; - parameterInput.className = "form-control"; - break; - default: - parameterInput = document.createElement("input"); - parameterInput.type = "text"; - parameterInput.className = "form-control"; - if (defaultValue !== undefined) parameterInput.value = defaultValue; - } - } - parameterInput.id = parameter.name; - - console.log("defaultValue", defaultValue); - console.log("parameterInput", parameterInput); - if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) { - let savedValue = operationSettings[operation][parameter.name]; - - switch (parameter.schema.type) { - case "number": - case "integer": - parameterInput.value = savedValue.toString(); - break; - case "boolean": - parameterInput.checked = savedValue; - break; - case "array": - case "object": - parameterInput.value = JSON.stringify(savedValue); - break; - default: - parameterInput.value = savedValue; - } - } - console.log("parameterInput2", parameterInput); - parameterDiv.appendChild(parameterInput); - - pipelineSettingsContent.appendChild(parameterDiv); - }); - - if (hasSettings) { - let saveButton = document.createElement("button"); - saveButton.textContent = saveSettings; - saveButton.className = "btn btn-primary"; - saveButton.addEventListener("click", function (event) { - event.preventDefault(); - let settings = {}; - operationData.forEach((parameter) => { - if (parameter.name !== "fileInput") { - let value = document.getElementById(parameter.name).value; - switch (parameter.schema.type) { - case "number": - case "integer": - settings[parameter.name] = Number(value); - break; - case "boolean": - settings[parameter.name] = document.getElementById(parameter.name).checked; - break; - case "array": - case "object": - if (value === null || value === "") { - settings[parameter.name] = ""; - } else { - try { - const parsedValue = JSON.parse(value); - if (Array.isArray(parsedValue)) { - settings[parameter.name] = parsedValue; - } else { - settings[parameter.name] = value; - } - } catch (e) { - settings[parameter.name] = value; - } - } - break; - default: - settings[parameter.name] = value; - } - } - }); - operationSettings[operation] = settings; - //pipelineSettingsModal.style.display = "none"; - }); - pipelineSettingsContent.appendChild(saveButton); - saveButton.click(); - } - //pipelineSettingsModal.style.display = "block"; - - //pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() { - // pipelineSettingsModal.style.display = "none"; - //} - - //window.onclick = function(event) { - // if (event.target == pipelineSettingsModal) { - // pipelineSettingsModal.style.display = "none"; - // } - //} - } - showpipelineSettingsModal(selectedOperation); - updateConfigInDropdown(); - hideOrShowPipelineHeader(); -}); - -function loadBrowserPipelinesIntoDropdown() { - let pipelineSelect = document.getElementById("pipelineSelect"); - - // Retrieve the current set of option values for comparison - let existingOptions = new Set(); - for (let option of pipelineSelect.options) { - existingOptions.add(option.value); - } - - // Iterate over all items in localStorage - for (let i = 0; i < localStorage.length; i++) { - let key = localStorage.key(i); - if (key.startsWith("#Pipeline-")) { - let pipelineData = localStorage.getItem(key); - // Check if the data is already in the dropdown - if (!existingOptions.has(pipelineData)) { - let pipelineName = key.replace("#Pipeline-", ""); // Extract pipeline name from the key - let option = new Option(pipelineName, pipelineData); // Use pipeline data as the option value - pipelineSelect.appendChild(option); - } - } - } -} -loadBrowserPipelinesIntoDropdown(); - -function updateConfigInDropdown() { - let pipelineSelect = document.getElementById("pipelineSelect"); - let selectedOption = pipelineSelect.options[pipelineSelect.selectedIndex]; - - // Get the current configuration as JSON - let pipelineConfigJson = configToJson(); - console.log("pipelineConfigJson", pipelineConfigJson); - if (!pipelineConfigJson) { - console.error("Failed to update configuration: Invalid configuration"); - return; - } - - // Update the value of the selected option with the new configuration - selectedOption.value = pipelineConfigJson; -} - -var saveBtn = document.getElementById("savePipelineBtn"); -var saveBrowserBtn = document.getElementById("saveBrowserPipelineBtn"); -// Remove any existing event listeners -saveBtn.removeEventListener("click", savePipeline); -saveBrowserBtn.removeEventListener("click", savePipelineToBrowser); -// Add the event listener -saveBtn.addEventListener("click", savePipeline); -saveBrowserBtn.addEventListener("click", savePipelineToBrowser); - -function configToJson() { - if (!validatePipeline()) { - return null; // Return null if validation fails - } - - var pipelineName = document.getElementById("pipelineName").value; - let pipelineList = document.getElementById("pipelineList").children; - let pipelineConfig = { - name: pipelineName, - pipeline: [], - _examples: { - outputDir: "{outputFolder}/{folderName}", - outputFileName: "{filename}-{pipelineName}-{date}-{time}", - }, - outputDir: "{outputFolder}", - outputFileName: "{filename}", - }; - - for (let i = 0; i < pipelineList.length; i++) { - let operationName = pipelineList[i].querySelector(".operationName").textContent; - let parameters = operationSettings[operationName] || {}; - - parameters["fileInput"] = "automated"; - - pipelineConfig.pipeline.push({ - operation: operationName, - parameters: parameters, - }); - } - return JSON.stringify(pipelineConfig, null, 2); -} - -function savePipelineToBrowser() { - let pipelineConfigJson = configToJson(); - if (!pipelineConfigJson) { - console.error("Failed to save pipeline: Invalid configuration"); - return; - } - - let pipelineName = document.getElementById("pipelineName").value; - if (!pipelineName) { - console.error("Failed to save pipeline: Pipeline name is required"); - return; - } - localStorage.setItem("#Pipeline-" +pipelineName, pipelineConfigJson); - console.log("Pipeline configuration saved to localStorage"); -} -function savePipeline() { - let pipelineConfigJson = configToJson(); - if (!pipelineConfigJson) { - console.error("Failed to save pipeline: Invalid configuration"); - return; - } - - let pipelineName = document.getElementById("pipelineName").value; - console.log("Downloading..."); - let a = document.createElement("a"); - a.href = URL.createObjectURL(new Blob([pipelineConfigJson], { type: "application/json" })); - a.download = pipelineName + ".json"; - a.style.display = "none"; - - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); -} - -async function processPipelineConfig(configString) { - console.log("configString", configString); - let pipelineConfig = JSON.parse(configString); - let pipelineList = document.getElementById("pipelineList"); - - while (pipelineList.firstChild) { - pipelineList.removeChild(pipelineList.firstChild); - } - document.getElementById("pipelineName").value = pipelineConfig.name; - for (const operationConfig of pipelineConfig.pipeline) { - let operationsDropdown = document.getElementById("operationsDropdown"); - operationsDropdown.value = operationConfig.operation; - operationSettings[operationConfig.operation] = operationConfig.parameters; - - // assuming addOperation is async - await new Promise((resolve) => { - document.getElementById("addOperationBtn").addEventListener("click", resolve, { once: true }); - document.getElementById("addOperationBtn").click(); - }); - - let lastOperation = pipelineList.lastChild; - - Object.keys(operationConfig.parameters).forEach((parameterName) => { - let input = document.getElementById(parameterName); - if (input) { - switch (input.type) { - case "checkbox": - input.checked = operationConfig.parameters[parameterName]; - break; - case "number": - input.value = operationConfig.parameters[parameterName].toString(); - break; - case "file": - if (parameterName !== "fileInput") { - // Create a new file input element - let newInput = document.createElement("input"); - newInput.type = "file"; - newInput.id = parameterName; - - // Add the new file input to the main page (change the selector according to your needs) - document.querySelector("#main").appendChild(newInput); - } - break; - case "text": - case "textarea": - default: - var value = operationConfig.parameters[parameterName] - if (typeof value !== 'string') { - input.value = JSON.stringify(value) ; - } else { - input.value = value; - } - - } - } - }); - } -} - -document.getElementById("uploadPipelineBtn").addEventListener("click", function () { - document.getElementById("uploadPipelineInput").click(); -}); - -document.getElementById("uploadPipelineInput").addEventListener("change", function (e) { - let reader = new FileReader(); - reader.onload = function (event) { - processPipelineConfig(event.target.result); - }; - reader.readAsText(e.target.files[0]); - hideOrShowPipelineHeader(); -}); - -document.getElementById("pipelineSelect").addEventListener("change", function (e) { - let selectedPipelineJson = e.target.value; // assuming the selected value is the JSON string of the pipeline config - processPipelineConfig(selectedPipelineJson); -}); - -function hideOrShowPipelineHeader() { - var pipelineHeader = document.getElementById("pipelineHeader"); - var pipelineList = document.getElementById("pipelineList"); - - if (pipelineList.children.length === 0) { - // Hide the pipeline header if there are no items in the pipeline list - pipelineHeader.style.display = "none"; - } else { - // Show the pipeline header if there are items in the pipeline list - pipelineHeader.style.display = "block"; - } -} diff --git a/app/core/src/main/resources/static/js/redact.js b/app/core/src/main/resources/static/js/redact.js deleted file mode 100644 index 0e683a5bf..000000000 --- a/app/core/src/main/resources/static/js/redact.js +++ /dev/null @@ -1,1090 +0,0 @@ -import {PDFViewerApplication} from '../pdfjs-legacy/js/viewer.mjs'; -import UUID from './uuid.js'; - -let zoomScaleValue = 1.0; - -let activeOverlay; -let drawingLayer = null; - -const doNothing = () => {}; - -function addRedactedPagePreview(pagesSelector) { - document.querySelectorAll(pagesSelector).forEach((page) => { - let textLayer = page.querySelector('.textLayer'); - if (textLayer) textLayer.classList.add('redacted-page-preview'); - }); -} - -function addRedactedThumbnailPreview(sidebarPagesSelector) { - document.querySelectorAll(sidebarPagesSelector).forEach((thumbnail) => { - thumbnail.classList.add('redacted-thumbnail-preview'); - let thumbnailImage = thumbnail.querySelector('.thumbnailImage'); - if (thumbnailImage) thumbnailImage.classList.add('redacted-thumbnail-image-preview'); - }); -} - -function removeRedactedPagePreview() { - document.querySelectorAll('.textLayer').forEach((textLayer) => textLayer.classList.remove('redacted-page-preview')); - document.querySelectorAll('#thumbnailView > a > div.thumbnail').forEach((thumbnail) => { - thumbnail.classList.remove('redacted-thumbnail-preview'); - let thumbnailImage = thumbnail.querySelector('.thumbnailImage'); - if (thumbnailImage) thumbnailImage.classList.remove('redacted-thumbnail-image-preview'); - }); -} - -function extractPagesDetailed(pagesInput, totalPageCount) { - let parts = pagesInput.split(',').filter((s) => s); - let pagesDetailed = { - numbers: new Set(), - functions: new Set(), - ranges: new Set(), - all: false, - }; - for (let part of parts) { - let trimmedPart = part.trim(); - if ('all' == trimmedPart) { - pagesDetailed.all = true; - return pagesDetailed; - } else if (isValidFunction(trimmedPart)) { - pagesDetailed.functions.add(formatNFunction(trimmedPart)); - } else if (trimmedPart.includes('-')) { - let range = trimmedPart - .replaceAll(' ', '') - .split('-') - .filter((s) => s); - if (range && range.length == 2 && range[0].trim() > 0 && range[1].trim() > 0) - pagesDetailed.ranges.add({ - low: range[0].trim(), - high: range[1].trim(), - }); - } else if (isPageNumber(trimmedPart)) { - pagesDetailed.numbers.add(trimmedPart <= totalPageCount ? trimmedPart : totalPageCount); - } - } - - return pagesDetailed; -} - -function formatNFunction(expression) { - let result = insertMultiplicationBeforeN(expression.replaceAll(' ', '')); - let multiplyByOpeningRoundBracketPattern = /([0-9n)])\(/g; // example: n(n-1), 9(n-1), (n-1)(n-2) - result = result.replaceAll(multiplyByOpeningRoundBracketPattern, '$1*('); - - let multiplyByClosingRoundBracketPattern = /\)([0-9n)])/g; // example: (n-1)n, (n-1)9, (n-1)(n-2) - result = result.replaceAll(multiplyByClosingRoundBracketPattern, ')*$1'); - return result; -} - -function insertMultiplicationBeforeN(expression) { - let result = expression.replaceAll(/(\d)n/g, '$1*n'); - while (result.match(/nn/)) { - result = result.replaceAll(/nn/g, 'n*n'); // From nn -> n*n - } - return result; -} - -function validatePages(pages) { - let parts = pages.split(',').filter((s) => s); - let errors = []; - for (let part of parts) { - let trimmedPart = part.trim(); - if ('all' == trimmedPart) continue; - else if (trimmedPart.includes('n')) { - if (!isValidFunction(trimmedPart)) - errors.push(`${trimmedPart} is an invalid function, it should consist of digits 0-9, n, *, -, /, (, ), \\.`); - } else if (trimmedPart.includes('-')) { - let range = trimmedPart.split('-').filter((s) => s); - if (!range || range.length != 2) - errors.push(`${trimmedPart} is an invalid range, it should consist of from-to, example: 1-5`); - else if (range[0].trim() <= 0 || range[1].trim() <= 0) - errors.push(`${trimmedPart} has invalid range(s), page numbers should be positive.`); - } else if (!isPageNumber(trimmedPart)) { - errors.push(`${trimmedPart} is invalid, it should either be a function, page number or a range.`); - } - } - - return {errors}; -} - -function isPageNumber(page) { - return /^[0-9]*$/.test(page); -} - -function isValidFunction(part) { - return part.includes('n') && /[0-9n+\-*/() ]+$/.test(part); -} - -function hideContainer(container) { - container?.classList.add('d-none'); -} - -const RedactionModes = Object.freeze({ - DRAWING: Symbol('drawing'), - TEXT: Symbol('text'), - NONE: Symbol('none'), -}); - -function removePDFJSButtons() { - document.getElementById('print')?.remove(); - document.getElementById('download')?.remove(); - document.getElementById('editorStamp')?.remove(); - document.getElementById('editorFreeText')?.remove(); - document.getElementById('editorInk')?.remove(); - document.getElementById('secondaryToolbarToggle')?.remove(); - document.getElementById('openFile')?.remove(); -} - -function hideInitialPage() { - document.body.style.overflowY = 'hidden'; - let redactionsFormContainer = document.getElementById('redactionFormContainer'); - for ( - let el = redactionsFormContainer.previousElementSibling; - el && el instanceof HTMLBRElement; - el = el.previousElementSibling - ) { - el.classList.add('d-none'); - } - redactionsFormContainer.classList.add('d-none'); - const footer = document.getElementsByTagName('footer')[0]; - - // Check if the parent of the footer has the id "viewerContainer" - if (footer.parentElement && footer.parentElement.id !== 'viewerContainer') { - footer.classList.add('d-none'); - } -} - -window.addEventListener('load', (e) => { - let isChromium = - !!window.chrome || - (!!navigator.userAgentData && navigator.userAgentData.brands.some((data) => data.brand == 'Chromium')); - - let isSafari = - /constructor/i.test(window.HTMLElement) || - (function (p) { - return p.toString() === '[object SafariRemoteNotification]'; - })(!window['safari'] || (typeof safari !== 'undefined' && window['safari'].pushNotification)); - let isWebkit = navigator.userAgent.search(/webkit/i) > 0; - let isGecko = navigator.userAgent.search(/gecko/i) > 0; - let isFirefox = typeof InstallTrigger !== 'undefined'; - - let hiddenInput = document.getElementById('fileInput'); - let outerContainer = document.getElementById('outerContainer'); - let printContainer = document.getElementById('printContainer'); - - let toolbarViewerRight = document.getElementById('toolbarViewerRight'); - let showMoreBtn = document.getElementById('showMoreBtn'); - - window.onresize = (e) => { - if (window.innerWidth > 1125 && showMoreBtn.classList.contains('toggled')) { - showMoreBtn.click(); - } else if (window.innerWidth > 1125 && toolbarViewerRight.hasAttribute('style')) { - toolbarViewerRight.style.removeProperty('display'); - } - }; - - showMoreBtn.onclick = (e) => { - if (showMoreBtn.classList.contains('toggled')) { - toolbarViewerRight.style.display = 'none'; - showMoreBtn.classList.remove('toggled'); - } else { - toolbarViewerRight.style.display = 'flex'; - showMoreBtn.classList.add('toggled'); - } - }; - - let viewer = document.getElementById('viewer'); - - hiddenInput.files = undefined; - let redactionMode = RedactionModes.NONE; - - let redactions = []; - - let redactionsInput = document.getElementById('redactions-input'); - - let redactionsPalette = document.getElementById('redactions-palette'); - let redactionsPaletteInput = redactionsPalette.querySelector('input'); - - let redactionsPaletteContainer = document.getElementById('redactionsPaletteContainer'); - - let redactedPagesDetails = { - numbers: new Set(), - ranges: new Set(), - functions: new Set(), - all: false, - }; - let pageBasedRedactionBtn = document.getElementById('pageBasedRedactionBtn'); - let pageBasedRedactionOverlay = document.getElementById('pageBasedRedactionOverlay'); - pageBasedRedactionBtn.onclick = (e) => pageBasedRedactionOverlay.classList.remove('d-none'); - - pageBasedRedactionOverlay.querySelector('input[type=text]').onchange = (e) => { - let input = e.target; - let parentElement = input.parentElement; - - resetFieldFeedbackMessages(input, parentElement); - - let value = input.value.trim(); - let {errors} = validatePages(value); - if (errors && errors.length > 0) { - applyPageRedactionBtn.disabled = 'true'; - displayFieldErrorMessages(input, errors); - } else { - applyPageRedactionBtn.removeAttribute('disabled'); - input.classList.add('is-valid'); - } - }; - - let applyPageRedactionBtn = document.getElementById('applyPageRedactionBtn'); - applyPageRedactionBtn.onclick = (e) => { - pageBasedRedactionOverlay.querySelectorAll('input').forEach((input) => { - const id = input.getAttribute('data-for'); - if (id == 'pageNumbers') { - let {errors} = validatePages(input.value); - - resetFieldFeedbackMessages(input, input.parentElement); - - if (errors?.length > 0) { - applyPageRedactionBtn.disabled = true; - displayFieldErrorMessages(input, errors); - } else { - pageBasedRedactionOverlay.classList.add('d-none'); - input.classList.remove('is-valid'); - - let totalPagesCount = PDFViewerApplication.pdfViewer.pagesCount; - let pagesDetailed = extractPagesDetailed(input.value, totalPagesCount); - redactedPagesDetails = pagesDetailed; - addPageRedactionPreviewToPages(pagesDetailed, totalPagesCount); - } - } else if (id == 'pageRedactColor') setPageRedactionColor(input.value); - let formInput = document.getElementById(id); - if (formInput) formInput.value = input.value; - }); - }; - - let closePageRedactionBtn = document.getElementById('closePageRedactionBtn'); - closePageRedactionBtn.onclick = (e) => { - pageBasedRedactionOverlay.classList.add('d-none'); - pageBasedRedactionOverlay.querySelectorAll('input').forEach((input) => { - const id = input.getAttribute('data-for'); - if (id == 'pageNumbers') { - resetFieldFeedbackMessages(input, input.parentElement); - } - let formInput = document.getElementById(id); - if (formInput) input.value = formInput.value; - }); - }; - - let pdfToImageCheckbox = document.getElementById('convertPDFToImage'); - - let pdfToImageBtn = document.getElementById('pdfToImageBtn'); - pdfToImageBtn.onclick = (e) => { - pdfToImageBtn.classList.toggle('btn-success'); - pdfToImageBtn.classList.toggle('btn-danger'); - pdfToImageCheckbox.checked = !pdfToImageCheckbox.checked; - }; - - let fileChooser = document.getElementsByClassName('custom-file-chooser')[0]; - let fileChooserInput = fileChooser.querySelector(`#${fileChooser.getAttribute('data-bs-element-id')}`); - - let uploadButton = document.getElementById('uploadBtn'); - uploadButton.onclick = (e) => fileChooserInput.click(); - - document.addEventListener('file-input-change', (e) => { - redactions = []; - _setRedactionsInput(redactions); - }); - - let submitBtn = document.getElementById('submitBtn'); - - let downloadBtn = document.getElementById('downloadBtn'); - let downloadBtnIcon = document.getElementById('downloadBtnIcon'); - - downloadBtn.onclick = (e) => { - submitBtn.click(); - setTimeout(_showOrHideLoadingSpinner, 100); // wait 100 milliseconds so that submitBtn would be disabled - }; - - function _showOrHideLoadingSpinner() { - if (!submitBtn.disabled) { - downloadBtnIcon.innerHTML = 'download'; - downloadBtnIcon.classList.remove('spin-animation'); - return; - } - - downloadBtnIcon.innerHTML = 'progress_activity'; - downloadBtnIcon.classList.add('spin-animation'); - setTimeout(_showOrHideLoadingSpinner, 500); - } - - redactionsPaletteContainer.onclick = (e) => redactionsPalette.click(); - - function clearSelection() { - if (window.getSelection) { - if (window.getSelection().empty) { - // Chrome - window.getSelection().empty(); - } else if (window.getSelection().removeAllRanges) { - // Firefox - window.getSelection().removeAllRanges(); - } - } else if (document.selection) { - // IE? - document.selection.empty(); - } - } - - viewer.onmouseup = (e) => { - if (redactionMode !== RedactionModes.TEXT) return; - const containsText = window.getSelection() && window.getSelection().toString() != ''; - if(containsText){ - redactTextSelection(); - clearSelection(); - } - }; - - redactionsPaletteInput.onchange = (e) => { - let color = e.target.value; - redactionsPalette.style.setProperty('--palette-color', color); - }; - - document.addEventListener('file-input-change', (e) => { - let fileChooser = document.getElementsByClassName('custom-file-chooser')[0]; - let fileChooserInput = fileChooser.querySelector(`#${fileChooser.getAttribute('data-bs-element-id')}`); - - hiddenInput.files = fileChooserInput.files; - if (!hiddenInput.files || hiddenInput.files.length === 0) { - hideContainer(outerContainer); - hideContainer(printContainer); - } else { - outerContainer?.classList.remove('d-none'); - printContainer?.classList.remove('d-none'); - hideInitialPage(); - } - - hiddenInput.dispatchEvent(new Event('change', {bubbles: true})); - }); - - PDFViewerApplication.downloadOrSave = doNothing; - PDFViewerApplication.triggerPrinting = doNothing; - - let redactionContainersDivs = {}; - PDFViewerApplication.eventBus.on('pagerendered', (e) => { - removePDFJSButtons(); - - let textSelectionRedactionBtn = document.getElementById('man-text-select-redact'); - let drawRedactionBtn = document.getElementById('man-shape-redact'); - - textSelectionRedactionBtn.onclick = _handleTextSelectionRedactionBtnClick; - drawRedactionBtn.onclick = _handleDrawRedactionBtnClick; - - let layer = e.source.textLayer.div; - layer.setAttribute('data-page', e.pageNumber); - if (redactedPagesDetails.all || redactedPagesDetails.numbers.has(e.pageNumber)) { - layer.classList.add('redacted-page-preview'); - } else { - layer.classList.remove('redacted-page-preview'); - } - - zoomScaleValue = e.source.scale ? e.source.scale : e.source.pageScale; - document.documentElement.style.setProperty('--zoom-scale', zoomScaleValue); - - let redactionsContainer = document.getElementById(`redactions-container-${e.pageNumber}`); - if (!redactionsContainer && !redactionContainersDivs[`${e.pageNumber}`]) { - redactionsContainer = document.createElement('div'); - redactionsContainer.style.position = 'relative'; - redactionsContainer.style.height = '100%'; - redactionsContainer.style.width = '100%'; - redactionsContainer.id = `redactions-container-${e.pageNumber}`; - redactionsContainer.style.setProperty('z-index', 'unset'); - - layer.appendChild(redactionsContainer); - redactionContainersDivs[`${e.pageNumber}`] = redactionsContainer; - } else if (!redactionsContainer && redactionContainersDivs[`${e.pageNumber}`]) { - redactionsContainer = redactionContainersDivs[`${e.pageNumber}`]; - - layer.appendChild(redactionsContainer); - // Dispatch event to update text layer references for elements' events - redactionsContainer.querySelectorAll('.selected-wrapper').forEach((area) => - area.dispatchEvent( - new CustomEvent('textLayer-reference-changed', { - bubbles: true, - detail: {textLayer: layer}, - }) - ) - ); - } - - document.onpointerup = (e) => { - if (drawingLayer && e.target != drawingLayer && e.button == 0) - drawingLayer.dispatchEvent(new Event('external-pointerup')); - }; - - initDraw(layer, redactionsContainer); - enableTextRedactionMode(); - - function _handleTextSelectionRedactionBtnClick(e) { - if (textSelectionRedactionBtn.classList.contains('toggled')) { - resetTextSelection(); - } else { - enableTextRedactionMode(); - } - } - - function enableTextRedactionMode() { - if(!textSelectionRedactionBtn.classList.contains('toggled')){ - textSelectionRedactionBtn.classList.add('toggled'); - } - resetDrawRedactions(); - redactionMode = RedactionModes.TEXT; - }; - - function resetTextSelection() { - textSelectionRedactionBtn.classList.remove('toggled'); - redactionMode = RedactionModes.NONE; - clearSelection(); - } - - function _handleDrawRedactionBtnClick(e) { - if (drawRedactionBtn.classList.contains('toggled')) { - resetDrawRedactions(); - } else { - resetTextSelection(); - drawRedactionBtn.classList.add('toggled'); - document.documentElement.style.setProperty('--textLayer-pointer-events', 'none'); - document.documentElement.style.setProperty('--textLayer-user-select', 'none'); - redactionMode = RedactionModes.DRAWING; - } - } - - function resetDrawRedactions() { - redactionMode = RedactionModes.NONE; - drawRedactionBtn.classList.remove('toggled'); - document.documentElement.style.setProperty('--textLayer-pointer-events', 'auto'); - document.documentElement.style.setProperty('--textLayer-user-select', 'auto'); - window.dispatchEvent(new CustomEvent('reset-drawing', {bubbles: true})); - } - - function initDraw(canvas, redactionsContainer) { - let mouse = { - x: 0, - y: 0, - startX: 0, - startY: 0, - }; - let element = null; - let drawnRedaction = null; - - window.addEventListener('reset-drawing', (e) => { - _clearDrawing(); - canvas.style.cursor = 'default'; - document.documentElement.style.setProperty('--textLayer-pointer-events', 'auto'); - document.documentElement.style.setProperty('--textLayer-user-select', 'auto'); - }); - - window.addEventListener('drawing-entered', (e) => { - let target = e.detail?.target; - if (canvas === target) return; - _clearDrawing(); - }); - - window.addEventListener('cancel-drawing', (e) => { - _clearDrawing(); - canvas.style.cursor = 'default'; - }); - - function setMousePosition(e) { - if (isChromium || isSafari || isWebkit) { - mouse.x = e.offsetX; - mouse.y = e.offsetY; - } else if (isFirefox || isGecko) { - mouse.x = e.layerX; - mouse.y = e.layerY; - } else { - let rect = (e.target || e.srcElement).getBoundingClientRect(); - mouse.x = e.clientX - rect.left; - mouse.y = e.clientY - rect.top; - } - } - - window.onkeydown = (e) => { - if (e.key === 'Escape' && redactionMode === RedactionModes.DRAWING) { - window.dispatchEvent(new CustomEvent('cancel-drawing', {bubbles: true})); - } - }; - - canvas.onpointerenter = (e) => { - window.dispatchEvent( - new CustomEvent('drawing-entered', { - bubbles: true, - detail: {target: canvas}, - }) - ); - }; - - canvas.onpointerup = (e) => { - let isLeftClick = e.button == 0; - if (!isLeftClick) return; - - if (element !== null) { - _saveAndResetDrawnRedaction(); - console.log('finished.'); - } - }; - - canvas.addEventListener('external-pointerup', (e) => { - if (element != null) { - _saveAndResetDrawnRedaction(); - } - }); - - canvas.onpointerleave = (e) => { - let ev = copyEvent(e, 'pointerleave'); - let {left, top} = calculateMouseCoordinateToRotatedBox(canvas, e); - - ev.layerX = left; - ev.offsetX = left; - - ev.layerY = top; - ev.offsetY = top; - - setMousePosition(ev); - if (element !== null) { - draw(); - } - }; - - canvas.onpointerdown = (e) => { - let isLeftClick = e.button == 0; - if (!isLeftClick) return; - - if (element == null) { - if (redactionMode !== RedactionModes.DRAWING) { - console.warn('Drawing attempt when redaction mode is', redactionMode.description); - return; - } - console.log('begun.'); - _captureAndDrawStartingPointOfDrawnRedaction(); - } - }; - - canvas.onpointermove = function (e) { - setMousePosition(e); - if (element !== null) { - draw(); - } - }; - - function draw() { - let scaleFactor = _getScaleFactor(); - - let width = Math.abs(mouse.x - mouse.startX); - element.style.width = _toCalcZoomPx(_scaleToDisplay(width)); - - let height = Math.abs(mouse.y - mouse.startY); - element.style.height = _toCalcZoomPx(_scaleToDisplay(height)); - - let left = mouse.x - mouse.startX < 0 ? mouse.x : mouse.startX; - element.style.left = _toCalcZoomPx(_scaleToDisplay(left)); - - let top = mouse.y - mouse.startY < 0 ? mouse.y : mouse.startY; - element.style.top = _toCalcZoomPx(_scaleToDisplay(top)); - - if (drawnRedaction) { - drawnRedaction.left = _scaleToPDF(left, scaleFactor); - drawnRedaction.top = _scaleToPDF(top, scaleFactor); - drawnRedaction.width = _scaleToPDF(width, scaleFactor); - drawnRedaction.height = _scaleToPDF(height, scaleFactor); - } - } - - function _clearDrawing() { - if (element) element.remove(); - if (drawingLayer == canvas) drawingLayer = null; - element = null; - drawnRedaction = null; - } - - function _saveAndResetDrawnRedaction() { - if (!element) return; - if ( - !element.style.height || - element.style.height.includes('(0px * var') || - !element.style.width || - element.style.width.includes('(0px * var') - ) { - element.remove(); - } else { - element.classList.add('selected-wrapper'); - element.classList.remove('rectangle'); - - addRedactionOverlay(element, drawnRedaction, canvas); - redactions.push(drawnRedaction); - _setRedactionsInput(redactions); - } - drawingLayer = null; - element = null; - drawnRedaction = null; - canvas.style.cursor = 'default'; - } - - function _captureAndDrawStartingPointOfDrawnRedaction() { - mouse.startX = mouse.x; - mouse.startY = mouse.y; - - element = document.createElement('div'); - element.className = 'rectangle'; - drawingLayer = canvas; - - let left = mouse.x; - let top = mouse.y; - - element.style.left = _toCalcZoomPx(_scaleToDisplay(left)); - element.style.top = _toCalcZoomPx(_scaleToDisplay(top)); - - let scaleFactor = _getScaleFactor(); - let color = redactionsPalette.style.getPropertyValue('--palette-color'); - - element.style.setProperty('--palette-color', color); - - drawnRedaction = { - left: _scaleToPDF(left, scaleFactor), - top: _scaleToPDF(top, scaleFactor), - width: 0.0, - height: 0.0, - color: color, - pageNumber: parseInt(canvas.getAttribute('data-page')), - element: element, - id: UUID.uuidv4(), - }; - - redactionsContainer.appendChild(element); - canvas.style.cursor = 'crosshair'; - } - } - }); - - PDFViewerApplication.eventBus.on('rotationchanging', (e) => { - if (!activeOverlay) return; - hideOverlay(); - }); - - function _getScaleFactor() { - return parseFloat(viewer.style.getPropertyValue('--scale-factor')); - } - - function getTextLayer(element) { - let current = element; - while (current) { - if (current instanceof HTMLDivElement && current.classList.contains('textLayer')) return current; - current = current.parentElement; - } - - return current; - } - - document.onclick = (e) => { - if ( - (e.target && e.target.classList.contains('selected-wrapper') && e.target.firstChild == activeOverlay) || - e.target == activeOverlay - ) - return; - if (activeOverlay) hideOverlay(); - }; - - document.addEventListener('keydown', (e) => { - if (e.key === 'Delete' && activeOverlay) { - activeOverlay.querySelector('.delete-icon')?.dispatchEvent(new Event('click', {bubbles: true})); - return; - } - const isRedactionShortcut = e.ctrlKey && (e.key == 's' || e.key == 'S' || e.code == 'KeyS'); - if (!isRedactionShortcut || redactionMode !== RedactionModes.TEXT) return; - - redactTextSelection(); - }); - - function rotateTextBox(rect, textLayerRect, angle) { - let left, top, width, height; - if (!angle || angle == 0) { - left = rect.left - textLayerRect.left; - top = rect.top - textLayerRect.top; - width = rect.width; - height = rect.height; - } else if (angle == 90) { - left = rect.top - textLayerRect.top; - top = textLayerRect.right - rect.right; - width = rect.height; - height = rect.width; - } else if (angle == 180) { - left = textLayerRect.right - rect.right; - top = textLayerRect.bottom - rect.bottom; - width = rect.width; - height = rect.height; - } else if (angle == 270) { - left = textLayerRect.bottom - rect.bottom; - top = rect.left - textLayerRect.left; - width = rect.height; - height = rect.width; - } - - return {left, top, width, height}; - } - - function redactTextSelection() { - let selection = window.getSelection(); - if (!selection || selection.rangeCount <= 0) return; - let range = selection.getRangeAt(0); - - let textLayer = getTextLayer(range.startContainer); - if (!textLayer) return; - - const pageNumber = textLayer.getAttribute('data-page'); - let redactionsArea = textLayer.querySelector(`#redactions-container-${pageNumber}`); - let textLayerRect = textLayer.getBoundingClientRect(); - - let rects = range.getClientRects(); - let scaleFactor = _getScaleFactor(); - - let color = redactionsPalette.style.getPropertyValue('--palette-color'); - - let angle = textLayer.getAttribute('data-main-rotation'); - for (const rect of rects) { - if (!rect || !rect.width || !rect.height) continue; - let redactionElement = document.createElement('div'); - redactionElement.classList.add('selected-wrapper'); - - let {left, top, width, height} = rotateTextBox(rect, textLayerRect, angle); - - let leftDisplayScaled = _scaleToDisplay(left); - let topDisplayScaled = _scaleToDisplay(top); - let widthDisplayScaled = _scaleToDisplay(width); - let heightDisplayScaled = _scaleToDisplay(height); - - let redactionInfo = { - left: _scaleToPDF(left, scaleFactor), - top: _scaleToPDF(top, scaleFactor), - width: _scaleToPDF(width, scaleFactor), - height: _scaleToPDF(height, scaleFactor), - pageNumber: parseInt(pageNumber), - color: color, - element: redactionElement, - id: UUID.uuidv4(), - }; - - redactions.push(redactionInfo); - - redactionElement.style.left = _toCalcZoomPx(leftDisplayScaled); - redactionElement.style.top = _toCalcZoomPx(topDisplayScaled); - - redactionElement.style.width = _toCalcZoomPx(widthDisplayScaled); - redactionElement.style.height = _toCalcZoomPx(heightDisplayScaled); - redactionElement.style.setProperty('--palette-color', color); - - redactionsArea.appendChild(redactionElement); - - addRedactionOverlay(redactionElement, redactionInfo, textLayer); - } - - _setRedactionsInput(redactions); - } - - function _scaleToDisplay(value) { - return value / zoomScaleValue; - } - - function _scaleToPDF(value, scaleFactor) { - if (!scaleFactor) scaleFactor = document.documentElement.getPropertyValue('--scale-factor'); - return value / scaleFactor; - } - - function _toCalcZoomPx(val) { - return `calc(${val}px * var(--zoom-scale))`; - } - - function _setRedactionsInput(redactions) { - let stringifiedRedactions = JSON.stringify( - redactions.filter(_nonEmptyRedaction).map((red) => ({ - x: red.left, - y: red.top, - width: red.width, - height: red.height, - color: red.color, - page: red.pageNumber, - })) - ); - redactionsInput.value = stringifiedRedactions; - } - - function addRedactionOverlay(redactionElement, redactionInfo, textLayer) { - let redactionOverlay = document.createElement('div'); - - let deleteBtn = $( - `` - )[0]; - - deleteBtn.onclick = (e) => { - redactions = redactions.filter((red) => redactionInfo.id != red.id); - redactionElement.remove(); - _setRedactionsInput(redactions); - activeOverlay = null; - }; - - let colorPaletteLabel = $( - `` - )[0]; - - let colorPaletteInput = $(` - - `)[0]; - - colorPaletteLabel.appendChild(colorPaletteInput); - - colorPaletteLabel.onclick = (e) => { - if (colorPaletteLabel === e.target) { - e.stopPropagation(); - } - }; - - colorPaletteInput.onchange = (e) => { - let color = e.target.value; - redactionElement.style.setProperty('--palette-color', color); - let redactionIdx = redactions.findIndex((red) => redactionInfo.id === red.id); - if (redactionIdx < 0) return; - redactions[redactionIdx].color = color; - _setRedactionsInput(redactions); - }; - - redactionOverlay.appendChild(deleteBtn); - redactionOverlay.appendChild(colorPaletteLabel); - - redactionOverlay.classList.add('redaction-overlay'); - redactionOverlay.style.display = 'none'; - - redactionElement.addEventListener('textLayer-reference-changed', (e) => { - textLayer = e.detail.textLayer; - }); - - redactionElement.onclick = (e) => { - if (e.target != redactionElement) return; - if (activeOverlay) hideOverlay(); - redactionElement.classList.add('active-redaction'); - activeOverlay = redactionOverlay; - _adjustActiveOverlayCoordinates(); - }; - - redactionElement.appendChild(redactionOverlay); - - // Adjust active overlay coordinates to avoid placing the overlay out of page bounds - function _adjustActiveOverlayCoordinates() { - activeOverlay.style.visibility = 'hidden'; - activeOverlay.style.display = 'flex'; - textLayer = textLayer || getTextLayer(redactionElement); - let angle = parseInt(textLayer.getAttribute('data-main-rotation')); - if (textLayer) redactionOverlay.style.transform = `rotate(${angle * -1}deg)`; - - activeOverlay.style.removeProperty('left'); - activeOverlay.style.removeProperty('top'); - - let textRect = textLayer.getBoundingClientRect(); - let overlayRect = redactionOverlay.getBoundingClientRect(); - - let leftOffset = 0, - topOffset = 0; - if (overlayRect.right > textRect.right) { - leftOffset = textRect.right - overlayRect.right; - } else if (overlayRect.left < textRect.left) { - leftOffset = textRect.left - overlayRect.left; - } - - if (overlayRect.top < textRect.top) { - topOffset = textRect.top - overlayRect.top; - } else if (overlayRect.bottom > textRect.bottom) { - topOffset = textRect.bottom - overlayRect.bottom; - } - - switch (angle) { - case 90: - [leftOffset, topOffset] = [topOffset, -leftOffset]; - break; - case 180: - [leftOffset, topOffset] = [-leftOffset, -topOffset]; - break; - case 270: - [leftOffset, topOffset] = [-topOffset, leftOffset]; - break; - } - - if (leftOffset != 0) activeOverlay.style.left = `calc(50% + ${leftOffset}px`; - if (topOffset != 0) activeOverlay.style.top = `calc(100% + ${topOffset}px`; - activeOverlay.style.visibility = 'unset'; - } - } -}); - -function calculateMouseCoordinateToRotatedBox(canvas, e) { - let textRect = canvas.getBoundingClientRect(); - let left, - top = 0; - let angle = parseInt(canvas.getAttribute('data-main-rotation')); - switch (angle) { - case 0: - left = clamp(e.pageX - textRect.left, 0, textRect.width); - top = clamp(e.pageY - textRect.top, 0, textRect.height); - break; - - case 90: - left = clamp(e.pageY - textRect.top, 0, textRect.height); - top = clamp(textRect.right - e.pageX, 0, textRect.width); - break; - case 180: - left = clamp(textRect.right - e.pageX, 0, textRect.width); - top = clamp(textRect.bottom - e.pageY, 0, textRect.width); - break; - case 270: - left = clamp(textRect.bottom - e.pageY, 0, textRect.height); - top = clamp(e.pageX - textRect.left, 0, textRect.width); - break; - } - return {left, top}; -} - -function clamp(value, min, max) { - return Math.max(min, Math.min(value, max)); -} - -function addPageRedactionPreviewToPages(pagesDetailed, totalPagesCount) { - if (pagesDetailed.all) { - addRedactedPagePreview('#viewer > .page'); - addRedactedThumbnailPreview('#thumbnailView > a > div.thumbnail'); - } else { - removeRedactedPagePreview(); - - setPageNumbersFromRange(pagesDetailed, totalPagesCount); - setPageNumbersFromNFunctions(pagesDetailed, totalPagesCount); - - let pageNumbers = Array.from(pagesDetailed.numbers); - if (pageNumbers?.length > 0) { - let pagesSelector = pageNumbers.map((number) => `#viewer > .page[data-page-number="${number}"]`).join(','); - addRedactedPagePreview(pagesSelector); - let thumbnailSelector = pageNumbers - .map((number) => `#thumbnailView > a > div.thumbnail[data-page-number="${number}"]`) - .join(','); - addRedactedThumbnailPreview(thumbnailSelector); - } - } -} - -function resetFieldFeedbackMessages(input, parentElement) { - if (parentElement) parentElement.querySelectorAll('.invalid-feedback').forEach((feedback) => feedback.remove()); - if (input) { - input.classList.remove('is-invalid'); - input.classList.remove('is-valid'); - } -} - -function displayFieldErrorMessages(input, errors) { - input.classList.add('is-invalid'); - errors.forEach((error) => { - let element = document.createElement('div'); - element.classList.add('invalid-feedback'); - element.classList.add('list-styling'); - element.textContent = error; - input.parentElement.appendChild(element); - }); -} - -function setPageRedactionColor(color) { - document.documentElement.style.setProperty('--page-redaction-color', color); -} - -function setPageNumbersFromNFunctions(pagesDetailed, totalPagesCount) { - pagesDetailed.functions.forEach((fun) => { - if (!isValidFunction(fun)) return; - for (let n = 1; n <= totalPagesCount; n++) { - let pageNumber = eval(fun); - if (!pageNumber || pageNumber <= 0 || pageNumber > totalPagesCount) continue; - pagesDetailed.numbers.add(pageNumber); - } - }); -} - -function setPageNumbersFromRange(pagesDetailed, totalPagesCount) { - pagesDetailed.ranges.forEach((range) => { - for (let i = range.low; i <= range.high && i <= totalPagesCount; i++) { - pagesDetailed.numbers.add(i); - } - }); -} - -function hideOverlay() { - activeOverlay.style.display = 'none'; - activeOverlay.parentElement.classList.remove('active-redaction'); - activeOverlay = null; -} - -function _isEmptyRedaction(redaction) { - return ( - redaction.left == null || - redaction.top == null || - redaction.width == null || - redaction.height == null || - redaction.pageNumber == null - ); -} - -function _nonEmptyRedaction(redaction) { - return !_isEmptyRedaction(redaction); -} - -function copyEvent(e, type) { - if (type == 'pointerleave') - return { - layerX: e.layerX, - layerY: e.layerY, - pageX: e.pageX, - pageY: e.pageY, - clientX: e.clientX, - clientY: e.clientY, - button: e.button, - height: e.height, - width: e.width, - offsetX: e.offsetX, - offsetY: e.offsetY, - pointerId: e.pointerId, - pointerType: e.pointerType, - type: e.type, - screenX: e.screenX, - screenY: e.screenY, - tiltX: e.tiltX, - tiltY: e.tiltY, - x: e.x, - y: e.y, - altKey: e.altKey, - ctrlKey: e.ctrlKey, - isPrimary: e.isPrimary, - isTrusted: e.isTrusted, - metaKey: e.metaKey, - pressure: e.pressure, - returnValue: e.returnValue, - shiftKey: e.shiftKey, - timeStamp: e.timeStamp, - which: e.which, - twist: e.twist, - tangentialPressure: e.tangentialPressure, - target: e.target, - srcElement: e.srcElement, - relatedTarget: e.relatedTarget, - rangeOffset: e.rangeOffset, - rangeParent: e.rangeParent, - explicitOriginalTarget: e.explicitOriginalTarget, - eventPhase: e.eventPhase, - detail: e.detail, - defaultPrevented: e.defaultPrevented, - currentTarget: e.currentTarget, - buttons: e.buttons, - azimuthAngle: e.azimuthAngle, - altitudeAngle: e.altitudeAngle, - }; - - return {}; -} diff --git a/app/core/src/main/resources/static/js/search.js b/app/core/src/main/resources/static/js/search.js deleted file mode 100644 index 0b8877e62..000000000 --- a/app/core/src/main/resources/static/js/search.js +++ /dev/null @@ -1,143 +0,0 @@ - -window.onload = function () { - var items = document.querySelectorAll(".dropdown-item, .nav-link"); - var dummyContainer = document.createElement("div"); - dummyContainer.style.position = "absolute"; - dummyContainer.style.visibility = "hidden"; - dummyContainer.style.whiteSpace = "nowrap"; // Ensure we measure full width - document.body.appendChild(dummyContainer); - - var maxWidth = 0; - - items.forEach(function (item) { - var clone = item.cloneNode(true); - dummyContainer.appendChild(clone); - var width = clone.offsetWidth; - if (width > maxWidth) { - maxWidth = width; - } - dummyContainer.removeChild(clone); - }); - - document.body.removeChild(dummyContainer); - - // Store max width for later use - window.navItemMaxWidth = maxWidth; -}; - -document.querySelector("#navbarSearchInput").addEventListener("input", function (e) { - var searchText = e.target.value.trim().toLowerCase(); - var items = document.querySelectorAll("a.dropdown-item[data-bs-tags]"); - var resultsBox = document.querySelector("#searchResults"); - - resultsBox.innerHTML = ""; - - if (searchText !== "") { - var addedResults = new Set(); - - items.forEach(function (item) { - var titleElement = item.querySelector(".icon-text"); - var iconElement = item.querySelector(".material-symbols-rounded, .icon"); - var itemHref = item.getAttribute("href"); - var tags = item.getAttribute("data-bs-tags") || ""; - - if (titleElement && iconElement && itemHref !== "#") { - var title = titleElement.innerText.trim(); - - if ( - (title.toLowerCase().includes(searchText) || tags.toLowerCase().includes(searchText)) && - !addedResults.has(itemHref) - ) { - var dropdownItem = document.createElement("div"); - dropdownItem.className = "dropdown-item d-flex justify-content-between align-items-center"; - - var contentWrapper = document.createElement("div"); - contentWrapper.className = "d-flex align-items-center flex-grow-1"; - contentWrapper.style.textDecoration = "none"; - contentWrapper.style.color = "inherit"; - - var divElement = item.querySelector("div"); - if (divElement) { - var originalContent = divElement.cloneNode(true); - contentWrapper.appendChild(originalContent); - } else { - // Fallback: create content manually if div is not found - var fallbackContent = document.createElement("div"); - fallbackContent.innerHTML = item.innerHTML; - contentWrapper.appendChild(fallbackContent); - } - - contentWrapper.onclick = function () { - window.location.href = itemHref; - }; - - dropdownItem.appendChild(contentWrapper); - resultsBox.appendChild(dropdownItem); - addedResults.add(itemHref); - } - } - }); - } - - resultsBox.style.width = window.navItemMaxWidth + "px"; -}); - -document.addEventListener('DOMContentLoaded', function () { - const searchDropdown = document.getElementById('searchDropdown'); - const searchInput = document.getElementById('navbarSearchInput'); - - // Check if elements are missing and skip initialization if necessary - if (!searchDropdown || !searchInput) { - console.warn('Search dropdown or input not found. Skipping initialization.'); - return; - } - const dropdownMenu = searchDropdown.querySelector('.dropdown-menu'); - if (!dropdownMenu) { - console.warn('Dropdown menu not found within the search dropdown. Skipping initialization.'); - return; - } - - // Create a single dropdown instance - const dropdownInstance = new bootstrap.Dropdown(searchDropdown); - - // Handle click for mobile - searchDropdown.addEventListener('click', function (e) { - e.preventDefault(); - const isOpen = dropdownMenu.classList.contains('show'); - // Close all other open dropdowns - document.querySelectorAll('.navbar-nav .dropdown-menu.show').forEach((menu) => { - if (menu !== dropdownMenu) { - const parentDropdown = menu.closest('.dropdown'); - if (parentDropdown) { - const parentToggle = parentDropdown.querySelector('[data-bs-toggle="dropdown"]'); - if (parentToggle) { - let instance = bootstrap.Dropdown.getInstance(parentToggle); - if (!instance) { - instance = new bootstrap.Dropdown(parentToggle); - } - instance.hide(); - } - } - } - }); - if (!isOpen) { - dropdownInstance.show(); - setTimeout(() => searchInput.focus(), 150); - } else { - dropdownInstance.hide(); - } - }); - - // Hide dropdown if it's open and user clicks outside - document.addEventListener('click', function (e) { - if (!searchDropdown.contains(e.target) && dropdownMenu.classList.contains('show')) { - dropdownInstance.hide(); - } - }); - - // Keep dropdown open if search input is clicked - searchInput.addEventListener('click', function (e) { - e.stopPropagation(); - }); - -}); diff --git a/app/core/src/main/resources/static/js/settings.js b/app/core/src/main/resources/static/js/settings.js deleted file mode 100644 index 6954913c5..000000000 --- a/app/core/src/main/resources/static/js/settings.js +++ /dev/null @@ -1,41 +0,0 @@ -// Get the download option from local storage, or set it to 'sameWindow' if it doesn't exist -var downloadOption = localStorage.getItem("downloadOption") || "sameWindow"; - -// Set the selected option in the dropdown -document.getElementById("downloadOption").value = downloadOption; - -// Save the selected option to local storage when the dropdown value changes -document.getElementById("downloadOption").addEventListener("change", function () { - downloadOption = this.value; - localStorage.setItem("downloadOption", downloadOption); -}); - -// Get the zipThreshold value from local storage, or set it to 0 if it doesn't exist -var zipThreshold = parseInt(localStorage.getItem("zipThreshold"), 10) || 4; - -// Set the value of the slider and the display span -document.getElementById("zipThreshold").value = zipThreshold; -document.getElementById("zipThresholdValue").textContent = zipThreshold; - -// Save the selected value to local storage when the slider value changes -document.getElementById("zipThreshold").addEventListener("input", function () { - zipThreshold = this.value; - document.getElementById("zipThresholdValue").textContent = zipThreshold; - localStorage.setItem("zipThreshold", zipThreshold); -}); - -var boredWaiting = localStorage.getItem("boredWaiting") || "disabled"; -document.getElementById("boredWaiting").checked = boredWaiting === "enabled"; - -document.getElementById("boredWaiting").addEventListener("change", function () { - boredWaiting = this.checked ? "enabled" : "disabled"; - localStorage.setItem("boredWaiting", boredWaiting); -}); - -var cacheInputs = localStorage.getItem("cacheInputs") || "disabled"; -document.getElementById("cacheInputs").checked = cacheInputs === "enabled"; - -document.getElementById("cacheInputs").addEventListener("change", function () { - cacheInputs = this.checked ? "enabled" : "disabled"; - localStorage.setItem("cacheInputs", cacheInputs); -}); diff --git a/app/core/src/main/resources/static/js/sign/signature-canvas.js b/app/core/src/main/resources/static/js/sign/signature-canvas.js deleted file mode 100644 index 042236d69..000000000 --- a/app/core/src/main/resources/static/js/sign/signature-canvas.js +++ /dev/null @@ -1,137 +0,0 @@ -const signaturePadCanvas = document.getElementById('drawing-pad-canvas'); -const undoButton = document.getElementById("signature-undo-button"); -const redoButton = document.getElementById("signature-redo-button"); -const signaturePad = new SignaturePad(signaturePadCanvas, { - minWidth: 1, - maxWidth: 2, - penColor: '#000000', // default color -}); - -(function initSignatureColor() { - const colorInput = document.getElementById('signature-color'); - if (!colorInput) return; - - if (colorInput.value) { - signaturePad.penColor = colorInput.value; - } - colorInput.addEventListener('input', () => { - signaturePad.penColor = colorInput.value || '#000000'; - }); -})(); - -let undoData = []; - -signaturePad.addEventListener("endStroke", () => { - undoData = []; -}); - -window.addEventListener("keydown", (event) => { - switch (true) { - case event.key === "z" && event.ctrlKey: - undoButton?.click(); - break; - case event.key === "y" && event.ctrlKey: - redoButton?.click(); - break; - } -}); - -function undoDraw() { - const data = signaturePad.toData(); - if (data && data.length > 0) { - const removed = data.pop(); - undoData.push(removed); - signaturePad.fromData(data); - } -} - -function redoDraw() { - if (undoData.length > 0) { - const data = signaturePad.toData(); - data.push(undoData.pop()); - signaturePad.fromData(data); - } -} - -function addDraggableFromPad() { - if (signaturePad.isEmpty()) return; - const startTime = Date.now(); - const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas); - console.log(Date.now() - startTime); - DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl); -} - -function getCroppedCanvasDataUrl(canvas) { - let originalCtx = canvas.getContext('2d', { willReadFrequently: true }); - let originalWidth = canvas.width; - let originalHeight = canvas.height; - let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight); - - let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1; - - for (let y = 0; y < originalHeight; y++) { - for (let x = 0; x < originalWidth; x++) { - let idx = (y * originalWidth + x) * 4; - let alpha = imageData.data[idx + 3]; - if (alpha > 0) { - if (minX > x) minX = x; - if (maxX < x) maxX = x; - if (minY > y) minY = y; - if (maxY < y) maxY = y; - } - } - } - - let croppedWidth = maxX - minX; - let croppedHeight = maxY - minY; - if (croppedWidth < 0 || croppedHeight < 0) return null; - let cutImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight); - - let croppedCanvas = document.createElement('canvas'); - let croppedCtx = croppedCanvas.getContext('2d'); - - croppedCanvas.width = croppedWidth; - croppedCanvas.height = croppedHeight; - croppedCtx.putImageData(cutImageData, 0, 0); - - return croppedCanvas.toDataURL(); -} - -function isMobile() { - const userAgentCheck = /Mobi|Android|iPhone|iPad|iPod|Windows Phone|Opera Mini/i.test(navigator.userAgent); - const viewportCheck = window.matchMedia('(max-width: 768px)').matches; - return userAgentCheck || viewportCheck; -} - -function getDeviceScalingFactor() { - return isMobile() ? 3 : 10; -} - -function resizeCanvas() { - const ratio = Math.max(window.devicePixelRatio || 1, 1); - const additionalFactor = getDeviceScalingFactor(); - - signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor; - signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor; - signaturePadCanvas.getContext('2d').scale(ratio * additionalFactor, ratio * additionalFactor); - - signaturePad.clear(); -} - -const debounce = (fn, delay = 100) => { - let timer; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => fn(...args), delay); - }; -}; - -const debouncedResize = debounce(resizeCanvas, 200); - -new IntersectionObserver((entries) => { - if (entries.some((entry) => entry.intersectionRatio > 0)) { - debouncedResize(); - } -}).observe(signaturePadCanvas); - -new ResizeObserver(debouncedResize).observe(signaturePadCanvas); diff --git a/app/core/src/main/resources/static/js/tab-container.js b/app/core/src/main/resources/static/js/tab-container.js deleted file mode 100644 index afc441934..000000000 --- a/app/core/src/main/resources/static/js/tab-container.js +++ /dev/null @@ -1,37 +0,0 @@ -TabContainer = { - initTabGroups() { - const groups = document.querySelectorAll('.tab-group'); - const unloadedGroups = [...groups].filter((g) => !g.initialised); - unloadedGroups.forEach((group) => { - const containers = group.querySelectorAll('.tab-container'); - const tabTitles = [...containers].map((c) => c.getAttribute('data-title')); - const tabList = document.createElement('div'); - tabList.classList.add('tab-buttons'); - tabTitles.forEach((title) => { - const tabButton = document.createElement('button'); - tabButton.textContent = title; - tabButton.onclick = (e) => { - this.setActiveTab(e.target); - }; - tabList.appendChild(tabButton); - }); - group.prepend(tabList); - - this.setActiveTab(tabList.firstChild); - - group.initialised = true; - }); - }, - setActiveTab(tabButton) { - const group = tabButton.closest('.tab-group'); - - group.querySelectorAll('.active').forEach((el) => el.classList.remove('active')); - - tabButton.classList.add('active'); - group.querySelector(`[data-title="${tabButton.innerHTML}"]`).classList.add('active'); - }, -}; - -document.addEventListener('DOMContentLoaded', () => { - TabContainer.initTabGroups(); -}); diff --git a/app/core/src/main/resources/static/js/thirdParty/bootstrap.min.js b/app/core/src/main/resources/static/js/thirdParty/bootstrap.min.js deleted file mode 100644 index aed031fd6..000000000 --- a/app/core/src/main/resources/static/js/thirdParty/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.0.2 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,(function(t){"use strict";function e(t){if(t&&t.__esModule)return t;var e=Object.create(null);return t&&Object.keys(t).forEach((function(s){if("default"!==s){var i=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,i.get?i:{enumerable:!0,get:function(){return t[s]}})}})),e.default=t,Object.freeze(e)}var s=e(t);const i={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const s=[];let i=t.parentNode;for(;i&&i.nodeType===Node.ELEMENT_NODE&&3!==i.nodeType;)i.matches(e)&&s.push(i),i=i.parentNode;return s},prev(t,e){let s=t.previousElementSibling;for(;s;){if(s.matches(e))return[s];s=s.previousElementSibling}return[]},next(t,e){let s=t.nextElementSibling;for(;s;){if(s.matches(e))return[s];s=s.nextElementSibling}return[]}},n=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},o=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let s=t.getAttribute("href");if(!s||!s.includes("#")&&!s.startsWith("."))return null;s.includes("#")&&!s.startsWith("#")&&(s="#"+s.split("#")[1]),e=s&&"#"!==s?s.trim():null}return e},r=t=>{const e=o(t);return e&&document.querySelector(e)?e:null},a=t=>{const e=o(t);return e?document.querySelector(e):null},l=t=>{t.dispatchEvent(new Event("transitionend"))},c=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),h=t=>c(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?i.findOne(t):null,d=(t,e,s)=>{Object.keys(s).forEach(i=>{const n=s[i],o=e[i],r=o&&c(o)?"element":null==(a=o)?""+a:{}.toString.call(a).match(/\s([a-z]+)/i)[1].toLowerCase();var a;if(!new RegExp(n).test(r))throw new TypeError(`${t.toUpperCase()}: Option "${i}" provided type "${r}" but expected type "${n}".`)})},u=t=>!(!c(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),g=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),p=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?p(t.parentNode):null},f=()=>{},m=t=>t.offsetHeight,_=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},b=[],v=()=>"rtl"===document.documentElement.dir,y=t=>{var e;e=()=>{const e=_();if(e){const s=t.NAME,i=e.fn[s];e.fn[s]=t.jQueryInterface,e.fn[s].Constructor=t,e.fn[s].noConflict=()=>(e.fn[s]=i,t.jQueryInterface)}},"loading"===document.readyState?(b.length||document.addEventListener("DOMContentLoaded",()=>{b.forEach(t=>t())}),b.push(e)):e()},w=t=>{"function"==typeof t&&t()},E=(t,e,s=!0)=>{if(!s)return void w(t);const i=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:s}=window.getComputedStyle(t);const i=Number.parseFloat(e),n=Number.parseFloat(s);return i||n?(e=e.split(",")[0],s=s.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(s))):0})(e)+5;let n=!1;const o=({target:s})=>{s===e&&(n=!0,e.removeEventListener("transitionend",o),w(t))};e.addEventListener("transitionend",o),setTimeout(()=>{n||l(e)},i)},A=(t,e,s,i)=>{let n=t.indexOf(e);if(-1===n)return t[!s&&i?t.length-1:0];const o=t.length;return n+=s?1:-1,i&&(n=(n+o)%o),t[Math.max(0,Math.min(n,o-1))]},T=/[^.]*(?=\..*)\.|.*/,C=/\..*/,k=/::\d+$/,L={};let O=1;const D={mouseenter:"mouseover",mouseleave:"mouseout"},I=/^(mouseenter|mouseleave)/i,N=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function S(t,e){return e&&`${e}::${O++}`||t.uidEvent||O++}function x(t){const e=S(t);return t.uidEvent=e,L[e]=L[e]||{},L[e]}function M(t,e,s=null){const i=Object.keys(t);for(let n=0,o=i.length;nfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};i?i=t(i):s=t(s)}const[o,r,a]=P(e,s,i),l=x(t),c=l[a]||(l[a]={}),h=M(c,r,o?s:null);if(h)return void(h.oneOff=h.oneOff&&n);const d=S(r,e.replace(T,"")),u=o?function(t,e,s){return function i(n){const o=t.querySelectorAll(e);for(let{target:r}=n;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return n.delegateTarget=r,i.oneOff&&B.off(t,n.type,e,s),s.apply(r,[n]);return null}}(t,s,i):function(t,e){return function s(i){return i.delegateTarget=t,s.oneOff&&B.off(t,i.type,e),e.apply(t,[i])}}(t,s);u.delegationSelector=o?s:null,u.originalHandler=r,u.oneOff=n,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function H(t,e,s,i,n){const o=M(e[s],i,n);o&&(t.removeEventListener(s,o,Boolean(n)),delete e[s][o.uidEvent])}function R(t){return t=t.replace(C,""),D[t]||t}const B={on(t,e,s,i){j(t,e,s,i,!1)},one(t,e,s,i){j(t,e,s,i,!0)},off(t,e,s,i){if("string"!=typeof e||!t)return;const[n,o,r]=P(e,s,i),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void H(t,l,r,o,n?s:null)}c&&Object.keys(l).forEach(s=>{!function(t,e,s,i){const n=e[s]||{};Object.keys(n).forEach(o=>{if(o.includes(i)){const i=n[o];H(t,e,s,i.originalHandler,i.delegationSelector)}})}(t,l,s,e.slice(1))});const h=l[r]||{};Object.keys(h).forEach(s=>{const i=s.replace(k,"");if(!a||e.includes(i)){const e=h[s];H(t,l,r,e.originalHandler,e.delegationSelector)}})},trigger(t,e,s){if("string"!=typeof e||!t)return null;const i=_(),n=R(e),o=e!==n,r=N.has(n);let a,l=!0,c=!0,h=!1,d=null;return o&&i&&(a=i.Event(e,s),i(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(n,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==s&&Object.keys(s).forEach(t=>{Object.defineProperty(d,t,{get:()=>s[t]})}),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},$=new Map;var W={set(t,e,s){$.has(t)||$.set(t,new Map);const i=$.get(t);i.has(e)||0===i.size?i.set(e,s):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`)},get:(t,e)=>$.has(t)&&$.get(t).get(e)||null,remove(t,e){if(!$.has(t))return;const s=$.get(t);s.delete(e),0===s.size&&$.delete(t)}};class q{constructor(t){(t=h(t))&&(this._element=t,W.set(this._element,this.constructor.DATA_KEY,this))}dispose(){W.remove(this._element,this.constructor.DATA_KEY),B.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach(t=>{this[t]=null})}_queueCallback(t,e,s=!0){E(t,e,s)}static getInstance(t){return W.get(t,this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.0.2"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return"bs."+this.NAME}static get EVENT_KEY(){return"."+this.DATA_KEY}}class z extends q{static get NAME(){return"alert"}close(t){const e=t?this._getRootElement(t):this._element,s=this._triggerCloseEvent(e);null===s||s.defaultPrevented||this._removeElement(e)}_getRootElement(t){return a(t)||t.closest(".alert")}_triggerCloseEvent(t){return B.trigger(t,"close.bs.alert")}_removeElement(t){t.classList.remove("show");const e=t.classList.contains("fade");this._queueCallback(()=>this._destroyElement(t),t,e)}_destroyElement(t){t.remove(),B.trigger(t,"closed.bs.alert")}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"close"===t&&e[t](this)}))}static handleDismiss(t){return function(e){e&&e.preventDefault(),t.close(this)}}}B.on(document,"click.bs.alert.data-api",'[data-bs-dismiss="alert"]',z.handleDismiss(new z)),y(z);class F extends q{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=F.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function U(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function K(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}B.on(document,"click.bs.button.data-api",'[data-bs-toggle="button"]',t=>{t.preventDefault();const e=t.target.closest('[data-bs-toggle="button"]');F.getOrCreateInstance(e).toggle()}),y(F);const V={setDataAttribute(t,e,s){t.setAttribute("data-bs-"+K(e),s)},removeDataAttribute(t,e){t.removeAttribute("data-bs-"+K(e))},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter(t=>t.startsWith("bs")).forEach(s=>{let i=s.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=U(t.dataset[s])}),e},getDataAttribute:(t,e)=>U(t.getAttribute("data-bs-"+K(e))),offset(t){const e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},Q={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},X={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Y="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z};class et extends q{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=i.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return Q}static get NAME(){return"carousel"}next(){this._slide(Y)}nextWhenVisible(){!document.hidden&&u(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),i.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(l(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=i.findOne(".active.carousel-item",this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void B.one(this._element,"slid.bs.carousel",()=>this.to(t));if(e===t)return this.pause(),void this.cycle();const s=t>e?Y:G;this._slide(s,this._items[t])}_getConfig(t){return t={...Q,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("carousel",t,X),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&B.on(this._element,"keydown.bs.carousel",t=>this._keydown(t)),"hover"===this._config.pause&&(B.on(this._element,"mouseenter.bs.carousel",t=>this.pause(t)),B.on(this._element,"mouseleave.bs.carousel",t=>this.cycle(t))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},e=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},s=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(t=>this.cycle(t),500+this._config.interval))};i.find(".carousel-item img",this._element).forEach(t=>{B.on(t,"dragstart.bs.carousel",t=>t.preventDefault())}),this._pointerEvent?(B.on(this._element,"pointerdown.bs.carousel",e=>t(e)),B.on(this._element,"pointerup.bs.carousel",t=>s(t)),this._element.classList.add("pointer-event")):(B.on(this._element,"touchstart.bs.carousel",e=>t(e)),B.on(this._element,"touchmove.bs.carousel",t=>e(t)),B.on(this._element,"touchend.bs.carousel",t=>s(t)))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?i.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const s=t===Y;return A(this._items,e,s,this._config.wrap)}_triggerSlideEvent(t,e){const s=this._getItemIndex(t),n=this._getItemIndex(i.findOne(".active.carousel-item",this._element));return B.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:s})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=i.findOne(".active",this._indicatorsElement);e.classList.remove("active"),e.removeAttribute("aria-current");const s=i.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{B.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:u,from:o,to:a})};if(this._element.classList.contains("slide")){r.classList.add(d),m(r),n.classList.add(h),r.classList.add(h);const t=()=>{r.classList.remove(h,d),r.classList.add("active"),n.classList.remove("active",d,h),this._isSliding=!1,setTimeout(g,0)};this._queueCallback(t,n,!0)}else n.classList.remove("active"),r.classList.add("active"),this._isSliding=!1,g();l&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?v()?t===Z?G:Y:t===Z?Y:G:t}_orderToDirection(t){return[Y,G].includes(t)?v()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const s=et.getOrCreateInstance(t,e);let{_config:i}=s;"object"==typeof e&&(i={...i,...e});const n="string"==typeof e?e:i.slide;if("number"==typeof e)s.to(e);else if("string"==typeof n){if(void 0===s[n])throw new TypeError(`No method named "${n}"`);s[n]()}else i.interval&&i.ride&&(s.pause(),s.cycle())}static jQueryInterface(t){return this.each((function(){et.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=a(this);if(!e||!e.classList.contains("carousel"))return;const s={...V.getDataAttributes(e),...V.getDataAttributes(this)},i=this.getAttribute("data-bs-slide-to");i&&(s.interval=!1),et.carouselInterface(e,s),i&&et.getInstance(e).to(i),t.preventDefault()}}B.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",et.dataApiClickHandler),B.on(window,"load.bs.carousel.data-api",()=>{const t=i.find('[data-bs-ride="carousel"]');for(let e=0,s=t.length;et===this._element);null!==n&&o.length&&(this._selector=n,this._triggerArray.push(e))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}static get Default(){return st}static get NAME(){return"collapse"}toggle(){this._element.classList.contains("show")?this.hide():this.show()}show(){if(this._isTransitioning||this._element.classList.contains("show"))return;let t,e;this._parent&&(t=i.find(".show, .collapsing",this._parent).filter(t=>"string"==typeof this._config.parent?t.getAttribute("data-bs-parent")===this._config.parent:t.classList.contains("collapse")),0===t.length&&(t=null));const s=i.findOne(this._selector);if(t){const i=t.find(t=>s!==t);if(e=i?nt.getInstance(i):null,e&&e._isTransitioning)return}if(B.trigger(this._element,"show.bs.collapse").defaultPrevented)return;t&&t.forEach(t=>{s!==t&&nt.collapseInterface(t,"hide"),e||W.set(t,"bs.collapse",null)});const n=this._getDimension();this._element.classList.remove("collapse"),this._element.classList.add("collapsing"),this._element.style[n]=0,this._triggerArray.length&&this._triggerArray.forEach(t=>{t.classList.remove("collapsed"),t.setAttribute("aria-expanded",!0)}),this.setTransitioning(!0);const o="scroll"+(n[0].toUpperCase()+n.slice(1));this._queueCallback(()=>{this._element.classList.remove("collapsing"),this._element.classList.add("collapse","show"),this._element.style[n]="",this.setTransitioning(!1),B.trigger(this._element,"shown.bs.collapse")},this._element,!0),this._element.style[n]=this._element[o]+"px"}hide(){if(this._isTransitioning||!this._element.classList.contains("show"))return;if(B.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=this._element.getBoundingClientRect()[t]+"px",m(this._element),this._element.classList.add("collapsing"),this._element.classList.remove("collapse","show");const e=this._triggerArray.length;if(e>0)for(let t=0;t{this.setTransitioning(!1),this._element.classList.remove("collapsing"),this._element.classList.add("collapse"),B.trigger(this._element,"hidden.bs.collapse")},this._element,!0)}setTransitioning(t){this._isTransitioning=t}_getConfig(t){return(t={...st,...t}).toggle=Boolean(t.toggle),d("collapse",t,it),t}_getDimension(){return this._element.classList.contains("width")?"width":"height"}_getParent(){let{parent:t}=this._config;t=h(t);const e=`[data-bs-toggle="collapse"][data-bs-parent="${t}"]`;return i.find(e,t).forEach(t=>{const e=a(t);this._addAriaAndCollapsedClass(e,[t])}),t}_addAriaAndCollapsedClass(t,e){if(!t||!e.length)return;const s=t.classList.contains("show");e.forEach(t=>{s?t.classList.remove("collapsed"):t.classList.add("collapsed"),t.setAttribute("aria-expanded",s)})}static collapseInterface(t,e){let s=nt.getInstance(t);const i={...st,...V.getDataAttributes(t),..."object"==typeof e&&e?e:{}};if(!s&&i.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(i.toggle=!1),s||(s=new nt(t,i)),"string"==typeof e){if(void 0===s[e])throw new TypeError(`No method named "${e}"`);s[e]()}}static jQueryInterface(t){return this.each((function(){nt.collapseInterface(this,t)}))}}B.on(document,"click.bs.collapse.data-api",'[data-bs-toggle="collapse"]',(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=V.getDataAttributes(this),s=r(this);i.find(s).forEach(t=>{const s=nt.getInstance(t);let i;s?(null===s._parent&&"string"==typeof e.parent&&(s._config.parent=e.parent,s._parent=s._getParent()),i="toggle"):i=e,nt.collapseInterface(t,i)})})),y(nt);const ot=new RegExp("ArrowUp|ArrowDown|Escape"),rt=v()?"top-end":"top-start",at=v()?"top-start":"top-end",lt=v()?"bottom-end":"bottom-start",ct=v()?"bottom-start":"bottom-end",ht=v()?"left-start":"right-start",dt=v()?"right-start":"left-start",ut={offset:[0,2],boundary:"clippingParents",reference:"toggle",display:"dynamic",popperConfig:null,autoClose:!0},gt={offset:"(array|string|function)",boundary:"(string|element)",reference:"(string|element|object)",display:"string",popperConfig:"(null|object|function)",autoClose:"(boolean|string)"};class pt extends q{constructor(t,e){super(t),this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}static get Default(){return ut}static get DefaultType(){return gt}static get NAME(){return"dropdown"}toggle(){g(this._element)||(this._element.classList.contains("show")?this.hide():this.show())}show(){if(g(this._element)||this._menu.classList.contains("show"))return;const t=pt.getParentFromElement(this._element),e={relatedTarget:this._element};if(!B.trigger(this._element,"show.bs.dropdown",e).defaultPrevented){if(this._inNavbar)V.setDataAttribute(this._menu,"popper","none");else{if(void 0===s)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:c(this._config.reference)?e=h(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find(t=>"applyStyles"===t.name&&!1===t.enabled);this._popper=s.createPopper(e,this._menu,i),n&&V.setDataAttribute(this._menu,"popper","static")}"ontouchstart"in document.documentElement&&!t.closest(".navbar-nav")&&[].concat(...document.body.children).forEach(t=>B.on(t,"mouseover",f)),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),B.trigger(this._element,"shown.bs.dropdown",e)}}hide(){if(g(this._element)||!this._menu.classList.contains("show"))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_addEventListeners(){B.on(this._element,"click.bs.dropdown",t=>{t.preventDefault(),this.toggle()})}_completeHide(t){B.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>B.off(t,"mouseover",f)),this._popper&&this._popper.destroy(),this._menu.classList.remove("show"),this._element.classList.remove("show"),this._element.setAttribute("aria-expanded","false"),V.removeDataAttribute(this._menu,"popper"),B.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...V.getDataAttributes(this._element),...t},d("dropdown",t,this.constructor.DefaultType),"object"==typeof t.reference&&!c(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError("dropdown".toUpperCase()+': Option "reference" provided type "object" without a required "getBoundingClientRect" method.');return t}_getMenuElement(){return i.next(this._element,".dropdown-menu")[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ht;if(t.classList.contains("dropstart"))return dt;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?at:rt:e?ct:lt}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const s=i.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(u);s.length&&A(s,e,"ArrowDown"===t,!s.includes(e)).focus()}static dropdownInterface(t,e){const s=pt.getOrCreateInstance(t,e);if("string"==typeof e){if(void 0===s[e])throw new TypeError(`No method named "${e}"`);s[e]()}}static jQueryInterface(t){return this.each((function(){pt.dropdownInterface(this,t)}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=i.find('[data-bs-toggle="dropdown"]');for(let s=0,i=e.length;sthis.matches('[data-bs-toggle="dropdown"]')?this:i.prev(this,'[data-bs-toggle="dropdown"]')[0];return"Escape"===t.key?(s().focus(),void pt.clearMenus()):"ArrowUp"===t.key||"ArrowDown"===t.key?(e||s().click(),void pt.getInstance(s())._selectMenuItem(t)):void(e&&"Space"!==t.key||pt.clearMenus())}}B.on(document,"keydown.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',pt.dataApiKeydownHandler),B.on(document,"keydown.bs.dropdown.data-api",".dropdown-menu",pt.dataApiKeydownHandler),B.on(document,"click.bs.dropdown.data-api",pt.clearMenus),B.on(document,"keyup.bs.dropdown.data-api",pt.clearMenus),B.on(document,"click.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',(function(t){t.preventDefault(),pt.dropdownInterface(this)})),y(pt);class ft{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,"paddingRight",e=>e+t),this._setElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight",e=>e+t),this._setElementAttributes(".sticky-top","marginRight",e=>e-t)}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,s){const i=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+i)return;this._saveInitialAttribute(t,e);const n=window.getComputedStyle(t)[e];t.style[e]=s(Number.parseFloat(n))+"px"})}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight"),this._resetElementAttributes(".sticky-top","marginRight")}_saveInitialAttribute(t,e){const s=t.style[e];s&&V.setDataAttribute(t,e,s)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const s=V.getDataAttribute(t,e);void 0===s?t.style.removeProperty(e):(V.removeDataAttribute(t,e),t.style[e]=s)})}_applyManipulationCallback(t,e){c(t)?e(t):i.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const mt={isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},_t={isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"};class bt{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&m(this._getElement()),this._getElement().classList.add("show"),this._emulateAnimation(()=>{w(t)})):w(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove("show"),this._emulateAnimation(()=>{this.dispose(),w(t)})):w(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className="modal-backdrop",this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...mt,..."object"==typeof t?t:{}}).rootElement=h(t.rootElement),d("backdrop",t,_t),t}_append(){this._isAppended||(this._config.rootElement.appendChild(this._getElement()),B.on(this._getElement(),"mousedown.bs.backdrop",()=>{w(this._config.clickCallback)}),this._isAppended=!0)}dispose(){this._isAppended&&(B.off(this._element,"mousedown.bs.backdrop"),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){E(t,this._getElement(),this._config.isAnimated)}}const vt={backdrop:!0,keyboard:!0,focus:!0},yt={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"};class wt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=i.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new ft}static get Default(){return vt}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||B.trigger(this._element,"show.bs.modal",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add("modal-open"),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),B.on(this._element,"click.dismiss.bs.modal",'[data-bs-dismiss="modal"]',t=>this.hide(t)),B.on(this._dialog,"mousedown.dismiss.bs.modal",()=>{B.one(this._element,"mouseup.dismiss.bs.modal",t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)})}),this._showBackdrop(()=>this._showElement(t)))}hide(t){if(t&&["A","AREA"].includes(t.target.tagName)&&t.preventDefault(),!this._isShown||this._isTransitioning)return;if(B.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const e=this._isAnimated();e&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),B.off(document,"focusin.bs.modal"),this._element.classList.remove("show"),B.off(this._element,"click.dismiss.bs.modal"),B.off(this._dialog,"mousedown.dismiss.bs.modal"),this._queueCallback(()=>this._hideModal(),this._element,e)}dispose(){[window,this._dialog].forEach(t=>B.off(t,".bs.modal")),this._backdrop.dispose(),super.dispose(),B.off(document,"focusin.bs.modal")}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bt({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_getConfig(t){return t={...vt,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("modal",t,yt),t}_showElement(t){const e=this._isAnimated(),s=i.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,s&&(s.scrollTop=0),e&&m(this._element),this._element.classList.add("show"),this._config.focus&&this._enforceFocus(),this._queueCallback(()=>{this._config.focus&&this._element.focus(),this._isTransitioning=!1,B.trigger(this._element,"shown.bs.modal",{relatedTarget:t})},this._dialog,e)}_enforceFocus(){B.off(document,"focusin.bs.modal"),B.on(document,"focusin.bs.modal",t=>{document===t.target||this._element===t.target||this._element.contains(t.target)||this._element.focus()})}_setEscapeEvent(){this._isShown?B.on(this._element,"keydown.dismiss.bs.modal",t=>{this._config.keyboard&&"Escape"===t.key?(t.preventDefault(),this.hide()):this._config.keyboard||"Escape"!==t.key||this._triggerBackdropTransition()}):B.off(this._element,"keydown.dismiss.bs.modal")}_setResizeEvent(){this._isShown?B.on(window,"resize.bs.modal",()=>this._adjustDialog()):B.off(window,"resize.bs.modal")}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove("modal-open"),this._resetAdjustments(),this._scrollBar.reset(),B.trigger(this._element,"hidden.bs.modal")})}_showBackdrop(t){B.on(this._element,"click.dismiss.bs.modal",t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())}),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(B.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:s}=this._element,i=e>document.documentElement.clientHeight;!i&&"hidden"===s.overflowY||t.contains("modal-static")||(i||(s.overflowY="hidden"),t.add("modal-static"),this._queueCallback(()=>{t.remove("modal-static"),i||this._queueCallback(()=>{s.overflowY=""},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),s=e>0;(!s&&t&&!v()||s&&!t&&v())&&(this._element.style.paddingLeft=e+"px"),(s&&!t&&!v()||!s&&t&&v())&&(this._element.style.paddingRight=e+"px")}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const s=wt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===s[t])throw new TypeError(`No method named "${t}"`);s[t](e)}}))}}B.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=a(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),B.one(e,"show.bs.modal",t=>{t.defaultPrevented||B.one(e,"hidden.bs.modal",()=>{u(this)&&this.focus()})}),wt.getOrCreateInstance(e).toggle(this)})),y(wt);const Et={backdrop:!0,keyboard:!0,scroll:!1},At={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"};class Tt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._addEventListeners()}static get NAME(){return"offcanvas"}static get Default(){return Et}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||B.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||((new ft).hide(),this._enforceFocusOnElement(this._element)),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add("show"),this._queueCallback(()=>{B.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(B.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(B.off(document,"focusin.bs.offcanvas"),this._element.blur(),this._isShown=!1,this._element.classList.remove("show"),this._backdrop.hide(),this._queueCallback(()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new ft).reset(),B.trigger(this._element,"hidden.bs.offcanvas")},this._element,!0)))}dispose(){this._backdrop.dispose(),super.dispose(),B.off(document,"focusin.bs.offcanvas")}_getConfig(t){return t={...Et,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("offcanvas",t,At),t}_initializeBackDrop(){return new bt({isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_enforceFocusOnElement(t){B.off(document,"focusin.bs.offcanvas"),B.on(document,"focusin.bs.offcanvas",e=>{document===e.target||t===e.target||t.contains(e.target)||t.focus()}),t.focus()}_addEventListeners(){B.on(this._element,"click.dismiss.bs.offcanvas",'[data-bs-dismiss="offcanvas"]',()=>this.hide()),B.on(this._element,"keydown.dismiss.bs.offcanvas",t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()})}static jQueryInterface(t){return this.each((function(){const e=Tt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}B.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=a(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),g(this))return;B.one(e,"hidden.bs.offcanvas",()=>{u(this)&&this.focus()});const s=i.findOne(".offcanvas.show");s&&s!==e&&Tt.getInstance(s).hide(),Tt.getOrCreateInstance(e).toggle(this)})),B.on(window,"load.bs.offcanvas.data-api",()=>i.find(".offcanvas.show").forEach(t=>Tt.getOrCreateInstance(t).show())),y(Tt);const Ct=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),kt=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,Lt=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Ot=(t,e)=>{const s=t.nodeName.toLowerCase();if(e.includes(s))return!Ct.has(s)||Boolean(kt.test(t.nodeValue)||Lt.test(t.nodeValue));const i=e.filter(t=>t instanceof RegExp);for(let t=0,e=i.length;t{Ot(t,a)||s.removeAttribute(t.nodeName)})}return i.body.innerHTML}const It=new RegExp("(^|\\s)bs-tooltip\\S+","g"),Nt=new Set(["sanitize","allowList","sanitizeFn"]),St={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},xt={AUTO:"auto",TOP:"top",RIGHT:v()?"left":"right",BOTTOM:"bottom",LEFT:v()?"right":"left"},Mt={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Pt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"};class jt extends q{constructor(t,e){if(void 0===s)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return Mt}static get NAME(){return"tooltip"}static get Event(){return Pt}static get DefaultType(){return St}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),B.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.remove(),this._popper&&this._popper.destroy(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=B.trigger(this._element,this.constructor.Event.SHOW),e=p(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;const o=this.getTipElement(),r=n(this.constructor.NAME);o.setAttribute("id",r),this._element.setAttribute("aria-describedby",r),this.setContent(),this._config.animation&&o.classList.add("fade");const a="function"==typeof this._config.placement?this._config.placement.call(this,o,this._element):this._config.placement,l=this._getAttachment(a);this._addAttachmentClass(l);const{container:c}=this._config;W.set(o,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(c.appendChild(o),B.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=s.createPopper(this._element,o,this._getPopperConfig(l)),o.classList.add("show");const h="function"==typeof this._config.customClass?this._config.customClass():this._config.customClass;h&&o.classList.add(...h.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>{B.on(t,"mouseover",f)});const d=this.tip.classList.contains("fade");this._queueCallback(()=>{const t=this._hoverState;this._hoverState=null,B.trigger(this._element,this.constructor.Event.SHOWN),"out"===t&&this._leave(null,this)},this.tip,d)}hide(){if(!this._popper)return;const t=this.getTipElement();if(B.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove("show"),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>B.off(t,"mouseover",f)),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains("fade");this._queueCallback(()=>{this._isWithActiveTrigger()||("show"!==this._hoverState&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),B.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))},this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");return t.innerHTML=this._config.template,this.tip=t.children[0],this.tip}setContent(){const t=this.getTipElement();this.setElementContent(i.findOne(".tooltip-inner",t),this.getTitle()),t.classList.remove("fade","show")}setElementContent(t,e){if(null!==t)return c(e)?(e=h(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Dt(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){let t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this._config.title?this._config.title.call(this._element):this._config.title),t}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){const s=this.constructor.DATA_KEY;return(e=e||W.get(t.delegateTarget,s))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),W.set(t.delegateTarget,s,e)),e}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))}_getAttachment(t){return xt[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach(t=>{if("click"===t)B.on(this._element,this.constructor.Event.CLICK,this._config.selector,t=>this.toggle(t));else if("manual"!==t){const e="hover"===t?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,s="hover"===t?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;B.on(this._element,e,this._config.selector,t=>this._enter(t)),B.on(this._element,s,this._config.selector,t=>this._leave(t))}}),this._hideModalHandler=()=>{this._element&&this.hide()},B.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e._config.delay&&e._config.delay.show?e._timeout=setTimeout(()=>{"show"===e._hoverState&&e.show()},e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e._config.delay&&e._config.delay.hide?e._timeout=setTimeout(()=>{"out"===e._hoverState&&e.hide()},e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=V.getDataAttributes(this._element);return Object.keys(e).forEach(t=>{Nt.has(t)&&delete e[t]}),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:h(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d("tooltip",t,this.constructor.DefaultType),t.sanitize&&(t.template=Dt(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};if(this._config)for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(It);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){const e=jt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}y(jt);const Ht=new RegExp("(^|\\s)bs-popover\\S+","g"),Rt={...jt.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},Bt={...jt.DefaultType,content:"(string|element|function)"},$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class Wt extends jt{static get Default(){return Rt}static get NAME(){return"popover"}static get Event(){return $t}static get DefaultType(){return Bt}isWithContent(){return this.getTitle()||this._getContent()}getTipElement(){return this.tip||(this.tip=super.getTipElement(),this.getTitle()||i.findOne(".popover-header",this.tip).remove(),this._getContent()||i.findOne(".popover-body",this.tip).remove()),this.tip}setContent(){const t=this.getTipElement();this.setElementContent(i.findOne(".popover-header",t),this.getTitle());let e=this._getContent();"function"==typeof e&&(e=e.call(this._element)),this.setElementContent(i.findOne(".popover-body",t),e),t.classList.remove("fade","show")}_addAttachmentClass(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))}_getContent(){return this._element.getAttribute("data-bs-content")||this._config.content}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Ht);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}static jQueryInterface(t){return this.each((function(){const e=Wt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}y(Wt);const qt={offset:10,method:"auto",target:""},zt={offset:"number",method:"string",target:"(string|element)"};class Ft extends q{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._selector=`${this._config.target} .nav-link, ${this._config.target} .list-group-item, ${this._config.target} .dropdown-item`,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,B.on(this._scrollElement,"scroll.bs.scrollspy",()=>this._process()),this.refresh(),this._process()}static get Default(){return qt}static get NAME(){return"scrollspy"}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":"position",e="auto"===this._config.method?t:this._config.method,s="position"===e?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),i.find(this._selector).map(t=>{const n=r(t),o=n?i.findOne(n):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[V[e](o).top+s,n]}return null}).filter(t=>t).sort((t,e)=>t[0]-e[0]).forEach(t=>{this._offsets.push(t[0]),this._targets.push(t[1])})}dispose(){B.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){if("string"!=typeof(t={...qt,...V.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target&&c(t.target)){let{id:e}=t.target;e||(e=n("scrollspy"),t.target.id=e),t.target="#"+e}return d("scrollspy",t,zt),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),s=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=s){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`),s=i.findOne(e.join(","));s.classList.contains("dropdown-item")?(i.findOne(".dropdown-toggle",s.closest(".dropdown")).classList.add("active"),s.classList.add("active")):(s.classList.add("active"),i.parents(s,".nav, .list-group").forEach(t=>{i.prev(t,".nav-link, .list-group-item").forEach(t=>t.classList.add("active")),i.prev(t,".nav-item").forEach(t=>{i.children(t,".nav-link").forEach(t=>t.classList.add("active"))})})),B.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){i.find(this._selector).filter(t=>t.classList.contains("active")).forEach(t=>t.classList.remove("active"))}static jQueryInterface(t){return this.each((function(){const e=Ft.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}B.on(window,"load.bs.scrollspy.data-api",()=>{i.find('[data-bs-spy="scroll"]').forEach(t=>new Ft(t))}),y(Ft);class Ut extends q{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains("active"))return;let t;const e=a(this._element),s=this._element.closest(".nav, .list-group");if(s){const e="UL"===s.nodeName||"OL"===s.nodeName?":scope > li > .active":".active";t=i.find(e,s),t=t[t.length-1]}const n=t?B.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(B.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==n&&n.defaultPrevented)return;this._activate(this._element,s);const o=()=>{B.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),B.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,s){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?i.children(e,".active"):i.find(":scope > li > .active",e))[0],o=s&&n&&n.classList.contains("fade"),r=()=>this._transitionComplete(t,n,s);n&&o?(n.classList.remove("show"),this._queueCallback(r,t,!0)):r()}_transitionComplete(t,e,s){if(e){e.classList.remove("active");const t=i.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),m(t),t.classList.contains("fade")&&t.classList.add("show");let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&i.find(".dropdown-toggle",e).forEach(t=>t.classList.add("active")),t.setAttribute("aria-expanded",!0)}s&&s()}static jQueryInterface(t){return this.each((function(){const e=Ut.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}B.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),g(this)||Ut.getOrCreateInstance(this).show()})),y(Ut);const Kt={animation:"boolean",autohide:"boolean",delay:"number"},Vt={animation:!0,autohide:!0,delay:5e3};class Qt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Kt}static get Default(){return Vt}static get NAME(){return"toast"}show(){B.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove("hide"),m(this._element),this._element.classList.add("showing"),this._queueCallback(()=>{this._element.classList.remove("showing"),this._element.classList.add("show"),B.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this._element.classList.contains("show")&&(B.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.remove("show"),this._queueCallback(()=>{this._element.classList.add("hide"),B.trigger(this._element,"hidden.bs.toast")},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),super.dispose()}_getConfig(t){return t={...Vt,...V.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},d("toast",t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const s=t.relatedTarget;this._element===s||this._element.contains(s)||this._maybeScheduleHide()}_setListeners(){B.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',()=>this.hide()),B.on(this._element,"mouseover.bs.toast",t=>this._onInteraction(t,!0)),B.on(this._element,"mouseout.bs.toast",t=>this._onInteraction(t,!1)),B.on(this._element,"focusin.bs.toast",t=>this._onInteraction(t,!0)),B.on(this._element,"focusout.bs.toast",t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Qt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return y(Qt),{Alert:z,Button:F,Carousel:et,Collapse:nt,Dropdown:pt,Modal:wt,Offcanvas:Tt,Popover:Wt,ScrollSpy:Ft,Tab:Ut,Toast:Qt,Tooltip:jt}})); -//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/app/core/src/main/resources/static/js/thirdParty/bootstrap.min.js.map b/app/core/src/main/resources/static/js/thirdParty/bootstrap.min.js.map deleted file mode 100644 index 7a01d5cd8..000000000 --- a/app/core/src/main/resources/static/js/thirdParty/bootstrap.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../js/src/dom/selector-engine.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/data.js","../../js/src/base-component.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/dom/manipulator.js","../../js/src/carousel.js","../../js/src/collapse.js","../../node_modules/@popperjs/core/lib/enums.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","../../node_modules/@popperjs/core/lib/dom-utils/contains.js","../../node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","../../node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","../../node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","../../node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","../../node_modules/@popperjs/core/lib/utils/math.js","../../node_modules/@popperjs/core/lib/utils/within.js","../../node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","../../node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","../../node_modules/@popperjs/core/lib/utils/expandToHashMap.js","../../node_modules/@popperjs/core/lib/modifiers/arrow.js","../../node_modules/@popperjs/core/lib/modifiers/computeStyles.js","../../node_modules/@popperjs/core/lib/modifiers/eventListeners.js","../../node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","../../node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","../../node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","../../node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","../../node_modules/@popperjs/core/lib/utils/rectToClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","../../node_modules/@popperjs/core/lib/utils/getVariation.js","../../node_modules/@popperjs/core/lib/utils/computeOffsets.js","../../node_modules/@popperjs/core/lib/utils/detectOverflow.js","../../node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","../../node_modules/@popperjs/core/lib/modifiers/flip.js","../../node_modules/@popperjs/core/lib/modifiers/hide.js","../../node_modules/@popperjs/core/lib/modifiers/offset.js","../../node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","../../node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","../../node_modules/@popperjs/core/lib/utils/getAltAxis.js","../../node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","../../node_modules/@popperjs/core/lib/createPopper.js","../../node_modules/@popperjs/core/lib/utils/debounce.js","../../node_modules/@popperjs/core/lib/utils/mergeByName.js","../../node_modules/@popperjs/core/lib/utils/orderModifiers.js","../../node_modules/@popperjs/core/lib/popper-lite.js","../../node_modules/@popperjs/core/lib/popper.js","../../js/src/dropdown.js","../../js/src/util/scrollbar.js","../../js/src/util/backdrop.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"names":["SelectorEngine","find","selector","element","document","documentElement","concat","Element","prototype","querySelectorAll","call","findOne","querySelector","children","filter","child","matches","parents","ancestor","parentNode","nodeType","Node","ELEMENT_NODE","push","prev","previous","previousElementSibling","next","nextElementSibling","getUID","prefix","Math","floor","random","getElementById","getSelector","getAttribute","hrefAttr","includes","startsWith","split","trim","getSelectorFromElement","getElementFromSelector","triggerTransitionEnd","dispatchEvent","Event","isElement","obj","jquery","getElement","length","typeCheckConfig","componentName","config","configTypes","Object","keys","forEach","property","expectedTypes","value","valueType","toString","match","toLowerCase","RegExp","test","TypeError","toUpperCase","isVisible","getClientRects","getComputedStyle","getPropertyValue","isDisabled","classList","contains","disabled","hasAttribute","findShadowRoot","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","window","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","execute","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","index","indexOf","listLength","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","customEventsRegex","nativeEvents","Set","getUidEvent","uid","getEvent","findHandler","events","delegationSelector","uidEventList","i","len","event","originalHandler","normalizeParams","originalTypeEvent","delegationFn","delegation","typeEvent","getTypeEvent","has","addHandler","oneOff","wrapFn","relatedTarget","delegateTarget","this","handlers","previousFn","replace","domElements","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","on","one","inNamespace","isNamespace","elementEvent","namespace","storeElementEvent","handlerKey","removeNamespacedHandlers","slice","keyHandlers","trigger","args","isNative","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","evt","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","createEvent","initEvent","CustomEvent","cancelable","key","defineProperty","get","preventDefault","elementMap","Map","Data","set","instance","instanceMap","size","console","error","Array","from","remove","delete","BaseComponent","constructor","_element","DATA_KEY","dispose","EVENT_KEY","getOwnPropertyNames","propertyName","_queueCallback","isAnimated","[object Object]","getInstance","VERSION","Error","Alert","close","rootElement","_getRootElement","customEvent","_triggerCloseEvent","_removeElement","closest","_destroyElement","each","data","getOrCreateInstance","alertInstance","handleDismiss","Button","toggle","setAttribute","normalizeData","val","normalizeDataKey","chr","button","Manipulator","setDataAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","dataset","pureKey","charAt","getDataAttribute","offset","rect","getBoundingClientRect","top","scrollTop","left","scrollLeft","position","offsetTop","offsetLeft","Default","interval","keyboard","slide","pause","wrap","touch","DefaultType","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","KEY_TO_DIRECTION","ArrowLeft","ArrowRight","Carousel","super","_items","_interval","_activeElement","_isPaused","_isSliding","touchTimeout","touchStartX","touchDeltaX","_config","_getConfig","_indicatorsElement","_touchSupported","navigator","maxTouchPoints","_pointerEvent","PointerEvent","_addEventListeners","_slide","nextWhenVisible","hidden","cycle","clearInterval","_updateInterval","setInterval","visibilityState","bind","to","activeIndex","_getItemIndex","order","_handleSwipe","absDeltax","abs","direction","_keydown","_addTouchEventListeners","start","pointerType","touches","clientX","move","end","clearTimeout","itemImg","e","add","tagName","_getItemByOrder","isNext","_triggerSlideEvent","eventDirectionName","targetIndex","fromIndex","_setActiveIndicatorElement","activeIndicator","indicators","parseInt","elementInterval","defaultInterval","directionOrOrder","_directionToOrder","activeElementIndex","nextElement","nextElementIndex","isCycling","directionalClassName","orderClassName","_orderToDirection","triggerSlidEvent","completeCallBack","action","ride","carouselInterface","slideIndex","dataApiClickHandler","carousels","parent","Collapse","_isTransitioning","_triggerArray","id","toggleList","elem","filterElement","foundElem","_selector","_parent","_getParent","_addAriaAndCollapsedClass","hide","show","actives","activesData","container","tempActiveData","elemActive","collapseInterface","dimension","_getDimension","style","setTransitioning","scrollSize","triggerArrayLength","isTransitioning","selected","triggerArray","isOpen","triggerData","bottom","right","basePlacements","variationPlacements","reduce","acc","placement","placements","modifierPhases","getNodeName","nodeName","getWindow","node","ownerDocument","defaultView","isHTMLElement","HTMLElement","isShadowRoot","applyStyles$1","enabled","phase","_ref","state","elements","styles","assign","effect","_ref2","initialStyles","popper","options","strategy","margin","arrow","reference","hasOwnProperty","attribute","requires","getBasePlacement","width","height","x","y","getLayoutRect","clientRect","offsetWidth","rootNode","isSameNode","host","isTableElement","getDocumentElement","getParentNode","assignedSlot","getTrueOffsetParent","offsetParent","getOffsetParent","isFirefox","userAgent","currentNode","css","transform","perspective","contain","willChange","getContainingBlock","getMainAxisFromPlacement","round","within","mathMax","mathMin","mergePaddingObject","paddingObject","expandToHashMap","hashMap","arrow$1","_state$modifiersData$","arrowElement","popperOffsets","modifiersData","basePlacement","axis","padding","rects","toPaddingObject","arrowRect","minProp","maxProp","endDiff","startDiff","arrowOffsetParent","clientSize","clientHeight","clientWidth","centerToReference","center","axisProp","centerOffset","_options$element","requiresIfExists","unsetSides","mapToStyles","_Object$assign2","popperRect","offsets","gpuAcceleration","adaptive","roundOffsets","_ref3","dpr","devicePixelRatio","roundOffsetsByDPR","_ref3$x","_ref3$y","hasX","hasY","sideX","sideY","win","heightProp","widthProp","_Object$assign","commonStyles","computeStyles$1","_ref4","_options$gpuAccelerat","_options$adaptive","_options$roundOffsets","data-popper-placement","passive","eventListeners","_options$scroll","scroll","_options$resize","resize","scrollParents","scrollParent","update","hash","getOppositePlacement","matched","getOppositeVariationPlacement","getWindowScroll","pageXOffset","pageYOffset","getWindowScrollBarX","isScrollParent","_getComputedStyle","overflow","overflowX","overflowY","listScrollParents","_element$ownerDocumen","getScrollParent","isBody","visualViewport","updatedList","rectToClientRect","getClientRectFromMixedType","clippingParent","html","getViewportRect","clientTop","clientLeft","getInnerBoundingClientRect","winScroll","scrollWidth","scrollHeight","getDocumentRect","getVariation","computeOffsets","variation","commonX","commonY","mainAxis","detectOverflow","_options","_options$placement","_options$boundary","boundary","_options$rootBoundary","rootBoundary","_options$elementConte","elementContext","_options$altBoundary","altBoundary","_options$padding","altContext","referenceElement","clippingClientRect","mainClippingParents","clippingParents","clipperElement","getClippingParents","firstClippingParent","clippingRect","accRect","getClippingRect","contextElement","referenceClientRect","popperClientRect","elementClientRect","overflowOffsets","offsetData","multiply","computeAutoPlacement","flipVariations","_options$allowedAutoP","allowedAutoPlacements","allPlacements","allowedPlacements","overflows","sort","a","b","flip$1","_skip","_options$mainAxis","checkMainAxis","_options$altAxis","altAxis","checkAltAxis","specifiedFallbackPlacements","fallbackPlacements","_options$flipVariatio","preferredPlacement","oppositePlacement","getExpandedFallbackPlacements","referenceRect","checksMap","makeFallbackChecks","firstFittingPlacement","_basePlacement","isStartVariation","isVertical","mainVariationSide","altVariationSide","checks","every","check","_loop","_i","fittingPlacement","reset","getSideOffsets","preventedOffsets","isAnySideFullyClipped","some","side","hide$1","preventOverflow","referenceOverflow","popperAltOverflow","referenceClippingOffsets","popperEscapeOffsets","isReferenceHidden","hasPopperEscaped","data-popper-reference-hidden","data-popper-escaped","offset$1","_options$offset","invertDistance","skidding","distance","distanceAndSkiddingToXY","_data$state$placement","popperOffsets$1","preventOverflow$1","_options$tether","tether","_options$tetherOffset","tetherOffset","isBasePlacement","tetherOffsetValue","mainSide","altSide","additive","minLen","maxLen","arrowPaddingObject","arrowPaddingMin","arrowPaddingMax","arrowLen","minOffset","maxOffset","clientOffset","offsetModifierValue","tetherMin","tetherMax","preventedOffset","_mainSide","_altSide","_offset","_min","_max","_preventedOffset","getCompositeRect","elementOrVirtualElement","isFixed","isOffsetParentAnElement","DEFAULT_OPTIONS","modifiers","areValidElements","_len","arguments","_key","popperGenerator","generatorOptions","_generatorOptions","_generatorOptions$def","defaultModifiers","_generatorOptions$def2","defaultOptions","pending","orderedModifiers","effectCleanupFns","isDestroyed","setOptions","cleanupModifierEffects","merged","map","visited","result","modifier","dep","depModifier","orderModifiers","current","existing","m","_ref3$options","cleanupFn","forceUpdate","_state$elements","_state$orderedModifie","_state$orderedModifie2","Promise","resolve","then","undefined","destroy","onFirstUpdate","createPopper","computeStyles","applyStyles","flip","REGEXP_KEYDOWN","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","display","popperConfig","autoClose","Dropdown","_popper","_menu","_getMenuElement","_inNavbar","_detectNavbar","getParentFromElement","Popper","_getPopperConfig","isDisplayStatic","focus","_completeHide","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","_selectMenuItem","items","dropdownInterface","toggles","context","composedPath","isMenuTarget","clickEvent","isActive","stopPropagation","getToggleButton","clearMenus","click","dataApiKeydownHandler","ScrollBarHelper","getWidth","documentWidth","innerWidth","_disableOverFlow","_setElementAttributes","calculatedValue","_saveInitialAttribute","styleProp","scrollbarWidth","_applyManipulationCallback","_resetElementAttributes","actualValue","removeProperty","callBack","isOverflowing","clickCallback","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","className","appendChild","Modal","_dialog","_backdrop","_initializeBackDrop","_isShown","_ignoreBackdropClick","_scrollBar","_isAnimated","_adjustDialog","_setEscapeEvent","_setResizeEvent","_showBackdrop","_showElement","_hideModal","htmlElement","handleUpdate","modalBody","_enforceFocus","_triggerBackdropTransition","_resetAdjustments","currentTarget","isModalOverflowing","isBodyOverflowing","paddingLeft","paddingRight","showEvent","Offcanvas","visibility","_enforceFocusOnElement","blur","allReadyOpen","el","uriAttrs","SAFE_URL_PATTERN","DATA_URL_PATTERN","allowedAttribute","attr","allowedAttributeList","attrName","nodeValue","regExp","attrRegex","sanitizeHtml","unsafeHtml","allowList","sanitizeFn","createdDocument","DOMParser","parseFromString","allowlistKeys","elName","attributeList","allowedAttributes","innerHTML","BSCLS_PREFIX_REGEX","DISALLOWED_ATTRIBUTES","animation","template","title","delay","customClass","sanitize","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","*","area","br","col","code","div","em","hr","h1","h2","h3","h4","h5","h6","img","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","HIDE","HIDDEN","SHOW","SHOWN","INSERTED","CLICK","FOCUSIN","FOCUSOUT","MOUSEENTER","MOUSELEAVE","Tooltip","_isEnabled","_timeout","_hoverState","_activeTrigger","tip","_setListeners","enable","disable","toggleEnabled","_initializeOnDelegatedTarget","_isWithActiveTrigger","_enter","_leave","getTipElement","_hideModalHandler","isWithContent","shadowRoot","isInTheDom","tipId","setContent","attachment","_getAttachment","_addAttachmentClass","prevHoverState","_cleanTipClass","getTitle","setElementContent","content","textContent","updateAttachment","dataKey","_getDelegateConfig","_handlePopperPlacementChange","eventIn","eventOut","_fixTitle","originalTitleType","dataAttributes","dataAttr","tabClass","token","tClass","Popover","_getContent","method","ScrollSpy","_scrollElement","_offsets","_targets","_activeTarget","_scrollHeight","_process","refresh","autoMethod","offsetMethod","offsetBase","_getScrollTop","_getScrollHeight","targetSelector","targetBCR","item","_getOffsetHeight","innerHeight","maxScroll","_activate","_clear","queries","link","join","listGroup","navItem","spy","Tab","listElement","itemSelector","hideEvent","complete","active","_transitionComplete","dropdownChild","dropdownElement","dropdown","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","_onInteraction","isInteracting"],"mappings":"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","sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NODE_TEXT = 3\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children)\n .filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n\n let ancestor = element.parentNode\n\n while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== NODE_TEXT) {\n if (ancestor.matches(selector)) {\n parents.push(ancestor)\n }\n\n ancestor = ancestor.parentNode\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n }\n}\n\nexport default SelectorEngine\n","import SelectorEngine from '../dom/selector-engine'\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1000000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n// Shoutout AngusCroll (https://goo.gl/pxwQGp)\nconst toType = obj => {\n if (obj === null || obj === undefined) {\n return `${obj}`\n }\n\n return {}.toString.call(obj).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * --------------------------------------------------------------------------\n * Public Util Api\n * --------------------------------------------------------------------------\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttr = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttr || (!hrefAttr.includes('#') && !hrefAttr.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) {\n hrefAttr = `#${hrefAttr.split('#')[1]}`\n }\n\n selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null\n }\n\n return selector\n}\n\nconst getSelectorFromElement = element => {\n const selector = getSelector(element)\n\n if (selector) {\n return document.querySelector(selector) ? selector : null\n }\n\n return null\n}\n\nconst getElementFromSelector = element => {\n const selector = getSelector(element)\n\n return selector ? document.querySelector(selector) : null\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = obj => {\n if (!obj || typeof obj !== 'object') {\n return false\n }\n\n if (typeof obj.jquery !== 'undefined') {\n obj = obj[0]\n }\n\n return typeof obj.nodeType !== 'undefined'\n}\n\nconst getElement = obj => {\n if (isElement(obj)) { // it's a jQuery object or a node element\n return obj.jquery ? obj[0] : obj\n }\n\n if (typeof obj === 'string' && obj.length > 0) {\n return SelectorEngine.findOne(obj)\n }\n\n return null\n}\n\nconst typeCheckConfig = (componentName, config, configTypes) => {\n Object.keys(configTypes).forEach(property => {\n const expectedTypes = configTypes[property]\n const value = config[property]\n const valueType = value && isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${componentName.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n })\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n return getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\nconst reflow = element => element.offsetHeight\n\nconst getjQuery = () => {\n const { jQuery } = window\n\n if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n DOMContentLoadedCallbacks.forEach(callback => callback())\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = callback => {\n if (typeof callback === 'function') {\n callback()\n }\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element depending on the direction and if cycle is allowed\n if (index === -1) {\n return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0]\n }\n\n const listLength = list.length\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n getElement,\n getUID,\n getSelectorFromElement,\n getElementFromSelector,\n getTransitionDurationFromElement,\n triggerTransitionEnd,\n isElement,\n typeCheckConfig,\n isVisible,\n isDisabled,\n findShadowRoot,\n noop,\n getNextActiveElement,\n reflow,\n getjQuery,\n onDOMContentLoaded,\n isRTL,\n defineJQueryPlugin,\n execute,\n executeAfterTransition\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\nconst customEventsRegex = /^(mouseenter|mouseleave)/i\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * ------------------------------------------------------------------------\n * Private methods\n * ------------------------------------------------------------------------\n */\n\nfunction getUidEvent(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getEvent(element) {\n const uid = getUidEvent(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n event.delegateTarget = element\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (let i = domElements.length; i--;) {\n if (domElements[i] === target) {\n event.delegateTarget = target\n\n if (handler.oneOff) {\n // eslint-disable-next-line unicorn/consistent-destructuring\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n\n // To please ESLint\n return null\n }\n}\n\nfunction findHandler(events, handler, delegationSelector = null) {\n const uidEventList = Object.keys(events)\n\n for (let i = 0, len = uidEventList.length; i < len; i++) {\n const event = events[uidEventList[i]]\n\n if (event.originalHandler === handler && event.delegationSelector === delegationSelector) {\n return event\n }\n }\n\n return null\n}\n\nfunction normalizeParams(originalTypeEvent, handler, delegationFn) {\n const delegation = typeof handler === 'string'\n const originalHandler = delegation ? delegationFn : handler\n\n let typeEvent = getTypeEvent(originalTypeEvent)\n const isNative = nativeEvents.has(typeEvent)\n\n if (!isNative) {\n typeEvent = originalTypeEvent\n }\n\n return [delegation, originalHandler, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n if (!handler) {\n handler = delegationFn\n delegationFn = null\n }\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (customEventsRegex.test(originalTypeEvent)) {\n const wrapFn = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n if (delegationFn) {\n delegationFn = wrapFn(delegationFn)\n } else {\n handler = wrapFn(handler)\n }\n }\n\n const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)\n const events = getEvent(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null)\n\n if (previousFn) {\n previousFn.oneOff = previousFn.oneOff && oneOff\n\n return\n }\n\n const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = delegation ?\n bootstrapDelegationHandler(element, handler, delegationFn) :\n bootstrapHandler(element, handler)\n\n fn.delegationSelector = delegation ? handler : null\n fn.originalHandler = originalHandler\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, delegation)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n Object.keys(storeElementEvent).forEach(handlerKey => {\n if (handlerKey.includes(namespace)) {\n const event = storeElementEvent[handlerKey]\n\n removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)\n }\n })\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFn) {\n addHandler(element, event, handler, delegationFn, false)\n },\n\n one(element, event, handler, delegationFn) {\n addHandler(element, event, handler, delegationFn, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFn) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getEvent(element)\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof originalHandler !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!events || !events[typeEvent]) {\n return\n }\n\n removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null)\n return\n }\n\n if (isNamespace) {\n Object.keys(events).forEach(elementEvent => {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n })\n }\n\n const storeElementEvent = events[typeEvent] || {}\n Object.keys(storeElementEvent).forEach(keyHandlers => {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n const event = storeElementEvent[keyHandlers]\n\n removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)\n }\n })\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n const isNative = nativeEvents.has(typeEvent)\n\n let jQueryEvent\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n let evt = null\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n if (isNative) {\n evt = document.createEvent('HTMLEvents')\n evt.initEvent(typeEvent, bubbles, true)\n } else {\n evt = new CustomEvent(event, {\n bubbles,\n cancelable: true\n })\n }\n\n // merge custom information in our event\n if (typeof args !== 'undefined') {\n Object.keys(args).forEach(key => {\n Object.defineProperty(evt, key, {\n get() {\n return args[key]\n }\n })\n })\n }\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data'\nimport {\n executeAfterTransition,\n getElement\n} from './util/index'\nimport EventHandler from './dom/event-handler'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst VERSION = '5.0.2'\n\nclass BaseComponent {\n constructor(element) {\n element = getElement(element)\n\n if (!element) {\n return\n }\n\n this._element = element\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n Object.getOwnPropertyNames(this).forEach(propertyName => {\n this[propertyName] = null\n })\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n /** Static */\n\n static getInstance(element) {\n return Data.get(element, this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElementFromSelector\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst SELECTOR_DISMISS = '[data-bs-dismiss=\"alert\"]'\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_ALERT = 'alert'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Alert extends BaseComponent {\n // Getters\n\n static get NAME() {\n return NAME\n }\n\n // Public\n\n close(element) {\n const rootElement = element ? this._getRootElement(element) : this._element\n const customEvent = this._triggerCloseEvent(rootElement)\n\n if (customEvent === null || customEvent.defaultPrevented) {\n return\n }\n\n this._removeElement(rootElement)\n }\n\n // Private\n\n _getRootElement(element) {\n return getElementFromSelector(element) || element.closest(`.${CLASS_NAME_ALERT}`)\n }\n\n _triggerCloseEvent(element) {\n return EventHandler.trigger(element, EVENT_CLOSE)\n }\n\n _removeElement(element) {\n element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(element), element, isAnimated)\n }\n\n _destroyElement(element) {\n element.remove()\n\n EventHandler.trigger(element, EVENT_CLOSED)\n }\n\n // Static\n\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (config === 'close') {\n data[config](this)\n }\n })\n }\n\n static handleDismiss(alertInstance) {\n return function (event) {\n if (event) {\n event.preventDefault()\n }\n\n alertInstance.close(this)\n }\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, Alert.handleDismiss(new Alert()))\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n * add .Alert to jQuery only if jQuery is present\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\n\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Button extends BaseComponent {\n // Getters\n\n static get NAME() {\n return NAME\n }\n\n // Public\n\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n * add .Button to jQuery only if jQuery is present\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(val) {\n if (val === 'true') {\n return true\n }\n\n if (val === 'false') {\n return false\n }\n\n if (val === Number(val).toString()) {\n return Number(val)\n }\n\n if (val === '' || val === 'null') {\n return null\n }\n\n return val\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n\n Object.keys(element.dataset)\n .filter(key => key.startsWith('bs'))\n .forEach(key => {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n })\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n },\n\n offset(element) {\n const rect = element.getBoundingClientRect()\n\n return {\n top: rect.top + document.body.scrollTop,\n left: rect.left + document.body.scrollLeft\n }\n },\n\n position(element) {\n return {\n top: element.offsetTop,\n left: element.offsetLeft\n }\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElementFromSelector,\n isRTL,\n isVisible,\n getNextActiveElement,\n reflow,\n triggerTransitionEnd,\n typeCheckConfig\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n slide: false,\n pause: 'hover',\n wrap: true,\n touch: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)',\n keyboard: 'boolean',\n slide: '(boolean|string)',\n pause: '(string|boolean)',\n wrap: 'boolean',\n touch: 'boolean'\n}\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ACTIVE_ITEM = '.active.carousel-item'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_INDICATOR = '[data-bs-target]'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element)\n\n this._items = null\n this._interval = null\n this._activeElement = null\n this._isPaused = false\n this._isSliding = false\n this.touchTimeout = null\n this.touchStartX = 0\n this.touchDeltaX = 0\n\n this._config = this._getConfig(config)\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n this._pointerEvent = Boolean(window.PointerEvent)\n\n this._addEventListeners()\n }\n\n // Getters\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause(event) {\n if (!event) {\n this._isPaused = true\n }\n\n if (SelectorEngine.findOne(SELECTOR_NEXT_PREV, this._element)) {\n triggerTransitionEnd(this._element)\n this.cycle(true)\n }\n\n clearInterval(this._interval)\n this._interval = null\n }\n\n cycle(event) {\n if (!event) {\n this._isPaused = false\n }\n\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n\n if (this._config && this._config.interval && !this._isPaused) {\n this._updateInterval()\n\n this._interval = setInterval(\n (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),\n this._config.interval\n )\n }\n }\n\n to(index) {\n this._activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n const activeIndex = this._getItemIndex(this._activeElement)\n\n if (index > this._items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n if (activeIndex === index) {\n this.pause()\n this.cycle()\n return\n }\n\n const order = index > activeIndex ?\n ORDER_NEXT :\n ORDER_PREV\n\n this._slide(order, this._items[index])\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...Manipulator.getDataAttributes(this._element),\n ...(typeof config === 'object' ? config : {})\n }\n typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _handleSwipe() {\n const absDeltax = Math.abs(this.touchDeltaX)\n\n if (absDeltax <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltax / this.touchDeltaX\n\n this.touchDeltaX = 0\n\n if (!direction) {\n return\n }\n\n this._slide(direction > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT)\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, event => this.pause(event))\n EventHandler.on(this._element, EVENT_MOUSELEAVE, event => this.cycle(event))\n }\n\n if (this._config.touch && this._touchSupported) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n const start = event => {\n if (this._pointerEvent && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)) {\n this.touchStartX = event.clientX\n } else if (!this._pointerEvent) {\n this.touchStartX = event.touches[0].clientX\n }\n }\n\n const move = event => {\n // ensure swiping with one touch and not pinching\n this.touchDeltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this.touchStartX\n }\n\n const end = event => {\n if (this._pointerEvent && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)) {\n this.touchDeltaX = event.clientX - this.touchStartX\n }\n\n this._handleSwipe()\n if (this._config.pause === 'hover') {\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n }\n\n SelectorEngine.find(SELECTOR_ITEM_IMG, this._element).forEach(itemImg => {\n EventHandler.on(itemImg, EVENT_DRAG_START, e => e.preventDefault())\n })\n\n if (this._pointerEvent) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => end(event))\n }\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(direction)\n }\n }\n\n _getItemIndex(element) {\n this._items = element && element.parentNode ?\n SelectorEngine.find(SELECTOR_ITEM, element.parentNode) :\n []\n\n return this._items.indexOf(element)\n }\n\n _getItemByOrder(order, activeElement) {\n const isNext = order === ORDER_NEXT\n return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap)\n }\n\n _triggerSlideEvent(relatedTarget, eventDirectionName) {\n const targetIndex = this._getItemIndex(relatedTarget)\n const fromIndex = this._getItemIndex(SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element))\n\n return EventHandler.trigger(this._element, EVENT_SLIDE, {\n relatedTarget,\n direction: eventDirectionName,\n from: fromIndex,\n to: targetIndex\n })\n }\n\n _setActiveIndicatorElement(element) {\n if (this._indicatorsElement) {\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const indicators = SelectorEngine.find(SELECTOR_INDICATOR, this._indicatorsElement)\n\n for (let i = 0; i < indicators.length; i++) {\n if (Number.parseInt(indicators[i].getAttribute('data-bs-slide-to'), 10) === this._getItemIndex(element)) {\n indicators[i].classList.add(CLASS_NAME_ACTIVE)\n indicators[i].setAttribute('aria-current', 'true')\n break\n }\n }\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n if (elementInterval) {\n this._config.defaultInterval = this._config.defaultInterval || this._config.interval\n this._config.interval = elementInterval\n } else {\n this._config.interval = this._config.defaultInterval || this._config.interval\n }\n }\n\n _slide(directionOrOrder, element) {\n const order = this._directionToOrder(directionOrOrder)\n const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n const activeElementIndex = this._getItemIndex(activeElement)\n const nextElement = element || this._getItemByOrder(order, activeElement)\n\n const nextElementIndex = this._getItemIndex(nextElement)\n const isCycling = Boolean(this._interval)\n\n const isNext = order === ORDER_NEXT\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n const eventDirectionName = this._orderToDirection(order)\n\n if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) {\n this._isSliding = false\n return\n }\n\n if (this._isSliding) {\n return\n }\n\n const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n return\n }\n\n this._isSliding = true\n\n if (isCycling) {\n this.pause()\n }\n\n this._setActiveIndicatorElement(nextElement)\n this._activeElement = nextElement\n\n const triggerSlidEvent = () => {\n EventHandler.trigger(this._element, EVENT_SLID, {\n relatedTarget: nextElement,\n direction: eventDirectionName,\n from: activeElementIndex,\n to: nextElementIndex\n })\n }\n\n if (this._element.classList.contains(CLASS_NAME_SLIDE)) {\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n setTimeout(triggerSlidEvent, 0)\n }\n\n this._queueCallback(completeCallBack, activeElement, true)\n } else {\n activeElement.classList.remove(CLASS_NAME_ACTIVE)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n this._isSliding = false\n triggerSlidEvent()\n }\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _directionToOrder(direction) {\n if (![DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction)) {\n return direction\n }\n\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (![ORDER_NEXT, ORDER_PREV].includes(order)) {\n return order\n }\n\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n\n static carouselInterface(element, config) {\n const data = Carousel.getOrCreateInstance(element, config)\n\n let { _config } = data\n if (typeof config === 'object') {\n _config = {\n ..._config,\n ...config\n }\n }\n\n const action = typeof config === 'string' ? config : _config.slide\n\n if (typeof config === 'number') {\n data.to(config)\n } else if (typeof action === 'string') {\n if (typeof data[action] === 'undefined') {\n throw new TypeError(`No method named \"${action}\"`)\n }\n\n data[action]()\n } else if (_config.interval && _config.ride) {\n data.pause()\n data.cycle()\n }\n }\n\n static jQueryInterface(config) {\n return this.each(function () {\n Carousel.carouselInterface(this, config)\n })\n }\n\n static dataApiClickHandler(event) {\n const target = getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n const config = {\n ...Manipulator.getDataAttributes(target),\n ...Manipulator.getDataAttributes(this)\n }\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n config.interval = false\n }\n\n Carousel.carouselInterface(target, config)\n\n if (slideIndex) {\n Carousel.getInstance(target).to(slideIndex)\n }\n\n event.preventDefault()\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel.dataApiClickHandler)\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (let i = 0, len = carousels.length; i < len; i++) {\n Carousel.carouselInterface(carousels[i], Carousel.getInstance(carousels[i]))\n }\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n * add .Carousel to jQuery only if jQuery is present\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElement,\n getSelectorFromElement,\n getElementFromSelector,\n reflow,\n typeCheckConfig\n} from './util/index'\nimport Data from './dom/data'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst Default = {\n toggle: true,\n parent: ''\n}\n\nconst DefaultType = {\n toggle: 'boolean',\n parent: '(string|element)'\n}\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.show, .collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element)\n\n this._isTransitioning = false\n this._config = this._getConfig(config)\n this._triggerArray = SelectorEngine.find(\n `${SELECTOR_DATA_TOGGLE}[href=\"#${this._element.id}\"],` +\n `${SELECTOR_DATA_TOGGLE}[data-bs-target=\"#${this._element.id}\"]`\n )\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (let i = 0, len = toggleList.length; i < len; i++) {\n const elem = toggleList[i]\n const selector = getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElem => foundElem === this._element)\n\n if (selector !== null && filterElement.length) {\n this._selector = selector\n this._triggerArray.push(elem)\n }\n }\n\n this._parent = this._config.parent ? this._getParent() : null\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._element, this._triggerArray)\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n\n toggle() {\n if (this._element.classList.contains(CLASS_NAME_SHOW)) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._element.classList.contains(CLASS_NAME_SHOW)) {\n return\n }\n\n let actives\n let activesData\n\n if (this._parent) {\n actives = SelectorEngine.find(SELECTOR_ACTIVES, this._parent)\n .filter(elem => {\n if (typeof this._config.parent === 'string') {\n return elem.getAttribute('data-bs-parent') === this._config.parent\n }\n\n return elem.classList.contains(CLASS_NAME_COLLAPSE)\n })\n\n if (actives.length === 0) {\n actives = null\n }\n }\n\n const container = SelectorEngine.findOne(this._selector)\n if (actives) {\n const tempActiveData = actives.find(elem => container !== elem)\n activesData = tempActiveData ? Collapse.getInstance(tempActiveData) : null\n\n if (activesData && activesData._isTransitioning) {\n return\n }\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n if (actives) {\n actives.forEach(elemActive => {\n if (container !== elemActive) {\n Collapse.collapseInterface(elemActive, 'hide')\n }\n\n if (!activesData) {\n Data.set(elemActive, DATA_KEY, null)\n }\n })\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n if (this._triggerArray.length) {\n this._triggerArray.forEach(element => {\n element.classList.remove(CLASS_NAME_COLLAPSED)\n element.setAttribute('aria-expanded', true)\n })\n }\n\n this.setTransitioning(true)\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n this.setTransitioning(false)\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._element.classList.contains(CLASS_NAME_SHOW)) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n const triggerArrayLength = this._triggerArray.length\n if (triggerArrayLength > 0) {\n for (let i = 0; i < triggerArrayLength; i++) {\n const trigger = this._triggerArray[i]\n const elem = getElementFromSelector(trigger)\n\n if (elem && !elem.classList.contains(CLASS_NAME_SHOW)) {\n trigger.classList.add(CLASS_NAME_COLLAPSED)\n trigger.setAttribute('aria-expanded', false)\n }\n }\n }\n\n this.setTransitioning(true)\n\n const complete = () => {\n this.setTransitioning(false)\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n setTransitioning(isTransitioning) {\n this._isTransitioning = isTransitioning\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n config.toggle = Boolean(config.toggle) // Coerce string values\n typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(WIDTH) ? WIDTH : HEIGHT\n }\n\n _getParent() {\n let { parent } = this._config\n\n parent = getElement(parent)\n\n const selector = `${SELECTOR_DATA_TOGGLE}[data-bs-parent=\"${parent}\"]`\n\n SelectorEngine.find(selector, parent)\n .forEach(element => {\n const selected = getElementFromSelector(element)\n\n this._addAriaAndCollapsedClass(\n selected,\n [element]\n )\n })\n\n return parent\n }\n\n _addAriaAndCollapsedClass(element, triggerArray) {\n if (!element || !triggerArray.length) {\n return\n }\n\n const isOpen = element.classList.contains(CLASS_NAME_SHOW)\n\n triggerArray.forEach(elem => {\n if (isOpen) {\n elem.classList.remove(CLASS_NAME_COLLAPSED)\n } else {\n elem.classList.add(CLASS_NAME_COLLAPSED)\n }\n\n elem.setAttribute('aria-expanded', isOpen)\n })\n }\n\n // Static\n\n static collapseInterface(element, config) {\n let data = Collapse.getInstance(element)\n const _config = {\n ...Default,\n ...Manipulator.getDataAttributes(element),\n ...(typeof config === 'object' && config ? config : {})\n }\n\n if (!data && _config.toggle && typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n if (!data) {\n data = new Collapse(element, _config)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n }\n\n static jQueryInterface(config) {\n return this.each(function () {\n Collapse.collapseInterface(this, config)\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n const triggerData = Manipulator.getDataAttributes(this)\n const selector = getSelectorFromElement(this)\n const selectorElements = SelectorEngine.find(selector)\n\n selectorElements.forEach(element => {\n const data = Collapse.getInstance(element)\n let config\n if (data) {\n // update parent attribute\n if (data._parent === null && typeof triggerData.parent === 'string') {\n data._config.parent = triggerData.parent\n data._parent = data._getParent()\n }\n\n config = 'toggle'\n } else {\n config = triggerData\n }\n\n Collapse.collapseInterface(element, config)\n })\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n * add .Collapse to jQuery only if jQuery is present\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export default function getBoundingClientRect(element) {\n var rect = element.getBoundingClientRect();\n return {\n width: rect.width,\n height: rect.height,\n top: rect.top,\n right: rect.right,\n bottom: rect.bottom,\n left: rect.left,\n x: rect.left,\n y: rect.top\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') !== -1;\n var isIE = navigator.userAgent.indexOf('Trident') !== -1;\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport default function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport within from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n if (!isHTMLElement(arrowElement)) {\n console.error(['Popper: \"arrow\" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(['Popper: \"arrow\" modifier\\'s `element` must be a child of the popper', 'element.'].join(' '));\n }\n\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","import { top, left, right, bottom } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref) {\n var x = _ref.x,\n y = _ref.y;\n var win = window;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(round(x * dpr) / dpr) || 0,\n y: round(round(y * dpr) / dpr) || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets;\n\n var _ref3 = roundOffsets === true ? roundOffsetsByDPR(offsets) : typeof roundOffsets === 'function' ? roundOffsets(offsets) : offsets,\n _ref3$x = _ref3.x,\n x = _ref3$x === void 0 ? 0 : _ref3$x,\n _ref3$y = _ref3.y,\n y = _ref3$y === void 0 ? 0 : _ref3$y;\n\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top) {\n sideY = bottom; // $FlowFixMe[prop-missing]\n\n y -= offsetParent[heightProp] - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left) {\n sideX = right; // $FlowFixMe[prop-missing]\n\n x -= offsetParent[widthProp] - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) < 2 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref4) {\n var state = _ref4.state,\n options = _ref4.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n\n if (process.env.NODE_ENV !== \"production\") {\n var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';\n\n if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {\n return transitionProperty.indexOf(property) >= 0;\n })) {\n console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: \"transform\", \"top\", \"right\", \"bottom\", \"left\".', '\\n\\n', 'Disable the \"computeStyles\" modifier\\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\\n\\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));\n }\n }\n\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element) {\n var rect = getBoundingClientRect(element);\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element)) : isHTMLElement(clippingParent) ? getInnerBoundingClientRect(clippingParent) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nexport default function getViewportRect(element) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0; // NB: This isn't supported on iOS <= 12. If the keyboard is open, the popper\n // can be obscured underneath it.\n // Also, `html.clientHeight` adds the bottom bar height in Safari iOS, even\n // if it isn't open, so if this isn't available, the popper will be detected\n // to overflow the bottom of the screen too early.\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height; // Uses Layout Viewport (like Chrome; Safari does not currently)\n // In Chrome, it returns a value very close to 0 (+/-) but contains rounding\n // errors due to floating point numbers, so we need to check precision.\n // Safari returns a number <= 0, usually < -1 when pinch-zoomed\n // Feature detection fails in mobile emulation mode in Chrome.\n // Math.abs(win.innerWidth / visualViewport.scale - visualViewport.width) <\n // 0.001\n // Fallback here: \"Not Safari\" userAgent\n\n if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var referenceElement = state.elements.reference;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary);\n var referenceClientRect = getBoundingClientRect(referenceElement);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, \"auto\" cannot be used to allow \"bottom-start\".', 'Use \"auto-start\" instead.'].join(' '));\n }\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\";\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport within from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { max as mathMax, min as mathMin } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis || checkAltAxis) {\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = popperOffsets[mainAxis] + overflow[mainSide];\n var max = popperOffsets[mainAxis] - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - tetherOffsetValue : minLen - arrowLen - arrowPaddingMin - tetherOffsetValue;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + tetherOffsetValue : maxLen + arrowLen + arrowPaddingMax + tetherOffsetValue;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = state.modifiersData.offset ? state.modifiersData.offset[state.placement][mainAxis] : 0;\n var tetherMin = popperOffsets[mainAxis] + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = popperOffsets[mainAxis] + maxOffset - offsetModifierValue;\n\n if (checkMainAxis) {\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var _preventedOffset = within(tether ? mathMin(_min, tetherMin) : _min, _offset, tether ? mathMax(_max, tetherMax) : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\"; // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement);\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport getComputedStyle from \"./dom-utils/getComputedStyle.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport validateModifiers from \"./utils/validateModifiers.js\";\nimport uniqueBy from \"./utils/uniqueBy.js\";\nimport getBasePlacement from \"./utils/getBasePlacement.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nimport { auto } from \"./enums.js\";\nvar INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';\nvar INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(options) {\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n }); // Validate the provided modifiers so that the consumer will get warned\n // if one of the modifiers is invalid for any reason\n\n if (process.env.NODE_ENV !== \"production\") {\n var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {\n var name = _ref.name;\n return name;\n });\n validateModifiers(modifiers);\n\n if (getBasePlacement(state.options.placement) === auto) {\n var flipModifier = state.orderedModifiers.find(function (_ref2) {\n var name = _ref2.name;\n return name === 'flip';\n });\n\n if (!flipModifier) {\n console.error(['Popper: \"auto\" placements require the \"flip\" modifier be', 'present and enabled to work.'].join(' '));\n }\n }\n\n var _getComputedStyle = getComputedStyle(popper),\n marginTop = _getComputedStyle.marginTop,\n marginRight = _getComputedStyle.marginRight,\n marginBottom = _getComputedStyle.marginBottom,\n marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can\n // cause bugs with positioning, so we'll warn the consumer\n\n\n if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {\n return parseFloat(margin);\n })) {\n console.warn(['Popper: CSS \"margin\" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));\n }\n }\n\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(INVALID_ELEMENT_ERROR);\n }\n\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n var __debug_loops__ = 0;\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (process.env.NODE_ENV !== \"production\") {\n __debug_loops__ += 1;\n\n if (__debug_loops__ > 100) {\n console.error(INFINITE_LOOP_ERROR);\n break;\n }\n }\n\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(INVALID_ELEMENT_ERROR);\n }\n\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref3) {\n var name = _ref3.name,\n _ref3$options = _ref3.options,\n options = _ref3$options === void 0 ? {} : _ref3$options,\n effect = _ref3.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\n\nimport {\n defineJQueryPlugin,\n getElement,\n getElementFromSelector,\n isDisabled,\n isElement,\n isVisible,\n isRTL,\n noop,\n getNextActiveElement,\n typeCheckConfig\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst SPACE_KEY = 'Space'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`)\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_NAVBAR = 'navbar'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]'\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\n\nconst Default = {\n offset: [0, 2],\n boundary: 'clippingParents',\n reference: 'toggle',\n display: 'dynamic',\n popperConfig: null,\n autoClose: true\n}\n\nconst DefaultType = {\n offset: '(array|string|function)',\n boundary: '(string|element)',\n reference: '(string|element|object)',\n display: 'string',\n popperConfig: '(null|object|function)',\n autoClose: '(boolean|string)'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element)\n\n this._popper = null\n this._config = this._getConfig(config)\n this._menu = this._getMenuElement()\n this._inNavbar = this._detectNavbar()\n\n this._addEventListeners()\n }\n\n // Getters\n\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n\n toggle() {\n if (isDisabled(this._element)) {\n return\n }\n\n const isActive = this._element.classList.contains(CLASS_NAME_SHOW)\n\n if (isActive) {\n this.hide()\n return\n }\n\n this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._menu.classList.contains(CLASS_NAME_SHOW)) {\n return\n }\n\n const parent = Dropdown.getParentFromElement(this._element)\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n // Totally disable Popper for Dropdowns in Navbar\n if (this._inNavbar) {\n Manipulator.setDataAttribute(this._menu, 'popper', 'none')\n } else {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false)\n\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n\n if (isDisplayStatic) {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static')\n }\n }\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement &&\n !parent.closest(SELECTOR_NAVBAR_NAV)) {\n [].concat(...document.body.children)\n .forEach(elem => EventHandler.on(elem, 'mouseover', noop))\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.toggle(CLASS_NAME_SHOW)\n this._element.classList.toggle(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._menu.classList.contains(CLASS_NAME_SHOW)) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_CLICK, event => {\n event.preventDefault()\n this.toggle()\n })\n }\n\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n [].concat(...document.body.children)\n .forEach(elem => EventHandler.off(elem, 'mouseover', noop))\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = {\n ...this.constructor.Default,\n ...Manipulator.getDataAttributes(this._element),\n ...config\n }\n\n typeCheckConfig(NAME, config, this.constructor.DefaultType)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _getMenuElement() {\n return SelectorEngine.next(this._element, SELECTOR_MENU)[0]\n }\n\n _getPlacement() {\n const parentDropdown = this._element.parentNode\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(val => Number.parseInt(val, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display\n if (this._config.display === 'static') {\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible)\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n\n static dropdownInterface(element, config) {\n const data = Dropdown.getOrCreateInstance(element, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n }\n\n static jQueryInterface(config) {\n return this.each(function () {\n Dropdown.dropdownInterface(this, config)\n })\n }\n\n static clearMenus(event) {\n if (event && (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY))) {\n return\n }\n\n const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (let i = 0, len = toggles.length; i < len; i++) {\n const context = Dropdown.getInstance(toggles[i])\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n if (!context._element.classList.contains(CLASS_NAME_SHOW)) {\n continue\n }\n\n const relatedTarget = {\n relatedTarget: context._element\n }\n\n if (event) {\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static getParentFromElement(element) {\n return getElementFromSelector(element) || element.parentNode\n }\n\n static dataApiKeydownHandler(event) {\n // If not input/textarea:\n // - And not a key in REGEXP_KEYDOWN => not a dropdown command\n // If input/textarea:\n // - If space key => not a dropdown command\n // - If key is other than escape\n // - If key is not up or down => not a dropdown command\n // - If trigger inside the menu => not a dropdown command\n if (/input|textarea/i.test(event.target.tagName) ?\n event.key === SPACE_KEY || (event.key !== ESCAPE_KEY &&\n ((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) ||\n event.target.closest(SELECTOR_MENU))) :\n !REGEXP_KEYDOWN.test(event.key)) {\n return\n }\n\n const isActive = this.classList.contains(CLASS_NAME_SHOW)\n\n if (!isActive && event.key === ESCAPE_KEY) {\n return\n }\n\n event.preventDefault()\n event.stopPropagation()\n\n if (isDisabled(this)) {\n return\n }\n\n const getToggleButton = () => this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]\n\n if (event.key === ESCAPE_KEY) {\n getToggleButton().focus()\n Dropdown.clearMenus()\n return\n }\n\n if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) {\n if (!isActive) {\n getToggleButton().click()\n }\n\n Dropdown.getInstance(getToggleButton())._selectMenuItem(event)\n return\n }\n\n if (!isActive || event.key === SPACE_KEY) {\n Dropdown.clearMenus()\n }\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.dropdownInterface(this)\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n * add .Dropdown to jQuery only if jQuery is present\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine'\nimport Manipulator from '../dom/manipulator'\nimport { isElement } from './index'\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width)\n }\n\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProp, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProp)\n const calculatedValue = window.getComputedStyle(element)[styleProp]\n element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px`\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, 'paddingRight')\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight')\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight')\n }\n\n _saveInitialAttribute(element, styleProp) {\n const actualValue = element.style[styleProp]\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProp, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProp) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProp)\n if (typeof value === 'undefined') {\n element.style.removeProperty(styleProp)\n } else {\n Manipulator.removeDataAttribute(element, styleProp)\n element.style[styleProp] = value\n }\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n } else {\n SelectorEngine.find(selector, this._element).forEach(callBack)\n }\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport { execute, executeAfterTransition, getElement, reflow, typeCheckConfig } from './index'\n\nconst Default = {\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n isAnimated: false,\n rootElement: 'body', // give the choice to place backdrop under different elements\n clickCallback: null\n}\n\nconst DefaultType = {\n isVisible: 'boolean',\n isAnimated: 'boolean',\n rootElement: '(element|string)',\n clickCallback: '(function|null)'\n}\nconst NAME = 'backdrop'\nconst CLASS_NAME_BACKDROP = 'modal-backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nclass Backdrop {\n constructor(config) {\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n if (this._config.isAnimated) {\n reflow(this._getElement())\n }\n\n this._getElement().classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n // Private\n\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = CLASS_NAME_BACKDROP\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _getConfig(config) {\n config = {\n ...Default,\n ...(typeof config === 'object' ? config : {})\n }\n\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n this._config.rootElement.appendChild(this._getElement())\n\n EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElementFromSelector,\n isRTL,\n isVisible,\n reflow,\n typeCheckConfig\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport ScrollBarHelper from './util/scrollbar'\nimport BaseComponent from './base-component'\nimport Backdrop from './util/backdrop'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n focus: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n focus: 'boolean'\n}\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\nconst SELECTOR_DATA_DISMISS = '[data-bs-dismiss=\"modal\"]'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element)\n\n this._config = this._getConfig(config)\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._isShown = false\n this._ignoreBackdropClick = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n }\n\n // Getters\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n\n if (this._isAnimated()) {\n this._isTransitioning = true\n }\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._setEscapeEvent()\n this._setResizeEvent()\n\n EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, event => this.hide(event))\n\n EventHandler.on(this._dialog, EVENT_MOUSEDOWN_DISMISS, () => {\n EventHandler.one(this._element, EVENT_MOUSEUP_DISMISS, event => {\n if (event.target === this._element) {\n this._ignoreBackdropClick = true\n }\n })\n })\n\n this._showBackdrop(() => this._showElement(relatedTarget))\n }\n\n hide(event) {\n if (event && ['A', 'AREA'].includes(event.target.tagName)) {\n event.preventDefault()\n }\n\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n const isAnimated = this._isAnimated()\n\n if (isAnimated) {\n this._isTransitioning = true\n }\n\n this._setEscapeEvent()\n this._setResizeEvent()\n\n EventHandler.off(document, EVENT_FOCUSIN)\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n EventHandler.off(this._element, EVENT_CLICK_DISMISS)\n EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS)\n\n this._queueCallback(() => this._hideModal(), this._element, isAnimated)\n }\n\n dispose() {\n [window, this._dialog]\n .forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY))\n\n this._backdrop.dispose()\n super.dispose()\n\n /**\n * `document` has 2 events `EVENT_FOCUSIN` and `EVENT_CLICK_DATA_API`\n * Do not move `document` in `htmlElements` array\n * It will remove `EVENT_CLICK_DATA_API` event that should remain\n */\n EventHandler.off(document, EVENT_FOCUSIN)\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value\n isAnimated: this._isAnimated()\n })\n }\n\n _getConfig(config) {\n config = {\n ...Default,\n ...Manipulator.getDataAttributes(this._element),\n ...(typeof config === 'object' ? config : {})\n }\n typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _showElement(relatedTarget) {\n const isAnimated = this._isAnimated()\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n\n if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {\n // Don't move modal's DOM position\n document.body.appendChild(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n if (isAnimated) {\n reflow(this._element)\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n if (this._config.focus) {\n this._enforceFocus()\n }\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._element.focus()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, isAnimated)\n }\n\n _enforceFocus() {\n EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => {\n if (document !== event.target &&\n this._element !== event.target &&\n !this._element.contains(event.target)) {\n this._element.focus()\n }\n })\n }\n\n _setEscapeEvent() {\n if (this._isShown) {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (this._config.keyboard && event.key === ESCAPE_KEY) {\n event.preventDefault()\n this.hide()\n } else if (!this._config.keyboard && event.key === ESCAPE_KEY) {\n this._triggerBackdropTransition()\n }\n })\n } else {\n EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS)\n }\n }\n\n _setResizeEvent() {\n if (this._isShown) {\n EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog())\n } else {\n EventHandler.off(window, EVENT_RESIZE)\n }\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _showBackdrop(callback) {\n EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => {\n if (this._ignoreBackdropClick) {\n this._ignoreBackdropClick = false\n return\n }\n\n if (event.target !== event.currentTarget) {\n return\n }\n\n if (this._config.backdrop === true) {\n this.hide()\n } else if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n }\n })\n\n this._backdrop.show(callback)\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const { classList, scrollHeight, style } = this._element\n const isModalOverflowing = scrollHeight > document.documentElement.clientHeight\n\n // return if the following background transition hasn't yet completed\n if ((!isModalOverflowing && style.overflowY === 'hidden') || classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n style.overflowY = 'hidden'\n }\n\n classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n classList.remove(CLASS_NAME_STATIC)\n if (!isModalOverflowing) {\n this._queueCallback(() => {\n style.overflowY = ''\n }, this._dialog)\n }\n }, this._dialog)\n\n this._element.focus()\n }\n\n // ----------------------------------------------------------------------\n // the following methods are used to handle overflowing modals\n // ----------------------------------------------------------------------\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if ((!isBodyOverflowing && isModalOverflowing && !isRTL()) || (isBodyOverflowing && !isModalOverflowing && isRTL())) {\n this._element.style.paddingLeft = `${scrollbarWidth}px`\n }\n\n if ((isBodyOverflowing && !isModalOverflowing && !isRTL()) || (!isBodyOverflowing && isModalOverflowing && isRTL())) {\n this._element.style.paddingRight = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n * add .Modal to jQuery only if jQuery is present\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElementFromSelector,\n isDisabled,\n isVisible,\n typeCheckConfig\n} from './util/index'\nimport ScrollBarHelper from './util/scrollbar'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\nimport SelectorEngine from './dom/selector-engine'\nimport Manipulator from './dom/manipulator'\nimport Backdrop from './util/backdrop'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: 'boolean',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\nconst CLASS_NAME_SHOW = 'show'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_DISMISS = '[data-bs-dismiss=\"offcanvas\"]'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element)\n\n this._config = this._getConfig(config)\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._addEventListeners()\n }\n\n // Getters\n\n static get NAME() {\n return NAME\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._element.style.visibility = 'visible'\n\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n this._enforceFocusOnElement(this._element)\n }\n\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const completeCallBack = () => {\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n EventHandler.off(document, EVENT_FOCUSIN)\n this._element.blur()\n this._isShown = false\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._element.style.visibility = 'hidden'\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n super.dispose()\n EventHandler.off(document, EVENT_FOCUSIN)\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...Manipulator.getDataAttributes(this._element),\n ...(typeof config === 'object' ? config : {})\n }\n typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: this._config.backdrop,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: () => this.hide()\n })\n }\n\n _enforceFocusOnElement(element) {\n EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => {\n if (document !== event.target &&\n element !== event.target &&\n !element.contains(event.target)) {\n element.focus()\n }\n })\n element.focus()\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide())\n\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (this._config.keyboard && event.key === ESCAPE_KEY) {\n this.hide()\n }\n })\n }\n\n // Static\n\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (allReadyOpen && allReadyOpen !== target) {\n Offcanvas.getInstance(allReadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () =>\n SelectorEngine.find(OPEN_SELECTOR).forEach(el => Offcanvas.getOrCreateInstance(el).show())\n)\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst uriAttrs = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\n/**\n * A pattern that recognizes a commonly useful subset of URLs that are safe.\n *\n * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i\n\n/**\n * A pattern that matches safe data URLs. Only matches image, video and audio types.\n *\n * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i\n\nconst allowedAttribute = (attr, allowedAttributeList) => {\n const attrName = attr.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attrName)) {\n if (uriAttrs.has(attrName)) {\n return Boolean(SAFE_URL_PATTERN.test(attr.nodeValue) || DATA_URL_PATTERN.test(attr.nodeValue))\n }\n\n return true\n }\n\n const regExp = allowedAttributeList.filter(attrRegex => attrRegex instanceof RegExp)\n\n // Check if a regular expression validates the attribute.\n for (let i = 0, len = regExp.length; i < len; i++) {\n if (regExp[i].test(attrName)) {\n return true\n }\n }\n\n return false\n}\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFn) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFn && typeof sanitizeFn === 'function') {\n return sanitizeFn(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const allowlistKeys = Object.keys(allowList)\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (let i = 0, len = elements.length; i < len; i++) {\n const el = elements[i]\n const elName = el.nodeName.toLowerCase()\n\n if (!allowlistKeys.includes(elName)) {\n el.remove()\n\n continue\n }\n\n const attributeList = [].concat(...el.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elName] || [])\n\n attributeList.forEach(attr => {\n if (!allowedAttribute(attr, allowedAttributes)) {\n el.removeAttribute(attr.nodeName)\n }\n })\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\n\nimport {\n defineJQueryPlugin,\n findShadowRoot,\n getElement,\n getUID,\n isElement,\n isRTL,\n noop,\n typeCheckConfig\n} from './util/index'\nimport {\n DefaultAllowlist,\n sanitizeHtml\n} from './util/sanitizer'\nimport Data from './dom/data'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'tooltip'\nconst DATA_KEY = 'bs.tooltip'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst CLASS_PREFIX = 'bs-tooltip'\nconst BSCLS_PREFIX_REGEX = new RegExp(`(^|\\\\s)${CLASS_PREFIX}\\\\S+`, 'g')\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst DefaultType = {\n animation: 'boolean',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string',\n delay: '(number|object)',\n html: 'boolean',\n selector: '(string|boolean)',\n placement: '(string|function)',\n offset: '(array|string|function)',\n container: '(string|element|boolean)',\n fallbackPlacements: 'array',\n boundary: '(string|element)',\n customClass: '(string|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n allowList: 'object',\n popperConfig: '(null|object|function)'\n}\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n animation: true,\n template: '
' +\n '
' +\n '
' +\n '
',\n trigger: 'hover focus',\n title: '',\n delay: 0,\n html: false,\n selector: false,\n placement: 'top',\n offset: [0, 0],\n container: false,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n boundary: 'clippingParents',\n customClass: '',\n sanitize: true,\n sanitizeFn: null,\n allowList: DefaultAllowlist,\n popperConfig: null\n}\n\nconst Event = {\n HIDE: `hide${EVENT_KEY}`,\n HIDDEN: `hidden${EVENT_KEY}`,\n SHOW: `show${EVENT_KEY}`,\n SHOWN: `shown${EVENT_KEY}`,\n INSERTED: `inserted${EVENT_KEY}`,\n CLICK: `click${EVENT_KEY}`,\n FOCUSIN: `focusin${EVENT_KEY}`,\n FOCUSOUT: `focusout${EVENT_KEY}`,\n MOUSEENTER: `mouseenter${EVENT_KEY}`,\n MOUSELEAVE: `mouseleave${EVENT_KEY}`\n}\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst HOVER_STATE_SHOW = 'show'\nconst HOVER_STATE_OUT = 'out'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element)\n\n // private\n this._isEnabled = true\n this._timeout = 0\n this._hoverState = ''\n this._activeTrigger = {}\n this._popper = null\n\n // Protected\n this._config = this._getConfig(config)\n this.tip = null\n\n this._setListeners()\n }\n\n // Getters\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n static get Event() {\n return Event\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n // Public\n\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle(event) {\n if (!this._isEnabled) {\n return\n }\n\n if (event) {\n const context = this._initializeOnDelegatedTarget(event)\n\n context._activeTrigger.click = !context._activeTrigger.click\n\n if (context._isWithActiveTrigger()) {\n context._enter(null, context)\n } else {\n context._leave(null, context)\n }\n } else {\n if (this.getTipElement().classList.contains(CLASS_NAME_SHOW)) {\n this._leave(null, this)\n return\n }\n\n this._enter(null, this)\n }\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(`.${CLASS_NAME_MODAL}`), 'hide.bs.modal', this._hideModalHandler)\n\n if (this.tip) {\n this.tip.remove()\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this.isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.Event.SHOW)\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = shadowRoot === null ?\n this._element.ownerDocument.documentElement.contains(this._element) :\n shadowRoot.contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n const tip = this.getTipElement()\n const tipId = getUID(this.constructor.NAME)\n\n tip.setAttribute('id', tipId)\n this._element.setAttribute('aria-describedby', tipId)\n\n this.setContent()\n\n if (this._config.animation) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n const placement = typeof this._config.placement === 'function' ?\n this._config.placement.call(this, tip, this._element) :\n this._config.placement\n\n const attachment = this._getAttachment(placement)\n this._addAttachmentClass(attachment)\n\n const { container } = this._config\n Data.set(tip, this.constructor.DATA_KEY, this)\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.appendChild(tip)\n EventHandler.trigger(this._element, this.constructor.Event.INSERTED)\n }\n\n if (this._popper) {\n this._popper.update()\n } else {\n this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n const customClass = typeof this._config.customClass === 'function' ? this._config.customClass() : this._config.customClass\n if (customClass) {\n tip.classList.add(...customClass.split(' '))\n }\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n [].concat(...document.body.children).forEach(element => {\n EventHandler.on(element, 'mouseover', noop)\n })\n }\n\n const complete = () => {\n const prevHoverState = this._hoverState\n\n this._hoverState = null\n EventHandler.trigger(this._element, this.constructor.Event.SHOWN)\n\n if (prevHoverState === HOVER_STATE_OUT) {\n this._leave(null, this)\n }\n }\n\n const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(complete, this.tip, isAnimated)\n }\n\n hide() {\n if (!this._popper) {\n return\n }\n\n const tip = this.getTipElement()\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (this._hoverState !== HOVER_STATE_SHOW) {\n tip.remove()\n }\n\n this._cleanTipClass()\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.Event.HIDDEN)\n\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n [].concat(...document.body.children)\n .forEach(element => EventHandler.off(element, 'mouseover', noop))\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n\n const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(complete, this.tip, isAnimated)\n this._hoverState = ''\n }\n\n update() {\n if (this._popper !== null) {\n this._popper.update()\n }\n }\n\n // Protected\n\n isWithContent() {\n return Boolean(this.getTitle())\n }\n\n getTipElement() {\n if (this.tip) {\n return this.tip\n }\n\n const element = document.createElement('div')\n element.innerHTML = this._config.template\n\n this.tip = element.children[0]\n return this.tip\n }\n\n setContent() {\n const tip = this.getTipElement()\n this.setElementContent(SelectorEngine.findOne(SELECTOR_TOOLTIP_INNER, tip), this.getTitle())\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n }\n\n setElementContent(element, content) {\n if (element === null) {\n return\n }\n\n if (isElement(content)) {\n content = getElement(content)\n\n // content is a DOM node or a jQuery\n if (this._config.html) {\n if (content.parentNode !== element) {\n element.innerHTML = ''\n element.appendChild(content)\n }\n } else {\n element.textContent = content.textContent\n }\n\n return\n }\n\n if (this._config.html) {\n if (this._config.sanitize) {\n content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn)\n }\n\n element.innerHTML = content\n } else {\n element.textContent = content\n }\n }\n\n getTitle() {\n let title = this._element.getAttribute('data-bs-original-title')\n\n if (!title) {\n title = typeof this._config.title === 'function' ?\n this._config.title.call(this._element) :\n this._config.title\n }\n\n return title\n }\n\n updateAttachment(attachment) {\n if (attachment === 'right') {\n return 'end'\n }\n\n if (attachment === 'left') {\n return 'start'\n }\n\n return attachment\n }\n\n // Private\n\n _initializeOnDelegatedTarget(event, context) {\n const dataKey = this.constructor.DATA_KEY\n context = context || Data.get(event.delegateTarget, dataKey)\n\n if (!context) {\n context = new this.constructor(event.delegateTarget, this._getDelegateConfig())\n Data.set(event.delegateTarget, dataKey, context)\n }\n\n return context\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(val => Number.parseInt(val, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'onChange',\n enabled: true,\n phase: 'afterWrite',\n fn: data => this._handlePopperPlacementChange(data)\n }\n ],\n onFirstUpdate: data => {\n if (data.options.placement !== data.placement) {\n this._handlePopperPlacementChange(data)\n }\n }\n }\n\n return {\n ...defaultBsPopperConfig,\n ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n }\n }\n\n _addAttachmentClass(attachment) {\n this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`)\n }\n\n _getAttachment(placement) {\n return AttachmentMap[placement.toUpperCase()]\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n triggers.forEach(trigger => {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.Event.CLICK, this._config.selector, event => this.toggle(event))\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.Event.MOUSEENTER :\n this.constructor.Event.FOCUSIN\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.Event.MOUSELEAVE :\n this.constructor.Event.FOCUSOUT\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => this._enter(event))\n EventHandler.on(this._element, eventOut, this._config.selector, event => this._leave(event))\n }\n })\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(`.${CLASS_NAME_MODAL}`), 'hide.bs.modal', this._hideModalHandler)\n\n if (this._config.selector) {\n this._config = {\n ...this._config,\n trigger: 'manual',\n selector: ''\n }\n } else {\n this._fixTitle()\n }\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n const originalTitleType = typeof this._element.getAttribute('data-bs-original-title')\n\n if (title || originalTitleType !== 'string') {\n this._element.setAttribute('data-bs-original-title', title || '')\n if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('title', '')\n }\n }\n\n _enter(event, context) {\n context = this._initializeOnDelegatedTarget(event, context)\n\n if (event) {\n context._activeTrigger[\n event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER\n ] = true\n }\n\n if (context.getTipElement().classList.contains(CLASS_NAME_SHOW) || context._hoverState === HOVER_STATE_SHOW) {\n context._hoverState = HOVER_STATE_SHOW\n return\n }\n\n clearTimeout(context._timeout)\n\n context._hoverState = HOVER_STATE_SHOW\n\n if (!context._config.delay || !context._config.delay.show) {\n context.show()\n return\n }\n\n context._timeout = setTimeout(() => {\n if (context._hoverState === HOVER_STATE_SHOW) {\n context.show()\n }\n }, context._config.delay.show)\n }\n\n _leave(event, context) {\n context = this._initializeOnDelegatedTarget(event, context)\n\n if (event) {\n context._activeTrigger[\n event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER\n ] = context._element.contains(event.relatedTarget)\n }\n\n if (context._isWithActiveTrigger()) {\n return\n }\n\n clearTimeout(context._timeout)\n\n context._hoverState = HOVER_STATE_OUT\n\n if (!context._config.delay || !context._config.delay.hide) {\n context.hide()\n return\n }\n\n context._timeout = setTimeout(() => {\n if (context._hoverState === HOVER_STATE_OUT) {\n context.hide()\n }\n }, context._config.delay.hide)\n }\n\n _isWithActiveTrigger() {\n for (const trigger in this._activeTrigger) {\n if (this._activeTrigger[trigger]) {\n return true\n }\n }\n\n return false\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n Object.keys(dataAttributes).forEach(dataAttr => {\n if (DISALLOWED_ATTRIBUTES.has(dataAttr)) {\n delete dataAttributes[dataAttr]\n }\n })\n\n config = {\n ...this.constructor.Default,\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n typeCheckConfig(NAME, config, this.constructor.DefaultType)\n\n if (config.sanitize) {\n config.template = sanitizeHtml(config.template, config.allowList, config.sanitizeFn)\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n if (this._config) {\n for (const key in this._config) {\n if (this.constructor.Default[key] !== this._config[key]) {\n config[key] = this._config[key]\n }\n }\n }\n\n return config\n }\n\n _cleanTipClass() {\n const tip = this.getTipElement()\n const tabClass = tip.getAttribute('class').match(BSCLS_PREFIX_REGEX)\n if (tabClass !== null && tabClass.length > 0) {\n tabClass.map(token => token.trim())\n .forEach(tClass => tip.classList.remove(tClass))\n }\n }\n\n _handlePopperPlacementChange(popperData) {\n const { state } = popperData\n\n if (!state) {\n return\n }\n\n this.tip = state.elements.popper\n this._cleanTipClass()\n this._addAttachmentClass(this._getAttachment(state.placement))\n }\n\n // Static\n\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n * add .Tooltip to jQuery only if jQuery is present\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport SelectorEngine from './dom/selector-engine'\nimport Tooltip from './tooltip'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'popover'\nconst DATA_KEY = 'bs.popover'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst CLASS_PREFIX = 'bs-popover'\nconst BSCLS_PREFIX_REGEX = new RegExp(`(^|\\\\s)${CLASS_PREFIX}\\\\S+`, 'g')\n\nconst Default = {\n ...Tooltip.Default,\n placement: 'right',\n offset: [0, 8],\n trigger: 'click',\n content: '',\n template: '
' +\n '
' +\n '

' +\n '
' +\n '
'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(string|element|function)'\n}\n\nconst Event = {\n HIDE: `hide${EVENT_KEY}`,\n HIDDEN: `hidden${EVENT_KEY}`,\n SHOW: `show${EVENT_KEY}`,\n SHOWN: `shown${EVENT_KEY}`,\n INSERTED: `inserted${EVENT_KEY}`,\n CLICK: `click${EVENT_KEY}`,\n FOCUSIN: `focusin${EVENT_KEY}`,\n FOCUSOUT: `focusout${EVENT_KEY}`,\n MOUSEENTER: `mouseenter${EVENT_KEY}`,\n MOUSELEAVE: `mouseleave${EVENT_KEY}`\n}\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Popover extends Tooltip {\n // Getters\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n static get Event() {\n return Event\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n // Overrides\n\n isWithContent() {\n return this.getTitle() || this._getContent()\n }\n\n getTipElement() {\n if (this.tip) {\n return this.tip\n }\n\n this.tip = super.getTipElement()\n\n if (!this.getTitle()) {\n SelectorEngine.findOne(SELECTOR_TITLE, this.tip).remove()\n }\n\n if (!this._getContent()) {\n SelectorEngine.findOne(SELECTOR_CONTENT, this.tip).remove()\n }\n\n return this.tip\n }\n\n setContent() {\n const tip = this.getTipElement()\n\n // we use append for html objects to maintain js events\n this.setElementContent(SelectorEngine.findOne(SELECTOR_TITLE, tip), this.getTitle())\n let content = this._getContent()\n if (typeof content === 'function') {\n content = content.call(this._element)\n }\n\n this.setElementContent(SelectorEngine.findOne(SELECTOR_CONTENT, tip), content)\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n }\n\n // Private\n\n _addAttachmentClass(attachment) {\n this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`)\n }\n\n _getContent() {\n return this._element.getAttribute('data-bs-content') || this._config.content\n }\n\n _cleanTipClass() {\n const tip = this.getTipElement()\n const tabClass = tip.getAttribute('class').match(BSCLS_PREFIX_REGEX)\n if (tabClass !== null && tabClass.length > 0) {\n tabClass.map(token => token.trim())\n .forEach(tClass => tip.classList.remove(tClass))\n }\n }\n\n // Static\n\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n * add .Popover to jQuery only if jQuery is present\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.0.2): scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getSelectorFromElement,\n getUID,\n isElement,\n typeCheckConfig\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst Default = {\n offset: 10,\n method: 'auto',\n target: ''\n}\n\nconst DefaultType = {\n offset: 'number',\n method: 'string',\n target: '(string|element)'\n}\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_SCROLL = `scroll${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst METHOD_OFFSET = 'offset'\nconst METHOD_POSITION = 'position'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element)\n this._scrollElement = this._element.tagName === 'BODY' ? window : this._element\n this._config = this._getConfig(config)\n this._selector = `${this._config.target} ${SELECTOR_NAV_LINKS}, ${this._config.target} ${SELECTOR_LIST_ITEMS}, ${this._config.target} .${CLASS_NAME_DROPDOWN_ITEM}`\n this._offsets = []\n this._targets = []\n this._activeTarget = null\n this._scrollHeight = 0\n\n EventHandler.on(this._scrollElement, EVENT_SCROLL, () => this._process())\n\n this.refresh()\n this._process()\n }\n\n // Getters\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n\n refresh() {\n const autoMethod = this._scrollElement === this._scrollElement.window ?\n METHOD_OFFSET :\n METHOD_POSITION\n\n const offsetMethod = this._config.method === 'auto' ?\n autoMethod :\n this._config.method\n\n const offsetBase = offsetMethod === METHOD_POSITION ?\n this._getScrollTop() :\n 0\n\n this._offsets = []\n this._targets = []\n this._scrollHeight = this._getScrollHeight()\n\n const targets = SelectorEngine.find(this._selector)\n\n targets.map(element => {\n const targetSelector = getSelectorFromElement(element)\n const target = targetSelector ? SelectorEngine.findOne(targetSelector) : null\n\n if (target) {\n const targetBCR = target.getBoundingClientRect()\n if (targetBCR.width || targetBCR.height) {\n return [\n Manipulator[offsetMethod](target).top + offsetBase,\n targetSelector\n ]\n }\n }\n\n return null\n })\n .filter(item => item)\n .sort((a, b) => a[0] - b[0])\n .forEach(item => {\n this._offsets.push(item[0])\n this._targets.push(item[1])\n })\n }\n\n dispose() {\n EventHandler.off(this._scrollElement, EVENT_KEY)\n super.dispose()\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...Manipulator.getDataAttributes(this._element),\n ...(typeof config === 'object' && config ? config : {})\n }\n\n if (typeof config.target !== 'string' && isElement(config.target)) {\n let { id } = config.target\n if (!id) {\n id = getUID(NAME)\n config.target.id = id\n }\n\n config.target = `#${id}`\n }\n\n typeCheckConfig(NAME, config, DefaultType)\n\n return config\n }\n\n _getScrollTop() {\n return this._scrollElement === window ?\n this._scrollElement.pageYOffset :\n this._scrollElement.scrollTop\n }\n\n _getScrollHeight() {\n return this._scrollElement.scrollHeight || Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight\n )\n }\n\n _getOffsetHeight() {\n return this._scrollElement === window ?\n window.innerHeight :\n this._scrollElement.getBoundingClientRect().height\n }\n\n _process() {\n const scrollTop = this._getScrollTop() + this._config.offset\n const scrollHeight = this._getScrollHeight()\n const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight()\n\n if (this._scrollHeight !== scrollHeight) {\n this.refresh()\n }\n\n if (scrollTop >= maxScroll) {\n const target = this._targets[this._targets.length - 1]\n\n if (this._activeTarget !== target) {\n this._activate(target)\n }\n\n return\n }\n\n if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {\n this._activeTarget = null\n this._clear()\n return\n }\n\n for (let i = this._offsets.length; i--;) {\n const isActiveTarget = this._activeTarget !== this._targets[i] &&\n scrollTop >= this._offsets[i] &&\n (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1])\n\n if (isActiveTarget) {\n this._activate(this._targets[i])\n }\n }\n }\n\n _activate(target) {\n this._activeTarget = target\n\n this._clear()\n\n const queries = this._selector.split(',')\n .map(selector => `${selector}[data-bs-target=\"${target}\"],${selector}[href=\"${target}\"]`)\n\n const link = SelectorEngine.findOne(queries.join(','))\n\n if (link.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, link.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n\n link.classList.add(CLASS_NAME_ACTIVE)\n } else {\n // Set triggered link as active\n link.classList.add(CLASS_NAME_ACTIVE)\n\n SelectorEngine.parents(link, SELECTOR_NAV_LIST_GROUP)\n .forEach(listGroup => {\n // Set triggered links parents as active\n // With both