Merge branch 'master' into settings-bundle

This commit is contained in:
Jan Böhmer 2025-06-15 18:39:49 +02:00
commit 442457f11b
131 changed files with 12759 additions and 6750 deletions

View file

@ -128,6 +128,8 @@ const PLACEHOLDERS = [
['[[BARCODE_QR]]', 'QR code linking to this element'],
['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'],
['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'],
['[[BARCODE_C93]]', 'Code 93 barcode linking to this element'],
['[[BARCODE_DATAMATRIX]]', 'Datamatrix code linking to this element'],
]
},
{

View file

@ -69,6 +69,8 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
'QR code linking to this element': 'QR Code verknüpft mit diesem Element',
'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element',
'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element',
'Code 93 barcode linking to this element': 'Code 93 Barcode verknüpft mit diesem Element',
'Datamatrix code linking to this element': 'Datamatrix Code verknüpft mit diesem Element',
'Location ID': 'Lagerort ID',
'Name': 'Name',

View file

@ -23,6 +23,12 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller {
_tomSelect;
@ -46,6 +52,12 @@ export default class extends Controller {
}
return '<div>' + escape(data.label) + '</div>';
}
},
plugins: {
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
"restore_on_backspace": {}
}
};

View file

@ -23,6 +23,12 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
/**
* This is the frontend controller for StaticFileAutocompleteType form element.
* Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete.
@ -46,7 +52,13 @@ export default class extends Controller {
orderField: 'text',
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING'
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING',
plugins: {
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
'restore_on_backspace': {}
}
};
if (this.element.dataset.url) {

View file

@ -24,6 +24,9 @@ import {Controller} from "@hotwired/stimulus";
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller {
_tomSelect;
@ -37,11 +40,15 @@ export default class extends Controller {
const allowAdd = this.element.getAttribute("data-allow-add") === "true";
const addHint = this.element.getAttribute("data-add-hint") ?? "";
let settings = {
allowEmptyOption: true,
selectOnTab: true,
maxOptions: null,
create: allowAdd ? this.createItem.bind(this) : false,
createFilter: this.createFilter.bind(this),
// This three options allow us to paste element names with commas: (see issue #538)
maxItems: 1,
@ -81,8 +88,17 @@ export default class extends Controller {
//Add callbacks to update validity
onInitialize: this.updateValidity.bind(this),
onChange: this.updateValidity.bind(this),
plugins: {
"autoselect_typed": {},
}
};
//Add clear button plugin, if an empty option is present
if (this.element.querySelector("option[value='']") !== null) {
settings.plugins["clear_button"] = {};
}
this._tomSelect = new TomSelect(this.element, settings);
//Do not do a sync here as this breaks the initial rendering of the empty option
//this._tomSelect.sync();
@ -113,6 +129,31 @@ export default class extends Controller {
});
}
createFilter(input) {
//Normalize the input (replace spacing around arrows)
if (input.includes("->")) {
const inputs = input.split("->");
inputs.forEach((value, index) => {
inputs[index] = value.trim();
});
input = inputs.join("->");
} else {
input = input.trim();
}
const options = this._tomSelect.options;
//Iterate over all options and check if the input is already present
for (let index in options) {
const option = options[index];
if (option.path === input) {
return false;
}
}
return true;
}
updateValidity() {
//Mark this input as invalid, if the selected option is disabled

View file

@ -23,14 +23,21 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller {
_tomSelect;
connect() {
let settings = {
plugins: {
remove_button:{
}
remove_button:{},
'autoselect_typed': {},
'click_to_edit': {},
},
persistent: false,
selectOnTab: true,

View file

@ -25,9 +25,20 @@ import "katex/dist/katex.css";
export default class extends Controller {
static targets = ["input", "preview"];
static values = {
unit: {type: Boolean, default: false} //Render as upstanding (non-italic) text, useful for units
}
updatePreview()
{
katex.render(this.inputTarget.value, this.previewTarget, {
let value = "";
if (this.unitValue) {
value = "\\mathrm{" + this.inputTarget.value + "}";
} else {
value = this.inputTarget.value;
}
katex.render(value, this.previewTarget, {
throwOnError: false,
});
}

View file

@ -22,6 +22,13 @@ import TomSelect from "tom-select";
import katex from "katex";
import "katex/dist/katex.css";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
/* stimulusFetch: 'lazy' */
export default class extends Controller
{
@ -53,7 +60,10 @@ export default class extends Controller
connect() {
const settings = {
plugins: {
clear_button:{}
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
'restore_on_backspace': {}
},
persistent: false,
maxItems: 1,

View file

@ -111,4 +111,11 @@ ul.structural_link li a:hover {
.permission-checkbox:checked {
background-color: var(--bs-success);
border-color: var(--bs-success);
}
/***********************************************
* Katex rendering with same height as text
***********************************************/
.katex-same-height-as-text .katex {
font-size: 1.0em;
}

View file

@ -0,0 +1,63 @@
/**
* Autoselect Typed plugin for Tomselect
*
* This plugin allows automatically selecting an option matching the typed text when the Tomselect element goes out of
* focus (is blurred) and/or when the delimiter is typed.
*
* #select_on_blur option
* Tomselect natively supports the "createOnBlur" option. This option picks up any remaining text in the input field
* and uses it to create a new option and selects that option. It does behave a bit strangely though, in that it will
* not select an already existing option when the input is blurred, so if you typed something that matches an option in
* the list and then click outside the box (without pressing enter) the entered text is just removed (unless you have
* allow duplicates on in which case it will create a new option).
* This plugin fixes that, such that Tomselect will first try to select an option matching the remaining uncommitted
* text and only when no matching option is found tries to create a new one (if createOnBlur and create is on)
*
* #select_on_delimiter option
* Normally when typing the delimiter (space by default) Tomselect will try to create a new option (and select it) (if
* create is on), but if the typed text matches an option (and allow duplicates is off) it refuses to react at all until
* you press enter. With this option, the delimiter will also allow selecting an option, not just creating it.
*/
function select_current_input(self){
if(self.isLocked){
return
}
const val = self.inputValue()
//Do nothing if the input is empty
if (!val) {
return
}
if (self.options[val]) {
self.addItem(val)
self.setTextboxValue()
}
}
export default function(plugin_options_) {
const plugin_options = Object.assign({
//Autoselect the typed text when the input element goes out of focus
select_on_blur: true,
//Autoselect the typed text when the delimiter is typed
select_on_delimiter: true,
}, plugin_options_);
const self = this
if(plugin_options.select_on_blur) {
this.hook("before", "onBlur", function () {
select_current_input(self)
})
}
if(plugin_options.select_on_delimiter) {
this.hook("before", "onKeyPress", function (e) {
const character = String.fromCharCode(e.keyCode || e.which);
if (self.settings.mode === 'multi' && character === self.settings.delimiter) {
select_current_input(self)
}
})
}
}

View file

@ -0,0 +1,93 @@
/**
* click_to_edit plugin for Tomselect
*
* This plugin allows editing (and selecting text in) any selected item by clicking it.
*
* Usually, when the user typed some text and created an item in Tomselect that item cannot be edited anymore. To make
* a change, the item has to be deleted and retyped completely. There is also generally no way to copy text out of a
* tomselect item. The "restore_on_backspace" plugin improves that somewhat, by allowing the user to edit an item after
* pressing backspace. However, it is somewhat confusing to first have to focus the field an then hit backspace in order
* to copy a piece of text. It may also not be immediately obvious for editing.
* This plugin transforms an item into editable text when it is clicked, e.g. when the user tries to place the caret
* within an item or when they try to drag across the text to highlight it.
* It also plays nice with the remove_button plugin which still removes (deselects) an option entirely.
*
* It is recommended to also enable the autoselect_typed plugin when using this plugin. Without it, the text in the
* input field (i.e. the item that was just clicked) is lost when the user clicks outside the field. Also, when the user
* clicks an option (making it text) and then tries to enter another one by entering the delimiter (e.g. space) nothing
* happens until enter is pressed or the text is changed from what it was.
*/
/**
* Return a dom element from either a dom query string, jQuery object, a dom element or html string
* https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
*
* param query should be {}
*/
const getDom = query => {
if (query.jquery) {
return query[0];
}
if (query instanceof HTMLElement) {
return query;
}
if (isHtmlString(query)) {
var tpl = document.createElement('template');
tpl.innerHTML = query.trim(); // Never return a text node of whitespace as the result
return tpl.content.firstChild;
}
return document.querySelector(query);
};
const isHtmlString = arg => {
if (typeof arg === 'string' && arg.indexOf('<') > -1) {
return true;
}
return false;
};
function plugin(plugin_options_) {
const self = this
const plugin_options = Object.assign({
//If there is unsubmitted text in the input field, should that text be automatically used to select a matching
//element? If this is off, clicking on item1 and then clicking on item2 will result in item1 being deselected
auto_select_before_edit: true,
//If there is unsubmitted text in the input field, should that text be automatically used to create a matching
//element if no matching element was found or auto_select_before_edit is off?
auto_create_before_edit: true,
//customize this function to change which text the item is replaced with when clicking on it
text: option => {
return option[self.settings.labelField];
}
}, plugin_options_);
self.hook('after', 'setupTemplates', () => {
const orig_render_item = self.settings.render.item;
self.settings.render.item = (data, escape) => {
const item = getDom(orig_render_item.call(self, data, escape));
item.addEventListener('click', evt => {
if (self.isLocked) {
return;
}
const val = self.inputValue();
if (self.options[val]) {
self.addItem(val)
} else if (self.settings.create) {
self.createItem();
}
const option = self.options[item.dataset.value]
self.setTextboxValue(plugin_options.text.call(self, option));
self.focus();
self.removeItem(item);
}
);
return item;
}
});
}
export { plugin as default };