This commit is contained in:
Loïc Guibert
2022-09-30 20:02:02 +01:00
commit 66dafc36c3
2561 changed files with 454489 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
import $ from 'jquery';
const body = $('body');
body.on('change', '[data-acl_picker] select', (event) => {
const target = $(event.currentTarget);
const value = target.val();
const item = target.closest('.permissions-item');
const inputs = item.find('input[type="checkbox"], input[type="radio"]');
const hidden = item.find('input[type="hidden"][name]');
const wrapper = target.closest('[data-acl_picker_id]');
const type = item.data('fieldType');
if (type === 'access') {
inputs.each((index, input) => {
input = $(input);
const name = input.prop('name');
input.prop('name', name.replace(/(.*)(\[[^\]]*\])/, `$1[${value}]`));
});
} else if (type === 'permissions') {
const crudpContainer = item.find('[data-field-name]');
inputs.each((index, input) => {
input = $(input);
const rand = Math.round(Math.random() * 500);
const name = crudpContainer.data('fieldName');
const id = input.prop('id').split('_').slice(0, -1).join('_') + `_${value}+${rand}`;
// const key = input.data('crudpKey');
hidden.prop('name', name.replace(/(.*)(\[[^\]]*\])/, `$1[${value}]`));
input.prop('id', id);
input.next('label').prop('for', id);
});
}
wrapper.find('.permissions-item .button.add-item')[!value ? 'addClass' : 'removeClass']('disabled').prop('disabled', !value ? 'disabled' : null);
});
body.on('input', 'input[data-crudp-key]', (event) => {
const target = $(event.currentTarget);
const container = target.closest('.crudp-container');
const hidden = container.find('input[type="hidden"][name]');
const key = target.data('crudpKey');
const json = JSON.parse(hidden.val() || '{}');
json[key] = target.val();
hidden.val(JSON.stringify(json));
});
body.on('click', '[data-acl_picker] .remove-item', (event) => {
event.preventDefault();
const target = $(event.currentTarget);
const container = target.closest('.permissions-item');
const wrapper = target.closest('[data-acl_picker_id]');
container.remove();
const empty = wrapper.find('.permissions-item').length === 1;
// show the initial + button
if (empty) {
wrapper.find('.permissions-item.empty-list').removeClass('hidden');
}
});
body.on('click', '[data-acl_picker] .add-item', (event) => {
event.preventDefault();
const target = $(event.currentTarget);
const item = target.closest('.permissions-item');
const wrapper = target.closest('[data-acl_picker_id]');
const ID = wrapper.data('acl_picker_id');
const template = document.querySelector(`template[data-id="acl_picker-${ID}"]`);
const clone = $(template.content.firstElementChild).clone();
clone.insertAfter(item);
// randomize ids
clone.find('.switch-toggle input[type="radio"]').each((index, input) => {
input = $(input);
const id = input.prop('id');
const label = input.next('label');
const rand = (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toLowerCase();
input.prop('id', `${id}${rand}`);
label.prop('for', `${id}${rand}`);
});
// hide the initial + button
wrapper.find('.permissions-item.empty-list').addClass('hidden');
// disable all + buttons until one is selected
wrapper.find('.permissions-item .button.add-item').addClass('disabled').prop('disabled', 'disabled');
});

View File

@@ -0,0 +1,205 @@
import $ from 'jquery';
import Sortable from 'sortablejs';
let body = $('body');
class Template {
constructor(container) {
this.container = $(container);
if (this.getName() === undefined) {
this.container = this.container.closest('[data-grav-array-name]');
}
}
getName() {
return this.container.data('grav-array-name') || '';
}
getKeyPlaceholder() {
return this.container.data('grav-array-keyname') || 'Key';
}
getValuePlaceholder() {
return this.container.data('grav-array-valuename') || 'Value';
}
isValueOnly() {
return this.container.find('[data-grav-array-mode="value_only"]:first').length || false;
}
isTextArea() {
return this.container.data('grav-array-textarea') || false;
}
shouldBeDisabled() {
// check for toggleables, if field is toggleable and it's not enabled, render disabled
let toggle = this.container.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
return toggle.length && toggle.is(':not(:checked)');
}
getNewRow() {
let tpl = '';const value = this.isTextArea()
? `<textarea ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" name="" placeholder="${this.getValuePlaceholder()}"></textarea>`
: `<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" name="" value="" placeholder="${this.getValuePlaceholder()}" />`;
if (this.isValueOnly()) {
tpl += `
<div class="form-row array-field-value_only" data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
${value}
`;
} else {
tpl += `
<div class="form-row" data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="key" type="text" value="" placeholder="${this.getKeyPlaceholder()}" />
${value}
`;
}
tpl += `
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>`;
return tpl;
}
}
export default class ArrayField {
constructor() {
body.on('input', '[data-grav-array-type="key"], [data-grav-array-type="value"]', (event) => this.actionInput(event));
body.on('click touch', '[data-grav-array-action]:not([data-grav-array-action="sort"])', (event) => this.actionEvent(event));
this.arrays = $();
$('[data-grav-field="array"]').each((index, list) => this.addArray(list));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
addArray(list) {
list = $(list);
list.find('[data-grav-array-type="container"]').each((index, container) => {
container = $(container);
if (container.data('array-sort') || container[0].hasAttribute('data-array-nosort')) { return; }
container.data('array-sort', new Sortable(container.get(0), {
handle: '.fa-bars',
animation: 150,
onUpdate: () => {
const item = container.find('[data-grav-array-type="row"]:first');
this._setTemplate(item);
const template = item.data('array-template');
this.refreshNames(template);
}
}));
});
}
actionInput(event) {
let element = $(event.target);
let type = element.data('grav-array-type');
this._setTemplate(element);
let template = element.data('array-template');
let keyElement = type === 'key' ? element : element.siblings('[data-grav-array-type="key"]:first');
let valueElement = type === 'value' ? element : element.siblings('[data-grav-array-type="value"]:first');
let escaped_name = !template.isValueOnly() ? keyElement.val() : this.getIndexFor(element);
escaped_name = escaped_name.toString().replace(/\[/g, '%5B').replace(/]/g, '%5D');
let name = `${template.getName()}[${escaped_name}]`;
if (!template.isValueOnly() && (!keyElement.val() && !valueElement.val())) {
valueElement.attr('name', '');
} else {
// valueElement.attr('name', !valueElement.val() ? template.getName() : name);
valueElement.attr('name', name);
}
this.refreshNames(template);
}
actionEvent(event) {
event && event.preventDefault();
let element = $(event.target);
let action = element.data('grav-array-action');
let container = element.parents('[data-grav-array-type="container"]');
this._setTemplate(element);
this[`${action}Action`](element);
let siblings = container.find('> div');
container[siblings.length > 1 ? 'removeClass' : 'addClass']('one-child');
}
addAction(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
row.after(template.getNewRow());
}
remAction(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
let isLast = !row.siblings().length;
if (isLast) {
let newRow = $(template.getNewRow());
row.after(newRow);
newRow.find('[data-grav-array-type="value"]:last').attr('name', template.getName());
}
row.remove();
this.refreshNames(template);
}
refreshNames(template) {
if (!template.isValueOnly()) { return; }
let row = template.container.find('> div > [data-grav-array-type="row"]');
let inputs = row.find('[name]:not([name=""])');
inputs.each((index, input) => {
input = $(input);
const preserved_name = input.closest('[data-grav-array-name]');
const name = `${preserved_name.attr('data-grav-array-name')}[${index}]`;
input.attr('name', name);
});
if (!inputs.length) {
row.find('[data-grav-array-type="value"]').attr('name', template.getName());
}
}
getIndexFor(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
return template.container.find(`${template.isValueOnly() ? '> div ' : ''} > [data-grav-array-type="row"]`).index(row);
}
_setTemplate(element) {
if (!element.data('array-template')) {
element.data('array-template', new Template(element.closest('[data-grav-array-name]')));
}
}
_onAddedNodes(event, target/* , record, instance */) {
let arrays = $(target).find('[data-grav-field="array"]');
if (!arrays.length) { return; }
arrays.each((index, list) => {
list = $(list);
if (!~this.arrays.index(list)) {
this.addArray(list);
}
});
}
}
export let Instance = new ArrayField();

View File

@@ -0,0 +1,311 @@
import $ from 'jquery';
import Sortable from 'sortablejs';
import '../../utils/jquery-utils';
export default class CollectionsField {
constructor() {
this.lists = $();
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;
}
list.find('> [data-collection-holder]')[position === 'top'
? 'prepend'
: 'append'](template);
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().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');
}
static closeConfirmations() {
$('.list-confirm-deletion[data-action="delete"]').addClass('hidden');
}
confirmRemove(event) {
const button = $(event.currentTarget);
const list = $(button.closest('.item-actions'));
const action = list.find('.list-confirm-deletion[data-action="delete"]');
const isHidden = action.hasClass('hidden');
CollectionsField.closeConfirmations();
action[isHidden ? 'removeClass' : 'addClass']('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', 'data-grav-elements'].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);
}
}
}
export let Instance = new CollectionsField();

View File

