diff --git a/assets/controllers/common/dirty_form_controller.js b/assets/controllers/common/dirty_form_controller.js index 8e560a3f..aad2e6b0 100644 --- a/assets/controllers/common/dirty_form_controller.js +++ b/assets/controllers/common/dirty_form_controller.js @@ -19,7 +19,8 @@ import {Controller} from "@hotwired/stimulus"; import {visit} from "@hotwired/turbo"; -import {ConfirmSwal} from "../../helpers/swal"; +import * as bootbox from "bootbox"; +import "../../css/components/bootbox_extensions.css"; import "../../css/components/dirty_form.css"; /** @@ -206,10 +207,11 @@ export default class extends Controller { } _confirmNavigation(onConfirm) { - ConfirmSwal.fire({ - titleText: this.confirmTitleValue, - text: this.confirmMessageValue, - }).then(({isConfirmed}) => { if (isConfirmed) onConfirm(); }); + bootbox.confirm({ + title: this.confirmTitleValue, + message: this.confirmMessageValue, + callback: (result) => { if (result) onConfirm(); } + }); } _handleLinkClick(event) { diff --git a/assets/controllers/elements/collection_type_controller.js b/assets/controllers/elements/collection_type_controller.js index caeb4122..647ed5e5 100644 --- a/assets/controllers/elements/collection_type_controller.js +++ b/assets/controllers/elements/collection_type_controller.js @@ -19,7 +19,8 @@ import {Controller} from "@hotwired/stimulus"; -import {AlertSwal, ConfirmSwal} from "../../helpers/swal"; +import * as bootbox from "bootbox"; +import "../../css/components/bootbox_extensions.css"; import accept from "attr-accept"; export default class extends Controller { @@ -61,7 +62,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."); - AlertSwal.fire({"text": "You do not have the permissions to create a new element. (No protoype element is set)"}); + bootbox.alert("You do not have the permissions to create a new element. (No protoype element is set)"); return; } @@ -225,10 +226,8 @@ export default class extends Controller { } if(this.deleteMessageValue) { - ConfirmSwal.fire({ - text: this.deleteMessageValue, - }).then(({isConfirmed}) => { - if (isConfirmed) { + bootbox.confirm(this.deleteMessageValue, (result) => { + if (result) { del(); } }); diff --git a/assets/controllers/elements/datatables/datatables_controller.js b/assets/controllers/elements/datatables/datatables_controller.js index 4b84a834..d945004b 100644 --- a/assets/controllers/elements/datatables/datatables_controller.js +++ b/assets/controllers/elements/datatables/datatables_controller.js @@ -38,7 +38,9 @@ import 'datatables.net-colreorder-bs5'; import 'datatables.net-responsive-bs5'; import '../../../js/lib/datatables'; -import 'datatables.net-select-bs5'; +//import 'datatables.net-select-bs5'; +//Use the local version containing the fix for the select extension +import '../../../js/lib/dataTables.select.mjs'; 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 cfa386cc..c43fa276 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 {ConfirmSwal} from "../../../helpers/swal"; +import * as bootbox from "bootbox"; /** * This is the datatables controller for parts lists @@ -146,17 +146,15 @@ export default class extends DatatablesController { bubbles: true, //This line is important, otherwise Turbo will not receive the event }); - 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; + 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; + } } }); } diff --git a/assets/controllers/elements/delete_btn_controller.js b/assets/controllers/elements/delete_btn_controller.js index e1b37bcc..9ab15f7d 100644 --- a/assets/controllers/elements/delete_btn_controller.js +++ b/assets/controllers/elements/delete_btn_controller.js @@ -19,7 +19,8 @@ import {Controller} from "@hotwired/stimulus"; -import {ConfirmSwal} from "../../helpers/swal"; +import * as bootbox from "bootbox"; +import "../../css/components/bootbox_extensions.css"; export default class extends Controller { @@ -47,33 +48,32 @@ export default class extends Controller const submitter = event.submitter; const that = this; - 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; + 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; - //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 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 (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 be226517..3d59b492 100644 --- a/assets/controllers/elements/link_confirm_controller.js +++ b/assets/controllers/elements/link_confirm_controller.js @@ -19,7 +19,8 @@ import {Controller} from "@hotwired/stimulus"; -import {ConfirmSwal} from "../../helpers/swal"; +import * as bootbox from "bootbox"; +import "../../css/components/bootbox_extensions.css"; export default class extends Controller { @@ -52,19 +53,20 @@ export default class extends Controller const that = this; - 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; + 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; - //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 9ad2da1c..d9cfbc87 100644 --- a/assets/controllers/elements/password_strength_estimate_controller.js +++ b/assets/controllers/elements/password_strength_estimate_controller.js @@ -19,14 +19,12 @@ import {Controller} from "@hotwired/stimulus"; -import { ZxcvbnFactory } from '@zxcvbn-ts/core'; +import { zxcvbn, zxcvbnOptions } 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' */ @@ -36,8 +34,6 @@ export default class extends Controller { static targets = ["badge", "warning"] - _zxcvbnFactory; - _getTranslations() { //Get the current locale const locale = document.documentElement.lang; @@ -47,10 +43,6 @@ 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 @@ -64,39 +56,34 @@ 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, - ...zxcvbnDePackage.dictionary, - - "partdb": ['part-db', 'partdb', 'part_db', 'part-db-symfony', 'partdb-symfony', 'part_db_symfony'], + //...zxcvbnEnPackage.dictionary, }, translations: this._getTranslations(), }; - - this._zxcvbnFactory = new ZxcvbnFactory(options); + zxcvbnOptions.setOptions(options); //Add event listener to the password input field this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this)); } - async _onPasswordInput() { + _onPasswordInput() { //Retrieve the password const password = this._passwordInput.value; //Estimate the password strength - const result = await this._zxcvbnFactory.checkAsync(password); + const result = zxcvbn(password); //Update the badge this.badgeTarget.parentElement.classList.remove("d-none"); - this._setBadgeToLevel(result.score, result.crackTimes.onlineNoThrottlingXPerSecond.display); + this._setBadgeToLevel(result.score); this.warningTarget.innerHTML = result.feedback.warning; } - _setBadgeToLevel(level, time = null) { + _setBadgeToLevel(level) { let text, classes; switch (level) { @@ -131,11 +118,5 @@ 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 e0b2c4ba..a134bf9b 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 {AlertSwal} from "../../helpers/swal"; +import * as bootbox from "bootbox"; 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 == "") { - AlertSwal.fire({title: this.errorMissingValuesValue}); + bootbox.alert(this.errorMissingValuesValue); return; } if (dia_outer**dia_outer < dia_inner**dia_inner) { - AlertSwal.fire({title: this.errorOuterGreaterInnerValue}); + bootbox.alert(this.errorOuterGreaterInnerValue); return; } @@ -61,12 +61,12 @@ export default class extends Controller { return; } - const parts_per_meter = 1 / (part_distance / 1000); + var parts_per_meter = 1 / (part_distance / 1000); document.getElementById('result_parts_per_meter').textContent = parts_per_meter.toFixed(2) + ' 1/m'; - const parts_amount = (length / 1000) * parts_per_meter; + var parts_amount = (length/1000) * parts_per_meter; - document.getElementById('result_amount').textContent = Math.floor(parts_amount).toString(); + document.getElementById('result_amount').textContent = Math.floor(parts_amount); } -} +} \ No newline at end of file diff --git a/assets/themes/brite.js b/assets/css/components/bootbox_extensions.css similarity index 58% rename from assets/themes/brite.js rename to assets/css/components/bootbox_extensions.css index 41b82e93..42bbd78d 100644 --- a/assets/themes/brite.js +++ b/assets/css/components/bootbox_extensions.css @@ -1,7 +1,7 @@ /* * 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) + * 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 @@ -17,4 +17,30 @@ * along with this program. If not, see . */ -import "bootswatch/dist/brite/bootstrap.css"; +.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 diff --git a/assets/css/components/swal.css b/assets/css/components/swal.css deleted file mode 100644 index 4c2302a9..00000000 --- a/assets/css/components/swal.css +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 deleted file mode 100644 index e370a9ed..00000000 --- a/assets/helpers/swal.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 355fe919..4dd39581 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'; -import $ from 'jquery'; +// Need jQuery? Install it with "yarn add jquery", then uncomment to require it. +const $ = require('jquery'); //Only include javascript import '@fortawesome/fontawesome-free/css/all.css' -import 'bootstrap'; +require('bootstrap'); import "./error_handler"; import "./tab_remember"; import "./register_events"; import "./tristate_checkboxes"; -// Expose jQuery globally so legacy plugins and Bootstrap's jQuery integration -// can find it on window at runtime. -global.$ = global.jQuery = $; +//Define jquery globally +global.$ = global.jQuery = require("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 67695fb9..7f047af9 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 Swal from "../helpers/swal"; +import * as bootbox from "bootbox"; /** * 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,6 +40,21 @@ 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', @@ -68,67 +83,49 @@ class ErrorHandlerHelper { '505': 'HTTP Version Not Supported', }; - 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.', - }; - + //If the statusText is empty, we use the status code as text if (!statusText) { - statusText = httpStatusToText[String(statusCode)] ?? 'Unknown Error'; + statusText = httpStatusToText[statusCode]; } - const title = `${statusText} (HTTP ${statusCode})`; - const friendlyMsg = userFriendlyMessages[String(statusCode)] - ?? 'An unexpected error occurred. Please try again or contact the administrator.'; + //Create error text + const title = statusText + ' (Status ' + statusCode + ')'; - const short_location = location.length > 80 - ? location.substring(0, 80) + '…' - : location; + let trimString = function (string, length) { + return string.length > length ? + string.substring(0, length) + '...' : + string; + }; - const msg = ` -

${friendlyMsg}

-

If this error keeps happening, please contact your administrator.

- -
- -
`; + const short_location = trimString(location, 50); - const footer = `Error while loading: ${short_location}`; + 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.'; - 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(); - } + msg += '

' + 'View details' + ""; + msg += "
"; + + return msg; + }, + title: title, + callback: function () { + //Remove blur + $('#content').removeClass('loading-content'); + } + + }); + + alert.init(function (){ + var dstFrame = document.getElementById('error-iframe'); + //@ts-ignore + var dstDoc = dstFrame.contentDocument || dstFrame.contentWindow.document; + dstDoc.write(responseHTML) + dstDoc.close(); }); } @@ -174,4 +171,4 @@ class ErrorHandlerHelper { } } -export default new ErrorHandlerHelper(); +export default new ErrorHandlerHelper(); \ No newline at end of file diff --git a/assets/js/lib/dataTables.select.mjs b/assets/js/lib/dataTables.select.mjs new file mode 100644 index 00000000..bba97692 --- /dev/null +++ b/assets/js/lib/dataTables.select.mjs @@ -0,0 +1,1538 @@ +/********************* + * 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 1c6ed09b..547742ea 100644 --- a/assets/js/register_events.js +++ b/assets/js/register_events.js @@ -19,14 +19,15 @@ 'use strict'; -import {Dropdown, Modal, Tooltip} from "bootstrap"; +import {Dropdown} 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) { @@ -39,6 +40,8 @@ class RegisterEventHelper { }); this.registerModalDropRemovalOnFormSubmit(); + + } registerModalDropRemovalOnFormSubmit() { @@ -80,17 +83,11 @@ class RegisterEventHelper { registerTooltips() { const handler = () => { - document.querySelectorAll('.tooltip').forEach(el => el.remove()); - + $(".tooltip").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.) - 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'}); - }); + $('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'}); }; this.registerLoadHandler(handler); @@ -98,240 +95,242 @@ class RegisterEventHelper { } registerSpecialCharInput() { - 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 + $("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) ); + + } }); - }); + //@ts-ignore + this.greek_once = true; + }) } } -export default new RegisterEventHelper(); +export default new RegisterEventHelper(); \ No newline at end of file diff --git a/config/parameters.yaml b/config/parameters.yaml index e654a9b5..b1aa5314 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -53,7 +53,6 @@ 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 f906d814..f846f1d1 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": "^4.0.0", + "jquery": "^3.5.1", "popper.js": "^1.14.7", "regenerator-runtime": "^0.14.1", "webpack": "^5.74.0", @@ -38,21 +38,20 @@ "@algolia/autocomplete-theme-classic": "^1.17.0", "@jbtronics/bs-treeview": "^1.0.1", "@part-db/html5-qrcode": "^4.0.0", - "@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", + "@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", "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": "^12.0.0", + "compression-webpack-plugin": "^11.1.0", "datatables.net": "^2.0.0", "datatables.net-bs5": "^2.0.0", "datatables.net-buttons-bs5": "^3.0.0", @@ -70,12 +69,11 @@ "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": "^4.0.0" + "jquery": "^3.5.1" } } diff --git a/translations/frontend.cs.xlf b/translations/frontend.cs.xlf index 6417033b..4ba1f913 100644 --- a/translations/frontend.cs.xlf +++ b/translations/frontend.cs.xlf @@ -55,23 +55,5 @@ 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 817c703f..4b6a15b9 100644 --- a/translations/frontend.da.xlf +++ b/translations/frontend.da.xlf @@ -55,23 +55,5 @@ 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 6a4e7786..9ebd0d32 100644 --- a/translations/frontend.de.xlf +++ b/translations/frontend.de.xlf @@ -55,23 +55,5 @@ 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 fff724d2..bab41358 100644 --- a/translations/frontend.el.xlf +++ b/translations/frontend.el.xlf @@ -7,23 +7,5 @@ Αναζήτηση - - - 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 7f68558a..91617f79 100644 --- a/translations/frontend.en.xlf +++ b/translations/frontend.en.xlf @@ -55,29 +55,5 @@ 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 02c52715..7d339959 100644 --- a/translations/frontend.es.xlf +++ b/translations/frontend.es.xlf @@ -55,23 +55,5 @@ ¡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 2a459052..5ebfca51 100644 --- a/translations/frontend.fr.xlf +++ b/translations/frontend.fr.xlf @@ -55,23 +55,5 @@ 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 7724ce66..c303dedc 100644 --- a/translations/frontend.hu.xlf +++ b/translations/frontend.hu.xlf @@ -55,23 +55,5 @@ 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 c13ab47a..f163e3e2 100644 --- a/translations/frontend.it.xlf +++ b/translations/frontend.it.xlf @@ -55,23 +55,5 @@ 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 d8612e5b..90ffdf5f 100644 --- a/translations/frontend.ja.xlf +++ b/translations/frontend.ja.xlf @@ -19,23 +19,5 @@ 検索 - - - 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 4c90e933..d14f5a81 100644 --- a/translations/frontend.nl.xlf +++ b/translations/frontend.nl.xlf @@ -55,23 +55,5 @@ 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 e748286c..fface684 100644 --- a/translations/frontend.pl.xlf +++ b/translations/frontend.pl.xlf @@ -55,23 +55,5 @@ 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 9053c8c3..fb2f2335 100644 --- a/translations/frontend.pt_BR.xlf +++ b/translations/frontend.pt_BR.xlf @@ -55,23 +55,5 @@ 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 6e1879d8..f4665a74 100644 --- a/translations/frontend.ru.xlf +++ b/translations/frontend.ru.xlf @@ -55,23 +55,5 @@ Поехали! - - - dialog.btn.ok - ОК - - - - - dialog.btn.cancel - Отмена - - - - - dialog.btn.deny - Нет - - diff --git a/translations/frontend.uk.xlf b/translations/frontend.uk.xlf index a28941fa..fee1b03e 100644 --- a/translations/frontend.uk.xlf +++ b/translations/frontend.uk.xlf @@ -55,23 +55,5 @@ Почати! - - - dialog.btn.ok - ОК - - - - - dialog.btn.cancel - Скасувати - - - - - dialog.btn.deny - Ні - - diff --git a/translations/frontend.zh.xlf b/translations/frontend.zh.xlf index 7425b1e8..8bb063b8 100644 --- a/translations/frontend.zh.xlf +++ b/translations/frontend.zh.xlf @@ -55,23 +55,5 @@ GO! - - - dialog.btn.ok - 确定 - - - - - dialog.btn.cancel - 取消 - - - - - dialog.btn.deny - - - diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index fe219869..117e529b 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 - Do you really want to delete %name%? + You really want to delete %name%? diff --git a/webpack.config.js b/webpack.config.js index 259803fa..60ea145f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -119,14 +119,7 @@ Encore // requires WebpackEncoreBundle 1.4 or higher .enableIntegrityHashes(Encore.isProduction()) - // 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') - }) + // uncomment if you're having problems with a jQuery plugin .autoProvidejQuery() @@ -149,7 +142,7 @@ Encore ; //These are all the themes that are available in bootswatch -const AVAILABLE_THEMES = ['bootstrap', 'brite', 'cerulean', 'cosmo', 'cyborg', 'darkly', 'flatly', 'journal', +const AVAILABLE_THEMES = ['bootstrap', '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 7589ce13..19e9716e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2071,66 +2071,37 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -"@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== +"@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== dependencies: fastest-levenshtein "1.0.16" -"@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-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/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-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-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-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-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-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-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" +"@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== acorn-import-phases@^1.0.3: version "1.0.4" @@ -2271,6 +2242,11 @@ 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" @@ -2531,13 +2507,13 @@ commander@^8.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -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== +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== dependencies: schema-utils "^4.2.0" - serialize-javascript "^7.0.3" + serialize-javascript "^6.0.2" consola@^3.2.3: version "3.4.2" @@ -3451,10 +3427,10 @@ jest-worker@^30.0.5: merge-stream "^2.0.0" supports-color "^8.1.1" -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== +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== js-md5@^0.8.3: version "0.8.3" @@ -4506,6 +4482,13 @@ 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" @@ -4714,6 +4697,11 @@ 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" @@ -4749,6 +4737,13 @@ 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" @@ -4911,11 +4906,6 @@ 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"