diff --git a/assets/controllers/common/dirty_form_controller.js b/assets/controllers/common/dirty_form_controller.js index aad2e6b0..8e560a3f 100644 --- a/assets/controllers/common/dirty_form_controller.js +++ b/assets/controllers/common/dirty_form_controller.js @@ -19,8 +19,7 @@ import {Controller} from "@hotwired/stimulus"; import {visit} from "@hotwired/turbo"; -import * as bootbox from "bootbox"; -import "../../css/components/bootbox_extensions.css"; +import {ConfirmSwal} from "../../helpers/swal"; import "../../css/components/dirty_form.css"; /** @@ -207,11 +206,10 @@ export default class extends Controller { } _confirmNavigation(onConfirm) { - bootbox.confirm({ - title: this.confirmTitleValue, - message: this.confirmMessageValue, - callback: (result) => { if (result) onConfirm(); } - }); + ConfirmSwal.fire({ + titleText: this.confirmTitleValue, + text: this.confirmMessageValue, + }).then(({isConfirmed}) => { if (isConfirmed) onConfirm(); }); } _handleLinkClick(event) { diff --git a/assets/controllers/elements/collection_type_controller.js b/assets/controllers/elements/collection_type_controller.js index 647ed5e5..caeb4122 100644 --- a/assets/controllers/elements/collection_type_controller.js +++ b/assets/controllers/elements/collection_type_controller.js @@ -19,8 +19,7 @@ import {Controller} from "@hotwired/stimulus"; -import * as bootbox from "bootbox"; -import "../../css/components/bootbox_extensions.css"; +import {AlertSwal, ConfirmSwal} from "../../helpers/swal"; import accept from "attr-accept"; export default class extends Controller { @@ -62,7 +61,7 @@ export default class extends Controller { if(!prototype) { console.warn("Prototype is not set, we cannot create a new element. This is most likely due to missing permissions."); - bootbox.alert("You do not have the permissions to create a new element. (No protoype element is set)"); + AlertSwal.fire({"text": "You do not have the permissions to create a new element. (No protoype element is set)"}); return; } @@ -226,8 +225,10 @@ export default class extends Controller { } if(this.deleteMessageValue) { - bootbox.confirm(this.deleteMessageValue, (result) => { - if (result) { + ConfirmSwal.fire({ + text: this.deleteMessageValue, + }).then(({isConfirmed}) => { + if (isConfirmed) { del(); } }); diff --git a/assets/controllers/elements/datatables/datatables_controller.js b/assets/controllers/elements/datatables/datatables_controller.js index d945004b..4b84a834 100644 --- a/assets/controllers/elements/datatables/datatables_controller.js +++ b/assets/controllers/elements/datatables/datatables_controller.js @@ -38,9 +38,7 @@ import 'datatables.net-colreorder-bs5'; import 'datatables.net-responsive-bs5'; import '../../../js/lib/datatables'; -//import 'datatables.net-select-bs5'; -//Use the local version containing the fix for the select extension -import '../../../js/lib/dataTables.select.mjs'; +import 'datatables.net-select-bs5'; const EVENT_DT_LOADED = 'dt:loaded'; diff --git a/assets/controllers/elements/datatables/parts_controller.js b/assets/controllers/elements/datatables/parts_controller.js index c43fa276..cfa386cc 100644 --- a/assets/controllers/elements/datatables/parts_controller.js +++ b/assets/controllers/elements/datatables/parts_controller.js @@ -20,7 +20,7 @@ import DatatablesController from "./datatables_controller.js"; import TomSelect from "tom-select"; -import * as bootbox from "bootbox"; +import {ConfirmSwal} from "../../../helpers/swal"; /** * This is the datatables controller for parts lists @@ -146,15 +146,17 @@ export default class extends DatatablesController { bubbles: true, //This line is important, otherwise Turbo will not receive the event }); - const confirm = bootbox.confirm({ - message: message, title: title, callback: function (result) { - //If the dialog was confirmed, then submit the form. - if (result) { - that._confirmed = true; - form.dispatchEvent(that._our_event); - } else { - that._confirmed = false; - } + ConfirmSwal.fire({ + titleText: title, + text: message, + icon: "warning" + }).then(({isConfirmed}) => { + //If the dialog was confirmed, then submit the form. + if (isConfirmed) { + that._confirmed = true; + form.dispatchEvent(that._our_event); + } else { + that._confirmed = false; } }); } diff --git a/assets/controllers/elements/delete_btn_controller.js b/assets/controllers/elements/delete_btn_controller.js index 9ab15f7d..e1b37bcc 100644 --- a/assets/controllers/elements/delete_btn_controller.js +++ b/assets/controllers/elements/delete_btn_controller.js @@ -19,8 +19,7 @@ import {Controller} from "@hotwired/stimulus"; -import * as bootbox from "bootbox"; -import "../../css/components/bootbox_extensions.css"; +import {ConfirmSwal} from "../../helpers/swal"; export default class extends Controller { @@ -48,32 +47,33 @@ export default class extends Controller const submitter = event.submitter; const that = this; - const confirm = bootbox.confirm({ - message: message, title: title, callback: function (result) { - //If the dialog was confirmed, then submit the form. - if (result) { - //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form - that._confirmed = true; + ConfirmSwal.fire({ + titleText: title, + html: message, //Message contains a
tag and no user injectable HTML + }).then(({isConfirmed}) => { + //If the dialog was confirmed, then submit the form. + if (isConfirmed) { + //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form + that._confirmed = true; - //Create a submit button in the form and click it to submit the form - //Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273 - const submit_btn = document.createElement('button'); - submit_btn.type = 'submit'; - submit_btn.style.display = 'none'; + //Create a submit button in the form and click it to submit the form + //Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273 + const submit_btn = document.createElement('button'); + submit_btn.type = 'submit'; + submit_btn.style.display = 'none'; - //If the clicked button has a value, set it on the submit button - if (submitter.value) { - submit_btn.value = submitter.value; - } - if (submitter.name) { - submit_btn.name = submitter.name; - } - form.appendChild(submit_btn); - submit_btn.click(); - } else { - that._confirmed = false; + //If the clicked button has a value, set it on the submit button + if (submitter.value) { + submit_btn.value = submitter.value; } + if (submitter.name) { + submit_btn.name = submitter.name; + } + form.appendChild(submit_btn); + submit_btn.click(); + } else { + that._confirmed = false; } }); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/link_confirm_controller.js b/assets/controllers/elements/link_confirm_controller.js index 3d59b492..be226517 100644 --- a/assets/controllers/elements/link_confirm_controller.js +++ b/assets/controllers/elements/link_confirm_controller.js @@ -19,8 +19,7 @@ import {Controller} from "@hotwired/stimulus"; -import * as bootbox from "bootbox"; -import "../../css/components/bootbox_extensions.css"; +import {ConfirmSwal} from "../../helpers/swal"; export default class extends Controller { @@ -53,20 +52,19 @@ export default class extends Controller const that = this; - bootbox.confirm({ - title: this.titleValue, - message: this.messageValue, - callback: (result) => { - if (result) { - //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form - that._confirmed = true; + ConfirmSwal.fire({ + titleText: this.titleValue, + text: this.messageValue, + }).then(({isConfirmed}) => { + if (isConfirmed) { + //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form + that._confirmed = true; - //Click the link - that.element.click(); - } else { - that._confirmed = false; - } + //Click the link + that.element.click(); + } else { + that._confirmed = false; } }); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/password_strength_estimate_controller.js b/assets/controllers/elements/password_strength_estimate_controller.js index d9cfbc87..9ad2da1c 100644 --- a/assets/controllers/elements/password_strength_estimate_controller.js +++ b/assets/controllers/elements/password_strength_estimate_controller.js @@ -19,12 +19,14 @@ import {Controller} from "@hotwired/stimulus"; -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core'; +import { ZxcvbnFactory } from '@zxcvbn-ts/core'; import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common'; import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en'; import * as zxcvbnDePackage from '@zxcvbn-ts/language-de'; import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr'; import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja'; +import * as zxcvbnItPackage from '@zxcvbn-ts/language-it'; +import * as zxcvbnPlPackage from '@zxcvbn-ts/language-pl'; import {trans} from '../../translator.js'; /* stimulusFetch: 'lazy' */ @@ -34,6 +36,8 @@ export default class extends Controller { static targets = ["badge", "warning"] + _zxcvbnFactory; + _getTranslations() { //Get the current locale const locale = document.documentElement.lang; @@ -43,6 +47,10 @@ export default class extends Controller { return zxcvbnFrPackage.translations; } else if (locale.includes('ja')) { return zxcvbnJaPackage.translations; + } else if (locale.includes('it')) { + return zxcvbnItPackage.translations; + } else if (locale.includes('pl')) { + return zxcvbnPlPackage.translations; } //Fallback to english @@ -56,34 +64,39 @@ export default class extends Controller { //Configure zxcvbn const options = { graphs: zxcvbnCommonPackage.adjacencyGraphs, + useLevenshtein: true, dictionary: { ...zxcvbnCommonPackage.dictionary, // We could use the english dictionary here too, but it is very big. So we just use the common words - //...zxcvbnEnPackage.dictionary, + ...zxcvbnEnPackage.dictionary, + ...zxcvbnDePackage.dictionary, + + "partdb": ['part-db', 'partdb', 'part_db', 'part-db-symfony', 'partdb-symfony', 'part_db_symfony'], }, translations: this._getTranslations(), }; - zxcvbnOptions.setOptions(options); + + this._zxcvbnFactory = new ZxcvbnFactory(options); //Add event listener to the password input field this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this)); } - _onPasswordInput() { + async _onPasswordInput() { //Retrieve the password const password = this._passwordInput.value; //Estimate the password strength - const result = zxcvbn(password); + const result = await this._zxcvbnFactory.checkAsync(password); //Update the badge this.badgeTarget.parentElement.classList.remove("d-none"); - this._setBadgeToLevel(result.score); + this._setBadgeToLevel(result.score, result.crackTimes.onlineNoThrottlingXPerSecond.display); this.warningTarget.innerHTML = result.feedback.warning; } - _setBadgeToLevel(level) { + _setBadgeToLevel(level, time = null) { let text, classes; switch (level) { @@ -118,5 +131,11 @@ export default class extends Controller { //Re-add the classes this.badgeTarget.classList.add("badge"); this.badgeTarget.classList.add(...classes.split(" ")); + + if (time) { + this.badgeTarget.setAttribute("title", trans("user.password_strength.crack_time", {"%time%": time})); + } else { + this.badgeTarget.removeAttribute("title"); + } } } diff --git a/assets/controllers/pages/reelCalculator_controller.js b/assets/controllers/pages/reelCalculator_controller.js index a134bf9b..e0b2c4ba 100644 --- a/assets/controllers/pages/reelCalculator_controller.js +++ b/assets/controllers/pages/reelCalculator_controller.js @@ -18,7 +18,7 @@ */ import {Controller} from "@hotwired/stimulus"; -import * as bootbox from "bootbox"; +import {AlertSwal} from "../../helpers/swal"; export default class extends Controller { @@ -35,12 +35,12 @@ export default class extends Controller { const part_distance = document.getElementById('reel_part_distance').value; if (dia_inner == "" || dia_outer == "" || tape_thickness == "") { - bootbox.alert(this.errorMissingValuesValue); + AlertSwal.fire({title: this.errorMissingValuesValue}); return; } if (dia_outer**dia_outer < dia_inner**dia_inner) { - bootbox.alert(this.errorOuterGreaterInnerValue); + AlertSwal.fire({title: this.errorOuterGreaterInnerValue}); return; } @@ -61,12 +61,12 @@ export default class extends Controller { return; } - var parts_per_meter = 1 / (part_distance / 1000); + const parts_per_meter = 1 / (part_distance / 1000); document.getElementById('result_parts_per_meter').textContent = parts_per_meter.toFixed(2) + ' 1/m'; - var parts_amount = (length/1000) * parts_per_meter; + const parts_amount = (length / 1000) * parts_per_meter; - document.getElementById('result_amount').textContent = Math.floor(parts_amount); + document.getElementById('result_amount').textContent = Math.floor(parts_amount).toString(); } -} \ No newline at end of file +} diff --git a/assets/css/components/swal.css b/assets/css/components/swal.css new file mode 100644 index 00000000..4c2302a9 --- /dev/null +++ b/assets/css/components/swal.css @@ -0,0 +1,50 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Respect the dark mode of Bootstrap 5 set via data-bs-theme="dark" on the element. This is done by overriding the CSS variables of the bootstrap-5 theme of SweetAlert2. + */ + +html[data-bs-theme="dark"] [data-swal2-theme='bootstrap-5'] { + /* POPUP */ + --swal2-background: #212529; + --swal2-color: #fff; + --swal2-border: 1px solid #495057; + + /* INPUT */ + --swal2-input-background: #2b3035; + --swal2-input-border: 1px solid #495057; + --swal2-input-focus-border: 1px solid #86b7fe; + --swal2-input-focus-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + + /* VALIDATION MESSAGE */ + --swal2-validation-message-background: #2c0b0e; + --swal2-validation-message-color: #ea868f; + + /* FOOTER */ + --swal2-footer-border-color: #495057; + --swal2-footer-background: #343a40; + --swal2-footer-color: #adb5bd; + + /* CLOSE BUTTON */ + --swal2-close-button-color: #fff; + + /* TOASTS */ + --swal2-toast-border: 1px solid #495057; +} diff --git a/assets/helpers/swal.js b/assets/helpers/swal.js new file mode 100644 index 00000000..e370a9ed --- /dev/null +++ b/assets/helpers/swal.js @@ -0,0 +1,44 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import Swal from 'sweetalert2'; +import 'sweetalert2/themes/bootstrap-5.css'; +import '../css/components/swal.css' +import { trans } from '../translator'; + +const BaseSwal = Swal.mixin({ + position: "top", + theme: "bootstrap-5", + confirmButtonText: trans('dialog.btn.ok'), + cancelButtonText: trans('dialog.btn.cancel'), + denyButtonText: trans('dialog.btn.deny'), +}); + +const ConfirmSwal = BaseSwal.mixin({ + showCancelButton: true, + showCloseButton: true, + icon: "warning", +}); + +const AlertSwal = BaseSwal.mixin({ + showCloseButton: true, + icon: "info", +}); + +export { ConfirmSwal, AlertSwal, BaseSwal, BaseSwal as default,}; diff --git a/assets/js/app.js b/assets/js/app.js index 4dd39581..355fe919 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -30,21 +30,21 @@ import '../css/app/images.css'; // start the Stimulus application import '../stimulus_bootstrap'; -// Need jQuery? Install it with "yarn add jquery", then uncomment to require it. -const $ = require('jquery'); +import $ from 'jquery'; //Only include javascript import '@fortawesome/fontawesome-free/css/all.css' -require('bootstrap'); +import 'bootstrap'; import "./error_handler"; import "./tab_remember"; import "./register_events"; import "./tristate_checkboxes"; -//Define jquery globally -global.$ = global.jQuery = require("jquery"); +// Expose jQuery globally so legacy plugins and Bootstrap's jQuery integration +// can find it on window at runtime. +global.$ = global.jQuery = $; //Use the local WASM file for the ZXing library import { diff --git a/assets/js/error_handler.js b/assets/js/error_handler.js index 7f047af9..67695fb9 100644 --- a/assets/js/error_handler.js +++ b/assets/js/error_handler.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -import * as bootbox from "bootbox"; +import Swal from "../helpers/swal"; /** * If this class is imported the user is shown an error dialog if he calls an page via Turbo and an error is responded. @@ -40,21 +40,6 @@ class ErrorHandlerHelper { _showAlert(statusText, statusCode, location, responseHTML) { const httpStatusToText = { - '200': 'OK', - '201': 'Created', - '202': 'Accepted', - '203': 'Non-Authoritative Information', - '204': 'No Content', - '205': 'Reset Content', - '206': 'Partial Content', - '300': 'Multiple Choices', - '301': 'Moved Permanently', - '302': 'Found', - '303': 'See Other', - '304': 'Not Modified', - '305': 'Use Proxy', - '306': 'Unused', - '307': 'Temporary Redirect', '400': 'Bad Request', '401': 'Unauthorized', '402': 'Payment Required', @@ -83,49 +68,67 @@ class ErrorHandlerHelper { '505': 'HTTP Version Not Supported', }; - //If the statusText is empty, we use the status code as text - if (!statusText) { - statusText = httpStatusToText[statusCode]; - } - - //Create error text - const title = statusText + ' (Status ' + statusCode + ')'; - - let trimString = function (string, length) { - return string.length > length ? - string.substring(0, length) + '...' : - string; + const userFriendlyMessages = { + '400': 'The request was invalid or malformed.', + '401': 'You need to log in to access this resource.', + '403': 'You don\'t have permission to access this resource.', + '404': 'The requested page or resource could not be found.', + '408': 'The request timed out. Please check your connection and try again.', + '409': 'There was a conflict with the current state of the resource.', + '429': 'Too many requests sent. Please wait a moment and try again.', + '500': 'An internal server error occurred. This is not your fault.', + '502': 'The server received an invalid response from an upstream service.', + '503': 'The service is temporarily unavailable. Please try again later.', + '504': 'The server did not respond in time. Please try again later.', }; - const short_location = trimString(location, 50); + if (!statusText) { + statusText = httpStatusToText[String(statusCode)] ?? 'Unknown Error'; + } - const alert = bootbox.alert( - { - size: 'large', - message: function() { - let url = location; - let msg = `Error calling ${short_location}.
`; - msg += 'Try to reload the page or contact the administrator if this error persists.'; + const title = `${statusText} (HTTP ${statusCode})`; + const friendlyMsg = userFriendlyMessages[String(statusCode)] + ?? 'An unexpected error occurred. Please try again or contact the administrator.'; - msg += '

' + 'View details' + ""; - msg += "
"; + const short_location = location.length > 80 + ? location.substring(0, 80) + '…' + : location; - return msg; - }, - title: title, - callback: function () { - //Remove blur - $('#content').removeClass('loading-content'); - } + const msg = ` +

${friendlyMsg}

+

If this error keeps happening, please contact your administrator.

+ +
+ +
`; - }); + const footer = `Error while loading: ${short_location}`; - alert.init(function (){ - var dstFrame = document.getElementById('error-iframe'); - //@ts-ignore - var dstDoc = dstFrame.contentDocument || dstFrame.contentWindow.document; - dstDoc.write(responseHTML) - dstDoc.close(); + Swal.fire({ + icon: 'error', + title: title, + html: msg, + footer: footer, + width: '90%', + confirmButtonText: 'Reload page', + showCancelButton: true, + cancelButtonText: 'Close', + showCloseButton: true, + reverseButtons: true, + didOpen: () => { + const dstFrame = document.getElementById('error-iframe'); + //@ts-ignore + const dstDoc = dstFrame.contentDocument || dstFrame.contentWindow.document; + dstDoc.write(responseHTML); + dstDoc.close(); + }, + }).then((result) => { + document.getElementById('content').classList.remove('loading-content'); + if (result.isConfirmed) { + window.location.reload(); + } }); } @@ -171,4 +174,4 @@ class ErrorHandlerHelper { } } -export default new ErrorHandlerHelper(); \ No newline at end of file +export default new ErrorHandlerHelper(); diff --git a/assets/js/lib/dataTables.select.mjs b/assets/js/lib/dataTables.select.mjs deleted file mode 100644 index bba97692..00000000 --- a/assets/js/lib/dataTables.select.mjs +++ /dev/null @@ -1,1538 +0,0 @@ -/********************* - * This is the fixed version of the select extension for DataTables with the fix for the issue with the select extension - * (https://github.com/DataTables/Select/issues/51) - * We use this instead of the yarn version until the PR (https://github.com/DataTables/Select/pull/52) is merged and released - * /*******************/ - - -/*! Select for DataTables 2.0.0 - * © SpryMedia Ltd - datatables.net/license/mit - */ - -import jQuery from 'jquery'; -import DataTable from 'datatables.net'; - -// Allow reassignment of the $ variable -let $ = jQuery; - - -// Version information for debugger -DataTable.select = {}; - -DataTable.select.version = '2.0.0'; - -DataTable.select.init = function (dt) { - var ctx = dt.settings()[0]; - - if (!DataTable.versionCheck('2')) { - throw 'Warning: Select requires DataTables 2 or newer'; - } - - if (ctx._select) { - return; - } - - var savedSelected = dt.state.loaded(); - - var selectAndSave = function (e, settings, data) { - if (data === null || data.select === undefined) { - return; - } - - // Clear any currently selected rows, before restoring state - // None will be selected on first initialisation - if (dt.rows({ selected: true }).any()) { - dt.rows().deselect(); - } - if (data.select.rows !== undefined) { - dt.rows(data.select.rows).select(); - } - - if (dt.columns({ selected: true }).any()) { - dt.columns().deselect(); - } - if (data.select.columns !== undefined) { - dt.columns(data.select.columns).select(); - } - - if (dt.cells({ selected: true }).any()) { - dt.cells().deselect(); - } - if (data.select.cells !== undefined) { - for (var i = 0; i < data.select.cells.length; i++) { - dt.cell(data.select.cells[i].row, data.select.cells[i].column).select(); - } - } - - dt.state.save(); - }; - - dt.on('stateSaveParams', function (e, settings, data) { - data.select = {}; - data.select.rows = dt.rows({ selected: true }).ids(true).toArray(); - data.select.columns = dt.columns({ selected: true })[0]; - data.select.cells = dt.cells({ selected: true })[0].map(function (coords) { - return { row: dt.row(coords.row).id(true), column: coords.column }; - }); - }) - .on('stateLoadParams', selectAndSave) - .one('init', function () { - selectAndSave(undefined, undefined, savedSelected); - }); - - var init = ctx.oInit.select; - var defaults = DataTable.defaults.select; - var opts = init === undefined ? defaults : init; - - // Set defaults - var items = 'row'; - var style = 'api'; - var blurable = false; - var toggleable = true; - var info = true; - var selector = 'td, th'; - var className = 'selected'; - var headerCheckbox = true; - var setStyle = false; - - ctx._select = { - infoEls: [] - }; - - // Initialisation customisations - if (opts === true) { - style = 'os'; - setStyle = true; - } - else if (typeof opts === 'string') { - style = opts; - setStyle = true; - } - else if ($.isPlainObject(opts)) { - if (opts.blurable !== undefined) { - blurable = opts.blurable; - } - - if (opts.toggleable !== undefined) { - toggleable = opts.toggleable; - } - - if (opts.info !== undefined) { - info = opts.info; - } - - if (opts.items !== undefined) { - items = opts.items; - } - - if (opts.style !== undefined) { - style = opts.style; - setStyle = true; - } - else { - style = 'os'; - setStyle = true; - } - - if (opts.selector !== undefined) { - selector = opts.selector; - } - - if (opts.className !== undefined) { - className = opts.className; - } - - if (opts.headerCheckbox !== undefined) { - headerCheckbox = opts.headerCheckbox; - } - } - - dt.select.selector(selector); - dt.select.items(items); - dt.select.style(style); - dt.select.blurable(blurable); - dt.select.toggleable(toggleable); - dt.select.info(info); - ctx._select.className = className; - - // If the init options haven't enabled select, but there is a selectable - // class name, then enable - if (!setStyle && $(dt.table().node()).hasClass('selectable')) { - dt.select.style('os'); - } - - // Insert a checkbox into the header if needed - might need to wait - // for init complete, or it might already be done - if (headerCheckbox) { - initCheckboxHeader(dt); - - dt.on('init', function () { - initCheckboxHeader(dt); - }); - } -}; - -/* - -Select is a collection of API methods, event handlers, event emitters and -buttons (for the `Buttons` extension) for DataTables. It provides the following -features, with an overview of how they are implemented: - -## Selection of rows, columns and cells. Whether an item is selected or not is - stored in: - -* rows: a `_select_selected` property which contains a boolean value of the - DataTables' `aoData` object for each row -* columns: a `_select_selected` property which contains a boolean value of the - DataTables' `aoColumns` object for each column -* cells: a `_selected_cells` property which contains an array of boolean values - of the `aoData` object for each row. The array is the same length as the - columns array, with each element of it representing a cell. - -This method of using boolean flags allows Select to operate when nodes have not -been created for rows / cells (DataTables' defer rendering feature). - -## API methods - -A range of API methods are available for triggering selection and de-selection -of rows. Methods are also available to configure the selection events that can -be triggered by an end user (such as which items are to be selected). To a large -extent, these of API methods *is* Select. It is basically a collection of helper -functions that can be used to select items in a DataTable. - -Configuration of select is held in the object `_select` which is attached to the -DataTables settings object on initialisation. Select being available on a table -is not optional when Select is loaded, but its default is for selection only to -be available via the API - so the end user wouldn't be able to select rows -without additional configuration. - -The `_select` object contains the following properties: - -``` -{ - items:string - Can be `rows`, `columns` or `cells`. Defines what item - will be selected if the user is allowed to activate row - selection using the mouse. - style:string - Can be `none`, `single`, `multi` or `os`. Defines the - interaction style when selecting items - blurable:boolean - If row selection can be cleared by clicking outside of - the table - toggleable:boolean - If row selection can be cancelled by repeated clicking - on the row - info:boolean - If the selection summary should be shown in the table - information elements - infoEls:element[] - List of HTML elements with info elements for a table -} -``` - -In addition to the API methods, Select also extends the DataTables selector -options for rows, columns and cells adding a `selected` option to the selector -options object, allowing the developer to select only selected items or -unselected items. - -## Mouse selection of items - -Clicking on items can be used to select items. This is done by a simple event -handler that will select the items using the API methods. - - */ - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Local functions - */ - -/** - * Add one or more cells to the selection when shift clicking in OS selection - * style cell selection. - * - * Cell range is more complicated than row and column as we want to select - * in the visible grid rather than by index in sequence. For example, if you - * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1 - * should also be selected (and not 1-3, 1-4. etc) - * - * @param {DataTable.Api} dt DataTable - * @param {object} idx Cell index to select to - * @param {object} last Cell index to select from - * @private - */ -function cellRange(dt, idx, last) { - var indexes; - var columnIndexes; - var rowIndexes; - var selectColumns = function (start, end) { - if (start > end) { - var tmp = end; - end = start; - start = tmp; - } - - var record = false; - return dt - .columns(':visible') - .indexes() - .filter(function (i) { - if (i === start) { - record = true; - } - - if (i === end) { - // not else if, as start might === end - record = false; - return true; - } - - return record; - }); - }; - - var selectRows = function (start, end) { - var indexes = dt.rows({ search: 'applied' }).indexes(); - - // Which comes first - might need to swap - if (indexes.indexOf(start) > indexes.indexOf(end)) { - var tmp = end; - end = start; - start = tmp; - } - - var record = false; - return indexes.filter(function (i) { - if (i === start) { - record = true; - } - - if (i === end) { - record = false; - return true; - } - - return record; - }); - }; - - if (!dt.cells({ selected: true }).any() && !last) { - // select from the top left cell to this one - columnIndexes = selectColumns(0, idx.column); - rowIndexes = selectRows(0, idx.row); - } - else { - // Get column indexes between old and new - columnIndexes = selectColumns(last.column, idx.column); - rowIndexes = selectRows(last.row, idx.row); - } - - indexes = dt.cells(rowIndexes, columnIndexes).flatten(); - - if (!dt.cells(idx, { selected: true }).any()) { - // Select range - dt.cells(indexes).select(); - } - else { - // Deselect range - dt.cells(indexes).deselect(); - } -} - -/** - * Disable mouse selection by removing the selectors - * - * @param {DataTable.Api} dt DataTable to remove events from - * @private - */ -function disableMouseSelection(dt) { - var ctx = dt.settings()[0]; - var selector = ctx._select.selector; - - $(dt.table().container()) - .off('mousedown.dtSelect', selector) - .off('mouseup.dtSelect', selector) - .off('click.dtSelect', selector); - - $('body').off('click.dtSelect' + _safeId(dt.table().node())); -} - -/** - * Attach mouse listeners to the table to allow mouse selection of items - * - * @param {DataTable.Api} dt DataTable to remove events from - * @private - */ -function enableMouseSelection(dt) { - var container = $(dt.table().container()); - var ctx = dt.settings()[0]; - var selector = ctx._select.selector; - var matchSelection; - - container - .on('mousedown.dtSelect', selector, function (e) { - // Disallow text selection for shift clicking on the table so multi - // element selection doesn't look terrible! - if (e.shiftKey || e.metaKey || e.ctrlKey) { - container - .css('-moz-user-select', 'none') - .one('selectstart.dtSelect', selector, function () { - return false; - }); - } - - if (window.getSelection) { - matchSelection = window.getSelection(); - } - }) - .on('mouseup.dtSelect', selector, function () { - // Allow text selection to occur again, Mozilla style (tested in FF - // 35.0.1 - still required) - container.css('-moz-user-select', ''); - }) - .on('click.dtSelect', selector, function (e) { - var items = dt.select.items(); - var idx; - - // If text was selected (click and drag), then we shouldn't change - // the row's selected state - if (matchSelection) { - var selection = window.getSelection(); - - // If the element that contains the selection is not in the table, we can ignore it - // This can happen if the developer selects text from the click event - if ( - !selection.anchorNode || - $(selection.anchorNode).closest('table')[0] === dt.table().node() - ) { - if (selection !== matchSelection) { - return; - } - } - } - - var ctx = dt.settings()[0]; - var container = dt.table().container(); - - // Ignore clicks inside a sub-table - if ($(e.target).closest('div.dt-container')[0] != container) { - return; - } - - var cell = dt.cell($(e.target).closest('td, th')); - - // Check the cell actually belongs to the host DataTable (so child - // rows, etc, are ignored) - if (!cell.any()) { - return; - } - - var event = $.Event('user-select.dt'); - eventTrigger(dt, event, [items, cell, e]); - - if (event.isDefaultPrevented()) { - return; - } - - var cellIndex = cell.index(); - if (items === 'row') { - idx = cellIndex.row; - typeSelect(e, dt, ctx, 'row', idx); - } - else if (items === 'column') { - idx = cell.index().column; - typeSelect(e, dt, ctx, 'column', idx); - } - else if (items === 'cell') { - idx = cell.index(); - typeSelect(e, dt, ctx, 'cell', idx); - } - - ctx._select_lastCell = cellIndex; - }); - - // Blurable - $('body').on('click.dtSelect' + _safeId(dt.table().node()), function (e) { - if (ctx._select.blurable) { - // If the click was inside the DataTables container, don't blur - if ($(e.target).parents().filter(dt.table().container()).length) { - return; - } - - // Ignore elements which have been removed from the DOM (i.e. paging - // buttons) - if ($(e.target).parents('html').length === 0) { - return; - } - - // Don't blur in Editor form - if ($(e.target).parents('div.DTE').length) { - return; - } - - var event = $.Event('select-blur.dt'); - eventTrigger(dt, event, [e.target, e]); - - if (event.isDefaultPrevented()) { - return; - } - - clear(ctx, true); - } - }); -} - -/** - * Trigger an event on a DataTable - * - * @param {DataTable.Api} api DataTable to trigger events on - * @param {boolean} selected true if selected, false if deselected - * @param {string} type Item type acting on - * @param {boolean} any Require that there are values before - * triggering - * @private - */ -function eventTrigger(api, type, args, any) { - if (any && !api.flatten().length) { - return; - } - - if (typeof type === 'string') { - type = type + '.dt'; - } - - args.unshift(api); - - $(api.table().node()).trigger(type, args); -} - -/** - * Update the information element of the DataTable showing information about the - * items selected. This is done by adding tags to the existing text - * - * @param {DataTable.Api} api DataTable to update - * @private - */ -function info(api, node) { - if (api.select.style() === 'api' || api.select.info() === false) { - return; - } - - var rows = api.rows({ selected: true }).flatten().length; - var columns = api.columns({ selected: true }).flatten().length; - var cells = api.cells({ selected: true }).flatten().length; - - var add = function (el, name, num) { - el.append( - $('').append( - api.i18n( - 'select.' + name + 's', - { _: '%d ' + name + 's selected', 0: '', 1: '1 ' + name + ' selected' }, - num - ) - ) - ); - }; - - var el = $(node); - var output = $(''); - - add(output, 'row', rows); - add(output, 'column', columns); - add(output, 'cell', cells); - - var existing = el.children('span.select-info'); - - if (existing.length) { - existing.remove(); - } - - if (output.text() !== '') { - el.append(output); - } -} - -/** - * Add a checkbox to the header for checkbox columns, allowing all rows to - * be selected, deselected or just to show the state. - * - * @param {*} dt API - */ -function initCheckboxHeader( dt ) { - // Find any checkbox column(s) - dt.columns('.dt-select').every(function () { - var header = this.header(); - - if (! $('input', header).length) { - // If no checkbox yet, insert one - var input = $('') - .attr({ - class: 'dt-select-checkbox', - type: 'checkbox', - 'aria-label': dt.i18n('select.aria.headerCheckbox') || 'Select all rows' - }) - .appendTo(header) - .on('change', function () { - if (this.checked) { - dt.rows({search: 'applied'}).select(); - } - else { - dt.rows({selected: true}).deselect(); - } - }) - .on('click', function (e) { - e.stopPropagation(); - }); - - // Update the header checkbox's state when the selection in the - // table changes - dt.on('draw select deselect', function (e, pass, type) { - if (type === 'row' || ! type) { - var count = dt.rows({selected: true}).count(); - var search = dt.rows({search: 'applied', selected: true}).count(); - var available = dt.rows({search: 'applied'}).count(); - - if (search && search <= count && search === available) { - input - .prop('checked', true) - .prop('indeterminate', false); - } - else if (search === 0 && count === 0) { - input - .prop('checked', false) - .prop('indeterminate', false); - } - else { - input - .prop('checked', false) - .prop('indeterminate', true); - } - } - }); - } - }); -} - -/** - * Initialisation of a new table. Attach event handlers and callbacks to allow - * Select to operate correctly. - * - * This will occur _after_ the initial DataTables initialisation, although - * before Ajax data is rendered, if there is ajax data - * - * @param {DataTable.settings} ctx Settings object to operate on - * @private - */ -function init(ctx) { - var api = new DataTable.Api(ctx); - ctx._select_init = true; - - // Row callback so that classes can be added to rows and cells if the item - // was selected before the element was created. This will happen with the - // `deferRender` option enabled. - // - // This method of attaching to `aoRowCreatedCallback` is a hack until - // DataTables has proper events for row manipulation If you are reviewing - // this code to create your own plug-ins, please do not do this! - ctx.aoRowCreatedCallback.push(function (row, data, index) { - var i, ien; - var d = ctx.aoData[index]; - - // Row - if (d._select_selected) { - $(row).addClass(ctx._select.className); - } - - // Cells and columns - if separated out, we would need to do two - // loops, so it makes sense to combine them into a single one - for (i = 0, ien = ctx.aoColumns.length; i < ien; i++) { - if ( - ctx.aoColumns[i]._select_selected || - (d._selected_cells && d._selected_cells[i]) - ) { - $(d.anCells[i]).addClass(ctx._select.className); - } - } - } - ); - - // On Ajax reload we want to reselect all rows which are currently selected, - // if there is an rowId (i.e. a unique value to identify each row with) - api.on('preXhr.dt.dtSelect', function (e, settings) { - if (settings !== api.settings()[0]) { - // Not triggered by our DataTable! - return; - } - - // note that column selection doesn't need to be cached and then - // reselected, as they are already selected - var rows = api - .rows({ selected: true }) - .ids(true) - .filter(function (d) { - return d !== undefined; - }); - - var cells = api - .cells({ selected: true }) - .eq(0) - .map(function (cellIdx) { - var id = api.row(cellIdx.row).id(true); - return id ? { row: id, column: cellIdx.column } : undefined; - }) - .filter(function (d) { - return d !== undefined; - }); - - // On the next draw, reselect the currently selected items - api.one('draw.dt.dtSelect', function () { - api.rows(rows).select(); - - // `cells` is not a cell index selector, so it needs a loop - if (cells.any()) { - cells.each(function (id) { - api.cells(id.row, id.column).select(); - }); - } - }); - }); - - // Update the table information element with selected item summary - api.on('info.dt', function (e, ctx, node) { - // Store the info node for updating on select / deselect - if (!ctx._select.infoEls.includes(node)) { - ctx._select.infoEls.push(node); - } - - info(api, node); - }); - - api.on('select.dtSelect.dt deselect.dtSelect.dt', function () { - ctx._select.infoEls.forEach(function (el) { - info(api, el); - }); - - api.state.save(); - }); - - // Clean up and release - api.on('destroy.dtSelect', function () { - // Remove class directly rather than calling deselect - which would trigger events - $(api.rows({ selected: true }).nodes()).removeClass(api.settings()[0]._select.className); - - disableMouseSelection(api); - api.off('.dtSelect'); - $('body').off('.dtSelect' + _safeId(api.table().node())); - }); -} - -/** - * Add one or more items (rows or columns) to the selection when shift clicking - * in OS selection style - * - * @param {DataTable.Api} dt DataTable - * @param {string} type Row or column range selector - * @param {object} idx Item index to select to - * @param {object} last Item index to select from - * @private - */ -function rowColumnRange(dt, type, idx, last) { - // Add a range of rows from the last selected row to this one - var indexes = dt[type + 's']({ search: 'applied' }).indexes(); - var idx1 = indexes.indexOf(last); - var idx2 = indexes.indexOf(idx); - - if (!dt[type + 's']({ selected: true }).any() && idx1 === -1) { - // select from top to here - slightly odd, but both Windows and Mac OS - // do this - indexes.splice(indexes.indexOf(idx) + 1, indexes.length); - } - else { - // reverse so we can shift click 'up' as well as down - if (idx1 > idx2) { - var tmp = idx2; - idx2 = idx1; - idx1 = tmp; - } - - indexes.splice(idx2 + 1, indexes.length); - indexes.splice(0, idx1); - } - - if (!dt[type](idx, { selected: true }).any()) { - // Select range - dt[type + 's'](indexes).select(); - } - else { - // Deselect range - need to keep the clicked on row selected - indexes.splice(indexes.indexOf(idx), 1); - dt[type + 's'](indexes).deselect(); - } -} - -/** - * Clear all selected items - * - * @param {DataTable.settings} ctx Settings object of the host DataTable - * @param {boolean} [force=false] Force the de-selection to happen, regardless - * of selection style - * @private - */ -function clear(ctx, force) { - if (force || ctx._select.style === 'single') { - var api = new DataTable.Api(ctx); - - api.rows({ selected: true }).deselect(); - api.columns({ selected: true }).deselect(); - api.cells({ selected: true }).deselect(); - } -} - -/** - * Select items based on the current configuration for style and items. - * - * @param {object} e Mouse event object - * @param {DataTables.Api} dt DataTable - * @param {DataTable.settings} ctx Settings object of the host DataTable - * @param {string} type Items to select - * @param {int|object} idx Index of the item to select - * @private - */ -function typeSelect(e, dt, ctx, type, idx) { - var style = dt.select.style(); - var toggleable = dt.select.toggleable(); - var isSelected = dt[type](idx, { selected: true }).any(); - - if (isSelected && !toggleable) { - return; - } - - if (style === 'os') { - if (e.ctrlKey || e.metaKey) { - // Add or remove from the selection - dt[type](idx).select(!isSelected); - } - else if (e.shiftKey) { - if (type === 'cell') { - cellRange(dt, idx, ctx._select_lastCell || null); - } - else { - rowColumnRange( - dt, - type, - idx, - ctx._select_lastCell ? ctx._select_lastCell[type] : null - ); - } - } - else { - // No cmd or shift click - deselect if selected, or select - // this row only - var selected = dt[type + 's']({ selected: true }); - - if (isSelected && selected.flatten().length === 1) { - dt[type](idx).deselect(); - } - else { - selected.deselect(); - dt[type](idx).select(); - } - } - } - else if (style == 'multi+shift') { - if (e.shiftKey) { - if (type === 'cell') { - cellRange(dt, idx, ctx._select_lastCell || null); - } - else { - rowColumnRange( - dt, - type, - idx, - ctx._select_lastCell ? ctx._select_lastCell[type] : null - ); - } - } - else { - dt[type](idx).select(!isSelected); - } - } - else { - dt[type](idx).select(!isSelected); - } -} - -function _safeId(node) { - return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-'); -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables selectors - */ - -// row and column are basically identical just assigned to different properties -// and checking a different array, so we can dynamically create the functions to -// reduce the code size -$.each( - [ - { type: 'row', prop: 'aoData' }, - { type: 'column', prop: 'aoColumns' } - ], - function (i, o) { - DataTable.ext.selector[o.type].push(function (settings, opts, indexes) { - var selected = opts.selected; - var data; - var out = []; - - if (selected !== true && selected !== false) { - return indexes; - } - - for (var i = 0, ien = indexes.length; i < ien; i++) { - data = settings[o.prop][indexes[i]]; - - if ( - data && ( - (selected === true && data._select_selected === true) || - (selected === false && !data._select_selected) - ) - ) { - out.push(indexes[i]); - } - } - - return out; - }); - } -); - -DataTable.ext.selector.cell.push(function (settings, opts, cells) { - var selected = opts.selected; - var rowData; - var out = []; - - if (selected === undefined) { - return cells; - } - - for (var i = 0, ien = cells.length; i < ien; i++) { - rowData = settings.aoData[cells[i].row]; - - if ( - rowData && ( - (selected === true && - rowData._selected_cells && - rowData._selected_cells[cells[i].column] === true) || - (selected === false && - (!rowData._selected_cells || !rowData._selected_cells[cells[i].column])) - ) - ) { - out.push(cells[i]); - } - } - - return out; -}); - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables API - * - * For complete documentation, please refer to the docs/api directory or the - * DataTables site - */ - -// Local variables to improve compression -var apiRegister = DataTable.Api.register; -var apiRegisterPlural = DataTable.Api.registerPlural; - -apiRegister('select()', function () { - return this.iterator('table', function (ctx) { - DataTable.select.init(new DataTable.Api(ctx)); - }); -}); - -apiRegister('select.blurable()', function (flag) { - if (flag === undefined) { - return this.context[0]._select.blurable; - } - - return this.iterator('table', function (ctx) { - ctx._select.blurable = flag; - }); -}); - -apiRegister('select.toggleable()', function (flag) { - if (flag === undefined) { - return this.context[0]._select.toggleable; - } - - return this.iterator('table', function (ctx) { - ctx._select.toggleable = flag; - }); -}); - -apiRegister('select.info()', function (flag) { - if (flag === undefined) { - return this.context[0]._select.info; - } - - return this.iterator('table', function (ctx) { - ctx._select.info = flag; - }); -}); - -apiRegister('select.items()', function (items) { - if (items === undefined) { - return this.context[0]._select.items; - } - - return this.iterator('table', function (ctx) { - ctx._select.items = items; - - eventTrigger(new DataTable.Api(ctx), 'selectItems', [items]); - }); -}); - -// Takes effect from the _next_ selection. None disables future selection, but -// does not clear the current selection. Use the `deselect` methods for that -apiRegister('select.style()', function (style) { - if (style === undefined) { - return this.context[0]._select.style; - } - - return this.iterator('table', function (ctx) { - if (!ctx._select) { - DataTable.select.init(new DataTable.Api(ctx)); - } - - if (!ctx._select_init) { - init(ctx); - } - - ctx._select.style = style; - - // Add / remove mouse event handlers. They aren't required when only - // API selection is available - var dt = new DataTable.Api(ctx); - disableMouseSelection(dt); - - if (style !== 'api') { - enableMouseSelection(dt); - } - - eventTrigger(new DataTable.Api(ctx), 'selectStyle', [style]); - }); -}); - -apiRegister('select.selector()', function (selector) { - if (selector === undefined) { - return this.context[0]._select.selector; - } - - return this.iterator('table', function (ctx) { - disableMouseSelection(new DataTable.Api(ctx)); - - ctx._select.selector = selector; - - if (ctx._select.style !== 'api') { - enableMouseSelection(new DataTable.Api(ctx)); - } - }); -}); - -apiRegister('select.last()', function (set) { - let ctx = this.context[0]; - - if (set) { - ctx._select_lastCell = set; - return this; - } - - return ctx._select_lastCell; -}); - -apiRegisterPlural('rows().select()', 'row().select()', function (select) { - var api = this; - - if (select === false) { - return this.deselect(); - } - - this.iterator('row', function (ctx, idx) { - clear(ctx); - - // There is a good amount of knowledge of DataTables internals in - // this function. It _could_ be done without that, but it would hurt - // performance (or DT would need new APIs for this work) - var dtData = ctx.aoData[idx]; - var dtColumns = ctx.aoColumns; - - $(dtData.nTr).addClass(ctx._select.className); - dtData._select_selected = true; - - for (var i=0 ; i 0); - }); - - this.disable(); - }, - destroy: function (dt, node, config) { - dt.off(config._eventNamespace); - } - }, - showSelected: { - text: i18n('showSelected', 'Show only selected'), - className: 'buttons-show-selected', - action: function (e, dt) { - if (dt.search.fixed('dt-select')) { - // Remove existing function - dt.search.fixed('dt-select', null); - - this.active(false); - } - else { - // Use a fixed filtering function to match on selected rows - // This needs to reference the internal aoData since that is - // where Select stores its reference for the selected state - var dataSrc = dt.settings()[0].aoData; - - dt.search.fixed('dt-select', function (text, data, idx) { - // _select_selected is set by Select on the data object for the row - return dataSrc[idx]._select_selected; - }); - - this.active(true); - } - - dt.draw(); - } - } -}); - -$.each(['Row', 'Column', 'Cell'], function (i, item) { - var lc = item.toLowerCase(); - - DataTable.ext.buttons['select' + item + 's'] = { - text: i18n('select' + item + 's', 'Select ' + lc + 's'), - className: 'buttons-select-' + lc + 's', - action: function () { - this.select.items(lc); - }, - init: function (dt) { - var that = this; - - dt.on('selectItems.dt.DT', function (e, ctx, items) { - that.active(items === lc); - }); - } - }; -}); - -DataTable.type('select-checkbox', { - className: 'dt-select', - detect: function (data) { - // Rendering function will tell us if it is a checkbox type - return data === 'select-checkbox' ? data : false; - }, - order: { - pre: function (d) { - return d === 'X' ? -1 : 0; - } - } -}); - -$.extend(true, DataTable.defaults.oLanguage, { - select: { - aria: { - rowCheckbox: 'Select row' - } - } -}); - -DataTable.render.select = function (valueProp, nameProp) { - var valueFn = valueProp ? DataTable.util.get(valueProp) : null; - var nameFn = nameProp ? DataTable.util.get(nameProp) : null; - - return function (data, type, row, meta) { - var dtRow = meta.settings.aoData[meta.row]; - var selected = dtRow._select_selected; - var ariaLabel = meta.settings.oLanguage.select.aria.rowCheckbox; - - if (type === 'display') { - return $('') - .attr({ - 'aria-label': ariaLabel, - class: 'dt-select-checkbox', - name: nameFn ? nameFn(row) : null, - type: 'checkbox', - value: valueFn ? valueFn(row) : null, - checked: selected - })[0]; - } - else if (type === 'type') { - return 'select-checkbox'; - } - else if (type === 'filter') { - return ''; - } - - return selected ? 'X' : ''; - } -} - -// Legacy checkbox ordering -DataTable.ext.order['select-checkbox'] = function (settings, col) { - return this.api() - .column(col, { order: 'index' }) - .nodes() - .map(function (td) { - if (settings._select.items === 'row') { - return $(td).parent().hasClass(settings._select.className); - } - else if (settings._select.items === 'cell') { - return $(td).hasClass(settings._select.className); - } - return false; - }); -}; - -$.fn.DataTable.select = DataTable.select; - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Initialisation - */ - -// DataTables creation - check if select has been defined in the options. Note -// this required that the table be in the document! If it isn't then something -// needs to trigger this method unfortunately. The next major release of -// DataTables will rework the events and address this. -$(document).on('preInit.dt.dtSelect', function (e, ctx) { - if (e.namespace !== 'dt') { - return; - } - - DataTable.select.init(new DataTable.Api(ctx)); -}); - - -export default DataTable; diff --git a/assets/js/register_events.js b/assets/js/register_events.js index 547742ea..1c6ed09b 100644 --- a/assets/js/register_events.js +++ b/assets/js/register_events.js @@ -19,15 +19,14 @@ 'use strict'; -import {Dropdown} from "bootstrap"; +import {Dropdown, Modal, Tooltip} from "bootstrap"; import ClipboardJS from "clipboard"; -import {Modal} from "bootstrap"; class RegisterEventHelper { constructor() { this.registerTooltips(); this.configureDropdowns(); - + // Only register special character input if enabled in configuration const keybindingsEnabled = document.body.dataset.keybindingsSpecialCharacters !== 'false'; if (keybindingsEnabled) { @@ -40,8 +39,6 @@ class RegisterEventHelper { }); this.registerModalDropRemovalOnFormSubmit(); - - } registerModalDropRemovalOnFormSubmit() { @@ -83,11 +80,17 @@ class RegisterEventHelper { registerTooltips() { const handler = () => { - $(".tooltip").remove(); + document.querySelectorAll('.tooltip').forEach(el => el.remove()); + //Exclude dropdown buttons from tooltips, otherwise we run into endless errors from bootstrap (bootstrap.esm.js:614 Bootstrap doesn't allow more than one instance per element. Bound instance: bs.dropdown.) - $('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title], small[title]') - //@ts-ignore - .tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'}); + const tooltipSelector = 'a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title], small[title]'; + document.querySelectorAll(tooltipSelector).forEach(el => { + const existing = Tooltip.getInstance(el); + if (existing) { + existing.dispose(); + } + new Tooltip(el, {container: 'body', placement: 'auto', boundary: 'window'}); + }); }; this.registerLoadHandler(handler); @@ -95,242 +98,240 @@ class RegisterEventHelper { } registerSpecialCharInput() { - this.registerLoadHandler(() => { - //@ts-ignore - $("input[type=text], input[type=search]").unbind("keydown").keydown(function (event) { - let use_special_char = event.altKey; - - let greek_char = ""; - if (use_special_char){ - //Use the key property to determine the greek letter (as it is independent of the keyboard layout) - switch(event.key) { - //Greek letters - case "a": //Alpha (lowercase) - greek_char = "\u03B1"; - break; - case "A": //Alpha (uppercase) - greek_char = "\u0391"; - break; - case "b": //Beta (lowercase) - greek_char = "\u03B2"; - break; - case "B": //Beta (uppercase) - greek_char = "\u0392"; - break; - case "g": //Gamma (lowercase) - greek_char = "\u03B3"; - break; - case "G": //Gamma (uppercase) - greek_char = "\u0393"; - break; - case "d": //Delta (lowercase) - greek_char = "\u03B4"; - break; - case "D": //Delta (uppercase) - greek_char = "\u0394"; - break; - case "e": //Epsilon (lowercase) - greek_char = "\u03B5"; - break; - case "E": //Epsilon (uppercase) - greek_char = "\u0395"; - break; - case "z": //Zeta (lowercase) - greek_char = "\u03B6"; - break; - case "Z": //Zeta (uppercase) - greek_char = "\u0396"; - break; - case "h": //Eta (lowercase) - greek_char = "\u03B7"; - break; - case "H": //Eta (uppercase) - greek_char = "\u0397"; - break; - case "q": //Theta (lowercase) - greek_char = "\u03B8"; - break; - case "Q": //Theta (uppercase) - greek_char = "\u0398"; - break; - case "i": //Iota (lowercase) - greek_char = "\u03B9"; - break; - case "I": //Iota (uppercase) - greek_char = "\u0399"; - break; - case "k": //Kappa (lowercase) - greek_char = "\u03BA"; - break; - case "K": //Kappa (uppercase) - greek_char = "\u039A"; - break; - case "l": //Lambda (lowercase) - greek_char = "\u03BB"; - break; - case "L": //Lambda (uppercase) - greek_char = "\u039B"; - break; - case "m": //Mu (lowercase) - greek_char = "\u03BC"; - break; - case "M": //Mu (uppercase) - greek_char = "\u039C"; - break; - case "n": //Nu (lowercase) - greek_char = "\u03BD"; - break; - case "N": //Nu (uppercase) - greek_char = "\u039D"; - break; - case "x": //Xi (lowercase) - greek_char = "\u03BE"; - break; - case "X": //Xi (uppercase) - greek_char = "\u039E"; - break; - case "o": //Omicron (lowercase) - greek_char = "\u03BF"; - break; - case "O": //Omicron (uppercase) - greek_char = "\u039F"; - break; - case "p": //Pi (lowercase) - greek_char = "\u03C0"; - break; - case "P": //Pi (uppercase) - greek_char = "\u03A0"; - break; - case "r": //Rho (lowercase) - greek_char = "\u03C1"; - break; - case "R": //Rho (uppercase) - greek_char = "\u03A1"; - break; - case "s": //Sigma (lowercase) - greek_char = "\u03C3"; - break; - case "S": //Sigma (uppercase) - greek_char = "\u03A3"; - break; - case "t": //Tau (lowercase) - greek_char = "\u03C4"; - break; - case "T": //Tau (uppercase) - greek_char = "\u03A4"; - break; - case "u": //Upsilon (lowercase) - greek_char = "\u03C5"; - break; - case "U": //Upsilon (uppercase) - greek_char = "\u03A5"; - break; - case "f": //Phi (lowercase) - greek_char = "\u03C6"; - break; - case "F": //Phi (uppercase) - greek_char = "\u03A6"; - break; - case "c": //Chi (lowercase) - greek_char = "\u03C7"; - break; - case "C": //Chi (uppercase) - greek_char = "\u03A7"; - break; - case "y": //Psi (lowercase) - greek_char = "\u03C8"; - break; - case "Y": //Psi (uppercase) - greek_char = "\u03A8"; - break; - case "w": //Omega (lowercase) - greek_char = "\u03C9"; - break; - case "W": //Omega (uppercase) - greek_char = "\u03A9"; - break; - } - - //Use keycodes for special characters as the shift char on the number keys are layout dependent - switch (event.keyCode) { - case 49: //1 key - //Product symbol on shift, sum on no shift - greek_char = event.shiftKey ? "\u220F" : "\u2211"; - break; - case 50: //2 key - //Integral on no shift, partial derivative on shift - greek_char = event.shiftKey ? "\u2202" : "\u222B"; - break; - case 51: //3 key - //Less than or equal on no shift, greater than or equal on shift - greek_char = event.shiftKey ? "\u2265" : "\u2264"; - break; - case 52: //4 key - //Empty set on shift, infinity on no shift - greek_char = event.shiftKey ? "\u2205" : "\u221E"; - break; - case 53: //5 key - //Not equal on shift, approx equal on no shift - greek_char = event.shiftKey ? "\u2260" : "\u2248"; - break; - case 54: //6 key - //Element of on no shift, not element of on shift - greek_char = event.shiftKey ? "\u2209" : "\u2208"; - break; - case 55: //7 key - //And on shift, or on no shift - greek_char = event.shiftKey ? "\u2227" : "\u2228"; - break; - case 56: //8 key - //Proportional to on shift, angle on no shift - greek_char = event.shiftKey ? "\u221D" : "\u2220"; - break; - case 57: //9 key - //Cube root on shift, square root on no shift - greek_char = event.shiftKey ? "\u221B" : "\u221A"; - break; - case 48: //0 key - //Minus-Plus on shift, plus-minus on no shift - greek_char = event.shiftKey ? "\u2213" : "\u00B1"; - break; - - //Special characters - case 219: //hyphen (or ß on german layout) - //Copyright on no shift, TM on shift - greek_char = event.shiftKey ? "\u2122" : "\u00A9"; - break; - case 191: //forward slash (or # on german layout) - //Generic currency on no shift, paragraph on shift - greek_char = event.shiftKey ? "\u00B6" : "\u00A4"; - break; - - //Currency symbols - case 192: //: or (ö on german layout) - //Euro on no shift, pound on shift - greek_char = event.shiftKey ? "\u00A3" : "\u20AC"; - break; - case 221: //; or (ä on german layout) - //Yen on no shift, dollar on shift - greek_char = event.shiftKey ? "\u0024" : "\u00A5"; - break; - - - } - - if(greek_char=="") return; - - let $txt = $(this); - //@ts-ignore - let caretPos = $txt[0].selectionStart; - let textAreaTxt = $txt.val().toString(); - $txt.val(textAreaTxt.substring(0, caretPos) + greek_char + textAreaTxt.substring(caretPos) ); + const keydownHandler = function(event) { + let use_special_char = event.altKey; + let greek_char = ""; + if (use_special_char){ + //Use the key property to determine the greek letter (as it is independent of the keyboard layout) + switch(event.key) { + //Greek letters + case "a": //Alpha (lowercase) + greek_char = "α"; + break; + case "A": //Alpha (uppercase) + greek_char = "Α"; + break; + case "b": //Beta (lowercase) + greek_char = "β"; + break; + case "B": //Beta (uppercase) + greek_char = "Β"; + break; + case "g": //Gamma (lowercase) + greek_char = "γ"; + break; + case "G": //Gamma (uppercase) + greek_char = "Γ"; + break; + case "d": //Delta (lowercase) + greek_char = "δ"; + break; + case "D": //Delta (uppercase) + greek_char = "Δ"; + break; + case "e": //Epsilon (lowercase) + greek_char = "ε"; + break; + case "E": //Epsilon (uppercase) + greek_char = "Ε"; + break; + case "z": //Zeta (lowercase) + greek_char = "ζ"; + break; + case "Z": //Zeta (uppercase) + greek_char = "Ζ"; + break; + case "h": //Eta (lowercase) + greek_char = "η"; + break; + case "H": //Eta (uppercase) + greek_char = "Η"; + break; + case "q": //Theta (lowercase) + greek_char = "θ"; + break; + case "Q": //Theta (uppercase) + greek_char = "Θ"; + break; + case "i": //Iota (lowercase) + greek_char = "ι"; + break; + case "I": //Iota (uppercase) + greek_char = "Ι"; + break; + case "k": //Kappa (lowercase) + greek_char = "κ"; + break; + case "K": //Kappa (uppercase) + greek_char = "Κ"; + break; + case "l": //Lambda (lowercase) + greek_char = "λ"; + break; + case "L": //Lambda (uppercase) + greek_char = "Λ"; + break; + case "m": //Mu (lowercase) + greek_char = "μ"; + break; + case "M": //Mu (uppercase) + greek_char = "Μ"; + break; + case "n": //Nu (lowercase) + greek_char = "ν"; + break; + case "N": //Nu (uppercase) + greek_char = "Ν"; + break; + case "x": //Xi (lowercase) + greek_char = "ξ"; + break; + case "X": //Xi (uppercase) + greek_char = "Ξ"; + break; + case "o": //Omicron (lowercase) + greek_char = "ο"; + break; + case "O": //Omicron (uppercase) + greek_char = "Ο"; + break; + case "p": //Pi (lowercase) + greek_char = "π"; + break; + case "P": //Pi (uppercase) + greek_char = "Π"; + break; + case "r": //Rho (lowercase) + greek_char = "ρ"; + break; + case "R": //Rho (uppercase) + greek_char = "Ρ"; + break; + case "s": //Sigma (lowercase) + greek_char = "σ"; + break; + case "S": //Sigma (uppercase) + greek_char = "Σ"; + break; + case "t": //Tau (lowercase) + greek_char = "τ"; + break; + case "T": //Tau (uppercase) + greek_char = "Τ"; + break; + case "u": //Upsilon (lowercase) + greek_char = "υ"; + break; + case "U": //Upsilon (uppercase) + greek_char = "Υ"; + break; + case "f": //Phi (lowercase) + greek_char = "φ"; + break; + case "F": //Phi (uppercase) + greek_char = "Φ"; + break; + case "c": //Chi (lowercase) + greek_char = "χ"; + break; + case "C": //Chi (uppercase) + greek_char = "Χ"; + break; + case "y": //Psi (lowercase) + greek_char = "ψ"; + break; + case "Y": //Psi (uppercase) + greek_char = "Ψ"; + break; + case "w": //Omega (lowercase) + greek_char = "ω"; + break; + case "W": //Omega (uppercase) + greek_char = "Ω"; + break; } + + //Use keycodes for special characters as the shift char on the number keys are layout dependent + switch (event.keyCode) { + case 49: //1 key + //Product symbol on shift, sum on no shift + greek_char = event.shiftKey ? "∏" : "∑"; + break; + case 50: //2 key + //Integral on no shift, partial derivative on shift + greek_char = event.shiftKey ? "∂" : "∫"; + break; + case 51: //3 key + //Less than or equal on no shift, greater than or equal on shift + greek_char = event.shiftKey ? "≥" : "≤"; + break; + case 52: //4 key + //Empty set on shift, infinity on no shift + greek_char = event.shiftKey ? "∅" : "∞"; + break; + case 53: //5 key + //Not equal on shift, approx equal on no shift + greek_char = event.shiftKey ? "≠" : "≈"; + break; + case 54: //6 key + //Element of on no shift, not element of on shift + greek_char = event.shiftKey ? "∉" : "∈"; + break; + case 55: //7 key + //And on shift, or on no shift + greek_char = event.shiftKey ? "∧" : "∨"; + break; + case 56: //8 key + //Proportional to on shift, angle on no shift + greek_char = event.shiftKey ? "∝" : "∠"; + break; + case 57: //9 key + //Cube root on shift, square root on no shift + greek_char = event.shiftKey ? "∛" : "√"; + break; + case 48: //0 key + //Minus-Plus on shift, plus-minus on no shift + greek_char = event.shiftKey ? "∓" : "±"; + break; + + //Special characters + case 219: //hyphen (or ß on german layout) + //Copyright on no shift, TM on shift + greek_char = event.shiftKey ? "™" : "©"; + break; + case 191: //forward slash (or # on german layout) + //Generic currency on no shift, paragraph on shift + greek_char = event.shiftKey ? "¶" : "¤"; + break; + + //Currency symbols + case 192: //: or (ö on german layout) + //Euro on no shift, pound on shift + greek_char = event.shiftKey ? "£" : "€"; + break; + case 221: //; or (ä on german layout) + //Yen on no shift, dollar on shift + greek_char = event.shiftKey ? "$" : "¥"; + break; + } + + if(greek_char=="") return; + + const txt = event.currentTarget; + const caretPos = txt.selectionStart; + const textAreaTxt = txt.value; + txt.value = textAreaTxt.substring(0, caretPos) + greek_char + textAreaTxt.substring(caretPos); + } + }; + + this.registerLoadHandler(() => { + document.querySelectorAll('input[type=text], input[type=search]').forEach(input => { + input.removeEventListener('keydown', keydownHandler); + input.addEventListener('keydown', keydownHandler); }); - //@ts-ignore - this.greek_once = true; - }) + }); } } -export default new RegisterEventHelper(); \ No newline at end of file +export default new RegisterEventHelper(); diff --git a/assets/css/components/bootbox_extensions.css b/assets/themes/brite.js similarity index 58% rename from assets/css/components/bootbox_extensions.css rename to assets/themes/brite.js index 42bbd78d..41b82e93 100644 --- a/assets/css/components/bootbox_extensions.css +++ b/assets/themes/brite.js @@ -1,7 +1,7 @@ /* * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). * - * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) + * Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -17,30 +17,4 @@ * along with this program. If not, see . */ -.modal-body > .bootbox-close-button { - position: absolute; - top: 0; - right: 0; - padding: 0.5rem 0.75rem; - z-index: 1; -} -.modal .bootbox-close-button { - font-weight: 100; -} - -button.bootbox-close-button { - padding: 0; - background-color: transparent; - border: 0; - -webkit-appearance: none; -} - -.bootbox-close-button { - /* float: right; */ - font-size: 1.40625rem; - font-weight: 600; - line-height: 1; - color: #000; - text-shadow: none; - opacity: .5; -} \ No newline at end of file +import "bootswatch/dist/brite/bootstrap.css"; diff --git a/config/parameters.yaml b/config/parameters.yaml index b1aa5314..e654a9b5 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -53,6 +53,7 @@ parameters: # Themes commented here by default, are not really usable, because of display problems. Enable them at your own risk! partdb.available_themes: - bootstrap + - brite - cerulean - cosmo - cyborg diff --git a/package.json b/package.json index f846f1d1..f906d814 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "bootstrap": "^5.1.3", "core-js": "^3.38.0", "intl-messageformat": "^10.5.11", - "jquery": "^3.5.1", + "jquery": "^4.0.0", "popper.js": "^1.14.7", "regenerator-runtime": "^0.14.1", "webpack": "^5.74.0", @@ -38,20 +38,21 @@ "@algolia/autocomplete-theme-classic": "^1.17.0", "@jbtronics/bs-treeview": "^1.0.1", "@part-db/html5-qrcode": "^4.0.0", - "@zxcvbn-ts/core": "^3.0.2", - "@zxcvbn-ts/language-common": "^3.0.3", - "@zxcvbn-ts/language-de": "^3.0.1", - "@zxcvbn-ts/language-en": "^3.0.1", - "@zxcvbn-ts/language-fr": "^3.0.1", - "@zxcvbn-ts/language-ja": "^3.0.1", + "@zxcvbn-ts/core": "^4.1.2", + "@zxcvbn-ts/language-common": "^4.1.2", + "@zxcvbn-ts/language-de": "^4.1.1", + "@zxcvbn-ts/language-en": "^4.1.1", + "@zxcvbn-ts/language-fr": "^4.1.1", + "@zxcvbn-ts/language-it": "^4.1.1", + "@zxcvbn-ts/language-ja": "^4.1.1", + "@zxcvbn-ts/language-pl": "^4.1.1", "attr-accept": "^2.2.5", "barcode-detector": "^3.0.5", - "bootbox": "^6.0.0", "bootswatch": "^5.1.3", "bs-custom-file-input": "^1.3.4", "ckeditor5": "^48.0.0", "clipboard": "^2.0.4", - "compression-webpack-plugin": "^11.1.0", + "compression-webpack-plugin": "^12.0.0", "datatables.net": "^2.0.0", "datatables.net-bs5": "^2.0.0", "datatables.net-buttons-bs5": "^3.0.0", @@ -69,11 +70,12 @@ "marked-mangle": "^1.0.1", "pdfmake": "^0.3.7", "stimulus-use": "^0.52.0", + "sweetalert2": "^11.26.25", "tom-select": "^2.1.0", "ts-loader": "^9.2.6", "typescript": "^6.0.2" }, "resolutions": { - "jquery": "^3.5.1" + "jquery": "^4.0.0" } } diff --git a/translations/frontend.cs.xlf b/translations/frontend.cs.xlf index 4ba1f913..6417033b 100644 --- a/translations/frontend.cs.xlf +++ b/translations/frontend.cs.xlf @@ -55,5 +55,23 @@ Jdi! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Zrušit + + + + + dialog.btn.deny + Ne + + diff --git a/translations/frontend.da.xlf b/translations/frontend.da.xlf index 4b6a15b9..817c703f 100644 --- a/translations/frontend.da.xlf +++ b/translations/frontend.da.xlf @@ -55,5 +55,23 @@ Kom nu! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Annuller + + + + + dialog.btn.deny + Nej + + diff --git a/translations/frontend.de.xlf b/translations/frontend.de.xlf index 9ebd0d32..6a4e7786 100644 --- a/translations/frontend.de.xlf +++ b/translations/frontend.de.xlf @@ -55,5 +55,23 @@ Los! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Abbrechen + + + + + dialog.btn.deny + Nein + + diff --git a/translations/frontend.el.xlf b/translations/frontend.el.xlf index bab41358..fff724d2 100644 --- a/translations/frontend.el.xlf +++ b/translations/frontend.el.xlf @@ -7,5 +7,23 @@ Αναζήτηση + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Ακύρωση + + + + + dialog.btn.deny + Όχι + + \ No newline at end of file diff --git a/translations/frontend.en.xlf b/translations/frontend.en.xlf index 91617f79..7f68558a 100644 --- a/translations/frontend.en.xlf +++ b/translations/frontend.en.xlf @@ -55,5 +55,29 @@ Go! + + + user.password_strength.crack_time + Estimated time to crack: %time% + + + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Cancel + + + + + dialog.btn.deny + No + + diff --git a/translations/frontend.es.xlf b/translations/frontend.es.xlf index 7d339959..02c52715 100644 --- a/translations/frontend.es.xlf +++ b/translations/frontend.es.xlf @@ -55,5 +55,23 @@ ¡Vamos! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Cancelar + + + + + dialog.btn.deny + No + + diff --git a/translations/frontend.fr.xlf b/translations/frontend.fr.xlf index 5ebfca51..2a459052 100644 --- a/translations/frontend.fr.xlf +++ b/translations/frontend.fr.xlf @@ -55,5 +55,23 @@ Rechercher ! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Annuler + + + + + dialog.btn.deny + Non + + diff --git a/translations/frontend.hu.xlf b/translations/frontend.hu.xlf index c303dedc..7724ce66 100644 --- a/translations/frontend.hu.xlf +++ b/translations/frontend.hu.xlf @@ -55,5 +55,23 @@ Indítás! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Mégse + + + + + dialog.btn.deny + Nem + + diff --git a/translations/frontend.it.xlf b/translations/frontend.it.xlf index f163e3e2..c13ab47a 100644 --- a/translations/frontend.it.xlf +++ b/translations/frontend.it.xlf @@ -55,5 +55,23 @@ Cerca! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Annulla + + + + + dialog.btn.deny + No + + diff --git a/translations/frontend.ja.xlf b/translations/frontend.ja.xlf index 90ffdf5f..d8612e5b 100644 --- a/translations/frontend.ja.xlf +++ b/translations/frontend.ja.xlf @@ -19,5 +19,23 @@ 検索 + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + キャンセル + + + + + dialog.btn.deny + いいえ + + \ No newline at end of file diff --git a/translations/frontend.nl.xlf b/translations/frontend.nl.xlf index d14f5a81..4c90e933 100644 --- a/translations/frontend.nl.xlf +++ b/translations/frontend.nl.xlf @@ -55,5 +55,23 @@ Ga! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Annuleren + + + + + dialog.btn.deny + Nee + + diff --git a/translations/frontend.pl.xlf b/translations/frontend.pl.xlf index fface684..e748286c 100644 --- a/translations/frontend.pl.xlf +++ b/translations/frontend.pl.xlf @@ -55,5 +55,23 @@ Idź! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Anuluj + + + + + dialog.btn.deny + Nie + + diff --git a/translations/frontend.pt_BR.xlf b/translations/frontend.pt_BR.xlf index fb2f2335..9053c8c3 100644 --- a/translations/frontend.pt_BR.xlf +++ b/translations/frontend.pt_BR.xlf @@ -55,5 +55,23 @@ Vá! + + + dialog.btn.ok + OK + + + + + dialog.btn.cancel + Cancelar + + + + + dialog.btn.deny + Não + + diff --git a/translations/frontend.ru.xlf b/translations/frontend.ru.xlf index f4665a74..6e1879d8 100644 --- a/translations/frontend.ru.xlf +++ b/translations/frontend.ru.xlf @@ -55,5 +55,23 @@ Поехали! + + + dialog.btn.ok + ОК + + + + + dialog.btn.cancel + Отмена + + + + + dialog.btn.deny + Нет + + diff --git a/translations/frontend.uk.xlf b/translations/frontend.uk.xlf index fee1b03e..a28941fa 100644 --- a/translations/frontend.uk.xlf +++ b/translations/frontend.uk.xlf @@ -55,5 +55,23 @@ Почати! + + + dialog.btn.ok + ОК + + + + + dialog.btn.cancel + Скасувати + + + + + dialog.btn.deny + Ні + + diff --git a/translations/frontend.zh.xlf b/translations/frontend.zh.xlf index 8bb063b8..7425b1e8 100644 --- a/translations/frontend.zh.xlf +++ b/translations/frontend.zh.xlf @@ -55,5 +55,23 @@ GO! + + + dialog.btn.ok + 确定 + + + + + dialog.btn.cancel + 取消 + + + + + dialog.btn.deny + + + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 117e529b..fe219869 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -490,7 +490,7 @@ The user will have to set up all two-factor authentication methods again and pri entity.delete.confirm_title - You really want to delete %name%? + Do you really want to delete %name%? diff --git a/webpack.config.js b/webpack.config.js index 60ea145f..259803fa 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -119,7 +119,14 @@ Encore // requires WebpackEncoreBundle 1.4 or higher .enableIntegrityHashes(Encore.isProduction()) - // uncomment if you're having problems with a jQuery plugin + // Force all jquery imports to the UMD build so webpack always receives the + // jQuery function directly instead of an ESM namespace object. Without this, + // webpack's ESM interop wraps jquery.module.js in a namespace + // { default, jQuery, $ } which has no .fn, crashing Bootstrap's + // defineJQueryPlugin when it tries to access $.fn.alert. + .addAliases({ + 'jquery': path.resolve(__dirname, 'node_modules/jquery/dist/jquery.js') + }) .autoProvidejQuery() @@ -142,7 +149,7 @@ Encore ; //These are all the themes that are available in bootswatch -const AVAILABLE_THEMES = ['bootstrap', 'cerulean', 'cosmo', 'cyborg', 'darkly', 'flatly', 'journal', +const AVAILABLE_THEMES = ['bootstrap', 'brite', 'cerulean', 'cosmo', 'cyborg', 'darkly', 'flatly', 'journal', 'litera', 'lumen', 'lux', 'materia', 'minty', 'morph', 'pulse', 'quartz', 'sandstone', 'simplex', 'sketchy', 'slate', 'solar', 'spacelab', 'superhero', 'united', 'vapor', 'yeti', 'zephyr']; diff --git a/yarn.lock b/yarn.lock index 19e9716e..7589ce13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2071,37 +2071,66 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -"@zxcvbn-ts/core@^3.0.2": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@zxcvbn-ts/core/-/core-3.0.4.tgz#c5bde72235eb6c273cec78b672bb47c0d7045cad" - integrity sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw== +"@zxcvbn-ts/core@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/core/-/core-4.1.2.tgz#2d280f3d1a558201cf34080c4d7de335afd4cc4a" + integrity sha512-RQmxWB3AMI+HGQErQdUv6Aq32aQhp6xOxrfgCP0+T9MsLZoP3xtLHuT8O8VojsUxdmQVZfJlYkYb1A0wOwIS+Q== dependencies: fastest-levenshtein "1.0.16" -"@zxcvbn-ts/language-common@^3.0.3": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-common/-/language-common-3.0.4.tgz#fa1d2a42f8c8a589555859795da90d6b8027b7c4" - integrity sha512-viSNNnRYtc7ULXzxrQIVUNwHAPSXRtoIwy/Tq4XQQdIknBzw4vz36lQLF6mvhMlTIlpjoN/Z1GFu/fwiAlUSsw== +"@zxcvbn-ts/dictionary-compression@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/dictionary-compression/-/dictionary-compression-3.0.1.tgz#f357ad46e08fff8ba92f6f163d6b38b9533fc849" + integrity sha512-p3KyPzxGc3vWSap5hHA6SllbUCmh7s+NtpGyC3qEWrxYJT9t9TUAzjPm48Okipo+UUyPQfDlIvTcs9JRShBFiQ== -"@zxcvbn-ts/language-de@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-de/-/language-de-3.0.2.tgz#fbd0d1be9454e308bbd63bf5487d4c17670094f0" - integrity sha512-CPl2314qWtnJl4EkeEqFbL4unP6yEAHO976ER+df8CQcKsF4FxdZYEahkleWU66dhNI2VKnmJKNMzq8QtHQKcw== +"@zxcvbn-ts/language-common@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-common/-/language-common-4.1.2.tgz#c38c52500865d3a2ab7fa1193d747dafc4f2b995" + integrity sha512-uJlBzhC9/KjPImqdnc1/lPxmdn4xKbkruN5p1mASWkXA0gli+GZ5LrVL+dqscA8Pcf4OfudE56TtCWeHljJOvA== + dependencies: + "@zxcvbn-ts/dictionary-compression" "^3.0.1" -"@zxcvbn-ts/language-en@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-en/-/language-en-3.0.2.tgz#162ada6b2b556444efd5a7700e70845cfde6d6ec" - integrity sha512-Zp+zL+I6Un2Bj0tRXNs6VUBq3Djt+hwTwUz4dkt2qgsQz47U0/XthZ4ULrT/RxjwJRl5LwiaKOOZeOtmixHnjg== +"@zxcvbn-ts/language-de@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-de/-/language-de-4.1.1.tgz#c6a91f43119fdedefe35b7049c8e4f7af9dd88fa" + integrity sha512-ig4zeCxg4yp5VU4/Iuq5CCHLJtbmHjczK87HKw/K2jYkpk1s7C4jRi+n3XgcPNRP71nvTxGhpPWWlsziCnm5xA== + dependencies: + "@zxcvbn-ts/dictionary-compression" "^3.0.1" -"@zxcvbn-ts/language-fr@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-fr/-/language-fr-3.0.2.tgz#79c5f0475fd388502f04f5560456db37dc0dde35" - integrity sha512-Tj9jS/Z8mNBAD21pn8Mp4O86CPrwImysO1fM3DG+fsfk8W79/MDzqpFDBHiqpu69Uo3LPPctMHEEteakFWt4Qg== +"@zxcvbn-ts/language-en@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-en/-/language-en-4.1.1.tgz#20ca499affb4d6972d777ec04bb0c786d33add73" + integrity sha512-6UdzuBd3Uex8TKubohcn+uXRVAH34Zjs2eCfT4hQVo9zeTy7AkQRQfdV4OnHR5hQfW/XBrK/AGTZk7VBWh7wwQ== + dependencies: + "@zxcvbn-ts/dictionary-compression" "^3.0.1" -"@zxcvbn-ts/language-ja@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-ja/-/language-ja-3.0.2.tgz#299bb6f5465f99405577491b1b31352058540c76" - integrity sha512-YjQyt+eMe3EdpeJiSt81AMF8HfEXXCary/VRoG+0erZBzRjfJ1U3JdSiu9wFFxiEF8Cb5FEmTQ6nQPyraezH6Q== +"@zxcvbn-ts/language-fr@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-fr/-/language-fr-4.1.1.tgz#7d1eccaad7b4dbfe31efe018e9239893bdc33bc8" + integrity sha512-5LW8KMiXLWKG6fTv/BdQbe76sa2EjYmvd59sM3Re+hZMGYEPOdjnAT5qFChQ2Zj8WIaU3P197Y6A0X8OgfoiqQ== + dependencies: + "@zxcvbn-ts/dictionary-compression" "^3.0.1" + +"@zxcvbn-ts/language-it@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-it/-/language-it-4.1.1.tgz#b6fda40099e85a4fc1c5d14d75c9de8a304dd061" + integrity sha512-YxKCBO1rKuCMPYRyOxfUZA+3ju8OO8W9Qx8h/vHrHvuGIavK7L+fgXTUrhjHU8M+zE0pQZxS4wOdfgLFZaP57w== + dependencies: + "@zxcvbn-ts/dictionary-compression" "^3.0.1" + +"@zxcvbn-ts/language-ja@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-ja/-/language-ja-4.1.1.tgz#acd36abe4f6083dceda22771148d0948e0e421d9" + integrity sha512-ZDFUZfm7hlmuiHOMLq7p85wE3Pa7s1WXixU6X+POTuRTjGwXi4LMtiS9wli7zXTEvxSUMdVWBx5ZgyIF6D0S8A== + dependencies: + "@zxcvbn-ts/dictionary-compression" "^3.0.1" + +"@zxcvbn-ts/language-pl@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-pl/-/language-pl-4.1.1.tgz#627d4c365a69e3f78d3e2b6e9667d1b57b6cc1ff" + integrity sha512-hF6Qu9cyHx7sSEzNOQrJQntq8geoincsGvlOC0wkD43LnUVSTR49MKxdsifsemlQgtxR7aUKDgK/e/RFHOljoQ== + dependencies: + "@zxcvbn-ts/dictionary-compression" "^3.0.1" acorn-import-phases@^1.0.3: version "1.0.4" @@ -2242,11 +2271,6 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -bootbox@^6.0.0: - version "6.0.4" - resolved "https://registry.yarnpkg.com/bootbox/-/bootbox-6.0.4.tgz#005f12d712da5d5723a13a0495e5fc4ad2cc1472" - integrity sha512-LoOT8WbiH6YjlhIxzJ3nZHK1p9tlcoa6QNILSGJMx9ihydzFk+DVzNVWNpHk8MZLOkizey1XhFu1dhoqk77xVg== - bootstrap@^5.1.3: version "5.3.8" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.8.tgz#6401a10057a22752d21f4e19055508980656aeed" @@ -2507,13 +2531,13 @@ commander@^8.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -compression-webpack-plugin@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-11.1.0.tgz#ee340d2029cf99ccecdea9ad1410b377d15b48b3" - integrity sha512-zDOQYp10+upzLxW+VRSjEpRRwBXJdsb5lBMlRxx1g8hckIFBpe3DTI0en2w7h+beuq89576RVzfiXrkdPGrHhA== +compression-webpack-plugin@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-12.0.0.tgz#6842bb3720407afb0686500411c765c37f37164d" + integrity sha512-LR4mS19Jqq41XfA3xVMLrtzVNzqJbUHdzPeLRfQoLiAS9s87f0021fDuU89xxVQFcB6d20ufBkv4j1rQ4OowHw== dependencies: schema-utils "^4.2.0" - serialize-javascript "^6.0.2" + serialize-javascript "^7.0.3" consola@^3.2.3: version "3.4.2" @@ -3427,10 +3451,10 @@ jest-worker@^30.0.5: merge-stream "^2.0.0" supports-color "^8.1.1" -jquery@>=1.7, jquery@^3.5.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" - integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== +jquery@>=1.7, jquery@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-4.0.0.tgz#95c33ac29005ff72ec444c5ba1cf457e61404fbb" + integrity sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg== js-md5@^0.8.3: version "0.8.3" @@ -4482,13 +4506,6 @@ property-information@^7.0.0: resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.2.0.tgz#0809b34264e995c0bfcd3227028a1e35210af80a" integrity sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg== -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" @@ -4697,11 +4714,6 @@ restructure@^3.0.0: resolved "https://registry.yarnpkg.com/restructure/-/restructure-3.0.2.tgz#e6b2fad214f78edee21797fa8160fef50eb9b49a" integrity sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw== -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -4737,13 +4749,6 @@ semver@^7.3.2, semver@^7.3.4, semver@^7.6.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== -serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - serialize-javascript@^7.0.3: version "7.0.6" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.0.6.tgz#f2f20c8af0757e4d8fa329d0210636da0682ddef" @@ -4906,6 +4911,11 @@ svgo@^4.0.1: picocolors "^1.1.1" sax "^1.5.0" +sweetalert2@^11.26.25: + version "11.26.25" + resolved "https://registry.yarnpkg.com/sweetalert2/-/sweetalert2-11.26.25.tgz#ba24ffb79c67648ac51f834620baa15866a56e35" + integrity sha512-+hunCOJdJ6FLj04T9YSLvvZXRjsvIkTeTKP2e4VF8CaBias961BTnWiSFAy7F/CM5eq3QK2Rraoc5Gzftslvkg== + tagged-tag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/tagged-tag/-/tagged-tag-1.0.0.tgz#a0b5917c2864cba54841495abfa3f6b13edcf4d6"