@@ -0,0 +1,579 @@
import $ from 'jquery';
import clamp from 'mout/math/clamp';
import bind from 'mout/function/bind';
import { rgbstr2hex, hsb2hex, hex2hsb, hex2rgb, parseHex } from '../../utils/colors';
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const body = $('body');
const MOUSEDOWN = 'mousedown touchstart MSPointerDown pointerdown';
const MOUSEMOVE = 'mousemove touchmove MSPointerMove pointermove';
const MOUSEUP = 'mouseup touchend MSPointerUp pointerup';
const FOCUSIN = isFirefox ? 'focus' : 'focusin';
export default class ColorpickerField {
constructor(selector) {
this.selector = selector;
this.field = $(this.selector);
this.options = Object.assign({}, this.field.data('grav-colorpicker'));
this.built = false;
this.attach();
if (this.options.update) {
this.field.on('change._grav_colorpicker', (event, field, hex, opacity) => {
let backgroundColor = hex;
let rgb = hex2rgb(hex);
if (opacity < 1) {
backgroundColor = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + opacity + ')';
}
let target = field.closest(this.options.update);
if (!target.length) {
target = field.siblings(this.options.update);
}
if (!target.length) {
target = field.parent('.g-colorpicker').find(this.options.update);
}
target.css({ backgroundColor });
});
}
}
attach() {
body.on(FOCUSIN, this.selector, (event) => this.show(event, event.currentTarget));
body.on(MOUSEDOWN, this.selector + ' .g-colorpicker, ' + this.selector + ' .g-colorpicker i', this.bound('iconClick'));
body.on('keydown', this.selector, (event) => {
switch (event.keyCode) {
case 9: // tab
this.hide();
break;
case 13: // enter
case 27: // esc
this.hide();
event.currentTarget.blur();
break;
}
return true;
});
// Update on keyup
body.on('keyup', this.selector, (event) => {
this.updateFromInput(true, event.currentTarget);
return true;
});
// Update on paste
body.on('paste', this.selector, (event) => {
setTimeout(() => this.updateFromInput(true, event.currentTarget), 1);
});
}
show(event, target) {
target = $(target);
if (!this.built) {
this.build();
}
this.element = target;
this.reposition();
this.wrapper.addClass('cp-visible');
this.updateFromInput();
this.wrapper.on(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
body.on(MOUSEMOVE, this.bound('bodyMove'));
body.on(MOUSEDOWN, this.bound('bodyClick'));
body.on(MOUSEUP, this.bound('targetReset'));
$('#admin-main > .content-wrapper').on('scroll', this.bound('reposition'));
}
hide() {
if (!this.built) { return; }
this.wrapper.removeClass('cp-visible');
this.wrapper.undelegate(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
body.off(MOUSEMOVE, this.bound('bodyMove'));
body.off(MOUSEDOWN, this.bound('bodyClick'));
body.off(MOUSEUP, this.bound('targetReset'));
$('#admin-main > .content-wrapper').on('scroll', this.bound('reposition'));
}
build() {
this.wrapper = $('<div class="cp-wrapper cp-with-opacity cp-mode-hue" />');
this.slider = $('<div class="cp-slider cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-picker" />'));
this.opacitySlider = $('<div class="cp-opacity-slider cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-picker" />'));
this.grid = $('<div class="cp-grid cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-grid-inner" />')).append($('<div class="cp-picker" />'));
$('<div />').appendTo(this.grid.find('.cp-picker'));
let tabs = $('<div class="cp-tabs" />').appendTo(this.wrapper);
this.tabs = {
hue: $('<div class="cp-tab-hue active" />').text('HUE').appendTo(tabs),
brightness: $('<div class="cp-tab-brightness" />').text('BRI').appendTo(tabs),
saturation: $('<div class="cp-tab-saturation" />').text('SAT').appendTo(tabs),
wheel: $('<div class="cp-tab-wheel" />').text('WHEEL').appendTo(tabs),
transparent: $('<div class="cp-tab-transp" />').text('TRANSPARENT').appendTo(tabs)
};
tabs.on(MOUSEDOWN, '> div', (event) => {
let element = $(event.currentTarget);
if (element.is(this.tabs.transparent)) {
let sliderHeight = this.opacitySlider.height();
this.opacity = 0;
this.opacitySlider.find('.cp-picker').css({ 'top': clamp(sliderHeight - (sliderHeight * this.opacity), 0, sliderHeight) });
this.move(this.opacitySlider, { manualOpacity: true });
return;
}
let active = tabs.find('.active');
let mode = active.attr('class').replace(/\s|active|cp-tab-/g, '');
let newMode = element.attr('class').replace(/\s|active|cp-tab-/g, '');
this.wrapper.removeClass('cp-mode-' + mode).addClass('cp-mode-' + newMode);
active.removeClass('active');
element.addClass('active');
this.mode = newMode;
this.updateFromInput();
});
this.wrapper.appendTo('.content-wrapper');
this.built = true;
this.mode = 'hue';
}
reposition() {
let ct = $('.content-wrapper')[0];
let offset = this.element[0].getBoundingClientRect();
let ctOffset = ct.getBoundingClientRect();
let delta = { x: 0, y: 0 };
if (this.options.offset) {
delta.x = this.options.offset.x || 0;
delta.y = this.options.offset.y || 0;
}
this.wrapper.css({
top: offset.top + offset.height + ct.scrollTop - ctOffset.top + delta.y,
left: offset.left + ct.scrollLeft - ctOffset.left + delta.x
});
}
iconClick(event) {
if (this.wrapper && this.wrapper.hasClass('cp-visible')) { return true; }
event && event.preventDefault();
let input = $(event.currentTarget).find('input');
setTimeout(() => input.focus(), 50);
}
bodyMove(event) {
event && event.preventDefault();
if (this.target) { this.move(this.target, event); }
}
bodyClick(event) {
let target = $(event.target);
if (!target.closest('.cp-wrapper').length && !target.is(this.selector)) {
this.hide();
}
}
bodyDown(event) {
event && event.preventDefault();
this.target = $(event.currentTarget);
this.move(this.target, event, true);
}
targetReset(event) {
event && event.preventDefault();
this.target = null;
}
move(target, event) {
let input = this.element;
let picker = target.find('.cp-picker');
let clientRect = target[0].getBoundingClientRect();
let offsetX = clientRect.left + window.scrollX;
let offsetY = clientRect.top + window.scrollY;
let x = Math.round((event ? event.pageX : 0) - offsetX);
let y = Math.round((event ? event.pageY : 0) - offsetY);
let wx;
let wy;
let r;
let phi;
// Touch support
let touchEvents = event.changedTouches || (event.originalEvent && event.originalEvent.changedTouches);
if (event && touchEvents) {
x = (touchEvents ? touchEvents[0].pageX : 0) - offsetX;
y = (touchEvents ? touchEvents[0].pageY : 0) - offsetY;
}
if (event && event.manualOpacity) {
y = clientRect.height;
}
// Constrain picker to its container
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x > clientRect.width) x = clientRect.width;
if (y > clientRect.height) y = clientRect.height;
// Constrain color wheel values to the wheel
if (target.parent('.cp-mode-wheel').length && picker.parent('.cp-grid').length) {
wx = 75 - x;
wy = 75 - y;
r = Math.sqrt(wx * wx + wy * wy);
phi = Math.atan2(wy, wx);
if (phi < 0) phi += Math.PI * 2;
if (r > 75) {
x = 75 - (75 * Math.cos(phi));
y = 75 - (75 * Math.sin(phi));
}
x = Math.round(x);
y = Math.round(y);
}
// Move the picker
if (target.hasClass('cp-grid')) {
picker.css({
top: y,
left: x
});
this.updateFromPicker(input, target);
} else {
picker.css({
top: y
});
this.updateFromPicker(input, target);
}
}
updateFromInput(dontFireEvent, element) {
element = element ? $(element) : this.element;
let value = element.val();
let opacity = value.replace(/\s/g, '').match(/^rgba?\([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},(.+)\)/);
let hex;
let hsb;
value = rgbstr2hex(value) || value;
opacity = opacity ? clamp(opacity[1], 0, 1) : 1;
if (!(hex = parseHex(value))) { hex = '#ffffff'; }
hsb = hex2hsb(hex);
if (this.built) {
// opacity
this.opacity = opacity;
var sliderHeight = this.opacitySlider.height();
this.opacitySlider.find('.cp-picker').css({ 'top': clamp(sliderHeight - (sliderHeight * this.opacity), 0, sliderHeight) });
// bg color
let gridHeight = this.grid.height();
let gridWidth = this.grid.width();
let r;
let phi;
let x;
let y;
sliderHeight = this.slider.height();
switch (this.mode) {
case 'wheel':
// Set grid position
r = clamp(Math.ceil(hsb.s * 0.75), 0, gridHeight / 2);
phi = hsb.h * Math.PI / 180;
x = clamp(75 - Math.cos(phi) * r, 0, gridWidth);
y = clamp(75 - Math.sin(phi) * r, 0, gridHeight);
this.grid.css({ backgroundColor: 'transparent' }).find('.cp-picker').css({
top: y,
left: x
});
// Set slider position
y = 150 - (hsb.b / (100 / gridHeight));
if (hex === '') y = 0;
this.slider.find('.cp-picker').css({ top: y });
// Update panel color
this.slider.css({
backgroundColor: hsb2hex({
h: hsb.h,
s: hsb.s,
b: 100
})
});
break;
case 'saturation':
// Set grid position
x = clamp((5 * hsb.h) / 12, 0, 150);
y = clamp(gridHeight - Math.ceil(hsb.b / (100 / gridHeight)), 0, gridHeight);
this.grid.find('.cp-picker').css({
top: y,
left: x
});
// Set slider position
y = clamp(sliderHeight - (hsb.s * (sliderHeight / 100)), 0, sliderHeight);
this.slider.find('.cp-picker').css({ top: y });
// Update UI
this.slider.css({
backgroundColor: hsb2hex({
h: hsb.h,
s: 100,
b: hsb.b
})
});
this.grid.find('.cp-grid-inner').css({ opacity: hsb.s / 100 });
break;
case 'brightness':
// Set grid position
x = clamp((5 * hsb.h) / 12, 0, 150);
y = clamp(gridHeight - Math.ceil(hsb.s / (100 / gridHeight)), 0, gridHeight);
this.grid.find('.cp-picker').css({
top: y,
left: x
});
// Set slider position
y = clamp(sliderHeight - (hsb.b * (sliderHeight / 100)), 0, sliderHeight);
this.slider.find('.cp-picker').css({ top: y });
// Update UI
this.slider.css({
backgroundColor: hsb2hex({
h: hsb.h,
s: hsb.s,
b: 100
})
});
this.grid.find('.cp-grid-inner').css({ opacity: 1 - (hsb.b / 100) });
break;
case 'hue':
default:
// Set grid position
x = clamp(Math.ceil(hsb.s / (100 / gridWidth)), 0, gridWidth);
y = clamp(gridHeight - Math.ceil(hsb.b / (100 / gridHeight)), 0, gridHeight);
this.grid.find('.cp-picker').css({
top: y,
left: x
});
// Set slider position
y = clamp(sliderHeight - (hsb.h / (360 / sliderHeight)), 0, sliderHeight);
this.slider.find('.cp-picker').css({ top: y });
// Update panel color
this.grid.css({
backgroundColor: hsb2hex({
h: hsb.h,
s: 100,
b: 100
})
});
break;
}
}
if (!dontFireEvent) { element.val(this.getValue(hex)); }
(this.element || element).trigger('change._grav_colorpicker', [element, hex, opacity]);
}
updateFromPicker(input, target) {
var getCoords = function(picker, container) {
var left, top;
if (!picker.length || !container) return null;
left = picker[0].getBoundingClientRect().left;
top = picker[0].getBoundingClientRect().top;
return {
x: left - container[0].getBoundingClientRect().left + (picker[0].offsetWidth / 2),
y: top - container[0].getBoundingClientRect().top + (picker[0].offsetHeight / 2)
};
};
let hex;
let hue;
let saturation;
let brightness;
let x;
let y;
let r;
let phi;
// Panel objects
let grid = this.wrapper.find('.cp-grid');
let slider = this.wrapper.find('.cp-slider');
let opacitySlider = this.wrapper.find('.cp-opacity-slider');
// Picker objects
let gridPicker = grid.find('.cp-picker');
let sliderPicker = slider.find('.cp-picker');
let opacityPicker = opacitySlider.find('.cp-picker');
// Picker positions
let gridPos = getCoords(gridPicker, grid);
let sliderPos = getCoords(sliderPicker, slider);
let opacityPos = getCoords(opacityPicker, opacitySlider);
// Sizes
let gridWidth = grid[0].getBoundingClientRect().width;
let gridHeight = grid[0].getBoundingClientRect().height;
let sliderHeight = slider[0].getBoundingClientRect().height;
let opacitySliderHeight = opacitySlider[0].getBoundingClientRect().height;
let value = this.element.val();
value = rgbstr2hex(value) || value;
if (!(hex = parseHex(value))) { hex = '#ffffff'; }
// Handle colors
if (target.hasClass('cp-grid') || target.hasClass('cp-slider')) {
// Determine HSB values
switch (this.mode) {
case 'wheel':
// Calculate hue, saturation, and brightness
x = (gridWidth / 2) - gridPos.x;
y = (gridHeight / 2) - gridPos.y;
r = Math.sqrt(x * x + y * y);
phi = Math.atan2(y, x);
if (phi < 0) phi += Math.PI * 2;
if (r > 75) {
r = 75;
gridPos.x = 69 - (75 * Math.cos(phi));
gridPos.y = 69 - (75 * Math.sin(phi));
}
saturation = clamp(r / 0.75, 0, 100);
hue = clamp(phi * 180 / Math.PI, 0, 360);
brightness = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
hex = hsb2hex({
h: hue,
s: saturation,
b: brightness
});
// Update UI
slider.css({
backgroundColor: hsb2hex({
h: hue,
s: saturation,
b: 100
})
});
break;
case 'saturation':
// Calculate hue, saturation, and brightness
hue = clamp(parseInt(gridPos.x * (360 / gridWidth), 10), 0, 360);
saturation = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
brightness = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
hex = hsb2hex({
h: hue,
s: saturation,
b: brightness
});
// Update UI
slider.css({
backgroundColor: hsb2hex({
h: hue,
s: 100,
b: brightness
})
});
grid.find('.cp-grid-inner').css({ opacity: saturation / 100 });
break;
case 'brightness':
// Calculate hue, saturation, and brightness
hue = clamp(parseInt(gridPos.x * (360 / gridWidth), 10), 0, 360);
saturation = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
brightness = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
hex = hsb2hex({
h: hue,
s: saturation,
b: brightness
});
// Update UI
slider.css({
backgroundColor: hsb2hex({
h: hue,
s: saturation,
b: 100
})
});
grid.find('.cp-grid-inner').css({ opacity: 1 - (brightness / 100) });
break;
default:
// Calculate hue, saturation, and brightness
hue = clamp(360 - parseInt(sliderPos.y * (360 / sliderHeight), 10), 0, 360);
saturation = clamp(Math.floor(gridPos.x * (100 / gridWidth)), 0, 100);
brightness = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
hex = hsb2hex({
h: hue,
s: saturation,
b: brightness
});
// Update UI
grid.css({
backgroundColor: hsb2hex({
h: hue,
s: 100,
b: 100
})
});
break;
}
}
// Handle opacity
if (target.hasClass('cp-opacity-slider')) {
this.opacity = parseFloat(1 - (opacityPos.y / opacitySliderHeight)).toFixed(2);
}
// Adjust case
input.val(this.getValue(hex));
// Handle change event
this.element.trigger('change._grav_colorpicker', [this.element, hex, this.opacity]);
}
getValue(hex) {
if (this.opacity === 1) { return hex; }
let rgb = hex2rgb(hex);
return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + this.opacity + ')';
}
bound(name) {
let bound = this._bound || (this._bound = {});
return bound[name] || (bound[name] = bind(this[name], this));
}
}
export let Instance = new ColorpickerField('[data-grav-colorpicker]');

View File

