init
This commit is contained in:
3
user/plugins/admin/themes/grav/.babelrc
Normal file
3
user/plugins/admin/themes/grav/.babelrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": [ "@babel/preset-env" ]
|
||||
}
|
||||
1
user/plugins/admin/themes/grav/.eslintignore
Normal file
1
user/plugins/admin/themes/grav/.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
165
user/plugins/admin/themes/grav/.eslintrc
Normal file
165
user/plugins/admin/themes/grav/.eslintrc
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"accessor-pairs": 2,
|
||||
"array-bracket-spacing": 0,
|
||||
"block-scoped-var": 0,
|
||||
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
||||
"camelcase": 0,
|
||||
"comma-dangle": [2, "never"],
|
||||
"comma-spacing": [2, { "before": false, "after": true }],
|
||||
"comma-style": [2, "last"],
|
||||
"complexity": 0,
|
||||
"computed-property-spacing": 0,
|
||||
"consistent-return": 0,
|
||||
"consistent-this": 0,
|
||||
"constructor-super": 2,
|
||||
"curly": [2, "multi-line"],
|
||||
"default-case": 0,
|
||||
"dot-location": [2, "property"],
|
||||
"dot-notation": 0,
|
||||
"eol-last": 2,
|
||||
"eqeqeq": [2, "allow-null"],
|
||||
"func-names": 0,
|
||||
"func-style": 0,
|
||||
"generator-star-spacing": [2, { "before": true, "after": true }],
|
||||
"guard-for-in": 0,
|
||||
"handle-callback-err": [2, "^(err|error)$" ],
|
||||
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
|
||||
"linebreak-style": 0,
|
||||
"lines-around-comment": 0,
|
||||
"max-nested-callbacks": 0,
|
||||
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
|
||||
"new-parens": 2,
|
||||
"newline-after-var": 0,
|
||||
"no-alert": 0,
|
||||
"no-array-constructor": 2,
|
||||
"no-caller": 2,
|
||||
"no-catch-shadow": 0,
|
||||
"no-cond-assign": 2,
|
||||
"no-console": 0,
|
||||
"no-constant-condition": 0,
|
||||
"no-continue": 0,
|
||||
"no-control-regex": 2,
|
||||
"no-debugger": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-div-regex": 0,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-else-return": 0,
|
||||
"no-empty": 0,
|
||||
"no-empty-character-class": 2,
|
||||
"no-eq-null": 0,
|
||||
"no-eval": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 0,
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-inline-comments": 0,
|
||||
"no-inner-declarations": [2, "functions"],
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-lonely-if": 0,
|
||||
"no-loop-func": 0,
|
||||
"no-mixed-requires": 0,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [2, { "max": 1 }],
|
||||
"no-native-reassign": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-nested-ternary": 0,
|
||||
"no-new": 2,
|
||||
"no-new-func": 0,
|
||||
"no-new-object": 2,
|
||||
"no-new-require": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-param-reassign": 0,
|
||||
"no-path-concat": 0,
|
||||
"no-process-env": 0,
|
||||
"no-process-exit": 0,
|
||||
"no-proto": 0,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-restricted-modules": 0,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 0,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow": 0,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-sync": 0,
|
||||
"no-ternary": 0,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-undefined": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unneeded-ternary": 2,
|
||||
"no-unreachable": 2,
|
||||
"no-unused-expressions": 0,
|
||||
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
|
||||
"no-use-before-define": 0,
|
||||
"no-var": 0,
|
||||
"no-void": 0,
|
||||
"no-warning-comments": 0,
|
||||
"no-with": 2,
|
||||
"object-curly-spacing": 0,
|
||||
"object-shorthand": 0,
|
||||
"one-var": [2, { "initialized": "never" }],
|
||||
"operator-assignment": 0,
|
||||
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
|
||||
"padded-blocks": 0,
|
||||
"prefer-const": 0,
|
||||
"quote-props": 0,
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"radix": 2,
|
||||
"semi": [2, "always"],
|
||||
"semi-spacing": 0,
|
||||
"sort-vars": 0,
|
||||
"keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, "never"],
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
|
||||
"strict": 0,
|
||||
"use-isnan": 2,
|
||||
"valid-jsdoc": 0,
|
||||
"valid-typeof": 2,
|
||||
"vars-on-top": 0,
|
||||
"wrap-iife": [2, "any"],
|
||||
"wrap-regex": 0,
|
||||
"yoda": [2, "never"]
|
||||
}
|
||||
}
|
||||
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();
|
||||
});
|
||||
625
user/plugins/admin/themes/grav/css-compiled/nucleus.css
vendored
Normal file
625
user/plugins/admin/themes/grav/css-compiled/nucleus.css
vendored
Normal file
@@ -0,0 +1,625 @@
|
||||
*, *::before, *::after {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box; }
|
||||
|
||||
@-webkit-viewport {
|
||||
width: device-width; }
|
||||
|
||||
@-moz-viewport {
|
||||
width: device-width; }
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width; }
|
||||
|
||||
@-o-viewport {
|
||||
width: device-width; }
|
||||
|
||||
@viewport {
|
||||
width: device-width; }
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%; }
|
||||
|
||||
body {
|
||||
margin: 0; }
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block; }
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
vertical-align: baseline; }
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0; }
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none; }
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
text-decoration: none; }
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0; }
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted; }
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold; }
|
||||
|
||||
dfn {
|
||||
font-style: italic; }
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000; }
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 0.65rem;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline; }
|
||||
|
||||
sup {
|
||||
top: -0.5em; }
|
||||
|
||||
sub {
|
||||
bottom: -0.25em; }
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%; }
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden; }
|
||||
|
||||
figure {
|
||||
margin: 1em 40px; }
|
||||
|
||||
hr {
|
||||
height: 0; }
|
||||
|
||||
pre {
|
||||
overflow: auto; }
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-size: 0.9rem; }
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
margin: 0; }
|
||||
|
||||
button {
|
||||
overflow: visible; }
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none; }
|
||||
|
||||
button,
|
||||
html input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
cursor: pointer; }
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default; }
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0; }
|
||||
|
||||
input {
|
||||
line-height: normal; }
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
padding: 0; }
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto; }
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; }
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none; }
|
||||
|
||||
legend {
|
||||
border: 0;
|
||||
padding: 0; }
|
||||
|
||||
textarea {
|
||||
overflow: auto; }
|
||||
|
||||
optgroup {
|
||||
font-weight: bold; }
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
width: 100%; }
|
||||
|
||||
tr, td, th {
|
||||
vertical-align: middle; }
|
||||
|
||||
th, td {
|
||||
padding: 0.375rem 0; }
|
||||
|
||||
th {
|
||||
text-align: left; }
|
||||
|
||||
.container {
|
||||
width: 75em;
|
||||
margin: 0 auto;
|
||||
padding: 0; }
|
||||
@media only all and (min-width: 60em) and (max-width: 74.938em) {
|
||||
.container {
|
||||
width: 60em; } }
|
||||
@media only all and (min-width: 48em) and (max-width: 59.938em) {
|
||||
.container {
|
||||
width: 48em; } }
|
||||
@media only all and (min-width: 30.063em) and (max-width: 47.938em) {
|
||||
.container {
|
||||
width: 30em; } }
|
||||
@media only all and (max-width: 30em) {
|
||||
.container {
|
||||
width: 100%; } }
|
||||
|
||||
.grid {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: box;
|
||||
display: -webkit-flex;
|
||||
display: -moz-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-flow: row;
|
||||
-moz-flex-flow: row;
|
||||
flex-flow: row;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
@media only all and (max-width: 47.938em) {
|
||||
.grid {
|
||||
-webkit-flex-flow: row wrap;
|
||||
-moz-flex-flow: row wrap;
|
||||
flex-flow: row wrap; } }
|
||||
|
||||
.block {
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-moz-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1; }
|
||||
@media only all and (max-width: 47.938em) {
|
||||
.block {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 100%;
|
||||
-moz-flex: 0 100%;
|
||||
-ms-flex: 0 100%;
|
||||
flex: 0 100%; } }
|
||||
|
||||
.content {
|
||||
margin: 0.625rem;
|
||||
padding: 0.938rem; }
|
||||
|
||||
@media only all and (max-width: 47.938em) {
|
||||
body [class*="size-"] {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 100%;
|
||||
-moz-flex: 0 100%;
|
||||
-ms-flex: 0 100%;
|
||||
flex: 0 100%; } }
|
||||
|
||||
.size-1-2 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 50%;
|
||||
-moz-flex: 0 50%;
|
||||
-ms-flex: 0 50%;
|
||||
flex: 0 50%; }
|
||||
|
||||
.size-1-3 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 33.33333%;
|
||||
-moz-flex: 0 33.33333%;
|
||||
-ms-flex: 0 33.33333%;
|
||||
flex: 0 33.33333%; }
|
||||
|
||||
.size-1-4 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 25%;
|
||||
-moz-flex: 0 25%;
|
||||
-ms-flex: 0 25%;
|
||||
flex: 0 25%; }
|
||||
|
||||
.size-1-5 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 20%;
|
||||
-moz-flex: 0 20%;
|
||||
-ms-flex: 0 20%;
|
||||
flex: 0 20%; }
|
||||
|
||||
.size-1-6 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 16.66667%;
|
||||
-moz-flex: 0 16.66667%;
|
||||
-ms-flex: 0 16.66667%;
|
||||
flex: 0 16.66667%; }
|
||||
|
||||
.size-1-7 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 14.28571%;
|
||||
-moz-flex: 0 14.28571%;
|
||||
-ms-flex: 0 14.28571%;
|
||||
flex: 0 14.28571%; }
|
||||
|
||||
.size-1-8 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 12.5%;
|
||||
-moz-flex: 0 12.5%;
|
||||
-ms-flex: 0 12.5%;
|
||||
flex: 0 12.5%; }
|
||||
|
||||
.size-1-9 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 11.11111%;
|
||||
-moz-flex: 0 11.11111%;
|
||||
-ms-flex: 0 11.11111%;
|
||||
flex: 0 11.11111%; }
|
||||
|
||||
.size-1-10 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 10%;
|
||||
-moz-flex: 0 10%;
|
||||
-ms-flex: 0 10%;
|
||||
flex: 0 10%; }
|
||||
|
||||
.size-1-11 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 9.09091%;
|
||||
-moz-flex: 0 9.09091%;
|
||||
-ms-flex: 0 9.09091%;
|
||||
flex: 0 9.09091%; }
|
||||
|
||||
.size-1-12 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 8.33333%;
|
||||
-moz-flex: 0 8.33333%;
|
||||
-ms-flex: 0 8.33333%;
|
||||
flex: 0 8.33333%; }
|
||||
|
||||
@media only all and (min-width: 48em) and (max-width: 59.938em) {
|
||||
.size-tablet-1-2 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 50%;
|
||||
-moz-flex: 0 50%;
|
||||
-ms-flex: 0 50%;
|
||||
flex: 0 50%; }
|
||||
.size-tablet-1-3 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 33.33333%;
|
||||
-moz-flex: 0 33.33333%;
|
||||
-ms-flex: 0 33.33333%;
|
||||
flex: 0 33.33333%; }
|
||||
.size-tablet-1-4 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 25%;
|
||||
-moz-flex: 0 25%;
|
||||
-ms-flex: 0 25%;
|
||||
flex: 0 25%; }
|
||||
.size-tablet-1-5 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 20%;
|
||||
-moz-flex: 0 20%;
|
||||
-ms-flex: 0 20%;
|
||||
flex: 0 20%; }
|
||||
.size-tablet-1-6 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 16.66667%;
|
||||
-moz-flex: 0 16.66667%;
|
||||
-ms-flex: 0 16.66667%;
|
||||
flex: 0 16.66667%; }
|
||||
.size-tablet-1-7 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 14.28571%;
|
||||
-moz-flex: 0 14.28571%;
|
||||
-ms-flex: 0 14.28571%;
|
||||
flex: 0 14.28571%; }
|
||||
.size-tablet-1-8 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 12.5%;
|
||||
-moz-flex: 0 12.5%;
|
||||
-ms-flex: 0 12.5%;
|
||||
flex: 0 12.5%; }
|
||||
.size-tablet-1-9 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 11.11111%;
|
||||
-moz-flex: 0 11.11111%;
|
||||
-ms-flex: 0 11.11111%;
|
||||
flex: 0 11.11111%; }
|
||||
.size-tablet-1-10 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 10%;
|
||||
-moz-flex: 0 10%;
|
||||
-ms-flex: 0 10%;
|
||||
flex: 0 10%; }
|
||||
.size-tablet-1-11 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 9.09091%;
|
||||
-moz-flex: 0 9.09091%;
|
||||
-ms-flex: 0 9.09091%;
|
||||
flex: 0 9.09091%; }
|
||||
.size-tablet-1-12 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 8.33333%;
|
||||
-moz-flex: 0 8.33333%;
|
||||
-ms-flex: 0 8.33333%;
|
||||
flex: 0 8.33333%; } }
|
||||
|
||||
@media only all and (max-width: 47.938em) {
|
||||
@supports not (flex-wrap: wrap) {
|
||||
.grid {
|
||||
display: block;
|
||||
-webkit-box-lines: inherit;
|
||||
-moz-box-lines: inherit;
|
||||
box-lines: inherit;
|
||||
-webkit-flex-wrap: inherit;
|
||||
-moz-flex-wrap: inherit;
|
||||
-ms-flex-wrap: inherit;
|
||||
flex-wrap: inherit; }
|
||||
.block {
|
||||
display: block;
|
||||
-webkit-box-flex: inherit;
|
||||
-moz-box-flex: inherit;
|
||||
box-flex: inherit;
|
||||
-webkit-flex: inherit;
|
||||
-moz-flex: inherit;
|
||||
-ms-flex: inherit;
|
||||
flex: inherit; } } }
|
||||
|
||||
.first-block {
|
||||
-webkit-box-ordinal-group: 0;
|
||||
-webkit-order: -1;
|
||||
-ms-flex-order: -1;
|
||||
order: -1; }
|
||||
|
||||
.last-block {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-webkit-order: 1;
|
||||
-ms-flex-order: 1;
|
||||
order: 1; }
|
||||
|
||||
.fixed-blocks {
|
||||
-webkit-flex-flow: row wrap;
|
||||
-moz-flex-flow: row wrap;
|
||||
flex-flow: row wrap; }
|
||||
.fixed-blocks .block {
|
||||
-webkit-box-flex: inherit;
|
||||
-moz-box-flex: inherit;
|
||||
box-flex: inherit;
|
||||
-webkit-flex: inherit;
|
||||
-moz-flex: inherit;
|
||||
-ms-flex: inherit;
|
||||
flex: inherit;
|
||||
width: 25%; }
|
||||
@media only all and (min-width: 60em) and (max-width: 74.938em) {
|
||||
.fixed-blocks .block {
|
||||
width: 33.33333%; } }
|
||||
@media only all and (min-width: 48em) and (max-width: 59.938em) {
|
||||
.fixed-blocks .block {
|
||||
width: 50%; } }
|
||||
@media only all and (max-width: 47.938em) {
|
||||
.fixed-blocks .block {
|
||||
width: 100%; } }
|
||||
|
||||
@supports not (flex-wrap: wrap) {
|
||||
.fixed-blocks {
|
||||
display: block;
|
||||
-webkit-flex-flow: inherit;
|
||||
-moz-flex-flow: inherit;
|
||||
flex-flow: inherit; } }
|
||||
|
||||
body {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5; }
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 1.5rem 0 0.75rem 0;
|
||||
text-rendering: optimizeLegibility; }
|
||||
|
||||
h1 {
|
||||
font-size: 3.1rem; }
|
||||
|
||||
h2 {
|
||||
font-size: 2.4rem; }
|
||||
|
||||
h3 {
|
||||
font-size: 2rem; }
|
||||
|
||||
h4 {
|
||||
font-size: 1.65rem; }
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem; }
|
||||
|
||||
h6 {
|
||||
font-size: 0.75rem; }
|
||||
|
||||
p {
|
||||
margin: 0.75rem 0 1.5rem; }
|
||||
|
||||
ul, ol {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem; }
|
||||
ul ul, ul ol, ol ul, ol ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0; }
|
||||
|
||||
blockquote {
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.5rem; }
|
||||
blockquote p:last-child {
|
||||
margin: 0; }
|
||||
|
||||
cite {
|
||||
display: block;
|
||||
font-size: 0.775rem; }
|
||||
cite:before {
|
||||
content: "\2014 \0020"; }
|
||||
|
||||
pre {
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.938rem; }
|
||||
|
||||
code {
|
||||
vertical-align: bottom; }
|
||||
|
||||
small {
|
||||
font-size: 0.775rem; }
|
||||
|
||||
hr {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
margin: 1.5rem 0; }
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
padding: 0.938rem;
|
||||
margin: 0 0 1.5rem 0; }
|
||||
|
||||
input,
|
||||
label,
|
||||
select {
|
||||
display: block; }
|
||||
|
||||
label {
|
||||
margin-bottom: 0.375rem; }
|
||||
label.required:after {
|
||||
content: "*"; }
|
||||
label abbr {
|
||||
display: none; }
|
||||
|
||||
textarea, input[type="email"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="url"], input[type="color"], input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="week"], select[multiple=multiple] {
|
||||
-webkit-transition: border-color;
|
||||
-moz-transition: border-color;
|
||||
transition: border-color;
|
||||
border-radius: 0.1875rem;
|
||||
padding: 0.375rem 0.375rem;
|
||||
width: 100%; }
|
||||
textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus {
|
||||
outline: none; }
|
||||
|
||||
textarea {
|
||||
resize: vertical; }
|
||||
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
display: inline;
|
||||
margin-right: 0.375rem; }
|
||||
|
||||
input[type="file"] {
|
||||
width: 100%; }
|
||||
|
||||
select {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
margin-bottom: 1.5rem; }
|
||||
|
||||
button,
|
||||
input[type="submit"] {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
border: inherit; }
|
||||
|
||||
/*# sourceMappingURL=nucleus.css.map */
|
||||
93
user/plugins/admin/themes/grav/css-compiled/nucleus.css.map
Normal file
93
user/plugins/admin/themes/grav/css-compiled/nucleus.css.map
Normal file
File diff suppressed because one or more lines are too long
1520
user/plugins/admin/themes/grav/css-compiled/preset.css
vendored
Normal file
1520
user/plugins/admin/themes/grav/css-compiled/preset.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18
user/plugins/admin/themes/grav/css-compiled/preset.css.map
Normal file
18
user/plugins/admin/themes/grav/css-compiled/preset.css.map
Normal file
File diff suppressed because one or more lines are too long
13
user/plugins/admin/themes/grav/css-compiled/simple-fonts.css
vendored
Normal file
13
user/plugins/admin/themes/grav/css-compiled/simple-fonts.css
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
body, h5, h6,
|
||||
.badge, .note, .grav-mdeditor-preview,
|
||||
input, select, textarea, button, .selectize-input,
|
||||
h1, h2, h3, h4,
|
||||
.fontfamily-sans .CodeMirror pre,
|
||||
#admin-menu li, .form-tabs > label, .label {
|
||||
font-family: "Helvetica Neue", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; }
|
||||
|
||||
.CodeMirror pre,
|
||||
code, kbd, pre, samp, .mono {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; }
|
||||
|
||||
/*# sourceMappingURL=simple-fonts.css.map */
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 3,
|
||||
"file": "simple-fonts.css",
|
||||
"sources": [
|
||||
"../scss/simple-fonts.scss",
|
||||
"../hdr0"
|
||||
],
|
||||
"names": [],
|
||||
"mappings": "AAAA,AAAA,IAAI,EAAE,EAAE,EAAE,EAAE;AACZ,MAAM,EAAE,KAAK,EAAE,sBAAsB;AACrC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB;AACjD,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;AACd,gBAAgB,CAAC,WAAW,CAAC,GAAG;AAChC,WAAW,CAAC,EAAE,EAAE,UAAU,GAAG,KAAK,EAAE,MAAM,CAAE;EACxC,WAAW,EAAE,sEAAsE,GACtF;;AAED,AAAA,WAAW,CAAC,GAAG;AACf,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAE;EACzB,WAAW,EAAE,wEAAwE,GACxF"
|
||||
}
|
||||
5644
user/plugins/admin/themes/grav/css-compiled/template.css
vendored
Normal file
5644
user/plugins/admin/themes/grav/css-compiled/template.css
vendored
Normal file
File diff suppressed because one or more lines are too long
129
user/plugins/admin/themes/grav/css-compiled/template.css.map
Normal file
129
user/plugins/admin/themes/grav/css-compiled/template.css.map
Normal file
File diff suppressed because one or more lines are too long
1
user/plugins/admin/themes/grav/css/chartist.min.css
vendored
Normal file
1
user/plugins/admin/themes/grav/css/chartist.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
334
user/plugins/admin/themes/grav/css/codemirror/codemirror.css
Normal file
334
user/plugins/admin/themes/grav/css/codemirror/codemirror.css
Normal file
@@ -0,0 +1,334 @@
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror .CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror .CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
background: #7e7;
|
||||
}
|
||||
.CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
div.CodeMirror-overwrite div.CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3 {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px; margin-right: -30px;
|
||||
padding-bottom: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 30px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actuall scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
margin-bottom: -30px;
|
||||
/* Hack to make IE7 behave */
|
||||
*zoom:1;
|
||||
*display:inline;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background: #ffa;
|
||||
background: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
|
||||
.CodeMirror span { *vertical-align: text-bottom; }
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
||||
41
user/plugins/admin/themes/grav/css/codemirror/themes/3024-day.css
vendored
Normal file
41
user/plugins/admin/themes/grav/css/codemirror/themes/3024-day.css
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
|
||||
Name: 3024 day
|
||||
Author: Jan T. Sott (https://github.com/idleberg)
|
||||
|
||||
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
|
||||
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
|
||||
|
||||
*/
|
||||
|
||||
.cm-s-3024-day.CodeMirror { background: #f7f7f7; color: #3a3432; }
|
||||
.cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4; }
|
||||
|
||||
.cm-s-3024-day .CodeMirror-line::selection, .cm-s-3024-day .CodeMirror-line > span::selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4; }
|
||||
.cm-s-3024-day .CodeMirror-line::-moz-selection, .cm-s-3024-day .CodeMirror-line > span::-moz-selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9; }
|
||||
|
||||
.cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7; border-right: 0px; }
|
||||
.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; }
|
||||
.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; }
|
||||
.cm-s-3024-day .CodeMirror-linenumber { color: #807d7c; }
|
||||
|
||||
.cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855; }
|
||||
|
||||
.cm-s-3024-day span.cm-comment { color: #cdab53; }
|
||||
.cm-s-3024-day span.cm-atom { color: #a16a94; }
|
||||
.cm-s-3024-day span.cm-number { color: #a16a94; }
|
||||
|
||||
.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute { color: #01a252; }
|
||||
.cm-s-3024-day span.cm-keyword { color: #db2d20; }
|
||||
.cm-s-3024-day span.cm-string { color: #fded02; }
|
||||
|
||||
.cm-s-3024-day span.cm-variable { color: #01a252; }
|
||||
.cm-s-3024-day span.cm-variable-2 { color: #01a0e4; }
|
||||
.cm-s-3024-day span.cm-def { color: #e8bbd0; }
|
||||
.cm-s-3024-day span.cm-bracket { color: #3a3432; }
|
||||
.cm-s-3024-day span.cm-tag { color: #db2d20; }
|
||||
.cm-s-3024-day span.cm-link { color: #a16a94; }
|
||||
.cm-s-3024-day span.cm-error { background: #db2d20; color: #5c5855; }
|
||||
|
||||
.cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff; }
|
||||
.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important; }
|
||||
39
user/plugins/admin/themes/grav/css/codemirror/themes/3024-night.css
vendored
Normal file
39
user/plugins/admin/themes/grav/css/codemirror/themes/3024-night.css
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
|
||||
Name: 3024 night
|
||||
Author: Jan T. Sott (https://github.com/idleberg)
|
||||
|
||||
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
|
||||
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
|
||||
|
||||
*/
|
||||
|
||||
.cm-s-3024-night.CodeMirror { background: #090300; color: #d6d5d4; }
|
||||
.cm-s-3024-night div.CodeMirror-selected { background: #3a3432; }
|
||||
.cm-s-3024-night .CodeMirror-line::selection, .cm-s-3024-night .CodeMirror-line > span::selection, .cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99); }
|
||||
.cm-s-3024-night .CodeMirror-line::-moz-selection, .cm-s-3024-night .CodeMirror-line > span::-moz-selection, .cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99); }
|
||||
.cm-s-3024-night .CodeMirror-gutters { background: #090300; border-right: 0px; }
|
||||
.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; }
|
||||
.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; }
|
||||
.cm-s-3024-night .CodeMirror-linenumber { color: #5c5855; }
|
||||
|
||||
.cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c; }
|
||||
|
||||
.cm-s-3024-night span.cm-comment { color: #cdab53; }
|
||||
.cm-s-3024-night span.cm-atom { color: #a16a94; }
|
||||
.cm-s-3024-night span.cm-number { color: #a16a94; }
|
||||
|
||||
.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute { color: #01a252; }
|
||||
.cm-s-3024-night span.cm-keyword { color: #db2d20; }
|
||||
.cm-s-3024-night span.cm-string { color: #fded02; }
|
||||
|
||||
.cm-s-3024-night span.cm-variable { color: #01a252; }
|
||||
.cm-s-3024-night span.cm-variable-2 { color: #01a0e4; }
|
||||
.cm-s-3024-night span.cm-def { color: #e8bbd0; }
|
||||
.cm-s-3024-night span.cm-bracket { color: #d6d5d4; }
|
||||
.cm-s-3024-night span.cm-tag { color: #db2d20; }
|
||||
.cm-s-3024-night span.cm-link { color: #a16a94; }
|
||||
.cm-s-3024-night span.cm-error { background: #db2d20; color: #807d7c; }
|
||||
|
||||
.cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F; }
|
||||
.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
|
||||
32
user/plugins/admin/themes/grav/css/codemirror/themes/abcdef.css
vendored
Normal file
32
user/plugins/admin/themes/grav/css/codemirror/themes/abcdef.css
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
.cm-s-abcdef.CodeMirror { background: #0f0f0f; color: #defdef; }
|
||||
.cm-s-abcdef div.CodeMirror-selected { background: #515151; }
|
||||
.cm-s-abcdef .CodeMirror-line::selection, .cm-s-abcdef .CodeMirror-line > span::selection, .cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99); }
|
||||
.cm-s-abcdef .CodeMirror-line::-moz-selection, .cm-s-abcdef .CodeMirror-line > span::-moz-selection, .cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99); }
|
||||
.cm-s-abcdef .CodeMirror-gutters { background: #555; border-right: 2px solid #314151; }
|
||||
.cm-s-abcdef .CodeMirror-guttermarker { color: #222; }
|
||||
.cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure; }
|
||||
.cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF; }
|
||||
.cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00; }
|
||||
|
||||
.cm-s-abcdef span.cm-keyword { color: darkgoldenrod; font-weight: bold; }
|
||||
.cm-s-abcdef span.cm-atom { color: #77F; }
|
||||
.cm-s-abcdef span.cm-number { color: violet; }
|
||||
.cm-s-abcdef span.cm-def { color: #fffabc; }
|
||||
.cm-s-abcdef span.cm-variable { color: #abcdef; }
|
||||
.cm-s-abcdef span.cm-variable-2 { color: #cacbcc; }
|
||||
.cm-s-abcdef span.cm-variable-3, .cm-s-abcdef span.cm-type { color: #def; }
|
||||
.cm-s-abcdef span.cm-property { color: #fedcba; }
|
||||
.cm-s-abcdef span.cm-operator { color: #ff0; }
|
||||
.cm-s-abcdef span.cm-comment { color: #7a7b7c; font-style: italic;}
|
||||
.cm-s-abcdef span.cm-string { color: #2b4; }
|
||||
.cm-s-abcdef span.cm-meta { color: #C9F; }
|
||||
.cm-s-abcdef span.cm-qualifier { color: #FFF700; }
|
||||
.cm-s-abcdef span.cm-builtin { color: #30aabc; }
|
||||
.cm-s-abcdef span.cm-bracket { color: #8a8a8a; }
|
||||
.cm-s-abcdef span.cm-tag { color: #FFDD44; }
|
||||
.cm-s-abcdef span.cm-attribute { color: #DDFF00; }
|
||||
.cm-s-abcdef span.cm-error { color: #FF0000; }
|
||||
.cm-s-abcdef span.cm-header { color: aquamarine; font-weight: bold; }
|
||||
.cm-s-abcdef span.cm-link { color: blueviolet; }
|
||||
|
||||
.cm-s-abcdef .CodeMirror-activeline-background { background: #314151; }
|
||||
5
user/plugins/admin/themes/grav/css/codemirror/themes/ambiance-mobile.css
vendored
Normal file
5
user/plugins/admin/themes/grav/css/codemirror/themes/ambiance-mobile.css
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.cm-s-ambiance.CodeMirror {
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
74
user/plugins/admin/themes/grav/css/codemirror/themes/ambiance.css
vendored
Normal file
74
user/plugins/admin/themes/grav/css/codemirror/themes/ambiance.css
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user