mirror of
https://github.com/getgrav/grav-plugin-admin.git
synced 2025-10-26 07:46:31 +01:00
List field items will now require confirmation before getting deleted
This commit is contained in:
@@ -3,259 +3,306 @@ import Sortable from 'sortablejs';
|
||||
import '../../utils/jquery-utils';
|
||||
|
||||
export default class CollectionsField {
|
||||
constructor() {
|
||||
this.lists = $();
|
||||
constructor() {
|
||||
this.lists = $();
|
||||
|
||||
$('[data-type="collection"]').each((index, list) => this.addList(list));
|
||||
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
|
||||
const body = $('body');
|
||||
$('[data-type="collection"]').each((index, list) => this.addList(list));
|
||||
body.on('mutation._grav', this._onAddedNodes.bind(this));
|
||||
body.on('click', (event) => {
|
||||
const target = $(event.target);
|
||||
if (!(target.is('[data-action="confirm"], [data-action="delete"]') || target.closest('[data-action="confirm"], [data-action="delete"]').length)) {
|
||||
CollectionsField.closeConfirmations();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
addList(list) {
|
||||
list = $(list);
|
||||
this.lists = this.lists.add(list);
|
||||
|
||||
list.on('click', '> .collection-actions [data-action="add"]', (event) => this.addItem(event));
|
||||
list.on('click', '> ul > li > .item-actions [data-action="confirm"]', (event) => this.confirmRemove(event));
|
||||
list.on('click', '> ul > li > .item-actions [data-action="delete"]', (event) => this.removeItem(event));
|
||||
list.on('click', '> ul > li > .item-actions [data-action="collapse"]', (event) => this.collapseItem(event));
|
||||
list.on('click', '> ul > li > .item-actions [data-action="expand"]', (event) => this.expandItem(event));
|
||||
list.on('click', '> .collection-actions [data-action-sort="date"]', (event) => this.sortItems(event));
|
||||
list.on('click', '> .collection-actions [data-action="collapse_all"]', (event) => this.collapseItems(event));
|
||||
list.on('click', '> .collection-actions [data-action="expand_all"]', (event) => this.expandItems(event));
|
||||
list.on('input change', '[data-key-observe]', (event) => this.observeKey(event));
|
||||
|
||||
list.find('[data-collection-holder]').each((index, container) => {
|
||||
container = $(container);
|
||||
if (container.data('collection-sort') || container[0].hasAttribute('data-collection-nosort')) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.data('collection-sort', new Sortable(container.get(0), {
|
||||
forceFallback: false,
|
||||
handle: '.collection-sort',
|
||||
animation: 150,
|
||||
onUpdate: () => this.reindex(container)
|
||||
}));
|
||||
});
|
||||
|
||||
this._updateActionsStateBasedOnMinMax(list);
|
||||
}
|
||||
|
||||
addItem(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let position = button.data('action-add') || 'bottom';
|
||||
let list = $(button.closest('[data-type="collection"]'));
|
||||
let template = $(list.find('> [data-collection-template="new"]').data('collection-template-html'));
|
||||
|
||||
this._updateActionsStateBasedOnMinMax(list);
|
||||
let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let maxItems = list.data('max');
|
||||
if (typeof maxItems !== 'undefined' && items.length >= maxItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
addList(list) {
|
||||
list = $(list);
|
||||
this.lists = this.lists.add(list);
|
||||
list.find('> [data-collection-holder]')[position === 'top'
|
||||
? 'prepend'
|
||||
: 'append'](template);
|
||||
this.reindex(list);
|
||||
|
||||
list.on('click', '> .collection-actions [data-action="add"]', (event) => this.addItem(event));
|
||||
list.on('click', '> ul > li > .item-actions [data-action="delete"]', (event) => this.removeItem(event));
|
||||
list.on('click', '> ul > li > .item-actions [data-action="collapse"]', (event) => this.collapseItem(event));
|
||||
list.on('click', '> ul > li > .item-actions [data-action="expand"]', (event) => this.expandItem(event));
|
||||
list.on('click', '> .collection-actions [data-action-sort="date"]', (event) => this.sortItems(event));
|
||||
list.on('click', '> .collection-actions [data-action="collapse_all"]', (event) => this.collapseItems(event));
|
||||
list.on('click', '> .collection-actions [data-action="expand_all"]', (event) => this.expandItems(event));
|
||||
list.on('input change', '[data-key-observe]', (event) => this.observeKey(event));
|
||||
items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]');
|
||||
let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]');
|
||||
|
||||
list.find('[data-collection-holder]').each((index, container) => {
|
||||
container = $(container);
|
||||
if (container.data('collection-sort') || container[0].hasAttribute('data-collection-nosort')) { return; }
|
||||
|
||||
container.data('collection-sort', new Sortable(container.get(0), {
|
||||
forceFallback: false,
|
||||
handle: '.collection-sort',
|
||||
animation: 150,
|
||||
onUpdate: () => this.reindex(container)
|
||||
}));
|
||||
});
|
||||
|
||||
this._updateActionsStateBasedOnMinMax(list);
|
||||
if (items.length) {
|
||||
if (topAction.length) {
|
||||
topAction.parent().removeClass('hidden');
|
||||
}
|
||||
if (sortAction.length && items.length > 1) {
|
||||
sortAction.removeClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
addItem(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let position = button.data('action-add') || 'bottom';
|
||||
let list = $(button.closest('[data-type="collection"]'));
|
||||
let template = $(list.find('> [data-collection-template="new"]').data('collection-template-html'));
|
||||
// refresh toggleables in a list
|
||||
$('[data-grav-field="toggleable"] input[type="checkbox"]').trigger('change');
|
||||
}
|
||||
|
||||
this._updateActionsStateBasedOnMinMax(list);
|
||||
let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let maxItems = list.data('max');
|
||||
if (typeof maxItems !== 'undefined' && items.length >= maxItems) {
|
||||
static closeConfirmations() {
|
||||
$('.list-confirm-deletion[data-action="delete"]').addClass('hidden');
|
||||
}
|
||||
|
||||
confirmRemove(event) {
|
||||
CollectionsField.closeConfirmations();
|
||||
|
||||
const button = $(event.currentTarget);
|
||||
const list = $(button.closest('.item-actions'));
|
||||
list.find('.list-confirm-deletion[data-action="delete"]').removeClass('hidden');
|
||||
}
|
||||
|
||||
removeItem(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let item = button.closest('[data-collection-item]');
|
||||
let list = $(button.closest('[data-type="collection"]'));
|
||||
|
||||
let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let minItems = list.data('min');
|
||||
|
||||
if (typeof minItems !== 'undefined' && items.length <= minItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.remove();
|
||||
this.reindex(list);
|
||||
|
||||
items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]');
|
||||
let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]');
|
||||
|
||||
if (!items.length) {
|
||||
if (topAction.length) {
|
||||
topAction.parent().addClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (sortAction.length && items.length <= 1) {
|
||||
sortAction.addClass('hidden');
|
||||
}
|
||||
this._updateActionsStateBasedOnMinMax(list);
|
||||
}
|
||||
|
||||
collapseItems(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="collapse"]');
|
||||
|
||||
items.click();
|
||||
}
|
||||
|
||||
collapseItem(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let item = button.closest('[data-collection-item]');
|
||||
|
||||
button.attr('data-action', 'expand').removeClass('fa-chevron-circle-down').addClass('fa-chevron-circle-right');
|
||||
item.addClass('collection-collapsed');
|
||||
}
|
||||
|
||||
expandItems(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="expand"]');
|
||||
|
||||
items.click();
|
||||
}
|
||||
|
||||
expandItem(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let item = button.closest('[data-collection-item]');
|
||||
|
||||
button.attr('data-action', 'collapse').removeClass('fa-chevron-circle-right').addClass('fa-chevron-circle-down');
|
||||
item.removeClass('collection-collapsed');
|
||||
}
|
||||
|
||||
sortItems(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let sortby = button.data('action-sort');
|
||||
let sortby_dir = button.data('action-sort-dir') || 'asc';
|
||||
let list = $(button.closest('[data-type="collection"]'));
|
||||
let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
|
||||
items.sort((a, b) => {
|
||||
let A = $(a).find('[name$="[' + sortby + ']"]');
|
||||
let B = $(b).find('[name$="[' + sortby + ']"]');
|
||||
let sort;
|
||||
|
||||
if (sortby_dir === 'asc') {
|
||||
sort = (A.val() < B.val())
|
||||
? -1
|
||||
: (A.val() > B.val())
|
||||
? 1
|
||||
: 0;
|
||||
} else {
|
||||
sort = (A.val() > B.val())
|
||||
? -1
|
||||
: (A.val() < B.val())
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
return sort;
|
||||
}).each((_, container) => {
|
||||
$(container).parent().append(container);
|
||||
});
|
||||
|
||||
this.reindex(list);
|
||||
}
|
||||
|
||||
observeKey(event) {
|
||||
let input = $(event.target);
|
||||
let value = input.val();
|
||||
let item = input.closest('[data-collection-key]');
|
||||
|
||||
item.data('collection-key-backup', item.data('collection-key')).data('collection-key', value);
|
||||
this.reindex(null, item);
|
||||
}
|
||||
|
||||
reindex(list, items) {
|
||||
items = items || $(list).closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
|
||||
items.each((index, item) => {
|
||||
item = $(item);
|
||||
|
||||
let observed = item.find('[data-key-observe]');
|
||||
let observedValue = observed.val();
|
||||
let hasCustomKey = observed.length;
|
||||
let currentKey = item.data('collection-key-backup');
|
||||
|
||||
item.attr('data-collection-key', hasCustomKey
|
||||
? observedValue
|
||||
: index);
|
||||
|
||||
['name', 'data-grav-field-name', 'for', 'id', 'data-grav-file-settings', 'data-file-post-add', 'data-file-post-remove', 'data-grav-array-name'].forEach((prop) => {
|
||||
item.find('[' + prop + '], [_' + prop + ']').each(function() {
|
||||
let element = $(this);
|
||||
let indexes = [];
|
||||
let array_index = null;
|
||||
let regexps = [
|
||||
new RegExp('\\[(\\d+|\\*|' + currentKey + ')\\]', 'g'),
|
||||
new RegExp('\\.(\\d+|\\*|' + currentKey + ')\\.', 'g')
|
||||
];
|
||||
|
||||
// special case to preserve array field index keys
|
||||
if (prop === 'name' && element.data('gravArrayType')) {
|
||||
const match_index = element.attr(prop).match(/\[[0-9]{1,}\]$/);
|
||||
const pattern = element[0].closest('[data-grav-array-name]').dataset.gravArrayName;
|
||||
if (match_index && pattern) {
|
||||
array_index = match_index[0];
|
||||
element.attr(prop, `${pattern}${match_index[0]}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCustomKey && !observedValue) {
|
||||
element.attr(`_${prop}`, element.attr(prop));
|
||||
element.attr(prop, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
list.find('> [data-collection-holder]')[position === 'top' ? 'prepend' : 'append'](template);
|
||||
this.reindex(list);
|
||||
if (element.attr(`_${prop}`)) {
|
||||
element.attr(prop, element.attr(`_${prop}`));
|
||||
element.attr(`_${prop}`, null);
|
||||
}
|
||||
|
||||
items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]');
|
||||
let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]');
|
||||
element.parents('[data-collection-key]').map((idx, parent) => indexes.push($(parent).attr('data-collection-key')));
|
||||
indexes.reverse();
|
||||
|
||||
if (items.length) {
|
||||
if (topAction.length) { topAction.parent().removeClass('hidden'); }
|
||||
if (sortAction.length && items.length > 1) { sortAction.removeClass('hidden'); }
|
||||
}
|
||||
|
||||
// refresh toggleables in a list
|
||||
$('[data-grav-field="toggleable"] input[type="checkbox"]').trigger('change');
|
||||
}
|
||||
|
||||
removeItem(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let item = button.closest('[data-collection-item]');
|
||||
let list = $(button.closest('[data-type="collection"]'));
|
||||
|
||||
let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let minItems = list.data('min');
|
||||
|
||||
if (typeof minItems !== 'undefined' && items.length <= minItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.remove();
|
||||
this.reindex(list);
|
||||
|
||||
items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]');
|
||||
let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]');
|
||||
|
||||
if (!items.length) {
|
||||
if (topAction.length) { topAction.parent().addClass('hidden'); }
|
||||
}
|
||||
|
||||
if (sortAction.length && items.length <= 1) { sortAction.addClass('hidden'); }
|
||||
this._updateActionsStateBasedOnMinMax(list);
|
||||
}
|
||||
|
||||
collapseItems(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="collapse"]');
|
||||
|
||||
items.click();
|
||||
}
|
||||
|
||||
collapseItem(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let item = button.closest('[data-collection-item]');
|
||||
|
||||
button.attr('data-action', 'expand').removeClass('fa-chevron-circle-down').addClass('fa-chevron-circle-right');
|
||||
item.addClass('collection-collapsed');
|
||||
}
|
||||
|
||||
expandItems(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="expand"]');
|
||||
|
||||
items.click();
|
||||
}
|
||||
|
||||
expandItem(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let item = button.closest('[data-collection-item]');
|
||||
|
||||
button.attr('data-action', 'collapse').removeClass('fa-chevron-circle-right').addClass('fa-chevron-circle-down');
|
||||
item.removeClass('collection-collapsed');
|
||||
}
|
||||
|
||||
sortItems(event) {
|
||||
let button = $(event.currentTarget);
|
||||
let sortby = button.data('action-sort');
|
||||
let sortby_dir = button.data('action-sort-dir') || 'asc';
|
||||
let list = $(button.closest('[data-type="collection"]'));
|
||||
let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
|
||||
items.sort((a, b) => {
|
||||
let A = $(a).find('[name$="[' + sortby + ']"]');
|
||||
let B = $(b).find('[name$="[' + sortby + ']"]');
|
||||
let sort;
|
||||
|
||||
if (sortby_dir === 'asc') {
|
||||
sort = (A.val() < B.val()) ? -1 : (A.val() > B.val()) ? 1 : 0;
|
||||
} else {
|
||||
sort = (A.val() > B.val()) ? -1 : (A.val() < B.val()) ? 1 : 0;
|
||||
let matchedKey = currentKey;
|
||||
let replaced = element.attr(prop).replace(regexps[0], (/* str, p1, offset */) => {
|
||||
let extras = '';
|
||||
if (array_index) {
|
||||
extras = array_index;
|
||||
console.log(indexes, extras);
|
||||
}
|
||||
|
||||
return sort;
|
||||
}).each((_, container) => {
|
||||
$(container).parent().append(container);
|
||||
matchedKey = indexes.shift() || matchedKey;
|
||||
return `[${matchedKey}]${extras}`;
|
||||
});
|
||||
|
||||
replaced = replaced.replace(regexps[1], (/* str, p1, offset */) => {
|
||||
matchedKey = indexes.shift() || matchedKey;
|
||||
return `.${matchedKey}.`;
|
||||
});
|
||||
|
||||
element.attr(prop, replaced);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.reindex(list);
|
||||
_onAddedNodes(event, target/* , record, instance */) {
|
||||
let collections = $(target).find('[data-type="collection"]');
|
||||
if (!collections.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
observeKey(event) {
|
||||
let input = $(event.target);
|
||||
let value = input.val();
|
||||
let item = input.closest('[data-collection-key]');
|
||||
collections.each((index, collection) => {
|
||||
collection = $(collection);
|
||||
if (!~this.lists.index(collection)) {
|
||||
this.addList(collection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
item.data('collection-key-backup', item.data('collection-key')).data('collection-key', value);
|
||||
this.reindex(null, item);
|
||||
_updateActionsStateBasedOnMinMax(list) {
|
||||
let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let minItems = list.data('min');
|
||||
let maxItems = list.data('max');
|
||||
|
||||
list.find('> .collection-actions [data-action="add"]').attr('disabled', false);
|
||||
list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', false);
|
||||
|
||||
if (typeof minItems !== 'undefined' && items.length <= minItems) {
|
||||
list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', true);
|
||||
}
|
||||
|
||||
reindex(list, items) {
|
||||
items = items || $(list).closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
|
||||
items.each((index, item) => {
|
||||
item = $(item);
|
||||
|
||||
let observed = item.find('[data-key-observe]');
|
||||
let observedValue = observed.val();
|
||||
let hasCustomKey = observed.length;
|
||||
let currentKey = item.data('collection-key-backup');
|
||||
|
||||
item.attr('data-collection-key', hasCustomKey ? observedValue : index);
|
||||
|
||||
['name', 'data-grav-field-name', 'for', 'id', 'data-grav-file-settings', 'data-file-post-add', 'data-file-post-remove', 'data-grav-array-name'].forEach((prop) => {
|
||||
item.find('[' + prop + '], [_' + prop + ']').each(function() {
|
||||
let element = $(this);
|
||||
let indexes = [];
|
||||
let array_index = null;
|
||||
let regexps = [
|
||||
new RegExp('\\[(\\d+|\\*|' + currentKey + ')\\]', 'g'),
|
||||
new RegExp('\\.(\\d+|\\*|' + currentKey + ')\\.', 'g')
|
||||
];
|
||||
|
||||
// special case to preserve array field index keys
|
||||
if (prop === 'name' && element.data('gravArrayType')) {
|
||||
const match_index = element.attr(prop).match(/\[[0-9]{1,}\]$/);
|
||||
const pattern = element[0].closest('[data-grav-array-name]').dataset.gravArrayName;
|
||||
if (match_index && pattern) {
|
||||
array_index = match_index[0];
|
||||
element.attr(prop, `${pattern}${match_index[0]}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCustomKey && !observedValue) {
|
||||
element.attr(`_${prop}`, element.attr(prop));
|
||||
element.attr(prop, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.attr(`_${prop}`)) {
|
||||
element.attr(prop, element.attr(`_${prop}`));
|
||||
element.attr(`_${prop}`, null);
|
||||
}
|
||||
|
||||
element.parents('[data-collection-key]').map((idx, parent) => indexes.push($(parent).attr('data-collection-key')));
|
||||
indexes.reverse();
|
||||
|
||||
let matchedKey = currentKey;
|
||||
let replaced = element.attr(prop).replace(regexps[0], (/* str, p1, offset */) => {
|
||||
let extras = '';
|
||||
if (array_index) { extras = array_index; console.log(indexes, extras); }
|
||||
|
||||
matchedKey = indexes.shift() || matchedKey;
|
||||
return `[${matchedKey}]${extras}`;
|
||||
});
|
||||
|
||||
replaced = replaced.replace(regexps[1], (/* str, p1, offset */) => {
|
||||
matchedKey = indexes.shift() || matchedKey;
|
||||
return `.${matchedKey}.`;
|
||||
});
|
||||
|
||||
element.attr(prop, replaced);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_onAddedNodes(event, target/* , record, instance */) {
|
||||
let collections = $(target).find('[data-type="collection"]');
|
||||
if (!collections.length) { return; }
|
||||
|
||||
collections.each((index, collection) => {
|
||||
collection = $(collection);
|
||||
if (!~this.lists.index(collection)) {
|
||||
this.addList(collection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_updateActionsStateBasedOnMinMax(list) {
|
||||
let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
|
||||
let minItems = list.data('min');
|
||||
let maxItems = list.data('max');
|
||||
|
||||
list.find('> .collection-actions [data-action="add"]').attr('disabled', false);
|
||||
list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', false);
|
||||
|
||||
if (typeof minItems !== 'undefined' && items.length <= minItems) {
|
||||
list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', true);
|
||||
}
|
||||
|
||||
if (typeof maxItems !== 'undefined' && items.length >= maxItems) {
|
||||
list.find('> .collection-actions [data-action="add"]').attr('disabled', true);
|
||||
}
|
||||
if (typeof maxItems !== 'undefined' && items.length >= maxItems) {
|
||||
list.find('> .collection-actions [data-action="add"]').attr('disabled', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new CollectionsField();
|
||||
|
||||
Reference in New Issue
Block a user