@@ -0,0 +1,85 @@
import $ from 'jquery';
import '../../utils/cron-ui';
import { translations } from 'grav-config';
export default class CronField {
constructor() {
this.items = $();
$('[data-grav-field="cron"]').each((index, cron) => this.addCron(cron));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
addCron(cron) {
cron = $(cron);
this.items = this.items.add(cron);
cron.find('.cron-selector').each((index, container) => {
container = $(container);
const input = container.closest('[data-grav-field]').find('input');
container.jqCron({
numeric_zero_pad: true,
enabled_minute: true,
multiple_dom: true,
multiple_month: true,
multiple_mins: true,
multiple_dow: true,
multiple_time_hours: true,
multiple_time_minutes: true,
default_period: 'hour',
default_value: input.val() || '* * * * *',
no_reset_button: false,
bind_to: input,
bind_method: {
set: function($element, value) {
$element.val(value);
}
},
texts: {
en: {
empty: translations.GRAV_CORE['CRON.EVERY'],
empty_minutes: translations.GRAV_CORE['CRON.EVERY'],
empty_time_hours: translations.GRAV_CORE['CRON.EVERY_HOUR'],
empty_time_minutes: translations.GRAV_CORE['CRON.EVERY_MINUTE'],
empty_day_of_week: translations.GRAV_CORE['CRON.EVERY_DAY_OF_WEEK'],
empty_day_of_month: translations.GRAV_CORE['CRON.EVERY_DAY_OF_MONTH'],
empty_month: translations.GRAV_CORE['CRON.EVERY_MONTH'],
name_minute: translations.GRAV_CORE['NICETIME.MINUTE'],
name_hour: translations.GRAV_CORE['NICETIME.HOUR'],
name_day: translations.GRAV_CORE['NICETIME.DAY'],
name_week: translations.GRAV_CORE['NICETIME.WEEK'],
name_month: translations.GRAV_CORE['NICETIME.MONTH'],
name_year: translations.GRAV_CORE['NICETIME.YEAR'],
text_period: translations.GRAV_CORE['CRON.TEXT_PERIOD'],
text_mins: translations.GRAV_CORE['CRON.TEXT_MINS'],
text_time: translations.GRAV_CORE['CRON.TEXT_TIME'],
text_dow: translations.GRAV_CORE['CRON.TEXT_DOW'],
text_month: translations.GRAV_CORE['CRON.TEXT_MONTH'],
text_dom: translations.GRAV_CORE['CRON.TEXT_DOM'],
error1: translations.GRAV_CORE['CRON.ERROR1'],
error2: translations.GRAV_CORE['CRON.ERROR2'],
error3: translations.GRAV_CORE['CRON.ERROR3'],
error4: translations.GRAV_CORE['CRON.ERROR4'],
weekdays: translations.GRAV_CORE['DAYS_OF_THE_WEEK'],
months: translations.GRAV_CORE['MONTHS_OF_THE_YEAR']
}
}
});
});
}
_onAddedNodes(event, target/* , record, instance */) {
let crons = $(target).find('[data-grav-field="cron"]');
if (!crons.length) { return; }
crons.each((index, list) => {
list = $(list);
if (!~this.items.index(list)) {
this.addCron(list);
}
});
}
}
export let Instance = new CronField();

View File

@@ -0,0 +1,63 @@
import $ from 'jquery';
import { config } from 'grav-config';
import '../../utils/bootstrap-datetimepicker';
export default class DateTimeField {
get defaults() {
return {
showTodayButton: true,
showClear: true,
locale: config.language || 'en',
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar-o',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-bullseye',
clear: 'fa fa-trash-o',
close: 'fa fa-remove'
}
};
}
constructor(options) {
this.items = $();
this.options = Object.assign({}, this.defaults, options);
$('[data-grav-datetime]').each((index, field) => this.addItem(field));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
addItem(list) {
list = $(list);
this.items = this.items.add(list);
if (list.data('DateTimePicker')) { return; }
let options = Object.assign({}, this.options, list.data('grav-datetime') || {});
list.datetimepicker(options).on('dp.show dp.update', this._disableDecades);
list.siblings('.field-icons').on('click', () => list.mousedown().focus());
}
_onAddedNodes(event, target/* , record, instance */) {
let fields = $(target).find('[data-grav-datetime]');
if (!fields.length) { return; }
fields.each((index, field) => {
field = $(field);
if (!~this.items.index(field)) {
this.addItem(field);
}
});
}
_disableDecades() {
$('.datepicker-years .picker-switch').removeAttr('title').on('click', (e) => e.stopPropagation());
}
}
export let Instance = new DateTimeField();

View File

@@ -0,0 +1,237 @@
import $ from 'jquery';
import Buttons, { strategies as buttonStrategies } from './editor/buttons';
import codemirror from 'codemirror';
import { watch } from 'watchjs';
import jsyaml from 'js-yaml';
global.jsyaml = jsyaml;
// Modes
import 'codemirror/mode/css/css';
import 'codemirror/mode/gfm/gfm';
import 'codemirror/mode/htmlmixed/htmlmixed';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/markdown/markdown';
import 'codemirror/mode/php/php';
import 'codemirror/mode/sass/sass';
import 'codemirror/mode/twig/twig';
import 'codemirror/mode/xml/xml';
import 'codemirror/mode/yaml/yaml';
// Add-ons
import 'codemirror/addon/edit/continuelist';
import 'codemirror/addon/mode/overlay';
import 'codemirror/addon/selection/active-line';
import 'codemirror/addon/lint/lint';
import 'codemirror/addon/lint/lint.css';
import 'codemirror/addon/lint/css-lint';
import 'codemirror/addon/lint/javascript-lint';
import 'codemirror/addon/lint/json-lint';
import 'codemirror/addon/lint/yaml-lint';
let IS_MOUSEDOWN = false;
const ThemesMap = ['paper'];
const Defaults = {
codemirror: {
mode: 'htmlmixed',
theme: 'paper',
lineWrapping: true,
dragDrop: true,
autoCloseTags: true,
matchTags: true,
autoCloseBrackets: true,
matchBrackets: true,
indentUnit: 4,
indentWithTabs: false,
tabSize: 4,
hintOptions: { completionSingle: false },
extraKeys: { 'Enter': 'newlineAndIndentContinueMarkdownList' }
}
};
export default class EditorField {
constructor(options) {
let body = $('body');
this.editors = $();
this.options = Object.assign({}, Defaults, options);
this.buttons = Buttons;
this.buttonStrategies = buttonStrategies;
watch(Buttons, (/* key, modifier, prev, next */) => {
this.editors.each((index, editor) => $(editor).data('toolbar').renderButtons());
});
$('[data-grav-editor]').each((index, editor) => this.addEditor(editor));
$(() => { body.trigger('grav-editor-ready'); });
body.on('mutation._grav', this._onAddedNodes.bind(this));
body.on('mouseup._grav', () => {
if (!IS_MOUSEDOWN) { return true; }
body.unbind('mousemove._grav');
IS_MOUSEDOWN = false;
});
body.on('mousedown._grav', '.grav-editor-resizer', (event) => {
event && event.preventDefault();
IS_MOUSEDOWN = true;
let target = $(event.currentTarget);
let container = target.siblings('.grav-editor-content');
let editor = container.find('.CodeMirror');
let codemirror = container.find('textarea').data('codemirror');
body.on('mousemove._grav', (event) => {
editor.css('height', Math.max(100, event.pageY - container.offset().top));
codemirror.refresh();
});
});
}
addButton(button, options) {
if (options && (options.before || options.after)) {
let index = this.buttons.navigation.findIndex((obj) => {
let key = Object.keys(obj).shift();
return obj[key].identifier === (options.before || options.after);
});
if (!~index) {
options = 'end';
} else {
this.buttons.navigation.splice(options.before ? index : index + 1, 0, button);
}
}
if (options === 'start') { this.buttons.navigation.splice(0, 0, button); }
if (!options || options === 'end') { this.buttons.navigation.push(button); }
}
addEditor(textarea) {
textarea = $(textarea);
let options = Object.assign(
{},
this.options.codemirror,
textarea.data('grav-editor').codemirror
);
let theme = options.theme || 'paper';
this.editors = this.editors.add(textarea);
if (theme && !~ThemesMap.indexOf(theme)) {
ThemesMap.push(theme);
// let themeCSS = `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.12.0/theme/${theme}.min.css`;
// $('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', themeCSS));
}
if (options.mode === 'yaml') {
Object.assign(options.extraKeys, { Tab: function(cm) { cm.replaceSelection(' ', 'end'); }});
}
let editor = codemirror.fromTextArea(textarea.get(0), options);
textarea.data('codemirror', editor);
textarea.data('toolbar', new Toolbar(textarea));
textarea.addClass('code-mirrored');
if (options.toolbar === false) {
textarea.data('toolbar').ui.navigation.addClass('grav-editor-hide-toolbar');
}
editor.on('change', () => editor.save());
}
_onAddedNodes(event, target/* , record, instance */) {
let editors = $(target).find('[data-grav-editor]');
if (!editors.length) { return; }
editors.each((index, editor) => {
editor = $(editor);
if (!~this.editors.index(editor)) {
this.addEditor(editor);
}
});
}
}
export class Toolbar {
static templates() {
return {
navigation: `
<div class="grav-editor-toolbar">
<div class="grav-editor-actions"></div>
<div class="grav-editor-modes"></div>
</div>
`
};
}
constructor(editor) {
this.editor = $(editor);
this.codemirror = this.editor.data('codemirror');
this.buttons = Buttons.navigation;
this.ui = {
navigation: $(Toolbar.templates().navigation)
};
this.editor.parent('.grav-editor-content')
.before(this.ui.navigation)
.after(this.ui.states);
this.renderButtons();
}
renderButtons() {
let map = { 'actions': 'navigation', 'modes': 'states'};
['actions', 'modes'].forEach((type) => {
this.ui.navigation.find(`.grav-editor-${type}`).empty().append('<ul />');
Buttons[map[type]].forEach((button) => this.renderButton(button, type));
});
}
renderButton(button, type, location = null) {
Object.keys(button).forEach((key) => {
let obj = button[key];
if (!obj.modes) { obj.modes = []; }
if (!~this.codemirror.options.ignore.indexOf(key) && (!obj.modes.length || obj.modes.indexOf(this.codemirror.options.mode) > -1)) {
let hint = obj.title ? `data-hint="${obj.title}"` : '';
let element = $(`<li class="grav-editor-button-${key}"><a class="hint--top" ${hint}>${obj.label}</a></li>`);
(location || this.ui.navigation.find(`.grav-editor-${type} ul:not(.dropdown-menu)`)).append(element);
if (obj.shortcut) {
this.addShortcut(obj.identifier, obj.shortcut, element);
}
obj.action && obj.action.call(obj.action, {
codemirror: this.codemirror,
button: element,
textarea: this.editor,
ui: this.ui
});
if (obj.children) {
let childrenContainer = $('<ul class="dropdown-menu" />');
element.addClass('button-group').find('a').wrap('<div class="dropdown-toggle" data-toggle="dropdown"></div>');
element.find('a').append(' <i class="fa fa-caret-down"></i>');
element.append(childrenContainer);
obj.children.forEach((child) => this.renderButton(child, type, childrenContainer));
}
}
});
}
addShortcut(identifier, shortcut, element) {
let map = {};
if (!Array.isArray(shortcut)) {
shortcut = [shortcut];
}
shortcut.forEach((key) => {
map[key] = () => {
element.trigger(`click.editor.${identifier}`, [this.codemirror]);
};
});
this.codemirror.addKeyMap(map);
}
}
export let Instance = new EditorField();

View File

@@ -0,0 +1,416 @@
import $ from 'jquery';
import { config, translations } from 'grav-config';
import request from '../../../utils/request';
let replacer = ({ name, replace, codemirror, button, mode = 'replaceSelections', runner }) => {
button.on(`click.editor.${name}`, () => {
strategies[mode]({ token: '$1', template: replace, codemirror, runner });
});
};
export let strategies = {
replaceSelections({ template, token, codemirror, runner }) {
let replacements = [];
let ranges = [];
let selections = codemirror.getSelections();
let list = codemirror.listSelections();
let accumulator = {};
selections.forEach((selection, index) => {
let markup = template.replace(token, selection);
let cursor = markup.indexOf('$cur');
let { line, ch } = list[index].anchor;
markup = markup.replace('$cur', '');
markup = runner ? runner(selection, markup, list) : markup;
replacements.push(markup);
if (!accumulator[line]) { accumulator[line] = 0; }
ch += accumulator[line] + (cursor === -1 ? markup.length : cursor);
let range = { ch, line };
ranges.push({ anchor: range, head: range });
accumulator[line] += markup.length - selection.length;
});
codemirror.replaceSelections(replacements);
codemirror.setSelections(ranges);
codemirror.focus();
},
replaceLine({ template, token, codemirror, runner }) {
let list = codemirror.listSelections();
let range;
list.forEach((selection) => {
let lines = {
min: Math.min(selection.anchor.line, selection.head.line),
max: Math.max(selection.anchor.line, selection.head.line)
};
codemirror.eachLine(lines.min, lines.max + 1, (handler) => {
let markup = template.replace(token, handler.text);
let line = codemirror.getLineNumber(handler);
markup = runner ? runner(handler, markup) : markup;
codemirror.replaceRange(markup, { line, ch: 0 }, { line, ch: markup.length });
range = { line, ch: markup.length };
});
});
codemirror.setSelection(range, range, 'end');
codemirror.focus();
},
replaceRange() {}
};
const flipDisabled = (codemirror, button, type) => {
let hasHistory = codemirror.historySize()[type];
let element = button.find('a');
button[hasHistory ? 'removeClass' : 'addClass']('button-disabled');
if (!hasHistory) {
element.attr('title-disabled', element.attr('title'));
element.attr('data-hint-disabled', element.attr('data-hint'));
element.removeAttr('title').removeAttr('data-hint');
} else {
element.attr('title', element.attr('title-disabled'));
element.attr('data-hint', element.attr('data-hint-disabled'));
element.removeAttr('title-disabled').removeAttr('data-hint-disabled');
}
};
export default {
navigation: [
{
undo: {
identifier: 'undo',
title: translations.PLUGIN_ADMIN.UNDO,
label: '<i class="fa fa-fw fa-undo"></i>',
modes: [],
action({ codemirror, button, textarea}) {
button.addClass('button-disabled');
codemirror.on('change', () => flipDisabled(codemirror, button, 'undo'));
button.on('click.editor.undo', () => {
codemirror.undo();
});
}
}
},
{
redo: {
identifier: 'redo',
title: translations.PLUGIN_ADMIN.REDO,
label: '<i class="fa fa-fw fa-repeat"></i>',
modes: [],
action({ codemirror, button, textarea}) {
button.addClass('button-disabled');
codemirror.on('change', () => flipDisabled(codemirror, button, 'redo'));
button.on('click.editor.redo', () => {
codemirror.redo();
});
}
}
},
{
headers: {
identifier: 'headers',
title: translations.PLUGIN_ADMIN.HEADERS,
label: '<i class="fa fa-fw fa-header"></i>',
modes: ['gfm', 'markdown'],
children: [
{
h1: {
identifier: 'h1',
label: '<i class="fa fa-fw fa-header"></i>1',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'h1', replace: '# $1', codemirror, button, mode: 'replaceLine' });
}
}
},
{
h2: {
identifier: 'h2',
label: '<i class="fa fa-fw fa-header"></i>2',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'h2', replace: '## $1', codemirror, button, mode: 'replaceLine' });
}
}
},
{
h3: {
identifier: 'h3',
label: '<i class="fa fa-fw fa-header"></i>3',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'h3', replace: '### $1', codemirror, button, mode: 'replaceLine' });
}
}
},
{
h4: {
identifier: 'h4',
label: '<i class="fa fa-fw fa-header"></i>4',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'h4', replace: '#### $1', codemirror, button, mode: 'replaceLine' });
}
}
},
{
h5: {
identifier: 'h5',
label: '<i class="fa fa-fw fa-header"></i>5',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'h5', replace: '##### $1', codemirror, button, mode: 'replaceLine' });
}
}
},
{
h6: {
identifier: 'h6',
label: '<i class="fa fa-fw fa-header"></i>6',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'h6', replace: '###### $1', codemirror, button, mode: 'replaceLine' });
}
}
}
]
}
},
{
bold: {
identifier: 'bold',
title: translations.PLUGIN_ADMIN.BOLD,
label: '<i class="fa fa-fw fa-bold"></i>',
modes: ['gfm', 'markdown'],
shortcut: ['Ctrl-B', 'Cmd-B'],
action({ codemirror, button, textarea }) {
replacer({ name: 'bold', replace: '**$1$cur**', codemirror, button });
}
}
}, {
italic: {
identifier: 'italic',
title: translations.PLUGIN_ADMIN.ITALIC,
label: '<i class="fa fa-fw fa-italic"></i>',
modes: ['gfm', 'markdown'],
shortcut: ['Ctrl-I', 'Cmd-I'],
action({ codemirror, button, textarea }) {
replacer({ name: 'italic', replace: '_$1$cur_', codemirror, button });
}
}
}, {
strike: {
identifier: 'strike',
title: translations.PLUGIN_ADMIN.STRIKETHROUGH,
label: '<i class="fa fa-fw fa-strikethrough"></i>',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'strike', replace: '~~$1$cur~~', codemirror, button });
}
}
}, {
delimiter: {
identifier: 'delimiter',
title: translations.PLUGIN_ADMIN.SUMMARY_DELIMITER,
label: '<i class="fa fa-fw fa-minus"></i>',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'delimiter', replace: `${config.site.delimiter}$1`, codemirror, button, mode: 'replaceLine' });
}
}
}, {
link: {
identifier: 'link',
title: translations.PLUGIN_ADMIN.LINK,
label: '<i class="fa fa-fw fa-link"></i>',
modes: ['gfm', 'markdown'],
shortcut: ['Ctrl-K', 'Cmd-K'],
action({ codemirror, button, textarea }) {
replacer({ name: 'link', replace: '[$1]($cur)', codemirror, button });
}
}
}, {
image: {
identifier: 'image',
title: translations.PLUGIN_ADMIN.IMAGE,
label: '<i class="fa fa-fw fa-picture-o"></i>',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'image', replace: '![$1]($cur)', codemirror, button });
}
}
}, {
blockquote: {
identifier: 'blockquote',
title: translations.PLUGIN_ADMIN.BLOCKQUOTE,
label: '<i class="fa fa-fw fa-quote-right"></i>',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'blockquote', replace: '> $1', codemirror, button, mode: 'replaceLine' });
}
}
}, {
listUl: {
identifier: 'listUl',
title: translations.PLUGIN_ADMIN.UNORDERED_LIST,
label: '<i class="fa fa-fw fa-list-ul"></i>',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({ name: 'listUl', replace: '* $1', codemirror, button, mode: 'replaceLine' });
}
}
}, {
listOl: {
identifier: 'listOl',
title: translations.PLUGIN_ADMIN.ORDERED_LIST,
label: '<i class="fa fa-fw fa-list-ol"></i>',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea }) {
replacer({
name: 'listOl',
replace: '. $1',
codemirror,
button,
mode: 'replaceLine',
runner: function(line, markup) {
let lineNo = codemirror.getLineNumber(line);
let previousLine = codemirror.getLine(lineNo - 1) || '';
let match = previousLine.match(/^(\d+)\./);
let prefix = 1 + (match ? Number(match[1]) : 0);
return `${prefix}${markup}`;
}
});
}
}
}
],
states: [{
code: {
identifier: 'editor',
title: translations.PLUGIN_ADMIN.EDITOR,
label: '<i class="fa fa-fw fa-code"></i>',
action({ codemirror, button, textarea, ui }) {
if (textarea.data('grav-editor-mode') === 'editor') {
button.addClass('editor-active');
}
button.on('click.states.editor', () => {
button.siblings().removeClass('editor-active');
button.addClass('editor-active');
textarea.data('grav-editor-mode', 'editor');
let previewContainer = textarea.data('grav-editor-preview-container');
let content = textarea.parent('.grav-editor-content');
content.addClass('is-active');
ui.navigation.find('.grav-editor-actions').css('visibility', 'visible');
if (previewContainer) {
previewContainer.removeClass('is-active');
}
});
}
}
}, {
preview: {
identifier: 'preview',
title: translations.PLUGIN_ADMIN.PREVIEW,
label: '<i class="fa fa-fw fa-eye"></i>',
modes: ['gfm', 'markdown'],
action({ codemirror, button, textarea, ui }) {
if (textarea.data('grav-editor-mode') === 'preview') {
button.addClass('editor-active');
}
button.on('click.states.preview', () => {
let previewContainer = textarea.data('grav-editor-preview-container');
let content = textarea.parent('.grav-editor-content');
button.siblings().removeClass('editor-active');
button.addClass('editor-active');
textarea.data('grav-editor-mode', 'preview');
if (!previewContainer) {
previewContainer = $('<div class="grav-editor-preview" />');
content.after(previewContainer);
textarea.data('grav-editor-preview-container', previewContainer);
}
previewContainer.css({ height: content.height() });
previewContainer.addClass('is-active');
content.removeClass('is-active');
ui.navigation.find('.grav-editor-actions').css('visibility', 'hidden');
let url = `${textarea.data('grav-urlpreview')}/task${config.param_sep}processmarkdown`;
let params = textarea.closest('form').serializeArray();
let body = {};
params.map((obj) => { body[obj.name] = obj.value; });
request(url, {
method: 'post',
body
}, (response) => previewContainer.html(response.preview));
});
}
}
}, {
fullscreen: {
identifier: 'fullscreen',
title: translations.PLUGIN_ADMIN.FULLSCREEN,
label: '<i class="fa fa-fw fa-expand"></i>',
action({ codemirror, button, textarea }) {
button.on('click.editor.fullscreen', () => {
let container = textarea.closest('.grav-editor');
let wrapper = codemirror.getWrapperElement();
let contentWrapper = $('.content-wrapper');
if (!container.hasClass('grav-editor-fullscreen')) {
textarea.data('fullScreenRestore', {
scrollTop: global.pageYOffset,
scrollLeft: global.pageXOffset,
width: wrapper.style.width,
height: wrapper.style.height
});
wrapper.style.width = '';
wrapper.style.height = textarea.parent('.grav-editor-content').height() + 'px';
global.document.documentElement.style.overflow = 'hidden';
let hints = container.find('.grav-editor-toolbar .hint--top');
if (hints) {
hints.removeClass('hint--top').addClass('hint--bottom');
$(hints[hints.length - 1]).addClass('hint--bottom-left');
}
if (contentWrapper) { contentWrapper.css('overflow', 'visible'); }
} else {
global.document.documentElement.style.overflow = '';
let state = textarea.data('fullScreenRestore');
wrapper.style.width = state.width;
wrapper.style.height = state.height;
global.scrollTo(state.scrollLeft, state.scrollTop);
let hints = container.find('.grav-editor-toolbar .hint--bottom');
if (hints) {
hints.removeClass('hint--bottom').addClass('hint--top');
$(hints[hints.length - 1]).removeClass('hint--bottom-left');
}
if (contentWrapper) { contentWrapper.css('overflow', 'auto'); }
}
container.toggleClass('grav-editor-fullscreen');
setTimeout(() => {
codemirror.refresh();
// this.preview.parent().css('height', this.code.height());
$(global).trigger('resize');
}, 5);
});
}
}
}]
};

