init
This commit is contained in:
42
user/plugins/admin/themes/grav/app/dashboard/backup.js
Normal file
42
user/plugins/admin/themes/grav/app/dashboard/backup.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import $ from 'jquery';
|
||||
import { translations } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
import { Instances as Charts } from './chart';
|
||||
|
||||
$('[data-backup][data-ajax*="backup/"]').on('click', function() {
|
||||
let element = $(this);
|
||||
let url = element.data('ajax');
|
||||
const inDropdown = element.closest('.dropdown-menu');
|
||||
|
||||
(inDropdown.length ? inDropdown : element)
|
||||
.closest('.button-group').find('> button:first')
|
||||
.attr('disabled', 'disabled')
|
||||
.find('> .fa').removeClass('fa-life-ring').addClass('fa-spin fa-refresh');
|
||||
|
||||
request(url, (/* response */) => {
|
||||
if (Charts && Charts.backups) {
|
||||
Charts.backups.updateData({ series: [0, 100] });
|
||||
Charts.backups.element.find('.numeric').html(`0 <em>${translations.PLUGIN_ADMIN.DAYS.toLowerCase()}</em>`);
|
||||
}
|
||||
|
||||
(inDropdown.length ? inDropdown : element)
|
||||
.closest('.button-group').find('> button:first')
|
||||
.removeAttr('disabled')
|
||||
.find('> .fa').removeClass('fa-spin fa-refresh').addClass('fa-life-ring');
|
||||
});
|
||||
});
|
||||
|
||||
$('[data-backup][data-ajax*="backupDelete"]').on('click', function() {
|
||||
let element = $(this);
|
||||
let url = element.data('ajax');
|
||||
const tr = element.closest('tr');
|
||||
tr.addClass('deleting');
|
||||
|
||||
request(url, (response) => {
|
||||
if (response.status === 'success') {
|
||||
tr.remove();
|
||||
} else {
|
||||
tr.removeClass('deleting');
|
||||
}
|
||||
});
|
||||
});
|
||||
49
user/plugins/admin/themes/grav/app/dashboard/cache.js
Normal file
49
user/plugins/admin/themes/grav/app/dashboard/cache.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import $ from 'jquery';
|
||||
import { config } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
|
||||
const getUrl = (type = '') => {
|
||||
if (type) {
|
||||
type = `cleartype:${type}/`;
|
||||
}
|
||||
|
||||
return `${config.base_url_relative}/cache.json/task${config.param_sep}clearCache/${type}admin-nonce${config.param_sep}${config.admin_nonce}`;
|
||||
};
|
||||
|
||||
export default class Cache {
|
||||
constructor() {
|
||||
this.element = $('[data-clear-cache]');
|
||||
$('body').on('click', '[data-clear-cache]', (event) => this.clear(event, event.target));
|
||||
}
|
||||
|
||||
clear(event, element) {
|
||||
let type = '';
|
||||
|
||||
if (event && event.preventDefault) { event.preventDefault(); }
|
||||
if (typeof event === 'string') { type = event; }
|
||||
|
||||
element = element ? $(element) : $(`[data-clear-cache-type="${type}"]`);
|
||||
type = type || $(element).data('clear-cache-type') || '';
|
||||
let url = element.data('clearCache') || getUrl(type);
|
||||
|
||||
this.disable();
|
||||
|
||||
request(url, () => this.enable());
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.element
|
||||
.removeAttr('disabled')
|
||||
.find('> .fa').removeClass('fa-refresh fa-spin fa-retweet').addClass('fa-retweet');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.element
|
||||
.attr('disabled', 'disabled')
|
||||
.find('> .fa').removeClass('fa-retweet').addClass('fa-refresh fa-spin');
|
||||
}
|
||||
}
|
||||
|
||||
let Instance = new Cache();
|
||||
|
||||
export { Instance };
|
||||
138
user/plugins/admin/themes/grav/app/dashboard/chart.js
Normal file
138
user/plugins/admin/themes/grav/app/dashboard/chart.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import $ from 'jquery';
|
||||
import chartist from 'chartist';
|
||||
import { translations } from 'grav-config';
|
||||
import { Instance as gpm } from '../utils/gpm';
|
||||
import { Instance as updates } from '../updates';
|
||||
|
||||
// let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
|
||||
export const defaults = {
|
||||
data: {
|
||||
series: [100, 0]
|
||||
},
|
||||
options: {
|
||||
Pie: {
|
||||
donut: true,
|
||||
donutWidth: 10,
|
||||
startAngle: 0,
|
||||
total: 100,
|
||||
showLabel: false,
|
||||
height: 150,
|
||||
// chartPadding: !isFirefox ? 10 : 25 // workaround for older versions of firefox
|
||||
chartPadding: 5
|
||||
},
|
||||
Bar: {
|
||||
height: 164,
|
||||
chartPadding: 20, // workaround for older versions of firefox
|
||||
|
||||
axisX: {
|
||||
showGrid: false,
|
||||
labelOffset: {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
},
|
||||
axisY: {
|
||||
offset: 15,
|
||||
showLabel: true,
|
||||
showGrid: true,
|
||||
labelOffset: {
|
||||
x: 5,
|
||||
y: 5
|
||||
},
|
||||
scaleMinSpace: 25
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default class Chart {
|
||||
constructor(element, options = {}, data = {}) {
|
||||
this.element = $(element) || [];
|
||||
if (!this.element[0]) { return; }
|
||||
|
||||
let type = (this.element.data('chart-type') || 'pie').toLowerCase();
|
||||
this.type = type.charAt(0).toUpperCase() + type.substr(1).toLowerCase();
|
||||
|
||||
options = Object.assign({}, defaults.options[this.type], options);
|
||||
data = Object.assign({}, defaults.data, data);
|
||||
Object.assign(this, {
|
||||
options,
|
||||
data
|
||||
});
|
||||
this.chart = chartist[this.type](this.element.find('.ct-chart').empty()[0], this.data, this.options);
|
||||
this.chart.on('created', () => {
|
||||
this.element.find('.hidden').removeClass('hidden');
|
||||
|
||||
// FIX: workaround for chartist issue not allowing HTML in labels anymore
|
||||
// https://github.com/gionkunz/chartist-js/issues/937
|
||||
this.element.find('.ct-label').each((index, label) => {
|
||||
label = $(label);
|
||||
const text = label.html().replace('<', '<').replace('>', '>');
|
||||
label.html(text);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateData(data) {
|
||||
Object.assign(this.data, data);
|
||||
this.chart.update(this.data);
|
||||
}
|
||||
};
|
||||
|
||||
export class UpdatesChart extends Chart {
|
||||
constructor(element, options = {}, data = {}) {
|
||||
super(element, options, data);
|
||||
|
||||
this.chart.on('draw', (data) => this.draw(data));
|
||||
|
||||
gpm.on('fetched', (response) => {
|
||||
if (!response.payload) { return; }
|
||||
|
||||
let payload = response.payload.grav;
|
||||
let missing = (response.payload.resources.total + (payload.isUpdatable ? 1 : 0)) * 100 / (response.payload.installed + (payload.isUpdatable ? 1 : 0));
|
||||
let updated = 100 - missing;
|
||||
|
||||
this.updateData({ series: [updated, missing] });
|
||||
|
||||
if (response.payload.resources.total) {
|
||||
updates.maintenance('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
draw(data) {
|
||||
if (data.index) { return; }
|
||||
|
||||
let notice = translations.PLUGIN_ADMIN[data.value === 100 ? 'FULLY_UPDATED' : 'UPDATES_AVAILABLE'];
|
||||
this.element.find('.numeric span').text(`${Math.round(data.value)}%`);
|
||||
this.element.find('.js__updates-available-description').html(notice);
|
||||
this.element.find('.hidden').removeClass('hidden');
|
||||
}
|
||||
|
||||
updateData(data) {
|
||||
super.updateData(data);
|
||||
|
||||
// missing updates
|
||||
if (this.data.series[0] < 100) {
|
||||
this.element.closest('#updates').find('[data-update-packages]').fadeIn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let charts = {};
|
||||
|
||||
$('[data-chart-name]').each(function() {
|
||||
let element = $(this);
|
||||
let name = element.data('chart-name') || '';
|
||||
let options = element.data('chart-options') || {};
|
||||
let data = element.data('chart-data') || {};
|
||||
|
||||
if (name === 'updates') {
|
||||
charts[name] = new UpdatesChart(element, options, data);
|
||||
} else {
|
||||
charts[name] = new Chart(element, options, data);
|
||||
}
|
||||
});
|
||||
|
||||
export let Instances = charts;
|
||||
12
user/plugins/admin/themes/grav/app/dashboard/index.js
Normal file
12
user/plugins/admin/themes/grav/app/dashboard/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Chart, { UpdatesChart, Instances } from './chart';
|
||||
import { Instance as Cache } from './cache';
|
||||
import './backup';
|
||||
|
||||
export default {
|
||||
Chart: {
|
||||
Chart,
|
||||
UpdatesChart,
|
||||
Instances
|
||||
},
|
||||
Cache
|
||||
};
|
||||
1
user/plugins/admin/themes/grav/app/dashboard/update.js
Normal file
1
user/plugins/admin/themes/grav/app/dashboard/update.js
Normal file
@@ -0,0 +1 @@
|
||||
// See ../updates/update.js
|
||||
@@ -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');
|
||||
});
|
||||
205
user/plugins/admin/themes/grav/app/forms/fields/array.js
Normal file
205
user/plugins/admin/themes/grav/app/forms/fields/array.js
Normal 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();
|
||||
311
user/plugins/admin/themes/grav/app/forms/fields/collections.js
Normal file
311
user/plugins/admin/themes/grav/app/forms/fields/collections.js
Normal 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();
|
||||
579
user/plugins/admin/themes/grav/app/forms/fields/colorpicker.js
Normal file
579
user/plugins/admin/themes/grav/app/forms/fields/colorpicker.js
Normal 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]');
|
||||
85
user/plugins/admin/themes/grav/app/forms/fields/cron.js
Normal file
85
user/plugins/admin/themes/grav/app/forms/fields/cron.js
Normal 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();
|
||||
63
user/plugins/admin/themes/grav/app/forms/fields/datetime.js
Normal file
63
user/plugins/admin/themes/grav/app/forms/fields/datetime.js
Normal 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();
|
||||
237
user/plugins/admin/themes/grav/app/forms/fields/editor.js
Normal file
237
user/plugins/admin/themes/grav/app/forms/fields/editor.js
Normal 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();
|
||||
@@ -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: '', 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
12
user/plugins/admin/themes/grav/app/forms/fields/elements.js
Normal file
12
user/plugins/admin/themes/grav/app/forms/fields/elements.js
Normal 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');
|
||||
144
user/plugins/admin/themes/grav/app/forms/fields/filepicker.js
Normal file
144
user/plugins/admin/themes/grav/app/forms/fields/filepicker.js
Normal 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();
|
||||
402
user/plugins/admin/themes/grav/app/forms/fields/files.js
Normal file
402
user/plugins/admin/themes/grav/app/forms/fields/files.js
Normal 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(/</g, '<').replace(/>/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('<', '<').replace('>', '>');
|
||||
|
||||
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) ? `` : `[${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;
|
||||
})();
|
||||
20
user/plugins/admin/themes/grav/app/forms/fields/folder.js
Normal file
20
user/plugins/admin/themes/grav/app/forms/fields/folder.js
Normal 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;
|
||||
298
user/plugins/admin/themes/grav/app/forms/fields/iconpicker.js
Normal file
298
user/plugins/admin/themes/grav/app/forms/fields/iconpicker.js
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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; });
|
||||
76
user/plugins/admin/themes/grav/app/forms/fields/index.js
Normal file
76
user/plugins/admin/themes/grav/app/forms/fields/index.js
Normal 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
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
281
user/plugins/admin/themes/grav/app/forms/fields/multilevel.js
Normal file
281
user/plugins/admin/themes/grav/app/forms/fields/multilevel.js
Normal 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);
|
||||
});
|
||||
|
||||
});
|
||||
270
user/plugins/admin/themes/grav/app/forms/fields/parents.js
Normal file
270
user/plugins/admin/themes/grav/app/forms/fields/parents.js
Normal 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();
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
10
user/plugins/admin/themes/grav/app/forms/fields/range.js
Normal file
10
user/plugins/admin/themes/grav/app/forms/fields/range.js
Normal 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());
|
||||
});
|
||||
66
user/plugins/admin/themes/grav/app/forms/fields/selectize.js
Normal file
66
user/plugins/admin/themes/grav/app/forms/fields/selectize.js
Normal 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();
|
||||
160
user/plugins/admin/themes/grav/app/forms/fields/selectunique.js
Normal file
160
user/plugins/admin/themes/grav/app/forms/fields/selectunique.js
Normal 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();
|
||||
13
user/plugins/admin/themes/grav/app/forms/fields/text.js
Normal file
13
user/plugins/admin/themes/grav/app/forms/fields/text.js
Normal 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');
|
||||
});
|
||||
});
|
||||
139
user/plugins/admin/themes/grav/app/forms/form.js
Normal file
139
user/plugins/admin/themes/grav/app/forms/form.js
Normal 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');
|
||||
16
user/plugins/admin/themes/grav/app/forms/index.js
Normal file
16
user/plugins/admin/themes/grav/app/forms/index.js
Normal 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
|
||||
}
|
||||
};
|
||||
150
user/plugins/admin/themes/grav/app/forms/state.js
Normal file
150
user/plugins/admin/themes/grav/app/forms/state.js
Normal 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 };
|
||||
73
user/plugins/admin/themes/grav/app/main.js
Normal file
73
user/plugins/admin/themes/grav/app/main.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// polyfills
|
||||
import '@babel/polyfill';
|
||||
|
||||
import $ from 'jquery';
|
||||
import './utils/remodal';
|
||||
import 'simplebar/dist/simplebar.min.js';
|
||||
import { UriToMarkdown } from './forms/fields/files.js';
|
||||
import GPM, { Instance as gpm } from './utils/gpm';
|
||||
import KeepAlive from './utils/keepalive';
|
||||
import Updates, { Instance as updates, Notifications, Feed } from './updates';
|
||||
import Dashboard from './dashboard';
|
||||
import Pages from './pages';
|
||||
import Forms from './forms';
|
||||
import Cookies from './utils/cookies';
|
||||
import './plugins';
|
||||
import './themes';
|
||||
import MediaFilter, { Instance as MediaFilterInstance} from './media';
|
||||
import toastr from './utils/toastr';
|
||||
import request from './utils/request';
|
||||
import './utils/2fa';
|
||||
import './tools';
|
||||
import './whitelabel';
|
||||
|
||||
// bootstrap jQuery extensions
|
||||
import './utils/bootstrap-transition';
|
||||
import './utils/bootstrap-collapse';
|
||||
import './utils/bootstrap-dropdown';
|
||||
|
||||
// tabs memory
|
||||
import './utils/tabs-memory';
|
||||
|
||||
// changelog
|
||||
import './utils/changelog';
|
||||
|
||||
// Main Sidebar
|
||||
import Sidebar, { Instance as sidebar } from './utils/sidebar';
|
||||
|
||||
// starts the keep alive, auto runs every X seconds
|
||||
KeepAlive.start();
|
||||
|
||||
// global event to catch sidebar_state changes
|
||||
$(global).on('sidebar_state._grav', () => {
|
||||
Object.keys(Dashboard.Chart.Instances).forEach((chart) => {
|
||||
setTimeout(() => Dashboard.Chart.Instances[chart].chart.update(), 10);
|
||||
});
|
||||
});
|
||||
|
||||
export default {
|
||||
GPM: {
|
||||
GPM,
|
||||
Instance: gpm
|
||||
},
|
||||
KeepAlive,
|
||||
Dashboard,
|
||||
Pages,
|
||||
Forms,
|
||||
Updates: {
|
||||
Updates,
|
||||
Notifications,
|
||||
Feed,
|
||||
Instance: updates
|
||||
},
|
||||
Sidebar: {
|
||||
Sidebar,
|
||||
Instance: sidebar
|
||||
},
|
||||
MediaFilter: {
|
||||
MediaFilter,
|
||||
Instance: MediaFilterInstance
|
||||
},
|
||||
Scrollbar: { Scrollbar: { deprecated: true }, Instance: { deprecated: true } },
|
||||
Utils: { request, toastr, Cookies, UriToMarkdown }
|
||||
};
|
||||
226
user/plugins/admin/themes/grav/app/media/index.js
Normal file
226
user/plugins/admin/themes/grav/app/media/index.js
Normal file
@@ -0,0 +1,226 @@
|
||||
import $ from 'jquery';
|
||||
import { config, uri_params } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
|
||||
export default class Filter {
|
||||
constructor() {
|
||||
this.URI = `${config.base_url_relative}/media-manager/`;
|
||||
}
|
||||
|
||||
filter(name, value) {
|
||||
let filtered = [];
|
||||
let keys = Object.keys(uri_params);
|
||||
if (!~keys.indexOf(name)) { keys.push(name); }
|
||||
|
||||
keys.forEach((key) => {
|
||||
let filter = Filter.cleanValue(key === name ? value : uri_params[key]);
|
||||
if (filter !== '*') {
|
||||
filtered.push(`${key}${config.param_sep}${filter}`);
|
||||
}
|
||||
});
|
||||
|
||||
global.location = this.URI + filtered.join('/');
|
||||
}
|
||||
|
||||
static cleanValue(value) {
|
||||
return encodeURIComponent(value.replace('/', '\\'));
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new Filter();
|
||||
var isLoading = false;
|
||||
|
||||
var filters = {};
|
||||
var global_index = 1;
|
||||
var files_ended = false;
|
||||
const MEDIA_PAGINATION_INTERVAL = 20;
|
||||
|
||||
/* handle changing file type / date filter */
|
||||
$('body').on('change', '.thumbs-list-container select.filter', (event) => {
|
||||
let target = $(event.currentTarget);
|
||||
let filterName = target.data('name');
|
||||
let filterValue = target.val();
|
||||
|
||||
if (filterValue) {
|
||||
filters[filterName] = filterValue;
|
||||
} else {
|
||||
delete filters[filterName];
|
||||
}
|
||||
|
||||
filterFiles();
|
||||
});
|
||||
|
||||
/* initialize media uploader */
|
||||
if ($('.thumbs-list-container .dropzone')[0]) {
|
||||
$('.thumbs-list-container .dropzone')[0].dropzone.on('queuecomplete', function() {
|
||||
let body = {};
|
||||
if (filters.page) { body.page = filters.page; }
|
||||
if (filters.date) { body.date = filters.date; }
|
||||
if (filters.type) { body.type = filters.type; }
|
||||
|
||||
$('.dropzone')[0].dropzone.files.forEach(function(file) { file.previewElement.remove(); });
|
||||
$('.dropzone').first().removeClass('dz-started');
|
||||
|
||||
request(`${config.base_url_relative}/media-manager.json/task${config.param_sep}clearMediaCache`, { method: 'post', body }, () => {
|
||||
filterFiles();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* handle loading media */
|
||||
var loadMedia = function loadMedia(filters, callback) {
|
||||
var url = `${config.base_url_relative}/media.json/tmpl${config.param_sep}media-list-content/index${config.param_sep}${global_index}`;
|
||||
|
||||
if (filters.page) {
|
||||
url += `/page${config.param_sep}${(filters.page).split('/').join('%5C')}`;
|
||||
}
|
||||
if (filters.type && filters.type !== '*') {
|
||||
url += `/type${config.param_sep}${filters.type}`;
|
||||
}
|
||||
if (filters.date && filters.date !== '*') {
|
||||
url += `/date${config.param_sep}${filters.date}`;
|
||||
}
|
||||
|
||||
if (!isLoading) {
|
||||
isLoading = true;
|
||||
|
||||
$('.spinning-wheel').show();
|
||||
$.get(url, function(content) {
|
||||
$('.js__files').append(content);
|
||||
$('.spinning-wheel').hide();
|
||||
$('.media-container .media-range').trigger('change');
|
||||
isLoading = false;
|
||||
global_index++;
|
||||
|
||||
callback(content);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var cleanFilesList = function cleanFilesList() {
|
||||
$('.js__files .card-item').remove();
|
||||
};
|
||||
|
||||
var resetActiveStateInSidebar = function resetActiveStateInSidebar() {
|
||||
$('.pages-list-container .row').removeClass('active'); // clear active state in sidebar
|
||||
};
|
||||
|
||||
var showEmptyState = function showEmptyState() {
|
||||
$('.thumbs-list-container').append('<p class="card-item empty-space">No media found</p>');
|
||||
};
|
||||
|
||||
var filterFiles = function filterFiles() {
|
||||
cleanFilesList();
|
||||
global_index = 0;
|
||||
files_ended = false;
|
||||
$('.empty-space').remove();
|
||||
loadMedia(filters, function(content) {
|
||||
if (!content.trim().length) {
|
||||
showEmptyState();
|
||||
} else {
|
||||
if (!filters.page && (!filters.date || filters.date === '*') && (!filters.type || filters.type === '*')) {
|
||||
$('.js__files').trigger('fillView');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* handle changing page */
|
||||
$('body').on('click', '.pages-list-container .js__page-link', (event) => {
|
||||
var page = $(event.target).data('page');
|
||||
filters['page'] = page;
|
||||
|
||||
$('.media-list-title .page-indicator').html(page); // set indication
|
||||
$('.js__reset-pages-filter').removeClass('hidden'); // activate reset pages icon
|
||||
resetActiveStateInSidebar();
|
||||
$(event.target).parents('.row').addClass('active'); // set active state in sidebar
|
||||
$('.js__file-uploader').removeClass('hidden');
|
||||
|
||||
// customize processing URL, as the page changes dynamically
|
||||
if ($('.dropzone')[0]) {
|
||||
$('.dropzone')[0].dropzone.on('processing', function(file) {
|
||||
this.options.url = `${config.base_url_relative}/media-manager${page}.json/task${config.param_sep}addmedia`;
|
||||
});
|
||||
}
|
||||
|
||||
$('.js__button-clear-media-cache').addClass('hidden');
|
||||
filterFiles();
|
||||
|
||||
disableInfiniteScrolling(); // only infinite scroll on main list, not inside single pages
|
||||
});
|
||||
|
||||
/* handle clearing page filter */
|
||||
$('body').on('click', '.js__reset-pages-filter', (event) => {
|
||||
$('.media-list-title .page-indicator').html('All Pages'); // set indication
|
||||
cleanFilesList();
|
||||
resetActiveStateInSidebar();
|
||||
$('.js__reset-pages-filter').addClass('hidden'); // remove reset pages icon
|
||||
$('.js__file-uploader').addClass('hidden');
|
||||
$('.js__button-clear-media-cache').removeClass('hidden');
|
||||
delete filters['page'];
|
||||
|
||||
filterFiles();
|
||||
});
|
||||
|
||||
/* handle infinite loading */
|
||||
var enableInfiniteScrolling = function enableInfiniteScrolling() {
|
||||
$('.spinning-wheel').hide();
|
||||
var view = $('.mediapicker-scroll').last();
|
||||
|
||||
if (!view.length) { return; }
|
||||
|
||||
$(view).on('scroll', function() {
|
||||
if (($(this).scrollTop() + $(this).innerHeight() + 100) >= $(this)[0].scrollHeight) {
|
||||
fillView();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var loadNextBatch = function loadNextBatch(callback) {
|
||||
if (files_ended) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadMedia({}, function(content) {
|
||||
if (!$(content).length || ((content.split('card-item').length - 1) < MEDIA_PAGINATION_INTERVAL)) {
|
||||
files_ended = true;
|
||||
} else {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
$('.media-container .media-range').trigger('input');
|
||||
});
|
||||
};
|
||||
|
||||
var fillView = function fillView() {
|
||||
if (!$('.js__files').find('.card-item').last().offset()) {
|
||||
setTimeout(function() {
|
||||
// retry later
|
||||
fillView();
|
||||
}, 300);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($('.js__files').find('.card-item').last().offset().top - 1 <= $('.media-container').height()) {
|
||||
loadNextBatch(function() {
|
||||
fillView();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* disable infinite loading */
|
||||
var disableInfiniteScrolling = function disableInfiniteScrolling() {
|
||||
$('.spinning-wheel').hide();
|
||||
$('.content-wrapper').unbind('scroll');
|
||||
};
|
||||
|
||||
$('.js__files').on('fillView', function(event) {
|
||||
// the first batch got the max number of media files, try loading more
|
||||
if (($('.js__files')[0].innerHTML.split('card-item').length - 1) === MEDIA_PAGINATION_INTERVAL) {
|
||||
fillView();
|
||||
enableInfiniteScrolling();
|
||||
}
|
||||
});
|
||||
148
user/plugins/admin/themes/grav/app/pages/filter.js
Normal file
148
user/plugins/admin/themes/grav/app/pages/filter.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import $ from 'jquery';
|
||||
import { config, translations } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
import debounce from 'debounce';
|
||||
import { Instance as pagesTree } from './tree';
|
||||
import 'selectize';
|
||||
import '../utils/selectize-required-fix.js';
|
||||
import '../utils/storage';
|
||||
|
||||
/* @formatter:off */
|
||||
/* eslint-disable */
|
||||
const options = [
|
||||
{ flag: translations.PLUGIN_ADMIN.MODULE, key: 'Module', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.VISIBLE, key: 'Visible', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.ROUTABLE, key: 'Routable', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.PUBLISHED, key: 'Published', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_MODULE, key: 'NonModule', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_VISIBLE, key: 'NonVisible', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_ROUTABLE, key: 'NonRoutable', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_PUBLISHED, key: 'NonPublished', cat: 'mode' }
|
||||
];
|
||||
/* @formatter:on */
|
||||
/* eslint-enable */
|
||||
|
||||
export default class PagesFilter {
|
||||
constructor(filters, search) {
|
||||
this.filters = $(filters);
|
||||
this.search = $(search);
|
||||
this.options = options;
|
||||
this.tree = pagesTree;
|
||||
let storage = JSON.parse(localStorage.getItem('grav:admin:pages:filter') || '{}');
|
||||
|
||||
if (!this.filters.length || !this.search.length) { return; }
|
||||
|
||||
this.labels = this.filters.data('filter-labels');
|
||||
|
||||
this.search.on('input', debounce(() => this.filter(), 250));
|
||||
this.filters.on('change', () => this.filter());
|
||||
|
||||
// restore state
|
||||
if (storage.flags || storage.query) {
|
||||
this.setValues(storage);
|
||||
this.filter();
|
||||
}
|
||||
|
||||
this._initSelectize();
|
||||
}
|
||||
|
||||
filter(value) {
|
||||
let data = { flags: '', query: '' };
|
||||
|
||||
if (typeof value === 'object') {
|
||||
Object.assign(data, value);
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
data.query = value;
|
||||
}
|
||||
if (typeof value === 'undefined') {
|
||||
data.flags = this.filters.val();
|
||||
data.query = this.search.val();
|
||||
}
|
||||
|
||||
if (!Object.keys(data).filter((key) => data[key] !== '').length) {
|
||||
this.resetValues();
|
||||
return;
|
||||
}
|
||||
|
||||
data.flags = data.flags.replace(/(\s{1,})?,(\s{1,})?/g, ',');
|
||||
this.setValues({ flags: data.flags, query: data.query }, 'silent');
|
||||
|
||||
request(`${config.base_url_relative}/pages-filter.json/task${config.param_sep}filterPages`, {
|
||||
method: 'post',
|
||||
body: data
|
||||
}, (response) => {
|
||||
this.refreshDOM(response);
|
||||
});
|
||||
}
|
||||
|
||||
refreshDOM(response) {
|
||||
let items = $('[data-nav-id]');
|
||||
|
||||
if (!response) {
|
||||
items.removeClass('search-match').show();
|
||||
this.tree.restore();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
items.removeClass('search-match').hide();
|
||||
|
||||
response.results.forEach((page) => {
|
||||
let match = items.filter(`[data-nav-id="${page}"]`).addClass('search-match').show();
|
||||
match.parents('[data-nav-id]').addClass('search-match').show();
|
||||
|
||||
this.tree.expand(page, 'no-store');
|
||||
});
|
||||
}
|
||||
|
||||
setValues({ flags = '', query = ''}, silent) {
|
||||
let flagsArray = flags.replace(/(\s{1,})?,(\s{1,})?/g, ',').split(',');
|
||||
if (this.filters.val() !== flags) {
|
||||
let selectize = this.filters.data('selectize');
|
||||
this.filters[selectize ? 'setValue' : 'val'](flagsArray, silent);
|
||||
}
|
||||
if (this.search.val() !== query) { this.search.val(query); }
|
||||
|
||||
localStorage.setItem('grav:admin:pages:filter', JSON.stringify({ flags, query }));
|
||||
}
|
||||
|
||||
resetValues() {
|
||||
this.setValues('', 'silent');
|
||||
this.refreshDOM();
|
||||
}
|
||||
|
||||
_initSelectize() {
|
||||
let extras = {
|
||||
type: this.filters.data('filter-types') || {},
|
||||
access: this.filters.data('filter-access-levels') || {}
|
||||
};
|
||||
|
||||
Object.keys(extras).forEach((cat) => {
|
||||
Object.keys(extras[cat]).forEach((key) => {
|
||||
this.options.push({
|
||||
cat,
|
||||
key,
|
||||
flag: extras[cat][key]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.filters.selectize({
|
||||
maxItems: null,
|
||||
valueField: 'key',
|
||||
labelField: 'flag',
|
||||
searchField: ['flag', 'key'],
|
||||
options: this.options,
|
||||
optgroups: this.labels,
|
||||
optgroupField: 'cat',
|
||||
optgroupLabelField: 'name',
|
||||
optgroupValueField: 'id',
|
||||
optgroupOrder: this.labels.map((item) => item.id),
|
||||
plugins: ['optgroup_columns', 'required-fix']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let Instance = new PagesFilter('input[name="page-filter"]', 'input[name="page-search"]');
|
||||
export { Instance };
|
||||
53
user/plugins/admin/themes/grav/app/pages/index.js
Normal file
53
user/plugins/admin/themes/grav/app/pages/index.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import $ from 'jquery';
|
||||
import Sortable from 'sortablejs';
|
||||
import PageFilters, { Instance as PageFiltersInstance } from './filter';
|
||||
import Page from './page';
|
||||
|
||||
const pad = (n, s) => (`000${n}`).substr(-s);
|
||||
|
||||
// Pages Ordering
|
||||
let Ordering = null;
|
||||
let orderingElement = $('#ordering');
|
||||
if (orderingElement.length) {
|
||||
Ordering = new Sortable(orderingElement.get(0), {
|
||||
filter: '.ignore',
|
||||
onUpdate: function() {
|
||||
/* Old single page index behavior
|
||||
|
||||
let item = $(event.item);
|
||||
let index = orderingElement.children().index(item) + 1;
|
||||
$('[data-order]').val(index);
|
||||
*/
|
||||
|
||||
let indexes = [];
|
||||
const children = orderingElement.children();
|
||||
const padZero = (children.length + '').split('').length;
|
||||
children.each((index, item) => {
|
||||
item = $(item);
|
||||
indexes.push(item.data('id'));
|
||||
item.find('.page-order').text(`${pad(index + 1, padZero)}.`);
|
||||
});
|
||||
|
||||
$('[data-order]').val(indexes.join(','));
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('input', '[name="data[folder]"]', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const activeOrder = $('[data-id][data-active-id]');
|
||||
|
||||
activeOrder.data('id', target.val());
|
||||
|
||||
Ordering.options.onUpdate();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
Ordering,
|
||||
Page,
|
||||
PageFilters: {
|
||||
PageFilters,
|
||||
Instance: PageFiltersInstance
|
||||
}
|
||||
};
|
||||
69
user/plugins/admin/themes/grav/app/pages/page/add.js
Normal file
69
user/plugins/admin/themes/grav/app/pages/page/add.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import $ from 'jquery';
|
||||
import '../../utils/jquery-utils';
|
||||
import request from '../../utils/request';
|
||||
import { config } from 'grav-config';
|
||||
|
||||
let custom = false;
|
||||
let folder = $('[data-remodal-id="modal"] input[name="data[folder]"], [data-remodal-id="module"] input[name="data[folder]"], [data-remodal-id="modal-page-copy"] input[name="data[folder]"]');
|
||||
let title = $('[data-remodal-id="modal"] input[name="data[title]"], [data-remodal-id="module"] input[name="data[title]"], [data-remodal-id="modal-page-copy"] input[name="data[title]"]');
|
||||
let getFields = (type, target) => {
|
||||
target = $(target);
|
||||
let query = `[data-remodal-id="${target.closest('[data-remodal-id]').data('remodal-id')}"]`;
|
||||
|
||||
return {
|
||||
title: type === 'title' ? $(target) : $(`${query} input[name="data[title]"]`),
|
||||
folder: type === 'folder' ? $(target) : $(`${query} input[name="data[folder]"]`)
|
||||
};
|
||||
};
|
||||
|
||||
title.on('input focus blur', (event) => {
|
||||
if (custom) { return true; }
|
||||
let elements = getFields('title', event.currentTarget);
|
||||
|
||||
let slug = $.slugify(elements.title.val(), {custom: { "'": '', '‘': '', '’': '' }});
|
||||
elements.folder.val(slug);
|
||||
});
|
||||
|
||||
folder.on('input', (event) => {
|
||||
let elements = getFields('folder', event.currentTarget);
|
||||
|
||||
let input = elements.folder.get(0);
|
||||
let value = elements.folder.val();
|
||||
let selection = {
|
||||
start: input.selectionStart,
|
||||
end: input.selectionEnd
|
||||
};
|
||||
|
||||
value = value.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
|
||||
elements.folder.val(value);
|
||||
custom = !!value;
|
||||
|
||||
// restore cursor position
|
||||
input.setSelectionRange(selection.start, selection.end);
|
||||
|
||||
});
|
||||
|
||||
folder.on('focus blur', (event) => {
|
||||
getFields('title').title.trigger('input');
|
||||
});
|
||||
|
||||
$(document).on('change', '[name="data[route]"]', (event) => {
|
||||
const rawroute = $(event.currentTarget).val();
|
||||
const pageTemplate = $('[name="data[name]"]');
|
||||
const URI = `${config.base_url_relative}/ajax.json/task${config.param_sep}getChildTypes`;
|
||||
|
||||
if (pageTemplate.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
request(URI, {
|
||||
method: 'post',
|
||||
body: { rawroute }
|
||||
}, (response) => {
|
||||
const type = response.child_type;
|
||||
if (type !== '' && type !== 'default') {
|
||||
pageTemplate.val(type);
|
||||
pageTemplate.data('selectize').setValue(type);
|
||||
}
|
||||
});
|
||||
});
|
||||
15
user/plugins/admin/themes/grav/app/pages/page/delete.js
Normal file
15
user/plugins/admin/themes/grav/app/pages/page/delete.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
$(document).on('click', '[data-remodal-target="delete"]', function() {
|
||||
let confirm = $('[data-remodal-id="delete"] [data-delete-action]');
|
||||
let link = $(this).data('delete-url');
|
||||
|
||||
confirm.data('delete-action', link);
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-delete-action]', function() {
|
||||
let remodal = $.remodal.lookup[$('[data-remodal-id="delete"]').data('remodal')];
|
||||
|
||||
global.location.href = $(this).data('delete-action');
|
||||
remodal.close();
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
$('.disable-after-click').on('click', function() {
|
||||
$(this).addClass('pointer-events-disabled');
|
||||
});
|
||||
47
user/plugins/admin/themes/grav/app/pages/page/index.js
Normal file
47
user/plugins/admin/themes/grav/app/pages/page/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import $ from 'jquery';
|
||||
import './add';
|
||||
import './move';
|
||||
import './delete';
|
||||
import './unset';
|
||||
import './disable-buttons';
|
||||
import PageMedia, { Instance as PageMediaInstances } from './media';
|
||||
import './multilang';
|
||||
|
||||
const switcher = $('input[type="radio"][name="mode-switch"]');
|
||||
|
||||
if (switcher) {
|
||||
let link = switcher.closest(':checked').data('leave-url');
|
||||
let fakeLink = $(`<a href="${link}" />`);
|
||||
|
||||
switcher.parent().append(fakeLink);
|
||||
|
||||
switcher.siblings('label').on('mousedown touchdown', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
// let remodal = $.remodal.lookup[$('[data-remodal-id="changes"]').data('remodal')];
|
||||
let confirm = $('[data-remodal-id="changes"] [data-leave-action="continue"]');
|
||||
|
||||
confirm.one('click', () => {
|
||||
$(global).on('beforeunload._grav');
|
||||
fakeLink.off('click._grav');
|
||||
|
||||
$(event.target).trigger('click');
|
||||
});
|
||||
|
||||
fakeLink.trigger('click._grav');
|
||||
});
|
||||
|
||||
switcher.on('change', (event) => {
|
||||
let radio = $(event.target);
|
||||
link = radio.data('leave-url');
|
||||
|
||||
setTimeout(() => fakeLink.attr('href', link).get(0).click(), 5);
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
Media: {
|
||||
PageMedia,
|
||||
PageMediaInstances
|
||||
}
|
||||
};
|
||||
293
user/plugins/admin/themes/grav/app/pages/page/media.js
Normal file
293
user/plugins/admin/themes/grav/app/pages/page/media.js
Normal file
@@ -0,0 +1,293 @@
|
||||
import $ from 'jquery';
|
||||
import Cookies from '../../utils/cookies.js';
|
||||
import request from '../../utils/request';
|
||||
import FilesField, { UriToMarkdown } from '../../forms/fields/files';
|
||||
import { config, translations } from 'grav-config';
|
||||
import { Instance as Editor } from '../../forms/fields/editor';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
const previewTemplate = `
|
||||
<div class="dz-preview dz-file-preview">
|
||||
<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-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>
|
||||
<a class="dz-insert" title="${translations.PLUGIN_ADMIN.INSERT}" href="javascript:undefined;" data-dz-insert>${translations.PLUGIN_ADMIN.INSERT}</a>
|
||||
</div>`.trim();
|
||||
|
||||
export default class PageMedia extends FilesField {
|
||||
constructor({ container = '#grav-dropzone', options = {} } = {}) {
|
||||
options = Object.assign(options, { previewTemplate });
|
||||
super({ container, options });
|
||||
if (!this.container.length) { return; }
|
||||
|
||||
this.urls = {
|
||||
fetch: `${this.container.data('media-url')}/task${config.param_sep}listmedia`,
|
||||
add: `${this.container.data('media-url')}/task${config.param_sep}addmedia`,
|
||||
delete: `${this.container.data('media-url')}/task${config.param_sep}delmedia`
|
||||
};
|
||||
|
||||
this.dropzone.options.url = this.urls.add;
|
||||
|
||||
if (typeof this.options.fetchMedia === 'undefined' || this.options.fetchMedia) {
|
||||
this.fetchMedia();
|
||||
}
|
||||
|
||||
if (typeof this.options.attachDragDrop === 'undefined' || this.options.attachDragDrop) {
|
||||
this.attachDragDrop();
|
||||
}
|
||||
|
||||
const field = $(`[name="${this.container.data('dropzone-field')}"]`);
|
||||
|
||||
if (field.length) {
|
||||
this.sortable = new Sortable(this.container.get(0), {
|
||||
animation: 150,
|
||||
// forceFallback: true,
|
||||
setData: (dataTransfer, target) => {
|
||||
target = $(target);
|
||||
let uri = encodeURI(target.find('.dz-filename').text());
|
||||
let shortcode = UriToMarkdown(uri);
|
||||
this.dropzone.disable();
|
||||
target.addClass('hide-backface');
|
||||
dataTransfer.effectAllowed = 'copy';
|
||||
dataTransfer.setData('text', shortcode);
|
||||
},
|
||||
onSort: () => {
|
||||
let names = [];
|
||||
this.container.find('[data-dz-name]').each((index, file) => {
|
||||
file = $(file);
|
||||
const name = file.text().trim();
|
||||
names.push(name);
|
||||
});
|
||||
|
||||
field.val(names.join(','));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchMedia() {
|
||||
const order = this.container.closest('.form-field').find('[name="data[header][media_order]"]').val();
|
||||
const body = { uri: this.getURI(), order };
|
||||
let url = this.urls.fetch;
|
||||
|
||||
request(url, { method: 'post', body }, (response) => {
|
||||
let results = response.results;
|
||||
|
||||
Object.keys(results).forEach((name) => {
|
||||
let data = results[name];
|
||||
let mock = { name, size: data.size, accepted: true, extras: data };
|
||||
|
||||
this.dropzone.files.push(mock);
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mock);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
|
||||
});
|
||||
|
||||
this.updateThumbsSize();
|
||||
this.container.find('.dz-preview').prop('draggable', 'true');
|
||||
});
|
||||
}
|
||||
|
||||
onDropzoneSending(file, xhr, formData) {
|
||||
/*
|
||||
// Cannot call super because Safari and IE API don't implement `delete`
|
||||
super.onDropzoneSending(file, xhr, formData);
|
||||
formData.delete('task');
|
||||
*/
|
||||
|
||||
formData.append('name', this.options.dotNotation || file.name);
|
||||
formData.append('admin-nonce', config.admin_nonce);
|
||||
formData.append('uri', this.getURI());
|
||||
}
|
||||
|
||||
onDropzoneComplete(file) {
|
||||
super.onDropzoneComplete(file);
|
||||
if (this.sortable) {
|
||||
this.sortable.options.onSort();
|
||||
}
|
||||
|
||||
// accepted
|
||||
this.updateThumbsSize();
|
||||
this.updateMediaCount();
|
||||
$('.dz-preview').prop('draggable', 'true');
|
||||
}
|
||||
|
||||
onDropzoneAddedFile(file, ...extra) {
|
||||
super.onDropzoneAddedFile(file, extra);
|
||||
|
||||
this.updateThumbsSize();
|
||||
}
|
||||
|
||||
onDropzoneRemovedFile(file, ...extra) {
|
||||
super.onDropzoneRemovedFile(file, ...extra);
|
||||
|
||||
this.updateMediaCount();
|
||||
if (this.sortable) {
|
||||
this.sortable.options.onSort();
|
||||
}
|
||||
}
|
||||
|
||||
updateThumbsSize() {
|
||||
const status = JSON.parse(Cookies.get('grav-admin-pagemedia') || '{}');
|
||||
|
||||
if (status.width) {
|
||||
const input = this.container.closest('.pagemedia-field').find('.media-resizer');
|
||||
updateMediaSizes(input[0], status.width, false);
|
||||
}
|
||||
}
|
||||
|
||||
updateMediaCount() {
|
||||
const element = this.container.closest('.pagemedia-field').find('[data-pagemedia-count]');
|
||||
element.text(`(${this.dropzone.files.length})`);
|
||||
}
|
||||
|
||||
attachDragDrop() {
|
||||
this.container.delegate('[data-dz-insert]', 'click', (e) => {
|
||||
let target = $(e.currentTarget).parent('.dz-preview').find('.dz-filename');
|
||||
let editor = Editor.editors.filter((index, editor) => $(editor).attr('name') === 'data[content]');
|
||||
|
||||
if (editor.length) {
|
||||
editor = editor.data('codemirror');
|
||||
editor.focus();
|
||||
|
||||
let filename = encodeURI(target.text());
|
||||
let shortcode = UriToMarkdown(filename);
|
||||
editor.doc.replaceSelection(shortcode);
|
||||
}
|
||||
});
|
||||
|
||||
this.container.delegate('[data-dz-view]', 'mouseenter', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
let file = target.parent('.dz-preview').find('.dz-filename');
|
||||
let filename = encodeURI(file.text());
|
||||
let URL = target.closest('[data-media-path]').data('media-path');
|
||||
let original = this.dropzone.files.filter((file) => encodeURI(file.name) === filename).shift();
|
||||
|
||||
original = original && ((original.extras && original.extras.original) || encodeURI(original.name));
|
||||
|
||||
target.attr('href', `${URL}/${original}`);
|
||||
});
|
||||
|
||||
this.container.delegate('[data-dz-metadata]', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
const target = $(e.currentTarget);
|
||||
const file = target.parent('.dz-preview').find('.dz-filename');
|
||||
const filename = encodeURI(file.text());
|
||||
const cleanName = file.text().replace('<', '<').replace('>', '>');
|
||||
|
||||
let fileObj = this.dropzone.files.filter((file) => file.name === global.decodeURI(filename)).shift() || {};
|
||||
|
||||
if (!fileObj.extras) {
|
||||
fileObj.extras = { metadata: [] };
|
||||
}
|
||||
|
||||
if (Array.isArray(fileObj.extras.metadata) && !fileObj.extras.metadata.length) {
|
||||
fileObj.extras.metadata = { '': `${cleanName}.meta.yaml doesn't exist` };
|
||||
}
|
||||
|
||||
fileObj = fileObj.extras;
|
||||
|
||||
const modal_element = $('body').find('[data-remodal-id="metadata"]');
|
||||
const modal = $.remodal.lookup[modal_element.data('remodal')];
|
||||
|
||||
modal_element.find('h1 strong').html(cleanName);
|
||||
if (fileObj.url) {
|
||||
modal_element.find('.meta-preview').html(`<img src="${fileObj.url}" />`);
|
||||
}
|
||||
|
||||
const container = modal_element.find('.meta-content').html('<ul />').find('ul');
|
||||
Object.keys(fileObj.metadata).forEach((meta) => {
|
||||
const cleanMeta = fileObj.metadata[meta].replace('<', '<').replace('>', '>');
|
||||
container.append(`<li><strong>${meta ? meta + ':' : ''}</strong> ${cleanMeta}</li>`);
|
||||
});
|
||||
|
||||
modal.open();
|
||||
});
|
||||
|
||||
this.container.delegate('.dz-preview', 'dragstart', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
let uri = encodeURI(target.find('.dz-filename').text());
|
||||
let shortcode = UriToMarkdown(uri);
|
||||
this.dropzone.disable();
|
||||
target.addClass('hide-backface');
|
||||
e.originalEvent.dataTransfer.effectAllowed = 'copy';
|
||||
e.originalEvent.dataTransfer.setData('text', shortcode);
|
||||
});
|
||||
|
||||
this.container.delegate('.dz-preview', 'dragend', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
this.dropzone.enable();
|
||||
target.removeClass('hide-backface');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const updateMediaSizes = (input, width, store = true) => {
|
||||
const storageLocation = input.dataset.storageLocation || 'grav-admin-pagemedia';
|
||||
const status = JSON.parse(Cookies.get(storageLocation) || '{}');
|
||||
|
||||
const height = 150 * width / 200;
|
||||
const media = $(input).closest('.pagemedia-field').find('.dz-details, [data-dz-thumbnail]');
|
||||
|
||||
media.css({ width, height });
|
||||
|
||||
if (store) {
|
||||
const data = Object.assign({}, status, { width });
|
||||
Cookies.set(storageLocation, JSON.stringify(data), { expires: Infinity });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateMediaCollapseStatus = (element, store = true) => {
|
||||
const storageLocation = element.dataset.storageLocation || 'grav-admin-pagemedia';
|
||||
const status = JSON.parse(Cookies.get(storageLocation) || '{}');
|
||||
|
||||
element = $(element);
|
||||
const icon = element.find('i.fa');
|
||||
const container = element.closest('.pagemedia-field');
|
||||
const panel = container.find('.form-data');
|
||||
const slider = container.find('.media-resizer').parent();
|
||||
|
||||
const isCollapsed = !icon.hasClass('fa-chevron-down');
|
||||
const collapsed = !isCollapsed;
|
||||
|
||||
icon.removeClass('fa-chevron-down fa-chevron-right').addClass(isCollapsed ? 'fa-chevron-down' : 'fa-chevron-right');
|
||||
slider[isCollapsed ? 'removeClass' : 'addClass']('hidden');
|
||||
panel[isCollapsed ? 'slideDown' : 'slideUp']();
|
||||
|
||||
if (store) {
|
||||
const data = Object.assign({}, status, { collapsed });
|
||||
Cookies.set(storageLocation, JSON.stringify(data), { expires: Infinity });
|
||||
}
|
||||
};
|
||||
|
||||
$(document).on('input', '.media-resizer', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const width = target.val();
|
||||
|
||||
updateMediaSizes(event.currentTarget, width);
|
||||
});
|
||||
|
||||
$(document).on('click', '.media-collapser', (event) => {
|
||||
updateMediaCollapseStatus(event.currentTarget);
|
||||
});
|
||||
|
||||
$(document).ready(() => {
|
||||
$('.media-resizer').each((index, input) => {
|
||||
const storageLocation = input.dataset.storageLocation || 'grav-admin-pagemedia';
|
||||
const status = JSON.parse(Cookies.get(storageLocation) || '{}');
|
||||
|
||||
if (status.width) {
|
||||
updateMediaSizes(input, status.width, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export let Instance = new PageMedia();
|
||||
63
user/plugins/admin/themes/grav/app/pages/page/move.js
Normal file
63
user/plugins/admin/themes/grav/app/pages/page/move.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
$(document).on('click', '[data-page-move] button[name="task"][value="save"]', (event) => {
|
||||
/* let route = $('form#blueprints:first select[name="data[route]"]');
|
||||
let moveTo = $('[data-page-move] select').val();
|
||||
|
||||
if (route.length && route.val() !== moveTo) {
|
||||
let selectize = route.data('selectize');
|
||||
route.val(moveTo);
|
||||
|
||||
if (selectize) selectize.setValue(moveTo);
|
||||
}*/
|
||||
|
||||
const modal = $(event.currentTarget).closest('[data-remodal-id]');
|
||||
const parents = modal.data('parents') || {};
|
||||
const finder = parents.finder;
|
||||
|
||||
if (!parents || !finder) { return true; }
|
||||
|
||||
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];
|
||||
|
||||
field.val(value);
|
||||
parentLabel.text(value);
|
||||
parentName.text(name);
|
||||
finder.config.defaultPath = value;
|
||||
|
||||
$('<div />').css({
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
zIndex: 15000
|
||||
}).appendTo($('body'));
|
||||
});
|
||||
|
||||
/*
|
||||
$(document).on('click', '[data-remodal-id="parents"] [data-parents-select]', (event) => {
|
||||
const modal = $(event.currentTarget).closest('[data-remodal-id]');
|
||||
const parents = modal.data('parents');
|
||||
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];
|
||||
|
||||
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();
|
||||
});
|
||||
*/
|
||||
22
user/plugins/admin/themes/grav/app/pages/page/multilang.js
Normal file
22
user/plugins/admin/themes/grav/app/pages/page/multilang.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
$('[name="task"][value="saveas"], [name="task"][value="switchlanguage"]').on('mousedown touchstart', (event) => {
|
||||
let fields = ['lang', 'redirect'];
|
||||
let element = $(event.currentTarget);
|
||||
let form = $(`#${element.attr('form')}`);
|
||||
|
||||
if (!form.length) { return; }
|
||||
fields.forEach((field) => {
|
||||
let value = element.attr(field);
|
||||
if (!value) { return; }
|
||||
let input = form.find(`[name="data[${field}]"]`);
|
||||
if (!input.length) {
|
||||
input = $(`<input type="hidden" name="data[${field}]" value="" />`);
|
||||
form.append(input);
|
||||
}
|
||||
|
||||
input.val(value);
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
18
user/plugins/admin/themes/grav/app/pages/page/unset.js
Normal file
18
user/plugins/admin/themes/grav/app/pages/page/unset.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
$(document).on('click', '.dz-unset', function() {
|
||||
|
||||
const file_upload = $(this).closest('.files-upload');
|
||||
$(this).closest('.dz-image-preview').remove();
|
||||
const unset_image = $(this).closest('.dz-image-preview').find('[data-dz-name]').text().trim();
|
||||
const images = JSON.parse(file_upload.find('input[data-grav-field="hidden"]').val()) || {};
|
||||
let image_array = {};
|
||||
|
||||
$.each(images, function(ind, obj) {
|
||||
if (!ind.endsWith(unset_image)) {
|
||||
image_array[ind] = obj;
|
||||
}
|
||||
});
|
||||
|
||||
file_upload.find('input[data-grav-field="hidden"]').val(JSON.stringify(image_array));
|
||||
});
|
||||
125
user/plugins/admin/themes/grav/app/pages/tree.js
Normal file
125
user/plugins/admin/themes/grav/app/pages/tree.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import $ from 'jquery';
|
||||
import '../utils/storage';
|
||||
|
||||
const sessionKey = 'grav:admin:pages';
|
||||
|
||||
if (!sessionStorage.getItem(sessionKey)) {
|
||||
sessionStorage.setItem(sessionKey, '{}');
|
||||
}
|
||||
|
||||
export default class PagesTree {
|
||||
constructor(query, elements = undefined) {
|
||||
this.query = query;
|
||||
this.elements = $(elements !== undefined ? elements : this.query);
|
||||
this.session = JSON.parse(sessionStorage.getItem(sessionKey) || '{}');
|
||||
|
||||
if (!this.elements.length) { return; }
|
||||
|
||||
this.restore();
|
||||
|
||||
this.elements.find('.page-icon').on('click', (event) => this.toggle(event.target));
|
||||
this.elements.data('tree_init', 1);
|
||||
|
||||
$('[data-page-toggleall]').on('click', (event) => {
|
||||
let element = $(event.target).closest('[data-page-toggleall]');
|
||||
let action = element.data('page-toggleall');
|
||||
|
||||
this[action]();
|
||||
});
|
||||
}
|
||||
|
||||
reload() {
|
||||
const elements = $(this.query).filter((index, element) => !$(element).data('tree_init'));
|
||||
if (!elements.length) { return; }
|
||||
this.constructor(this.query, elements);
|
||||
}
|
||||
|
||||
toggle(elements, dontStore = false) {
|
||||
if (typeof elements === 'string') {
|
||||
elements = $(`[data-nav-id="${elements}"]`).find('[data-toggle="children"]');
|
||||
}
|
||||
|
||||
elements = $(elements || this.elements);
|
||||
elements.each((index, element) => {
|
||||
element = $(element);
|
||||
let state = this.getState(element.closest('[data-toggle="children"]'));
|
||||
this[state.isOpen ? 'collapse' : 'expand'](state.id, dontStore);
|
||||
});
|
||||
}
|
||||
|
||||
collapse(elements, dontStore = false) {
|
||||
if (typeof elements === 'string') {
|
||||
elements = $(`[data-nav-id="${elements}"]`).find('[data-toggle="children"]');
|
||||
}
|
||||
|
||||
elements = $(elements || this.elements);
|
||||
elements.each((index, element) => {
|
||||
element = $(element);
|
||||
let state = this.getState(element);
|
||||
|
||||
if (state.isOpen) {
|
||||
state.children.hide();
|
||||
state.icon.removeClass('children-open').addClass('children-closed');
|
||||
if (!dontStore) { delete this.session[state.id]; }
|
||||
}
|
||||
});
|
||||
|
||||
if (!dontStore) { this.save(); }
|
||||
}
|
||||
|
||||
expand(elements, dontStore = false) {
|
||||
if (typeof elements === 'string') {
|
||||
let element = $(`[data-nav-id="${elements}"]`);
|
||||
let parents = element.parents('[data-nav-id]');
|
||||
|
||||
// loop back through parents, we don't want to expand an hidden child
|
||||
if (parents.length) {
|
||||
parents = parents.find('[data-toggle="children"]:first');
|
||||
parents = parents.add(element.find('[data-toggle="children"]:first'));
|
||||
return this.expand(parents, dontStore);
|
||||
}
|
||||
|
||||
elements = element.find('[data-toggle="children"]:first');
|
||||
}
|
||||
|
||||
elements = $(elements || this.elements);
|
||||
elements.each((index, element) => {
|
||||
element = $(element);
|
||||
let state = this.getState(element);
|
||||
|
||||
if (!state.isOpen) {
|
||||
state.children.show();
|
||||
state.icon.removeClass('children-closed').addClass('children-open');
|
||||
if (!dontStore) { this.session[state.id] = 1; }
|
||||
}
|
||||
});
|
||||
|
||||
if (!dontStore) { this.save(); }
|
||||
}
|
||||
|
||||
restore() {
|
||||
this.collapse(null, true);
|
||||
|
||||
Object.keys(this.session).forEach((key) => {
|
||||
this.expand(key, 'no-store');
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
return sessionStorage.setItem(sessionKey, JSON.stringify(this.session));
|
||||
}
|
||||
|
||||
getState(element) {
|
||||
element = $(element);
|
||||
|
||||
return {
|
||||
id: element.closest('[data-nav-id]').data('nav-id'),
|
||||
children: element.closest('li.page-item').find('ul:first'),
|
||||
icon: element.find('.page-icon'),
|
||||
get isOpen() { return this.icon.hasClass('children-open'); }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let Instance = new PagesTree('[data-toggle="children"]');
|
||||
export { Instance };
|
||||
92
user/plugins/admin/themes/grav/app/plugins/index.js
Normal file
92
user/plugins/admin/themes/grav/app/plugins/index.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import $ from 'jquery';
|
||||
import packages from '../utils/packages';
|
||||
import camelCase from 'mout/string/camelCase';
|
||||
import debounce from 'debounce';
|
||||
import contains from 'mout/string/contains';
|
||||
|
||||
// Plugins sliders details
|
||||
$('.gpm-name, .gpm-actions').on('click', function(e) {
|
||||
let element = $(this);
|
||||
let target = $(e.target);
|
||||
let tag = target.prop('tagName').toLowerCase();
|
||||
|
||||
if (tag === 'a' || element.parent('a').length || target.parent('a').length) { return true; }
|
||||
|
||||
let wrapper = element.siblings('.gpm-details').find('.table-wrapper');
|
||||
|
||||
wrapper.slideToggle({
|
||||
duration: 350,
|
||||
complete: () => {
|
||||
let visible = wrapper.is(':visible');
|
||||
wrapper
|
||||
.closest('tr')
|
||||
.find('.gpm-details-expand i')
|
||||
.removeClass('fa-chevron-' + (visible ? 'down' : 'up'))
|
||||
.addClass('fa-chevron-' + (visible ? 'up' : 'down'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Removing plugin
|
||||
$(document).on('click', '[data-plugin-action="remove-package"]', (event) => {
|
||||
packages.handleRemovingPackage('plugin', event);
|
||||
});
|
||||
|
||||
// Reinstall plugin
|
||||
$(document).on('click', '[data-plugin-action="reinstall-package"]', (event) => {
|
||||
packages.handleReinstallPackage('plugin', event);
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-plugin-action="remove-dependency-package"]', (event) => {
|
||||
packages.handleRemovingDependency('plugin', event);
|
||||
});
|
||||
|
||||
// Trigger the add new plugin / update plugin modal
|
||||
$(document).on('click', '[data-plugin-action="start-package-installation"]', (event) => {
|
||||
packages.handleGettingPackageDependencies('plugin', event, 'install');
|
||||
});
|
||||
|
||||
// Trigger the update all plugins modal
|
||||
$(document).on('click', '[data-plugin-action="start-packages-update"]', (event) => {
|
||||
packages.handleGettingPackageDependencies('plugin', event);
|
||||
});
|
||||
|
||||
// Install a plugin dependencies and the plugin
|
||||
$(document).on('click', '[data-plugin-action="install-dependencies-and-package"]', (event) => {
|
||||
packages.handleInstallingDependenciesAndPackage('plugin', event);
|
||||
});
|
||||
|
||||
// Install a plugin
|
||||
$(document).on('click', '[data-plugin-action="install-package"]', (event) => {
|
||||
packages.handleInstallingPackage('plugin', event);
|
||||
});
|
||||
|
||||
// Sort plugins/themes dropdown
|
||||
$(document).on('change', '.sort-actions select', (event) => {
|
||||
let direction = $('.sort-actions .sort-icon .fa').hasClass('fa-sort-amount-desc') ? 'desc' : 'asc';
|
||||
let sorting = $(event.currentTarget).val();
|
||||
|
||||
packages.Sort[camelCase(`by-${sorting}`)](direction);
|
||||
});
|
||||
|
||||
// Sort plugins/themes icon
|
||||
$(document).on('click', '.sort-icon', (event) => {
|
||||
let icon = $(event.currentTarget).find('.fa');
|
||||
let current = icon.hasClass('fa-sort-amount-asc') ? 'asc' : 'desc';
|
||||
let opposite = current === 'asc' ? 'desc' : 'asc';
|
||||
|
||||
icon.removeClass(`fa-sort-amount-${current}`).addClass(`fa-sort-amount-${opposite}`);
|
||||
$('.sort-actions select').trigger('change');
|
||||
});
|
||||
|
||||
// Filter plugin/theme
|
||||
$(document).on('input', '[data-gpm-filter]', debounce((event) => {
|
||||
let value = $($(event.currentTarget)).val();
|
||||
let items = $('[data-gpm-plugin], [data-gpm-theme]');
|
||||
|
||||
items.hide().filter((index, item) => {
|
||||
item = $(item);
|
||||
|
||||
return contains(item.data('gpm-plugin'), value) || contains(item.data('gpm-theme'), value) || contains(item.data('gpm-name').toLowerCase(), value.toLowerCase());
|
||||
}).show();
|
||||
}, 250));
|
||||
46
user/plugins/admin/themes/grav/app/themes/index.js
Normal file
46
user/plugins/admin/themes/grav/app/themes/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import $ from 'jquery';
|
||||
import packages from '../utils/packages';
|
||||
|
||||
// Themes Switcher Warning
|
||||
$(document).on('mousedown', '[data-remodal-target="theme-switch-warn"]', (event) => {
|
||||
let name = $(event.target).closest('[data-gpm-theme]').find('.gpm-name a:first').text();
|
||||
let remodal = $('.remodal.theme-switcher');
|
||||
|
||||
remodal.find('strong').text(name);
|
||||
remodal.find('.button.continue').attr('href', $(event.target).attr('href'));
|
||||
});
|
||||
|
||||
// Removing theme
|
||||
$(document).on('click', '[data-theme-action="remove-package"]', (event) => {
|
||||
packages.handleRemovingPackage('theme', event);
|
||||
});
|
||||
|
||||
// Reinstall theme
|
||||
$(document).on('click', '[data-theme-action="reinstall-package"]', (event) => {
|
||||
packages.handleReinstallPackage('theme', event);
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-theme-action="remove-dependency-package"]', (event) => {
|
||||
packages.handleRemovingDependency('theme', event);
|
||||
});
|
||||
|
||||
// Opened the add new theme / update theme modal
|
||||
$(document).on('click', '[data-theme-action="start-package-installation"]', (event) => {
|
||||
packages.handleGettingPackageDependencies('theme', event, 'install');
|
||||
});
|
||||
|
||||
// Trigger the update all themes modal
|
||||
$(document).on('click', '[data-theme-action="start-packages-update"]', (event) => {
|
||||
packages.handleGettingPackageDependencies('theme', event);
|
||||
});
|
||||
|
||||
// Install a theme dependencies and the theme
|
||||
$(document).on('click', '[data-theme-action="install-dependencies-and-package"]', (event) => {
|
||||
packages.handleInstallingDependenciesAndPackage('theme', event);
|
||||
});
|
||||
|
||||
// Install a theme
|
||||
$(document).on('click', '[data-theme-action="install-package"]', (event) => {
|
||||
packages.handleInstallingPackage('theme', event);
|
||||
});
|
||||
|
||||
1
user/plugins/admin/themes/grav/app/tools/index.js
Normal file
1
user/plugins/admin/themes/grav/app/tools/index.js
Normal file
@@ -0,0 +1 @@
|
||||
import './logs';
|
||||
14
user/plugins/admin/themes/grav/app/tools/logs.js
Normal file
14
user/plugins/admin/themes/grav/app/tools/logs.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import $ from 'jquery';
|
||||
import { setParam } from 'mout/queryString';
|
||||
|
||||
const prepareQuery = (key, value) => {
|
||||
return setParam(global.location.href, key, value);
|
||||
};
|
||||
|
||||
$(document).on('change', '.logs-content .block-select select[name]', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const name = target.attr('name');
|
||||
const value = target.val();
|
||||
|
||||
global.location.href = prepareQuery(name, value);
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import $ from 'jquery';
|
||||
import request from '../utils/request';
|
||||
|
||||
const switcher = $('input[type="radio"][name="channel-switch"]');
|
||||
|
||||
if (switcher) {
|
||||
switcher.on('change', (event) => {
|
||||
let radio = $(event.target);
|
||||
let url = `${radio.parent('[data-url]').data('url')}`;
|
||||
|
||||
request(url, {
|
||||
method: 'post',
|
||||
body: {
|
||||
task: 'gpmRelease',
|
||||
release: radio.val()
|
||||
}
|
||||
},
|
||||
(response) => {
|
||||
if (response.reload) {
|
||||
global.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
26
user/plugins/admin/themes/grav/app/updates/check.js
Normal file
26
user/plugins/admin/themes/grav/app/updates/check.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import $ from 'jquery';
|
||||
import { Instance as gpm } from '../utils/gpm';
|
||||
import { translations } from 'grav-config';
|
||||
import toastr from '../utils/toastr';
|
||||
|
||||
// Check for updates trigger
|
||||
$('[data-gpm-checkupdates]').on('click', function() {
|
||||
let element = $(this);
|
||||
element.find('i').addClass('fa-spin');
|
||||
|
||||
gpm.fetch((response) => {
|
||||
element.find('i').removeClass('fa-spin');
|
||||
let payload = response.payload;
|
||||
|
||||
if (!payload) { return; }
|
||||
if (!payload.grav.isUpdatable && !payload.resources.total) {
|
||||
toastr.success(translations.PLUGIN_ADMIN.EVERYTHING_UP_TO_DATE);
|
||||
} else {
|
||||
var grav = payload.grav.isUpdatable ? 'Grav v' + payload.grav.available : '';
|
||||
var resources = payload.resources.total ? payload.resources.total + ' ' + translations.PLUGIN_ADMIN.UPDATES_ARE_AVAILABLE : '';
|
||||
|
||||
if (!resources) { grav += ' ' + translations.PLUGIN_ADMIN.IS_AVAILABLE_FOR_UPDATE; }
|
||||
toastr.info(grav + (grav && resources ? ' ' + translations.PLUGIN_ADMIN.AND + ' ' : '') + resources);
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
68
user/plugins/admin/themes/grav/app/updates/feed.js
Normal file
68
user/plugins/admin/themes/grav/app/updates/feed.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import $ from 'jquery';
|
||||
import { config } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
|
||||
const URI = `${config.base_url_relative}/ajax.json/task${config.param_sep}getNewsFeed`;
|
||||
|
||||
class Feed {
|
||||
constructor() {
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
fetch(refresh = false, callback = function() {}) {
|
||||
request(URI, {
|
||||
method: 'post',
|
||||
body: { refresh }
|
||||
}, (response) => {
|
||||
this.data = response;
|
||||
callback(response);
|
||||
});
|
||||
}
|
||||
|
||||
refresh(refresh = false) {
|
||||
const feed = $('#news-feed .widget-content');
|
||||
if (!feed.length) { return; }
|
||||
|
||||
let loader = feed.find('.widget-loader');
|
||||
loader.find('div').remove();
|
||||
loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
|
||||
loader.show();
|
||||
|
||||
feed.find('> ul').hide();
|
||||
|
||||
if (!this.data || this.data.error || refresh) {
|
||||
this.fetch(refresh, this.updateContent.bind(this));
|
||||
} else {
|
||||
this.updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
updateContent() {
|
||||
const feed = $('#news-feed .widget-content');
|
||||
if (!feed.length) { return; }
|
||||
|
||||
let loader = feed.find('.widget-loader').hide();
|
||||
let content = feed.find('> ul').empty().show();
|
||||
|
||||
if (this.data.error || this.data.status === 'error') {
|
||||
loader.show().find('div').remove();
|
||||
loader.find('.fa-refresh').removeClass('fa-refresh fa-spin').addClass('fa-warning');
|
||||
loader.append(`<div>${this.data.error ? this.data.error.message : this.data.message || 'Unable to download news feed'}</div>`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.data && this.data.feed_data) {
|
||||
content.append(this.data.feed_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let feed = new Feed();
|
||||
$(document).ready(() => feed.refresh());
|
||||
$(document).on('click', '[data-refresh="feed"]', (event) => {
|
||||
event.preventDefault();
|
||||
feed.refresh(true);
|
||||
});
|
||||
|
||||
export default feed;
|
||||
196
user/plugins/admin/themes/grav/app/updates/index.js
Normal file
196
user/plugins/admin/themes/grav/app/updates/index.js
Normal file
@@ -0,0 +1,196 @@
|
||||
import $ from 'jquery';
|
||||
import unique from 'mout/array/unique';
|
||||
import { config, translations } from 'grav-config';
|
||||
import { Instance as gpm } from '../utils/gpm';
|
||||
import Notifications from './notifications';
|
||||
|
||||
import Feed from './feed';
|
||||
import './check';
|
||||
import './update';
|
||||
import './channel-switcher';
|
||||
|
||||
export default class Updates {
|
||||
constructor(payload = {}) {
|
||||
this.setPayload(payload);
|
||||
this.task = `task${config.param_sep}`;
|
||||
this.updateURL = '';
|
||||
}
|
||||
|
||||
setPayload(payload = {}) {
|
||||
this.payload = payload;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
fetch(force = false) {
|
||||
gpm.fetch((response) => this.setPayload(response), force);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
maintenance(mode = 'hide') {
|
||||
let element = $('#updates [data-update-packages]');
|
||||
|
||||
element[mode === 'show' ? 'fadeIn' : 'fadeOut']();
|
||||
|
||||
if (mode === 'hide') {
|
||||
$('.badges.with-updates').removeClass('with-updates').find('.badge.updates').remove();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
grav() {
|
||||
let payload = this.payload.grav;
|
||||
|
||||
if (payload && payload.isUpdatable) {
|
||||
let task = this.task;
|
||||
let bar = '';
|
||||
|
||||
if (!payload.isSymlink) {
|
||||
this.updateURL = `${config.base_url_relative}/update.json/${task}updategrav/admin-nonce${config.param_sep}${config.admin_nonce}`;
|
||||
bar += `<button data-remodal-target="update-grav" class="button button-small secondary pointer-events-none" id="grav-update-button">${translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW} <span class="cnt-down">(5s)</span></button>`;
|
||||
} else {
|
||||
bar += `<span class="hint--left" style="float: right;" data-hint="${translations.PLUGIN_ADMIN.GRAV_SYMBOLICALLY_LINKED}"><i class="fa fa-fw fa-link"></i></span>`;
|
||||
}
|
||||
|
||||
bar += `
|
||||
Grav <b>v${payload.available}</b> ${translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE}! <span class="less">(${translations.PLUGIN_ADMIN.CURRENT} v${payload.version})</span>
|
||||
`;
|
||||
|
||||
let element = $('[data-gpm-grav]').removeClass('hidden');
|
||||
|
||||
if (element.is(':empty')) {
|
||||
element.hide();
|
||||
}
|
||||
|
||||
element
|
||||
.addClass('grav')
|
||||
.html(`${bar}`)
|
||||
.slideDown(150, function() {
|
||||
var c = 5;
|
||||
var x = setInterval(function() {
|
||||
c -= 1;
|
||||
element.find('.pointer-events-none .cnt-down').text('(' + c + 's)');
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function() {
|
||||
clearInterval(x);
|
||||
element.find('.pointer-events-none .cnt-down').remove();
|
||||
element.find('.pointer-events-none').removeClass('pointer-events-none');
|
||||
}, 5000);
|
||||
})
|
||||
.parent('#messages').addClass('default-box-shadow');
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
resources() {
|
||||
if (!this.payload || !this.payload.resources || !this.payload.resources.total) {
|
||||
return this.maintenance('hide');
|
||||
}
|
||||
|
||||
let is_current_package_latest = true;
|
||||
let map = ['plugins', 'themes'];
|
||||
let singles = ['plugin', 'theme'];
|
||||
let { plugins, themes } = this.payload.resources;
|
||||
|
||||
if (!this.payload.resources.total) { return this; }
|
||||
|
||||
[plugins, themes].forEach(function(resources, index) {
|
||||
if (!resources || Array.isArray(resources)) { return; }
|
||||
let length = Object.keys(resources).length;
|
||||
let type = map[index];
|
||||
|
||||
// sidebar
|
||||
$(`#admin-menu a[href$="/${map[index]}"]`)
|
||||
.find('.badges')
|
||||
.addClass('with-updates')
|
||||
.find('.badge.updates').text(length);
|
||||
|
||||
var type_translation = '';
|
||||
// update all
|
||||
|
||||
if (type === 'plugins') {
|
||||
type_translation = translations.PLUGIN_ADMIN.PLUGINS;
|
||||
} else {
|
||||
type_translation = translations.PLUGIN_ADMIN.THEMES;
|
||||
}
|
||||
|
||||
let updateAll = $(`.grav-update.${type}`);
|
||||
updateAll.css('display', 'block').html(`
|
||||
<p>
|
||||
<a href="#" class="button button-small secondary" data-remodal-target="update-packages" data-packages-slugs="${Object.keys(resources).join()}" data-${singles[index]}-action="start-packages-update">${translations.PLUGIN_ADMIN.UPDATE} ${translations.PLUGIN_ADMIN.ALL} ${type_translation}</a>
|
||||
<i class="fa fa-bullhorn"></i>
|
||||
${length} ${translations.PLUGIN_ADMIN.OF_YOUR} ${type_translation.toLowerCase()} ${translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE}
|
||||
</p>
|
||||
`);
|
||||
|
||||
let existing_slugs = $('[data-update-packages]').attr('data-packages-slugs') || '';
|
||||
|
||||
if (existing_slugs) {
|
||||
existing_slugs = existing_slugs.split(',');
|
||||
} else {
|
||||
existing_slugs = [];
|
||||
}
|
||||
|
||||
let slugs = unique(existing_slugs.concat(Object.keys(resources))).join();
|
||||
$('[data-update-packages]').attr('data-packages-slugs', `${slugs}`);
|
||||
|
||||
Object.keys(resources).forEach(function(item) {
|
||||
// listing page
|
||||
let container = $(`[data-gpm-${singles[index]}="${item}"]`);
|
||||
let element = container.find('.gpm-name');
|
||||
let url = element.find('a');
|
||||
let content_wrapper = container.parents('.content-wrapper');
|
||||
|
||||
if (type === 'plugins' && !element.find('.badge.update').length) {
|
||||
element.append(`<a class="plugin-update-button" href="${url.attr('href')}"><span class="badge update">${translations.PLUGIN_ADMIN.UPDATE_AVAILABLE}!</span></a>`);
|
||||
content_wrapper.addClass('has-updates');
|
||||
} else if (type === 'themes') {
|
||||
element.append(`<div class="gpm-ribbon"><a href="${url.attr('href')}">${translations.PLUGIN_ADMIN.UPDATE.toUpperCase()}</a></div>`);
|
||||
content_wrapper.addClass('has-updates');
|
||||
}
|
||||
|
||||
// details page
|
||||
if (container.length) {
|
||||
let details = $(`.grav-update.${singles[index]}`);
|
||||
if (details.length) {
|
||||
let releaseType = resources[item].type === 'testing' ? '<span class="gpm-testing">test release</span>' : '';
|
||||
details.html(`
|
||||
<p>
|
||||
<a href="#" class="button button-small secondary" data-remodal-target="update-packages" data-packages-slugs="${item}" data-${singles[index]}-action="start-package-installation">${translations.PLUGIN_ADMIN.UPDATE} ${singles[index].charAt(0).toUpperCase() + singles[index].substr(1).toLowerCase()}</a>
|
||||
<i class="fa fa-bullhorn"></i>
|
||||
<strong>v${resources[item].available}</strong> ${releaseType} ${translations.PLUGIN_ADMIN.OF_THIS} ${singles[index]} ${translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE}!
|
||||
</p>
|
||||
`).css('display', 'block');
|
||||
|
||||
is_current_package_latest = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('[data-update-packages]').removeClass('hidden');
|
||||
});
|
||||
|
||||
$('.content-wrapper').addClass('updates-checked');
|
||||
|
||||
if (!is_current_package_latest) {
|
||||
$('.warning-reinstall-not-latest-release').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Instance = new Updates();
|
||||
export { Instance, Notifications, Feed };
|
||||
|
||||
// automatically refresh UI for updates (graph, sidebar, plugin/themes pages) after every fetch
|
||||
gpm.on('fetched', (response, raw) => {
|
||||
Instance.setPayload(response.payload || {});
|
||||
Instance.grav().resources();
|
||||
});
|
||||
|
||||
if (config.enable_auto_updates_check === '1') {
|
||||
gpm.fetch();
|
||||
}
|
||||
169
user/plugins/admin/themes/grav/app/updates/notifications.js
Normal file
169
user/plugins/admin/themes/grav/app/updates/notifications.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import $ from 'jquery';
|
||||
import { config } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
|
||||
const canFetchNotifications = () => config.notifications.enabled;
|
||||
const notificationsFilters = () => config.notifications.filters;
|
||||
|
||||
class Notifications {
|
||||
|
||||
static addShowAllInFeed() {
|
||||
$('#notifications ul').append('<li class="show-all" data-notification-action="show-all-notifications">Show all</li>');
|
||||
}
|
||||
|
||||
static showNotificationInFeed(notification) {
|
||||
let notifications = $('#notifications').removeClass('hidden');
|
||||
|
||||
let loader = notifications.find('.widget-loader').hide();
|
||||
let content = notifications.find('.widget-content > ul').show();
|
||||
loader.find('div').remove();
|
||||
loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
|
||||
|
||||
content
|
||||
.append(notification)
|
||||
.find('li:nth-child(n+11)').addClass('hidden'); // hide all items > 10
|
||||
|
||||
if (content.find('li.hidden').length) {
|
||||
Notifications.addShowAllInFeed();
|
||||
}
|
||||
}
|
||||
|
||||
static showNotificationInTop(notification) {
|
||||
const container = $('.top-notifications-container');
|
||||
const dummy = $('<div />').html(notification);
|
||||
|
||||
container.removeClass('hidden').append(dummy.children());
|
||||
dummy.children().slideDown(150);
|
||||
}
|
||||
|
||||
static showNotificationInDashboard(notification) {
|
||||
const container = $('.dashboard-notifications-container');
|
||||
const dummy = $('<div />').html(notification);
|
||||
|
||||
container.removeClass('hidden').append(dummy.children());
|
||||
dummy.children().slideDown(150);
|
||||
}
|
||||
|
||||
static showNotificationInPlugins(notification) {
|
||||
const container = $('.plugins-notifications-container');
|
||||
const dummy = $('<div />').html(notification);
|
||||
|
||||
container.removeClass('hidden').append(dummy.children());
|
||||
dummy.children().slideDown(150);
|
||||
}
|
||||
|
||||
static showNotificationInThemes(notification) {
|
||||
const container = $('.themes-notifications-container');
|
||||
const dummy = $('<div />').html(notification);
|
||||
|
||||
container.removeClass('hidden').append(dummy.children());
|
||||
dummy.children().slideDown(150);
|
||||
}
|
||||
|
||||
static processLocation(location, notification) {
|
||||
switch (location) {
|
||||
case 'feed':
|
||||
Notifications.showNotificationInFeed(notification);
|
||||
break;
|
||||
case 'top':
|
||||
if (!notification.read) {
|
||||
Notifications.showNotificationInTop(notification);
|
||||
}
|
||||
break;
|
||||
case 'dashboard':
|
||||
if (!notification.read) {
|
||||
Notifications.showNotificationInDashboard(notification);
|
||||
}
|
||||
break;
|
||||
case 'plugins':
|
||||
if (!notification.read) {
|
||||
Notifications.showNotificationInPlugins(notification);
|
||||
}
|
||||
break;
|
||||
case 'themes':
|
||||
if (!notification.read) {
|
||||
Notifications.showNotificationInThemes(notification);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Grav.default.Notifications.fetch()
|
||||
fetch({ filter = notificationsFilters(), refresh = false } = {}) {
|
||||
if (!canFetchNotifications()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let feed = $('#notifications');
|
||||
let loader = feed.find('.widget-loader');
|
||||
let content = feed.find('.widget-content > ul');
|
||||
loader.find('div').remove();
|
||||
loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
|
||||
loader.show();
|
||||
content.hide();
|
||||
|
||||
let processNotifications = (response) => {
|
||||
let notifications = response.notifications;
|
||||
|
||||
$('#notifications').find('.widget-content > ul').empty();
|
||||
|
||||
if (notifications) {
|
||||
Object.keys(notifications).forEach((location) => Notifications.processLocation(location, notifications[location]));
|
||||
}
|
||||
};
|
||||
|
||||
request(`${config.base_url_relative}/task${config.param_sep}getNotifications`, {
|
||||
method: 'post',
|
||||
body: { refresh, filter }
|
||||
}, (response) => {
|
||||
processNotifications(response);
|
||||
}).catch(() => {
|
||||
let widget = $('#notifications .widget-content');
|
||||
widget
|
||||
.find('.widget-loader')
|
||||
.find('div').remove();
|
||||
|
||||
widget
|
||||
.find('.widget-loader')
|
||||
.append('<div>Failed to retrieve notifications</div>')
|
||||
.find('.fa-spin')
|
||||
.removeClass('fa-spin fa-refresh').addClass('fa-warning');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let notifications = new Notifications();
|
||||
export default notifications;
|
||||
|
||||
if (canFetchNotifications()) {
|
||||
notifications.fetch();
|
||||
|
||||
/* Hide a notification and store it hidden */
|
||||
// <a href="#" data-notification-action="hide-notification" data-notification-id="${notification.id}" class="close hide-notification"><i class="fa fa-close"></i></a>
|
||||
$(document).on('click', '[data-notification-action="hide-notification"]', (event) => {
|
||||
let notification_id = $(event.target).parents('.hide-notification').data('notification-id');
|
||||
|
||||
let url = `${config.base_url_relative}/notifications.json/task${config.param_sep}hideNotification/notification_id${config.param_sep}${notification_id}`;
|
||||
|
||||
request(url, { method: 'post' }, () => {});
|
||||
|
||||
$(event.target).parents('.single-notification').hide();
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-notification-action="hide-notification"]', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const notification = target.parent();
|
||||
|
||||
notification.slideUp(() => notification.remove());
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-notification-action="show-all-notifications"]', (event) => {
|
||||
$('#notifications .show-all').hide();
|
||||
$('#notifications .hidden').removeClass('hidden');
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-refresh="notifications"]', (event) => {
|
||||
event.preventDefault();
|
||||
notifications.fetch({ filter: ['feed'], refresh: true });
|
||||
});
|
||||
}
|
||||
22
user/plugins/admin/themes/grav/app/updates/update.js
Normal file
22
user/plugins/admin/themes/grav/app/updates/update.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import $ from 'jquery';
|
||||
import { translations } from 'grav-config';
|
||||
import formatBytes from '../utils/formatbytes';
|
||||
import request from '../utils/request';
|
||||
import { Instance as Update } from './index';
|
||||
|
||||
// Dashboard update and Grav update
|
||||
$(document).on('click.remodal', '[data-remodal-id="update-grav"] [data-remodal-action="confirm"]', () => {
|
||||
const element = $('#grav-update-button');
|
||||
element.html(`${translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT} ${formatBytes(Update.payload.grav.assets['grav-update'].size)}..`);
|
||||
|
||||
element.attr('disabled', 'disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin');
|
||||
|
||||
request(Update.updateURL, (response) => {
|
||||
if (response.type === 'updategrav') {
|
||||
$('[data-gpm-grav]').remove();
|
||||
$('#footer .grav-version').html(response.version);
|
||||
}
|
||||
|
||||
element.removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-cloud-download');
|
||||
});
|
||||
});
|
||||
32
user/plugins/admin/themes/grav/app/utils/2fa.js
Normal file
32
user/plugins/admin/themes/grav/app/utils/2fa.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import $ from 'jquery';
|
||||
import { config } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
|
||||
const body = $('body');
|
||||
|
||||
// Dashboard update and Grav update
|
||||
body.on('click', '[data-2fa-regenerate]', function(event) {
|
||||
event.preventDefault();
|
||||
let element = $(this);
|
||||
let url = `${config.base_url_relative}/ajax.json/task${config.param_sep}regenerate2FASecret`;
|
||||
|
||||
element.attr('disabled', 'disabled').find('> .fa').addClass('fa-spin');
|
||||
|
||||
request(url, { method: 'post' }, (response) => {
|
||||
$('[data-2fa-image]').attr('src', response.image);
|
||||
$('[data-2fa-secret]').text(response.secret);
|
||||
$('[data-2fa-value]').val(response.secret.replace(' ', ''));
|
||||
|
||||
element.removeAttr('disabled').find('> .fa').removeClass('fa-spin');
|
||||
});
|
||||
});
|
||||
|
||||
const toggleSecret = () => {
|
||||
const toggle = $('#toggle_twofa_enabled1');
|
||||
const secret = $('.twofa-secret');
|
||||
|
||||
secret[toggle.is(':checked') ? 'addClass' : 'removeClass']('show');
|
||||
};
|
||||
|
||||
body.on('click', '.twofa-toggle input', toggleSecret);
|
||||
toggleSecret();
|
||||
210
user/plugins/admin/themes/grav/app/utils/bootstrap-collapse.js
vendored
Normal file
210
user/plugins/admin/themes/grav/app/utils/bootstrap-collapse.js
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
import jQuery from 'jquery';
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: collapse.js v3.4.0
|
||||
* http://getbootstrap.com/javascript/#collapse
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
/* jshint latedef: false */
|
||||
|
||||
+(function($) {
|
||||
'use strict';
|
||||
|
||||
// COLLAPSE PUBLIC CLASS DEFINITION
|
||||
// ================================
|
||||
|
||||
var Collapse = function(element, options) {
|
||||
this.$element = $(element);
|
||||
this.options = $.extend({}, Collapse.DEFAULTS, options);
|
||||
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
|
||||
'[data-toggle="collapse"][data-target="#' + element.id + '"]');
|
||||
this.transitioning = null;
|
||||
|
||||
if (this.options.parent) {
|
||||
this.$parent = this.getParent();
|
||||
} else {
|
||||
this.addAriaAndCollapsedClass(this.$element, this.$trigger);
|
||||
}
|
||||
|
||||
if (this.options.toggle) this.toggle();
|
||||
};
|
||||
|
||||
Collapse.VERSION = '3.4.0';
|
||||
|
||||
Collapse.TRANSITION_DURATION = 350;
|
||||
|
||||
Collapse.DEFAULTS = {
|
||||
toggle: true
|
||||
};
|
||||
|
||||
Collapse.prototype.dimension = function() {
|
||||
var hasWidth = this.$element.hasClass('width');
|
||||
return hasWidth ? 'width' : 'height';
|
||||
};
|
||||
|
||||
Collapse.prototype.show = function() {
|
||||
if (this.transitioning || this.$element.hasClass('in')) return;
|
||||
|
||||
var activesData;
|
||||
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing');
|
||||
|
||||
if (actives && actives.length) {
|
||||
activesData = actives.data('bs.collapse');
|
||||
if (activesData && activesData.transitioning) return;
|
||||
}
|
||||
|
||||
var startEvent = $.Event('show.bs.collapse');
|
||||
this.$element.trigger(startEvent);
|
||||
if (startEvent.isDefaultPrevented()) return;
|
||||
|
||||
if (actives && actives.length) {
|
||||
Plugin.call(actives, 'hide');
|
||||
activesData || actives.data('bs.collapse', null);
|
||||
}
|
||||
|
||||
var dimension = this.dimension();
|
||||
|
||||
this.$element
|
||||
.removeClass('collapse')
|
||||
.addClass('collapsing')[dimension](0)
|
||||
.attr('aria-expanded', true);
|
||||
|
||||
this.$trigger
|
||||
.removeClass('collapsed')
|
||||
.attr('aria-expanded', true);
|
||||
|
||||
this.transitioning = 1;
|
||||
|
||||
var complete = function() {
|
||||
this.$element
|
||||
.removeClass('collapsing')
|
||||
.addClass('collapse in')[dimension]('');
|
||||
this.transitioning = 0;
|
||||
this.$element
|
||||
.trigger('shown.bs.collapse');
|
||||
};
|
||||
|
||||
if (!$.support.transition) return complete.call(this);
|
||||
|
||||
var scrollSize = $.camelCase(['scroll', dimension].join('-'));
|
||||
|
||||
this.$element
|
||||
.one('bsTransitionEnd', $.proxy(complete, this))
|
||||
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]);
|
||||
};
|
||||
|
||||
Collapse.prototype.hide = function() {
|
||||
if (this.transitioning || !this.$element.hasClass('in')) return;
|
||||
|
||||
var startEvent = $.Event('hide.bs.collapse');
|
||||
this.$element.trigger(startEvent);
|
||||
if (startEvent.isDefaultPrevented()) return;
|
||||
|
||||
var dimension = this.dimension();
|
||||
|
||||
this.$element[dimension](this.$element[dimension]())[0].offsetHeight;
|
||||
|
||||
this.$element
|
||||
.addClass('collapsing')
|
||||
.removeClass('collapse in')
|
||||
.attr('aria-expanded', false);
|
||||
|
||||
this.$trigger
|
||||
.addClass('collapsed')
|
||||
.attr('aria-expanded', false);
|
||||
|
||||
this.transitioning = 1;
|
||||
|
||||
var complete = function() {
|
||||
this.transitioning = 0;
|
||||
this.$element
|
||||
.removeClass('collapsing')
|
||||
.addClass('collapse')
|
||||
.trigger('hidden.bs.collapse');
|
||||
};
|
||||
|
||||
if (!$.support.transition) return complete.call(this);
|
||||
|
||||
this.$element[dimension](0)
|
||||
.one('bsTransitionEnd', $.proxy(complete, this))
|
||||
.emulateTransitionEnd(Collapse.TRANSITION_DURATION);
|
||||
};
|
||||
|
||||
Collapse.prototype.toggle = function() {
|
||||
this[this.$element.hasClass('in') ? 'hide' : 'show']();
|
||||
};
|
||||
|
||||
Collapse.prototype.getParent = function() {
|
||||
return $(this.options.parent)
|
||||
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
||||
.each($.proxy(function(i, element) {
|
||||
var $element = $(element);
|
||||
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element);
|
||||
}, this))
|
||||
.end();
|
||||
};
|
||||
|
||||
Collapse.prototype.addAriaAndCollapsedClass = function($element, $trigger) {
|
||||
var isOpen = $element.hasClass('in');
|
||||
|
||||
$element.attr('aria-expanded', isOpen);
|
||||
$trigger
|
||||
.toggleClass('collapsed', !isOpen)
|
||||
.attr('aria-expanded', isOpen);
|
||||
};
|
||||
|
||||
function getTargetFromTrigger($trigger) {
|
||||
var href;
|
||||
var target = $trigger.attr('data-target') ||
|
||||
(href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, ''); // strip for ie7
|
||||
|
||||
return $(target);
|
||||
}
|
||||
|
||||
// COLLAPSE PLUGIN DEFINITION
|
||||
// ==========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function() {
|
||||
var $this = $(this);
|
||||
var data = $this.data('bs.collapse');
|
||||
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option === 'object' && option);
|
||||
|
||||
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false;
|
||||
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)));
|
||||
if (typeof option === 'string') data[option]();
|
||||
});
|
||||
}
|
||||
|
||||
var old = $.fn.collapse;
|
||||
|
||||
$.fn.collapse = Plugin;
|
||||
$.fn.collapse.Constructor = Collapse;
|
||||
|
||||
// COLLAPSE NO CONFLICT
|
||||
// ====================
|
||||
|
||||
$.fn.collapse.noConflict = function() {
|
||||
$.fn.collapse = old;
|
||||
return this;
|
||||
};
|
||||
|
||||
// COLLAPSE DATA-API
|
||||
// =================
|
||||
|
||||
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function(e) {
|
||||
var $this = $(this);
|
||||
|
||||
if (!$this.attr('data-target')) e.preventDefault();
|
||||
|
||||
var $target = getTargetFromTrigger($this);
|
||||
var data = $target.data('bs.collapse');
|
||||
var option = data ? 'toggle' : $this.data();
|
||||
|
||||
Plugin.call($target, option);
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
2632
user/plugins/admin/themes/grav/app/utils/bootstrap-datetimepicker.js
vendored
Normal file
2632
user/plugins/admin/themes/grav/app/utils/bootstrap-datetimepicker.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
169
user/plugins/admin/themes/grav/app/utils/bootstrap-dropdown.js
vendored
Normal file
169
user/plugins/admin/themes/grav/app/utils/bootstrap-dropdown.js
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
import jQuery from 'jquery';
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: dropdown.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#dropdowns
|
||||
* ========================================================================
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/v3-dev/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
+(function($) {
|
||||
'use strict';
|
||||
|
||||
// DROPDOWN CLASS DEFINITION
|
||||
// =========================
|
||||
|
||||
const backdrop = '.dropdown-backdrop';
|
||||
const toggle = '[data-toggle="dropdown"]';
|
||||
const Dropdown = function(element) {
|
||||
$(element).on('click.bs.dropdown', this.toggle);
|
||||
};
|
||||
|
||||
Dropdown.VERSION = '3.4.1';
|
||||
|
||||
function getParent($this) {
|
||||
let selector = $this.attr('data-target');
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href');
|
||||
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7
|
||||
}
|
||||
|
||||
const $parent = selector !== '#' ? $(document).find(selector) : null;
|
||||
|
||||
return $parent && $parent.length ? $parent : $this.parent();
|
||||
}
|
||||
|
||||
function clearMenus(e) {
|
||||
if (e && e.which === 3) { return; }
|
||||
$(backdrop).remove();
|
||||
$(toggle).each(function() {
|
||||
const $this = $(this);
|
||||
const $parent = getParent($this);
|
||||
const relatedTarget = { relatedTarget: this };
|
||||
|
||||
if (!$parent.hasClass('open')) { return; }
|
||||
|
||||
if (e && e.type === 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) { return; }
|
||||
|
||||
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget));
|
||||
|
||||
if (e.isDefaultPrevented()) { return; }
|
||||
|
||||
$this.attr('aria-expanded', 'false');
|
||||
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget));
|
||||
});
|
||||
}
|
||||
|
||||
Dropdown.prototype.toggle = function(e) {
|
||||
const $this = $(this);
|
||||
|
||||
if ($this.is('.disabled, :disabled')) { return; }
|
||||
|
||||
const $parent = getParent($this);
|
||||
const isActive = $parent.hasClass('open');
|
||||
|
||||
clearMenus();
|
||||
|
||||
if (!isActive) {
|
||||
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
|
||||
// if mobile we use a backdrop because click events don't delegate
|
||||
$(document.createElement('div'))
|
||||
.addClass('dropdown-backdrop')
|
||||
.insertAfter($(this))
|
||||
.on('click', clearMenus);
|
||||
}
|
||||
|
||||
const relatedTarget = { relatedTarget: this };
|
||||
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget));
|
||||
|
||||
if (e.isDefaultPrevented()) { return; }
|
||||
|
||||
$this
|
||||
.trigger('focus')
|
||||
.attr('aria-expanded', 'true');
|
||||
|
||||
$parent
|
||||
.toggleClass('open')
|
||||
.trigger($.Event('shown.bs.dropdown', relatedTarget));
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Dropdown.prototype.keydown = function(e) {
|
||||
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return;
|
||||
|
||||
const $this = $(this);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if ($this.is('.disabled, :disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $parent = getParent($this);
|
||||
const isActive = $parent.hasClass('open');
|
||||
|
||||
if (!isActive && e.which !== 27 || isActive && e.which === 27) {
|
||||
if (e.which === 27) {
|
||||
$parent.find(toggle).trigger('focus');
|
||||
}
|
||||
|
||||
return $this.trigger('click');
|
||||
}
|
||||
|
||||
const desc = ' li:not(.disabled):visible a';
|
||||
const $items = $parent.find('.dropdown-menu' + desc);
|
||||
|
||||
if (!$items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = $items.index(e.target);
|
||||
|
||||
if (e.which === 38 && index > 0) { index--; } // up
|
||||
if (e.which === 40 && index < $items.length - 1) { index++; } // down
|
||||
if (!~index) { index = 0; }
|
||||
|
||||
$items.eq(index).trigger('focus');
|
||||
};
|
||||
|
||||
// DROPDOWN PLUGIN DEFINITION
|
||||
// ==========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function() {
|
||||
const $this = $(this);
|
||||
let data = $this.data('bs.dropdown');
|
||||
|
||||
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)));
|
||||
if (typeof option === 'string') data[option].call($this);
|
||||
});
|
||||
}
|
||||
|
||||
const old = $.fn.dropdown;
|
||||
|
||||
$.fn.dropdown = Plugin;
|
||||
$.fn.dropdown.Constructor = Dropdown;
|
||||
|
||||
// DROPDOWN NO CONFLICT
|
||||
// ====================
|
||||
|
||||
$.fn.dropdown.noConflict = function() {
|
||||
$.fn.dropdown = old;
|
||||
return this;
|
||||
};
|
||||
|
||||
// APPLY TO STANDARD DROPDOWN ELEMENTS
|
||||
// ===================================
|
||||
|
||||
$(document)
|
||||
.on('click.bs.dropdown.data-api', clearMenus)
|
||||
.on('click.bs.dropdown.data-api', '.dropdown form', function(e) { e.stopPropagation(); })
|
||||
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
||||
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
|
||||
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown);
|
||||
}(jQuery));
|
||||
52
user/plugins/admin/themes/grav/app/utils/bootstrap-transition.js
vendored
Normal file
52
user/plugins/admin/themes/grav/app/utils/bootstrap-transition.js
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import jQuery from 'jquery';
|
||||
|
||||
+(function($) {
|
||||
'use strict';
|
||||
|
||||
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
||||
// ============================================================
|
||||
|
||||
function transitionEnd() {
|
||||
var el = document.createElement('bootstrap');
|
||||
|
||||
var transEndEventNames = {
|
||||
WebkitTransition: 'webkitTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd otransitionend',
|
||||
transition: 'transitionend'
|
||||
};
|
||||
|
||||
for (var name in transEndEventNames) {
|
||||
if (el.style[name] !== undefined) {
|
||||
return { end: transEndEventNames[name] };
|
||||
}
|
||||
}
|
||||
|
||||
return false; // explicit for ie8 ( ._.)
|
||||
}
|
||||
|
||||
// http://blog.alexmaccaw.com/css-transitions
|
||||
$.fn.emulateTransitionEnd = function(duration) {
|
||||
var called = false;
|
||||
var $el = this;
|
||||
$(this).one('bsTransitionEnd', function() { called = true; });
|
||||
var callback = function() { if (!called) $($el).trigger($.support.transition.end); };
|
||||
setTimeout(callback, duration);
|
||||
return this;
|
||||
};
|
||||
|
||||
$(function() {
|
||||
$.support.transition = transitionEnd();
|
||||
|
||||
if (!$.support.transition) return;
|
||||
|
||||
$.event.special.bsTransitionEnd = {
|
||||
bindType: $.support.transition.end,
|
||||
delegateType: $.support.transition.end,
|
||||
handle: function(e) {
|
||||
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
25
user/plugins/admin/themes/grav/app/utils/changelog.js
Normal file
25
user/plugins/admin/themes/grav/app/utils/changelog.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/* eslint-disable */
|
||||
import $ from 'jquery';
|
||||
|
||||
let TRIGGER = null;
|
||||
|
||||
$(document).on('click', '[data-remodal-changelog]', (event) => {
|
||||
TRIGGER = event.currentTarget;
|
||||
});
|
||||
|
||||
$(document).on('opened', '[data-remodal-id="changelog"]', () => {
|
||||
const instance = $.remodal.lookup[$('[data-remodal-id=changelog]').data('remodal')];
|
||||
instance.$modal.html('<div class="changelog-overflow center" style="padding:5rem 0;text-align:center;"><i class="fa fa-spinner fa-spin fa-3x fa-fw"></i></div>');
|
||||
if (!TRIGGER) { return true; }
|
||||
|
||||
const url = $(TRIGGER).data('remodalChangelog');
|
||||
|
||||
$.ajax({url: url}).done(function(data) {
|
||||
instance.$modal.html(data);
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('closed', '[data-remodal-id="changelog"]', () => {
|
||||
const instance = $.remodal.lookup[$('[data-remodal-id=changelog]').data('remodal')];
|
||||
instance.$modal.html('');
|
||||
});
|
||||
152
user/plugins/admin/themes/grav/app/utils/colors.js
Normal file
152
user/plugins/admin/themes/grav/app/utils/colors.js
Normal file
@@ -0,0 +1,152 @@
|
||||
// Parses a string and returns a valid hex string when possible
|
||||
// parseHex('#fff') => '#ffffff'
|
||||
export const parseHex = (string) => {
|
||||
string = string.replace(/[^A-F0-9]/ig, '');
|
||||
if (string.length !== 3 && string.length !== 6) return '';
|
||||
if (string.length === 3) {
|
||||
string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2];
|
||||
}
|
||||
|
||||
return '#' + string.toLowerCase();
|
||||
};
|
||||
|
||||
// Converts an HSB object to an RGB object
|
||||
// hsb2rgb({h: 0, s: 0, b: 100}) => {r: 255, g: 255, b: 255}
|
||||
export const hsb2rgb = (hsb) => {
|
||||
let rgb = {};
|
||||
let h = Math.round(hsb.h);
|
||||
let s = Math.round(hsb.s * 255 / 100);
|
||||
let v = Math.round(hsb.b * 255 / 100);
|
||||
if (s === 0) {
|
||||
rgb.r = rgb.g = rgb.b = v;
|
||||
} else {
|
||||
var t1 = v;
|
||||
var t2 = (255 - s) * v / 255;
|
||||
var t3 = (t1 - t2) * (h % 60) / 60;
|
||||
if (h === 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3;
|
||||
} else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3;
|
||||
} else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3;
|
||||
} else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3;
|
||||
} else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3;
|
||||
} else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3;
|
||||
} else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
}
|
||||
}
|
||||
return {
|
||||
r: Math.round(rgb.r),
|
||||
g: Math.round(rgb.g),
|
||||
b: Math.round(rgb.b)
|
||||
};
|
||||
};
|
||||
|
||||
// Converts an RGB object to a HEX string
|
||||
// rgb2hex({r: 255, g: 255, b: 255}) => #ffffff
|
||||
export const rgb2hex = (rgb) => {
|
||||
var hex = [
|
||||
rgb.r.toString(16),
|
||||
rgb.g.toString(16),
|
||||
rgb.b.toString(16)
|
||||
];
|
||||
|
||||
hex.forEach((val, nr) => {
|
||||
if (val.length === 1) hex[nr] = '0' + val;
|
||||
});
|
||||
|
||||
return '#' + hex.join('');
|
||||
};
|
||||
|
||||
// Converts and RGB(a) string to a HEX string
|
||||
// rgbstr2hex('rgba(255, 255, 255, 0.5)') => #ffffff
|
||||
export const rgbstr2hex = (rgb) => {
|
||||
rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
|
||||
|
||||
return (rgb && rgb.length === 4) ? '#' +
|
||||
('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) +
|
||||
('0' + parseInt(rgb[2], 10).toString(16)).slice(-2) +
|
||||
('0' + parseInt(rgb[3], 10).toString(16)).slice(-2) : '';
|
||||
};
|
||||
|
||||
// Converts an HSB object to a HEX string
|
||||
// hsb2hex({h: 0, s: 0, b: 100}) => #ffffff
|
||||
export const hsb2hex = (hsb) => {
|
||||
return rgb2hex(hsb2rgb(hsb));
|
||||
};
|
||||
|
||||
// Converts a HEX string to an HSB object
|
||||
// hex2hsb('#ffffff') => {h: 0, s: 0, b: 100}
|
||||
export const hex2hsb = (hex) => {
|
||||
let hsb = rgb2hsb(hex2rgb(hex));
|
||||
if (hsb.s === 0) hsb.h = 360;
|
||||
|
||||
return hsb;
|
||||
};
|
||||
|
||||
// Converts an RGB object to an HSB object
|
||||
// rgb2hsb({r: 255, g: 255, b: 255}) => {h: 0, s: 0, b: 100}
|
||||
export const rgb2hsb = (rgb) => {
|
||||
let hsb = {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
};
|
||||
let min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
let max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
let delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max !== 0 ? 255 * delta / max : 0;
|
||||
if (hsb.s !== 0) {
|
||||
if (rgb.r === max) {
|
||||
hsb.h = (rgb.g - rgb.b) / delta;
|
||||
} else if (rgb.g === max) {
|
||||
hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
} else {
|
||||
hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
}
|
||||
} else {
|
||||
hsb.h = -1;
|
||||
}
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) {
|
||||
hsb.h += 360;
|
||||
}
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
|
||||
return hsb;
|
||||
};
|
||||
|
||||
// Converts a HEX string to an RGB object
|
||||
// hex2rgb('#ffffff') => {r: 255, g: 255, b: 255}
|
||||
export const hex2rgb = (hex) => {
|
||||
hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
|
||||
|
||||
return {
|
||||
/* jshint ignore:start */
|
||||
r: hex >> 16,
|
||||
g: (hex & 0x00FF00) >> 8,
|
||||
b: (hex & 0x0000FF)
|
||||
/* jshint ignore:end */
|
||||
};
|
||||
};
|
||||
164
user/plugins/admin/themes/grav/app/utils/cookies.js
Normal file
164
user/plugins/admin/themes/grav/app/utils/cookies.js
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Cookies.js - 1.2.3-grav
|
||||
* https://github.com/ScottHamper/Cookies
|
||||
*
|
||||
* With SameSite support by Grav
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*/
|
||||
|
||||
const factory = function(window) {
|
||||
if (typeof window.document !== 'object') {
|
||||
throw new Error('Cookies.js requires a `window` with a `document` object');
|
||||
}
|
||||
|
||||
const Cookies = (key, value, options) => {
|
||||
return arguments.length === 1
|
||||
? Cookies.get(key)
|
||||
: Cookies.set(key, value, options);
|
||||
};
|
||||
|
||||
// Allows for setter injection in unit tests
|
||||
Cookies._document = window.document;
|
||||
|
||||
// Used to ensure cookie keys do not collide with
|
||||
// built-in `Object` properties
|
||||
Cookies._cacheKeyPrefix = 'cookey.'; // Hurr hurr, :)
|
||||
|
||||
Cookies._maxExpireDate = new Date('Fri, 31 Dec 9999 23:59:59 UTC');
|
||||
|
||||
Cookies.defaults = {
|
||||
path: '/',
|
||||
secure: false,
|
||||
sameSite: 'Lax'
|
||||
};
|
||||
|
||||
Cookies.get = (key) => {
|
||||
if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) {
|
||||
Cookies._renewCache();
|
||||
}
|
||||
|
||||
const value = Cookies._cache[Cookies._cacheKeyPrefix + key];
|
||||
|
||||
return value === undefined ? undefined : decodeURIComponent(value);
|
||||
};
|
||||
|
||||
Cookies.set = (key, value, options) => {
|
||||
options = Cookies._getExtendedOptions(options);
|
||||
options.expires = Cookies._getExpiresDate(value === undefined ? -1 : options.expires);
|
||||
|
||||
Cookies._document.cookie = Cookies._generateCookieString(key, value, options);
|
||||
|
||||
return Cookies;
|
||||
};
|
||||
|
||||
Cookies.expire = (key, options) => {
|
||||
return Cookies.set(key, undefined, options);
|
||||
};
|
||||
|
||||
Cookies._getExtendedOptions = (options) => {
|
||||
return {
|
||||
path: options && options.path || Cookies.defaults.path,
|
||||
domain: options && options.domain || Cookies.defaults.domain,
|
||||
expires: options && options.expires || Cookies.defaults.expires,
|
||||
secure: options && options.secure !== undefined ? options.secure : Cookies.defaults.secure,
|
||||
sameSite: options && options.sameSite || Cookies.defaults.sameSite
|
||||
};
|
||||
};
|
||||
|
||||
Cookies._isValidDate = (date) => {
|
||||
return Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
|
||||
};
|
||||
|
||||
Cookies._getExpiresDate = (expires, now) => {
|
||||
now = now || new Date();
|
||||
|
||||
if (typeof expires === 'number') {
|
||||
expires = expires === Infinity
|
||||
? Cookies._maxExpireDate
|
||||
: new Date(now.getTime() + expires * 1000);
|
||||
} else if (typeof expires === 'string') {
|
||||
expires = new Date(expires);
|
||||
}
|
||||
|
||||
if (expires && !Cookies._isValidDate(expires)) {
|
||||
throw new Error('`expires` parameter cannot be converted to a valid Date instance');
|
||||
}
|
||||
|
||||
return expires;
|
||||
};
|
||||
|
||||
Cookies._generateCookieString = (key, value, options) => {
|
||||
key = key.replace(/[^#$&+\^`|]/g, encodeURIComponent);
|
||||
key = key.replace(/\(/g, '%28').replace(/\)/g, '%29');
|
||||
value = (value + '').replace(/[^!#$&-+\--:<-\[\]-~]/g, encodeURIComponent);
|
||||
options = options || {};
|
||||
|
||||
let cookieString = key + '=' + value;
|
||||
cookieString += options.path ? ';path=' + options.path : '';
|
||||
cookieString += options.domain ? ';domain=' + options.domain : '';
|
||||
cookieString += options.expires ? ';expires=' + options.expires.toUTCString() : '';
|
||||
cookieString += options.secure ? ';secure' : '';
|
||||
cookieString += options.sameSite ? ';SameSite=' + options.sameSite : '';
|
||||
|
||||
return cookieString;
|
||||
};
|
||||
|
||||
Cookies._getCacheFromString = (documentCookie) => {
|
||||
let cookieCache = {};
|
||||
const cookiesArray = documentCookie ? documentCookie.split('; ') : [];
|
||||
|
||||
for (let i = 0; i < cookiesArray.length; i++) {
|
||||
const cookieKvp = Cookies._getKeyValuePairFromCookieString(cookiesArray[i]);
|
||||
|
||||
if (cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] === undefined) {
|
||||
cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] = cookieKvp.value;
|
||||
}
|
||||
}
|
||||
|
||||
return cookieCache;
|
||||
};
|
||||
|
||||
Cookies._getKeyValuePairFromCookieString = (cookieString) => {
|
||||
// "=" is a valid character in a cookie value according to RFC6265, so cannot `split('=')`
|
||||
let separatorIndex = cookieString.indexOf('=');
|
||||
|
||||
// IE omits the "=" when the cookie value is an empty string
|
||||
separatorIndex = separatorIndex < 0 ? cookieString.length : separatorIndex;
|
||||
|
||||
const key = cookieString.substr(0, separatorIndex);
|
||||
let decodedKey;
|
||||
try {
|
||||
decodedKey = decodeURIComponent(key);
|
||||
} catch (e) {
|
||||
if (console && typeof console.error === 'function') {
|
||||
console.error('Could not decode cookie with key "' + key + '"', e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key: decodedKey,
|
||||
value: cookieString.substr(separatorIndex + 1) // Defer decoding value until accessed
|
||||
};
|
||||
};
|
||||
|
||||
Cookies._renewCache = () => {
|
||||
Cookies._cache = Cookies._getCacheFromString(Cookies._document.cookie);
|
||||
Cookies._cachedDocumentCookie = Cookies._document.cookie;
|
||||
};
|
||||
|
||||
Cookies._areEnabled = () => {
|
||||
const testKey = 'cookies.js';
|
||||
const areEnabled = Cookies.set(testKey, 1).get(testKey) === '1';
|
||||
Cookies.expire(testKey);
|
||||
return areEnabled;
|
||||
};
|
||||
|
||||
Cookies.enabled = Cookies._areEnabled();
|
||||
|
||||
return Cookies;
|
||||
};
|
||||
|
||||
global.Cookies = (global && typeof global.document === 'object') ? factory(global) : factory;
|
||||
|
||||
export default global.Cookies;
|
||||
864
user/plugins/admin/themes/grav/app/utils/cron-ui.js
Normal file
864
user/plugins/admin/themes/grav/app/utils/cron-ui.js
Normal file
@@ -0,0 +1,864 @@
|
||||
/* eslint-disable */
|
||||
import $ from 'jquery';
|
||||
/*
|
||||
* This file is part of the Arnapou jqCron package.
|
||||
*
|
||||
* (c) Arnaud Buathier <arnaud@arnapou.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default settings
|
||||
*/
|
||||
var jqCronDefaultSettings = {
|
||||
texts: {},
|
||||
monthdays: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31],
|
||||
hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||
hour_labels: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"],
|
||||
minutes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
|
||||
lang: 'en',
|
||||
enabled_minute: false,
|
||||
enabled_hour: true,
|
||||
enabled_day: true,
|
||||
enabled_week: true,
|
||||
enabled_month: true,
|
||||
enabled_year: true,
|
||||
multiple_dom: false,
|
||||
multiple_month: false,
|
||||
multiple_mins: false,
|
||||
multiple_dow: false,
|
||||
multiple_time_hours: false,
|
||||
multiple_time_minutes: false,
|
||||
numeric_zero_pad: false,
|
||||
default_period: 'day',
|
||||
default_value: '',
|
||||
no_reset_button: true,
|
||||
disabled: false,
|
||||
bind_to: null,
|
||||
bind_method: {
|
||||
set: function($element, value) {
|
||||
$element.is(':input') ? $element.val(value) : $element.data('jqCronValue', value);
|
||||
},
|
||||
get: function($element) {
|
||||
return $element.is(':input') ? $element.val() : $element.data('jqCronValue');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom extend of json for jqCron settings.
|
||||
* We don't use jQuery.extend because simple extend does not fit our needs, and deep extend has a bad
|
||||
* feature for us : it replaces keys of "Arrays" instead of replacing the full array.
|
||||
*/
|
||||
(function($){
|
||||
var extend = function(dst, src) {
|
||||
for(var i in src) {
|
||||
if($.isPlainObject(src[i])) {
|
||||
dst[i] = extend(dst[i] && $.isPlainObject(dst[i]) ? dst[i] : {}, src[i]);
|
||||
}
|
||||
else if($.isArray(src[i])) {
|
||||
dst[i] = src[i].slice(0);
|
||||
}
|
||||
else if(src[i] !== undefined) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
};
|
||||
this.jqCronMergeSettings = function(obj) {
|
||||
return extend(extend({}, jqCronDefaultSettings), obj || {});
|
||||
};
|
||||
}).call(window, $);
|
||||
|
||||
/**
|
||||
* Shortcut to get the instance of jqCron instance from one jquery object
|
||||
*/
|
||||
(function($){
|
||||
$.fn.jqCronGetInstance = function() {
|
||||
return this.data('jqCron');
|
||||
};
|
||||
}).call(window, $);
|
||||
|
||||
/**
|
||||
* Main plugin
|
||||
*/
|
||||
(function($){
|
||||
$.fn.jqCron = function(settings) {
|
||||
var saved_settings = settings;
|
||||
return this.each(function() {
|
||||
var cron, saved;
|
||||
var $this = $(this);
|
||||
var settings = jqCronMergeSettings(saved_settings); // clone settings
|
||||
var translations = settings.texts[settings.lang];
|
||||
|
||||
if (typeof(translations) !== 'object' || $.isEmptyObject(translations)) {
|
||||
console && console.error(
|
||||
'Missing translations for language "' + settings.lang + '". ' +
|
||||
'Please include jqCron.' + settings.lang + '.js or manually provide ' +
|
||||
'the necessary translations when calling $.fn.jqCron().'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!settings.jquery_container) {
|
||||
if($this.is(':container')) {
|
||||
settings.jquery_element = $this.uniqueId('jqCron');
|
||||
}
|
||||
else if($this.is(':autoclose')) {
|
||||
// delete already generated dom if exists
|
||||
if($this.next('.jqCron').length == 1) {
|
||||
$this.next('.jqCron').remove();
|
||||
}
|
||||
// generate new
|
||||
settings.jquery_element = $('<span class="jqCron"></span>').uniqueId('jqCron').insertAfter($this);
|
||||
}
|
||||
else {
|
||||
console && console.error(settings.texts[settings.lang].error1.replace('%s', this.tagName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// autoset bind_to if it is an input
|
||||
if($this.is(':input')) {
|
||||
settings.bind_to = settings.bind_to || $this;
|
||||
}
|
||||
|
||||
// init cron object
|
||||
if(settings.bind_to){
|
||||
if(settings.bind_to.is(':input')) {
|
||||
// auto bind from input to object if an input, textarea ...
|
||||
settings.bind_to.blur(function(){
|
||||
var value = settings.bind_method.get(settings.bind_to);
|
||||
$this.jqCronGetInstance().setCron(value);
|
||||
});
|
||||
}
|
||||
saved = settings.bind_method.get(settings.bind_to);
|
||||
cron = new jqCron(settings);
|
||||
cron.setCron(saved);
|
||||
}
|
||||
else {
|
||||
cron = new jqCron(settings);
|
||||
}
|
||||
$(this).data('jqCron', cron);
|
||||
});
|
||||
};
|
||||
}).call(window, $);
|
||||
|
||||
/**
|
||||
* jqCron class
|
||||
*/
|
||||
(function($){
|
||||
var jqCronInstances = [];
|
||||
|
||||
function jqCron(settings) {
|
||||
var _initialized = false;
|
||||
var _self = this;
|
||||
var _$elt = this;
|
||||
var _$obj = $('<span class="jqCron-container"></span>');
|
||||
var _$blocks = $('<span class="jqCron-blocks"></span>');
|
||||
var _$blockPERIOD = $('<span class="jqCron-period"></span>');
|
||||
var _$blockDOM = $('<span class="jqCron-dom"></span>');
|
||||
var _$blockMONTH = $('<span class="jqCron-month"></span>');
|
||||
var _$blockMINS = $('<span class="jqCron-mins"></span>');
|
||||
var _$blockDOW = $('<span class="jqCron-dow"></span>');
|
||||
var _$blockTIME = $('<span class="jqCron-time"></span>');
|
||||
var _$cross = $('<span class="jqCron-cross">✘</span>');
|
||||
var _selectors = [];
|
||||
var _selectorPeriod, _selectorMins, _selectorTimeH, _selectorTimeM, _selectorDow, _selectorDom, _selectorMonth;
|
||||
|
||||
// instanciate a new selector
|
||||
function newSelector($block, multiple, type){
|
||||
var selector = new jqCronSelector(_self, $block, multiple, type);
|
||||
selector.$.bind('selector:open', function(){
|
||||
// we close all opened selectors of all other jqCron
|
||||
for(var n = jqCronInstances.length; n--; ){
|
||||
if(jqCronInstances[n] != _self) {
|
||||
jqCronInstances[n].closeSelectors();
|
||||
}
|
||||
else {
|
||||
// we close all other opened selectors of this jqCron
|
||||
for(var o = _selectors.length; o--; ){
|
||||
if(_selectors[o] != selector) {
|
||||
_selectors[o].close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
selector.$.bind('selector:change', function(){
|
||||
var boundChanged = false;
|
||||
// don't propagate if not initialized
|
||||
if(!_initialized) return;
|
||||
// bind data between two minute selectors (only if they have the same multiple settings)
|
||||
if(settings.multiple_mins == settings.multiple_time_minutes) {
|
||||
if(selector == _selectorMins) {
|
||||
boundChanged = _selectorTimeM.setValue(_selectorMins.getValue());
|
||||
}
|
||||
else if(selector == _selectorTimeM) {
|
||||
boundChanged = _selectorMins.setValue(_selectorTimeM.getValue());
|
||||
}
|
||||
}
|
||||
// we propagate the change event to the main object
|
||||
boundChanged || _$obj.trigger('cron:change', _self.getCron());
|
||||
});
|
||||
_selectors.push(selector);
|
||||
return selector;
|
||||
}
|
||||
|
||||
// disable the selector
|
||||
this.disable = function(){
|
||||
_$obj.addClass('disable');
|
||||
settings.disable = true;
|
||||
_self.closeSelectors();
|
||||
};
|
||||
|
||||
// return if the selector is disabled
|
||||
this.isDisabled = function() {
|
||||
return settings.disable == true;
|
||||
};
|
||||
|
||||
// enable the selector
|
||||
this.enable = function(){
|
||||
_$obj.removeClass('disable');
|
||||
settings.disable = false;
|
||||
};
|
||||
|
||||
// get cron value
|
||||
this.getCron = function(){
|
||||
var period = _selectorPeriod.getValue();
|
||||
var items = ['*', '*', '*', '*', '*'];
|
||||
if(period == 'hour') {
|
||||
items[0] = _selectorMins.getCronValue();
|
||||
}
|
||||
if(period == 'day' || period == 'week' || period == 'month' || period == 'year') {
|
||||
items[0] = _selectorTimeM.getCronValue();
|
||||
items[1] = _selectorTimeH.getCronValue();
|
||||
}
|
||||
if(period == 'month' || period == 'year') {
|
||||
items[2] = _selectorDom.getCronValue();
|
||||
}
|
||||
if(period == 'year') {
|
||||
items[3] = _selectorMonth.getCronValue();
|
||||
}
|
||||
if(period == 'week') {
|
||||
items[4] = _selectorDow.getCronValue();
|
||||
}
|
||||
return items.join(' ');
|
||||
};
|
||||
|
||||
// set cron (string like * * * * *)
|
||||
this.setCron = function(str) {
|
||||
if(!str) return;
|
||||
try {
|
||||
str = str.replace(/\s+/g, ' ').replace(/^ +/, '').replace(/ +$/, ''); // sanitize
|
||||
var mask = str.replace(/[^\* ]/g, '-').replace(/-+/g, '-').replace(/ +/g, '');
|
||||
var items = str.split(' ');
|
||||
if (items.length != 5) _self.error(_self.getText('error2'));
|
||||
if(mask == '*****') { // 1 possibility
|
||||
_selectorPeriod.setValue('minute');
|
||||
}
|
||||
else if(mask == '-****') { // 1 possibility
|
||||
_selectorPeriod.setValue('hour');
|
||||
_selectorMins.setCronValue(items[0]);
|
||||
_selectorTimeM.setCronValue(items[0]);
|
||||
}
|
||||
else if(mask.substring(2, mask.length) == '***') { // 4 possibilities
|
||||
_selectorPeriod.setValue('day');
|
||||
_selectorMins.setCronValue(items[0]);
|
||||
_selectorTimeM.setCronValue(items[0]);
|
||||
_selectorTimeH.setCronValue(items[1]);
|
||||
}
|
||||
else if(mask.substring(2, mask.length) == '-**') { // 4 possibilities
|
||||
_selectorPeriod.setValue('month');
|
||||
_selectorMins.setCronValue(items[0]);
|
||||
_selectorTimeM.setCronValue(items[0]);
|
||||
_selectorTimeH.setCronValue(items[1]);
|
||||
_selectorDom.setCronValue(items[2]);
|
||||
}
|
||||
else if(mask.substring(2, mask.length) == '**-') { // 4 possibilities
|
||||
_selectorPeriod.setValue('week');
|
||||
_selectorMins.setCronValue(items[0]);
|
||||
_selectorTimeM.setCronValue(items[0]);
|
||||
_selectorTimeH.setCronValue(items[1]);
|
||||
_selectorDow.setCronValue(items[4]);
|
||||
}
|
||||
else if (mask.substring(3, mask.length) == '-*') { // 8 possibilities
|
||||
_selectorPeriod.setValue('year');
|
||||
_selectorMins.setCronValue(items[0]);
|
||||
_selectorTimeM.setCronValue(items[0]);
|
||||
_selectorTimeH.setCronValue(items[1]);
|
||||
_selectorDom.setCronValue(items[2]);
|
||||
_selectorMonth.setCronValue(items[3]);
|
||||
}
|
||||
else {
|
||||
_self.error(_self.getText('error4'));
|
||||
}
|
||||
_self.clearError();
|
||||
} catch(e) {}
|
||||
};
|
||||
|
||||
// close all child selectors
|
||||
this.closeSelectors = function(){
|
||||
for(var n = _selectors.length; n--; ){
|
||||
_selectors[n].close();
|
||||
}
|
||||
};
|
||||
|
||||
// get the main element id
|
||||
this.getId = function(){
|
||||
return _$elt.attr('id');
|
||||
}
|
||||
|
||||
// get the translated text
|
||||
this.getText = function(key) {
|
||||
var text = settings.texts[settings.lang][key] || null;
|
||||
if(typeof(text) == "string" && text.match('<b')){
|
||||
text = text.replace(/(<b *\/>)/gi, '</span><b /><span class="jqCron-text">');
|
||||
text = '<span class="jqCron-text">' + text + '</span>';
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
// get the human readable text
|
||||
this.getHumanText = function() {
|
||||
var texts=[];
|
||||
_$obj
|
||||
.find('> span > span:visible')
|
||||
.find('.jqCron-text, .jqCron-selector > span')
|
||||
.each(function() {
|
||||
var text = $(this).text().replace(/\s+$/g, '').replace(/^\s+/g, '');
|
||||
text && texts.push(text);
|
||||
});
|
||||
return texts.join(' ').replace(/\s:\s/g, ':');
|
||||
}
|
||||
|
||||
// get settings
|
||||
this.getSettings = function(){
|
||||
return settings;
|
||||
};
|
||||
|
||||
// display an error
|
||||
this.error = function(msg) {
|
||||
console && console.error('[jqCron Error] ' + msg);
|
||||
_$obj.addClass('jqCron-error').attr('title', msg);
|
||||
throw msg;
|
||||
};
|
||||
|
||||
// clear error
|
||||
this.clearError = function(){
|
||||
_$obj.attr('title', '').removeClass('jqCron-error');
|
||||
};
|
||||
|
||||
// clear
|
||||
this.clear = function() {
|
||||
_selectorDom.setValue([]);
|
||||
_selectorDow.setValue([]);
|
||||
_selectorMins.setValue([]);
|
||||
_selectorMonth.setValue([]);
|
||||
_selectorTimeH.setValue([]);
|
||||
_selectorTimeM.setValue([]);
|
||||
_self.triggerChange();
|
||||
};
|
||||
|
||||
// init (called in constructor)
|
||||
this.init = function(){
|
||||
var n,i,labelsList,list;
|
||||
if(_initialized) return;
|
||||
|
||||
settings = jqCronMergeSettings(settings);
|
||||
settings.jquery_element || _self.error(_self.getText('error3'));
|
||||
_$elt = settings.jquery_element;
|
||||
_$elt.append(_$obj);
|
||||
_$obj.data('id', settings.id);
|
||||
_$obj.data('jqCron', _self);
|
||||
_$obj.append(_$blocks);
|
||||
settings.no_reset_button || _$obj.append(_$cross);
|
||||
(!settings.disable) || _$obj.addClass('disable');
|
||||
_$blocks.append(_$blockPERIOD);
|
||||
|
||||
if ( /^(ko)$/i.test(settings.lang) )
|
||||
{
|
||||
_$blocks.append(_$blockMONTH, _$blockDOM);
|
||||
}
|
||||
else
|
||||
{
|
||||
_$blocks.append(_$blockDOM, _$blockMONTH);
|
||||
}
|
||||
|
||||
_$blocks.append(_$blockMINS);
|
||||
_$blocks.append(_$blockDOW);
|
||||
_$blocks.append(_$blockTIME);
|
||||
|
||||
// various binding
|
||||
_$cross.click(function(){
|
||||
_self.isDisabled() || _self.clear();
|
||||
});
|
||||
|
||||
// binding from cron to target
|
||||
_$obj.bind('cron:change', function(evt, value){
|
||||
if(!settings.bind_to) return;
|
||||
settings.bind_method.set && settings.bind_method.set(settings.bind_to, value);
|
||||
_self.clearError();
|
||||
});
|
||||
|
||||
// PERIOD
|
||||
_$blockPERIOD.append(_self.getText('text_period'));
|
||||
_selectorPeriod = newSelector(_$blockPERIOD, false, 'period');
|
||||
settings.enabled_minute && _selectorPeriod.add('minute', _self.getText('name_minute'));
|
||||
settings.enabled_hour && _selectorPeriod.add('hour', _self.getText('name_hour'));
|
||||
settings.enabled_day && _selectorPeriod.add('day', _self.getText('name_day'));
|
||||
settings.enabled_week && _selectorPeriod.add('week', _self.getText('name_week'));
|
||||
settings.enabled_month && _selectorPeriod.add('month', _self.getText('name_month'));
|
||||
settings.enabled_year && _selectorPeriod.add('year', _self.getText('name_year'));
|
||||
_selectorPeriod.$.bind('selector:change', function(e, value){
|
||||
_$blockDOM.hide();
|
||||
_$blockMONTH.hide();
|
||||
_$blockMINS.hide();
|
||||
_$blockDOW.hide();
|
||||
_$blockTIME.hide();
|
||||
if(value == 'hour') {
|
||||
_$blockMINS.show();
|
||||
}
|
||||
else if(value == 'day') {
|
||||
_$blockTIME.show();
|
||||
}
|
||||
else if(value == 'week') {
|
||||
_$blockDOW.show();
|
||||
_$blockTIME.show();
|
||||
}
|
||||
else if(value == 'month') {
|
||||
_$blockDOM.show();
|
||||
_$blockTIME.show();
|
||||
}
|
||||
else if(value == 'year') {
|
||||
_$blockDOM.show();
|
||||
_$blockMONTH.show();
|
||||
_$blockTIME.show();
|
||||
}
|
||||
});
|
||||
_selectorPeriod.setValue(settings.default_period);
|
||||
|
||||
// MINS (minutes)
|
||||
_$blockMINS.append(_self.getText('text_mins'));
|
||||
_selectorMins = newSelector(_$blockMINS, settings.multiple_mins, 'minutes');
|
||||
for(i=0, list=settings.minutes; i<list.length; i++){
|
||||
_selectorMins.add(list[i], list[i]);
|
||||
}
|
||||
|
||||
// TIME (hour:min)
|
||||
_$blockTIME.append(_self.getText('text_time'));
|
||||
_selectorTimeH = newSelector(_$blockTIME, settings.multiple_time_hours, 'time_hours');
|
||||
for(i=0, list=settings.hours, labelsList=settings.hour_labels; i<list.length; i++){
|
||||
_selectorTimeH.add(list[i], labelsList[i]);
|
||||
}
|
||||
_selectorTimeM = newSelector(_$blockTIME, settings.multiple_time_minutes, 'time_minutes');
|
||||
for(i=0, list=settings.minutes; i<list.length; i++){
|
||||
_selectorTimeM.add(list[i], list[i]);
|
||||
}
|
||||
|
||||
// DOW (day of week)
|
||||
_$blockDOW.append(_self.getText('text_dow'));
|
||||
_selectorDow = newSelector(_$blockDOW, settings.multiple_dow, 'day_of_week');
|
||||
for(i=0, list=_self.getText('weekdays'); i<list.length; i++){
|
||||
_selectorDow.add(i+1, list[i]);
|
||||
}
|
||||
|
||||
// DOM (day of month)
|
||||
_$blockDOM.append(_self.getText('text_dom'));
|
||||
_selectorDom = newSelector(_$blockDOM, settings.multiple_dom, 'day_of_month');
|
||||
for(i=0, list=settings.monthdays; i<list.length; i++){
|
||||
_selectorDom.add(list[i], list[i]);
|
||||
}
|
||||
|
||||
// MONTH (day of week)
|
||||
_$blockMONTH.append(_self.getText('text_month'));
|
||||
_selectorMonth = newSelector(_$blockMONTH, settings.multiple_month, 'month');
|
||||
for(i=0, list=_self.getText('months'); i<list.length; i++){
|
||||
_selectorMonth.add(i+1, list[i]);
|
||||
}
|
||||
|
||||
// close all selectors when we click in body
|
||||
$('body').click(function(){
|
||||
var i, n = _selectors.length;
|
||||
for(i = 0; i < n; i++){
|
||||
_selectors[i].close();
|
||||
}
|
||||
});
|
||||
_initialized = true;
|
||||
|
||||
// default value
|
||||
if(settings.default_value) {
|
||||
_self.setCron(settings.default_value);
|
||||
}
|
||||
};
|
||||
|
||||
// trigger a change event
|
||||
this.triggerChange = function(){
|
||||
_$obj.trigger('cron:change', _self.getCron());
|
||||
};
|
||||
|
||||
// store instance in array
|
||||
jqCronInstances.push(this);
|
||||
|
||||
// expose main jquery object
|
||||
this.$ = _$obj;
|
||||
|
||||
// init
|
||||
try {
|
||||
this.init();
|
||||
_self.triggerChange();
|
||||
} catch(e){}
|
||||
}
|
||||
this.jqCron = jqCron;
|
||||
}).call(window, $);
|
||||
|
||||
|
||||
/**
|
||||
* jqCronSelector class
|
||||
*/
|
||||
(function($){
|
||||
function jqCronSelector(_cron, _$block, _multiple, _type){
|
||||
var _self = this;
|
||||
var _$list = $('<ul class="jqCron-selector-list"></ul>');
|
||||
var _$title = $('<span class="jqCron-selector-title"></span>');
|
||||
var _$selector = $('<span class="jqCron-selector"></span>');
|
||||
var _values = {};
|
||||
var _value = [];
|
||||
var _hasNumericTexts = true;
|
||||
var _numeric_zero_pad = _cron.getSettings().numeric_zero_pad;
|
||||
|
||||
// return an array without doublon
|
||||
function array_unique(l){
|
||||
var i=0,n=l.length,k={},a=[];
|
||||
while(i<n) {
|
||||
k[l[i]] || (k[l[i]] = 1 && a.push(l[i]));
|
||||
i++;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// get the value (an array if multiple, else a single value)
|
||||
this.getValue = function(){
|
||||
return _multiple ? _value : _value[0];
|
||||
};
|
||||
|
||||
// get a correct string for cron
|
||||
this.getCronValue = function(){
|
||||
if(_value.length == 0) return '*';
|
||||
var cron = [_value[0]], i, s = _value[0], c = _value[0], n = _value.length;
|
||||
for(i=1; i<n; i++) {
|
||||
if(_value[i] == c+1) {
|
||||
c = _value[i];
|
||||
cron[cron.length-1] = s+'-'+c;
|
||||
}
|
||||
else {
|
||||
s = c = _value[i];
|
||||
cron.push(c);
|
||||
}
|
||||
}
|
||||
return cron.join(',');
|
||||
};
|
||||
|
||||
// set the cron value
|
||||
this.setCronValue = function(str) {
|
||||
var values = [], m ,i, n;
|
||||
if(str !== '*') {
|
||||
while(str != '') {
|
||||
// test "*/n" expression
|
||||
m = str.match(/^\*\/([0-9]+),?/);
|
||||
if(m && m.length == 2) {
|
||||
for(i=0; i<=59; i+=(m[1]|0)) {
|
||||
values.push(i);
|
||||
}
|
||||
str = str.replace(m[0], '');
|
||||
continue;
|
||||
}
|
||||
// test "a-b/n" expression
|
||||
m = str.match(/^([0-9]+)-([0-9]+)\/([0-9]+),?/);
|
||||
if(m && m.length == 4) {
|
||||
for(i=(m[1]|0); i<=(m[2]|0); i+=(m[3]|0)) {
|
||||
values.push(i);
|
||||
}
|
||||
str = str.replace(m[0], '');
|
||||
continue;
|
||||
}
|
||||
// test "a-b" expression
|
||||
m = str.match(/^([0-9]+)-([0-9]+),?/);
|
||||
if(m && m.length == 3) {
|
||||
for(i=(m[1]|0); i<=(m[2]|0); i++) {
|
||||
values.push(i);
|
||||
}
|
||||
str = str.replace(m[0], '');
|
||||
continue;
|
||||
}
|
||||
// test "c" expression
|
||||
m = str.match(/^([0-9]+),?/);
|
||||
if(m && m.length == 2) {
|
||||
values.push(m[1]|0);
|
||||
str = str.replace(m[0], '');
|
||||
continue;
|
||||
}
|
||||
// something goes wrong in the expression
|
||||
return ;
|
||||
}
|
||||
}
|
||||
_self.setValue(values);
|
||||
};
|
||||
|
||||
// close the selector
|
||||
this.close = function(){
|
||||
_$selector.trigger('selector:close');
|
||||
};
|
||||
|
||||
// open the selector
|
||||
this.open = function(){
|
||||
_$selector.trigger('selector:open');
|
||||
};
|
||||
|
||||
// whether the selector is open
|
||||
this.isOpened = function() {
|
||||
return _$list.is(':visible');
|
||||
};
|
||||
|
||||
// add a selected value to the list
|
||||
this.addValue = function(key) {
|
||||
var values = _multiple ? _value.slice(0) : []; // clone array
|
||||
values.push(key);
|
||||
_self.setValue(values);
|
||||
};
|
||||
|
||||
// remove a selected value from the list
|
||||
this.removeValue = function(key) {
|
||||
if(_multiple) {
|
||||
var i, newValue = [];
|
||||
for(i=0; i<_value.length; i++){
|
||||
if(key != [_value[i]]) {
|
||||
newValue.push(_value[i]);
|
||||
}
|
||||
}
|
||||
_self.setValue(newValue);
|
||||
}
|
||||
else {
|
||||
_self.clear();
|
||||
}
|
||||
};
|
||||
|
||||
// set the selected value(s) of the list
|
||||
this.setValue = function(keys){
|
||||
var i, newKeys = [], saved = _value.join(' ');
|
||||
if(!$.isArray(keys)) keys = [keys];
|
||||
_$list.find('li').removeClass('selected');
|
||||
keys = array_unique(keys);
|
||||
keys.sort(function(a, b){
|
||||
var ta = typeof(a);
|
||||
var tb = typeof(b);
|
||||
if(ta==tb && ta=="number") return a-b;
|
||||
else return String(a) == String(b) ? 0 : (String(a) < String(b) ? -1 : 1);
|
||||
});
|
||||
if(_multiple) {
|
||||
for(i=0; i<keys.length; i++){
|
||||
if(keys[i] in _values) {
|
||||
_values[keys[i]].addClass('selected');
|
||||
newKeys.push(keys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(keys[0] in _values) {
|
||||
_values[keys[0]].addClass('selected');
|
||||
newKeys.push(keys[0]);
|
||||
}
|
||||
}
|
||||
// remove unallowed values
|
||||
_value = newKeys;
|
||||
if(saved != _value.join(' ')) {
|
||||
_$selector.trigger('selector:change', _multiple ? keys : keys[0]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// get the title text
|
||||
this.getTitleText = function(){
|
||||
var getValueText = function(key) {
|
||||
return (key in _values) ? _values[key].text() : key;
|
||||
};
|
||||
|
||||
if(_value.length == 0) {
|
||||
return _cron.getText('empty_' + _type) || _cron.getText('empty');
|
||||
}
|
||||
var cron = [getValueText(_value[0])], i, s = _value[0], c = _value[0], n = _value.length;
|
||||
for(i=1; i<n; i++) {
|
||||
if(_value[i] == c+1) {
|
||||
c = _value[i];
|
||||
cron[cron.length-1] = getValueText(s)+'-'+getValueText(c);
|
||||
}
|
||||
else {
|
||||
s = c = _value[i];
|
||||
cron.push(getValueText(c));
|
||||
}
|
||||
}
|
||||
return cron.join(',');
|
||||
};
|
||||
|
||||
// clear list
|
||||
this.clear = function() {
|
||||
_values = {};
|
||||
_self.setValue([]);
|
||||
_$list.empty();
|
||||
};
|
||||
|
||||
// add a (key, value) pair
|
||||
this.add = function(key, value) {
|
||||
if(!(value+'').match(/^[0-9]+$/)) _hasNumericTexts = false;
|
||||
if(_numeric_zero_pad && _hasNumericTexts && value < 10) {
|
||||
value = '0'+value;
|
||||
}
|
||||
var $item = $('<li>' + value + '</li>');
|
||||
_$list.append($item);
|
||||
_values[key] = $item;
|
||||
$item.click(function(){
|
||||
if(_multiple && $(this).hasClass('selected')) {
|
||||
_self.removeValue(key);
|
||||
}
|
||||
else {
|
||||
_self.addValue(key);
|
||||
if(!_multiple) _self.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// expose main jquery object
|
||||
this.$ = _$selector;
|
||||
|
||||
// constructor
|
||||
_$block.find('b:eq(0)').after(_$selector).remove();
|
||||
_$selector
|
||||
.addClass('jqCron-selector-' + _$block.find('.jqCron-selector').length)
|
||||
.append(_$title)
|
||||
.append(_$list)
|
||||
.bind('selector:open', function(){
|
||||
if(_hasNumericTexts) {
|
||||
var nbcols = 1, n = _$list.find('li').length;
|
||||
if(n > 5 && n <= 16) nbcols = 2;
|
||||
else if(n > 16 && n <= 23) nbcols = 3;
|
||||
else if(n > 23 && n <= 40) nbcols = 4;
|
||||
else if(n > 40) nbcols = 5;
|
||||
_$list.addClass('cols'+nbcols);
|
||||
}
|
||||
_$list.show();
|
||||
})
|
||||
.bind('selector:close', function(){
|
||||
_$list.hide();
|
||||
})
|
||||
.bind('selector:change', function(){
|
||||
_$title.html(_self.getTitleText());
|
||||
})
|
||||
.click(function(e){
|
||||
e.stopPropagation();
|
||||
})
|
||||
.trigger('selector:change')
|
||||
;
|
||||
$.fn.disableSelection && _$selector.disableSelection(); // only work with jQuery UI
|
||||
_$title.click(function(e){
|
||||
(_self.isOpened() || _cron.isDisabled()) ? _self.close() : _self.open();
|
||||
});
|
||||
_self.close();
|
||||
_self.clear();
|
||||
}
|
||||
this.jqCronSelector = jqCronSelector;
|
||||
}).call(window, $);
|
||||
|
||||
/**
|
||||
* Generate unique id for each element.
|
||||
* Skip elements which have already an id.
|
||||
*/
|
||||
(function($){
|
||||
var jqUID = 0;
|
||||
var jqGetUID = function(prefix){
|
||||
var id;
|
||||
while(1) {
|
||||
jqUID++;
|
||||
id = ((prefix || 'JQUID')+'') + jqUID;
|
||||
if(!document.getElementById(id)) return id;
|
||||
}
|
||||
};
|
||||
$.fn.uniqueId = function(prefix) {
|
||||
return this.each(function(){
|
||||
if($(this).attr('id')) return;
|
||||
var id = jqGetUID(prefix);
|
||||
$(this).attr('id', id);
|
||||
});
|
||||
};
|
||||
}).call(window, $);
|
||||
|
||||
|
||||
/**
|
||||
* Extends jQuery selectors with new block selector
|
||||
*/
|
||||
(function($){
|
||||
$.extend($.expr[':'], {
|
||||
container: function(a) {
|
||||
return (a.tagName+'').toLowerCase() in {
|
||||
a:1,
|
||||
abbr:1,
|
||||
acronym:1,
|
||||
address:1,
|
||||
b:1,
|
||||
big:1,
|
||||
blockquote:1,
|
||||
button:1,
|
||||
cite:1,
|
||||
code:1,
|
||||
dd: 1,
|
||||
del:1,
|
||||
dfn:1,
|
||||
div:1,
|
||||
dt:1,
|
||||
em:1,
|
||||
fieldset:1,
|
||||
form:1,
|
||||
h1:1,
|
||||
h2:1,
|
||||
h3:1,
|
||||
h4:1,
|
||||
h5:1,
|
||||
h6: 1,
|
||||
i:1,
|
||||
ins:1,
|
||||
kbd:1,
|
||||
label:1,
|
||||
li:1,
|
||||
p:1,
|
||||
pre:1,
|
||||
q:1,
|
||||
samp:1,
|
||||
small:1,
|
||||
span:1,
|
||||
strong:1,
|
||||
sub: 1,
|
||||
sup:1,
|
||||
td:1,
|
||||
tt:1
|
||||
};
|
||||
},
|
||||
autoclose: function(a) {
|
||||
return (a.tagName+'').toLowerCase() in {
|
||||
area:1,
|
||||
base:1,
|
||||
basefont:1,
|
||||
br:1,
|
||||
col:1,
|
||||
frame:1,
|
||||
hr:1,
|
||||
img:1,
|
||||
input:1,
|
||||
link:1,
|
||||
meta:1,
|
||||
param:1
|
||||
};
|
||||
}
|
||||
});
|
||||
}).call(window, $);
|
||||
325
user/plugins/admin/themes/grav/app/utils/finderjs.js
Normal file
325
user/plugins/admin/themes/grav/app/utils/finderjs.js
Normal file
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* (c) Trilby Media, LLC
|
||||
* Author Djamil Legato
|
||||
*
|
||||
* Based on Mark Matyas's Finderjs
|
||||
* MIT License
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
export const DEFAULTS = {
|
||||
labelKey: 'name',
|
||||
valueKey: 'value', // new
|
||||
childKey: 'children',
|
||||
iconKey: 'icon', // new
|
||||
itemKey: 'item-key', // new
|
||||
pathBar: true,
|
||||
className: {
|
||||
container: 'fjs-container',
|
||||
pathBar: 'fjs-path-bar',
|
||||
col: 'fjs-col',
|
||||
list: 'fjs-list',
|
||||
item: 'fjs-item',
|
||||
active: 'fjs-active',
|
||||
children: 'fjs-has-children',
|
||||
url: 'fjs-url',
|
||||
itemPrepend: 'fjs-item-prepend',
|
||||
itemContent: 'fjs-item-content',
|
||||
itemAppend: 'fjs-item-append'
|
||||
}
|
||||
};
|
||||
|
||||
class Finder {
|
||||
constructor(container, data, options) {
|
||||
this.$emitter = new EventEmitter();
|
||||
this.container = $(container);
|
||||
this.data = data;
|
||||
|
||||
this.config = $.extend({}, DEFAULTS, options);
|
||||
|
||||
// dom events
|
||||
this.container.on('click', this.clickEvent.bind(this));
|
||||
this.container.on('keydown', this.keydownEvent.bind(this));
|
||||
|
||||
// internal events
|
||||
this.$emitter.on('item-selected', this.itemSelected.bind(this));
|
||||
this.$emitter.on('create-column', this.addColumn.bind(this));
|
||||
this.$emitter.on('navigate', this.navigate.bind(this));
|
||||
this.$emitter.on('go-to', this.goTo.bind(this, this.data));
|
||||
|
||||
this.container.addClass(this.config.className.container).attr('tabindex', 0);
|
||||
|
||||
this.createColumn(this.data);
|
||||
|
||||
if (this.config.pathBar) {
|
||||
this.pathBar = this.createPathBar();
|
||||
this.pathBar.on('click', '[data-breadcrumb-node]', (event) => {
|
||||
event.preventDefault();
|
||||
const location = $(event.currentTarget).data('breadcrumbNode');
|
||||
this.goTo(this.data, location);
|
||||
});
|
||||
}
|
||||
|
||||
// '' is <Root>
|
||||
if (this.config.defaultPath || this.config.defaultPath === '') {
|
||||
this.goTo(this.data, this.config.defaultPath);
|
||||
}
|
||||
}
|
||||
|
||||
reload(data = this.data) {
|
||||
this.createColumn(data);
|
||||
|
||||
// '' is <Root>
|
||||
if (this.config.defaultPath || this.config.defaultPath === '') {
|
||||
this.goTo(data, this.config.defaultPath);
|
||||
}
|
||||
}
|
||||
|
||||
createColumn(data, parent) {
|
||||
const callback = (data) => this.createColumn(data, parent);
|
||||
|
||||
if (typeof data === 'function') {
|
||||
data.call(this, parent, callback);
|
||||
} else if (Array.isArray(data) || typeof data === 'object') {
|
||||
if (typeof data === 'object') {
|
||||
data = Array.from(data);
|
||||
}
|
||||
const list = this.createList(data);
|
||||
const div = $('<div />');
|
||||
div.append(list).addClass(this.config.className.col);
|
||||
this.$emitter.emit('create-column', div);
|
||||
|
||||
return div;
|
||||
} else {
|
||||
throw new Error('Unknown data type');
|
||||
}
|
||||
}
|
||||
|
||||
createPathBar() {
|
||||
this.container.siblings(`.${this.config.className.pathBar}`).remove();
|
||||
const pathBar = $(`<div class="${this.config.className.pathBar}" />`);
|
||||
pathBar.insertAfter(this.container);
|
||||
|
||||
return pathBar;
|
||||
}
|
||||
|
||||
clickEvent(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const target = $(event.target);
|
||||
const column = target.closest(`.${this.config.className.col}`);
|
||||
const item = target.closest(`.${this.config.className.item}`);
|
||||
|
||||
if (item.length) {
|
||||
this.$emitter.emit('item-selected', { column, item });
|
||||
}
|
||||
}
|
||||
|
||||
keydownEvent(event) {
|
||||
const codes = { 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
|
||||
|
||||
if (event.keyCode in codes) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.$emitter.emit('navigate', {
|
||||
direction: codes[event.keyCode]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
itemSelected(value) {
|
||||
const element = value.item;
|
||||
if (!element.length) { return false; }
|
||||
const item = element[0]._item;
|
||||
const column = value.column;
|
||||
const data = item[this.config.childKey] || this.data;
|
||||
const active = $(column).find(`.${this.config.className.active}`);
|
||||
|
||||
if (active.length) {
|
||||
active.removeClass(this.config.className.active);
|
||||
}
|
||||
|
||||
element.addClass(this.config.className.active);
|
||||
column.nextAll().remove(); // ?!?!?
|
||||
|
||||
this.container[0].focus();
|
||||
window.scrollTo(window.pageXOffset, window.pageYOffset);
|
||||
|
||||
this.updatePathBar();
|
||||
|
||||
let newColumn;
|
||||
if (data) {
|
||||
newColumn = this.createColumn(data, item);
|
||||
this.$emitter.emit('interior-selected', item);
|
||||
} else {
|
||||
this.$emitter.emit('leaf-selected', item);
|
||||
}
|
||||
|
||||
return newColumn;
|
||||
}
|
||||
|
||||
addColumn(column) {
|
||||
this.container.append(column);
|
||||
this.$emitter.emit('column-created', column);
|
||||
}
|
||||
|
||||
navigate(value) {
|
||||
const active = this.findLastActive();
|
||||
const direction = value.direction;
|
||||
let column;
|
||||
let item;
|
||||
let target;
|
||||
|
||||
if (active) {
|
||||
item = active.item;
|
||||
column = active.column;
|
||||
|
||||
if (direction === 'up' && item.prev().length) {
|
||||
target = item.prev();
|
||||
} else if (direction === 'down' && item.next().length) {
|
||||
target = item.next();
|
||||
} else if (direction === 'right' && column.next().length) {
|
||||
column = column.next();
|
||||
target = column.find(`.${this.config.className.item}`).first();
|
||||
} else if (direction === 'left' && column.prev().length) {
|
||||
column = column.prev();
|
||||
target = column.find(`.${this.config.className.active}`).first() || column.find(`.${this.config.className.item}`);
|
||||
}
|
||||
} else {
|
||||
column = this.container.find(`.${this.config.className.col}`).first();
|
||||
target = column.find(`.${this.config.className.item}`).first();
|
||||
}
|
||||
|
||||
if (target) {
|
||||
this.$emitter.emit('item-selected', {
|
||||
column,
|
||||
item: target
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
goTo(data, path) {
|
||||
path = Array.isArray(path) ? path : path.split('/').map(bit => bit.trim()).filter(Boolean);
|
||||
|
||||
if (path.length) {
|
||||
this.container.children().remove();
|
||||
}
|
||||
|
||||
if (typeof data === 'function') {
|
||||
data.call(this, null, (data) => this.selectPath(path, data));
|
||||
} else {
|
||||
this.selectPath(path, data);
|
||||
}
|
||||
}
|
||||
|
||||
selectPath(path, data, column) {
|
||||
column = column || (path.length ? this.createColumn(data) : this.container.find(`> .${this.config.className.col}`));
|
||||
|
||||
const current = path[0] || '';
|
||||
const children = data.find((item) => item[this.config.itemKey] === current);
|
||||
const newColumn = this.itemSelected({
|
||||
column,
|
||||
item: column.find(`[data-fjs-item="${current}"]`).first()
|
||||
});
|
||||
|
||||
path.shift();
|
||||
|
||||
if (path.length && children) {
|
||||
this.selectPath(path, children[this.config.childKey], newColumn);
|
||||
}
|
||||
}
|
||||
|
||||
findLastActive() {
|
||||
const active = this.container.find(`.${this.config.className.active}`);
|
||||
if (!active.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = active.last();
|
||||
const column = item.closest(`.${this.config.className.col}`);
|
||||
|
||||
return { item, column };
|
||||
}
|
||||
|
||||
createList(data) {
|
||||
const list = $('<ul />');
|
||||
const items = data.map((item) => this.createItem(item));
|
||||
|
||||
const fragments = items.reduce((fragment, current) => {
|
||||
fragment.appendChild(current[0] || current);
|
||||
|
||||
return fragment;
|
||||
}, document.createDocumentFragment());
|
||||
|
||||
list.append(fragments).addClass(this.config.className.list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
createItem(item) {
|
||||
const listItem = $('<li />');
|
||||
const listItemClasses = [this.config.className.item];
|
||||
const link = $('<a />');
|
||||
const createItemContent = this.config.createItemContent || this.createItemContent;
|
||||
const fragment = createItemContent.call(this, item);
|
||||
link.append(fragment)
|
||||
.attr('href', '')
|
||||
.attr('tabindex', -1);
|
||||
|
||||
if (item.url) {
|
||||
link.attr('href', item.url);
|
||||
listItemClasses.push(item.className);
|
||||
}
|
||||
|
||||
if (item[this.config.childKey]) {
|
||||
listItemClasses.push(this.config.className[this.config.childKey]);
|
||||
}
|
||||
|
||||
listItemClasses.push(`fjs-item-${item.type}`);
|
||||
listItem.addClass(listItemClasses.join(' '));
|
||||
listItem.append(link)
|
||||
.attr('data-fjs-item', item[this.config.itemKey]);
|
||||
|
||||
listItem[0]._item = item;
|
||||
|
||||
return listItem;
|
||||
}
|
||||
|
||||
updatePathBar() {
|
||||
if (!this.config.pathBar) { return false; }
|
||||
|
||||
const activeItems = this.container.find(`.${this.config.className.active}`);
|
||||
let itemKeys = '';
|
||||
this.pathBar.children().empty();
|
||||
activeItems.each((index, activeItem) => {
|
||||
const item = activeItem._item;
|
||||
const isLast = (index + 1) === activeItems.length;
|
||||
itemKeys += `/${item[this.config.itemKey]}`;
|
||||
this.pathBar.append(`
|
||||
<span class="breadcrumb-node breadcrumb-node-${item.type}" ${item.type === 'dir' ? `data-breadcrumb-node="${itemKeys}"` : ''}>
|
||||
<i class="fa fa-fw ${this.getIcon(item.type)}"></i>
|
||||
<span class="breadcrumb-node-name">${$('<div />').html(item[this.config.labelKey]).html()}</span>
|
||||
${!isLast ? '<i class="fa fa-fw fa-chevron-right"></i>' : ''}
|
||||
</span>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
getIcon(type) {
|
||||
switch (type) {
|
||||
case 'root':
|
||||
return 'fa-sitemap';
|
||||
case 'file':
|
||||
return 'fa-file-o';
|
||||
case 'dir':
|
||||
default:
|
||||
return 'fa-folder';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Finder;
|
||||
11
user/plugins/admin/themes/grav/app/utils/formatbytes.js
Normal file
11
user/plugins/admin/themes/grav/app/utils/formatbytes.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
export default function formatBytes(bytes, decimals) {
|
||||
if (bytes === 0) return '0 Byte';
|
||||
|
||||
let k = 1000;
|
||||
let value = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
let decimal = decimals + 1 || 3;
|
||||
|
||||
return (bytes / Math.pow(k, value)).toPrecision(decimal) + ' ' + sizes[value];
|
||||
}
|
||||
57
user/plugins/admin/themes/grav/app/utils/gpm.js
Normal file
57
user/plugins/admin/themes/grav/app/utils/gpm.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { parseJSON, parseStatus, userFeedbackError } from './response';
|
||||
import { config } from 'grav-config';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
export default class GPM extends EventEmitter {
|
||||
constructor(action = 'getUpdates') {
|
||||
super();
|
||||
this.payload = {};
|
||||
this.raw = {};
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
setPayload(payload = {}) {
|
||||
this.payload = payload;
|
||||
this.emit('payload', payload);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setAction(action = 'getUpdates') {
|
||||
this.action = action;
|
||||
this.emit('action', action);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
fetch(callback = () => true, flush = false) {
|
||||
let data = new FormData();
|
||||
data.append('admin-nonce', config.admin_nonce);
|
||||
|
||||
if (flush) {
|
||||
data.append('flush', true);
|
||||
}
|
||||
|
||||
this.emit('fetching', this);
|
||||
|
||||
fetch(`${config.base_url_relative}/update.json/task${config.param_sep}getUpdates`, {
|
||||
credentials: 'same-origin',
|
||||
method: 'post',
|
||||
body: data
|
||||
}).then((response) => { this.raw = response; return response; })
|
||||
.then(parseStatus)
|
||||
.then(parseJSON)
|
||||
.then((response) => this.response(response))
|
||||
.then((response) => callback(response, this.raw))
|
||||
.then((response) => this.emit('fetched', this.payload, this.raw, this))
|
||||
.catch(userFeedbackError);
|
||||
}
|
||||
|
||||
response(response) {
|
||||
this.payload = response;
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new GPM();
|
||||
50
user/plugins/admin/themes/grav/app/utils/jquery-utils.js
vendored
Normal file
50
user/plugins/admin/themes/grav/app/utils/jquery-utils.js
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import $ from 'jquery';
|
||||
import getSlug from 'speakingurl';
|
||||
|
||||
// jQuery no parents filter
|
||||
$.expr[':']['noparents'] = $.expr.createPseudo((text) => (element) => $(element).parents(text).length < 1);
|
||||
|
||||
// Slugify
|
||||
// CommonJS and ES6 version of https://github.com/madflow/jquery-slugify
|
||||
$.fn.slugify = (source, options) => {
|
||||
return this.each((element) => {
|
||||
let target = $(element);
|
||||
let source = $(source);
|
||||
|
||||
target.on('keyup change', () => {
|
||||
target.data('locked', target.val() !== '' && target.val() !== undefined);
|
||||
});
|
||||
|
||||
source.on('keyup change', () => {
|
||||
if (target.data('locked') === true) { return true; }
|
||||
|
||||
let isInput = target.is('input') || target.is('textarea');
|
||||
target[isInput ? 'val' : 'text']($.slugify(source.val(), options));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Static method.
|
||||
$.slugify = (sourceString, options) => {
|
||||
options = $.extend({}, $.slugify.options, options);
|
||||
options.lang = options.lang || $('html').prop('lang');
|
||||
|
||||
if (typeof options.preSlug === 'function') {
|
||||
sourceString = options.preSlug(sourceString);
|
||||
}
|
||||
|
||||
sourceString = options.slugFunc(sourceString, options);
|
||||
|
||||
if (typeof options.postSlug === 'function') {
|
||||
sourceString = options.postSlug(sourceString);
|
||||
}
|
||||
|
||||
return sourceString;
|
||||
};
|
||||
|
||||
// Default plugin options
|
||||
$.slugify.options = {
|
||||
preSlug: null,
|
||||
postSlug: null,
|
||||
slugFunc: (input, opts) => getSlug(input, opts)
|
||||
};
|
||||
34
user/plugins/admin/themes/grav/app/utils/keepalive.js
Normal file
34
user/plugins/admin/themes/grav/app/utils/keepalive.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { config } from 'grav-config';
|
||||
import { userFeedbackError } from './response';
|
||||
|
||||
const MAX_SAFE_DELAY = 2147483647;
|
||||
|
||||
class KeepAlive {
|
||||
constructor() {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
start() {
|
||||
let timeout = config.admin_timeout / 1.5 * 1000;
|
||||
this.timer = setInterval(() => this.fetch(), Math.min(timeout, MAX_SAFE_DELAY));
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this.timer);
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
fetch() {
|
||||
let data = new FormData();
|
||||
data.append('admin-nonce', config.admin_nonce);
|
||||
|
||||
fetch(`${config.base_url_relative}/task${config.param_sep}keepAlive`, {
|
||||
credentials: 'same-origin',
|
||||
method: 'post',
|
||||
body: data
|
||||
}).catch(userFeedbackError);
|
||||
}
|
||||
}
|
||||
|
||||
export default new KeepAlive();
|
||||
21
user/plugins/admin/themes/grav/app/utils/offline.js
Normal file
21
user/plugins/admin/themes/grav/app/utils/offline.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import $ from 'jquery';
|
||||
import isOnline from '../utils/offline';
|
||||
|
||||
const offlineElement = $('#offline-status');
|
||||
|
||||
$(window).on('offline', () => {
|
||||
offlineElement.slideDown();
|
||||
});
|
||||
|
||||
$(window).on('online', () => {
|
||||
offlineElement.slideUp();
|
||||
});
|
||||
|
||||
$(document).ready(() => {
|
||||
if (!isOnline) {
|
||||
offlineElement.slideDown();
|
||||
}
|
||||
});
|
||||
|
||||
// assume online if can't check
|
||||
export default typeof global.navigator.onLine !== 'undefined' ? global.navigator.onLine : true;
|
||||
498
user/plugins/admin/themes/grav/app/utils/packages.js
Normal file
498
user/plugins/admin/themes/grav/app/utils/packages.js
Normal file
@@ -0,0 +1,498 @@
|
||||
import $ from 'jquery';
|
||||
import { config, translations } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
import { Instance as gpm } from '../utils/gpm';
|
||||
|
||||
class Sorter {
|
||||
getElements(elements, container) {
|
||||
this.elements = elements || document.querySelectorAll('[data-gpm-plugin], [data-gpm-theme]');
|
||||
this.container = container || document.querySelector('.gpm-plugins > table > tbody, .gpm-themes > .themes.card-row');
|
||||
|
||||
return this.elements;
|
||||
}
|
||||
|
||||
static sort(A, B, direction = 'asc') {
|
||||
if (A > B) { return (direction === 'asc') ? 1 : -1; }
|
||||
if (A < B) { return (direction === 'asc') ? -1 : 1; }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
byCommon(direction = 'asc', data = '') {
|
||||
const elements = this.getElements();
|
||||
this.removeGumroad();
|
||||
|
||||
Array.from(elements).sort((a, b) => {
|
||||
let A = a.dataset[data].toString().toLowerCase();
|
||||
let B = b.dataset[data].toString().toLowerCase();
|
||||
|
||||
return Sorter.sort(A, B, direction);
|
||||
}).forEach((element) => {
|
||||
this.container.appendChild(element);
|
||||
});
|
||||
|
||||
this.addGumroad();
|
||||
return this.container;
|
||||
}
|
||||
|
||||
byName(direction = 'asc', data = 'gpmName') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byAuthor(direction = 'asc', data = 'gpmAuthor') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byOfficial(direction = 'asc', data = 'gpmOfficial') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byPremium(direction = 'asc', data = 'gpmPremium') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byReleaseDate(direction = 'asc', data = 'gpmReleaseDate') {
|
||||
const elements = this.getElements();
|
||||
|
||||
this.removeGumroad();
|
||||
Array.from(elements).sort((a, b) => {
|
||||
let A = new Date(a.dataset[data]).getTime();
|
||||
let B = new Date(b.dataset[data]).getTime();
|
||||
|
||||
return Sorter.sort(A, B, direction === 'asc' ? 'desc' : 'asc');
|
||||
}).forEach((element) => {
|
||||
this.container.appendChild(element);
|
||||
});
|
||||
|
||||
this.addGumroad();
|
||||
return this.container;
|
||||
}
|
||||
|
||||
byUpdatable(direction = 'asc', data = 'gpmUpdatable') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byEnabled(direction = 'asc', data = 'gpmEnabled') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byTesting(direction = 'asc', data = 'gpmTesting') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
addGumroad() {
|
||||
if (window.GumroadOverlay) {
|
||||
window.GumroadOverlay.startNodeAdditionObserver();
|
||||
}
|
||||
}
|
||||
|
||||
removeGumroad() {
|
||||
if (window.GumroadOverlay) {
|
||||
window.GumroadOverlay.nodeAdditionObserver.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Packages {
|
||||
constructor() {
|
||||
this.Sort = new Sorter();
|
||||
}
|
||||
|
||||
static getBackToList(type) {
|
||||
global.location.href = `${config.base_url_relative}/${type}s`;
|
||||
}
|
||||
|
||||
static addDependencyToList(type, dependency, slug = '') {
|
||||
if (['admin', 'form', 'login', 'email', 'grav'].indexOf(dependency) !== -1) { return; }
|
||||
let container = $('.package-dependencies-container');
|
||||
let text = `${dependency} <a href="#" class="button" data-dependency-slug="${dependency}" data-${type}-action="remove-dependency-package">Remove</a>`;
|
||||
|
||||
if (slug) {
|
||||
text += ` (was needed by ${slug})`;
|
||||
}
|
||||
|
||||
container.append(`<li>${text}</li>`);
|
||||
}
|
||||
|
||||
addDependenciesToList(dependencies, slug = '') {
|
||||
dependencies.forEach((dependency) => {
|
||||
Packages.addDependencyToList('plugin', dependency.name || dependency, slug);
|
||||
});
|
||||
}
|
||||
|
||||
static getTaskUrl(type, task) {
|
||||
let url = `${config.base_url_relative}`;
|
||||
url += `/${type}s.json`;
|
||||
url += `/task${config.param_sep}${task}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
static getRemovePackageUrl(type) {
|
||||
return `${Packages.getTaskUrl(type, 'removePackage')}`;
|
||||
}
|
||||
|
||||
static getReinstallPackageUrl(type) {
|
||||
return `${Packages.getTaskUrl(type, 'reinstallPackage')}`;
|
||||
}
|
||||
|
||||
static getGetPackagesDependenciesUrl(type) {
|
||||
return `${Packages.getTaskUrl(type, 'getPackagesDependencies')}`;
|
||||
}
|
||||
|
||||
static getInstallDependenciesOfPackagesUrl(type) {
|
||||
return `${Packages.getTaskUrl(type, 'installDependenciesOfPackages')}`;
|
||||
}
|
||||
|
||||
static getInstallPackageUrl(type) {
|
||||
return `${Packages.getTaskUrl(type, 'installPackage')}`;
|
||||
}
|
||||
|
||||
removePackage(type, slug) {
|
||||
let url = Packages.getRemovePackageUrl(type);
|
||||
|
||||
request(url, {
|
||||
method: 'post',
|
||||
body: {
|
||||
package: slug
|
||||
}
|
||||
}, (response) => {
|
||||
if (response.status === 'success') {
|
||||
$('.remove-package-confirm').addClass('hidden');
|
||||
|
||||
if (response.dependencies && response.dependencies.length > 0) {
|
||||
this.addDependenciesToList(response.dependencies);
|
||||
$('.remove-package-dependencies').removeClass('hidden');
|
||||
} else {
|
||||
$('.remove-package-done').removeClass('hidden');
|
||||
}
|
||||
|
||||
// The package was removed. When the modal closes, move to the packages list
|
||||
$(document).on('closing', '[data-remodal-id="remove-package"]', () => {
|
||||
Packages.getBackToList(type);
|
||||
});
|
||||
} else {
|
||||
$('.remove-package-confirm').addClass('hidden');
|
||||
$('.remove-package-error').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reinstallPackage(type, slug, package_name, current_version) {
|
||||
$('.button-bar button').addClass('hidden');
|
||||
$('.button-bar .spinning-wheel').removeClass('hidden');
|
||||
|
||||
let url = Packages.getReinstallPackageUrl(type);
|
||||
|
||||
request(url, {
|
||||
method: 'post',
|
||||
body: {
|
||||
slug: slug,
|
||||
type: type,
|
||||
package_name: package_name,
|
||||
current_version: current_version
|
||||
}
|
||||
}, (response) => {
|
||||
if (response.status === 'success') {
|
||||
$('.reinstall-package-confirm').addClass('hidden');
|
||||
$('.reinstall-package-done').removeClass('hidden');
|
||||
} else {
|
||||
$('.reinstall-package-confirm').addClass('hidden');
|
||||
$('.reinstall-package-error').removeClass('hidden');
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
removeDependency(type, slug, button) {
|
||||
let url = Packages.getRemovePackageUrl(type);
|
||||
|
||||
request(url, {
|
||||
method: 'post',
|
||||
body: {
|
||||
package: slug
|
||||
}
|
||||
}, (response) => {
|
||||
if (response.status === 'success') {
|
||||
button.removeClass('button');
|
||||
button.replaceWith($('<span>Removed successfully</span>'));
|
||||
|
||||
if (response.dependencies && response.dependencies.length > 0) {
|
||||
this.addDependenciesToList(response.dependencies, slug);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static addNeededDependencyToList(action, slug) {
|
||||
$('.install-dependencies-package-container .type-' + action).removeClass('hidden');
|
||||
let list = $('.install-dependencies-package-container .type-' + action + ' ul');
|
||||
|
||||
if (action !== 'install') {
|
||||
let current_version = '';
|
||||
let available_version = '';
|
||||
let name = '';
|
||||
|
||||
let resources = gpm.payload.payload.resources;
|
||||
|
||||
if (resources.plugins[slug]) {
|
||||
available_version = resources.plugins[slug].available;
|
||||
current_version = resources.plugins[slug].version;
|
||||
name = resources.plugins[slug].name;
|
||||
} else if (resources.themes[slug]) {
|
||||
available_version = resources.themes[slug].available;
|
||||
current_version = resources.themes[slug].version;
|
||||
name = resources.themes[slug].name;
|
||||
}
|
||||
|
||||
list.append(`<li>${name ? name : slug}, ${translations.PLUGIN_ADMIN.FROM} v<strong>${current_version}</strong> ${translations.PLUGIN_ADMIN.TO} v<strong>${available_version}</strong></li>`);
|
||||
} else {
|
||||
list.append(`<li>${name ? name : slug}</li>`);
|
||||
}
|
||||
}
|
||||
|
||||
getPackagesDependencies(type, slugs, finishedLoadingCallback) {
|
||||
let url = Packages.getGetPackagesDependenciesUrl(type);
|
||||
|
||||
request(url, {
|
||||
method: 'post',
|
||||
body: {
|
||||
packages: slugs
|
||||
}
|
||||
}, (response) => {
|
||||
|
||||
finishedLoadingCallback();
|
||||
|
||||
if (response.status === 'success') {
|
||||
if (response.dependencies) {
|
||||
let hasDependencies = false;
|
||||
for (var dependency in response.dependencies) {
|
||||
if (response.dependencies.hasOwnProperty(dependency)) {
|
||||
if (dependency === 'grav') {
|
||||
continue;
|
||||
}
|
||||
hasDependencies = true;
|
||||
let dependencyName = dependency;
|
||||
let action = response.dependencies[dependency];
|
||||
|
||||
Packages.addNeededDependencyToList(action, dependencyName);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDependencies) {
|
||||
$('[data-packages-modal] .install-dependencies-package-container').removeClass('hidden');
|
||||
} else {
|
||||
$('[data-packages-modal] .install-package-container').removeClass('hidden');
|
||||
}
|
||||
} else {
|
||||
$('[data-packages-modal] .install-package-container').removeClass('hidden');
|
||||
}
|
||||
} else {
|
||||
$('[data-packages-modal] .install-package-error').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
installDependenciesOfPackages(type, slugs, callbackSuccess, callbackError) {
|
||||
let url = Packages.getInstallDependenciesOfPackagesUrl(type);
|
||||
|
||||
request(url, {
|
||||
method: 'post',
|
||||
body: {
|
||||
packages: slugs
|
||||
}
|
||||
}, callbackSuccess);
|
||||
}
|
||||
|
||||
installPackages(type, slugs, callbackSuccess) {
|
||||
let url = Packages.getInstallPackageUrl(type);
|
||||
|
||||
global.Promise.all(slugs.map((slug) => {
|
||||
return new global.Promise((resolve, reject) => {
|
||||
request(url, {
|
||||
method: 'post',
|
||||
body: {
|
||||
package: slug,
|
||||
type: type
|
||||
}
|
||||
}, (response) => {
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
})).then(callbackSuccess);
|
||||
|
||||
}
|
||||
|
||||
static getSlugsFromEvent(event) {
|
||||
let slugs = '';
|
||||
if ($(event.target).is('[data-packages-slugs]')) {
|
||||
slugs = $(event.target).attr('data-packages-slugs');
|
||||
} else {
|
||||
slugs = $(event.target).parent('[data-packages-slugs]').attr('data-packages-slugs');
|
||||
}
|
||||
|
||||
if (typeof slugs === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
slugs = slugs.split(',');
|
||||
return typeof slugs === 'string' ? [slugs] : slugs;
|
||||
}
|
||||
|
||||
handleGettingPackageDependencies(type, event, action = 'update') {
|
||||
let slugs = Packages.getSlugsFromEvent(event);
|
||||
|
||||
if (!slugs) {
|
||||
alert('No slug set');
|
||||
return;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
$('.packages-names-list').html('');
|
||||
$('.install-dependencies-package-container li').remove();
|
||||
|
||||
slugs.forEach((slug) => {
|
||||
if (action === 'update') {
|
||||
let current_version = '';
|
||||
let available_version = '';
|
||||
let name = '';
|
||||
|
||||
let resources = gpm.payload.payload.resources;
|
||||
|
||||
if (resources.plugins[slug]) {
|
||||
available_version = resources.plugins[slug].available;
|
||||
current_version = resources.plugins[slug].version;
|
||||
name = resources.plugins[slug].name;
|
||||
} else if (resources.themes[slug]) {
|
||||
available_version = resources.themes[slug].available;
|
||||
current_version = resources.themes[slug].version;
|
||||
name = resources.themes[slug].name;
|
||||
}
|
||||
|
||||
$('.packages-names-list').append(`<li>${name ? name : slug}, ${translations.PLUGIN_ADMIN.FROM} v<strong>${current_version}</strong> ${translations.PLUGIN_ADMIN.TO} v<strong>${available_version}</strong></li>`);
|
||||
} else {
|
||||
$('.packages-names-list').append(`<li>${name ? name : slug}</li>`);
|
||||
}
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// fix mismatching types when sharing install modal between plugins/themes
|
||||
const query = '[data-packages-modal] [data-theme-action], [data-packages-modal] [data-plugin-action]';
|
||||
const data = $(query).data('themeAction') || $(query).data('pluginAction');
|
||||
$(query).removeAttr('data-theme-action').removeAttr('data-plugin-action').attr(`data-${type}-action`, data);
|
||||
|
||||
// Restore original state
|
||||
$('[data-packages-modal] .loading').removeClass('hidden');
|
||||
$('[data-packages-modal] .install-dependencies-package-container').addClass('hidden');
|
||||
$('[data-packages-modal] .install-package-container').addClass('hidden');
|
||||
$('[data-packages-modal] .installing-dependencies').addClass('hidden');
|
||||
$('[data-packages-modal] .installing-package').addClass('hidden');
|
||||
$('[data-packages-modal] .installation-complete').addClass('hidden');
|
||||
$('[data-packages-modal] .install-package-error').addClass('hidden');
|
||||
|
||||
this.getPackagesDependencies(type, slugs, () => {
|
||||
let slugs_string = slugs.join();
|
||||
$(`[data-packages-modal] [data-${type}-action="install-dependencies-and-package"]`).attr('data-packages-slugs', slugs_string);
|
||||
$(`[data-packages-modal] [data-${type}-action="install-package"]`).attr('data-packages-slugs', slugs_string);
|
||||
$('[data-packages-modal] .loading').addClass('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
handleInstallingDependenciesAndPackage(type, event) {
|
||||
let slugs = Packages.getSlugsFromEvent(event);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
$('[data-packages-modal] .install-dependencies-package-container').addClass('hidden');
|
||||
$('[data-packages-modal] .install-package-container').addClass('hidden');
|
||||
$('[data-packages-modal] .installing-dependencies').removeClass('hidden');
|
||||
|
||||
this.installDependenciesOfPackages(type, slugs, (response) => {
|
||||
$('[data-packages-modal] .installing-dependencies').addClass('hidden');
|
||||
$('[data-packages-modal] .installing-package').removeClass('hidden');
|
||||
this.installPackages(type, slugs, () => {
|
||||
$('[data-packages-modal] .installing-package').addClass('hidden');
|
||||
$('[data-packages-modal] .installation-complete').removeClass('hidden');
|
||||
|
||||
if (response.status === 'error') {
|
||||
let remodal = $.remodal.lookup[$('[data-packages-modal]').data('remodal')];
|
||||
remodal.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (slugs.length === 1) {
|
||||
global.location.href = `${config.base_url_relative}/${type}s/${slugs[0]}`;
|
||||
} else {
|
||||
global.location.href = `${config.base_url_relative}/${type}s`;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleInstallingPackage(type, event) {
|
||||
let slugs = Packages.getSlugsFromEvent(event);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
$('[data-packages-modal] .install-package-container').addClass('hidden');
|
||||
$('[data-packages-modal] .installing-package').removeClass('hidden');
|
||||
|
||||
this.installPackages(type, slugs, (response) => {
|
||||
$('[data-packages-modal] .installing-package').addClass('hidden');
|
||||
$('[data-packages-modal] .installation-complete').removeClass('hidden');
|
||||
|
||||
const errors = Array.from(response).filter((r) => r.status === 'error');
|
||||
|
||||
if (errors && errors.length) {
|
||||
let remodal = $.remodal.lookup[$('[data-packages-modal].remodal-is-opened').data('remodal')];
|
||||
remodal.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (slugs.length === 1) {
|
||||
global.location.href = `${config.base_url_relative}/${type}s/${slugs[0]}`;
|
||||
} else {
|
||||
global.location.href = `${config.base_url_relative}/${type}s`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleRemovingPackage(type, event) {
|
||||
let slug = $(event.target).attr('data-packages-slugs');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.removePackage(type, slug);
|
||||
}
|
||||
|
||||
handleReinstallPackage(type, event) {
|
||||
let target = $(event.target);
|
||||
let slug = target.attr('data-package-slug');
|
||||
let package_name = target.attr('data-package-name');
|
||||
let current_version = target.attr('data-package-current-version');
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.reinstallPackage(type, slug, package_name, current_version);
|
||||
}
|
||||
|
||||
handleRemovingDependency(type, event) {
|
||||
let slug = $(event.target).attr('data-dependency-slug');
|
||||
let button = $(event.target);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.removeDependency(type, slug, button);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Packages();
|
||||
820
user/plugins/admin/themes/grav/app/utils/remodal.js
Normal file
820
user/plugins/admin/themes/grav/app/utils/remodal.js
Normal file
@@ -0,0 +1,820 @@
|
||||
/* Remodal from https://github.com/vodkabears/Remodal
|
||||
* With Stackable option from https://github.com/antstorm/Remodal patch
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
!(function(root, factory) {
|
||||
return factory(root, $);
|
||||
})(this, function(global, $) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Name of the plugin
|
||||
* @private
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
var PLUGIN_NAME = 'remodal';
|
||||
|
||||
/**
|
||||
* Namespace for CSS and events
|
||||
* @private
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
var NAMESPACE = window.REMODAL_GLOBALS && window.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME;
|
||||
|
||||
/**
|
||||
* Animationstart event with vendor prefixes
|
||||
* @private
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
var ANIMATIONSTART_EVENTS = $.map(
|
||||
['animationstart', 'webkitAnimationStart', 'MSAnimationStart', 'oAnimationStart'],
|
||||
|
||||
function(eventName) {
|
||||
return eventName + '.' + NAMESPACE;
|
||||
}
|
||||
|
||||
).join(' ');
|
||||
|
||||
/**
|
||||
* Animationend event with vendor prefixes
|
||||
* @private
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
var ANIMATIONEND_EVENTS = $.map(
|
||||
['animationend', 'webkitAnimationEnd', 'MSAnimationEnd', 'oAnimationEnd'],
|
||||
|
||||
function(eventName) {
|
||||
return eventName + '.' + NAMESPACE;
|
||||
}
|
||||
|
||||
).join(' ');
|
||||
|
||||
/**
|
||||
* Default settings
|
||||
* @private
|
||||
* @const
|
||||
* @type {Object}
|
||||
*/
|
||||
var DEFAULTS = $.extend({
|
||||
hashTracking: true,
|
||||
closeOnConfirm: true,
|
||||
closeOnCancel: true,
|
||||
closeOnEscape: true,
|
||||
closeOnOutsideClick: true,
|
||||
modifier: '',
|
||||
stack: false,
|
||||
appendTo: null
|
||||
}, window.REMODAL_GLOBALS && window.REMODAL_GLOBALS.DEFAULTS);
|
||||
|
||||
/**
|
||||
* States of the Remodal
|
||||
* @private
|
||||
* @const
|
||||
* @enum {String}
|
||||
*/
|
||||
var STATES = {
|
||||
CLOSING: 'closing',
|
||||
CLOSED: 'closed',
|
||||
OPENING: 'opening',
|
||||
OPENED: 'opened'
|
||||
};
|
||||
|
||||
/**
|
||||
* Reasons of the state change.
|
||||
* @private
|
||||
* @const
|
||||
* @enum {String}
|
||||
*/
|
||||
var STATE_CHANGE_REASONS = {
|
||||
CONFIRMATION: 'confirmation',
|
||||
CANCELLATION: 'cancellation'
|
||||
};
|
||||
|
||||
/**
|
||||
* Is animation supported?
|
||||
* @private
|
||||
* @const
|
||||
* @type {Boolean}
|
||||
*/
|
||||
var IS_ANIMATION = (function() {
|
||||
var style = document.createElement('div').style;
|
||||
|
||||
return style.animationName !== undefined ||
|
||||
style.WebkitAnimationName !== undefined ||
|
||||
style.MozAnimationName !== undefined ||
|
||||
style.msAnimationName !== undefined ||
|
||||
style.OAnimationName !== undefined;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Is iOS?
|
||||
* @private
|
||||
* @const
|
||||
* @type {Boolean}
|
||||
*/
|
||||
var IS_IOS = /iPad|iPhone|iPod/.test(navigator.platform);
|
||||
|
||||
/**
|
||||
* Current modal
|
||||
* @private
|
||||
* @type {Remodal}
|
||||
*/
|
||||
var openModals = [];
|
||||
|
||||
/**
|
||||
* Scrollbar position
|
||||
* @private
|
||||
* @type {Number}
|
||||
*/
|
||||
var scrollTop;
|
||||
|
||||
/**
|
||||
* Returns an animation duration
|
||||
* @private
|
||||
* @param {jQuery} $elem
|
||||
* @returns {Number}
|
||||
*/
|
||||
function getAnimationDuration($elem) {
|
||||
if (
|
||||
IS_ANIMATION &&
|
||||
$elem.css('animation-name') === 'none' &&
|
||||
$elem.css('-webkit-animation-name') === 'none' &&
|
||||
$elem.css('-moz-animation-name') === 'none' &&
|
||||
$elem.css('-o-animation-name') === 'none' &&
|
||||
$elem.css('-ms-animation-name') === 'none'
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var duration = $elem.css('animation-duration') ||
|
||||
$elem.css('-webkit-animation-duration') ||
|
||||
$elem.css('-moz-animation-duration') ||
|
||||
$elem.css('-o-animation-duration') ||
|
||||
$elem.css('-ms-animation-duration') ||
|
||||
'0s';
|
||||
|
||||
var delay = $elem.css('animation-delay') ||
|
||||
$elem.css('-webkit-animation-delay') ||
|
||||
$elem.css('-moz-animation-delay') ||
|
||||
$elem.css('-o-animation-delay') ||
|
||||
$elem.css('-ms-animation-delay') ||
|
||||
'0s';
|
||||
|
||||
var iterationCount = $elem.css('animation-iteration-count') ||
|
||||
$elem.css('-webkit-animation-iteration-count') ||
|
||||
$elem.css('-moz-animation-iteration-count') ||
|
||||
$elem.css('-o-animation-iteration-count') ||
|
||||
$elem.css('-ms-animation-iteration-count') ||
|
||||
'1';
|
||||
|
||||
var max;
|
||||
var len;
|
||||
var num;
|
||||
var i;
|
||||
|
||||
duration = duration.split(', ');
|
||||
delay = delay.split(', ');
|
||||
iterationCount = iterationCount.split(', ');
|
||||
|
||||
// The 'duration' size is the same as the 'delay' size
|
||||
for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) {
|
||||
num = parseFloat(duration[i]) * parseInt(iterationCount[i], 10) + parseFloat(delay[i]);
|
||||
|
||||
if (num > max) {
|
||||
max = num;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a scrollbar width
|
||||
* @private
|
||||
* @returns {Number}
|
||||
*/
|
||||
function getScrollbarWidth() {
|
||||
if ($(document).height() <= $(window).height()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var outer = document.createElement('div');
|
||||
var inner = document.createElement('div');
|
||||
var widthNoScroll;
|
||||
var widthWithScroll;
|
||||
|
||||
outer.style.visibility = 'hidden';
|
||||
outer.style.width = '100px';
|
||||
document.body.appendChild(outer);
|
||||
|
||||
widthNoScroll = outer.offsetWidth;
|
||||
|
||||
// Force scrollbars
|
||||
outer.style.overflow = 'scroll';
|
||||
|
||||
// Add inner div
|
||||
inner.style.width = '100%';
|
||||
outer.appendChild(inner);
|
||||
|
||||
widthWithScroll = inner.offsetWidth;
|
||||
|
||||
// Remove divs
|
||||
outer.parentNode.removeChild(outer);
|
||||
|
||||
return widthNoScroll - widthWithScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the screen
|
||||
* @private
|
||||
*/
|
||||
function lockScreen() {
|
||||
if (IS_IOS) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $html = $('html');
|
||||
var lockedClass = namespacify('is-locked');
|
||||
var paddingRight;
|
||||
var $body;
|
||||
|
||||
if (!$html.hasClass(lockedClass)) {
|
||||
$body = $(document.body);
|
||||
|
||||
// Zepto does not support '-=', '+=' in the `css` method
|
||||
paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth();
|
||||
|
||||
$body.css('padding-right', paddingRight + 'px');
|
||||
$html.addClass(lockedClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks the screen
|
||||
* @private
|
||||
*/
|
||||
function unlockScreen() {
|
||||
if (IS_IOS) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $html = $('html');
|
||||
var lockedClass = namespacify('is-locked');
|
||||
var paddingRight;
|
||||
var $body;
|
||||
|
||||
if ($html.hasClass(lockedClass)) {
|
||||
$body = $(document.body);
|
||||
|
||||
// Zepto does not support '-=', '+=' in the `css` method
|
||||
paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth();
|
||||
|
||||
$body.css('padding-right', paddingRight + 'px');
|
||||
$html.removeClass(lockedClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a state for an instance
|
||||
* @private
|
||||
* @param {Remodal} instance
|
||||
* @param {STATES} state
|
||||
* @param {Boolean} isSilent If true, Remodal does not trigger events
|
||||
* @param {String} Reason of a state change.
|
||||
*/
|
||||
function setState(instance, state, isSilent, reason) {
|
||||
|
||||
var newState = namespacify('is', state);
|
||||
var allStates = [namespacify('is', STATES.CLOSING),
|
||||
namespacify('is', STATES.OPENING),
|
||||
namespacify('is', STATES.CLOSED),
|
||||
namespacify('is', STATES.OPENED)].join(' ');
|
||||
|
||||
instance.$bg
|
||||
.removeClass(allStates)
|
||||
.addClass(newState);
|
||||
|
||||
instance.$overlay
|
||||
.removeClass(allStates)
|
||||
.addClass(newState);
|
||||
|
||||
instance.$wrapper
|
||||
.removeClass(allStates)
|
||||
.addClass(newState);
|
||||
|
||||
instance.$modal
|
||||
.removeClass(allStates)
|
||||
.addClass(newState);
|
||||
|
||||
instance.state = state;
|
||||
!isSilent && instance.$modal.trigger({
|
||||
type: state,
|
||||
reason: reason
|
||||
}, [{ reason: reason }]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes with the animation
|
||||
* @param {Function} doBeforeAnimation
|
||||
* @param {Function} doAfterAnimation
|
||||
* @param {Remodal} instance
|
||||
*/
|
||||
function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) {
|
||||
var runningAnimationsCount = 0;
|
||||
|
||||
var handleAnimationStart = function(e) {
|
||||
if (e.target !== this) {
|
||||
return;
|
||||
}
|
||||
|
||||
runningAnimationsCount++;
|
||||
};
|
||||
|
||||
var handleAnimationEnd = function(e) {
|
||||
if (e.target !== this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (--runningAnimationsCount === 0) {
|
||||
|
||||
// Remove event listeners
|
||||
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
||||
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
||||
});
|
||||
|
||||
doAfterAnimation();
|
||||
}
|
||||
};
|
||||
|
||||
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
||||
instance[elemName]
|
||||
.on(ANIMATIONSTART_EVENTS, handleAnimationStart)
|
||||
.on(ANIMATIONEND_EVENTS, handleAnimationEnd);
|
||||
});
|
||||
|
||||
doBeforeAnimation();
|
||||
|
||||
// If the animation is not supported by a browser or its duration is 0
|
||||
if (
|
||||
getAnimationDuration(instance.$bg) === 0 &&
|
||||
getAnimationDuration(instance.$overlay) === 0 &&
|
||||
getAnimationDuration(instance.$wrapper) === 0 &&
|
||||
getAnimationDuration(instance.$modal) === 0
|
||||
) {
|
||||
|
||||
// Remove event listeners
|
||||
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
||||
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
||||
});
|
||||
|
||||
doAfterAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes immediately
|
||||
* @private
|
||||
* @param {Remodal} instance
|
||||
*/
|
||||
function halt(instance) {
|
||||
if (instance.state === STATES.CLOSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
||||
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
||||
});
|
||||
|
||||
removeModal(instance);
|
||||
instance.$bg.removeClass(instance.settings.modifier);
|
||||
instance.$overlay.removeClass(instance.settings.modifier).hide();
|
||||
instance.$wrapper.hide();
|
||||
|
||||
if (openModals.length === 0) {
|
||||
unlockScreen();
|
||||
}
|
||||
|
||||
setState(instance, STATES.CLOSED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string with options
|
||||
* @private
|
||||
* @param str
|
||||
* @returns {Object}
|
||||
*/
|
||||
function parseOptions(str) {
|
||||
var obj = {};
|
||||
var arr;
|
||||
var len;
|
||||
var val;
|
||||
var i;
|
||||
|
||||
// Remove spaces before and after delimiters
|
||||
str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ',');
|
||||
|
||||
// Parse a string
|
||||
arr = str.split(',');
|
||||
for (i = 0, len = arr.length; i < len; i++) {
|
||||
arr[i] = arr[i].split(':');
|
||||
val = arr[i][1];
|
||||
|
||||
// Convert a string value if it is like a boolean
|
||||
if (typeof val === 'string' || val instanceof String) {
|
||||
val = val === 'true' || (val === 'false' ? false : val);
|
||||
}
|
||||
|
||||
// Convert a string value if it is like a number
|
||||
if (typeof val === 'string' || val instanceof String) {
|
||||
val = !isNaN(val) ? +val : val;
|
||||
}
|
||||
|
||||
obj[arr[i][0]] = val;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string separated by dashes and prefixed with NAMESPACE
|
||||
* @private
|
||||
* @param {...String}
|
||||
* @returns {String}
|
||||
*/
|
||||
function namespacify() {
|
||||
var result = NAMESPACE;
|
||||
|
||||
for (var i = 0; i < arguments.length; ++i) {
|
||||
result += '-' + arguments[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the hashchange event
|
||||
* @private
|
||||
* @listens hashchange
|
||||
*/
|
||||
function handleHashChangeEvent() {
|
||||
var id = location.hash.replace('#', '');
|
||||
var instance;
|
||||
var $elem;
|
||||
|
||||
var current = currentModal();
|
||||
|
||||
if (!id) {
|
||||
|
||||
// Check if we have currently opened modal and animation was completed
|
||||
if (current && current.state === STATES.OPENED && current.settings.hashTracking) {
|
||||
current.close();
|
||||
}
|
||||
} else {
|
||||
|
||||
if (!current || current.id !== id) {
|
||||
// Catch syntax error if your hash is bad
|
||||
try {
|
||||
$elem = $(
|
||||
'[data-' + PLUGIN_NAME + '-id="' + id + '"]'
|
||||
);
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
if ($elem && $elem.length) {
|
||||
instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
|
||||
|
||||
if (instance && instance.settings.hashTracking) {
|
||||
instance.open();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function currentModal() {
|
||||
return openModals[openModals.length - 1];
|
||||
}
|
||||
|
||||
function removeModal(remodal) {
|
||||
var index = openModals.indexOf(remodal);
|
||||
|
||||
if (index >= 0) {
|
||||
openModals.slice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remodal constructor
|
||||
* @constructor
|
||||
* @param {jQuery} $modal
|
||||
* @param {Object} options
|
||||
*/
|
||||
function Remodal($modal, options) {
|
||||
var $body = $(document.body);
|
||||
var $appendTo = $body;
|
||||
var remodal = this;
|
||||
|
||||
remodal.id = $modal.attr('data-' + PLUGIN_NAME + '-id');
|
||||
remodal.settings = $.extend({}, DEFAULTS, options);
|
||||
remodal.index = $[PLUGIN_NAME].lookup.push(remodal) - 1;
|
||||
remodal.state = STATES.CLOSED;
|
||||
|
||||
// remodal.$overlay = $('.' + namespacify('overlay'));
|
||||
|
||||
if (remodal.settings.appendTo !== null && remodal.settings.appendTo.length) {
|
||||
$appendTo = $(remodal.settings.appendTo);
|
||||
}
|
||||
|
||||
if (!remodal.$overlay) {
|
||||
remodal.$overlay = $('<div>').addClass(namespacify('overlay') + ' ' + namespacify('is', STATES.CLOSED)).hide();
|
||||
$appendTo.append(remodal.$overlay);
|
||||
}
|
||||
|
||||
remodal.$bg = $('.' + namespacify('bg')).addClass(namespacify('is', STATES.CLOSED));
|
||||
|
||||
remodal.$modal = $modal
|
||||
.addClass(
|
||||
NAMESPACE + ' ' +
|
||||
namespacify('is-initialized') + ' ' +
|
||||
remodal.settings.modifier + ' ' +
|
||||
namespacify('is', STATES.CLOSED))
|
||||
.attr('tabindex', '-1');
|
||||
|
||||
remodal.$wrapper = $('<div>')
|
||||
.addClass(
|
||||
namespacify('wrapper') + ' ' +
|
||||
remodal.settings.modifier + ' ' +
|
||||
namespacify('is', STATES.CLOSED))
|
||||
.hide()
|
||||
.append(remodal.$modal);
|
||||
$appendTo.append(remodal.$wrapper);
|
||||
|
||||
// Add the event listener for the close button
|
||||
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="close"]', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
remodal.close();
|
||||
});
|
||||
|
||||
// Add the event listener for the cancel button
|
||||
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="cancel"]', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
remodal.$modal.trigger(STATE_CHANGE_REASONS.CANCELLATION);
|
||||
|
||||
if (remodal.settings.closeOnCancel) {
|
||||
remodal.close(STATE_CHANGE_REASONS.CANCELLATION);
|
||||
}
|
||||
});
|
||||
|
||||
// Add the event listener for the confirm button
|
||||
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="confirm"]', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
remodal.$modal.trigger(STATE_CHANGE_REASONS.CONFIRMATION);
|
||||
|
||||
if (remodal.settings.closeOnConfirm) {
|
||||
remodal.close(STATE_CHANGE_REASONS.CONFIRMATION);
|
||||
}
|
||||
});
|
||||
|
||||
// Add the event listener for the overlay
|
||||
remodal.$wrapper.on('click.' + NAMESPACE, function(e) {
|
||||
var $target = $(e.target);
|
||||
var isWrapper = $target.hasClass(namespacify('wrapper'));
|
||||
var isWithin = $target.closest('.' + namespacify('is', STATES.OPENED)).length;
|
||||
|
||||
if (!isWrapper && isWithin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (remodal.settings.closeOnOutsideClick) {
|
||||
remodal.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a modal window
|
||||
* @public
|
||||
*/
|
||||
Remodal.prototype.open = function() {
|
||||
var remodal = this;
|
||||
var current;
|
||||
var modalCount;
|
||||
|
||||
// Check if the animation was completed
|
||||
if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// id = remodal.$modal.attr('data-' + PLUGIN_NAME + '-id');
|
||||
|
||||
if (remodal.id && remodal.settings.hashTracking) {
|
||||
scrollTop = $(window).scrollTop();
|
||||
location.hash = remodal.id;
|
||||
}
|
||||
|
||||
if (!remodal.settings.stack) {
|
||||
current = currentModal();
|
||||
if (current && current !== remodal) {
|
||||
halt(current);
|
||||
}
|
||||
}
|
||||
|
||||
modalCount = openModals.push(remodal);
|
||||
remodal.$overlay.css('z-index', function(_, value) { return parseInt(value, 10) + modalCount; });
|
||||
remodal.$wrapper.css('z-index', function(_, value) { return parseInt(value, 10) + modalCount; });
|
||||
|
||||
lockScreen();
|
||||
remodal.$bg.addClass(remodal.settings.modifier);
|
||||
remodal.$overlay.addClass(remodal.settings.modifier).show();
|
||||
remodal.$wrapper.show().scrollTop(0);
|
||||
remodal.$modal.focus();
|
||||
|
||||
syncWithAnimation(
|
||||
function() {
|
||||
setState(remodal, STATES.OPENING);
|
||||
},
|
||||
|
||||
function() {
|
||||
setState(remodal, STATES.OPENED);
|
||||
},
|
||||
|
||||
remodal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes a modal window
|
||||
* @public
|
||||
* @param {String} reason
|
||||
*/
|
||||
Remodal.prototype.close = function(reason) {
|
||||
var remodal = this;
|
||||
var current;
|
||||
|
||||
// Check if the animation was completed
|
||||
if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING || remodal.state === STATES.CLOSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeModal(remodal);
|
||||
|
||||
if (
|
||||
remodal.settings.hashTracking &&
|
||||
remodal.id === location.hash.substr(1)
|
||||
) {
|
||||
current = currentModal();
|
||||
if (current) {
|
||||
location.hash = current.id;
|
||||
} else {
|
||||
location.hash = '';
|
||||
$(window).scrollTop(scrollTop);
|
||||
}
|
||||
}
|
||||
|
||||
syncWithAnimation(
|
||||
function() {
|
||||
setState(remodal, STATES.CLOSING, false, reason);
|
||||
},
|
||||
|
||||
function() {
|
||||
remodal.$bg.removeClass(remodal.settings.modifier);
|
||||
remodal.$overlay.removeClass(remodal.settings.modifier).hide();
|
||||
remodal.$wrapper.hide();
|
||||
|
||||
if (openModals.length === 0) {
|
||||
unlockScreen();
|
||||
}
|
||||
|
||||
setState(remodal, STATES.CLOSED, false, reason);
|
||||
},
|
||||
|
||||
remodal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a current state of a modal
|
||||
* @public
|
||||
* @returns {STATES}
|
||||
*/
|
||||
Remodal.prototype.getState = function() {
|
||||
return this.state;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys a modal
|
||||
* @public
|
||||
*/
|
||||
Remodal.prototype.destroy = function() {
|
||||
var lookup = $[PLUGIN_NAME].lookup;
|
||||
var instanceCount;
|
||||
|
||||
halt(this);
|
||||
this.$wrapper.remove();
|
||||
|
||||
delete lookup[this.index];
|
||||
instanceCount = $.grep(lookup, function(instance) {
|
||||
return !!instance;
|
||||
}).length;
|
||||
|
||||
if (instanceCount === 0) {
|
||||
this.$overlay.remove();
|
||||
this.$bg.removeClass(
|
||||
namespacify('is', STATES.CLOSING) + ' ' +
|
||||
namespacify('is', STATES.OPENING) + ' ' +
|
||||
namespacify('is', STATES.CLOSED) + ' ' +
|
||||
namespacify('is', STATES.OPENED));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Special plugin object for instances
|
||||
* @public
|
||||
* @type {Object}
|
||||
*/
|
||||
$[PLUGIN_NAME] = {
|
||||
lookup: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin constructor
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
* @returns {JQuery}
|
||||
*/
|
||||
$.fn[PLUGIN_NAME] = function(opts) {
|
||||
var instance;
|
||||
var $elem;
|
||||
|
||||
this.each(function(index, elem) {
|
||||
$elem = $(elem);
|
||||
|
||||
if ($elem.data(PLUGIN_NAME) == null) {
|
||||
instance = new Remodal($elem, opts);
|
||||
$elem.data(PLUGIN_NAME, instance.index);
|
||||
|
||||
if (
|
||||
instance.settings.hashTracking &&
|
||||
instance.id === location.hash.substr(1)
|
||||
) {
|
||||
instance.open();
|
||||
}
|
||||
} else {
|
||||
instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
|
||||
}
|
||||
});
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// data-remodal-target opens a modal window with the special Id
|
||||
$(document).on('click', '[data-' + PLUGIN_NAME + '-target]', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var elem = e.currentTarget;
|
||||
var id = elem.getAttribute('data-' + PLUGIN_NAME + '-target');
|
||||
var $target = $('[data-' + PLUGIN_NAME + '-id="' + id + '"]');
|
||||
|
||||
$[PLUGIN_NAME].lookup[$target.data(PLUGIN_NAME)].open();
|
||||
});
|
||||
|
||||
// Auto initialization of modal windows
|
||||
// They should have the 'remodal' class attribute
|
||||
// Also you can write the `data-remodal-options` attribute to pass params into the modal
|
||||
$(document).find('.' + NAMESPACE).each(function(i, container) {
|
||||
var $container = $(container);
|
||||
var options = $container.data(PLUGIN_NAME + '-options');
|
||||
|
||||
if (!options) {
|
||||
options = {};
|
||||
} else if (typeof options === 'string' || options instanceof String) {
|
||||
options = parseOptions(options);
|
||||
}
|
||||
|
||||
$container[PLUGIN_NAME](options);
|
||||
});
|
||||
|
||||
// Handles the keydown event
|
||||
$(document).on('keydown.' + NAMESPACE, function(e) {
|
||||
var current = currentModal();
|
||||
|
||||
if (current && current.settings.closeOnEscape && current.state === STATES.OPENED && e.keyCode === 27) {
|
||||
current.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Handles the hashchange event
|
||||
$(window).on('hashchange.' + NAMESPACE, handleHashChangeEvent);
|
||||
});
|
||||
});
|
||||
38
user/plugins/admin/themes/grav/app/utils/request.js
Normal file
38
user/plugins/admin/themes/grav/app/utils/request.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { parseStatus, parseJSON, userFeedback, userFeedbackError } from './response';
|
||||
import { config } from 'grav-config';
|
||||
|
||||
let raw;
|
||||
let request = function(url, options = {}, callback = () => true) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (options.method && options.method === 'post') {
|
||||
let data = new FormData();
|
||||
|
||||
options.body = Object.assign({ 'admin-nonce': config.admin_nonce }, options.body || {});
|
||||
Object.keys(options.body).map((key) => data.append(key, options.body[key]));
|
||||
options.body = data;
|
||||
}
|
||||
|
||||
options = Object.assign({
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}, options);
|
||||
|
||||
return fetch(url, options)
|
||||
.then((response) => {
|
||||
raw = response;
|
||||
return response;
|
||||
})
|
||||
.then(parseStatus)
|
||||
.then(parseJSON)
|
||||
.then(userFeedback)
|
||||
.then((response) => callback(response, raw))
|
||||
.catch(userFeedbackError);
|
||||
};
|
||||
|
||||
export default request;
|
||||
101
user/plugins/admin/themes/grav/app/utils/response.js
Normal file
101
user/plugins/admin/themes/grav/app/utils/response.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import $ from 'jquery';
|
||||
import toastr from './toastr';
|
||||
import isOnline from './offline';
|
||||
import { config } from 'grav-config';
|
||||
import trim from 'mout/string/trim';
|
||||
|
||||
let UNLOADING = false;
|
||||
let error = function(response) {
|
||||
let error = new Error(response.statusText || response || '');
|
||||
error.response = response;
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
export function parseStatus(response) {
|
||||
return response;
|
||||
|
||||
/* Whoops can handle JSON responses so we don't need this for now.
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
} else {
|
||||
throw error(response);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
export function parseJSON(response) {
|
||||
return response.text().then((text) => {
|
||||
let parsed = text;
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch (error) {
|
||||
let content = document.createElement('div');
|
||||
content.innerHTML = text;
|
||||
|
||||
let the_error = new Error();
|
||||
the_error.stack = trim(content.innerText);
|
||||
|
||||
throw the_error;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
});
|
||||
}
|
||||
|
||||
export function userFeedback(response) {
|
||||
if (UNLOADING) { return true; }
|
||||
|
||||
let status = response.status || (response.error ? 'error' : '');
|
||||
let message = response.message || (response.error ? response.error.message : null);
|
||||
let settings = response.toastr || null;
|
||||
let backup;
|
||||
|
||||
switch (status) {
|
||||
case 'unauthenticated':
|
||||
document.location.href = config.base_url_relative;
|
||||
throw error('Logged out');
|
||||
case 'unauthorized':
|
||||
status = 'error';
|
||||
message = message || 'Unauthorized.';
|
||||
break;
|
||||
case 'error':
|
||||
status = 'error';
|
||||
message = message || 'Unknown error.';
|
||||
break;
|
||||
case 'success':
|
||||
status = 'success';
|
||||
message = message || '';
|
||||
break;
|
||||
default:
|
||||
status = 'error';
|
||||
message = message || 'Invalid AJAX response.';
|
||||
break;
|
||||
}
|
||||
|
||||
if (settings) {
|
||||
backup = Object.assign({}, toastr.options);
|
||||
Object.keys(settings).forEach((key) => { toastr.options[key] = settings[key]; });
|
||||
}
|
||||
|
||||
if (message && (isOnline || (!isOnline && status !== 'error'))) {
|
||||
toastr[status === 'success' ? 'success' : 'error'](message);
|
||||
}
|
||||
|
||||
if (settings) {
|
||||
toastr.options = backup;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export function userFeedbackError(error) {
|
||||
if (UNLOADING) { return true; }
|
||||
let stack = error.stack ? `<pre><code>${error.stack}</code></pre>` : '';
|
||||
toastr.error(`Fetch Failed: <br /> ${error.message} ${stack}`);
|
||||
console.error(`${error.message} at ${error.stack}`);
|
||||
}
|
||||
|
||||
$(global).on('beforeunload._ajax', () => {
|
||||
UNLOADING = true;
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import $ from 'jquery';
|
||||
import Selectize from 'selectize';
|
||||
|
||||
Selectize.define('option_click', function(options) {
|
||||
const self = this;
|
||||
const setup = self.setup;
|
||||
this.setup = function() {
|
||||
setup.apply(self, arguments);
|
||||
let clicking = false;
|
||||
|
||||
// Detect click on a .clickable
|
||||
self.$dropdown_content.on('mousedown click', function(e) {
|
||||
const target = $(e.target);
|
||||
if (target.hasClass('clickable') || target.closest('.clickable').length) {
|
||||
if (e.type === 'mousedown') {
|
||||
clicking = true;
|
||||
self.isFocused = false; // awful hack to defuse the document mousedown listener
|
||||
} else {
|
||||
self.isFocused = true;
|
||||
setTimeout(function() {
|
||||
clicking = false; // wait until blur has been preempted
|
||||
});
|
||||
}
|
||||
} else { // cleanup in case user right-clicked or dragged off the element
|
||||
clicking = false;
|
||||
self.isFocused = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Intercept default handlers
|
||||
self.$dropdown.off('mousedown click', '[data-selectable]').on('mousedown click', '[data-selectable]', function() {
|
||||
if (!clicking) {
|
||||
return self.onOptionSelect.apply(self, arguments);
|
||||
}
|
||||
});
|
||||
self.$control_input.off('blur').on('blur', function() {
|
||||
if (!clicking) {
|
||||
return self.onBlur.apply(self, arguments);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* This is a plugin to override the `.refreshValidityState` method of
|
||||
* the Selectize library (https://selectize.github.io/selectize.js/).
|
||||
* The library is not maintained anymore (as of 2017-09-13) and contains
|
||||
* a bug which causes Microsoft Edge to not work with selectized [required]
|
||||
* form fields. This plugin should be removed if
|
||||
* https://github.com/selectize/selectize.js/pull/1320 is ever merged
|
||||
* and a new version of Selectize gets released.
|
||||
*/
|
||||
|
||||
import Selectize from 'selectize';
|
||||
|
||||
Selectize.define('required-fix', function(options) {
|
||||
this.refreshValidityState = () => {
|
||||
if (!this.isRequired) return false;
|
||||
|
||||
let invalid = !this.items.length;
|
||||
this.isInvalid = invalid;
|
||||
|
||||
if (invalid) {
|
||||
this.$control_input.attr('required', '');
|
||||
this.$input.removeAttr('required');
|
||||
} else {
|
||||
this.$control_input.removeAttr('required');
|
||||
this.$input.attr('required');
|
||||
}
|
||||
};
|
||||
});
|
||||
180
user/plugins/admin/themes/grav/app/utils/sidebar.js
Normal file
180
user/plugins/admin/themes/grav/app/utils/sidebar.js
Normal file
@@ -0,0 +1,180 @@
|
||||
import $ from 'jquery';
|
||||
import Cookies from '../utils/cookies';
|
||||
|
||||
const MOBILE_BREAKPOINT = 48 - 0.062;
|
||||
const DESKTOP_BREAKPOINT = 75 + 0.063;
|
||||
const EVENTS = 'touchstart._grav click._grav';
|
||||
const TARGETS = '[data-sidebar-mobile-toggle], #overlay';
|
||||
const MOBILE_QUERY = `(max-width: ${MOBILE_BREAKPOINT}em)`;
|
||||
const DESKTOP_QUERY = `(min-width: ${DESKTOP_BREAKPOINT}em)`;
|
||||
|
||||
let map = new global.Map();
|
||||
|
||||
export default class Sidebar {
|
||||
constructor() {
|
||||
this.timeout = null;
|
||||
this.isOpen = false;
|
||||
this.body = $('body');
|
||||
this.matchMedia = global.matchMedia(MOBILE_QUERY);
|
||||
this.enable();
|
||||
}
|
||||
|
||||
enable() {
|
||||
const sidebar = $('#admin-sidebar');
|
||||
|
||||
this.matchMedia.addListener(this._getBound('checkMatch'));
|
||||
this.checkMatch(this.matchMedia);
|
||||
this.body.on(EVENTS, '[data-sidebar-toggle]', this._getBound('toggleSidebarState'));
|
||||
|
||||
if (sidebar.data('quickopen')) {
|
||||
sidebar.hover(this._getBound('quickOpenIn'), this._getBound('quickOpenOut'));
|
||||
}
|
||||
}
|
||||
|
||||
disable() {
|
||||
const sidebar = $('#admin-sidebar');
|
||||
|
||||
this.close();
|
||||
this.matchMedia.removeListener(this._getBound('checkMatch'));
|
||||
this.body.off(EVENTS, '[data-sidebar-toggle]', this._getBound('toggleSidebarState'));
|
||||
if (sidebar.data('quickopen')) {
|
||||
sidebar.off('mouseenter mouseleave');
|
||||
}
|
||||
}
|
||||
|
||||
attach() {
|
||||
this.body.on(EVENTS, TARGETS, this._getBound('toggle'));
|
||||
}
|
||||
|
||||
detach() {
|
||||
this.body.off(EVENTS, TARGETS, this._getBound('toggle'));
|
||||
}
|
||||
|
||||
quickOpenIn(/* event */) {
|
||||
let isDesktop = global.matchMedia(DESKTOP_QUERY).matches;
|
||||
let delay = $('#admin-sidebar').data('quickopen-delay') || 500;
|
||||
if (this.body.hasClass('sidebar-mobile-open')) { return; }
|
||||
|
||||
let shouldQuickOpen = isDesktop ? this.body.hasClass('sidebar-closed') : !this.body.hasClass('sidebar-open');
|
||||
if (!shouldQuickOpen && !this.body.hasClass('sidebar-quickopen')) { return this.quickOpenOut(); }
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.body.addClass('sidebar-open sidebar-quickopen');
|
||||
$(global).trigger('sidebar_state._grav', isDesktop);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
quickOpenOut(/* event */) {
|
||||
clearTimeout(this.timeout);
|
||||
if (this.body.hasClass('sidebar-quickopen')) {
|
||||
this.body.removeClass('sidebar-open sidebar-quickopen');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
open(event, quick = false) {
|
||||
if (event) { event.preventDefault(); }
|
||||
let overlay = $('#overlay');
|
||||
let sidebar = $('#admin-sidebar');
|
||||
|
||||
this.body.addClass('sidebar-mobile-open');
|
||||
overlay.css('display', 'block');
|
||||
|
||||
if (!quick) {
|
||||
sidebar.css('display', 'block').animate({
|
||||
opacity: 1
|
||||
}, 200, () => {
|
||||
this.isOpen = true;
|
||||
});
|
||||
} else {
|
||||
sidebar.css({ display: 'block', opacity: 1 });
|
||||
this.isOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
close(event, quick = false) {
|
||||
if (event) { event.preventDefault(); }
|
||||
let overlay = $('#overlay');
|
||||
let sidebar = $('#admin-sidebar');
|
||||
|
||||
this.body.removeClass('sidebar-mobile-open');
|
||||
overlay.css('display', 'none');
|
||||
|
||||
if (!quick) {
|
||||
sidebar.animate({
|
||||
opacity: 0
|
||||
}, 200, () => {
|
||||
sidebar.css('display', 'none');
|
||||
this.isOpen = false;
|
||||
});
|
||||
} else {
|
||||
sidebar.css({ opacity: 0, display: 'none' });
|
||||
this.isOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
toggle(event) {
|
||||
if (event) { event.preventDefault(); }
|
||||
return this[this.isOpen ? 'close' : 'open'](event);
|
||||
}
|
||||
|
||||
toggleSidebarState(event) {
|
||||
if (event) { event.preventDefault(); }
|
||||
clearTimeout(this.timeout);
|
||||
let isDesktop = global.matchMedia(DESKTOP_QUERY).matches;
|
||||
let cookie = null;
|
||||
|
||||
if (isDesktop) {
|
||||
this.body.removeClass('sidebar-open');
|
||||
}
|
||||
|
||||
if (!isDesktop) {
|
||||
this.body.removeClass('sidebar-closed');
|
||||
this.body.removeClass('sidebar-mobile-open');
|
||||
}
|
||||
|
||||
this.body.toggleClass(`sidebar-${isDesktop ? 'closed' : 'open'}`);
|
||||
$(global).trigger('sidebar_state._grav', isDesktop);
|
||||
|
||||
if (isDesktop) {
|
||||
cookie = !this.body.hasClass('sidebar-closed');
|
||||
} else {
|
||||
cookie = this.body.hasClass('sidebar-open');
|
||||
}
|
||||
|
||||
Cookies.set('grav-admin-sidebar', cookie, { expires: Infinity });
|
||||
}
|
||||
|
||||
checkMatch(data) {
|
||||
let sidebar = $('#admin-sidebar');
|
||||
let overlay = $('#overlay');
|
||||
this.isOpen = false;
|
||||
|
||||
overlay.css('display', 'none');
|
||||
sidebar.css({
|
||||
display: data.matches ? 'none' : 'inherit',
|
||||
opacity: data.matches ? 0 : 1
|
||||
});
|
||||
|
||||
if (data.matches) {
|
||||
this.body.removeClass('sidebar-open sidebar-closed');
|
||||
}
|
||||
|
||||
this[data.matches ? 'attach' : 'detach']();
|
||||
}
|
||||
|
||||
_resetMap() {
|
||||
return map.clear();
|
||||
}
|
||||
|
||||
_getBound(fn) {
|
||||
if (map.has(fn)) {
|
||||
return map.get(fn);
|
||||
}
|
||||
|
||||
return map.set(fn, this[fn].bind(this)).get(fn);
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new Sidebar();
|
||||
41
user/plugins/admin/themes/grav/app/utils/storage.js
Normal file
41
user/plugins/admin/themes/grav/app/utils/storage.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// localStorage
|
||||
(function() {
|
||||
function isSupported() {
|
||||
var item = 'localStoragePollyfill';
|
||||
try {
|
||||
localStorage.setItem(item, item);
|
||||
localStorage.removeItem(item);
|
||||
sessionStorage.setItem(item, item);
|
||||
sessionStorage.removeItem(item);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSupported()) {
|
||||
try {
|
||||
Storage.prototype._data = {};
|
||||
|
||||
Storage.prototype.setItem = function(id, val) {
|
||||
this._data[id] = String(val);
|
||||
return this._data[id];
|
||||
};
|
||||
|
||||
Storage.prototype.getItem = function(id) {
|
||||
return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
|
||||
};
|
||||
|
||||
Storage.prototype.removeItem = function(id) {
|
||||
return delete this._data[id];
|
||||
};
|
||||
|
||||
Storage.prototype.clear = function() {
|
||||
this._data = {};
|
||||
return this._data;
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('localStorage pollyfill error: ', e);
|
||||
}
|
||||
}
|
||||
}());
|
||||
29
user/plugins/admin/themes/grav/app/utils/tabs-memory.js
Normal file
29
user/plugins/admin/themes/grav/app/utils/tabs-memory.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import $ from 'jquery';
|
||||
import Cookies from '../utils/cookies';
|
||||
import { Instance as Editors } from '../forms/fields/editor';
|
||||
|
||||
let Data = JSON.parse(Cookies.get('grav-tabs-state') || '{}');
|
||||
|
||||
$('body').on('touchstart click', '[data-tabid]', (event) => {
|
||||
event && event.stopPropagation();
|
||||
let target = $(event.currentTarget);
|
||||
|
||||
Data[target.data('tabkey')] = target.data('scope');
|
||||
Cookies.set('grav-tabs-state', JSON.stringify(Data), { expires: Infinity });
|
||||
|
||||
const panel = $(`[id="${target.data('tabid')}"]`);
|
||||
|
||||
target.siblings('[data-tabid]').removeClass('active');
|
||||
target.addClass('active');
|
||||
|
||||
panel.siblings('[id]').removeClass('active');
|
||||
panel.addClass('active');
|
||||
|
||||
Editors.editors.each((index, editor) => {
|
||||
let codemirror = $(editor).data('codemirror');
|
||||
if (!codemirror) { return; }
|
||||
if (codemirror.display.lastWrapWidth === 0) {
|
||||
codemirror.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
6
user/plugins/admin/themes/grav/app/utils/toastr.js
Normal file
6
user/plugins/admin/themes/grav/app/utils/toastr.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import toastr from 'toastr';
|
||||
|
||||
toastr.options.positionClass = 'toast-top-right';
|
||||
toastr.options.preventDuplicates = true;
|
||||
|
||||
export default toastr;
|
||||
25
user/plugins/admin/themes/grav/app/whitelabel/compile.js
Normal file
25
user/plugins/admin/themes/grav/app/whitelabel/compile.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { config } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
|
||||
export default ({ preview = false, exportScss = false, color_scheme = {}, fonts = {}, callback = () => {} } = {}) => {
|
||||
let task = exportScss ? 'exportScss' : 'compileScss';
|
||||
// console.log(config);
|
||||
const URI = `${config.base_url_relative}.json/task:${task}`;
|
||||
request(URI, {
|
||||
method: 'post',
|
||||
body: Object.assign({}, preview ? { preview } : null, color_scheme)
|
||||
}, callback);
|
||||
};
|
||||
|
||||
export const prepareElement = (element) => {
|
||||
element.data('busy_right_now', true);
|
||||
if (!element.data('current_icon')) {
|
||||
element.data('current_icon', element.find('.fa').attr('class'));
|
||||
}
|
||||
element.find('.fa').attr('class', 'fa fa-fw fa-spin fa-refresh');
|
||||
};
|
||||
|
||||
export const resetElement = (element) => {
|
||||
element.data('busy_right_now', false);
|
||||
element.find('.fa').attr('class', element.data('current_icon'));
|
||||
};
|
||||
93
user/plugins/admin/themes/grav/app/whitelabel/index.js
Normal file
93
user/plugins/admin/themes/grav/app/whitelabel/index.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import $ from 'jquery';
|
||||
import Compile, { prepareElement, resetElement } from './compile';
|
||||
import Forms from '../forms';
|
||||
import { hex2rgb } from '../utils/colors';
|
||||
import './presets';
|
||||
|
||||
const body = $('body');
|
||||
const FormState = Forms.FormState.Instance;
|
||||
const compiler = (element, preview = false, exportScss = false, callback = () => {}) => {
|
||||
prepareElement(element);
|
||||
|
||||
let fields = FormState.collect();
|
||||
Compile({
|
||||
preview,
|
||||
exportScss,
|
||||
color_scheme: !fields ? [] : fields.filter((value, key) => key.match(/^data\[whitelabel]\[color_scheme]/)).toJS(),
|
||||
callback: (response) => {
|
||||
callback.call(callback, response);
|
||||
resetElement(element);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
body.on('click', '[data-preview-scss]', (event) => {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.currentTarget);
|
||||
if (element.data('busy_right_now')) { return false; }
|
||||
|
||||
compiler(element, true, false, (response) => {
|
||||
if (response.files) {
|
||||
Object.keys(response.files).forEach((key) => {
|
||||
let file = $(`#admin-pro-preview-${key}`);
|
||||
let timestamp = Date.now();
|
||||
if (!file.length) {
|
||||
file = $(`<link id="admin-pro-preview-${key}" type="text/css" rel="stylesheet" />`);
|
||||
$('head').append(file);
|
||||
|
||||
if (!$('[data-reset-scss]').length) {
|
||||
let reset = $('<button class="button" data-reset-scss style="margin-left: 5px;"><i class="fa fa-fw fa-history"></i> Reset</button>');
|
||||
reset.insertAfter(element);
|
||||
}
|
||||
}
|
||||
|
||||
file.attr('href', `${response.files[key]}?${timestamp}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
body.on('click', '[data-recompile-scss]', (event) => {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.currentTarget);
|
||||
if (element.data('busy_right_now')) { return false; }
|
||||
|
||||
compiler(element, true, false);
|
||||
});
|
||||
|
||||
body.on('click', '[data-export-scss]', (event) => {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.currentTarget);
|
||||
if (element.data('busy_right_now')) { return false; }
|
||||
|
||||
compiler(element, true, true, (response) => {
|
||||
if (response.files) {
|
||||
Object.keys(response.files).forEach((key) => {
|
||||
if (key === 'download') {
|
||||
let element = document.createElement('a');
|
||||
element.setAttribute('href', response.files[key]);
|
||||
element.setAttribute('download', '');
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
body.on('change._grav_colorpicker', '[data-grav-colorpicker]', (event, input, hex, opacity) => {
|
||||
let RGB = hex2rgb(hex);
|
||||
let YIQ = ((RGB.r * 299) + (RGB.g * 587) + (RGB.b * 114)) / 1000;
|
||||
let contrast = YIQ >= 128 || opacity <= 0.50 ? 'dark' : 'light';
|
||||
|
||||
input.parent().removeClass('dark-text light-text').addClass(`${contrast}-text`);
|
||||
});
|
||||
|
||||
body.ready(() => {
|
||||
$('[data-grav-colorpicker]').trigger('keyup');
|
||||
});
|
||||
169
user/plugins/admin/themes/grav/app/whitelabel/presets.js
Normal file
169
user/plugins/admin/themes/grav/app/whitelabel/presets.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import $ from 'jquery';
|
||||
import Forms from '../forms';
|
||||
|
||||
let body = $('body');
|
||||
let fields = [];
|
||||
const FormState = Forms.FormState.Instance;
|
||||
const setField = (field, value) => {
|
||||
let name = field.prop('name');
|
||||
let tag = field.prop('tagName').toLowerCase();
|
||||
let type = field.prop('type');
|
||||
|
||||
fields.push(name);
|
||||
switch (tag) {
|
||||
case 'select':
|
||||
field.val(value);
|
||||
field.data('selectize').setValue(value);
|
||||
field.trigger('change');
|
||||
break;
|
||||
case 'input':
|
||||
if (type === 'radio') {
|
||||
let strValue = value ? '1' : '0';
|
||||
field.filter((index, radio) => $(radio).val() === strValue).prop('checked', true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (type === 'checkbox') {
|
||||
field.prop('checked', value);
|
||||
break;
|
||||
}
|
||||
field.val(value);
|
||||
field.trigger('keyup');
|
||||
}
|
||||
};
|
||||
|
||||
body.on('click', '[data-preset-values]', (event) => {
|
||||
let target = $(event.currentTarget);
|
||||
let data = target.data('preset-values');
|
||||
|
||||
Object.keys(data).forEach((section) => {
|
||||
if (typeof data[section] === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(data[section]).forEach((key) => {
|
||||
let field = $(`[name="data[whitelabel][color_scheme][${section}][${key}]"], [name="data[${section}][${key}]"]`);
|
||||
let value = data[section][key];
|
||||
setField(field, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
body.on('click', '[data-reset-scss]', (event) => {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.currentTarget);
|
||||
let links = $('link[id^=admin-pro-preview-]');
|
||||
|
||||
element.remove();
|
||||
links.remove();
|
||||
|
||||
fields.forEach((field) => {
|
||||
let value = FormState.loadState.get(field);
|
||||
setField($(`[name="${field}"]`), value);
|
||||
});
|
||||
fields = [];
|
||||
});
|
||||
|
||||
// Horizontal Scroll Functionality
|
||||
$.fn.hscrollarrows = function() {
|
||||
return this.each(function() {
|
||||
|
||||
let navNext = $('<a class="nav-next hide"></a>');
|
||||
let navPrev = $('<a class="nav-prev hide"></a>');
|
||||
let scrollTime = null;
|
||||
let resizeTime = null;
|
||||
let scrolling = false;
|
||||
|
||||
let elm_w = 0;
|
||||
let elem_data_w = 0;
|
||||
let max_scroll = 0;
|
||||
let inc_scroll = 0;
|
||||
|
||||
let calcData = function() {
|
||||
elm_w = elem.width();
|
||||
elem_data_w = elem_data.get(0).scrollWidth;
|
||||
max_scroll = elem_data_w - elm_w;
|
||||
inc_scroll = elm_w * 0.3; // 20%
|
||||
};
|
||||
|
||||
let revalidate = function() {
|
||||
calcData();
|
||||
stateNavs();
|
||||
};
|
||||
|
||||
let run = function() {
|
||||
calcData();
|
||||
setupNavs();
|
||||
};
|
||||
|
||||
let setupNavs = function() {
|
||||
|
||||
elem.parent().prepend(navNext);
|
||||
elem.parent().prepend(navPrev);
|
||||
navNext.on('click', next);
|
||||
navPrev.on('click', prev);
|
||||
stateNavs();
|
||||
|
||||
$(elem).scroll(function() {
|
||||
if (!scrolling) {
|
||||
clearTimeout(scrollTime);
|
||||
scrollTime = setTimeout(function() {
|
||||
stateNavs();
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).resize(function() {
|
||||
clearTimeout(resizeTime);
|
||||
resizeTime = setTimeout(function() {
|
||||
revalidate();
|
||||
}, 250);
|
||||
});
|
||||
};
|
||||
|
||||
let stateNavs = function() {
|
||||
let current_scroll = elem.scrollLeft();
|
||||
if (current_scroll < max_scroll) {
|
||||
navNext.removeClass('hide');
|
||||
} else {
|
||||
navNext.addClass('hide');
|
||||
}
|
||||
if (current_scroll > 0) {
|
||||
navPrev.removeClass('hide');
|
||||
} else {
|
||||
navPrev.addClass('hide');
|
||||
}
|
||||
scrolling = false;
|
||||
};
|
||||
|
||||
let next = function() {
|
||||
let current_scroll = elem.scrollLeft();
|
||||
if (current_scroll < max_scroll) {
|
||||
scrolling = true;
|
||||
elem.stop().animate({
|
||||
scrollLeft: (current_scroll + inc_scroll)
|
||||
}, stateNavs);
|
||||
}
|
||||
};
|
||||
|
||||
let prev = function() {
|
||||
let current_scroll = elem.scrollLeft();
|
||||
if (current_scroll > 0) {
|
||||
scrolling = true;
|
||||
elem.stop().animate({
|
||||
scrollLeft: (current_scroll - inc_scroll)
|
||||
}, stateNavs);
|
||||
}
|
||||
};
|
||||
|
||||
let elem = $(this);
|
||||
let elem_data = $(':first-child', elem);
|
||||
run();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
$('.jquery-horizontal-scroll').hscrollarrows();
|
||||
});
|
||||
Reference in New Issue
Block a user