Skip to content

Commit

Permalink
Release 2.18 (#4878)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinpapst committed Jun 16, 2024
1 parent 8792a1d commit 987b46b
Show file tree
Hide file tree
Showing 46 changed files with 1,800 additions and 846 deletions.
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

$fixer = new PhpCsFixer\Config();
$fixer
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
->setRiskyAllowed(true)
->setRules([
'encoding' => true,
Expand Down
2 changes: 2 additions & 0 deletions assets/js/KimaiLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import KimaiNotification from "./plugins/KimaiNotification";
import KimaiHotkeys from "./plugins/KimaiHotkeys";
import KimaiRemoteModal from "./plugins/KimaiRemoteModal";
import KimaiUser from "./plugins/KimaiUser";
import KimaiAutocompleteTags from "./forms/KimaiAutocompleteTags";

export default class KimaiLoader {

Expand Down Expand Up @@ -70,6 +71,7 @@ export default class KimaiLoader {
kimai.registerPlugin(new KimaiDateRangePicker('input[data-daterangepicker="on"]'));
kimai.registerPlugin(new KimaiDatePicker('input[data-datepicker="on"]'));
kimai.registerPlugin(new KimaiAutocomplete());
kimai.registerPlugin(new KimaiAutocompleteTags());
kimai.registerPlugin(new KimaiTimesheetForm());
kimai.registerPlugin(new KimaiTeamForm());
kimai.registerPlugin(new KimaiCopyDataForm());
Expand Down
64 changes: 30 additions & 34 deletions assets/js/forms/KimaiAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
*/

import TomSelect from 'tom-select';
import KimaiFormPlugin from "./KimaiFormPlugin";
import KimaiFormTomselectPlugin from "./KimaiFormTomselectPlugin";

/**
* Supporting auto-complete fields via API.
* Used for timesheet tagging in toolbar and edit dialogs.
*/
export default class KimaiAutocomplete extends KimaiFormPlugin {
export default class KimaiAutocomplete extends KimaiFormTomselectPlugin {

init()
{
Expand All @@ -28,19 +27,31 @@ export default class KimaiAutocomplete extends KimaiFormPlugin {
return true;
}

activateForm(form)
{
loadData(apiUrl, query, callback) {
/** @type {KimaiAPI} API */
const API = this.getContainer().getPlugin('api');

API.get(apiUrl, {'name': query}, (data) => {
let results = [];
for (let item of data) {
results.push({text: item.name, value: item.name});
}
callback(results);
}, () => {
callback();
});
}

activateForm(form)
{
[].slice.call(form.querySelectorAll(this.selector)).map((node) => {
const apiUrl = node.dataset['autocompleteUrl'];
let minChars = 3;
if (node.dataset['minimumCharacter'] !== undefined) {
minChars = parseInt(node.dataset['minimumCharacter']);
}

new TomSelect(node, {
let options = {
// see https://github.com/orchidjs/tom-select/issues/543#issuecomment-1664342257
onItemAdd: function(){
// remove remaining characters from input after selecting an item
Expand All @@ -58,36 +69,21 @@ export default class KimaiAutocomplete extends KimaiFormPlugin {
return query.length >= minChars;
},
load: (query, callback) => {
API.get(apiUrl, {'name': query}, (data) => {
const results = [].slice.call(data).map((result) => {
return {text: result, value: result};
});
callback(results);
}, () => {
callback();
});
this.loadData(apiUrl, query, callback);
},
render: {
// eslint-disable-next-line
not_loading: (data, escape) => {
// no default content
},
option_create: (data, escape) => {
const name = escape(data.input);
if (name.length < 3) {
return null;
}
const tpl = this.translate('select.search.create');
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>')
return '<div class="create">' + tplReplaced + '</div>';
},
no_results: (data, escape) => {
const tpl = this.translate('select.search.notfound');
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>')
return '<div class="no-results">' + tplReplaced + '</div>';
},
};

let render = {
// eslint-disable-next-line
not_loading: (data, escape) => {
// no default content
},
});
};

const rendererType = (node.dataset['renderer'] !== undefined) ? node.dataset['renderer'] : 'default';
options.render = {...render, ...this.getRenderer(rendererType)};

new TomSelect(node, options);
});
}

Expand Down
34 changes: 34 additions & 0 deletions assets/js/forms/KimaiAutocompleteTags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import KimaiAutocomplete from "./KimaiAutocomplete";

/**
* Used for timesheet tagging in toolbar and edit dialogs.
*/
export default class KimaiAutocompleteTags extends KimaiAutocomplete {

init()
{
this.selector = '[data-form-widget="tags"]';
}

loadData(apiUrl, query, callback) {
/** @type {KimaiAPI} API */
const API = this.getContainer().getPlugin('api');

API.get(apiUrl, {'name': query}, (data) => {
let results = [];
for (let item of data) {
results.push({text: item.name, value: item.name, color: item['color-safe']});
}
callback(results);
}, () => {
callback();
});
}
}
60 changes: 6 additions & 54 deletions assets/js/forms/KimaiFormSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
*/

import TomSelect from 'tom-select';
import KimaiFormPlugin from "./KimaiFormPlugin";
import KimaiFormTomselectPlugin from "./KimaiFormTomselectPlugin";

export default class KimaiFormSelect extends KimaiFormPlugin {
export default class KimaiFormSelect extends KimaiFormTomselectPlugin {

constructor(selector, apiSelects)
{
Expand Down Expand Up @@ -82,30 +82,20 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
plugins: plugins,
// if there are more than X entries, the other ones are hidden and can only be found
// by typing some characters to trigger the internal option search
// see App\Form\Type\TagsType::MAX_AMOUNT_SELECT
maxOptions: 500,
sortField:[{field: '$order'}, {field: '$score'}],
};

let render = {
option_create: (data, escape) => {
const name = escape(data.input);
if (name.length < 3) {
return null;
}
const tpl = this.translate('select.search.create');
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>');
return '<div class="create">' + tplReplaced + '</div>';
},
no_results: (data, escape) => {
const tpl = this.translate('select.search.notfound');
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>');
return '<div class="no-results">' + tplReplaced + '</div>';
},
onOptionAdd: (value) => {
node.dispatchEvent(new CustomEvent('create', {detail: {'value': value}}));
},
};

const rendererType = (node.dataset['renderer'] !== undefined) ? node.dataset['renderer'] : 'default';
options.render = {...render, ...this.getRenderer(rendererType)};

if (node.dataset['create'] !== undefined) {
options = {...options, ...{
persist: true,
Expand All @@ -124,44 +114,6 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
}};
}

if (node.dataset['renderer'] !== undefined && node.dataset['renderer'] === 'color') {
options.render = {...render, ...{
option: function(data, escape) {
let item = '<div class="list-group-item border-0 p-1 ps-2 text-nowrap">';
if (data.color !== undefined) {
item += '<span style="background-color:' + data.color + '" class="color-choice-item">&nbsp;</span>';
} else {
item += '<span class="color-choice-item">&nbsp;</span>';
}
item += escape(data.text) + '</div>';
return item;
},
item: function(data, escape) {
let item = '<div class="text-nowrap">';
if (data.color !== undefined) {
item += '<span style="background-color:' + data.color + '" class="color-choice-item">&nbsp;</span>';
} else {
item += '<span class="color-choice-item">&nbsp;</span>';
}
item += escape(data.text) + '</div>';
return item;
}
}};
} else {
options.render = {...render, ...{
// the empty entry would collapse and only show as a tiny 5px line if there is no content inside
option: function(data, escape) {
let text = data.text;
if (text === null || text.trim() === '') {
text = '&nbsp;';
} else {
text = escape(text);
}
return '<div>' + text + '</div>';
}
}};
}

const select = new TomSelect(node, options);
node.addEventListener('data-reloaded', (event) => {
select.clear(true);
Expand Down
80 changes: 80 additions & 0 deletions assets/js/forms/KimaiFormTomselectPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/*!
* [KIMAI] KimaiFormPlugin: base class for all none ID plugin that handle forms
*/

import KimaiFormPlugin from './KimaiFormPlugin';

export default class KimaiFormTomselectPlugin extends KimaiFormPlugin {

/**
* @param {string} rendererType
* @return array
*/
getRenderer(rendererType)
{
// default renderer

let render = {
option_create: (data, escape) => {
const name = escape(data.input);
if (name.length < 3) {
return null;
}
const tpl = this.translate('select.search.create');
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>')
return '<div class="create">' + tplReplaced + '</div>';
},
no_results: (data, escape) => {
const tpl = this.translate('select.search.notfound');
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>')
return '<div class="no-results">' + tplReplaced + '</div>';
},
};

if (rendererType === 'color') {
render = {...render, ...{
option: function(data, escape) {
let item = '<div class="list-group-item border-0 p-1 ps-2 text-nowrap">';
// if no color is set, do NOT add an empty placeholder
if (data.color !== undefined) {
item += '<span style="background-color:' + data.color + '" class="color-choice-item">&nbsp;</span>';
}
item += escape(data.text) + '</div>';
return item;
},
item: function(data, escape) {
let item = '<div class="text-nowrap">';
// if no color is set, do NOT add an empty placeholder
if (data.color !== undefined) {
item += '<span style="background-color:' + data.color + '" class="color-choice-item">&nbsp;</span>';
}
item += escape(data.text) + '</div>';
return item;
}
}};
} else {
render = {...render, ...{
// the empty entry would collapse and only show as a tiny 5px line if there is no content inside
option: function(data, escape) {
let text = data.text;
if (text === null || text.trim() === '') {
text = '&nbsp;';
} else {
text = escape(text);
}
return '<div>' + text + '</div>';
}
}};
}

return render;
}

}
Loading

0 comments on commit 987b46b

Please sign in to comment.