View File

@@ -0,0 +1,12 @@
import $ from 'jquery';
$(document).on('change', '[data-grav-elements] select', (event) => {
const target = $(event.currentTarget);
const value = target.val();
const id = target.closest('[data-grav-elements]').data('gravElements');
$(`[id^="${id}_"]`).css('display', 'none');
$(`[id="${id}__${value}"]`).css('display', 'inherit');
});
$('[data-grav-elements] select').trigger('change');

View File

@@ -0,0 +1,144 @@
import $ from 'jquery';
import { config, uri_params } from 'grav-config';
import request from '../../utils/request';
// const insertTextAt = (string, index, text) => [string.slice(0, index), text, string.slice(index)].join('');
export default class FilePickerField {
constructor(options) {
this.items = $();
this.options = Object.assign({}, this.defaults, options);
$('[data-grav-filepicker]').each((index, element) => this.addItem(element));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
_onAddedNodes(event, target/* , record, instance */) {
let fields = $(target).find('[data-grav-filepicker]');
if (!fields.length) { return; }
fields.each((index, field) => {
field = $(field);
if (!~this.items.index(field)) {
this.addItem(field);
}
});
}
addItem(element) {
element = $(element);
this.items = this.items.add(element);
let tag = element.prop('tagName').toLowerCase();
let isInput = tag === 'input' || tag === 'select';
let field = (isInput ? element : element.find('input, select'));
let folder = '';
let thumbs = {};
let onDemand = field.closest('[data-ondemand]').length > 0;
if (!field.length || field.get(0).selectize) { return; }
let getData = function getData(field, callback, mode = 'all') {
let url = config.current_url + `.json/task${config.param_sep}getFilesInFolder`;
let parent = field.closest('[data-grav-filepicker]');
let name = parent.data('name');
let value = parent.data('value');
let params = JSON.stringify(uri_params || '{}');
request(url, {
method: 'post',
body: { name, params }
}, (response) => {
if (typeof response.files === 'undefined') {
return;
}
let data = [];
thumbs = response.thumbs || {};
for (let i = 0; i < response.files.length; i++) {
if (mode === 'selected' && response.files[i] !== value) { continue; }
data.push({ 'name': response.files[i], 'status': 'available', thumb: thumbs[response.files[i]] || '' });
}
for (let i = 0; i < response.pending.length; i++) {
if (mode === 'selected' && response.pending[i] !== value) { continue; }
data.push({ 'name': response.pending[i], 'status': 'pending', thumb: thumbs[response.pending[i]] || '' });
}
folder = response.folder;
callback(data, value);
});
};
let imagesPreview = field.closest('[data-preview-images]').length > 0;
let selectedIsRendered = false;
let renderOption = function renderOption(item, escape) {
let image = '';
if (imagesPreview && folder && (!item.status || item.status === 'available') && item.name.match(/\.(jpg|jpeg|png|gif|webp)$/i)) {
// const fallback2x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@2x');
// const fallback3x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@3x');
const source = thumbs[item.name] || `${config.base_url_relative}/../${folder}/${item.name}`;
// onerror="if(this.src==='${fallback2x}'){this.src='${fallback3x}';}else{this.src='${fallback2x}'}"
image = `<img class="filepicker-field-image" src="${source}" />`;
}
return `<div>
<span class="title">
${image} <span class="name filepicker-field-name">${escape(item.name)}</span>
</span>
</div>`;
};
field.selectize({
plugins: ['required-fix'],
valueField: 'name',
labelField: 'name',
searchField: 'name',
optgroups: [
{$order: 1, value: 'pending', label: 'Pending'},
{$order: 2, value: 'available', label: 'Available'}
],
optgroupField: 'status',
// lockOptgroupOrder: true,
create: false,
preload: false, // 'focus',
render: {
option: function(item, escape) {
return renderOption(item, escape);
},
item: function(item, escape) {
return renderOption(item, escape);
}
},
onInitialize: function() {
if (!onDemand) {
this.load((callback) => getData(field, (data) => callback(data), 'selected'));
}
},
onLoad: function(/* data */) {
if (!selectedIsRendered) {
let name = this.getValue();
this.updateOption(name, { name });
selectedIsRendered = true;
}
},
onFocus: function() {
this.load((callback) => getData(field, (data) => callback(data)));
}
});
}
}
export let Instance = new FilePickerField();

View File

@@ -0,0 +1,402 @@
import $ from 'jquery';
import Dropzone from 'dropzone';
// import EXIF from 'exif-js';
import request from '../../utils/request';
import { config, translations } from 'grav-config';
// translations
const Dictionary = {
dictCancelUpload: translations.PLUGIN_ADMIN.DROPZONE_CANCEL_UPLOAD,
dictCancelUploadConfirmation: translations.PLUGIN_ADMIN.DROPZONE_CANCEL_UPLOAD_CONFIRMATION,
dictDefaultMessage: translations.PLUGIN_ADMIN.DROPZONE_DEFAULT_MESSAGE,
dictFallbackMessage: translations.PLUGIN_ADMIN.DROPZONE_FALLBACK_MESSAGE,
dictFallbackText: translations.PLUGIN_ADMIN.DROPZONE_FALLBACK_TEXT,
dictFileTooBig: translations.PLUGIN_ADMIN.DROPZONE_FILE_TOO_BIG,
dictInvalidFileType: translations.PLUGIN_ADMIN.DROPZONE_INVALID_FILE_TYPE,
dictMaxFilesExceeded: translations.PLUGIN_ADMIN.DROPZONE_MAX_FILES_EXCEEDED,
dictRemoveFile: translations.PLUGIN_ADMIN.DROPZONE_REMOVE_FILE,
dictResponseError: translations.PLUGIN_ADMIN.DROPZONE_RESPONSE_ERROR
};
Dropzone.autoDiscover = false;
Dropzone.options.gravPageDropzone = {};
Dropzone.confirm = (question, accepted, rejected) => {
let doc = $(document);
let modalSelector = '[data-remodal-id="delete-media"]';
let removeEvents = () => {
doc.off('confirmation', modalSelector, accept);
doc.off('cancellation', modalSelector, reject);
$(modalSelector).find('.remodal-confirm').removeClass('pointer-events-disabled');
};
let accept = () => {
accepted && accepted();
removeEvents();
};
let reject = () => {
rejected && rejected();
removeEvents();
};
$.remodal.lookup[$(modalSelector).data('remodal')].open();
doc.on('confirmation', modalSelector, accept);
doc.on('cancellation', modalSelector, reject);
};
const DropzoneMediaConfig = {
timeout: 0,
thumbnailWidth: 200,
thumbnailHeight: 150,
addRemoveLinks: false,
dictDefaultMessage: translations.PLUGIN_ADMIN.DROP_FILES_HERE_TO_UPLOAD.replace(/&lt;/g, '<').replace(/&gt;/g, '>'),
dictRemoveFileConfirmation: '[placeholder]',
previewTemplate: `
<div class="dz-preview dz-file-preview dz-no-editor">
<div class="dz-details">
<div class="dz-filename"><span data-dz-name></span></div>
<div class="dz-size" data-dz-size></div>
<img data-dz-thumbnail />
</div>
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
<div class="dz-success-mark"><span>✔</span></div>
<div class="dz-error-mark"><span>✘</span></div>
<div class="dz-error-message"><span data-dz-errormessage></span></div>
<a class="dz-unset" title="${translations.PLUGIN_ADMIN.UNSET}" href="#" data-dz-unset>${translations.PLUGIN_ADMIN.UNSET}</a>
<a class="dz-remove" title="${translations.PLUGIN_ADMIN.DELETE}" href="javascript:undefined;" data-dz-remove>${translations.PLUGIN_ADMIN.DELETE}</a>
<a class="dz-metadata" title="${translations.PLUGIN_ADMIN.METADATA}" href="#" target="_blank" data-dz-metadata>${translations.PLUGIN_ADMIN.METADATA}</a>
<a class="dz-view" title="${translations.PLUGIN_ADMIN.VIEW}" href="#" target="_blank" data-dz-view>${translations.PLUGIN_ADMIN.VIEW}</a>
</div>`.trim()
};
// global.EXIF = EXIF;
const ACCEPT_FUNC = function(file, done, settings) {
const resolution = settings.resolution;
if (!resolution) return done();
const reader = new FileReader();
let error = '';
const hasMin = (resolution.min && (resolution.min.width || resolution.min.height));
const hasMax = (resolution.max && (resolution.max.width || resolution.max.height));
if (hasMin || (!(settings.resizeWidth || settings.resizeHeight) && hasMax)) {
reader.onload = function(event) {
if (!/image\//.test(file.type)) {
done();
return;
}
const image = new Image();
image.src = event.target.result;
image.onerror = function() {
done(translations.PLUGIN_ADMIN.FILE_ERROR_UPLOAD);
};
image.onload = function() {
if (resolution.min) {
Object.keys(resolution.min).forEach((attr) => {
if (resolution.min[attr] && this[attr] < resolution.min[attr]) {
error += translations.PLUGIN_FORM.RESOLUTION_MIN.replace(/{{attr}}/g, attr).replace(/{{min}}/g, resolution.min[attr]);
}
});
}
if (!(settings.resizeWidth || settings.resizeHeight)) {
if (resolution.max) {
Object.keys(resolution.max).forEach((attr) => {
if (resolution.max[attr] && this[attr] > resolution.max[attr]) {
error += translations.PLUGIN_FORM.RESOLUTION_MAX.replace(/{{attr}}/g, attr).replace(/{{max}}/g, resolution.max[attr]);
}
});
}
}
URL.revokeObjectURL(image.src); // release memory
return error ? done(error) : done();
};
};
reader.readAsDataURL(file);
} else {
return error ? done(error) : done();
}
};
export default class FilesField {
constructor({ container = '.dropzone.files-upload', options = {} } = {}) {
this.container = $(container);
if (!this.container.length) { return; }
this.urls = {};
this.customPost = this.container.data('filePostAdd') || {};
this.options = Object.assign({}, Dictionary, DropzoneMediaConfig, {
klass: this,
url: this.container.data('file-url-add') || config.current_url,
acceptedFiles: this.container.data('media-types'),
init: this.initDropzone
}, this.container.data('dropzone-options'), options);
this.options = Object.assign({}, this.options, {
accept: function(file, done) { ACCEPT_FUNC(file, done, this.options); }
});
this.dropzone = new Dropzone(container, this.options);
this.dropzone.on('complete', this.onDropzoneComplete.bind(this));
this.dropzone.on('success', this.onDropzoneSuccess.bind(this));
this.dropzone.on('addedfile', this.onDropzoneAddedFile.bind(this));
this.dropzone.on('removedfile', this.onDropzoneRemovedFile.bind(this));
this.dropzone.on('sending', this.onDropzoneSending.bind(this));
this.dropzone.on('error', this.onDropzoneError.bind(this));
this.container.on('mouseenter', '[data-dz-view]', (e) => {
const value = JSON.parse(this.container.find('[name][type="hidden"]').val() || '{}');
const target = $(e.currentTarget);
const file = target.parent('.dz-preview').find('.dz-filename');
const filename = encodeURI(file.text());
const URL = Object.keys(value).filter((key) => value[key].name === filename).shift();
target.attr('href', `${config.base_url_simple}/${URL}`);
});
}
initDropzone() {
let files = this.options.klass.container.find('[data-file]');
let dropzone = this;
if (!files.length) { return; }
files.each((index, file) => {
file = $(file);
let data = file.data('file');
let mock = {
name: data.name,
size: data.size,
type: data.type,
status: Dropzone.ADDED,
accepted: true,
url: this.options.url,
removeUrl: data.remove
};
dropzone.files.push(mock);
dropzone.options.addedfile.call(dropzone, mock);
if (mock.type.match(/^image\//)) {
dropzone.options.thumbnail.call(dropzone, mock, data.path);
dropzone.createThumbnailFromUrl(mock, data.path);
}
file.remove();
});
}
getURI() {
return this.container.data('mediaUri') || '';
}
onDropzoneSending(file, xhr, formData) {
if (Object.keys(this.customPost).length) {
Object.keys(this.customPost).forEach((key) => {
formData.append(key, this.customPost[key]);
});
} else {
formData.append('name', this.options.dotNotation);
formData.append('task', 'filesupload');
formData.append('uri', this.getURI());
}
formData.append('admin-nonce', config.admin_nonce);
}
onDropzoneSuccess(file, response, xhr) {
response = typeof response === 'string' ? JSON.parse(response) : response;
if (this.options.reloadPage) {
global.location.reload();
}
// store params for removing file from session before it gets saved
if (response.session) {
file.sessionParams = response.session;
file.removeUrl = this.options.url;
// Touch field value to force a mutation detection
const input = this.container.find('[name][type="hidden"]');
const value = input.val();
input.val(value + ' ');
}
return this.handleError({
file,
data: response,
mode: 'removeFile',
msg: `<p>${translations.PLUGIN_ADMIN.FILE_ERROR_UPLOAD} <strong>{{fileName}}</strong></p>
<pre>${response.message}</pre>`
});
}
onDropzoneComplete(file) {
if (!file.accepted && !file.rejected) {
let data = {
status: 'error',
message: `${translations.PLUGIN_ADMIN.FILE_UNSUPPORTED}: ${file.name.match(/\..+/).join('')}`
};
return this.handleError({
file,
data,
mode: 'removeFile',
msg: `<p>${translations.PLUGIN_ADMIN.FILE_ERROR_ADD} <strong>{{fileName}}</strong></p>
<pre>${data.message}</pre>`
});
}
if (this.options.reloadPage) {
global.location.reload();
}
}
b64_to_utf8(str) {
str = str.replace(/\s/g, '');
return decodeURIComponent(escape(window.atob(str)));
}
onDropzoneAddedFile(file, ...extra) {
return this.dropzone.options.addedfile(file);
}
onDropzoneRemovedFile(file, ...extra) {
if (!file.accepted || file.rejected) { return; }
let url = file.removeUrl || this.urls.delete || this.options.url;
let path = (url || '').match(/path:(.*)\//);
let body = { filename: file.name, uri: this.getURI() };
if (file.sessionParams) {
body.task = 'filessessionremove';
body.session = file.sessionParams;
}
const customPost = this.container.data('filePostRemove') || {};
if (Object.keys(customPost).length) {
body = {};
Object.keys(customPost).forEach((key) => {
body[key] = customPost[key];
});
}
body['filename'] = file.name;
body['admin-nonce'] = config.admin_nonce;
request(url, { method: 'post', body }, () => {
if (!path) { return; }
path = this.b64_to_utf8(path[1]);
let input = this.container.find('[name][type="hidden"]');
let data = JSON.parse(input.val() || '{}');
delete data[path];
input.val(JSON.stringify(data));
});
}
onDropzoneError(file, response, xhr) {
let message = xhr ? response.error.message : response;
$(file.previewElement).find('[data-dz-errormessage]').html(message);
return this.handleError({
file,
data: { status: 'error' },
msg: `<pre>${message}</pre>`
});
}
handleError(options) {
let { file, data, mode, msg } = options;
if (data.status !== 'error' && data.status !== 'unauthorized') { return; }
switch (mode) {
case 'addBack':
if (file instanceof File) {
this.dropzone.addFile.call(this.dropzone, file);
} else {
this.dropzone.files.push(file);
this.dropzone.options.addedfile.call(this.dropzone, file);
this.dropzone.options.thumbnail.call(this.dropzone, file, file.extras.url);
}
break;
case 'removeFile':
default:
if (~this.dropzone.files.indexOf(file)) {
file.rejected = true;
this.dropzone.removeFile.call(this.dropzone, file, { silent: true });
}
break;
}
let modal = $('[data-remodal-id="generic"]');
const cleanName = file.name.replace('<', '&lt;').replace('>', '&gt;');
modal.find('.error-content').html(msg.replace('{{fileName}}', cleanName));
$.remodal.lookup[modal.data('remodal')].open();
}
}
export function UriToMarkdown(uri) {
uri = uri.replace(/@3x|@2x|@1x/, '');
uri = uri.replace(/\(/g, '%28');
uri = uri.replace(/\)/g, '%29');
const title = uri.split('.').slice(0, -1).join('.');
return uri.match(/\.(jpe?g|png|gif|svg|webp|mp4|webm|ogv|mov)$/i) ? `![${title}](${uri} "${title}")` : `[${decodeURI(uri)}](${uri})`;
}
let instances = [];
let cache = $();
const onAddedNodes = (event, target/* , record, instance */) => {
let files = $(target).find('.dropzone.files-upload');
if (!files.length) { return; }
files.each((index, file) => {
file = $(file);
if (!~cache.index(file)) {
addNode(file);
}
});
};
const addNode = (container) => {
container = $(container);
let input = container.find('input[type="file"]');
let settings = container.data('grav-file-settings') || {};
if (settings.accept && ~settings.accept.indexOf('*')) {
settings.accept = [''];
}
let options = {
url: container.data('file-url-add') || (container.closest('form').attr('action') || config.current_url) + '.json',
paramName: settings.paramName || 'file',
dotNotation: settings.name || 'file',
acceptedFiles: settings.accept ? settings.accept.join(',') : input.attr('accept') || container.data('media-types'),
maxFilesize: typeof settings.filesize !== 'undefined' ? settings.filesize : 256,
maxFiles: settings.limit || null,
resizeWidth: settings.resizeWidth || null,
resizeHeight: settings.resizeHeight || null,
resizeQuality: settings.resizeQuality || null,
resolution: settings.resolution || null,
accept: function(file, done) { ACCEPT_FUNC(file, done, settings); }
};
cache = cache.add(container);
container = container[0];
instances.push(new FilesField({ container, options }));
};
export let Instance = (() => {
$('.dropzone.files-upload').each((i, container) => addNode(container));
$('body').on('mutation._grav', onAddedNodes);
return instances;
})();

View File

@@ -0,0 +1,20 @@
import $ from 'jquery';
const Regenerate = (field = '[name="data[folder]"]') => {
const element = $(field);
const title = $('[name="data[header][title]"]');
const slug = $.slugify(title.val(), {custom: {"'": ''}});
element.addClass('highlight').val(slug);
setTimeout(() => element.removeClass('highlight'), 500);
};
$(document).on('click', '[data-regenerate]', (event) => {
const target = $(event.currentTarget);
const field = $(target.data('regenerate'));
Regenerate(field);
});
export default Regenerate;

View File

@@ -0,0 +1,298 @@
import $ from 'jquery';
/* Icon Picker by QueryLoop
* Author: @eliorivero
* URL: http://queryloop.com/
* License: GPLv2
*/
var defaults = {
'mode': 'dialog', // show overlay 'dialog' panel or slide down 'inline' panel
'closeOnPick': true, // whether to close panel after picking or 'no'
'save': 'class', // save icon 'class' or 'code'
'size': '',
'classes': {
'launcher': '', // extra classes for launcher buttons
'clear': 'remove-times', // extra classes for button that removes preview and clears field
'highlight': '', // extra classes when highlighting an icon
'close': '' // extra classes for close button
},
'iconSets': { // example data structure. Used to specify which launchers will be created
'genericon': 'Genericon', // create a launcher to pick genericon icons
'fa': 'FontAwesome' // create a launcher to pick fontawesome icons
}
};
class QL_Icon_Picker {
constructor(element, options) {
this.iconSet = '';
this.iconSetName = '';
this.$field = '';
this.element = element;
this.settings = $.extend({}, defaults, options);
this._defaults = defaults;
this.init();
}
init() {
var $brick = $(this.element);
var pickerId = $brick.data('pickerid');
var $preview = $('<div class="icon-preview icon-preview-' + pickerId + '" />');
this.$field = $brick.find('input');
// Add preview area
this.makePreview($brick, pickerId, $preview);
// Make button to clear field and remove preview
this.makeClear(pickerId, $preview);
// Make buttons that open the panel of icons
this.makeLaunchers($brick, pickerId);
// Prepare display styles, inline and dialog
this.makeDisplay($brick);
}
makePreview($brick, pickerId, $preview) {
var $icon = $('<i />');
var iconValue = this.$field.val();
$preview.prependTo($brick);
$icon.prependTo($preview);
if (iconValue !== '') {
$preview.addClass('icon-preview-on');
$icon.addClass(iconValue);
}
}
makeClear(pickerId, $preview) {
var base = this;
var $clear = $('<a class="remove-icon ' + base.settings.classes.clear + '" />');
// Hide button to remove icon and preview and append it to preview area
$clear.hide().prependTo($preview);
// If there's a icon saved in the field, show remove icon button
if (base.$field.val() !== '') {
$clear.show();
}
$preview.on('click', '.remove-icon', function(e) {
e.preventDefault();
base.$field.val('');
$preview.removeClass('icon-preview-on').find('i').removeClass();
$(this).hide();
});
}
makeDisplay($brick) {
var base = this;
var close = base.settings.classes.close;
var $body = $('body');
var $close = $('<a href="#" class="icon-picker-close"/>');
if (base.settings.mode === 'inline') {
$brick.find('.icon-set').append($close).removeClass('dialog').addClass('ip-inline ' + base.settings.size).parent().addClass('icon-set-wrap');
} else if (base.settings.mode === 'dialog') {
$('.icon-set').addClass('dialog ' + base.settings.size);
if ($('.icon-picker-overlay').length <= 0) {
$body.append('<div class="icon-picker-overlay"/>').append($close);
}
}
$body
.on('click', '.icon-picker-close, .icon-picker-overlay', function(e) {
e.preventDefault();
base.closePicker($brick, $(base.iconSet), base.settings.mode);
})
.on('mouseenter mouseleave', '.icon-picker-close', function(e) {
if (e.type === 'mouseenter') {
$(this).addClass(close);
} else {
$(this).removeClass(close);
}
});
}
makeLaunchers($brick) {
var base = this;
var dataIconSets = $brick.data('iconsets');
var iconSet;
if (typeof dataIconSets === 'undefined') {
dataIconSets = base.settings.iconSets;
}
for (iconSet in dataIconSets) {
if (dataIconSets.hasOwnProperty(iconSet)) {
$brick.append('<a class="launch-icons button ' + base.settings.classes.launcher + '" data-icons="' + iconSet + '">' + dataIconSets[iconSet] + '</a>');
}
}
$brick.find('.launch-icons').on('click', function(e) {
e.preventDefault();
var $self = $(this);
var theseIcons = $self.data('icons');
base.iconSetName = theseIcons;
base.iconSet = '.' + theseIcons + '-set';
// Initialize picker
base.iconPick($brick);
// Show icon picker
base.showPicker($brick, $(base.iconSet), base.settings.mode);
});
}
iconPick($brick) {
var base = this;
var highlight = 'icon-highlight ' + base.settings.classes.highlight;
$(base.iconSet).on('click', 'li', function(e) {
e.preventDefault();
var $icon = $(this);
var icon = $icon.data(base.settings.save);
// Mark as selected
$('.icon-selected').removeClass('icon-selected');
$icon.addClass('icon-selected');
if (base.$field.data('format') === 'short') {
icon = icon.slice(6);
}
// Save icon value to field
base.$field.val(icon);
// Close icon picker
if (base.settings.closeOnPick) {
base.closePicker($brick, $icon.closest(base.iconSet), base.settings.mode);
}
// Set preview
base.setPreview($icon.data('class'));
// Broadcast event passing the selected icon.
$('body').trigger('iconselected.queryloop', icon);
});
$(base.iconSet).on('mouseenter mouseleave', 'li', function(e) {
if (e.type === 'mouseenter') {
$(this).addClass(highlight);
} else {
$(this).removeClass(highlight);
}
});
}
showPicker($brick, $icons, mode) {
if (mode === 'inline') {
$('.icon-set').removeClass('ip-inline-open');
$brick.find($icons).toggleClass('ip-inline-open');
} else if (mode === 'dialog') {
$brick.find('.icon-picker-close').addClass('make-visible');
$brick.find('.icon-picker-overlay').addClass('make-visible');
$icons.addClass('dialog-open');
}
$icons.find('.icon-selected').removeClass('icon-selected');
var selectedIcon = this.$field.val().replace(' ', '.');
if (selectedIcon !== '') {
if (this.settings.save === 'class') {
$icons.find('.' + selectedIcon).addClass('icon-selected');
} else {
$icons.find('[data-code="' + selectedIcon + '"]').addClass('icon-selected');
}
}
// Broadcast event when the picker is shown passing the picker mode.
$('body').trigger('iconpickershow.queryloop', mode);
}
closePicker($brick, $icons, mode) {
// Remove event so they don't fire from a different picker
$(this.iconSet).off('click', 'li');
if (mode === 'inline') {
$brick.find($icons).removeClass('ip-inline-open');
} else if (mode === 'dialog') {
$('.icon-picker-close, .icon-picker-overlay').removeClass('make-visible');
}
// Broadcast event when the picker is closed passing the picker mode.
$('body').trigger('iconpickerclose.queryloop', mode);
$('.icon-set').removeClass('dialog-open');
}
setPreview(preview) {
var $preview = $(this.element).find('.icon-preview');
$preview.addClass('icon-preview-on').find('i').removeClass()
.addClass(this.iconSetName)
.addClass(preview);
$preview.find('a').show();
}
}
/* Grav */
// extend $ with 3rd party QL Icon Picker
$.fn.qlIconPicker = function(options) {
this.each(function() {
if (!$.data(this, 'plugin_qlIconPicker')) {
$.data(this, 'plugin_qlIconPicker', new QL_Icon_Picker(this, options));
}
});
return this;
};
export default class IconpickerField {
constructor(options) {
this.items = $();
this.options = Object.assign({}, this.defaults, options);
$('[data-grav-iconpicker]').each((index, element) => this.addItem(element));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
_onAddedNodes(event, target/* , record, instance */) {
let fields = $(target).find('[data-grav-iconpicker]');
if (!fields.length) { return; }
fields.each((index, field) => {
field = $(field);
if (!~this.items.index(field)) {
this.addItem(field);
}
});
}
addItem(element) {
element = $(element);
this.items = this.items.add(element);
element.find('.icon-picker').qlIconPicker({
'save': 'class'
});
// hack to remove extra icon sets that are just copies
$('.icon-set:not(:first)').remove();
}
}
export let Instance = new IconpickerField();
// Fix to close the dialog when clicking outside
$(document).on('click', (event) => {
const target = $(event.target);
const match = '.icon-set.dialog-open, .launch-icons[data-icons]';
if (!target.is(match) && !target.closest(match).length) {
const dialogs = $('.icon-set.dialog-open');
// skip if there's no dialog open
if (dialogs.length) {
dialogs.each((index, dialog) => {
const picker = $(dialog).siblings('.icon-picker');
const data = picker.data('plugin_qlIconPicker');
data.closePicker(picker, $(data.iconSet), data.settings.mode);
});
}
}
});

View File

@@ -0,0 +1,52 @@
import $ from 'jquery';
document.addEventListener('click', (event) => {
if (document.querySelector('#pages-filters')) {
return true;
}
const wrapper = event.target.closest('.checkboxes.indeterminate');
if (wrapper) {
event.preventDefault();
const checkbox = wrapper.querySelector('input[type="checkbox"]:not([disabled])');
const checkStatus = wrapper.dataset._checkStatus;
wrapper.classList.remove('status-checked', 'status-unchecked', 'status-indeterminate');
switch (checkStatus) {
// checked, going indeterminate
case '1':
wrapper.dataset._checkStatus = '2';
checkbox.indeterminate = true;
checkbox.checked = false;
checkbox.value = 0;
wrapper.classList.add('status-indeterminate');
break;
// indeterminate, going unchecked
case '2':
wrapper.dataset._checkStatus = '0';
checkbox.indeterminate = false;
checkbox.checked = false;
checkbox.value = '';
wrapper.classList.add('status-unchecked');
break;
// unchecked, going checked
case '0':
default:
wrapper.dataset._checkStatus = '1';
checkbox.indeterminate = false;
checkbox.checked = true;
checkbox.value = 1;
wrapper.classList.add('status-checked');
break;
}
// const input = new CustomEvent('input', { detail: { target: checkbox }});
// document.dispatchEvent(input);
$(checkbox).trigger('input');
}
});
(document.querySelectorAll('input[type="checkbox"][indeterminate="true"]') || []).forEach((input) => { input.indeterminate = true; });

View File

@@ -0,0 +1,76 @@
import FilepickerField, { Instance as FilepickerFieldInstance } from './filepicker';
import SelectizeField, { Instance as SelectizeFieldInstance } from './selectize';
import ArrayField, { Instance as ArrayFieldInstance } from './array';
import CollectionsField, { Instance as CollectionsFieldInstance } from './collections';
import DateTimeField, { Instance as DateTimeFieldInstance } from './datetime';
import EditorField, { Instance as EditorFieldInstance } from './editor';
import ColorpickerField, { Instance as ColorpickerFieldInstance } from './colorpicker';
import FilesField, { Instance as FilesFieldInstance } from './files';
import FolderFieldInstance from './folder';
import SelectUniqueField, { Instance as SelectUniqueInstance } from './selectunique';
import IconpickerField, { Instance as IconpickerInstance } from './iconpicker';
import CronField, { Instance as CronFieldInstance } from './cron';
import ParentsField, { Instances as ParentsFieldInstance } from './parents';
import './acl-picker';
import './permissions';
import './indeterminate';
import './mediapicker';
import './multilevel';
import './text';
import './range';
import './elements';
export default {
FilepickerField: {
FilepickerField,
Instance: FilepickerFieldInstance
},
SelectizeField: {
SelectizeField,
Instance: SelectizeFieldInstance
},
ArrayField: {
ArrayField,
Instance: ArrayFieldInstance
},
CollectionsField: {
CollectionsField,
Instance: CollectionsFieldInstance
},
DateTimeField: {
DateTimeField,
Instance: DateTimeFieldInstance
},
EditorField: {
EditorField,
Instance: EditorFieldInstance
},
ColorpickerField: {
ColorpickerField,
Instance: ColorpickerFieldInstance
},
FilesField: {
FilesField,
Instance: FilesFieldInstance
},
FolderField: {
Regenerate: FolderFieldInstance
},
SelectUniqueField: {
SelectUniqueField,
Instance: SelectUniqueInstance
},
IconpickerField: {
IconpickerField,
Instance: IconpickerInstance
},
CronField: {
CronField,
Instance: CronFieldInstance
},
ParentsField: {
ParentsField,
Instance: ParentsFieldInstance
}
};

View File

@@ -0,0 +1,51 @@
import $ from 'jquery';
import { Instance as pagesTree } from '../../pages/tree';
$(function() {
let modal = '';
let body = $('body');
// Thumb Resizer
$(document).on('input change', '.media-container .media-range', function(event) {
const target = $(event.currentTarget);
const container = target.closest('.remodal');
let cards = container.find('.media-container div.card-item');
let width = target.val() + 'px';
cards.each(function() {
$(this).css('width', width);
});
});
body.on('click', '[data-mediapicker-modal-trigger]', function(event) {
const element = $(event.currentTarget);
let modal_identifier = $(this).data('grav-mediapicker-unique-identifier');
let modal_element = body.find(`[data-remodal-unique-identifier="${modal_identifier}"]`);
modal = $.remodal.lookup[modal_element.data('remodal')];
if (!modal) {
modal_element.remodal();
modal = $.remodal.lookup[modal_element.data('remodal')];
}
modal.open();
modal.dataField = element.find('input');
// load all media
modal_element.find('.js__files').trigger('fillView');
setTimeout(() => pagesTree.reload(), 100);
});
/* handle media modal click actions */
body.on('click', '[data-remodal-mediapicker] .media-container.in-modal .admin-media-details a', (event) => {
event.preventDefault();
event.stopPropagation();
let val = $(event.target).parents('.js__media-element').data('file-url');
let string = val.replace(/ /g, '%20');
modal.dataField.val(string);
modal.close();
});
});

View File

@@ -0,0 +1,281 @@
import $ from 'jquery';
$(function() {
const getField = function getField(level, name) {
let levelMargin = level * 50;
let top = (level === 0 ? 'top' : '');
let the_name = 'name="' + name + '"';
if (level === 0) {
// top
the_name = 'data-attr-name="' + name + '"';
}
const marginDir = window.getComputedStyle(document.body).direction === 'ltr' ? 'margin-left' : 'margin-right';
let field = `
<div class="element-wrapper">
<div class="form-row array-field-value_only js__multilevel-field ${top}"
data-grav-array-type="row">
<input
type="text"
${the_name}
placeholder="Enter value"
style="${marginDir}: ${levelMargin}px"
value="" />
<span class="fa fa-minus js__remove-item"></span>
<span class="fa fa-plus js__add-sibling hidden" data-level="${level}" ></span>
<span class="fa fa-plus-circle js__add-children hidden" data-level="${level}"></span>
</div>
</div>
`;
return field;
};
const hasChildInputs = function hasChildInputs($element) {
if ($element.attr('name')) {
return false;
}
return true;
};
const getTopItems = function getTopItems(element) {
return $(element + ' .js__multilevel-field.top');
};
const refreshControls = function refreshControls(unique_identifier) {
let element = '[data-grav-multilevel-field]';
if (unique_identifier) {
element = '[data-grav-multilevel-field][data-id="' + unique_identifier + '"]';
}
const hideButtons = function hideButtons() {
$(element + ' .js__add-sibling').addClass('hidden');
$(element + ' .js__add-children').addClass('hidden');
};
const restoreAddSiblingButtons = function restoreAddSiblingButtons() {
$(element + ' .children-wrapper').each(function() {
let elements = $(this).children();
elements.last().each(function() {
let field = $(this);
if (!$(this).hasClass('js__multilevel-field')) {
field = $(this).find('.js__multilevel-field').first();
}
field.find('.js__add-sibling').removeClass('hidden');
});
});
// add sibling to the last top element
$(element + ' .js__multilevel-field.top').last().find('.js__add-sibling').removeClass('hidden');
};
const restoreAddChildrenButtons = function restoreAddChildrenButtons() {
$(element + ' .js__multilevel-field').each(function() {
if ($(this).siblings('.children-wrapper').length === 0 || $(this).siblings('.children-wrapper').find('.js__multilevel-field').length === 0) {
$(this).find('.js__add-children').removeClass('hidden');
}
});
};
const preventRemovingLastTopItem = function preventRemovingLastTopItem() {
let top_items = getTopItems(element);
if (top_items.length === 1) {
top_items.first().find('.js__remove-item').addClass('hidden');
}
};
hideButtons();
restoreAddSiblingButtons();
restoreAddChildrenButtons();
preventRemovingLastTopItem();
};
const changeAllOccurrencesInTree = function($el, current_name, new_name) {
$el.parents('[data-grav-multilevel-field]').find('input').each(function() {
let $input = $(this);
if ($input.attr('name')) {
$input.attr('name', $input.attr('name').replace(current_name, new_name));
}
if ($input.attr('data-attr-name')) {
$input.attr('data-attr-name', $input.attr('data-attr-name').replace(current_name, new_name));
}
});
};
$(document).ready(function() {
refreshControls();
});
$(document).on('mouseleave', '[data-grav-multilevel-field]', function(event) {
let top_items = getTopItems('[data-id="' + $(this).data('id') + '"]');
let has_top_items_without_children = false;
let element_content = '';
top_items.each(function() {
let item = $(this);
if ($(item).siblings('.children-wrapper').find('input').length === 0) {
has_top_items_without_children = true;
element_content = item.find('input').val();
}
});
if (has_top_items_without_children) {
if (element_content) {
alert('Warning: if you save now, the element ' + element_content + ', without children, will be removed, because it\'s invalid YAML');
} else {
alert('Warning: if you save now, the top elements without children will be removed, because it\'s invalid YAML');
}
}
});
$(document).on('click', '[data-grav-multilevel-field] .js__add-children', function(event) {
let element = $(this);
let unique_container_id = element.closest('.js__multilevel-field').data('id');
let level = element.data('level') + 1;
const getParentOfElement = function getParentOfElement(element) {
let parent = element.closest('.js__multilevel-field').parent().first();
if (parent.find('.children-wrapper').length === 0) {
$(parent).append('<div class="children-wrapper"></div>');
}
parent = parent.find('.children-wrapper').first();
return parent;
};
const getNameFromParentInput = function getNameFromParentInput(parentInput, attr) {
if (parentInput.hasClass('children-wrapper')) {
parentInput = parentInput.siblings('.js__multilevel-field').first().find('input');
}
return parentInput.attr(attr) + '[' + parentInput.val() + ']';
};
const getInputFromChildrenWrapper = function getInputFromChildrenWrapper(parentChildrenWrapper) {
return parentChildrenWrapper.siblings('.js__multilevel-field').first().find('input');
};
let parentChildrenWrapper = getParentOfElement(element);
let parentInput = getInputFromChildrenWrapper(parentChildrenWrapper);
let attr = 'name';
if (parentInput.closest('.js__multilevel-field').hasClass('top')) {
attr = 'data-attr-name';
}
parentInput.attr(attr, parentInput.attr(attr).replace('[]', ''));
let name = getNameFromParentInput(parentInput, attr);
let field = getField(level, name);
$(parentChildrenWrapper).append(field);
refreshControls(unique_container_id);
});
$(document).on('click', '[data-grav-multilevel-field] .js__add-sibling', function(event) {
let element = $(this);
let unique_container_id = element.closest('.js__multilevel-field').data('id');
let level = element.data('level');
element.closest('.children-wrapper').find('.js__add-sibling').addClass('hidden');
let sibling = null;
let is_top = false;
if (element.closest('.js__multilevel-field').hasClass('top')) {
is_top = true;
}
if (is_top) {
sibling = element.closest('.js__multilevel-field').first().find('input').last();
} else {
sibling = element.siblings('input').first();
if (!sibling) {
sibling = element.closest('.children-wrapper').first().find('input').last();
}
}
const getParentOfElement = function getParentOfElement(element) {
let parent = element.closest('.js__multilevel-field').parent().first();
if (!parent.hasClass('element-wrapper')) {
if (parent.find('.element-wrapper').length === 0) {
$(parent).append('<div class="element-wrapper"></div>');
}
parent = parent.find('.element-wrapper').first();
}
return parent;
};
const getNameFromSibling = function getNameFromSibling(parent, sibling, is_top = false) {
let name = sibling.attr('name');
if (hasChildInputs(sibling)) {
let val = sibling.data('attr-name') + '[]';
sibling.removeAttr('name');
return val;
}
let last_index = name.lastIndexOf('[');
let almost_there = name.substr(last_index + 1);
let last_tag = almost_there.substr(0, almost_there.length - 1);
if ($.isNumeric(last_tag)) {
name = name.replace('[' + last_tag + ']', '[' + (parseInt(last_tag, 10) + 1) + ']');
} else {
if (is_top) {
name = name.replace('[' + last_tag + ']', '');
} else {
name = name + '[1]';
// change sibling name attr if necessary
if (sibling.attr('name').slice('-2') !== '[0]') {
changeAllOccurrencesInTree(sibling, sibling.attr('name'), sibling.attr('name') + '[0]');
}
}
}
return name;
};
let parent = getParentOfElement(element);
let name = getNameFromSibling(parent, sibling, is_top);
let field = getField(level, name);
$(field).insertAfter(parent);
refreshControls(unique_container_id);
});
$(document).on('click', '[data-grav-multilevel-field] .js__remove-item', function(event) {
$(this).parents('.element-wrapper').first().remove();
let unique_container_id = $(this).closest('.js__multilevel-field').data('id');
refreshControls(unique_container_id);
});
// Store old value before editing a field
$(document).on('focusin', '[data-grav-multilevel-field] input', function(event) {
$(this).data('current-value', $(this).val());
});
// Handle field edited event
$(document).on('change', '[data-grav-multilevel-field] input', function(event) {
let $el = $(this);
let old_value = $el.data('current-value');
let new_value = $el.val();
let full_name = $el.attr('name') || $el.attr('data-attr-name'); // first-level items have `data-attr-name` instead of `name`
let old_name_attr = full_name + '[' + old_value + ']';
let new_name_attr = full_name + '[' + new_value + ']';
changeAllOccurrencesInTree($el, old_name_attr, new_name_attr);
});
});

View File

@@ -0,0 +1,270 @@
import $ from 'jquery';
import Finder from '../../utils/finderjs';
import { config as gravConfig } from 'grav-config';
let XHRUUID = 0;
export const Instances = {};
export default class Parents {
constructor(container, field, data) {
this.container = $(container);
this.fieldName = field.attr('name');
this.field = $(`[name="${this.fieldName}"]`);
this.data = data;
this.parentLabel = $(`[data-parents-field-label="${this.fieldName}"]`);
this.parentName = $(`[data-parents-field-name="${this.fieldName}"]`);
const dataLoad = this.dataLoad;
this.finder = new Finder(
this.container,
(parent, callback) => {
return dataLoad.call(this, parent, callback);
},
{
labelKey: 'name',
defaultPath: this.field.val(),
createItemContent: function(item) {
return Parents.createItemContent(this.config, item);
}
}
);
/*
this.finder.$emitter.on('leaf-selected', (item) => {
console.log('selected', item);
this.finder.emit('create-column', () => this.createSimpleColumn(item));
});
this.finder.$emitter.on('item-selected', (selected) => {
console.log('selected', selected);
// for future use only - create column-card creation for file with details like in macOS finder
// this.finder.$emitter('create-column', () => this.createSimpleColumn(selected));
}); */
this.finder.$emitter.on('column-created', () => {
this.container[0].scrollLeft = this.container[0].scrollWidth - this.container[0].clientWidth;
});
}
static createItemContent(config, item) {
const frag = document.createDocumentFragment();
const label = $(`<span title="${item[config.labelKey]}" />`);
const infoContainer = $('<span class="info-container" />');
const iconPrepend = $('<i />');
const iconAppend = $('<i />');
const badge = $('<span class="badge" />');
const prependClasses = ['fa'];
const appendClasses = ['fa'];
// prepend icon
if (item.children || item.type === 'dir') {
prependClasses.push('fa-folder');
} else if (item.type === 'root') {
prependClasses.push('fa-sitemap');
} else if (item.type === 'file') {
prependClasses.push('fa-file-o');
}
iconPrepend.addClass(prependClasses.join(' '));
// text label
label.text(item[config.labelKey]).prepend(iconPrepend);
label.appendTo(frag);
// append icon
if (item.children || item['has-children']) {
appendClasses.push('fa-caret-right');
badge.text(item.size || item.count || 0);
badge.appendTo(infoContainer);
}
iconAppend.addClass(appendClasses.join(' '));
iconAppend.appendTo(infoContainer);
infoContainer.appendTo(frag);
return frag;
}
static createLoadingColumn() {
return $(`
<div class="fjs-col leaf-col" style="overflow: hidden;">
<div class="leaf-row">
<div class="grav-loading"><div class="grav-loader">Loading...</div></div>
</div>
</div>
`);
}
static createErrorColumn(error) {
return $(`
<div class="fjs-col leaf-col" style="overflow: hidden;">
<div class="leaf-row error">
<i class="fa fa-fw fa-warning"></i>
<span>${error}</span>
</div>
</div>
`);
}
createSimpleColumn(item) {}
dataLoad(parent, callback) {
if (!parent) {
return callback(this.data);
}
if (parent.type !== 'dir' || !parent['has-children']) {
return false;
}
const UUID = ++XHRUUID;
this.startLoader();
$.ajax({
url: `${gravConfig.current_url}`,
method: 'post',
data: Object.assign({}, getExtraFormData(this.container), {
route: b64_encode_unicode(parent.value),
field: this.field.data('fieldName'),
action: 'getLevelListing',
'admin-nonce': gravConfig.admin_nonce
}),
success: (response) => {
this.stopLoader();
if (response.status === 'error') {
this.finder.$emitter.emit('create-column', Parents.createErrorColumn(response.message)[0]);
return false;
}
// stale request
if (UUID !== XHRUUID) {
return false;
}
return callback(response.data);
}
});
}
startLoader() {
this.loadingIndicator = Parents.createLoadingColumn();
this.finder.$emitter.emit('create-column', this.loadingIndicator[0]);
return this.loadingIndicator;
}
stopLoader() {
return this.loadingIndicator && this.loadingIndicator.remove();
}
}
export const b64_encode_unicode = (str) => {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode('0x' + p1);
}));
};
export const b64_decode_unicode = (str) => {
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
};
const getExtraFormData = (container) => {
let form = container.closest('form');
if (container.closest('[data-remodal-id]').length) {
form = $('form#blueprints');
}
const data = {};
const unique_id = form.find('[name="__unique_form_id__"]');
data['__form-name__'] = form.find('[name="__form-name__"]').val();
data['form-nonce'] = form.find('[name="form-nonce"]').val();
if (unique_id.length) {
data['__unique_form_id__'] = unique_id.val();
}
return data;
};
$(document).on('click', '[data-parents]', (event) => {
event.preventDefault();
event.stopPropagation();
const target = $(event.currentTarget);
let field = target.closest('.parents-wrapper').find('input[name]');
let fieldName = field.attr('name');
if (!field.length) {
fieldName = target.data('parents');
field = $(`[name="${target.data('parents')}"]`).first();
}
const modal = $(`[data-remodal-id="${target.data('remodalTarget') || 'parents'}"]`);
const loader = modal.find('.grav-loading');
const content = modal.find('.parents-content');
loader.css('display', 'block');
content.html('');
$.ajax({
url: `${gravConfig.current_url}`,
method: 'post',
data: Object.assign({}, getExtraFormData(target), {
route: b64_encode_unicode(field.val()),
field: field.data('fieldName'),
action: 'getLevelListing',
'admin-nonce': gravConfig.admin_nonce,
initial: true
}),
success(response) {
loader.css('display', 'none');
if (response.status === 'error') {
content.html(response.message);
return true;
}
if (!Instances[`${fieldName}-${modal.data('remodalId')}`]) {
Instances[`${fieldName}-${modal.data('remodalId')}`] = new Parents(content, field, response.data);
} else {
Instances[`${fieldName}-${modal.data('remodalId')}`].finder.reload(response.data);
}
modal.data('parents', Instances[`${fieldName}-${modal.data('remodalId')}`]);
modal.data('parents-selectedField', field);
}
});
});
// apply finder selection to field
$(document).on('click', '[data-remodal-id].parents-container [data-parents-select]', (event) => {
const modal = $(event.currentTarget).closest('[data-remodal-id]');
const parents = modal.data('parents');
const selectedField = modal.data('parentsSelectedField');
const finder = parents.finder;
const field = parents.field;
const parentLabel = parents.parentLabel;
const parentName = parents.parentName;
const selection = finder.findLastActive().item[0];
const value = selection._item[finder.config.valueKey];
const name = selection._item[finder.config.labelKey];
if (selectedField.closest('.remodal').length) {
const index = field.index(selectedField);
selectedField.val(value);
$(parentLabel[index]).text(value);
$(parentName[index]).text(name);
} else {
field.val(value);
parentLabel.text(value);
parentName.text(name);
finder.config.defaultPath = value;
}
const remodal = $.remodal.lookup[$(`[data-remodal-id="${modal.data('remodalId')}"]`).data('remodal')];
remodal.close();
});

View File

@@ -0,0 +1,46 @@
import $ from 'jquery';
const body = $('body');
const radioSelector = '.permission-container.parent-section input[type="radio"]';
const handleParent = (event) => {
const target = $(event.currentTarget);
const value = target.val();
const container = target.closest('.parent-section');
const fieldset = container.next('fieldset');
const radios = fieldset.find(`input[type="radio"][value="${value}"]`);
if (container.data('isLocked') !== false) {
container.data('isUpdating', true);
radios.each((index, radio) => {
const ID = radio.id;
$(radio).siblings(`[for="${ID}"]`).trigger('click');
});
container.data('isUpdating', false);
}
};
const boundHandleParent = handleParent.bind(handleParent);
body.on('click', '.permission-container.parent-section label', (event) => {
const target = $(event.currentTarget);
const container = target.closest('.parent-section');
container.data('isLocked', true);
});
body.on('input', radioSelector, boundHandleParent);
body.on('input', '.permissions-container input[type="radio"][data-parent-id]', (event) => {
const target = $(event.currentTarget);
const parent = $(`[for="${target.data('parentId')}"]`);
const container = target.closest('fieldset').prev('.permission-container.parent-section');
if (container.data('isUpdating') === true) {
return true;
}
body.off('input', radioSelector, boundHandleParent);
container.data('isLocked', false);
parent.trigger('click');
body.on('input', radioSelector, boundHandleParent);
});

View File

@@ -0,0 +1,10 @@
import $ from 'jquery';
$(document).on('input', '[type="range"].rangefield, [type="number"].rangefield', (event) => {
const target = $(event.currentTarget);
const type = target.attr('type').toLowerCase();
const sibling = type === 'range' ? 'number' : 'range';
const feedback = target.siblings(`[type="${sibling}"].rangefield`);
feedback.val(target.val());
});

View File

@@ -0,0 +1,66 @@
import $ from 'jquery';
import 'selectize';
import '../../utils/selectize-required-fix';
import '../../utils/selectize-option-click';
const PagesRoute = {
option: function(item, escape) {
const label = escape(item.text).split(' ');
const arrows = label.shift();
const slug = label.shift();
return `<div class="selectize-route-option">
<span class="text-grey">${arrows}</span>
<span>
<span class="text-update">${slug.replace('(', '/').replace(')', '')}</span>
<span>${label.join(' ')}</span>
</span>
</div>`;
}
};
export default class SelectizeField {
constructor(options = {}) {
this.options = Object.assign({}, options);
this.elements = [];
$('[data-grav-selectize]').each((index, element) => this.add(element));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
add(element) {
element = $(element);
if (element.closest('template').length) {
return false;
}
let tag = element.prop('tagName').toLowerCase();
let isInput = tag === 'input' || tag === 'select';
let data = (isInput ? element.closest('[data-grav-selectize]') : element).data('grav-selectize') || {};
let field = (isInput ? element : element.find('input, select'));
if (field.attr('name') === 'data[route]') {
data = $.extend({}, data, { render: PagesRoute });
}
if (!field.length || field.get(0).selectize) { return; }
const plugins = $.merge(data.plugins ? data.plugins : [], ['required-fix']);
field.selectize($.extend({}, data, { plugins }));
this.elements.push(field.data('selectize'));
}
_onAddedNodes(event, target/* , record, instance */) {
let fields = $(target).find('select.fancy, input.fancy, [data-grav-selectize]').filter((index, element) => {
return !$(element).closest('template').length;
});
if (!fields.length) { return; }
fields.each((index, field) => this.add(field));
}
}
export let Instance = new SelectizeField();

View File

@@ -0,0 +1,160 @@
import $ from 'jquery';
import forIn from 'mout/object/forIn';
// import { config } from 'grav-config';
const Data = {};
export default class SelectUniqueField {
constructor(options) {
const body = $('body');
this.items = $();
this.options = Object.assign({}, this.defaults, options);
$('[data-select-observe]').each((index, element) => this.addSelect(element)).last().trigger('change', { load: true });
body.on('mutation._grav', this._onAddedNodes.bind(this));
body.on('mutation_removed._grav', this._onRemovedNodes.bind(this));
}
_onAddedNodes(event, target, record, instance) {
let fields = $(target).find('[data-select-observe]');
if (!fields.length) { return; }
fields.each((index, field) => {
field = $(field);
if (!~this.items.index(field)) {
this.addSelect(field);
}
});
}
_onRemovedNodes(event, data/* , instance */) {
const target = $(data.target);
const holder = target.data('collectionHolder');
if (!holder) { return false; }
const node = $(data.mutation.removedNodes);
const value = node.find('[data-select-observe]').val();
if (value) {
Data[holder].state[value] = value;
}
target.find('[data-select-observe]').each((index, field) => {
field = $(field);
if (field.val() !== value) {
this.updateOptions(field);
}
});
}
addSelect(element) {
this.items = this.items.add(element);
element = $(element);
const value = element.attr('value');
const holder = element.closest('[data-collection-holder]').data('collectionHolder');
const options = element.closest('[data-select-unique]').data('selectUnique');
if (!Data[holder]) {
let data = {};
if (Array.isArray(options)) {
options.slice(0).map((item) => { data[item] = item; });
} else {
data = Object.assign({}, options);
}
Data[holder] = { original: null, state: null };
Data[holder].original = Object.assign({}, data);
Data[holder].state = Object.assign({}, data);
}
this.updateOptions(element);
element.data('originalValue', value);
element.on('change', (event, extras) => {
const target = $(event.currentTarget);
if (target.data('dummyChange')) {
target.data('dummyChange', false);
return false;
}
this.refreshOptions(target, extras && extras.load ? null : element.data('originalValue'));
element.data('originalValue', target.val());
});
}
updateOptions(element) {
element = $(element);
const value = element.attr('value');
const holder = element.closest('[data-collection-holder]').data('collectionHolder');
forIn(Data[holder].state, (v, k) => {
const selected = k === value ? 'selected="selected"' : '';
if (element.get(0).selectize) {
const selectize = element.data('selectize');
selectize.removeOption(k);
selectize.addOption({ value: k, text: v });
} else {
element.append(`<option value="${k}" ${selected}>${v}</option>`);
}
if (selected) {
if (element.get(0).selectize) {
const selectize = element.data('selectize');
selectize.setValue(k);
}
delete Data[holder].state[value];
}
});
}
refreshOptions(element, originalValue) {
const value = element.val();
const holder = element.closest('[data-collection-holder]').data('collectionHolder');
delete Data[holder].state[value];
if (originalValue && Data[holder].original[originalValue]) {
Data[holder].state[originalValue] = Data[holder].original[originalValue];
}
this.items.each((index, select) => {
select = $(select);
if (select[0] === element[0]) { return; }
const selectedValue = select.val();
select.data('dummyChange', true);
if (select.get(0).selectize) {
const selectize = select.data('selectize');
if (selectize) {
selectize.clearOptions();
if (selectedValue) {
selectize.addOption({
value: selectedValue,
text: Data[holder].original[selectedValue] || selectedValue
});
}
forIn(Data[holder].state, (v, k) => {
selectize.addOption({ value: k, text: v });
});
selectize.setValue(selectedValue, true);
}
} else {
select.empty();
forIn(Data[holder].state, (v, k) => {
const selected = k === selectedValue ? 'selected="selected"' : '';
select.append(`<option value="${k}" ${selected}>${v}</option>`);
});
}
select.data('dummyChange', false);
});
}
}
export let Instance = new SelectUniqueField();

View File

@@ -0,0 +1,13 @@
import $ from 'jquery';
$(document).ready(function() {
$('.copy-to-clipboard').click(function(event) {
var $tempElement = $('<input>');
$('body').append($tempElement);
$tempElement.val($(this).prev('input').val()).select();
document.execCommand('Copy');
$tempElement.remove();
$(this).attr('data-hint', 'Copied to clipboard!').addClass('hint--left');
});
});

View File

@@ -0,0 +1,139 @@
import $ from 'jquery';
/* Dependencies for checking if changes happened since load on a form
import toastr from '../utils/toastr';
import { translations } from 'grav-config';
import { Instance as FormState } from './state';
*/
export default class Form {
constructor(form) {
this.form = $(form);
if (!this.form.length || this.form.prop('tagName').toLowerCase() !== 'form') { return; }
/* Option for not saving while nothing in a form has changed
this.form.on('submit', (event) => {
if (FormState.equals()) {
event.preventDefault();
toastr.info(translations.PLUGIN_ADMIN.NOTHING_TO_SAVE);
}
}); */
this._attachShortcuts();
this._attachToggleables();
this._attachDisabledFields();
this._submitUncheckedFields();
this.observer = new MutationObserver(this.addedNodes);
this.form.each((index, form) => this.observer.observe(form, { subtree: true, childList: true }));
}
_attachShortcuts() {
// CTRL + S / CMD + S - shortcut for [Save] when available
let saveTask = $('#titlebar [name="task"][value="save"][form="blueprints"]');
if (saveTask.length) {
$(global).on('keydown', function(event) {
const key = String.fromCharCode(event.which).toLowerCase();
if (!event.shiftKey && ((event.ctrlKey && !event.altKey) || event.metaKey) && key === 's') {
event.preventDefault();
saveTask.click();
}
});
}
}
_attachToggleables() {
let query = '[data-grav-field="toggleable"] input[type="checkbox"]';
this.form.on('change', query, (event) => {
let toggle = $(event.target);
let enabled = toggle.is(':checked');
let parent = toggle.closest('.form-field');
let label = parent.find('label.toggleable');
let fields = parent.find('.form-data');
let inputs = fields.find('input, select, textarea, button');
label.add(fields).css('opacity', enabled ? '' : 0.7);
inputs.map((index, input) => {
let isSelectize = input.selectize;
input = $(input);
if (isSelectize) {
isSelectize[enabled ? 'enable' : 'disable']();
} else {
input.prop('disabled', !enabled);
}
});
});
this.form.find(query).trigger('change');
}
_attachDisabledFields() {
let prefix = '.form-field-toggleable .form-data';
let query = [];
['input', 'select', 'label[for]', 'textarea', '.selectize-control'].forEach((item) => {
query.push(`${prefix} ${item}`);
});
this.form.on('mousedown', query.join(', '), (event) => {
let input = $(event.target);
let isFor = input.prop('for');
let isSelectize = (input.hasClass('selectize-control') || input.parents('.selectize-control')).length;
if (isFor) { input = $(`[id="${isFor}"]`); }
if (isSelectize) { input = input.closest('.selectize-control').siblings('select[name]'); }
if (!input.prop('disabled')) { return true; }
let toggle = input.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
toggle.trigger('click');
});
}
_submitUncheckedFields() {
let submitted = false;
this.form.each((index, form) => {
form = $(form);
form.on('submit', () => {
// workaround for MS Edge, submitting multiple forms at the same time
if (submitted) { return false; }
let formId = form.attr('id');
let unchecked = form.find('input[type="checkbox"]:not(:checked):not(:disabled)');
let submit = form.find('[type="submit"]').add(`[form="${formId}"][type="submit"]`);
if (!unchecked.length) { return true; }
submit.addClass('pointer-events-disabled');
unchecked.each((index, element) => {
element = $(element);
let name = element.prop('name');
let fake = $(`<input type="hidden" name="${name}" value="0" />`);
form.append(fake);
});
submitted = true;
return true;
});
});
}
addedNodes(mutations) {
mutations.forEach((mutation) => {
if (mutation.type !== 'childList') { return; }
if (mutation.addedNodes) {
$('body').trigger('mutation._grav', mutation.target, mutation, this);
}
if (mutation.removedNodes) {
$('body').trigger('mutation_removed._grav', { target: mutation.target, mutation }, this);
}
});
}
}
export let Instance = new Form('form#blueprints');

View File

@@ -0,0 +1,16 @@
import FormState, { Instance as FormStateInstance } from './state';
import Form, { Instance as FormInstance } from './form';
import Fields from './fields';
export default {
Form: {
Form,
Instance: FormInstance
},
Fields,
FormState: {
FormState,
Instance: FormStateInstance
}
};

View File

@@ -0,0 +1,150 @@
import $ from 'jquery';
import Immutable from 'immutable';
import immutablediff from 'immutablediff';
import '../utils/jquery-utils';
let FormLoadState = {};
const DOMBehaviors = {
attach() {
this.preventUnload();
this.preventClickAway();
},
preventUnload() {
let selector = '[name="task"][value^="save"], [data-delete-action], [data-flex-safe-action]';
if ($._data(window, 'events') && ($._data(window, 'events').beforeunload || []).filter((event) => event.namespace === '_grav').length) {
return;
}
// Allow some elements to leave the page without native confirmation
$(selector).on('click._grav', function(event) {
$(global).off('beforeunload');
});
// Catch browser uri change / refresh attempt and stop it if the form state is dirty
$(global).on('beforeunload._grav', () => {
if (Instance.equals() === false) {
return 'You have made changes on this page that you have not yet confirmed. If you navigate away from this page you will lose your unsaved changes.';
}
});
},
preventClickAway() {
let selector = 'a[href]:not([href^="#"]):not([target="_blank"]):not([href^="javascript:"])';
if ($._data($(selector).get(0), 'events') && ($._data($(selector).get(0), 'events').click || []).filter((event) => event.namespace === '_grav')) {
return;
}
// Prevent clicking away if the form state is dirty
// instead, display a confirmation before continuing
$(selector).on('click._grav', function(event) {
let isClean = Instance.equals();
if (isClean === null || isClean) { return true; }
event.preventDefault();
let destination = $(this).attr('href');
let modal = $('[data-remodal-id="changes"]');
let lookup = $.remodal.lookup[modal.data('remodal')];
let buttons = $('a.button', modal);
let handler = function(event) {
event.preventDefault();
let action = $(this).data('leave-action');
buttons.off('click', handler);
lookup.close();
if (action === 'continue') {
$(global).off('beforeunload');
global.location.href = destination;
}
};
buttons.on('click', handler);
lookup.open();
});
}
};
export default class FormState {
constructor(options = {
ignore: [],
form_id: 'blueprints'
}) {
this.options = options;
this.refresh();
if (!this.form || !this.fields.length) { return; }
FormLoadState = this.collect();
this.loadState = FormLoadState;
DOMBehaviors.attach();
}
refresh() {
this.form = $(`form#${this.options.form_id}`).filter(':noparents(.remodal)');
this.fields = $(`form#${this.options.form_id} *, [form="${this.options.form_id}"]`).filter(':input:not(.no-form)').filter(':noparents(.remodal)');
return this;
}
collect() {
if (!this.form || !this.fields.length) { return; }
let values = {};
this.refresh().fields.each((index, field) => {
field = $(field);
let name = field.prop('name');
let type = field.prop('type');
let tag = field.prop('tagName').toLowerCase();
let value;
if (name.startsWith('toggleable_') || name === 'data[lang]' || name === 'data[redirect]') {
return;
}
switch (type) {
case 'checkbox':
value = field.is(':checked');
break;
case 'radio':
if (!field.is(':checked')) { return; }
value = field.val();
break;
default:
value = field.val();
}
if (tag === 'select' && value === null) {
value = '';
}
if (Array.isArray(value)) {
value = value.join('|');
}
if (name && !~this.options.ignore.indexOf(name)) {
values[name] = value;
}
});
return Immutable.OrderedMap(values);
}
diff() {
return immutablediff(FormLoadState, this.collect());
}
// When the form doesn't exist or there are no fields, `equals` returns `null`
// for this reason, _NEVER_ check with !Instance.equals(), use Instance.equals() === false
equals() {
if (!this.form || !this.fields.length) { return null; }
return Immutable.is(FormLoadState, this.collect());
}
};
export let Instance = new FormState();
export { DOMBehaviors };