first commit
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"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)$" ],
|
||||
"indent": [2, 4, { "SwitchCase": 1 }],
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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').addClass('fa-trash');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.element
|
||||
.attr('disabled', 'disabled')
|
||||
.find('> .fa').removeClass('fa-trash').addClass('fa-refresh fa-spin');
|
||||
}
|
||||
}
|
||||
|
||||
let Instance = new Cache();
|
||||
|
||||
export { Instance };
|
||||
@@ -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;
|
||||
@@ -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
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
// See ../updates/update.js
|
||||
@@ -0,0 +1,193 @@
|
||||
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
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
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}]`;
|
||||
valueElement.attr('name', !valueElement.val() ? template.getName() : 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);
|
||||
let name = input.attr('name');
|
||||
name = name.replace(/\[\d+\]$/, `[${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();
|
||||
@@ -0,0 +1,261 @@
|
||||
import $ from 'jquery';
|
||||
import Sortable from 'sortablejs';
|
||||
import '../../utils/jquery-utils';
|
||||
|
||||
export default class CollectionsField {
|
||||
constructor() {
|
||||
this.lists = $();
|
||||
|
||||
$('[data-type="collection"]').each((index, list) => this.addList(list));
|
||||
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
|
||||
|
||||
}
|
||||
|
||||
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="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');
|
||||
}
|
||||
|
||||
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-grav-array-name'].forEach((prop) => {
|
||||
item.find('[' + prop + '], [_' + prop + ']').each(function() {
|
||||
let element = $(this);
|
||||
let indexes = [];
|
||||
let array_index = null;
|
||||
let regexps = [
|
||||
new RegExp('\\[(\\d+|\\*|' + currentKey + ')\\]', 'g'),
|
||||
new RegExp('\\.(\\d+|\\*|' + currentKey + ')\\.', 'g')
|
||||
];
|
||||
|
||||
// special case to preserve array field index keys
|
||||
if (prop === 'name' && element.data('gravArrayType')) {
|
||||
const match_index = element.attr(prop).match(/\[[0-9]{1,}\]$/);
|
||||
const pattern = element.closest('[data-grav-array-name]').data('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();
|
||||
@@ -0,0 +1,581 @@
|
||||
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();
|
||||
|
||||
let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
|
||||
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'));
|
||||
$(mainContainer).on('scroll', this.bound('reposition'));
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.built) { return; }
|
||||
this.wrapper.removeClass('cp-visible');
|
||||
|
||||
let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
|
||||
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'));
|
||||
$(mainContainer).off('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]');
|
||||
@@ -0,0 +1,145 @@
|
||||
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();
|
||||
|
||||
/*
|
||||
|
||||
// cron-selector
|
||||
$(document).ready(function() {
|
||||
|
||||
$('.cron-selector').each(function(index, wrapper) {
|
||||
wrapper = $(wrapper);
|
||||
const input = wrapper.closest('[data-grav-field]').find('input');
|
||||
|
||||
wrapper.jqCron({
|
||||
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: 'week',
|
||||
default_value: input.val() || '3 * * * *',
|
||||
no_reset_button: false,
|
||||
bind_to: input,
|
||||
bind_method: {
|
||||
set: function($element, value) {
|
||||
$element.val(value);
|
||||
}
|
||||
},
|
||||
texts: {
|
||||
en: {
|
||||
empty: 'every',
|
||||
empty_minutes: 'every',
|
||||
empty_time_hours: 'every hour',
|
||||
empty_time_minutes: 'every minute',
|
||||
empty_day_of_week: 'every day of the week',
|
||||
empty_day_of_month: 'every day of the month',
|
||||
empty_month: 'every month',
|
||||
name_minute: 'minute',
|
||||
name_hour: 'hour',
|
||||
name_day: 'day',
|
||||
name_week: 'week',
|
||||
name_month: 'month',
|
||||
name_year: 'year',
|
||||
text_period: 'Every <b />',
|
||||
text_mins: ' at <b /> minute(s) past the hour',
|
||||
text_time: ' at <b />:<b />',
|
||||
text_dow: ' on <b />',
|
||||
text_month: ' of <b />',
|
||||
text_dom: ' on <b />',
|
||||
error1: 'The tag %s is not supported !',
|
||||
error2: 'Bad number of elements',
|
||||
error3: 'The jquery_element should be set into jqCron settings',
|
||||
error4: 'Unrecognized expression',
|
||||
weekdays: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
||||
months: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
*/
|
||||
@@ -0,0 +1,63 @@
|
||||
import $ from 'jquery';
|
||||
import { config } from 'grav-config';
|
||||
import 'eonasdan-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();
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
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 = {};
|
||||
|
||||
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)$/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() {
|
||||
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();
|
||||
@@ -0,0 +1,383 @@
|
||||
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) {
|
||||
const image = new Image();
|
||||
image.src = event.target.result;
|
||||
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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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('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>${file.name}</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>${file.name}</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)));
|
||||
}
|
||||
|
||||
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"]');
|
||||
modal.find('.error-content').html(msg);
|
||||
$.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');
|
||||
|
||||
return uri.match(/\.(jpe?g|png|gif|svg|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,
|
||||
accept: function(file, done) { ACCEPT_FUNC(file, done, settings); }
|
||||
};
|
||||
|
||||
cache = cache.add(container);
|
||||
container = container[0];
|
||||
instances.push(new FilesField({ container, options }));
|
||||
};
|
||||
|
||||
export let Instances = (() => {
|
||||
$('.dropzone.files-upload').each((i, container) => addNode(container));
|
||||
$('body').on('mutation._grav', onAddedNodes);
|
||||
|
||||
return instances;
|
||||
})();
|
||||
@@ -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,72 @@
|
||||
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 MediapickerField, { Instance as MediapickerInstance } from './mediapicker';
|
||||
import MultilevelField, { Instance as MultilevelInstance } from './multilevel';
|
||||
import SelectUniqueField, { Instance as SelectUniqueInstance } from './selectunique';
|
||||
import IconpickerField, { Instance as IconpickerInstance } from './iconpicker';
|
||||
import CronField, { Instance as CronFieldInstance } from './cron';
|
||||
import TextField, { Instance as TextFieldInstance } from './text';
|
||||
|
||||
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
|
||||
},
|
||||
SelectUniqueField: {
|
||||
SelectUniqueField,
|
||||
Instance: SelectUniqueInstance
|
||||
},
|
||||
MediapickerField: {
|
||||
MediapickerField,
|
||||
Instance: MediapickerInstance
|
||||
},
|
||||
MultilevelField: {
|
||||
MultilevelField,
|
||||
Instance: MultilevelInstance
|
||||
},
|
||||
IconpickerField: {
|
||||
IconpickerField,
|
||||
Instance: IconpickerInstance
|
||||
},
|
||||
CronField: {CronField,
|
||||
Insance: CronFieldInstance
|
||||
},
|
||||
TextField: {TextField,
|
||||
Insance: TextFieldInstance
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import $ from 'jquery';
|
||||
import Scrollbar from '../../utils/scrollbar';
|
||||
import { Instance as pagesTree } from '../../pages/tree';
|
||||
|
||||
const queries = {
|
||||
tree: '.pages-list-container .mediapicker-scroll',
|
||||
thumb: '.thumbs-list-container .mediapicker-scroll'
|
||||
};
|
||||
|
||||
$(function() {
|
||||
let modal = '';
|
||||
let body = $('body');
|
||||
|
||||
let treescrolls = [];
|
||||
let thumbscrolls = [];
|
||||
|
||||
$(queries.tree).each((index, element) => {
|
||||
treescrolls.push(new Scrollbar(element, { autoshow: true }));
|
||||
});
|
||||
|
||||
$(queries.thumb).each((index, element) => {
|
||||
thumbscrolls.push(new Scrollbar(element, { autoshow: true }));
|
||||
});
|
||||
|
||||
// let treescroll = new Scrollbar('.pages-list-container .mediapicker-scroll', { autoshow: true });
|
||||
// let thumbscroll = new Scrollbar('.thumbs-list-container .mediapicker-scroll', { autoshow: true });
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
treescrolls.forEach((tree) => tree.update());
|
||||
thumbscrolls.forEach((thumb) => thumb.update());
|
||||
});
|
||||
|
||||
$(document).on('opened', '.remodal', function() {
|
||||
setTimeout(function() {
|
||||
treescrolls.forEach((tree) => tree.update());
|
||||
thumbscrolls.forEach((thumb) => thumb.update());
|
||||
}, 10);
|
||||
});
|
||||
|
||||
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')];
|
||||
}
|
||||
|
||||
$(queries.tree).filter((index, item) => !$(item).data('scrollbar')).each((index, item) => {
|
||||
treescrolls.push(new Scrollbar(item, { autoshow: true }));
|
||||
});
|
||||
|
||||
$(queries.thumb).filter((index, item) => !$(item).data('scrollbar')).each((index, item) => {
|
||||
thumbscrolls.push(new Scrollbar(item, { autoshow: true }));
|
||||
});
|
||||
|
||||
modal.open();
|
||||
modal.dataField = element.find('input');
|
||||
|
||||
treescrolls.forEach((tree) => tree.update());
|
||||
thumbscrolls.forEach((thumb) => thumb.update());
|
||||
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,279 @@
|
||||
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 + '"';
|
||||
}
|
||||
|
||||
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="margin-left: ${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);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
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);
|
||||
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]');
|
||||
if (!fields.length) { return; }
|
||||
|
||||
fields.each((index, field) => this.add(field));
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new SelectizeField();
|
||||
@@ -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();
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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"]');
|
||||
|
||||
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');
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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]';
|
||||
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 };
|
||||
@@ -0,0 +1,78 @@
|
||||
import $ from 'jquery';
|
||||
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 Scrollbar, { Instance as contentScrollbar } from './utils/scrollbar';
|
||||
import './plugins';
|
||||
import './themes';
|
||||
import { Filter as MediaFilter, Instance as MediaFilterInstance} from './media';
|
||||
import toastr from './utils/toastr';
|
||||
import request from './utils/request';
|
||||
import './utils/2fa';
|
||||
import './tools';
|
||||
|
||||
// bootstrap jQuery extensions
|
||||
import './utils/bootstrap-transition';
|
||||
import './utils/bootstrap-collapse';
|
||||
|
||||
// tabs memory
|
||||
import './utils/tabs-memory';
|
||||
|
||||
// Main Sidebar
|
||||
import Sidebar, { Instance as sidebar } from './utils/sidebar';
|
||||
|
||||
// starts the keep alive, auto runs every X seconds
|
||||
KeepAlive.start();
|
||||
|
||||
// Sidebar auto-refresh
|
||||
global.setInterval(() => {
|
||||
contentScrollbar.update();
|
||||
sidebar.scroller.update();
|
||||
}, 150);
|
||||
|
||||
// global event to catch sidebar_state changes
|
||||
$(global).on('sidebar_state._grav', () => {
|
||||
/* Should not be needed since Gemini Scrollbar v1.4.0
|
||||
* - Auto-update scrollbar on resize
|
||||
*
|
||||
* $('.admin-menu-wrapper').data('scrollbar').update();
|
||||
* $('#admin-main .content-wrapper').data('scrollbar').update();
|
||||
*/
|
||||
|
||||
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,
|
||||
Scrollbar: {
|
||||
Scrollbar,
|
||||
Instance: contentScrollbar
|
||||
},
|
||||
Updates: {
|
||||
Updates,
|
||||
Notifications,
|
||||
Feed,
|
||||
Instance: updates
|
||||
},
|
||||
Sidebar: {
|
||||
Sidebar,
|
||||
Instance: sidebar
|
||||
},
|
||||
MediaFilter: {
|
||||
MediaFilter,
|
||||
Instance: MediaFilterInstance
|
||||
},
|
||||
Utils: { request, toastr }
|
||||
};
|
||||
@@ -0,0 +1,230 @@
|
||||
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();
|
||||
var gemini = view.data('scrollbar');
|
||||
if (gemini) {
|
||||
gemini = gemini.getViewElement();
|
||||
}
|
||||
|
||||
if (!gemini || !gemini.length && !view.length) { return; }
|
||||
|
||||
$(gemini || 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();
|
||||
}
|
||||
});
|
||||
@@ -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.MODULAR, key: 'Modular', 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_MODULAR, key: 'NonModular', 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 };
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
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="modular"] input[name="data[folder]"]');
|
||||
let title = $('[data-remodal-id="modal"] input[name="data[title]"], [data-remodal-id="modular"] 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', event.currentTarget).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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,201 @@
|
||||
import $ from 'jquery';
|
||||
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.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);
|
||||
this.sortable.options.onSort();
|
||||
|
||||
// accepted
|
||||
$('.dz-preview').prop('draggable', 'true');
|
||||
}
|
||||
|
||||
onDropzoneRemovedFile(file, ...extra) {
|
||||
super.onDropzoneRemovedFile(file, ...extra);
|
||||
this.sortable.options.onSort();
|
||||
}
|
||||
|
||||
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) => encodeURIComponent(file.name) === filename).shift();
|
||||
|
||||
original = original.extras.original || encodeURIComponent(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());
|
||||
|
||||
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 = { '': `${global.decodeURI(filename)}.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(filename);
|
||||
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) => {
|
||||
container.append(`<li><strong>${meta ? meta + ':' : ''}</strong> ${fileObj.metadata[meta]}</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 let Instance = new PageMedia();
|
||||
@@ -0,0 +1,13 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
$('[data-page-move] button[name="task"][value="save"]').on('click', function() {
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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));
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
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]; }
|
||||
}
|
||||
});
|
||||
|
||||
const scroller = elements.closest('.mediapicker-scroll');
|
||||
if (scroller.length && scroller.data('scrollbar')) {
|
||||
scroller.data('scrollbar').update();
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
});
|
||||
|
||||
const scroller = elements.closest('.mediapicker-scroll');
|
||||
if (scroller.length && scroller.data('scrollbar')) {
|
||||
scroller.data('scrollbar').update();
|
||||
}
|
||||
|
||||
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 };
|
||||
@@ -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));
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import './logs';
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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;
|
||||
@@ -0,0 +1,186 @@
|
||||
import $ from 'jquery';
|
||||
import unique from 'mout/array/unique';
|
||||
import { config, translations } from 'grav-config';
|
||||
import formatBytes from '../utils/formatbytes';
|
||||
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}`;
|
||||
}
|
||||
|
||||
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) {
|
||||
bar += `<button data-maintenance-update="${config.base_url_relative}/update.json/${task}updategrav/admin-nonce${config.param_sep}${config.admin_nonce}" class="button button-small secondary" id="grav-update-button">${translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW}</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)
|
||||
.parent('#messages').addClass('default-box-shadow');
|
||||
}
|
||||
|
||||
$('#grav-update-button').on('click', function() {
|
||||
$(this).html(`${translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT} ${formatBytes(payload.assets['grav-update'].size)}..`);
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
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 });
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import $ from 'jquery';
|
||||
import request from '../utils/request';
|
||||
|
||||
// Dashboard update and Grav update
|
||||
$('body').on('click', '[data-maintenance-update]', function() {
|
||||
let element = $(this);
|
||||
let url = element.data('maintenanceUpdate');
|
||||
|
||||
element.attr('disabled', 'disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin');
|
||||
|
||||
request(url, (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');
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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 */
|
||||
};
|
||||
};
|
||||
@@ -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, $);
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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)
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { config } from 'grav-config';
|
||||
import { userFeedbackError } from './response';
|
||||
|
||||
class KeepAlive {
|
||||
constructor() {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
start() {
|
||||
let timeout = config.admin_timeout / 1.5 * 1000;
|
||||
this.timer = setInterval(() => this.fetch(), timeout);
|
||||
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();
|
||||
@@ -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;
|
||||
@@ -0,0 +1,458 @@
|
||||
import $ from 'jquery';
|
||||
import { config, translations } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
import { Instance as gpm } from '../utils/gpm';
|
||||
import { Promise } from 'es6-promise';
|
||||
|
||||
class Sorter {
|
||||
getElements(elements, container) {
|
||||
this.elements = elements || $('[data-gpm-plugin], [data-gpm-theme]');
|
||||
this.container = container || $('.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 = '') {
|
||||
let elements = this.getElements().sort((a, b) => {
|
||||
let A = $(a).data(data).toString().toLowerCase();
|
||||
let B = $(b).data(data).toString().toLowerCase();
|
||||
|
||||
return Sorter.sort(A, B, direction);
|
||||
});
|
||||
|
||||
return elements.appendTo(this.container);
|
||||
}
|
||||
|
||||
byName(direction = 'asc', data = 'gpm-name') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byAuthor(direction = 'asc', data = 'gpm-author') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byOfficial(direction = 'asc', data = 'gpm-official') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byReleaseDate(direction = 'asc', data = 'gpm-release-date') {
|
||||
let elements = this.getElements().sort((a, b) => {
|
||||
let A = new Date($(a).data(data)).getTime();
|
||||
let B = new Date($(b).data(data)).getTime();
|
||||
|
||||
return Sorter.sort(A, B, direction === 'asc' ? 'desc' : 'asc');
|
||||
});
|
||||
|
||||
elements.appendTo(this.container);
|
||||
}
|
||||
|
||||
byUpdatable(direction = 'asc', data = 'gpm-updatable') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byEnabled(direction = 'asc', data = 'gpm-enabled') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
|
||||
byTesting(direction = 'asc', data = 'gpm-testing') {
|
||||
return this.byCommon(direction, data);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Promise.all(slugs.map((slug) => {
|
||||
return request(url, {
|
||||
method: 'post',
|
||||
body: {
|
||||
package: slug,
|
||||
type: type
|
||||
}
|
||||
});
|
||||
})).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();
|
||||
|
||||
// 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] .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');
|
||||
|
||||
if (response.status === 'error') {
|
||||
let remodal = $.remodal.lookup[$('[data-packages-modal]').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();
|
||||
@@ -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;
|
||||
@@ -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,40 @@
|
||||
import $ from 'jquery';
|
||||
import GeminiScrollbar from 'gemini-scrollbar';
|
||||
|
||||
const defaults = {
|
||||
autoshow: false,
|
||||
createElements: true,
|
||||
forceGemini: false
|
||||
};
|
||||
|
||||
export default class Scrollbar {
|
||||
constructor(element, options) {
|
||||
this.element = $(element);
|
||||
this.created = false;
|
||||
if (!this.element.length) { return; }
|
||||
|
||||
this.options = Object.assign({}, defaults, options, { element: this.element[0] });
|
||||
|
||||
this.element.css('overflow', 'auto');
|
||||
this.instance = new GeminiScrollbar(this.options);
|
||||
this.create();
|
||||
this.element.data('scrollbar', this.instance);
|
||||
}
|
||||
|
||||
create() {
|
||||
this.instance.create();
|
||||
this.created = true;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (!this.created) { return false; }
|
||||
this.instance.update();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!this.created) { return false; }
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new Scrollbar('#admin-main .content-wrapper');
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,179 @@
|
||||
import $ from 'jquery';
|
||||
import Scrollbar from './scrollbar';
|
||||
import Map from 'es6-map';
|
||||
|
||||
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 Map();
|
||||
|
||||
export default class Sidebar {
|
||||
constructor() {
|
||||
this.timeout = null;
|
||||
this.isOpen = false;
|
||||
this.body = $('body');
|
||||
this.matchMedia = global.matchMedia(MOBILE_QUERY);
|
||||
this.scroller = new Scrollbar('.admin-menu-wrapper', { autoshow: true });
|
||||
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');
|
||||
let scrollbar = $('#admin-menu').data('scrollbar');
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (scrollbar) { scrollbar.update(); }
|
||||
}
|
||||
|
||||
close(event, quick = false) {
|
||||
if (event) { event.preventDefault(); }
|
||||
let overlay = $('#overlay');
|
||||
let sidebar = $('#admin-sidebar');
|
||||
let scrollbar = $('#admin-menu').data('scrollbar');
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (scrollbar) { scrollbar.update(); }
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,29 @@
|
||||
import $ from 'jquery';
|
||||
import Cookies from 'cookies-js';
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import toastr from 'toastr';
|
||||
|
||||
toastr.options.positionClass = 'toast-top-right';
|
||||
toastr.options.preventDuplicates = true;
|
||||
|
||||
export default toastr;
|
||||
@@ -0,0 +1,3 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500|Inconsolata:400,700|&subset=latin-ext");body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input{font-family:"Roboto","Helvetica","Tahoma","Geneva","Arial",sans-serif}h1,h2,h3,h4,.form-tabs>label,.label{font-family:"Josefin Slab","Helvetica","Tahoma","Geneva","Arial",sans-serif}code,kbd,pre,samp,body .CodeMirror{font-family:"Inconsolata","Monaco","Consolas","Lucida Console",monospace !important}
|
||||
|
||||
/*# sourceMappingURL=fonts.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"fonts.css","sources":["fonts.scss","configuration/fonts/_support.scss"],"sourcesContent":["$fonts-header: 'Josefin Slab' !default;\n$fonts-default: 'Roboto' !default;\n$fonts-mono: 'Inconsolata' !default;\n\n$font-definitions: (\n Josefin+Slab: 400,\n Roboto: '300,400,500',\n Inconsolata: '400,700'\n);\n\n@import \"configuration/fonts/support\";\n","@function str-replace($string, $search, $replace: '') {\n $index: str-index($string, $search);\n\n @if $index {\n @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);\n }\n\n @return $string;\n}\n\n@function admin-font-faces($fonts) {\n $url: \"https://fonts.googleapis.com/css?family=\";\n $nb: 0;\n\n @each $fontname, $weights in $fonts {\n\n @if $fontname == $fonts-default or\n $fontname == $fonts-header or\n $fontname == $fonts-mono {\n\n $nb: $nb + 1;\n $nb-word: 0;\n\n $fontname: str-replace(\"#{$fontname}\", \" \", \"+\");\n\n $url: $url + $fontname;\n\n @if $weights != null {\n $url: $url + \":\" + $weights;\n }\n\n @if $nb < 3 {\n $url: $url + \"|\";\n }\n }\n }\n\n @return $url + \"&subset=latin-ext\";\n}\n\n@mixin body-fonts($font) {\n body, h5, h6,\n .badge, .note, .grav-mdeditor-preview,\n input, select, textarea, button, .selectize-input {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin header-fonts($font) {\n h1, h2, h3, h4,\n .form-tabs > label, .label {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin mono-fonts($font) {\n code, kbd, pre, samp,\n body .CodeMirror {\n font-family: \"#{$font}\", \"Monaco\", \"Consolas\", \"Lucida Console\", monospace !important;\n }\n}\n$font-url: admin-font-faces($font-definitions);\n\n@import url(\"#{$font-url}\");\n\n@include body-fonts($fonts-default);\n\n@include header-fonts($fonts-header);\n\n@include mono-fonts($fonts-mono);\n\n\n\n\n\n"],"names":[],"mappings":"AC+DA,OAAO,CAAC,uGAAI,CAtBR,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,AAAC,CAC9C,WAAW,CAAE,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,UAAU,CAAG,KAAK,CAAE,MAAM,AAAC,CACvB,WAAW,CAAE,cAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,CACpB,IAAI,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input,h1,h2,h3,h4,#admin-menu li,.form-tabs>label,.label,body .CodeMirror pre{font-family:"Helvetica Neue", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif}code,kbd,pre,samp,.mono{font-family:"Monaco", "Consolas", "Lucida Console", monospace}
|
||||
|
||||
/*# sourceMappingURL=simple-fonts.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"simple-fonts.css","sources":["simple-fonts.scss"],"sourcesContent":["body, h5, h6,\n.badge, .note, .grav-mdeditor-preview,\ninput, select, textarea, button, .selectize-input,\nh1, h2, h3, h4,\n#admin-menu li, .form-tabs > label, .label,\nbody .CodeMirror pre {\n font-family: \"Helvetica Neue\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n}\ncode, kbd, pre, samp, .mono {\n font-family: \"Monaco\", \"Consolas\", \"Lucida Console\", monospace;\n}\n"],"names":[],"mappings":"AAAA,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,CACjD,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,WAAW,CAAC,EAAE,CAAE,UAAU,CAAG,KAAK,CAAE,MAAM,CAC1C,IAAI,CAAC,WAAW,CAAC,GAAG,AAAC,CACjB,WAAW,CAAE,sEAAsE,CACtF,AACD,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,CAAE,KAAK,AAAE,CACzB,WAAW,CAAE,iDAAiD,CACjE"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
|
||||
Name: Base16 Default Light
|
||||
Author: Chris Kempson (http://chriskempson.com)
|
||||
|
||||
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools)
|
||||
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
|
||||
|
||||
*/
|
||||
|
||||
.cm-s-base16-light.CodeMirror {background: #f5f5f5; color: #202020;}
|
||||
.cm-s-base16-light div.CodeMirror-selected {background: #e0e0e0 !important;}
|
||||
.cm-s-base16-light .CodeMirror-gutters {background: #f5f5f5; border-right: 0px;}
|
||||
.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; }
|
||||
.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; }
|
||||
.cm-s-base16-light .CodeMirror-linenumber {color: #b0b0b0;}
|
||||
.cm-s-base16-light .CodeMirror-cursor {border-left: 1px solid #505050 !important;}
|
||||
|
||||
.cm-s-base16-light span.cm-comment {color: #8f5536;}
|
||||
.cm-s-base16-light span.cm-atom {color: #aa759f;}
|
||||
.cm-s-base16-light span.cm-number {color: #aa759f;}
|
||||
|
||||
.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute {color: #90a959;}
|
||||
.cm-s-base16-light span.cm-keyword {color: #ac4142;}
|
||||
.cm-s-base16-light span.cm-string {color: #f4bf75;}
|
||||
|
||||
.cm-s-base16-light span.cm-variable {color: #90a959;}
|
||||
.cm-s-base16-light span.cm-variable-2 {color: #6a9fb5;}
|
||||
.cm-s-base16-light span.cm-def {color: #d28445;}
|
||||
.cm-s-base16-light span.cm-bracket {color: #202020;}
|
||||
.cm-s-base16-light span.cm-tag {color: #ac4142;}
|
||||
.cm-s-base16-light span.cm-link {color: #aa759f;}
|
||||
.cm-s-base16-light span.cm-error {background: #ac4142; color: #505050;}
|
||||
|
||||
.cm-s-base16-light .CodeMirror-activeline-background {background: #DDDCDC !important;}
|
||||
.cm-s-base16-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
|
||||
@@ -0,0 +1,332 @@
|
||||
/* 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 div.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.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-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; }
|
||||
@@ -0,0 +1,39 @@
|
||||
.cm-s-paper .cm-keyword {color: #555;}
|
||||
.cm-s-paper .cm-atom {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-number {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-def {color: #00f;}
|
||||
.cm-s-paper .cm-variable {color: black;}
|
||||
.cm-s-paper .cm-variable-2 {color: #555;}
|
||||
.cm-s-paper .cm-variable-3 {color: #085;}
|
||||
.cm-s-paper .cm-property {color: black;}
|
||||
.cm-s-paper .cm-operator {color: black;}
|
||||
.cm-s-paper .cm-comment {color: #959595;}
|
||||
.cm-s-paper .cm-string {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-string-2 {color: #f50;}
|
||||
.cm-s-paper .cm-meta {color: #555;}
|
||||
.cm-s-paper .cm-error {color: #f00;}
|
||||
.cm-s-paper .cm-qualifier {color: #555;}
|
||||
.cm-s-paper .cm-builtin {color: #555;}
|
||||
.cm-s-paper .cm-bracket {color: #997;}
|
||||
.cm-s-paper .cm-tag {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-attribute {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-header {color: #000;}
|
||||
.cm-s-paper .cm-quote {color: #888;}
|
||||
.cm-s-paper .cm-hr {color: #999;}
|
||||
.cm-s-paper .cm-link {color: #7f8c8d;}
|
||||
|
||||
.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-invalidchar {color: #f00;}
|
||||
|
||||
.cm-s-paper .cm-header-1 {font-size: 140%;}
|
||||
.cm-s-paper .cm-header-2 {font-size: 120%;}
|
||||
.cm-s-paper .cm-header-3 {font-size: 110%;}
|
||||
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Featherlight - ultra slim jQuery lightbox
|
||||
* Version 0.4.9 - http://noelboss.github.io/featherlight/
|
||||
*
|
||||
* Copyright 2014, Noël Raoul Bossart (http://www.noelboss.com)
|
||||
* MIT Licensed.
|
||||
**/
|
||||
@media all{.featherlight{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2;text-align:center;white-space:nowrap;cursor:pointer;background:#333;background:rgba(0,0,0,0)}.featherlight:last-of-type{background:rgba(0,0,0,.8)}.featherlight:before{content:'';display:inline-block;height:100%;vertical-align:middle;margin-right:-.25em}.featherlight .featherlight-content{position:relative;text-align:left;vertical-align:middle;display:inline-block;overflow:auto;padding:25px 25px 0;border-bottom:25px solid transparent;min-width:30%;margin-left:5%;margin-right:5%;max-height:95%;background:#fff;cursor:auto;white-space:normal}.featherlight .featherlight-inner{display:block}.featherlight .featherlight-close-icon{position:absolute;z-index:9999;top:0;right:0;line-height:25px;width:25px;cursor:pointer;text-align:center;font:Arial,sans-serif;background:#fff;background:rgba(255,255,255,.3);color:#000}.featherlight .featherlight-image{width:100%}.featherlight-iframe .featherlight-content{border-bottom:0;padding:0}.featherlight iframe{border:0}}@media only screen and (max-width:1024px){.featherlight .featherlight-content{margin-left:10px;margin-right:10px;max-height:98%;padding:10px 10px 0;border-bottom:10px solid transparent}}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
|
||||
/*! Hint.css (base version) - v1.3.5 - 2015-06-16
|
||||
* http://kushagragour.in/lab/hint/
|
||||
* Copyright (c) 2015 Kushagra Gour; Licensed MIT */
|
||||
|
||||
.hint--bottom:after,.hint--top:after{margin-left:-18px}.hint,[data-hint]{position:relative;display:inline-block}.hint:after,.hint:before,[data-hint]:after,[data-hint]:before{position:absolute;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0);visibility:hidden;opacity:0;z-index:1000000;pointer-events:none;-webkit-transition:.3s ease;-moz-transition:.3s ease;transition:.3s ease;-webkit-transition-delay:0ms;-moz-transition-delay:0ms;transition-delay:0ms}.hint--always:after,.hint--always:before,.hint:focus:after,.hint:focus:before,.hint:hover:after,.hint:hover:before,[data-hint]:focus:after,[data-hint]:focus:before,[data-hint]:hover:after,[data-hint]:hover:before{opacity:1;visibility:visible}.hint:hover:after,.hint:hover:before,[data-hint]:hover:after,[data-hint]:hover:before{-webkit-transition-delay:100ms;-moz-transition-delay:100ms;transition-delay:100ms}.hint:before,[data-hint]:before{content:'';position:absolute;background:0 0;border:6px solid transparent;z-index:1000001}.hint:after,[data-hint]:after{content:attr(data-hint);background:#383838;color:#fff;padding:8px 10px;font-size:12px;line-height:12px;white-space:nowrap}.hint--top:before{border-top-color:#383838;margin-bottom:-12px}.hint--bottom-left:before,.hint--bottom-right:before,.hint--bottom:before{border-bottom-color:#383838;margin-top:-12px}.hint--top:after,.hint--top:before{bottom:100%;left:50%}.hint--top:focus:after,.hint--top:focus:before,.hint--top:hover:after,.hint--top:hover:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--bottom:after,.hint--bottom:before{top:100%;left:50%}.hint--bottom:focus:after,.hint--bottom:focus:before,.hint--bottom:hover:after,.hint--bottom:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--right:before{border-right-color:#383838;margin-left:-12px;margin-bottom:-6px}.hint--right:after{margin-bottom:-14px}.hint--right:after,.hint--right:before{left:100%;bottom:50%}.hint--right:focus:after,.hint--right:focus:before,.hint--right:hover:after,.hint--right:hover:before{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translateX(8px)}.hint--left:before{border-left-color:#383838;margin-right:-12px;margin-bottom:-6px}.hint--left:after{margin-bottom:-14px}.hint--left:after,.hint--left:before{right:100%;bottom:50%}.hint--left:focus:after,.hint--left:focus:before,.hint--left:hover:after,.hint--left:hover:before{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translateX(-8px)}.hint--bottom-left:after,.hint--bottom-left:before{top:100%;left:50%}.hint--bottom-left:before{margin-top:-11px;left:calc(50% - 6px)}.hint--bottom-left:after{-webkit-transform:translateX(-100%);-moz-transform:translateX(-100%);transform:translateX(-100%);margin-left:12px}.hint--bottom-left:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--bottom-left:hover:after{-webkit-transform:translateX(-100%) translateY(8px);-moz-transform:translateX(-100%) translateY(8px);transform:translateX(-100%) translateY(8px)}.hint--bottom-right:after,.hint--bottom-right:before{top:100%;left:50%}.hint--bottom-right:before{margin-top:-11px;left:calc(50% - 6px)}.hint--bottom-right:after{-webkit-transform:translateX(0);-moz-transform:translateX(0);transform:translateX(0);margin-left:-12px}.hint--bottom-right:hover:after,.hint--bottom-right:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--always.hint--top:after,.hint--always.hint--top:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--always.hint--bottom:after,.hint--always.hint--bottom:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--always.hint--left:after,.hint--always.hint--left:before{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translateX(-8px)}.hint--always.hint--right:after,.hint--always.hint--right:before{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translateX(8px)}
|
||||
@@ -0,0 +1,223 @@
|
||||
/* Icon Picker by QueryLoop
|
||||
* Author: @eliorivero
|
||||
* URL: http://queryloop.com/
|
||||
* License: GPLv2
|
||||
*/
|
||||
|
||||
.icon-set {
|
||||
display: none;
|
||||
background: #fff;
|
||||
overflow-y: auto;
|
||||
-webkit-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.icon-set ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.icon-set li {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
margin: 0 0 5px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.icon-set li:hover:before, .icon-set .icon-selected:before {
|
||||
font-size: 32px;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
line-height: 1px;
|
||||
width: 32px;
|
||||
display: inline-block;
|
||||
}
|
||||
.icon-set.large li {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 32px;
|
||||
}
|
||||
.icon-set.large li:hover:before, .icon-set.large .icon-selected:before {
|
||||
font-size: 48px;
|
||||
width: 48px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.icon-highlight {
|
||||
background: #00a6cf;
|
||||
color: #fff;
|
||||
}
|
||||
.dialog {
|
||||
display: block;
|
||||
margin: 20px 0 0;
|
||||
position: fixed;
|
||||
right: 30%;
|
||||
bottom: 30%;
|
||||
width: 40%;
|
||||
height: 40%;
|
||||
max-height: 315px;
|
||||
z-index: 19998;
|
||||
visibility:hidden;
|
||||
opacity: 0;
|
||||
-webkit-box-shadow: 0 2px 4px rgba(0,0,0,0.25);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.25);
|
||||
-webkit-transform: scale(0.7) translateZ(0);
|
||||
-ms-transform: scale(0.7) translateZ(0);
|
||||
transform: scale(0.7) translateZ(0);
|
||||
-webkit-transition: all 0.2s;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.dialog-open {
|
||||
padding: 10px;
|
||||
visibility:visible;
|
||||
-webkit-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
.icon-set-wrap {
|
||||
position: relative;
|
||||
}
|
||||
.ip-inline {
|
||||
display: block;
|
||||
width: 276px;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
visibility:hidden;
|
||||
-webkit-transform: scaleX(0.5) rotateX(90deg);
|
||||
-ms-transform: scaleX(0.5) rotateX(90deg);
|
||||
transform: scaleX(0.5) rotateX(90deg);
|
||||
-webkit-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
-webkit-transform-origin: 50% 0;
|
||||
transform-origin: 50% 0;
|
||||
-webkit-transition: transform 0.3s ease-out;
|
||||
transition: transform 0.3s ease-out;
|
||||
}
|
||||
.ip-inline-open {
|
||||
padding: 10px;
|
||||
border: 1px solid #eee;
|
||||
height: auto;
|
||||
visibility:visible;
|
||||
-webkit-transform: scaleX(1) rotateY(0deg);
|
||||
-ms-transform: scaleX(1) rotateY(0deg);
|
||||
transform: scaleX(1) rotateY(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
.dialog, .icon-picker-close {
|
||||
top: 30%;
|
||||
left: 30%;
|
||||
}
|
||||
.icon-picker-close {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: #fff;
|
||||
background: #333;
|
||||
font-size: 19px;
|
||||
margin: 30px 0 0 20px;
|
||||
position: fixed;
|
||||
z-index: 19999;
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
||||
-webkit-transition: all 0.2s;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.icon-picker-close:after {
|
||||
content: "\00D7";
|
||||
width: 20px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
}
|
||||
.icon-picker-overlay {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
background: #222;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 19997;
|
||||
-webkit-transition: all 0.2s;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.icon-picker-close.make-visible {
|
||||
margin: 10px 0 0 -10px;
|
||||
}
|
||||
.ip-inline .icon-picker-close {
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: auto;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
margin: 0;
|
||||
}
|
||||
.make-visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.icon-selected {
|
||||
background: #349886;
|
||||
color: #fff;
|
||||
}
|
||||
.icon-preview {
|
||||
display: none;
|
||||
margin: 10px 0;
|
||||
position: relative;
|
||||
}
|
||||
.icon-preview-on {
|
||||
display: inline-block;
|
||||
}
|
||||
.icon-preview i {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
border: 1px solid #d5d5d5;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
}
|
||||
.icon-preview i:before {
|
||||
font-size: 32px;
|
||||
line-height: 48px;
|
||||
}
|
||||
.remove-icon {
|
||||
background-color: #00a6cf;
|
||||
color: #fff !important;
|
||||
border-radius: 3px;
|
||||
transition: background .2s ease-out;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
}
|
||||
.remove-icon:hover {
|
||||
color: #fff !important;
|
||||
background-color: #00a6cf;
|
||||
cursor: pointer;
|
||||
}
|
||||
.remove-times {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 21px;
|
||||
text-align: center;
|
||||
line-height: 99%;
|
||||
}
|
||||
.remove-times:after {
|
||||
content: "\00D7";
|
||||
display: block;
|
||||
line-height: 15px;
|
||||
}
|
||||
.launch-icons {
|
||||
margin: 5px 3px 0 0;
|
||||
}
|
||||
.launch-icons:active {
|
||||
margin: 5px 3px 0 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fixed-blocks {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.feature-items {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modular .features .feature {
|
||||
margin: 1%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
float: none;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* IE9 Resets and Normalization */
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
text-shadow: 0 1px white;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.builtin {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
color: #a67f59;
|
||||
background: hsla(0,0%,100%,.5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*!
|
||||
Pure v0.5.0
|
||||
Copyright 2014 Yahoo! Inc. All rights reserved.
|
||||
Licensed under the BSD License.
|
||||
https://github.com/yui/pure/blob/master/LICENSE.md
|
||||
*/
|
||||
.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}
|
||||
|
||||
/* Custom */
|
||||
[class *="pure-u"] {display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;}
|
||||
.pure-u-1-7 {width: 14.285%;}.pure-u-2-7 {width: 28.571%;}.pure-u-3-7 {width: 42.857%;}.pure-u-4-7 {width: 57.142%;}.pure-u-5-7 {width: 71.428%;}.pure-u-6-7 {width: 85.714%;}
|
||||
.pure-u-1-9 {width: 11.111%;}.pure-u-2-9 {width: 22.222%;}.pure-u-3-9 {width: 33.333%;}.pure-u-4-9 {width: 44.444%;}.pure-u-5-9 {width: 55.555%;}.pure-u-6-9 {width: 66.666%;}.pure-u-7-9 {width: 77.777%;}.pure-u-8-9 {width: 88.888%;}
|
||||
.pure-u-1-10 {width: 10%;}.pure-u-2-10 {width: 20%;}.pure-u-3-10 {width: 30%;}.pure-u-4-10 {width: 40%;}.pure-u-5-10 {width: 50%;}.pure-u-6-10 {width: 60%;}.pure-u-7-10 {width: 70%;}.pure-u-8-10 {width: 80%;}.pure-u-9-10 {width: 90%;}
|
||||
|
||||
.pure-u-1-11 {width: 9.090%;}.pure-u-2-11 {width: 18.181%;}.pure-u-3-11 {width: 27.272%;}.pure-u-4-11 {width: 36.363%;}.pure-u-5-11 {width: 45.454%;}.pure-u-6-11 {width: 54.545%;}.pure-u-7-11 {width: 63.636%;}.pure-u-8-11 {width: 72.727%;}.pure-u-9-11 {width: 81.818%;}.pure-u-10-11 {width: 90.909%;}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 463 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by Fontastic.me</metadata>
|
||||
<defs>
|
||||
<font id="untitled-font-1" horiz-adv-x="512">
|
||||
<font-face font-family="untitled-font-1" units-per-em="512" ascent="480" descent="-32"/>
|
||||
<missing-glyph horiz-adv-x="512" />
|
||||
|
||||
<glyph glyph-name="grav-logo" unicode="a" d="M101 253l-13 0c-1 0-3-2-3-4l0-23 0 0c-7-7-17-10-26-10-22 0-40 18-40 40 0 22 18 40 40 40 9 0 17-3 24-8 1-1 3-1 4 1l9 8c1 1 1 2 1 3 0 1-1 2-1 2-11 9-24 13-37 13-32 0-59-26-59-59 0-32 27-59 59-59 17 0 33 7 44 20 1 1 1 1 1 2l0 30c0 2-1 4-3 4m410 57c0 1-2 1-3 1l-13 0c-1 0-3-1-3-2l-38-87-38 87c-1 1-2 2-3 2l-14 0c-1 0-2 0-2-1-1-1-1-2-1-3l47-106c0-2 1-2 3-2l17 0c1 0 2 0 3 2l45 106c1 1 1 2 0 3m-175-1c-1 1-2 2-3 2l-17 0c-2 0-3-1-3-2l-46-106c0-1 0-2 0-3 1-1 2-1 3-1l13 0c2 0 3 0 4 2l37 87 38-87c1-2 2-2 4-2l13 0c1 0 2 0 3 1 0 1 1 2 0 3z m-123-72l1 1c14 6 22 20 22 35 0 21-17 38-39 38l-49 0c-1 0-3-1-3-3l0-106c0-2 2-3 3-3l13 0c2 0 3 1 3 3l0 90 33 0c11 0 20-9 20-19 0-9-6-17-15-19-1-1-3-1-5-1l-12 0c-1 0-2-1-3-2 0-1 0-2 0-3l35-48c1-1 2-1 3-1l15 0c1 0 3 0 3 2 1 1 1 2 0 3z"/>
|
||||
<glyph glyph-name="grav-symbol" unicode="b" d="M392 216c-27 17-42 32-55 59-9-18-30-45-66-67-17-55-102-161-129-146-8 5-10 11-8 17 2 15 33 49 33 49 0 0 1-7 11-22-13 41 22 91 31 108 15 5 16 24 16 24 1 29-12 50-25 62 9-11 12-27 12-43l0 0c0-1 0-3 0-5 0-12-4-30-12-30l0 0c-8 1-19-1-28-4l-20-5c0 0 11 1 17-5-7-10-22-23-38-31-23-11-30 11-18 25 3 4 6 6 9 9-2 2-3 4-4 7 0 0 0 0 0 0-1 8 0 17 10 32 2 3 4 6 7 10 0 0 0 0 0 0 0 0 1 0 1 0 0 1 0 1 0 1 1 1 1 1 2 2 13 13 32 26 62 37 36 52 48 62 48 62 4 4 11 9 14 10-19 32-22 78-18 90 0-1 0-1-1-2 2 4 3 5 5 8 5 6 22 9 28-4 4-7 4-15 4-22-13 1-26-15-26-15 0 0 12 5 26 6 0 0 3-4 8-10-6-11-17-36-9-62 1-6 3-10 5-14 0 0 0 0 1 0 0-1 0-1 0-1 12-19 35-21 35-21-11 6-19 14-25 24-3 6-5 10-6 14-6 23 3 37 8 46 12 18 33 28 55 26 32-4 55-33 52-64-3-20-14-36-30-45 4-10 0-23 0-23 9-12 10-19 9-25-12 2-24-7-24-7 0 0 24 6 38-6 8-10 14-18 18-25 5-9 29-10 26-26-3-17-21-17-49 2m-138 296c-140 0-254-114-254-254 0-141 114-255 254-255 141 0 255 114 255 255 0 140-114 254-255 254m16-189c3 3 3 8 0 11-3 3-8 3-11 0-3-3-3-8 0-11 3-3 8-3 11 0m-27-5c-3-3-3-8 0-11 3-3 8-3 11 0 3 3 3 8 0 11-3 3-8 3-11 0m47-4c-5 4-12 4-17 0l-10-10c-5-5-5-12 0-17l11-11c5-5 12-5 17 0l10 10c4 5 4 12 0 17z m60 46c-8 10 7 27 16 17 17-19-8-26-16-17m34-9c13 24-5 38-17 49-20 18-40 26-62 1-22-26-8-55 12-70 21-14 54-4 67 20"/>
|
||||
<glyph glyph-name="gantry-symbol" unicode="c" d="M439 310l0-76 65 38-52 30 0 8 9 5 48-27c-3 22-9 42-16 62l-54-32z m0-102l0-76 56 33c1 3 2 6 3 9l-46 27 0 8 9 5 42-24c5 20 8 42 8 64 0 1 0 2 0 3l-72-41z m-88-188l0-3c13 5 25 11 36 18l-3 2-33 19z m41 68l-34-19 33-19 10-6c9 6 17 13 26 20l0 42z m10 140l-18 12-33 19 0-76 76 43 0 84-67-38 31-19 19-12 0-8z m0-101l-18 11-33 19 0-75 76 42 0 84-69-38 33-19 19-11 0-8z m37-21l0-29c18 18 34 40 46 64l-46-26z m-117 288l11 45 10-45 45-10-45-11-10-45-11 45-45 11z m-266-115l89-51c2-1 5-3 5-6l0-201c11-5 22-9 34-12l0 37c8 9 17 22 42 31-18-1-30-7-42-13l0 103c8 9 18 21 44 31-19-2-32-7-42-13 0 2 0 5 1 7l36 92c4 4 12 8 17 10-4 0-9-1-14-2l4 11c1 2 2 4 3 5 2 2 5 4 5 4l0 91c0 1 1 0 2 0l5 0c0 0 2 1 2 0l0-91c0 0 2-1 3-1 2-2 3-5 5-8l43-111c2-4 3-9 3-14l0-175c11 2 23 5 34 8l0 261c0 3 2 5 4 6l89 51 59 34c-41 87-129 147-231 147-123 0-225-87-250-202z m-56-25c0 0 0-1 0-2l22 13 7-5 0-8-28-17c0-9 2-19 3-28l42-23 0 84-43 25c-2-13-3-26-3-39m46-96l0 8-37 21c2-9 5-17 9-26l4 3 7-5 0-8-6-4c7-13 14-26 23-38z m88-51l-34-19-26-14c18-18 38-33 60-44z m-76 0l0-15c2-2 4-4 6-6l27 15 34 19-34 19-33 19z m33 96l33 19-66 38 0-84 76-43 0 76-34-19-21-11-8 5 0 8z"/>
|
||||
<glyph glyph-name="gantry-logo" unicode="d" d="M295 219l0 39c-1 16-16 30-34 30-17 0-35-16-35-30l0-39-11 0 0 72 18 0 1 1c8 6 18 8 27 8 26 0 47-20 47-46l0 0c0 0 0-1 0-1l0-34z m32 0l0 79-14 0 0 13 14 0 0 34 13 0 0-34 13 0 0-13-13 0 0-79z m36 0l0 71 17 0 1 1c7 5 15 8 24 9l0-12c-16-2-28-15-30-30l0-2 0-3 0-34z m-211 69c-19 0-34-15-34-34 0-19 15-34 34-34 19 0 35 15 35 34 0 19-16 34-35 34m0-80c-25 0-46 20-46 46 0 25 21 46 46 46 26 0 47-21 47-46l0 0c0 0 0-5 0-5l0-30-17 0-1-1c-8-7-18-10-29-10m318-29c17 2 30 16 30 34l0 9-7-6c-8-5-17-8-27-8-25 0-45 19-46 43l0 39 12 0 0-40c1-17 17-31 34-31 17 0 34 15 34 29l0 0 0 42 12 0 0-79 0-2c-1-12-7-22-15-30-7-7-16-11-27-12z m-424 109c-19 0-34-15-34-34 0-19 15-34 34-34 19 0 35 15 35 34 0 19-16 34-35 34m5-109c17 2 30 16 30 34l0 9-8-6c-8-5-17-8-27-8-25 0-46 20-46 46 0 25 21 46 46 46 11 0 20-3 29-9l1-1 17 0 0-77c0-24-19-44-42-46z"/>
|
||||
<glyph glyph-name="gantry-logo-both" unicode="e" d="M362 229l0 26c-1 12-11 21-24 21-12 0-24-11-24-21l0-26-8 0 0 49 13 0 1 1c5 4 12 6 18 6 18 0 32-15 32-32l0 0c0-1 0-1 0-1l0-23z m22 0l0 54-10 0 0 9 10 0 0 24 9 0 0-24 9 0 0-9-9 0 0-54z m25 0l0 49 12 0 0 0c5 4 11 6 17 6l0-8c-11-1-19-10-21-21l0-1 0-2 0-23z m-146 47c-13 0-24-10-24-23 0-14 11-24 24-24 13 0 24 10 24 24 0 13-11 23-24 23m0-56c-18 0-32 15-32 33 0 17 14 32 32 32 18 0 32-15 32-32l0-1c0 0 0-3 0-3l0-20-11 0-1-1c-6-5-13-8-20-8m220-19c11 1 20 11 21 23l0 6-5-3c-6-4-12-7-19-7-17 0-31 14-32 31l0 27 8 0 0-28c1-12 12-21 24-21 12 0 23 10 24 20l0 0 0 29 8 0 0-55 0-2c-1-8-5-15-11-20-5-5-11-8-18-9z m-293 75c-13 0-24-10-24-23 0-14 11-24 24-24 13 0 23 10 23 24 0 13-10 23-23 23m3-75c12 1 20 11 21 23l0 6-6-3c-5-4-12-7-18-7-18 0-32 15-32 33 0 17 14 32 32 32 7 0 14-3 19-7l1 0 12 0 0-54c0-16-13-30-29-32z m-83 68l0-19 17 10-14 8 0 2 3 1 12-7c-1 5-2 11-4 16l-14-8z m0-25l0-19 14 8c1 1 1 2 1 2l-12 7 0 2 3 1 10-6c2 6 2 11 2 17 0 0 0 0 0 0l-18-10z m-22-47l0-1c3 1 6 3 9 5l-1 0-8 5z m11 17l-9-5 8-5 3-1c2 1 4 3 6 5l0 10z m2 35l-5 3-8 5 0-19 19 10 0 21-17-9 8-5 5-3 0-2z m0-25l-5 2-8 5 0-19 19 11 0 21-8-5-9-5 8-4 5-3 0-2z m9-6l0-7c5 5 9 10 12 16l-12-6z m-29 73l2 11 3-11 11-3-11-3-3-11-2 11-11 3z m-67-29l22-13c1 0 2-1 2-2l0-50c2-1 5-2 8-3l0 9c2 3 4 6 11 8-5 0-8-2-11-3l0 26c2 2 5 5 11 7-5 0-8-1-10-3 0 1 0 1 0 2l9 23c1 1 3 2 4 3-1 0-2-1-3-1l1 3c0 0 0 1 0 1 1 1 2 1 2 1l0 23c0 0 0 0 0 0l1 0c1 0 1 0 1 0l0-23c0 0 0 0 1 0 0-1 1-1 1-2l11-28c0-1 1-2 1-3l0-45c2 1 5 2 8 3l0 65c0 1 1 1 1 2l22 12 15 9c-10 22-32 37-58 37-31 0-56-22-63-51z m-14-6c0-1 0-1 0-1l5 3 2-1 0-2-7-4c0-3 1-5 1-7l10-6 0 21-10 6c-1-3-1-6-1-9m11-25l0 3-9 5c1-3 2-5 2-7l1 1 2-1 0-2-1-1c1-4 3-7 5-10z m23-12l-9-5-6-4c4-4 9-8 15-11z m-19 0l0-4c0-1 1-1 1-2l7 4 8 5-8 5-8 4z m8 24l8 4-16 10 0-21 19-11 0 19-9-5-5-2-2 1 0 2z"/>
|
||||
<glyph glyph-name="grav-logo-both" unicode="f" d="M240 254l-9 0c-1 0-2-1-2-2l0-15 0-1c-5-4-11-6-17-6-14 0-26 12-26 27 0 14 12 26 26 26 6 0 11-2 16-5 1-1 2-1 3 0l5 6c1 0 1 1 1 2 0 0 0 1-1 1-7 6-15 9-24 9-21 0-39-18-39-39 0-22 18-39 39-39 11 0 22 4 29 13 1 0 1 1 1 1l0 20c0 1-1 2-2 2m271 38c-1 1-1 1-2 1l-9 0c-1 0-2 0-2-1l-25-58-25 58c-1 1-1 1-2 1l-9 0c-1 0-2 0-2-1 0-1 0-1 0-2l31-70c0-1 1-1 2-1l11 0c1 0 1 0 2 1l30 70c0 1 0 1 0 2m-116 0c-1 1-1 1-2 1l-11 0c-1 0-2 0-2-1l-31-70c0-1 0-2 1-2 0-1 1-1 2-1l8 0c1 0 2 0 2 1l25 58 26-58c0-1 1-1 2-1l8 0c1 0 2 0 2 1 1 0 1 1 1 2z m-81-48l1 1c8 4 14 13 14 23 0 14-11 25-25 25l-33 0c-1 0-2-1-2-2l0-70c0-1 1-2 2-2l8 0c1 0 2 1 2 2l0 60 23 0c7 0 13-6 13-13 0-6-5-12-10-13-1 0-2 0-3 0l-9 0c0 0-1-1-2-2 0 0 0-1 1-2l23-31c0-1 1-1 1-1l10 0c1 0 2 0 2 1 1 1 1 2 0 2z m-205 1c-7 5-12 9-15 16-3-5-9-12-19-18-5-16-28-45-36-41-2 1-2 3-2 5 1 4 9 14 9 14 0 0 1-2 3-7-3 12 6 26 9 30 4 2 4 7 4 7 1 8-3 14-6 17 2-3 3-7 3-12l0 0c0 0 0-1 0-1 0-4-1-8-3-8l0 0c-3 0-6-1-8-2l-6-1c0 0 3 0 5-1-2-3-6-7-11-9-6-3-8 3-5 7 1 1 2 2 3 2-1 1-1 2-1 3 0 0 0 0 0 0-1 2 0 4 2 8 1 1 2 2 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 4 3 9 7 18 10 9 15 13 17 13 17 1 2 3 3 4 3-5 9-6 22-5 25 0 0 0 0 0 0 0 1 0 1 1 2 1 1 6 2 8-1 1-2 1-5 1-6-4 0-7-4-7-4 0 0 3 1 7 1 0 0 1-1 2-3-2-3-5-10-3-17 1-1 1-3 2-4 0 0 0 0 0 0 0 0 0 0 0 0 4-5 10-6 10-6-3 2-5 4-7 7-1 1-1 3-2 4-1 6 1 10 3 12 3 5 9 8 15 8 9-1 15-9 14-18-1-6-4-10-8-12 1-3 0-7 0-7 2-3 3-5 2-7-3 1-6-2-6-2 0 0 6 2 10-2 3-2 4-5 5-6 2-3 8-3 8-8-1-4-6-4-14 1m-38 82c-39 0-71-32-71-71 0-39 32-70 71-70 39 0 70 31 70 70 0 39-31 71-70 71m4-52c1 0 1 2 0 3-1 0-2 0-3 0-1-1-1-3 0-3 1-1 2-1 3 0m-7-2c-1-1-1-2 0-3 0-1 2-1 2 0 1 1 1 2 0 3 0 1-2 1-2 0m12-1c-1 1-3 1-4 0l-3-3c-1-1-1-3 0-4l3-3c1-2 3-2 5 0l3 2c1 2 1 4 0 5z m17 13c-2 3 2 7 5 5 4-6-3-8-5-5m10-3c3 7-2 11-5 14-6 5-11 7-17 0-6-7-3-15 3-19 6-4 15-1 19 5"/>
|
||||
</font></defs></svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user