Compare commits

...

14 commits

Author SHA1 Message Date
Jan Böhmer
36244ec63f Added brite bootswatch theme
Some checks are pending
Build assets artifact / Build assets artifact (push) Waiting to run
Docker Image Build / build (linux/amd64, amd64, ubuntu-latest) (push) Waiting to run
Docker Image Build / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build / merge (push) Blocked by required conditions
Docker Image Build (FrankenPHP) / build (linux/amd64, amd64, ubuntu-latest) (push) Waiting to run
Docker Image Build (FrankenPHP) / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build (FrankenPHP) / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build (FrankenPHP) / merge (push) Blocked by required conditions
Static analysis / Static analysis (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, sqlite) (push) Waiting to run
2026-06-21 16:10:38 +02:00
Jan Böhmer
f45e3a9ef8 Upgraded compression-webpack-plugin 2026-06-21 15:54:09 +02:00
Jan Böhmer
a46f1713fe Use the correct yarn urls 2026-06-21 15:52:52 +02:00
Jan Böhmer
22f23d9c82 Upgraded jquery to 4.0.0 2026-06-21 15:51:22 +02:00
Jan Böhmer
b4cf5b57fa Translate Swal buttons 2026-06-21 15:10:39 +02:00
Jan Böhmer
3491559e9f Migrated register_events from jquery to native JS 2026-06-21 14:52:30 +02:00
Jan Böhmer
b83fc73e18 Remove jquery command from error_handler.js 2026-06-21 14:46:15 +02:00
Jan Böhmer
8c88df4ecf Use upstream version of dataTables.select as the fix was merged 2026-06-21 14:41:56 +02:00
Jan Böhmer
176d5ad2b6 Improved page load error dialog
We now show a more user friendly message
2026-06-21 14:37:40 +02:00
Jan Böhmer
99e56c4b1d Moved alerts and dialogs from unsupported bootbox to Sweetalert2 library 2026-06-21 14:21:01 +02:00
Jan Böhmer
a489380f49 Merge remote-tracking branch 'origin/master' 2026-06-21 12:49:06 +02:00
Jan Böhmer
9127bcf25e Added it and pl translations for password estimator, use lvenshtein distance and block partdb word 2026-06-21 12:49:00 +02:00
Jan Böhmer
c3af73daae Use dictonaries for german and english words for password estimator 2026-06-21 12:37:31 +02:00
Jan Böhmer
7e90f6d707 Updated password strenght estimator to latest version and show crack time estimate as tooltip 2026-06-21 12:33:30 +02:00
36 changed files with 879 additions and 2015 deletions

View file

@ -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) {

View file

@ -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();
}
});

View file

@ -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';

View file

@ -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;
}
});
}

View file

@ -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 <br> 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;
}
});
}
}
}

View file

@ -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;
}
});
}
}
}

View file

@ -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");
}
}
}

View file

@ -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();
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
/**
* Respect the dark mode of Bootstrap 5 set via data-bs-theme="dark" on the <html> 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;
}

44
assets/helpers/swal.js Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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,};

View file

@ -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 {

View file

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 <a href="${url}">${short_location}</a>.<br>`;
msg += '<b>Try to reload the page or contact the administrator if this error persists.</b>';
const title = `${statusText} <small class="text-muted fs-6">(HTTP ${statusCode})</small>`;
const friendlyMsg = userFriendlyMessages[String(statusCode)]
?? 'An unexpected error occurred. Please try again or contact the administrator.';
msg += '<br><br><a class=\"btn btn-outline-secondary mb-2\" data-bs-toggle=\"collapse\" href=\"#iframe_div\" >' + 'View details' + "</a>";
msg += "<div class=\" collapse\" id='iframe_div'><iframe height='512' width='100%' id='error-iframe'></iframe></div>";
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 = `
<p class="mb-3">${friendlyMsg}</p>
<p class="text-muted small mb-3">If this error keeps happening, please contact your administrator.</p>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#swal-error-details" aria-expanded="false">
<i class="fas fa-code me-1"></i>Technical details
</button>
<div class="collapse mt-2" id="swal-error-details">
<iframe height="400" width="100%" id="error-iframe" style="border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);"></iframe>
</div>`;
});
const footer = `<span class="text-muted small">Error while loading: <a href="${location}" class="text-muted text-decoration-none" style="opacity:0.7;">${short_location}</a></span>`;
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: '<i class="fas fa-rotate-right me-1"></i>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();
export default new ErrorHandlerHelper();

File diff suppressed because it is too large Load diff

View file

@ -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();
export default new RegisterEventHelper();

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
.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;
}
import "bootswatch/dist/brite/bootstrap.css";

View file

@ -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

View file

@ -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"
}
}

View file

@ -55,5 +55,23 @@
<target>Jdi!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Zrušit</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Ne</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Kom nu!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Annuller</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Nej</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Los!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Abbrechen</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Nein</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -7,5 +7,23 @@
<target>Αναζήτηση</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Ακύρωση</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Όχι</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,29 @@
<target>Go!</target>
</segment>
</unit>
<unit id="8d38e7538" name="user.password_strength.crack_time">
<segment state="translated">
<source>user.password_strength.crack_time</source>
<target>Estimated time to crack: %time%</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Cancel</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>No</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>¡Vamos!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Cancelar</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>No</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Rechercher!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Annuler</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Non</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Indítás!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Mégse</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Nem</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Cerca!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Annulla</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>No</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -19,5 +19,23 @@
<target>検索</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>キャンセル</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>いいえ</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Ga!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Annuleren</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Nee</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Idź!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Anuluj</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Nie</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Vá!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>OK</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Cancelar</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Não</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Поехали!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>ОК</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Отмена</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Нет</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>Почати!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>ОК</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>Скасувати</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>Ні</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -55,5 +55,23 @@
<target>GO!</target>
</segment>
</unit>
<unit id="dBtnOk01" name="dialog.btn.ok">
<segment state="translated">
<source>dialog.btn.ok</source>
<target>确定</target>
</segment>
</unit>
<unit id="dBtnCcl1" name="dialog.btn.cancel">
<segment state="translated">
<source>dialog.btn.cancel</source>
<target>取消</target>
</segment>
</unit>
<unit id="dBtnDny1" name="dialog.btn.deny">
<segment state="translated">
<source>dialog.btn.deny</source>
<target>否</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -490,7 +490,7 @@ The user will have to set up all two-factor authentication methods again and pri
<unit id="BtSvnI0" name="entity.delete.confirm_title">
<segment state="translated">
<source>entity.delete.confirm_title</source>
<target>You really want to delete %name%?</target>
<target>Do you really want to delete %name%?</target>
</segment>
</unit>
<unit id="WR9DNyZ" name="entity.delete.message">

View file

@ -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'];

124
yarn.lock
View file

@ -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"