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

View File

@@ -0,0 +1,3 @@
{
"presets": [ "@babel/preset-env" ]
}

View File

@@ -0,0 +1 @@
node_modules/

View File

@@ -0,0 +1,165 @@
{
"root": true,
"env": {
"browser": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"rules": {
"accessor-pairs": 2,
"array-bracket-spacing": 0,
"block-scoped-var": 0,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"camelcase": 0,
"comma-dangle": [2, "never"],
"comma-spacing": [2, { "before": false, "after": true }],
"comma-style": [2, "last"],
"complexity": 0,
"computed-property-spacing": 0,
"consistent-return": 0,
"consistent-this": 0,
"constructor-super": 2,
"curly": [2, "multi-line"],
"default-case": 0,
"dot-location": [2, "property"],
"dot-notation": 0,
"eol-last": 2,
"eqeqeq": [2, "allow-null"],
"func-names": 0,
"func-style": 0,
"generator-star-spacing": [2, { "before": true, "after": true }],
"guard-for-in": 0,
"handle-callback-err": [2, "^(err|error)$" ],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"linebreak-style": 0,
"lines-around-comment": 0,
"max-nested-callbacks": 0,
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
"new-parens": 2,
"newline-after-var": 0,
"no-alert": 0,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 0,
"no-cond-assign": 2,
"no-console": 0,
"no-constant-condition": 0,
"no-continue": 0,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 0,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-else-return": 0,
"no-empty": 0,
"no-empty-character-class": 2,
"no-eq-null": 0,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": 0,
"no-extra-semi": 0,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-inline-comments": 0,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 0,
"no-loop-func": 0,
"no-mixed-requires": 0,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multiple-empty-lines": [2, { "max": 1 }],
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-nested-ternary": 0,
"no-new": 2,
"no-new-func": 0,
"no-new-object": 2,
"no-new-require": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-param-reassign": 0,
"no-path-concat": 0,
"no-process-env": 0,
"no-process-exit": 0,
"no-proto": 0,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-modules": 0,
"no-return-assign": 2,
"no-script-url": 0,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": 0,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undef-init": 2,
"no-undefined": 0,
"no-underscore-dangle": 0,
"no-unexpected-multiline": 2,
"no-unneeded-ternary": 2,
"no-unreachable": 2,
"no-unused-expressions": 0,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-use-before-define": 0,
"no-var": 0,
"no-void": 0,
"no-warning-comments": 0,
"no-with": 2,
"object-curly-spacing": 0,
"object-shorthand": 0,
"one-var": [2, { "initialized": "never" }],
"operator-assignment": 0,
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
"padded-blocks": 0,
"prefer-const": 0,
"quote-props": 0,
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"semi": [2, "always"],
"semi-spacing": 0,
"sort-vars": 0,
"keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
"strict": 0,
"use-isnan": 2,
"valid-jsdoc": 0,
"valid-typeof": 2,
"vars-on-top": 0,
"wrap-iife": [2, "any"],
"wrap-regex": 0,
"yoda": [2, "never"]
}
}

View File

@@ -0,0 +1,42 @@
import $ from 'jquery';
import { translations } from 'grav-config';
import request from '../utils/request';
import { Instances as Charts } from './chart';
$('[data-backup][data-ajax*="backup/"]').on('click', function() {
let element = $(this);
let url = element.data('ajax');
const inDropdown = element.closest('.dropdown-menu');
(inDropdown.length ? inDropdown : element)
.closest('.button-group').find('> button:first')
.attr('disabled', 'disabled')
.find('> .fa').removeClass('fa-life-ring').addClass('fa-spin fa-refresh');
request(url, (/* response */) => {
if (Charts && Charts.backups) {
Charts.backups.updateData({ series: [0, 100] });
Charts.backups.element.find('.numeric').html(`0 <em>${translations.PLUGIN_ADMIN.DAYS.toLowerCase()}</em>`);
}
(inDropdown.length ? inDropdown : element)
.closest('.button-group').find('> button:first')
.removeAttr('disabled')
.find('> .fa').removeClass('fa-spin fa-refresh').addClass('fa-life-ring');
});
});
$('[data-backup][data-ajax*="backupDelete"]').on('click', function() {
let element = $(this);
let url = element.data('ajax');
const tr = element.closest('tr');
tr.addClass('deleting');
request(url, (response) => {
if (response.status === 'success') {
tr.remove();
} else {
tr.removeClass('deleting');
}
});
});

View File

@@ -0,0 +1,49 @@
import $ from 'jquery';
import { config } from 'grav-config';
import request from '../utils/request';
const getUrl = (type = '') => {
if (type) {
type = `cleartype:${type}/`;
}
return `${config.base_url_relative}/cache.json/task${config.param_sep}clearCache/${type}admin-nonce${config.param_sep}${config.admin_nonce}`;
};
export default class Cache {
constructor() {
this.element = $('[data-clear-cache]');
$('body').on('click', '[data-clear-cache]', (event) => this.clear(event, event.target));
}
clear(event, element) {
let type = '';
if (event && event.preventDefault) { event.preventDefault(); }
if (typeof event === 'string') { type = event; }
element = element ? $(element) : $(`[data-clear-cache-type="${type}"]`);
type = type || $(element).data('clear-cache-type') || '';
let url = element.data('clearCache') || getUrl(type);
this.disable();
request(url, () => this.enable());
}
enable() {
this.element
.removeAttr('disabled')
.find('> .fa').removeClass('fa-refresh fa-spin fa-retweet').addClass('fa-retweet');
}
disable() {
this.element
.attr('disabled', 'disabled')
.find('> .fa').removeClass('fa-retweet').addClass('fa-refresh fa-spin');
}
}
let Instance = new Cache();
export { Instance };

View File

@@ -0,0 +1,138 @@
import $ from 'jquery';
import chartist from 'chartist';
import { translations } from 'grav-config';
import { Instance as gpm } from '../utils/gpm';
import { Instance as updates } from '../updates';
// let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
export const defaults = {
data: {
series: [100, 0]
},
options: {
Pie: {
donut: true,
donutWidth: 10,
startAngle: 0,
total: 100,
showLabel: false,
height: 150,
// chartPadding: !isFirefox ? 10 : 25 // workaround for older versions of firefox
chartPadding: 5
},
Bar: {
height: 164,
chartPadding: 20, // workaround for older versions of firefox
axisX: {
showGrid: false,
labelOffset: {
x: 0,
y: 0
}
},
axisY: {
offset: 15,
showLabel: true,
showGrid: true,
labelOffset: {
x: 5,
y: 5
},
scaleMinSpace: 25
}
}
}
};
export default class Chart {
constructor(element, options = {}, data = {}) {
this.element = $(element) || [];
if (!this.element[0]) { return; }
let type = (this.element.data('chart-type') || 'pie').toLowerCase();
this.type = type.charAt(0).toUpperCase() + type.substr(1).toLowerCase();
options = Object.assign({}, defaults.options[this.type], options);
data = Object.assign({}, defaults.data, data);
Object.assign(this, {
options,
data
});
this.chart = chartist[this.type](this.element.find('.ct-chart').empty()[0], this.data, this.options);
this.chart.on('created', () => {
this.element.find('.hidden').removeClass('hidden');
// FIX: workaround for chartist issue not allowing HTML in labels anymore
// https://github.com/gionkunz/chartist-js/issues/937
this.element.find('.ct-label').each((index, label) => {
label = $(label);
const text = label.html().replace('&lt;', '<').replace('&gt;', '>');
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;

View File

@@ -0,0 +1,12 @@
import Chart, { UpdatesChart, Instances } from './chart';
import { Instance as Cache } from './cache';
import './backup';
export default {
Chart: {
Chart,
UpdatesChart,
Instances
},
Cache
};

View File

@@ -0,0 +1 @@
// See ../updates/update.js

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
// polyfills
import '@babel/polyfill';
import $ from 'jquery';
import './utils/remodal';
import 'simplebar/dist/simplebar.min.js';
import { UriToMarkdown } from './forms/fields/files.js';
import GPM, { Instance as gpm } from './utils/gpm';
import KeepAlive from './utils/keepalive';
import Updates, { Instance as updates, Notifications, Feed } from './updates';
import Dashboard from './dashboard';
import Pages from './pages';
import Forms from './forms';
import Cookies from './utils/cookies';
import './plugins';
import './themes';
import MediaFilter, { Instance as MediaFilterInstance} from './media';
import toastr from './utils/toastr';
import request from './utils/request';
import './utils/2fa';
import './tools';
import './whitelabel';
// bootstrap jQuery extensions
import './utils/bootstrap-transition';
import './utils/bootstrap-collapse';
import './utils/bootstrap-dropdown';
// tabs memory
import './utils/tabs-memory';
// changelog
import './utils/changelog';
// Main Sidebar
import Sidebar, { Instance as sidebar } from './utils/sidebar';
// starts the keep alive, auto runs every X seconds
KeepAlive.start();
// global event to catch sidebar_state changes
$(global).on('sidebar_state._grav', () => {
Object.keys(Dashboard.Chart.Instances).forEach((chart) => {
setTimeout(() => Dashboard.Chart.Instances[chart].chart.update(), 10);
});
});
export default {
GPM: {
GPM,
Instance: gpm
},
KeepAlive,
Dashboard,
Pages,
Forms,
Updates: {
Updates,
Notifications,
Feed,
Instance: updates
},
Sidebar: {
Sidebar,
Instance: sidebar
},
MediaFilter: {
MediaFilter,
Instance: MediaFilterInstance
},
Scrollbar: { Scrollbar: { deprecated: true }, Instance: { deprecated: true } },
Utils: { request, toastr, Cookies, UriToMarkdown }
};

View File

@@ -0,0 +1,226 @@
import $ from 'jquery';
import { config, uri_params } from 'grav-config';
import request from '../utils/request';
export default class Filter {
constructor() {
this.URI = `${config.base_url_relative}/media-manager/`;
}
filter(name, value) {
let filtered = [];
let keys = Object.keys(uri_params);
if (!~keys.indexOf(name)) { keys.push(name); }
keys.forEach((key) => {
let filter = Filter.cleanValue(key === name ? value : uri_params[key]);
if (filter !== '*') {
filtered.push(`${key}${config.param_sep}${filter}`);
}
});
global.location = this.URI + filtered.join('/');
}
static cleanValue(value) {
return encodeURIComponent(value.replace('/', '\\'));
}
}
export let Instance = new Filter();
var isLoading = false;
var filters = {};
var global_index = 1;
var files_ended = false;
const MEDIA_PAGINATION_INTERVAL = 20;
/* handle changing file type / date filter */
$('body').on('change', '.thumbs-list-container select.filter', (event) => {
let target = $(event.currentTarget);
let filterName = target.data('name');
let filterValue = target.val();
if (filterValue) {
filters[filterName] = filterValue;
} else {
delete filters[filterName];
}
filterFiles();
});
/* initialize media uploader */
if ($('.thumbs-list-container .dropzone')[0]) {
$('.thumbs-list-container .dropzone')[0].dropzone.on('queuecomplete', function() {
let body = {};
if (filters.page) { body.page = filters.page; }
if (filters.date) { body.date = filters.date; }
if (filters.type) { body.type = filters.type; }
$('.dropzone')[0].dropzone.files.forEach(function(file) { file.previewElement.remove(); });
$('.dropzone').first().removeClass('dz-started');
request(`${config.base_url_relative}/media-manager.json/task${config.param_sep}clearMediaCache`, { method: 'post', body }, () => {
filterFiles();
});
});
}
/* handle loading media */
var loadMedia = function loadMedia(filters, callback) {
var url = `${config.base_url_relative}/media.json/tmpl${config.param_sep}media-list-content/index${config.param_sep}${global_index}`;
if (filters.page) {
url += `/page${config.param_sep}${(filters.page).split('/').join('%5C')}`;
}
if (filters.type && filters.type !== '*') {
url += `/type${config.param_sep}${filters.type}`;
}
if (filters.date && filters.date !== '*') {
url += `/date${config.param_sep}${filters.date}`;
}
if (!isLoading) {
isLoading = true;
$('.spinning-wheel').show();
$.get(url, function(content) {
$('.js__files').append(content);
$('.spinning-wheel').hide();
$('.media-container .media-range').trigger('change');
isLoading = false;
global_index++;
callback(content);
});
}
};
var cleanFilesList = function cleanFilesList() {
$('.js__files .card-item').remove();
};
var resetActiveStateInSidebar = function resetActiveStateInSidebar() {
$('.pages-list-container .row').removeClass('active'); // clear active state in sidebar
};
var showEmptyState = function showEmptyState() {
$('.thumbs-list-container').append('<p class="card-item empty-space">No media found</p>');
};
var filterFiles = function filterFiles() {
cleanFilesList();
global_index = 0;
files_ended = false;
$('.empty-space').remove();
loadMedia(filters, function(content) {
if (!content.trim().length) {
showEmptyState();
} else {
if (!filters.page && (!filters.date || filters.date === '*') && (!filters.type || filters.type === '*')) {
$('.js__files').trigger('fillView');
}
}
});
};
/* handle changing page */
$('body').on('click', '.pages-list-container .js__page-link', (event) => {
var page = $(event.target).data('page');
filters['page'] = page;
$('.media-list-title .page-indicator').html(page); // set indication
$('.js__reset-pages-filter').removeClass('hidden'); // activate reset pages icon
resetActiveStateInSidebar();
$(event.target).parents('.row').addClass('active'); // set active state in sidebar
$('.js__file-uploader').removeClass('hidden');
// customize processing URL, as the page changes dynamically
if ($('.dropzone')[0]) {
$('.dropzone')[0].dropzone.on('processing', function(file) {
this.options.url = `${config.base_url_relative}/media-manager${page}.json/task${config.param_sep}addmedia`;
});
}
$('.js__button-clear-media-cache').addClass('hidden');
filterFiles();
disableInfiniteScrolling(); // only infinite scroll on main list, not inside single pages
});
/* handle clearing page filter */
$('body').on('click', '.js__reset-pages-filter', (event) => {
$('.media-list-title .page-indicator').html('All Pages'); // set indication
cleanFilesList();
resetActiveStateInSidebar();
$('.js__reset-pages-filter').addClass('hidden'); // remove reset pages icon
$('.js__file-uploader').addClass('hidden');
$('.js__button-clear-media-cache').removeClass('hidden');
delete filters['page'];
filterFiles();
});
/* handle infinite loading */
var enableInfiniteScrolling = function enableInfiniteScrolling() {
$('.spinning-wheel').hide();
var view = $('.mediapicker-scroll').last();
if (!view.length) { return; }
$(view).on('scroll', function() {
if (($(this).scrollTop() + $(this).innerHeight() + 100) >= $(this)[0].scrollHeight) {
fillView();
}
});
};
var loadNextBatch = function loadNextBatch(callback) {
if (files_ended) {
return;
}
loadMedia({}, function(content) {
if (!$(content).length || ((content.split('card-item').length - 1) < MEDIA_PAGINATION_INTERVAL)) {
files_ended = true;
} else {
if (callback) {
callback();
}
}
$('.media-container .media-range').trigger('input');
});
};
var fillView = function fillView() {
if (!$('.js__files').find('.card-item').last().offset()) {
setTimeout(function() {
// retry later
fillView();
}, 300);
return;
}
if ($('.js__files').find('.card-item').last().offset().top - 1 <= $('.media-container').height()) {
loadNextBatch(function() {
fillView();
});
}
};
/* disable infinite loading */
var disableInfiniteScrolling = function disableInfiniteScrolling() {
$('.spinning-wheel').hide();
$('.content-wrapper').unbind('scroll');
};
$('.js__files').on('fillView', function(event) {
// the first batch got the max number of media files, try loading more
if (($('.js__files')[0].innerHTML.split('card-item').length - 1) === MEDIA_PAGINATION_INTERVAL) {
fillView();
enableInfiniteScrolling();
}
});

View File

@@ -0,0 +1,148 @@
import $ from 'jquery';
import { config, translations } from 'grav-config';
import request from '../utils/request';
import debounce from 'debounce';
import { Instance as pagesTree } from './tree';
import 'selectize';
import '../utils/selectize-required-fix.js';
import '../utils/storage';
/* @formatter:off */
/* eslint-disable */
const options = [
{ flag: translations.PLUGIN_ADMIN.MODULE, key: 'Module', cat: 'mode' },
{ flag: translations.PLUGIN_ADMIN.VISIBLE, key: 'Visible', cat: 'mode' },
{ flag: translations.PLUGIN_ADMIN.ROUTABLE, key: 'Routable', cat: 'mode' },
{ flag: translations.PLUGIN_ADMIN.PUBLISHED, key: 'Published', cat: 'mode' },
{ flag: translations.PLUGIN_ADMIN.NON_MODULE, key: 'NonModule', cat: 'mode' },
{ flag: translations.PLUGIN_ADMIN.NON_VISIBLE, key: 'NonVisible', cat: 'mode' },
{ flag: translations.PLUGIN_ADMIN.NON_ROUTABLE, key: 'NonRoutable', cat: 'mode' },
{ flag: translations.PLUGIN_ADMIN.NON_PUBLISHED, key: 'NonPublished', cat: 'mode' }
];
/* @formatter:on */
/* eslint-enable */
export default class PagesFilter {
constructor(filters, search) {
this.filters = $(filters);
this.search = $(search);
this.options = options;
this.tree = pagesTree;
let storage = JSON.parse(localStorage.getItem('grav:admin:pages:filter') || '{}');
if (!this.filters.length || !this.search.length) { return; }
this.labels = this.filters.data('filter-labels');
this.search.on('input', debounce(() => this.filter(), 250));
this.filters.on('change', () => this.filter());
// restore state
if (storage.flags || storage.query) {
this.setValues(storage);
this.filter();
}
this._initSelectize();
}
filter(value) {
let data = { flags: '', query: '' };
if (typeof value === 'object') {
Object.assign(data, value);
}
if (typeof value === 'string') {
data.query = value;
}
if (typeof value === 'undefined') {
data.flags = this.filters.val();
data.query = this.search.val();
}
if (!Object.keys(data).filter((key) => data[key] !== '').length) {
this.resetValues();
return;
}
data.flags = data.flags.replace(/(\s{1,})?,(\s{1,})?/g, ',');
this.setValues({ flags: data.flags, query: data.query }, 'silent');
request(`${config.base_url_relative}/pages-filter.json/task${config.param_sep}filterPages`, {
method: 'post',
body: data
}, (response) => {
this.refreshDOM(response);
});
}
refreshDOM(response) {
let items = $('[data-nav-id]');
if (!response) {
items.removeClass('search-match').show();
this.tree.restore();
return;
}
items.removeClass('search-match').hide();
response.results.forEach((page) => {
let match = items.filter(`[data-nav-id="${page}"]`).addClass('search-match').show();
match.parents('[data-nav-id]').addClass('search-match').show();
this.tree.expand(page, 'no-store');
});
}
setValues({ flags = '', query = ''}, silent) {
let flagsArray = flags.replace(/(\s{1,})?,(\s{1,})?/g, ',').split(',');
if (this.filters.val() !== flags) {
let selectize = this.filters.data('selectize');
this.filters[selectize ? 'setValue' : 'val'](flagsArray, silent);
}
if (this.search.val() !== query) { this.search.val(query); }
localStorage.setItem('grav:admin:pages:filter', JSON.stringify({ flags, query }));
}
resetValues() {
this.setValues('', 'silent');
this.refreshDOM();
}
_initSelectize() {
let extras = {
type: this.filters.data('filter-types') || {},
access: this.filters.data('filter-access-levels') || {}
};
Object.keys(extras).forEach((cat) => {
Object.keys(extras[cat]).forEach((key) => {
this.options.push({
cat,
key,
flag: extras[cat][key]
});
});
});
this.filters.selectize({
maxItems: null,
valueField: 'key',
labelField: 'flag',
searchField: ['flag', 'key'],
options: this.options,
optgroups: this.labels,
optgroupField: 'cat',
optgroupLabelField: 'name',
optgroupValueField: 'id',
optgroupOrder: this.labels.map((item) => item.id),
plugins: ['optgroup_columns', 'required-fix']
});
}
}
let Instance = new PagesFilter('input[name="page-filter"]', 'input[name="page-search"]');
export { Instance };

View File

@@ -0,0 +1,53 @@
import $ from 'jquery';
import Sortable from 'sortablejs';
import PageFilters, { Instance as PageFiltersInstance } from './filter';
import Page from './page';
const pad = (n, s) => (`000${n}`).substr(-s);
// Pages Ordering
let Ordering = null;
let orderingElement = $('#ordering');
if (orderingElement.length) {
Ordering = new Sortable(orderingElement.get(0), {
filter: '.ignore',
onUpdate: function() {
/* Old single page index behavior
let item = $(event.item);
let index = orderingElement.children().index(item) + 1;
$('[data-order]').val(index);
*/
let indexes = [];
const children = orderingElement.children();
const padZero = (children.length + '').split('').length;
children.each((index, item) => {
item = $(item);
indexes.push(item.data('id'));
item.find('.page-order').text(`${pad(index + 1, padZero)}.`);
});
$('[data-order]').val(indexes.join(','));
}
});
$(document).on('input', '[name="data[folder]"]', (event) => {
const target = $(event.currentTarget);
const activeOrder = $('[data-id][data-active-id]');
activeOrder.data('id', target.val());
Ordering.options.onUpdate();
});
}
export default {
Ordering,
Page,
PageFilters: {
PageFilters,
Instance: PageFiltersInstance
}
};

View File

@@ -0,0 +1,69 @@
import $ from 'jquery';
import '../../utils/jquery-utils';
import request from '../../utils/request';
import { config } from 'grav-config';
let custom = false;
let folder = $('[data-remodal-id="modal"] input[name="data[folder]"], [data-remodal-id="module"] input[name="data[folder]"], [data-remodal-id="modal-page-copy"] input[name="data[folder]"]');
let title = $('[data-remodal-id="modal"] input[name="data[title]"], [data-remodal-id="module"] input[name="data[title]"], [data-remodal-id="modal-page-copy"] input[name="data[title]"]');
let getFields = (type, target) => {
target = $(target);
let query = `[data-remodal-id="${target.closest('[data-remodal-id]').data('remodal-id')}"]`;
return {
title: type === 'title' ? $(target) : $(`${query} input[name="data[title]"]`),
folder: type === 'folder' ? $(target) : $(`${query} input[name="data[folder]"]`)
};
};
title.on('input focus blur', (event) => {
if (custom) { return true; }
let elements = getFields('title', event.currentTarget);
let slug = $.slugify(elements.title.val(), {custom: { "'": '', '': '', '': '' }});
elements.folder.val(slug);
});
folder.on('input', (event) => {
let elements = getFields('folder', event.currentTarget);
let input = elements.folder.get(0);
let value = elements.folder.val();
let selection = {
start: input.selectionStart,
end: input.selectionEnd
};
value = value.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
elements.folder.val(value);
custom = !!value;
// restore cursor position
input.setSelectionRange(selection.start, selection.end);
});
folder.on('focus blur', (event) => {
getFields('title').title.trigger('input');
});
$(document).on('change', '[name="data[route]"]', (event) => {
const rawroute = $(event.currentTarget).val();
const pageTemplate = $('[name="data[name]"]');
const URI = `${config.base_url_relative}/ajax.json/task${config.param_sep}getChildTypes`;
if (pageTemplate.length === 0) {
return;
}
request(URI, {
method: 'post',
body: { rawroute }
}, (response) => {
const type = response.child_type;
if (type !== '' && type !== 'default') {
pageTemplate.val(type);
pageTemplate.data('selectize').setValue(type);
}
});
});

View File

@@ -0,0 +1,15 @@
import $ from 'jquery';
$(document).on('click', '[data-remodal-target="delete"]', function() {
let confirm = $('[data-remodal-id="delete"] [data-delete-action]');
let link = $(this).data('delete-url');
confirm.data('delete-action', link);
});
$(document).on('click', '[data-delete-action]', function() {
let remodal = $.remodal.lookup[$('[data-remodal-id="delete"]').data('remodal')];
global.location.href = $(this).data('delete-action');
remodal.close();
});

View File

@@ -0,0 +1,5 @@
import $ from 'jquery';
$('.disable-after-click').on('click', function() {
$(this).addClass('pointer-events-disabled');
});

View File

@@ -0,0 +1,47 @@
import $ from 'jquery';
import './add';
import './move';
import './delete';
import './unset';
import './disable-buttons';
import PageMedia, { Instance as PageMediaInstances } from './media';
import './multilang';
const switcher = $('input[type="radio"][name="mode-switch"]');
if (switcher) {
let link = switcher.closest(':checked').data('leave-url');
let fakeLink = $(`<a href="${link}" />`);
switcher.parent().append(fakeLink);
switcher.siblings('label').on('mousedown touchdown', (event) => {
event.preventDefault();
// let remodal = $.remodal.lookup[$('[data-remodal-id="changes"]').data('remodal')];
let confirm = $('[data-remodal-id="changes"] [data-leave-action="continue"]');
confirm.one('click', () => {
$(global).on('beforeunload._grav');
fakeLink.off('click._grav');
$(event.target).trigger('click');
});
fakeLink.trigger('click._grav');
});
switcher.on('change', (event) => {
let radio = $(event.target);
link = radio.data('leave-url');
setTimeout(() => fakeLink.attr('href', link).get(0).click(), 5);
});
}
export default {
Media: {
PageMedia,
PageMediaInstances
}
};

View File

@@ -0,0 +1,293 @@
import $ from 'jquery';
import Cookies from '../../utils/cookies.js';
import request from '../../utils/request';
import FilesField, { UriToMarkdown } from '../../forms/fields/files';
import { config, translations } from 'grav-config';
import { Instance as Editor } from '../../forms/fields/editor';
import Sortable from 'sortablejs';
const previewTemplate = `
<div class="dz-preview dz-file-preview">
<div class="dz-details">
<div class="dz-filename"><span data-dz-name></span></div>
<div class="dz-size" data-dz-size></div>
<img data-dz-thumbnail />
</div>
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
<div class="dz-success-mark"><span>✔</span></div>
<div class="dz-error-mark"><span>✘</span></div>
<div class="dz-error-message"><span data-dz-errormessage></span></div>
<a class="dz-remove" title="${translations.PLUGIN_ADMIN.DELETE}" href="javascript:undefined;" data-dz-remove>${translations.PLUGIN_ADMIN.DELETE}</a>
<a class="dz-metadata" title="${translations.PLUGIN_ADMIN.METADATA}" href="#" target="_blank" data-dz-metadata>${translations.PLUGIN_ADMIN.METADATA}</a>
<a class="dz-view" title="${translations.PLUGIN_ADMIN.VIEW}" href="#" target="_blank" data-dz-view>${translations.PLUGIN_ADMIN.VIEW}</a>
<a class="dz-insert" title="${translations.PLUGIN_ADMIN.INSERT}" href="javascript:undefined;" data-dz-insert>${translations.PLUGIN_ADMIN.INSERT}</a>
</div>`.trim();
export default class PageMedia extends FilesField {
constructor({ container = '#grav-dropzone', options = {} } = {}) {
options = Object.assign(options, { previewTemplate });
super({ container, options });
if (!this.container.length) { return; }
this.urls = {
fetch: `${this.container.data('media-url')}/task${config.param_sep}listmedia`,
add: `${this.container.data('media-url')}/task${config.param_sep}addmedia`,
delete: `${this.container.data('media-url')}/task${config.param_sep}delmedia`
};
this.dropzone.options.url = this.urls.add;
if (typeof this.options.fetchMedia === 'undefined' || this.options.fetchMedia) {
this.fetchMedia();
}
if (typeof this.options.attachDragDrop === 'undefined' || this.options.attachDragDrop) {
this.attachDragDrop();
}
const field = $(`[name="${this.container.data('dropzone-field')}"]`);
if (field.length) {
this.sortable = new Sortable(this.container.get(0), {
animation: 150,
// forceFallback: true,
setData: (dataTransfer, target) => {
target = $(target);
let uri = encodeURI(target.find('.dz-filename').text());
let shortcode = UriToMarkdown(uri);
this.dropzone.disable();
target.addClass('hide-backface');
dataTransfer.effectAllowed = 'copy';
dataTransfer.setData('text', shortcode);
},
onSort: () => {
let names = [];
this.container.find('[data-dz-name]').each((index, file) => {
file = $(file);
const name = file.text().trim();
names.push(name);
});
field.val(names.join(','));
}
});
}
}
fetchMedia() {
const order = this.container.closest('.form-field').find('[name="data[header][media_order]"]').val();
const body = { uri: this.getURI(), order };
let url = this.urls.fetch;
request(url, { method: 'post', body }, (response) => {
let results = response.results;
Object.keys(results).forEach((name) => {
let data = results[name];
let mock = { name, size: data.size, accepted: true, extras: data };
this.dropzone.files.push(mock);
this.dropzone.options.addedfile.call(this.dropzone, mock);
this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
});
this.updateThumbsSize();
this.container.find('.dz-preview').prop('draggable', 'true');
});
}
onDropzoneSending(file, xhr, formData) {
/*
// Cannot call super because Safari and IE API don't implement `delete`
super.onDropzoneSending(file, xhr, formData);
formData.delete('task');
*/
formData.append('name', this.options.dotNotation || file.name);
formData.append('admin-nonce', config.admin_nonce);
formData.append('uri', this.getURI());
}
onDropzoneComplete(file) {
super.onDropzoneComplete(file);
if (this.sortable) {
this.sortable.options.onSort();
}
// accepted
this.updateThumbsSize();
this.updateMediaCount();
$('.dz-preview').prop('draggable', 'true');
}
onDropzoneAddedFile(file, ...extra) {
super.onDropzoneAddedFile(file, extra);
this.updateThumbsSize();
}
onDropzoneRemovedFile(file, ...extra) {
super.onDropzoneRemovedFile(file, ...extra);
this.updateMediaCount();
if (this.sortable) {
this.sortable.options.onSort();
}
}
updateThumbsSize() {
const status = JSON.parse(Cookies.get('grav-admin-pagemedia') || '{}');
if (status.width) {
const input = this.container.closest('.pagemedia-field').find('.media-resizer');
updateMediaSizes(input[0], status.width, false);
}
}
updateMediaCount() {
const element = this.container.closest('.pagemedia-field').find('[data-pagemedia-count]');
element.text(`(${this.dropzone.files.length})`);
}
attachDragDrop() {
this.container.delegate('[data-dz-insert]', 'click', (e) => {
let target = $(e.currentTarget).parent('.dz-preview').find('.dz-filename');
let editor = Editor.editors.filter((index, editor) => $(editor).attr('name') === 'data[content]');
if (editor.length) {
editor = editor.data('codemirror');
editor.focus();
let filename = encodeURI(target.text());
let shortcode = UriToMarkdown(filename);
editor.doc.replaceSelection(shortcode);
}
});
this.container.delegate('[data-dz-view]', 'mouseenter', (e) => {
let target = $(e.currentTarget);
let file = target.parent('.dz-preview').find('.dz-filename');
let filename = encodeURI(file.text());
let URL = target.closest('[data-media-path]').data('media-path');
let original = this.dropzone.files.filter((file) => encodeURI(file.name) === filename).shift();
original = original && ((original.extras && original.extras.original) || encodeURI(original.name));
target.attr('href', `${URL}/${original}`);
});
this.container.delegate('[data-dz-metadata]', 'click', (e) => {
e.preventDefault();
const target = $(e.currentTarget);
const file = target.parent('.dz-preview').find('.dz-filename');
const filename = encodeURI(file.text());
const cleanName = file.text().replace('<', '&lt;').replace('>', '&gt;');
let fileObj = this.dropzone.files.filter((file) => file.name === global.decodeURI(filename)).shift() || {};
if (!fileObj.extras) {
fileObj.extras = { metadata: [] };
}
if (Array.isArray(fileObj.extras.metadata) && !fileObj.extras.metadata.length) {
fileObj.extras.metadata = { '': `${cleanName}.meta.yaml doesn't exist` };
}
fileObj = fileObj.extras;
const modal_element = $('body').find('[data-remodal-id="metadata"]');
const modal = $.remodal.lookup[modal_element.data('remodal')];
modal_element.find('h1 strong').html(cleanName);
if (fileObj.url) {
modal_element.find('.meta-preview').html(`<img src="${fileObj.url}" />`);
}
const container = modal_element.find('.meta-content').html('<ul />').find('ul');
Object.keys(fileObj.metadata).forEach((meta) => {
const cleanMeta = fileObj.metadata[meta].replace('<', '&lt;').replace('>', '&gt;');
container.append(`<li><strong>${meta ? meta + ':' : ''}</strong> ${cleanMeta}</li>`);
});
modal.open();
});
this.container.delegate('.dz-preview', 'dragstart', (e) => {
let target = $(e.currentTarget);
let uri = encodeURI(target.find('.dz-filename').text());
let shortcode = UriToMarkdown(uri);
this.dropzone.disable();
target.addClass('hide-backface');
e.originalEvent.dataTransfer.effectAllowed = 'copy';
e.originalEvent.dataTransfer.setData('text', shortcode);
});
this.container.delegate('.dz-preview', 'dragend', (e) => {
let target = $(e.currentTarget);
this.dropzone.enable();
target.removeClass('hide-backface');
});
}
}
export const updateMediaSizes = (input, width, store = true) => {
const storageLocation = input.dataset.storageLocation || 'grav-admin-pagemedia';
const status = JSON.parse(Cookies.get(storageLocation) || '{}');
const height = 150 * width / 200;
const media = $(input).closest('.pagemedia-field').find('.dz-details, [data-dz-thumbnail]');
media.css({ width, height });
if (store) {
const data = Object.assign({}, status, { width });
Cookies.set(storageLocation, JSON.stringify(data), { expires: Infinity });
}
};
export const updateMediaCollapseStatus = (element, store = true) => {
const storageLocation = element.dataset.storageLocation || 'grav-admin-pagemedia';
const status = JSON.parse(Cookies.get(storageLocation) || '{}');
element = $(element);
const icon = element.find('i.fa');
const container = element.closest('.pagemedia-field');
const panel = container.find('.form-data');
const slider = container.find('.media-resizer').parent();
const isCollapsed = !icon.hasClass('fa-chevron-down');
const collapsed = !isCollapsed;
icon.removeClass('fa-chevron-down fa-chevron-right').addClass(isCollapsed ? 'fa-chevron-down' : 'fa-chevron-right');
slider[isCollapsed ? 'removeClass' : 'addClass']('hidden');
panel[isCollapsed ? 'slideDown' : 'slideUp']();
if (store) {
const data = Object.assign({}, status, { collapsed });
Cookies.set(storageLocation, JSON.stringify(data), { expires: Infinity });
}
};
$(document).on('input', '.media-resizer', (event) => {
const target = $(event.currentTarget);
const width = target.val();
updateMediaSizes(event.currentTarget, width);
});
$(document).on('click', '.media-collapser', (event) => {
updateMediaCollapseStatus(event.currentTarget);
});
$(document).ready(() => {
$('.media-resizer').each((index, input) => {
const storageLocation = input.dataset.storageLocation || 'grav-admin-pagemedia';
const status = JSON.parse(Cookies.get(storageLocation) || '{}');
if (status.width) {
updateMediaSizes(input, status.width, false);
}
});
});
export let Instance = new PageMedia();

View File

@@ -0,0 +1,63 @@
import $ from 'jquery';
$(document).on('click', '[data-page-move] button[name="task"][value="save"]', (event) => {
/* let route = $('form#blueprints:first select[name="data[route]"]');
let moveTo = $('[data-page-move] select').val();
if (route.length && route.val() !== moveTo) {
let selectize = route.data('selectize');
route.val(moveTo);
if (selectize) selectize.setValue(moveTo);
}*/
const modal = $(event.currentTarget).closest('[data-remodal-id]');
const parents = modal.data('parents') || {};
const finder = parents.finder;
if (!parents || !finder) { return true; }
const field = parents.field;
const parentLabel = parents.parentLabel;
const parentName = parents.parentName;
const selection = finder.findLastActive().item[0];
const value = selection._item[finder.config.valueKey];
const name = selection._item[finder.config.labelKey];
field.val(value);
parentLabel.text(value);
parentName.text(name);
finder.config.defaultPath = value;
$('<div />').css({
backgroundColor: 'rgba(255, 255, 255, 0.1)',
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
zIndex: 15000
}).appendTo($('body'));
});
/*
$(document).on('click', '[data-remodal-id="parents"] [data-parents-select]', (event) => {
const modal = $(event.currentTarget).closest('[data-remodal-id]');
const parents = modal.data('parents');
const finder = parents.finder;
const field = parents.field;
const parentLabel = parents.parentLabel;
const parentName = parents.parentName;
const selection = finder.findLastActive().item[0];
const value = selection._item[finder.config.valueKey];
const name = selection._item[finder.config.labelKey];
field.val(value);
parentLabel.text(value);
parentName.text(name);
finder.config.defaultPath = value;
const remodal = $.remodal.lookup[$(`[data-remodal-id="${modal.data('remodalId')}"]`).data('remodal')];
remodal.close();
});
*/

View File

@@ -0,0 +1,22 @@
import $ from 'jquery';
$('[name="task"][value="saveas"], [name="task"][value="switchlanguage"]').on('mousedown touchstart', (event) => {
let fields = ['lang', 'redirect'];
let element = $(event.currentTarget);
let form = $(`#${element.attr('form')}`);
if (!form.length) { return; }
fields.forEach((field) => {
let value = element.attr(field);
if (!value) { return; }
let input = form.find(`[name="data[${field}]"]`);
if (!input.length) {
input = $(`<input type="hidden" name="data[${field}]" value="" />`);
form.append(input);
}
input.val(value);
});
return true;
});

View File

@@ -0,0 +1,18 @@
import $ from 'jquery';
$(document).on('click', '.dz-unset', function() {
const file_upload = $(this).closest('.files-upload');
$(this).closest('.dz-image-preview').remove();
const unset_image = $(this).closest('.dz-image-preview').find('[data-dz-name]').text().trim();
const images = JSON.parse(file_upload.find('input[data-grav-field="hidden"]').val()) || {};
let image_array = {};
$.each(images, function(ind, obj) {
if (!ind.endsWith(unset_image)) {
image_array[ind] = obj;
}
});
file_upload.find('input[data-grav-field="hidden"]').val(JSON.stringify(image_array));
});

View File

@@ -0,0 +1,125 @@
import $ from 'jquery';
import '../utils/storage';
const sessionKey = 'grav:admin:pages';
if (!sessionStorage.getItem(sessionKey)) {
sessionStorage.setItem(sessionKey, '{}');
}
export default class PagesTree {
constructor(query, elements = undefined) {
this.query = query;
this.elements = $(elements !== undefined ? elements : this.query);
this.session = JSON.parse(sessionStorage.getItem(sessionKey) || '{}');
if (!this.elements.length) { return; }
this.restore();
this.elements.find('.page-icon').on('click', (event) => this.toggle(event.target));
this.elements.data('tree_init', 1);
$('[data-page-toggleall]').on('click', (event) => {
let element = $(event.target).closest('[data-page-toggleall]');
let action = element.data('page-toggleall');
this[action]();
});
}
reload() {
const elements = $(this.query).filter((index, element) => !$(element).data('tree_init'));
if (!elements.length) { return; }
this.constructor(this.query, elements);
}
toggle(elements, dontStore = false) {
if (typeof elements === 'string') {
elements = $(`[data-nav-id="${elements}"]`).find('[data-toggle="children"]');
}
elements = $(elements || this.elements);
elements.each((index, element) => {
element = $(element);
let state = this.getState(element.closest('[data-toggle="children"]'));
this[state.isOpen ? 'collapse' : 'expand'](state.id, dontStore);
});
}
collapse(elements, dontStore = false) {
if (typeof elements === 'string') {
elements = $(`[data-nav-id="${elements}"]`).find('[data-toggle="children"]');
}
elements = $(elements || this.elements);
elements.each((index, element) => {
element = $(element);
let state = this.getState(element);
if (state.isOpen) {
state.children.hide();
state.icon.removeClass('children-open').addClass('children-closed');
if (!dontStore) { delete this.session[state.id]; }
}
});
if (!dontStore) { this.save(); }
}
expand(elements, dontStore = false) {
if (typeof elements === 'string') {
let element = $(`[data-nav-id="${elements}"]`);
let parents = element.parents('[data-nav-id]');
// loop back through parents, we don't want to expand an hidden child
if (parents.length) {
parents = parents.find('[data-toggle="children"]:first');
parents = parents.add(element.find('[data-toggle="children"]:first'));
return this.expand(parents, dontStore);
}
elements = element.find('[data-toggle="children"]:first');
}
elements = $(elements || this.elements);
elements.each((index, element) => {
element = $(element);
let state = this.getState(element);
if (!state.isOpen) {
state.children.show();
state.icon.removeClass('children-closed').addClass('children-open');
if (!dontStore) { this.session[state.id] = 1; }
}
});
if (!dontStore) { this.save(); }
}
restore() {
this.collapse(null, true);
Object.keys(this.session).forEach((key) => {
this.expand(key, 'no-store');
});
}
save() {
return sessionStorage.setItem(sessionKey, JSON.stringify(this.session));
}
getState(element) {
element = $(element);
return {
id: element.closest('[data-nav-id]').data('nav-id'),
children: element.closest('li.page-item').find('ul:first'),
icon: element.find('.page-icon'),
get isOpen() { return this.icon.hasClass('children-open'); }
};
}
}
let Instance = new PagesTree('[data-toggle="children"]');
export { Instance };

View File

@@ -0,0 +1,92 @@
import $ from 'jquery';
import packages from '../utils/packages';
import camelCase from 'mout/string/camelCase';
import debounce from 'debounce';
import contains from 'mout/string/contains';
// Plugins sliders details
$('.gpm-name, .gpm-actions').on('click', function(e) {
let element = $(this);
let target = $(e.target);
let tag = target.prop('tagName').toLowerCase();
if (tag === 'a' || element.parent('a').length || target.parent('a').length) { return true; }
let wrapper = element.siblings('.gpm-details').find('.table-wrapper');
wrapper.slideToggle({
duration: 350,
complete: () => {
let visible = wrapper.is(':visible');
wrapper
.closest('tr')
.find('.gpm-details-expand i')
.removeClass('fa-chevron-' + (visible ? 'down' : 'up'))
.addClass('fa-chevron-' + (visible ? 'up' : 'down'));
}
});
});
// Removing plugin
$(document).on('click', '[data-plugin-action="remove-package"]', (event) => {
packages.handleRemovingPackage('plugin', event);
});
// Reinstall plugin
$(document).on('click', '[data-plugin-action="reinstall-package"]', (event) => {
packages.handleReinstallPackage('plugin', event);
});
$(document).on('click', '[data-plugin-action="remove-dependency-package"]', (event) => {
packages.handleRemovingDependency('plugin', event);
});
// Trigger the add new plugin / update plugin modal
$(document).on('click', '[data-plugin-action="start-package-installation"]', (event) => {
packages.handleGettingPackageDependencies('plugin', event, 'install');
});
// Trigger the update all plugins modal
$(document).on('click', '[data-plugin-action="start-packages-update"]', (event) => {
packages.handleGettingPackageDependencies('plugin', event);
});
// Install a plugin dependencies and the plugin
$(document).on('click', '[data-plugin-action="install-dependencies-and-package"]', (event) => {
packages.handleInstallingDependenciesAndPackage('plugin', event);
});
// Install a plugin
$(document).on('click', '[data-plugin-action="install-package"]', (event) => {
packages.handleInstallingPackage('plugin', event);
});
// Sort plugins/themes dropdown
$(document).on('change', '.sort-actions select', (event) => {
let direction = $('.sort-actions .sort-icon .fa').hasClass('fa-sort-amount-desc') ? 'desc' : 'asc';
let sorting = $(event.currentTarget).val();
packages.Sort[camelCase(`by-${sorting}`)](direction);
});
// Sort plugins/themes icon
$(document).on('click', '.sort-icon', (event) => {
let icon = $(event.currentTarget).find('.fa');
let current = icon.hasClass('fa-sort-amount-asc') ? 'asc' : 'desc';
let opposite = current === 'asc' ? 'desc' : 'asc';
icon.removeClass(`fa-sort-amount-${current}`).addClass(`fa-sort-amount-${opposite}`);
$('.sort-actions select').trigger('change');
});
// Filter plugin/theme
$(document).on('input', '[data-gpm-filter]', debounce((event) => {
let value = $($(event.currentTarget)).val();
let items = $('[data-gpm-plugin], [data-gpm-theme]');
items.hide().filter((index, item) => {
item = $(item);
return contains(item.data('gpm-plugin'), value) || contains(item.data('gpm-theme'), value) || contains(item.data('gpm-name').toLowerCase(), value.toLowerCase());
}).show();
}, 250));

View File

@@ -0,0 +1,46 @@
import $ from 'jquery';
import packages from '../utils/packages';
// Themes Switcher Warning
$(document).on('mousedown', '[data-remodal-target="theme-switch-warn"]', (event) => {
let name = $(event.target).closest('[data-gpm-theme]').find('.gpm-name a:first').text();
let remodal = $('.remodal.theme-switcher');
remodal.find('strong').text(name);
remodal.find('.button.continue').attr('href', $(event.target).attr('href'));
});
// Removing theme
$(document).on('click', '[data-theme-action="remove-package"]', (event) => {
packages.handleRemovingPackage('theme', event);
});
// Reinstall theme
$(document).on('click', '[data-theme-action="reinstall-package"]', (event) => {
packages.handleReinstallPackage('theme', event);
});
$(document).on('click', '[data-theme-action="remove-dependency-package"]', (event) => {
packages.handleRemovingDependency('theme', event);
});
// Opened the add new theme / update theme modal
$(document).on('click', '[data-theme-action="start-package-installation"]', (event) => {
packages.handleGettingPackageDependencies('theme', event, 'install');
});
// Trigger the update all themes modal
$(document).on('click', '[data-theme-action="start-packages-update"]', (event) => {
packages.handleGettingPackageDependencies('theme', event);
});
// Install a theme dependencies and the theme
$(document).on('click', '[data-theme-action="install-dependencies-and-package"]', (event) => {
packages.handleInstallingDependenciesAndPackage('theme', event);
});
// Install a theme
$(document).on('click', '[data-theme-action="install-package"]', (event) => {
packages.handleInstallingPackage('theme', event);
});

View File

@@ -0,0 +1 @@
import './logs';

View File

@@ -0,0 +1,14 @@
import $ from 'jquery';
import { setParam } from 'mout/queryString';
const prepareQuery = (key, value) => {
return setParam(global.location.href, key, value);
};
$(document).on('change', '.logs-content .block-select select[name]', (event) => {
const target = $(event.currentTarget);
const name = target.attr('name');
const value = target.val();
global.location.href = prepareQuery(name, value);
});

View File

@@ -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();
}
});
});
}

View File

@@ -0,0 +1,26 @@
import $ from 'jquery';
import { Instance as gpm } from '../utils/gpm';
import { translations } from 'grav-config';
import toastr from '../utils/toastr';
// Check for updates trigger
$('[data-gpm-checkupdates]').on('click', function() {
let element = $(this);
element.find('i').addClass('fa-spin');
gpm.fetch((response) => {
element.find('i').removeClass('fa-spin');
let payload = response.payload;
if (!payload) { return; }
if (!payload.grav.isUpdatable && !payload.resources.total) {
toastr.success(translations.PLUGIN_ADMIN.EVERYTHING_UP_TO_DATE);
} else {
var grav = payload.grav.isUpdatable ? 'Grav v' + payload.grav.available : '';
var resources = payload.resources.total ? payload.resources.total + ' ' + translations.PLUGIN_ADMIN.UPDATES_ARE_AVAILABLE : '';
if (!resources) { grav += ' ' + translations.PLUGIN_ADMIN.IS_AVAILABLE_FOR_UPDATE; }
toastr.info(grav + (grav && resources ? ' ' + translations.PLUGIN_ADMIN.AND + ' ' : '') + resources);
}
}, true);
});

View File

@@ -0,0 +1,68 @@
import $ from 'jquery';
import { config } from 'grav-config';
import request from '../utils/request';
const URI = `${config.base_url_relative}/ajax.json/task${config.param_sep}getNewsFeed`;
class Feed {
constructor() {
this.data = null;
}
fetch(refresh = false, callback = function() {}) {
request(URI, {
method: 'post',
body: { refresh }
}, (response) => {
this.data = response;
callback(response);
});
}
refresh(refresh = false) {
const feed = $('#news-feed .widget-content');
if (!feed.length) { return; }
let loader = feed.find('.widget-loader');
loader.find('div').remove();
loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
loader.show();
feed.find('> ul').hide();
if (!this.data || this.data.error || refresh) {
this.fetch(refresh, this.updateContent.bind(this));
} else {
this.updateContent();
}
}
updateContent() {
const feed = $('#news-feed .widget-content');
if (!feed.length) { return; }
let loader = feed.find('.widget-loader').hide();
let content = feed.find('> ul').empty().show();
if (this.data.error || this.data.status === 'error') {
loader.show().find('div').remove();
loader.find('.fa-refresh').removeClass('fa-refresh fa-spin').addClass('fa-warning');
loader.append(`<div>${this.data.error ? this.data.error.message : this.data.message || 'Unable to download news feed'}</div>`);
return;
}
if (this.data && this.data.feed_data) {
content.append(this.data.feed_data);
}
}
}
let feed = new Feed();
$(document).ready(() => feed.refresh());
$(document).on('click', '[data-refresh="feed"]', (event) => {
event.preventDefault();
feed.refresh(true);
});
export default feed;

View File

@@ -0,0 +1,196 @@
import $ from 'jquery';
import unique from 'mout/array/unique';
import { config, translations } from 'grav-config';
import { Instance as gpm } from '../utils/gpm';
import Notifications from './notifications';
import Feed from './feed';
import './check';
import './update';
import './channel-switcher';
export default class Updates {
constructor(payload = {}) {
this.setPayload(payload);
this.task = `task${config.param_sep}`;
this.updateURL = '';
}
setPayload(payload = {}) {
this.payload = payload;
return this;
}
fetch(force = false) {
gpm.fetch((response) => this.setPayload(response), force);
return this;
}
maintenance(mode = 'hide') {
let element = $('#updates [data-update-packages]');
element[mode === 'show' ? 'fadeIn' : 'fadeOut']();
if (mode === 'hide') {
$('.badges.with-updates').removeClass('with-updates').find('.badge.updates').remove();
}
return this;
}
grav() {
let payload = this.payload.grav;
if (payload && payload.isUpdatable) {
let task = this.task;
let bar = '';
if (!payload.isSymlink) {
this.updateURL = `${config.base_url_relative}/update.json/${task}updategrav/admin-nonce${config.param_sep}${config.admin_nonce}`;
bar += `<button data-remodal-target="update-grav" class="button button-small secondary pointer-events-none" id="grav-update-button">${translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW} <span class="cnt-down">(5s)</span></button>`;
} else {
bar += `<span class="hint--left" style="float: right;" data-hint="${translations.PLUGIN_ADMIN.GRAV_SYMBOLICALLY_LINKED}"><i class="fa fa-fw fa-link"></i></span>`;
}
bar += `
Grav <b>v${payload.available}</b> ${translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE}! <span class="less">(${translations.PLUGIN_ADMIN.CURRENT} v${payload.version})</span>
`;
let element = $('[data-gpm-grav]').removeClass('hidden');
if (element.is(':empty')) {
element.hide();
}
element
.addClass('grav')
.html(`${bar}`)
.slideDown(150, function() {
var c = 5;
var x = setInterval(function() {
c -= 1;
element.find('.pointer-events-none .cnt-down').text('(' + c + 's)');
}, 1000);
setTimeout(function() {
clearInterval(x);
element.find('.pointer-events-none .cnt-down').remove();
element.find('.pointer-events-none').removeClass('pointer-events-none');
}, 5000);
})
.parent('#messages').addClass('default-box-shadow');
}
return this;
}
resources() {
if (!this.payload || !this.payload.resources || !this.payload.resources.total) {
return this.maintenance('hide');
}
let is_current_package_latest = true;
let map = ['plugins', 'themes'];
let singles = ['plugin', 'theme'];
let { plugins, themes } = this.payload.resources;
if (!this.payload.resources.total) { return this; }
[plugins, themes].forEach(function(resources, index) {
if (!resources || Array.isArray(resources)) { return; }
let length = Object.keys(resources).length;
let type = map[index];
// sidebar
$(`#admin-menu a[href$="/${map[index]}"]`)
.find('.badges')
.addClass('with-updates')
.find('.badge.updates').text(length);
var type_translation = '';
// update all
if (type === 'plugins') {
type_translation = translations.PLUGIN_ADMIN.PLUGINS;
} else {
type_translation = translations.PLUGIN_ADMIN.THEMES;
}
let updateAll = $(`.grav-update.${type}`);
updateAll.css('display', 'block').html(`
<p>
<a href="#" class="button button-small secondary" data-remodal-target="update-packages" data-packages-slugs="${Object.keys(resources).join()}" data-${singles[index]}-action="start-packages-update">${translations.PLUGIN_ADMIN.UPDATE} ${translations.PLUGIN_ADMIN.ALL} ${type_translation}</a>
<i class="fa fa-bullhorn"></i>
${length} ${translations.PLUGIN_ADMIN.OF_YOUR} ${type_translation.toLowerCase()} ${translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE}
</p>
`);
let existing_slugs = $('[data-update-packages]').attr('data-packages-slugs') || '';
if (existing_slugs) {
existing_slugs = existing_slugs.split(',');
} else {
existing_slugs = [];
}
let slugs = unique(existing_slugs.concat(Object.keys(resources))).join();
$('[data-update-packages]').attr('data-packages-slugs', `${slugs}`);
Object.keys(resources).forEach(function(item) {
// listing page
let container = $(`[data-gpm-${singles[index]}="${item}"]`);
let element = container.find('.gpm-name');
let url = element.find('a');
let content_wrapper = container.parents('.content-wrapper');
if (type === 'plugins' && !element.find('.badge.update').length) {
element.append(`<a class="plugin-update-button" href="${url.attr('href')}"><span class="badge update">${translations.PLUGIN_ADMIN.UPDATE_AVAILABLE}!</span></a>`);
content_wrapper.addClass('has-updates');
} else if (type === 'themes') {
element.append(`<div class="gpm-ribbon"><a href="${url.attr('href')}">${translations.PLUGIN_ADMIN.UPDATE.toUpperCase()}</a></div>`);
content_wrapper.addClass('has-updates');
}
// details page
if (container.length) {
let details = $(`.grav-update.${singles[index]}`);
if (details.length) {
let releaseType = resources[item].type === 'testing' ? '<span class="gpm-testing">test release</span>' : '';
details.html(`
<p>
<a href="#" class="button button-small secondary" data-remodal-target="update-packages" data-packages-slugs="${item}" data-${singles[index]}-action="start-package-installation">${translations.PLUGIN_ADMIN.UPDATE} ${singles[index].charAt(0).toUpperCase() + singles[index].substr(1).toLowerCase()}</a>
<i class="fa fa-bullhorn"></i>
<strong>v${resources[item].available}</strong> ${releaseType} ${translations.PLUGIN_ADMIN.OF_THIS} ${singles[index]} ${translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE}!
</p>
`).css('display', 'block');
is_current_package_latest = false;
}
}
});
$('[data-update-packages]').removeClass('hidden');
});
$('.content-wrapper').addClass('updates-checked');
if (!is_current_package_latest) {
$('.warning-reinstall-not-latest-release').removeClass('hidden');
}
}
}
let Instance = new Updates();
export { Instance, Notifications, Feed };
// automatically refresh UI for updates (graph, sidebar, plugin/themes pages) after every fetch
gpm.on('fetched', (response, raw) => {
Instance.setPayload(response.payload || {});
Instance.grav().resources();
});
if (config.enable_auto_updates_check === '1') {
gpm.fetch();
}

View File

@@ -0,0 +1,169 @@
import $ from 'jquery';
import { config } from 'grav-config';
import request from '../utils/request';
const canFetchNotifications = () => config.notifications.enabled;
const notificationsFilters = () => config.notifications.filters;
class Notifications {
static addShowAllInFeed() {
$('#notifications ul').append('<li class="show-all" data-notification-action="show-all-notifications">Show all</li>');
}
static showNotificationInFeed(notification) {
let notifications = $('#notifications').removeClass('hidden');
let loader = notifications.find('.widget-loader').hide();
let content = notifications.find('.widget-content > ul').show();
loader.find('div').remove();
loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
content
.append(notification)
.find('li:nth-child(n+11)').addClass('hidden'); // hide all items > 10
if (content.find('li.hidden').length) {
Notifications.addShowAllInFeed();
}
}
static showNotificationInTop(notification) {
const container = $('.top-notifications-container');
const dummy = $('<div />').html(notification);
container.removeClass('hidden').append(dummy.children());
dummy.children().slideDown(150);
}
static showNotificationInDashboard(notification) {
const container = $('.dashboard-notifications-container');
const dummy = $('<div />').html(notification);
container.removeClass('hidden').append(dummy.children());
dummy.children().slideDown(150);
}
static showNotificationInPlugins(notification) {
const container = $('.plugins-notifications-container');
const dummy = $('<div />').html(notification);
container.removeClass('hidden').append(dummy.children());
dummy.children().slideDown(150);
}
static showNotificationInThemes(notification) {
const container = $('.themes-notifications-container');
const dummy = $('<div />').html(notification);
container.removeClass('hidden').append(dummy.children());
dummy.children().slideDown(150);
}
static processLocation(location, notification) {
switch (location) {
case 'feed':
Notifications.showNotificationInFeed(notification);
break;
case 'top':
if (!notification.read) {
Notifications.showNotificationInTop(notification);
}
break;
case 'dashboard':
if (!notification.read) {
Notifications.showNotificationInDashboard(notification);
}
break;
case 'plugins':
if (!notification.read) {
Notifications.showNotificationInPlugins(notification);
}
break;
case 'themes':
if (!notification.read) {
Notifications.showNotificationInThemes(notification);
}
break;
}
}
// Grav.default.Notifications.fetch()
fetch({ filter = notificationsFilters(), refresh = false } = {}) {
if (!canFetchNotifications()) {
return false;
}
let feed = $('#notifications');
let loader = feed.find('.widget-loader');
let content = feed.find('.widget-content > ul');
loader.find('div').remove();
loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
loader.show();
content.hide();
let processNotifications = (response) => {
let notifications = response.notifications;
$('#notifications').find('.widget-content > ul').empty();
if (notifications) {
Object.keys(notifications).forEach((location) => Notifications.processLocation(location, notifications[location]));
}
};
request(`${config.base_url_relative}/task${config.param_sep}getNotifications`, {
method: 'post',
body: { refresh, filter }
}, (response) => {
processNotifications(response);
}).catch(() => {
let widget = $('#notifications .widget-content');
widget
.find('.widget-loader')
.find('div').remove();
widget
.find('.widget-loader')
.append('<div>Failed to retrieve notifications</div>')
.find('.fa-spin')
.removeClass('fa-spin fa-refresh').addClass('fa-warning');
});
}
}
let notifications = new Notifications();
export default notifications;
if (canFetchNotifications()) {
notifications.fetch();
/* Hide a notification and store it hidden */
// <a href="#" data-notification-action="hide-notification" data-notification-id="${notification.id}" class="close hide-notification"><i class="fa fa-close"></i></a>
$(document).on('click', '[data-notification-action="hide-notification"]', (event) => {
let notification_id = $(event.target).parents('.hide-notification').data('notification-id');
let url = `${config.base_url_relative}/notifications.json/task${config.param_sep}hideNotification/notification_id${config.param_sep}${notification_id}`;
request(url, { method: 'post' }, () => {});
$(event.target).parents('.single-notification').hide();
});
$(document).on('click', '[data-notification-action="hide-notification"]', (event) => {
const target = $(event.currentTarget);
const notification = target.parent();
notification.slideUp(() => notification.remove());
});
$(document).on('click', '[data-notification-action="show-all-notifications"]', (event) => {
$('#notifications .show-all').hide();
$('#notifications .hidden').removeClass('hidden');
});
$(document).on('click', '[data-refresh="notifications"]', (event) => {
event.preventDefault();
notifications.fetch({ filter: ['feed'], refresh: true });
});
}

View File

@@ -0,0 +1,22 @@
import $ from 'jquery';
import { translations } from 'grav-config';
import formatBytes from '../utils/formatbytes';
import request from '../utils/request';
import { Instance as Update } from './index';
// Dashboard update and Grav update
$(document).on('click.remodal', '[data-remodal-id="update-grav"] [data-remodal-action="confirm"]', () => {
const element = $('#grav-update-button');
element.html(`${translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT} ${formatBytes(Update.payload.grav.assets['grav-update'].size)}..`);
element.attr('disabled', 'disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin');
request(Update.updateURL, (response) => {
if (response.type === 'updategrav') {
$('[data-gpm-grav]').remove();
$('#footer .grav-version').html(response.version);
}
element.removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-cloud-download');
});
});

View File

@@ -0,0 +1,32 @@
import $ from 'jquery';
import { config } from 'grav-config';
import request from '../utils/request';
const body = $('body');
// Dashboard update and Grav update
body.on('click', '[data-2fa-regenerate]', function(event) {
event.preventDefault();
let element = $(this);
let url = `${config.base_url_relative}/ajax.json/task${config.param_sep}regenerate2FASecret`;
element.attr('disabled', 'disabled').find('> .fa').addClass('fa-spin');
request(url, { method: 'post' }, (response) => {
$('[data-2fa-image]').attr('src', response.image);
$('[data-2fa-secret]').text(response.secret);
$('[data-2fa-value]').val(response.secret.replace(' ', ''));
element.removeAttr('disabled').find('> .fa').removeClass('fa-spin');
});
});
const toggleSecret = () => {
const toggle = $('#toggle_twofa_enabled1');
const secret = $('.twofa-secret');
secret[toggle.is(':checked') ? 'addClass' : 'removeClass']('show');
};
body.on('click', '.twofa-toggle input', toggleSecret);
toggleSecret();

View File

@@ -0,0 +1,210 @@
import jQuery from 'jquery';
/* ========================================================================
* Bootstrap: collapse.js v3.4.0
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
/* jshint latedef: false */
+(function($) {
'use strict';
// COLLAPSE PUBLIC CLASS DEFINITION
// ================================
var Collapse = function(element, options) {
this.$element = $(element);
this.options = $.extend({}, Collapse.DEFAULTS, options);
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
'[data-toggle="collapse"][data-target="#' + element.id + '"]');
this.transitioning = null;
if (this.options.parent) {
this.$parent = this.getParent();
} else {
this.addAriaAndCollapsedClass(this.$element, this.$trigger);
}
if (this.options.toggle) this.toggle();
};
Collapse.VERSION = '3.4.0';
Collapse.TRANSITION_DURATION = 350;
Collapse.DEFAULTS = {
toggle: true
};
Collapse.prototype.dimension = function() {
var hasWidth = this.$element.hasClass('width');
return hasWidth ? 'width' : 'height';
};
Collapse.prototype.show = function() {
if (this.transitioning || this.$element.hasClass('in')) return;
var activesData;
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing');
if (actives && actives.length) {
activesData = actives.data('bs.collapse');
if (activesData && activesData.transitioning) return;
}
var startEvent = $.Event('show.bs.collapse');
this.$element.trigger(startEvent);
if (startEvent.isDefaultPrevented()) return;
if (actives && actives.length) {
Plugin.call(actives, 'hide');
activesData || actives.data('bs.collapse', null);
}
var dimension = this.dimension();
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
.attr('aria-expanded', true);
this.$trigger
.removeClass('collapsed')
.attr('aria-expanded', true);
this.transitioning = 1;
var complete = function() {
this.$element
.removeClass('collapsing')
.addClass('collapse in')[dimension]('');
this.transitioning = 0;
this.$element
.trigger('shown.bs.collapse');
};
if (!$.support.transition) return complete.call(this);
var scrollSize = $.camelCase(['scroll', dimension].join('-'));
this.$element
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]);
};
Collapse.prototype.hide = function() {
if (this.transitioning || !this.$element.hasClass('in')) return;
var startEvent = $.Event('hide.bs.collapse');
this.$element.trigger(startEvent);
if (startEvent.isDefaultPrevented()) return;
var dimension = this.dimension();
this.$element[dimension](this.$element[dimension]())[0].offsetHeight;
this.$element
.addClass('collapsing')
.removeClass('collapse in')
.attr('aria-expanded', false);
this.$trigger
.addClass('collapsed')
.attr('aria-expanded', false);
this.transitioning = 1;
var complete = function() {
this.transitioning = 0;
this.$element
.removeClass('collapsing')
.addClass('collapse')
.trigger('hidden.bs.collapse');
};
if (!$.support.transition) return complete.call(this);
this.$element[dimension](0)
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION);
};
Collapse.prototype.toggle = function() {
this[this.$element.hasClass('in') ? 'hide' : 'show']();
};
Collapse.prototype.getParent = function() {
return $(this.options.parent)
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
.each($.proxy(function(i, element) {
var $element = $(element);
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element);
}, this))
.end();
};
Collapse.prototype.addAriaAndCollapsedClass = function($element, $trigger) {
var isOpen = $element.hasClass('in');
$element.attr('aria-expanded', isOpen);
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen);
};
function getTargetFromTrigger($trigger) {
var href;
var target = $trigger.attr('data-target') ||
(href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, ''); // strip for ie7
return $(target);
}
// COLLAPSE PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function() {
var $this = $(this);
var data = $this.data('bs.collapse');
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option === 'object' && option);
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false;
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)));
if (typeof option === 'string') data[option]();
});
}
var old = $.fn.collapse;
$.fn.collapse = Plugin;
$.fn.collapse.Constructor = Collapse;
// COLLAPSE NO CONFLICT
// ====================
$.fn.collapse.noConflict = function() {
$.fn.collapse = old;
return this;
};
// COLLAPSE DATA-API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function(e) {
var $this = $(this);
if (!$this.attr('data-target')) e.preventDefault();
var $target = getTargetFromTrigger($this);
var data = $target.data('bs.collapse');
var option = data ? 'toggle' : $this.data();
Plugin.call($target, option);
});
}(jQuery));

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,169 @@
import jQuery from 'jquery';
/* ========================================================================
* Bootstrap: dropdown.js v3.4.1
* https://getbootstrap.com/docs/3.4/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/v3-dev/LICENSE)
* ======================================================================== */
+(function($) {
'use strict';
// DROPDOWN CLASS DEFINITION
// =========================
const backdrop = '.dropdown-backdrop';
const toggle = '[data-toggle="dropdown"]';
const Dropdown = function(element) {
$(element).on('click.bs.dropdown', this.toggle);
};
Dropdown.VERSION = '3.4.1';
function getParent($this) {
let selector = $this.attr('data-target');
if (!selector) {
selector = $this.attr('href');
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7
}
const $parent = selector !== '#' ? $(document).find(selector) : null;
return $parent && $parent.length ? $parent : $this.parent();
}
function clearMenus(e) {
if (e && e.which === 3) { return; }
$(backdrop).remove();
$(toggle).each(function() {
const $this = $(this);
const $parent = getParent($this);
const relatedTarget = { relatedTarget: this };
if (!$parent.hasClass('open')) { return; }
if (e && e.type === 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) { return; }
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget));
if (e.isDefaultPrevented()) { return; }
$this.attr('aria-expanded', 'false');
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget));
});
}
Dropdown.prototype.toggle = function(e) {
const $this = $(this);
if ($this.is('.disabled, :disabled')) { return; }
const $parent = getParent($this);
const isActive = $parent.hasClass('open');
clearMenus();
if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate
$(document.createElement('div'))
.addClass('dropdown-backdrop')
.insertAfter($(this))
.on('click', clearMenus);
}
const relatedTarget = { relatedTarget: this };
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget));
if (e.isDefaultPrevented()) { return; }
$this
.trigger('focus')
.attr('aria-expanded', 'true');
$parent
.toggleClass('open')
.trigger($.Event('shown.bs.dropdown', relatedTarget));
}
return false;
};
Dropdown.prototype.keydown = function(e) {
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return;
const $this = $(this);
e.preventDefault();
e.stopPropagation();
if ($this.is('.disabled, :disabled')) {
return;
}
const $parent = getParent($this);
const isActive = $parent.hasClass('open');
if (!isActive && e.which !== 27 || isActive && e.which === 27) {
if (e.which === 27) {
$parent.find(toggle).trigger('focus');
}
return $this.trigger('click');
}
const desc = ' li:not(.disabled):visible a';
const $items = $parent.find('.dropdown-menu' + desc);
if (!$items.length) {
return;
}
let index = $items.index(e.target);
if (e.which === 38 && index > 0) { index--; } // up
if (e.which === 40 && index < $items.length - 1) { index++; } // down
if (!~index) { index = 0; }
$items.eq(index).trigger('focus');
};
// DROPDOWN PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function() {
const $this = $(this);
let data = $this.data('bs.dropdown');
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)));
if (typeof option === 'string') data[option].call($this);
});
}
const old = $.fn.dropdown;
$.fn.dropdown = Plugin;
$.fn.dropdown.Constructor = Dropdown;
// DROPDOWN NO CONFLICT
// ====================
$.fn.dropdown.noConflict = function() {
$.fn.dropdown = old;
return this;
};
// APPLY TO STANDARD DROPDOWN ELEMENTS
// ===================================
$(document)
.on('click.bs.dropdown.data-api', clearMenus)
.on('click.bs.dropdown.data-api', '.dropdown form', function(e) { e.stopPropagation(); })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown);
}(jQuery));

View File

@@ -0,0 +1,52 @@
import jQuery from 'jquery';
+(function($) {
'use strict';
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap');
var transEndEventNames = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] };
}
}
return false; // explicit for ie8 ( ._.)
}
// http://blog.alexmaccaw.com/css-transitions
$.fn.emulateTransitionEnd = function(duration) {
var called = false;
var $el = this;
$(this).one('bsTransitionEnd', function() { called = true; });
var callback = function() { if (!called) $($el).trigger($.support.transition.end); };
setTimeout(callback, duration);
return this;
};
$(function() {
$.support.transition = transitionEnd();
if (!$.support.transition) return;
$.event.special.bsTransitionEnd = {
bindType: $.support.transition.end,
delegateType: $.support.transition.end,
handle: function(e) {
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments);
}
};
});
}(jQuery));

View File

@@ -0,0 +1,25 @@
/* eslint-disable */
import $ from 'jquery';
let TRIGGER = null;
$(document).on('click', '[data-remodal-changelog]', (event) => {
TRIGGER = event.currentTarget;
});
$(document).on('opened', '[data-remodal-id="changelog"]', () => {
const instance = $.remodal.lookup[$('[data-remodal-id=changelog]').data('remodal')];
instance.$modal.html('<div class="changelog-overflow center" style="padding:5rem 0;text-align:center;"><i class="fa fa-spinner fa-spin fa-3x fa-fw"></i></div>');
if (!TRIGGER) { return true; }
const url = $(TRIGGER).data('remodalChangelog');
$.ajax({url: url}).done(function(data) {
instance.$modal.html(data);
});
});
$(document).on('closed', '[data-remodal-id="changelog"]', () => {
const instance = $.remodal.lookup[$('[data-remodal-id=changelog]').data('remodal')];
instance.$modal.html('');
});

View File

@@ -0,0 +1,152 @@
// Parses a string and returns a valid hex string when possible
// parseHex('#fff') => '#ffffff'
export const parseHex = (string) => {
string = string.replace(/[^A-F0-9]/ig, '');
if (string.length !== 3 && string.length !== 6) return '';
if (string.length === 3) {
string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2];
}
return '#' + string.toLowerCase();
};
// Converts an HSB object to an RGB object
// hsb2rgb({h: 0, s: 0, b: 100}) => {r: 255, g: 255, b: 255}
export const hsb2rgb = (hsb) => {
let rgb = {};
let h = Math.round(hsb.h);
let s = Math.round(hsb.s * 255 / 100);
let v = Math.round(hsb.b * 255 / 100);
if (s === 0) {
rgb.r = rgb.g = rgb.b = v;
} else {
var t1 = v;
var t2 = (255 - s) * v / 255;
var t3 = (t1 - t2) * (h % 60) / 60;
if (h === 360) h = 0;
if (h < 60) {
rgb.r = t1;
rgb.b = t2;
rgb.g = t2 + t3;
} else if (h < 120) {
rgb.g = t1;
rgb.b = t2;
rgb.r = t1 - t3;
} else if (h < 180) {
rgb.g = t1;
rgb.r = t2;
rgb.b = t2 + t3;
} else if (h < 240) {
rgb.b = t1;
rgb.r = t2;
rgb.g = t1 - t3;
} else if (h < 300) {
rgb.b = t1;
rgb.g = t2;
rgb.r = t2 + t3;
} else if (h < 360) {
rgb.r = t1;
rgb.g = t2;
rgb.b = t1 - t3;
} else {
rgb.r = 0;
rgb.g = 0;
rgb.b = 0;
}
}
return {
r: Math.round(rgb.r),
g: Math.round(rgb.g),
b: Math.round(rgb.b)
};
};
// Converts an RGB object to a HEX string
// rgb2hex({r: 255, g: 255, b: 255}) => #ffffff
export const rgb2hex = (rgb) => {
var hex = [
rgb.r.toString(16),
rgb.g.toString(16),
rgb.b.toString(16)
];
hex.forEach((val, nr) => {
if (val.length === 1) hex[nr] = '0' + val;
});
return '#' + hex.join('');
};
// Converts and RGB(a) string to a HEX string
// rgbstr2hex('rgba(255, 255, 255, 0.5)') => #ffffff
export const rgbstr2hex = (rgb) => {
rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
return (rgb && rgb.length === 4) ? '#' +
('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) +
('0' + parseInt(rgb[2], 10).toString(16)).slice(-2) +
('0' + parseInt(rgb[3], 10).toString(16)).slice(-2) : '';
};
// Converts an HSB object to a HEX string
// hsb2hex({h: 0, s: 0, b: 100}) => #ffffff
export const hsb2hex = (hsb) => {
return rgb2hex(hsb2rgb(hsb));
};
// Converts a HEX string to an HSB object
// hex2hsb('#ffffff') => {h: 0, s: 0, b: 100}
export const hex2hsb = (hex) => {
let hsb = rgb2hsb(hex2rgb(hex));
if (hsb.s === 0) hsb.h = 360;
return hsb;
};
// Converts an RGB object to an HSB object
// rgb2hsb({r: 255, g: 255, b: 255}) => {h: 0, s: 0, b: 100}
export const rgb2hsb = (rgb) => {
let hsb = {
h: 0,
s: 0,
b: 0
};
let min = Math.min(rgb.r, rgb.g, rgb.b);
let max = Math.max(rgb.r, rgb.g, rgb.b);
let delta = max - min;
hsb.b = max;
hsb.s = max !== 0 ? 255 * delta / max : 0;
if (hsb.s !== 0) {
if (rgb.r === max) {
hsb.h = (rgb.g - rgb.b) / delta;
} else if (rgb.g === max) {
hsb.h = 2 + (rgb.b - rgb.r) / delta;
} else {
hsb.h = 4 + (rgb.r - rgb.g) / delta;
}
} else {
hsb.h = -1;
}
hsb.h *= 60;
if (hsb.h < 0) {
hsb.h += 360;
}
hsb.s *= 100 / 255;
hsb.b *= 100 / 255;
return hsb;
};
// Converts a HEX string to an RGB object
// hex2rgb('#ffffff') => {r: 255, g: 255, b: 255}
export const hex2rgb = (hex) => {
hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
return {
/* jshint ignore:start */
r: hex >> 16,
g: (hex & 0x00FF00) >> 8,
b: (hex & 0x0000FF)
/* jshint ignore:end */
};
};

View File

@@ -0,0 +1,164 @@
/*
* Cookies.js - 1.2.3-grav
* https://github.com/ScottHamper/Cookies
*
* With SameSite support by Grav
*
* This is free and unencumbered software released into the public domain.
*/
const factory = function(window) {
if (typeof window.document !== 'object') {
throw new Error('Cookies.js requires a `window` with a `document` object');
}
const Cookies = (key, value, options) => {
return arguments.length === 1
? Cookies.get(key)
: Cookies.set(key, value, options);
};
// Allows for setter injection in unit tests
Cookies._document = window.document;
// Used to ensure cookie keys do not collide with
// built-in `Object` properties
Cookies._cacheKeyPrefix = 'cookey.'; // Hurr hurr, :)
Cookies._maxExpireDate = new Date('Fri, 31 Dec 9999 23:59:59 UTC');
Cookies.defaults = {
path: '/',
secure: false,
sameSite: 'Lax'
};
Cookies.get = (key) => {
if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) {
Cookies._renewCache();
}
const value = Cookies._cache[Cookies._cacheKeyPrefix + key];
return value === undefined ? undefined : decodeURIComponent(value);
};
Cookies.set = (key, value, options) => {
options = Cookies._getExtendedOptions(options);
options.expires = Cookies._getExpiresDate(value === undefined ? -1 : options.expires);
Cookies._document.cookie = Cookies._generateCookieString(key, value, options);
return Cookies;
};
Cookies.expire = (key, options) => {
return Cookies.set(key, undefined, options);
};
Cookies._getExtendedOptions = (options) => {
return {
path: options && options.path || Cookies.defaults.path,
domain: options && options.domain || Cookies.defaults.domain,
expires: options && options.expires || Cookies.defaults.expires,
secure: options && options.secure !== undefined ? options.secure : Cookies.defaults.secure,
sameSite: options && options.sameSite || Cookies.defaults.sameSite
};
};
Cookies._isValidDate = (date) => {
return Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
};
Cookies._getExpiresDate = (expires, now) => {
now = now || new Date();
if (typeof expires === 'number') {
expires = expires === Infinity
? Cookies._maxExpireDate
: new Date(now.getTime() + expires * 1000);
} else if (typeof expires === 'string') {
expires = new Date(expires);
}
if (expires && !Cookies._isValidDate(expires)) {
throw new Error('`expires` parameter cannot be converted to a valid Date instance');
}
return expires;
};
Cookies._generateCookieString = (key, value, options) => {
key = key.replace(/[^#$&+\^`|]/g, encodeURIComponent);
key = key.replace(/\(/g, '%28').replace(/\)/g, '%29');
value = (value + '').replace(/[^!#$&-+\--:<-\[\]-~]/g, encodeURIComponent);
options = options || {};
let cookieString = key + '=' + value;
cookieString += options.path ? ';path=' + options.path : '';
cookieString += options.domain ? ';domain=' + options.domain : '';
cookieString += options.expires ? ';expires=' + options.expires.toUTCString() : '';
cookieString += options.secure ? ';secure' : '';
cookieString += options.sameSite ? ';SameSite=' + options.sameSite : '';
return cookieString;
};
Cookies._getCacheFromString = (documentCookie) => {
let cookieCache = {};
const cookiesArray = documentCookie ? documentCookie.split('; ') : [];
for (let i = 0; i < cookiesArray.length; i++) {
const cookieKvp = Cookies._getKeyValuePairFromCookieString(cookiesArray[i]);
if (cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] === undefined) {
cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] = cookieKvp.value;
}
}
return cookieCache;
};
Cookies._getKeyValuePairFromCookieString = (cookieString) => {
// "=" is a valid character in a cookie value according to RFC6265, so cannot `split('=')`
let separatorIndex = cookieString.indexOf('=');
// IE omits the "=" when the cookie value is an empty string
separatorIndex = separatorIndex < 0 ? cookieString.length : separatorIndex;
const key = cookieString.substr(0, separatorIndex);
let decodedKey;
try {
decodedKey = decodeURIComponent(key);
} catch (e) {
if (console && typeof console.error === 'function') {
console.error('Could not decode cookie with key "' + key + '"', e);
}
}
return {
key: decodedKey,
value: cookieString.substr(separatorIndex + 1) // Defer decoding value until accessed
};
};
Cookies._renewCache = () => {
Cookies._cache = Cookies._getCacheFromString(Cookies._document.cookie);
Cookies._cachedDocumentCookie = Cookies._document.cookie;
};
Cookies._areEnabled = () => {
const testKey = 'cookies.js';
const areEnabled = Cookies.set(testKey, 1).get(testKey) === '1';
Cookies.expire(testKey);
return areEnabled;
};
Cookies.enabled = Cookies._areEnabled();
return Cookies;
};
global.Cookies = (global && typeof global.document === 'object') ? factory(global) : factory;
export default global.Cookies;

View File

@@ -0,0 +1,864 @@
/* eslint-disable */
import $ from 'jquery';
/*
* This file is part of the Arnapou jqCron package.
*
* (c) Arnaud Buathier <arnaud@arnapou.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Default settings
*/
var jqCronDefaultSettings = {
texts: {},
monthdays: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31],
hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
hour_labels: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"],
minutes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
lang: 'en',
enabled_minute: false,
enabled_hour: true,
enabled_day: true,
enabled_week: true,
enabled_month: true,
enabled_year: true,
multiple_dom: false,
multiple_month: false,
multiple_mins: false,
multiple_dow: false,
multiple_time_hours: false,
multiple_time_minutes: false,
numeric_zero_pad: false,
default_period: 'day',
default_value: '',
no_reset_button: true,
disabled: false,
bind_to: null,
bind_method: {
set: function($element, value) {
$element.is(':input') ? $element.val(value) : $element.data('jqCronValue', value);
},
get: function($element) {
return $element.is(':input') ? $element.val() : $element.data('jqCronValue');
}
}
};
/**
* Custom extend of json for jqCron settings.
* We don't use jQuery.extend because simple extend does not fit our needs, and deep extend has a bad
* feature for us : it replaces keys of "Arrays" instead of replacing the full array.
*/
(function($){
var extend = function(dst, src) {
for(var i in src) {
if($.isPlainObject(src[i])) {
dst[i] = extend(dst[i] && $.isPlainObject(dst[i]) ? dst[i] : {}, src[i]);
}
else if($.isArray(src[i])) {
dst[i] = src[i].slice(0);
}
else if(src[i] !== undefined) {
dst[i] = src[i];
}
}
return dst;
};
this.jqCronMergeSettings = function(obj) {
return extend(extend({}, jqCronDefaultSettings), obj || {});
};
}).call(window, $);
/**
* Shortcut to get the instance of jqCron instance from one jquery object
*/
(function($){
$.fn.jqCronGetInstance = function() {
return this.data('jqCron');
};
}).call(window, $);
/**
* Main plugin
*/
(function($){
$.fn.jqCron = function(settings) {
var saved_settings = settings;
return this.each(function() {
var cron, saved;
var $this = $(this);
var settings = jqCronMergeSettings(saved_settings); // clone settings
var translations = settings.texts[settings.lang];
if (typeof(translations) !== 'object' || $.isEmptyObject(translations)) {
console && console.error(
'Missing translations for language "' + settings.lang + '". ' +
'Please include jqCron.' + settings.lang + '.js or manually provide ' +
'the necessary translations when calling $.fn.jqCron().'
);
return;
}
if(!settings.jquery_container) {
if($this.is(':container')) {
settings.jquery_element = $this.uniqueId('jqCron');
}
else if($this.is(':autoclose')) {
// delete already generated dom if exists
if($this.next('.jqCron').length == 1) {
$this.next('.jqCron').remove();
}
// generate new
settings.jquery_element = $('<span class="jqCron"></span>').uniqueId('jqCron').insertAfter($this);
}
else {
console && console.error(settings.texts[settings.lang].error1.replace('%s', this.tagName));
return;
}
}
// autoset bind_to if it is an input
if($this.is(':input')) {
settings.bind_to = settings.bind_to || $this;
}
// init cron object
if(settings.bind_to){
if(settings.bind_to.is(':input')) {
// auto bind from input to object if an input, textarea ...
settings.bind_to.blur(function(){
var value = settings.bind_method.get(settings.bind_to);
$this.jqCronGetInstance().setCron(value);
});
}
saved = settings.bind_method.get(settings.bind_to);
cron = new jqCron(settings);
cron.setCron(saved);
}
else {
cron = new jqCron(settings);
}
$(this).data('jqCron', cron);
});
};
}).call(window, $);
/**
* jqCron class
*/
(function($){
var jqCronInstances = [];
function jqCron(settings) {
var _initialized = false;
var _self = this;
var _$elt = this;
var _$obj = $('<span class="jqCron-container"></span>');
var _$blocks = $('<span class="jqCron-blocks"></span>');
var _$blockPERIOD = $('<span class="jqCron-period"></span>');
var _$blockDOM = $('<span class="jqCron-dom"></span>');
var _$blockMONTH = $('<span class="jqCron-month"></span>');
var _$blockMINS = $('<span class="jqCron-mins"></span>');
var _$blockDOW = $('<span class="jqCron-dow"></span>');
var _$blockTIME = $('<span class="jqCron-time"></span>');
var _$cross = $('<span class="jqCron-cross">&#10008;</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, $);

View File

@@ -0,0 +1,325 @@
/**
* (c) Trilby Media, LLC
* Author Djamil Legato
*
* Based on Mark Matyas's Finderjs
* MIT License
*/
import $ from 'jquery';
import EventEmitter from 'eventemitter3';
export const DEFAULTS = {
labelKey: 'name',
valueKey: 'value', // new
childKey: 'children',
iconKey: 'icon', // new
itemKey: 'item-key', // new
pathBar: true,
className: {
container: 'fjs-container',
pathBar: 'fjs-path-bar',
col: 'fjs-col',
list: 'fjs-list',
item: 'fjs-item',
active: 'fjs-active',
children: 'fjs-has-children',
url: 'fjs-url',
itemPrepend: 'fjs-item-prepend',
itemContent: 'fjs-item-content',
itemAppend: 'fjs-item-append'
}
};
class Finder {
constructor(container, data, options) {
this.$emitter = new EventEmitter();
this.container = $(container);
this.data = data;
this.config = $.extend({}, DEFAULTS, options);
// dom events
this.container.on('click', this.clickEvent.bind(this));
this.container.on('keydown', this.keydownEvent.bind(this));
// internal events
this.$emitter.on('item-selected', this.itemSelected.bind(this));
this.$emitter.on('create-column', this.addColumn.bind(this));
this.$emitter.on('navigate', this.navigate.bind(this));
this.$emitter.on('go-to', this.goTo.bind(this, this.data));
this.container.addClass(this.config.className.container).attr('tabindex', 0);
this.createColumn(this.data);
if (this.config.pathBar) {
this.pathBar = this.createPathBar();
this.pathBar.on('click', '[data-breadcrumb-node]', (event) => {
event.preventDefault();
const location = $(event.currentTarget).data('breadcrumbNode');
this.goTo(this.data, location);
});
}
// '' is <Root>
if (this.config.defaultPath || this.config.defaultPath === '') {
this.goTo(this.data, this.config.defaultPath);
}
}
reload(data = this.data) {
this.createColumn(data);
// '' is <Root>
if (this.config.defaultPath || this.config.defaultPath === '') {
this.goTo(data, this.config.defaultPath);
}
}
createColumn(data, parent) {
const callback = (data) => this.createColumn(data, parent);
if (typeof data === 'function') {
data.call(this, parent, callback);
} else if (Array.isArray(data) || typeof data === 'object') {
if (typeof data === 'object') {
data = Array.from(data);
}
const list = this.createList(data);
const div = $('<div />');
div.append(list).addClass(this.config.className.col);
this.$emitter.emit('create-column', div);
return div;
} else {
throw new Error('Unknown data type');
}
}
createPathBar() {
this.container.siblings(`.${this.config.className.pathBar}`).remove();
const pathBar = $(`<div class="${this.config.className.pathBar}" />`);
pathBar.insertAfter(this.container);
return pathBar;
}
clickEvent(event) {
event.stopPropagation();
event.preventDefault();
const target = $(event.target);
const column = target.closest(`.${this.config.className.col}`);
const item = target.closest(`.${this.config.className.item}`);
if (item.length) {
this.$emitter.emit('item-selected', { column, item });
}
}
keydownEvent(event) {
const codes = { 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
if (event.keyCode in codes) {
event.stopPropagation();
event.preventDefault();
this.$emitter.emit('navigate', {
direction: codes[event.keyCode]
});
}
}
itemSelected(value) {
const element = value.item;
if (!element.length) { return false; }
const item = element[0]._item;
const column = value.column;
const data = item[this.config.childKey] || this.data;
const active = $(column).find(`.${this.config.className.active}`);
if (active.length) {
active.removeClass(this.config.className.active);
}
element.addClass(this.config.className.active);
column.nextAll().remove(); // ?!?!?
this.container[0].focus();
window.scrollTo(window.pageXOffset, window.pageYOffset);
this.updatePathBar();
let newColumn;
if (data) {
newColumn = this.createColumn(data, item);
this.$emitter.emit('interior-selected', item);
} else {
this.$emitter.emit('leaf-selected', item);
}
return newColumn;
}
addColumn(column) {
this.container.append(column);
this.$emitter.emit('column-created', column);
}
navigate(value) {
const active = this.findLastActive();
const direction = value.direction;
let column;
let item;
let target;
if (active) {
item = active.item;
column = active.column;
if (direction === 'up' && item.prev().length) {
target = item.prev();
} else if (direction === 'down' && item.next().length) {
target = item.next();
} else if (direction === 'right' && column.next().length) {
column = column.next();
target = column.find(`.${this.config.className.item}`).first();
} else if (direction === 'left' && column.prev().length) {
column = column.prev();
target = column.find(`.${this.config.className.active}`).first() || column.find(`.${this.config.className.item}`);
}
} else {
column = this.container.find(`.${this.config.className.col}`).first();
target = column.find(`.${this.config.className.item}`).first();
}
if (target) {
this.$emitter.emit('item-selected', {
column,
item: target
});
}
}
goTo(data, path) {
path = Array.isArray(path) ? path : path.split('/').map(bit => bit.trim()).filter(Boolean);
if (path.length) {
this.container.children().remove();
}
if (typeof data === 'function') {
data.call(this, null, (data) => this.selectPath(path, data));
} else {
this.selectPath(path, data);
}
}
selectPath(path, data, column) {
column = column || (path.length ? this.createColumn(data) : this.container.find(`> .${this.config.className.col}`));
const current = path[0] || '';
const children = data.find((item) => item[this.config.itemKey] === current);
const newColumn = this.itemSelected({
column,
item: column.find(`[data-fjs-item="${current}"]`).first()
});
path.shift();
if (path.length && children) {
this.selectPath(path, children[this.config.childKey], newColumn);
}
}
findLastActive() {
const active = this.container.find(`.${this.config.className.active}`);
if (!active.length) {
return null;
}
const item = active.last();
const column = item.closest(`.${this.config.className.col}`);
return { item, column };
}
createList(data) {
const list = $('<ul />');
const items = data.map((item) => this.createItem(item));
const fragments = items.reduce((fragment, current) => {
fragment.appendChild(current[0] || current);
return fragment;
}, document.createDocumentFragment());
list.append(fragments).addClass(this.config.className.list);
return list;
}
createItem(item) {
const listItem = $('<li />');
const listItemClasses = [this.config.className.item];
const link = $('<a />');
const createItemContent = this.config.createItemContent || this.createItemContent;
const fragment = createItemContent.call(this, item);
link.append(fragment)
.attr('href', '')
.attr('tabindex', -1);
if (item.url) {
link.attr('href', item.url);
listItemClasses.push(item.className);
}
if (item[this.config.childKey]) {
listItemClasses.push(this.config.className[this.config.childKey]);
}
listItemClasses.push(`fjs-item-${item.type}`);
listItem.addClass(listItemClasses.join(' '));
listItem.append(link)
.attr('data-fjs-item', item[this.config.itemKey]);
listItem[0]._item = item;
return listItem;
}
updatePathBar() {
if (!this.config.pathBar) { return false; }
const activeItems = this.container.find(`.${this.config.className.active}`);
let itemKeys = '';
this.pathBar.children().empty();
activeItems.each((index, activeItem) => {
const item = activeItem._item;
const isLast = (index + 1) === activeItems.length;
itemKeys += `/${item[this.config.itemKey]}`;
this.pathBar.append(`
<span class="breadcrumb-node breadcrumb-node-${item.type}" ${item.type === 'dir' ? `data-breadcrumb-node="${itemKeys}"` : ''}>
<i class="fa fa-fw ${this.getIcon(item.type)}"></i>
<span class="breadcrumb-node-name">${$('<div />').html(item[this.config.labelKey]).html()}</span>
${!isLast ? '<i class="fa fa-fw fa-chevron-right"></i>' : ''}
</span>
`);
});
}
getIcon(type) {
switch (type) {
case 'root':
return 'fa-sitemap';
case 'file':
return 'fa-file-o';
case 'dir':
default:
return 'fa-folder';
}
}
}
export default Finder;

View File

@@ -0,0 +1,11 @@
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
export default function formatBytes(bytes, decimals) {
if (bytes === 0) return '0 Byte';
let k = 1000;
let value = Math.floor(Math.log(bytes) / Math.log(k));
let decimal = decimals + 1 || 3;
return (bytes / Math.pow(k, value)).toPrecision(decimal) + ' ' + sizes[value];
}

View File

@@ -0,0 +1,57 @@
import { parseJSON, parseStatus, userFeedbackError } from './response';
import { config } from 'grav-config';
import EventEmitter from 'events';
export default class GPM extends EventEmitter {
constructor(action = 'getUpdates') {
super();
this.payload = {};
this.raw = {};
this.action = action;
}
setPayload(payload = {}) {
this.payload = payload;
this.emit('payload', payload);
return this;
}
setAction(action = 'getUpdates') {
this.action = action;
this.emit('action', action);
return this;
}
fetch(callback = () => true, flush = false) {
let data = new FormData();
data.append('admin-nonce', config.admin_nonce);
if (flush) {
data.append('flush', true);
}
this.emit('fetching', this);
fetch(`${config.base_url_relative}/update.json/task${config.param_sep}getUpdates`, {
credentials: 'same-origin',
method: 'post',
body: data
}).then((response) => { this.raw = response; return response; })
.then(parseStatus)
.then(parseJSON)
.then((response) => this.response(response))
.then((response) => callback(response, this.raw))
.then((response) => this.emit('fetched', this.payload, this.raw, this))
.catch(userFeedbackError);
}
response(response) {
this.payload = response;
return response;
}
}
export let Instance = new GPM();

View File

@@ -0,0 +1,50 @@
import $ from 'jquery';
import getSlug from 'speakingurl';
// jQuery no parents filter
$.expr[':']['noparents'] = $.expr.createPseudo((text) => (element) => $(element).parents(text).length < 1);
// Slugify
// CommonJS and ES6 version of https://github.com/madflow/jquery-slugify
$.fn.slugify = (source, options) => {
return this.each((element) => {
let target = $(element);
let source = $(source);
target.on('keyup change', () => {
target.data('locked', target.val() !== '' && target.val() !== undefined);
});
source.on('keyup change', () => {
if (target.data('locked') === true) { return true; }
let isInput = target.is('input') || target.is('textarea');
target[isInput ? 'val' : 'text']($.slugify(source.val(), options));
});
});
};
// Static method.
$.slugify = (sourceString, options) => {
options = $.extend({}, $.slugify.options, options);
options.lang = options.lang || $('html').prop('lang');
if (typeof options.preSlug === 'function') {
sourceString = options.preSlug(sourceString);
}
sourceString = options.slugFunc(sourceString, options);
if (typeof options.postSlug === 'function') {
sourceString = options.postSlug(sourceString);
}
return sourceString;
};
// Default plugin options
$.slugify.options = {
preSlug: null,
postSlug: null,
slugFunc: (input, opts) => getSlug(input, opts)
};

View File

@@ -0,0 +1,34 @@
import { config } from 'grav-config';
import { userFeedbackError } from './response';
const MAX_SAFE_DELAY = 2147483647;
class KeepAlive {
constructor() {
this.active = false;
}
start() {
let timeout = config.admin_timeout / 1.5 * 1000;
this.timer = setInterval(() => this.fetch(), Math.min(timeout, MAX_SAFE_DELAY));
this.active = true;
}
stop() {
clearInterval(this.timer);
this.active = false;
}
fetch() {
let data = new FormData();
data.append('admin-nonce', config.admin_nonce);
fetch(`${config.base_url_relative}/task${config.param_sep}keepAlive`, {
credentials: 'same-origin',
method: 'post',
body: data
}).catch(userFeedbackError);
}
}
export default new KeepAlive();

View File

@@ -0,0 +1,21 @@
import $ from 'jquery';
import isOnline from '../utils/offline';
const offlineElement = $('#offline-status');
$(window).on('offline', () => {
offlineElement.slideDown();
});
$(window).on('online', () => {
offlineElement.slideUp();
});
$(document).ready(() => {
if (!isOnline) {
offlineElement.slideDown();
}
});
// assume online if can't check
export default typeof global.navigator.onLine !== 'undefined' ? global.navigator.onLine : true;

View File

@@ -0,0 +1,498 @@
import $ from 'jquery';
import { config, translations } from 'grav-config';
import request from '../utils/request';
import { Instance as gpm } from '../utils/gpm';
class Sorter {
getElements(elements, container) {
this.elements = elements || document.querySelectorAll('[data-gpm-plugin], [data-gpm-theme]');
this.container = container || document.querySelector('.gpm-plugins > table > tbody, .gpm-themes > .themes.card-row');
return this.elements;
}
static sort(A, B, direction = 'asc') {
if (A > B) { return (direction === 'asc') ? 1 : -1; }
if (A < B) { return (direction === 'asc') ? -1 : 1; }
return 0;
}
byCommon(direction = 'asc', data = '') {
const elements = this.getElements();
this.removeGumroad();
Array.from(elements).sort((a, b) => {
let A = a.dataset[data].toString().toLowerCase();
let B = b.dataset[data].toString().toLowerCase();
return Sorter.sort(A, B, direction);
}).forEach((element) => {
this.container.appendChild(element);
});
this.addGumroad();
return this.container;
}
byName(direction = 'asc', data = 'gpmName') {
return this.byCommon(direction, data);
}
byAuthor(direction = 'asc', data = 'gpmAuthor') {
return this.byCommon(direction, data);
}
byOfficial(direction = 'asc', data = 'gpmOfficial') {
return this.byCommon(direction, data);
}
byPremium(direction = 'asc', data = 'gpmPremium') {
return this.byCommon(direction, data);
}
byReleaseDate(direction = 'asc', data = 'gpmReleaseDate') {
const elements = this.getElements();
this.removeGumroad();
Array.from(elements).sort((a, b) => {
let A = new Date(a.dataset[data]).getTime();
let B = new Date(b.dataset[data]).getTime();
return Sorter.sort(A, B, direction === 'asc' ? 'desc' : 'asc');
}).forEach((element) => {
this.container.appendChild(element);
});
this.addGumroad();
return this.container;
}
byUpdatable(direction = 'asc', data = 'gpmUpdatable') {
return this.byCommon(direction, data);
}
byEnabled(direction = 'asc', data = 'gpmEnabled') {
return this.byCommon(direction, data);
}
byTesting(direction = 'asc', data = 'gpmTesting') {
return this.byCommon(direction, data);
}
addGumroad() {
if (window.GumroadOverlay) {
window.GumroadOverlay.startNodeAdditionObserver();
}
}
removeGumroad() {
if (window.GumroadOverlay) {
window.GumroadOverlay.nodeAdditionObserver.disconnect();
}
}
}
class Packages {
constructor() {
this.Sort = new Sorter();
}
static getBackToList(type) {
global.location.href = `${config.base_url_relative}/${type}s`;
}
static addDependencyToList(type, dependency, slug = '') {
if (['admin', 'form', 'login', 'email', 'grav'].indexOf(dependency) !== -1) { return; }
let container = $('.package-dependencies-container');
let text = `${dependency} <a href="#" class="button" data-dependency-slug="${dependency}" data-${type}-action="remove-dependency-package">Remove</a>`;
if (slug) {
text += ` (was needed by ${slug})`;
}
container.append(`<li>${text}</li>`);
}
addDependenciesToList(dependencies, slug = '') {
dependencies.forEach((dependency) => {
Packages.addDependencyToList('plugin', dependency.name || dependency, slug);
});
}
static getTaskUrl(type, task) {
let url = `${config.base_url_relative}`;
url += `/${type}s.json`;
url += `/task${config.param_sep}${task}`;
return url;
}
static getRemovePackageUrl(type) {
return `${Packages.getTaskUrl(type, 'removePackage')}`;
}
static getReinstallPackageUrl(type) {
return `${Packages.getTaskUrl(type, 'reinstallPackage')}`;
}
static getGetPackagesDependenciesUrl(type) {
return `${Packages.getTaskUrl(type, 'getPackagesDependencies')}`;
}
static getInstallDependenciesOfPackagesUrl(type) {
return `${Packages.getTaskUrl(type, 'installDependenciesOfPackages')}`;
}
static getInstallPackageUrl(type) {
return `${Packages.getTaskUrl(type, 'installPackage')}`;
}
removePackage(type, slug) {
let url = Packages.getRemovePackageUrl(type);
request(url, {
method: 'post',
body: {
package: slug
}
}, (response) => {
if (response.status === 'success') {
$('.remove-package-confirm').addClass('hidden');
if (response.dependencies && response.dependencies.length > 0) {
this.addDependenciesToList(response.dependencies);
$('.remove-package-dependencies').removeClass('hidden');
} else {
$('.remove-package-done').removeClass('hidden');
}
// The package was removed. When the modal closes, move to the packages list
$(document).on('closing', '[data-remodal-id="remove-package"]', () => {
Packages.getBackToList(type);
});
} else {
$('.remove-package-confirm').addClass('hidden');
$('.remove-package-error').removeClass('hidden');
}
});
}
reinstallPackage(type, slug, package_name, current_version) {
$('.button-bar button').addClass('hidden');
$('.button-bar .spinning-wheel').removeClass('hidden');
let url = Packages.getReinstallPackageUrl(type);
request(url, {
method: 'post',
body: {
slug: slug,
type: type,
package_name: package_name,
current_version: current_version
}
}, (response) => {
if (response.status === 'success') {
$('.reinstall-package-confirm').addClass('hidden');
$('.reinstall-package-done').removeClass('hidden');
} else {
$('.reinstall-package-confirm').addClass('hidden');
$('.reinstall-package-error').removeClass('hidden');
}
window.location.reload();
});
}
removeDependency(type, slug, button) {
let url = Packages.getRemovePackageUrl(type);
request(url, {
method: 'post',
body: {
package: slug
}
}, (response) => {
if (response.status === 'success') {
button.removeClass('button');
button.replaceWith($('<span>Removed successfully</span>'));
if (response.dependencies && response.dependencies.length > 0) {
this.addDependenciesToList(response.dependencies, slug);
}
}
});
}
static addNeededDependencyToList(action, slug) {
$('.install-dependencies-package-container .type-' + action).removeClass('hidden');
let list = $('.install-dependencies-package-container .type-' + action + ' ul');
if (action !== 'install') {
let current_version = '';
let available_version = '';
let name = '';
let resources = gpm.payload.payload.resources;
if (resources.plugins[slug]) {
available_version = resources.plugins[slug].available;
current_version = resources.plugins[slug].version;
name = resources.plugins[slug].name;
} else if (resources.themes[slug]) {
available_version = resources.themes[slug].available;
current_version = resources.themes[slug].version;
name = resources.themes[slug].name;
}
list.append(`<li>${name ? name : slug}, ${translations.PLUGIN_ADMIN.FROM} v<strong>${current_version}</strong> ${translations.PLUGIN_ADMIN.TO} v<strong>${available_version}</strong></li>`);
} else {
list.append(`<li>${name ? name : slug}</li>`);
}
}
getPackagesDependencies(type, slugs, finishedLoadingCallback) {
let url = Packages.getGetPackagesDependenciesUrl(type);
request(url, {
method: 'post',
body: {
packages: slugs
}
}, (response) => {
finishedLoadingCallback();
if (response.status === 'success') {
if (response.dependencies) {
let hasDependencies = false;
for (var dependency in response.dependencies) {
if (response.dependencies.hasOwnProperty(dependency)) {
if (dependency === 'grav') {
continue;
}
hasDependencies = true;
let dependencyName = dependency;
let action = response.dependencies[dependency];
Packages.addNeededDependencyToList(action, dependencyName);
}
}
if (hasDependencies) {
$('[data-packages-modal] .install-dependencies-package-container').removeClass('hidden');
} else {
$('[data-packages-modal] .install-package-container').removeClass('hidden');
}
} else {
$('[data-packages-modal] .install-package-container').removeClass('hidden');
}
} else {
$('[data-packages-modal] .install-package-error').removeClass('hidden');
}
});
}
installDependenciesOfPackages(type, slugs, callbackSuccess, callbackError) {
let url = Packages.getInstallDependenciesOfPackagesUrl(type);
request(url, {
method: 'post',
body: {
packages: slugs
}
}, callbackSuccess);
}
installPackages(type, slugs, callbackSuccess) {
let url = Packages.getInstallPackageUrl(type);
global.Promise.all(slugs.map((slug) => {
return new global.Promise((resolve, reject) => {
request(url, {
method: 'post',
body: {
package: slug,
type: type
}
}, (response) => {
resolve(response);
});
});
})).then(callbackSuccess);
}
static getSlugsFromEvent(event) {
let slugs = '';
if ($(event.target).is('[data-packages-slugs]')) {
slugs = $(event.target).attr('data-packages-slugs');
} else {
slugs = $(event.target).parent('[data-packages-slugs]').attr('data-packages-slugs');
}
if (typeof slugs === 'undefined') {
return null;
}
slugs = slugs.split(',');
return typeof slugs === 'string' ? [slugs] : slugs;
}
handleGettingPackageDependencies(type, event, action = 'update') {
let slugs = Packages.getSlugsFromEvent(event);
if (!slugs) {
alert('No slug set');
return;
}
// Cleanup
$('.packages-names-list').html('');
$('.install-dependencies-package-container li').remove();
slugs.forEach((slug) => {
if (action === 'update') {
let current_version = '';
let available_version = '';
let name = '';
let resources = gpm.payload.payload.resources;
if (resources.plugins[slug]) {
available_version = resources.plugins[slug].available;
current_version = resources.plugins[slug].version;
name = resources.plugins[slug].name;
} else if (resources.themes[slug]) {
available_version = resources.themes[slug].available;
current_version = resources.themes[slug].version;
name = resources.themes[slug].name;
}
$('.packages-names-list').append(`<li>${name ? name : slug}, ${translations.PLUGIN_ADMIN.FROM} v<strong>${current_version}</strong> ${translations.PLUGIN_ADMIN.TO} v<strong>${available_version}</strong></li>`);
} else {
$('.packages-names-list').append(`<li>${name ? name : slug}</li>`);
}
});
event.preventDefault();
event.stopPropagation();
// fix mismatching types when sharing install modal between plugins/themes
const query = '[data-packages-modal] [data-theme-action], [data-packages-modal] [data-plugin-action]';
const data = $(query).data('themeAction') || $(query).data('pluginAction');
$(query).removeAttr('data-theme-action').removeAttr('data-plugin-action').attr(`data-${type}-action`, data);
// Restore original state
$('[data-packages-modal] .loading').removeClass('hidden');
$('[data-packages-modal] .install-dependencies-package-container').addClass('hidden');
$('[data-packages-modal] .install-package-container').addClass('hidden');
$('[data-packages-modal] .installing-dependencies').addClass('hidden');
$('[data-packages-modal] .installing-package').addClass('hidden');
$('[data-packages-modal] .installation-complete').addClass('hidden');
$('[data-packages-modal] .install-package-error').addClass('hidden');
this.getPackagesDependencies(type, slugs, () => {
let slugs_string = slugs.join();
$(`[data-packages-modal] [data-${type}-action="install-dependencies-and-package"]`).attr('data-packages-slugs', slugs_string);
$(`[data-packages-modal] [data-${type}-action="install-package"]`).attr('data-packages-slugs', slugs_string);
$('[data-packages-modal] .loading').addClass('hidden');
});
}
handleInstallingDependenciesAndPackage(type, event) {
let slugs = Packages.getSlugsFromEvent(event);
event.preventDefault();
event.stopPropagation();
$('[data-packages-modal] .install-dependencies-package-container').addClass('hidden');
$('[data-packages-modal] .install-package-container').addClass('hidden');
$('[data-packages-modal] .installing-dependencies').removeClass('hidden');
this.installDependenciesOfPackages(type, slugs, (response) => {
$('[data-packages-modal] .installing-dependencies').addClass('hidden');
$('[data-packages-modal] .installing-package').removeClass('hidden');
this.installPackages(type, slugs, () => {
$('[data-packages-modal] .installing-package').addClass('hidden');
$('[data-packages-modal] .installation-complete').removeClass('hidden');
if (response.status === 'error') {
let remodal = $.remodal.lookup[$('[data-packages-modal]').data('remodal')];
remodal.close();
return;
}
setTimeout(() => {
if (slugs.length === 1) {
global.location.href = `${config.base_url_relative}/${type}s/${slugs[0]}`;
} else {
global.location.href = `${config.base_url_relative}/${type}s`;
}
}, 1000);
});
});
}
handleInstallingPackage(type, event) {
let slugs = Packages.getSlugsFromEvent(event);
event.preventDefault();
event.stopPropagation();
$('[data-packages-modal] .install-package-container').addClass('hidden');
$('[data-packages-modal] .installing-package').removeClass('hidden');
this.installPackages(type, slugs, (response) => {
$('[data-packages-modal] .installing-package').addClass('hidden');
$('[data-packages-modal] .installation-complete').removeClass('hidden');
const errors = Array.from(response).filter((r) => r.status === 'error');
if (errors && errors.length) {
let remodal = $.remodal.lookup[$('[data-packages-modal].remodal-is-opened').data('remodal')];
remodal.close();
return;
}
if (slugs.length === 1) {
global.location.href = `${config.base_url_relative}/${type}s/${slugs[0]}`;
} else {
global.location.href = `${config.base_url_relative}/${type}s`;
}
});
}
handleRemovingPackage(type, event) {
let slug = $(event.target).attr('data-packages-slugs');
event.preventDefault();
event.stopPropagation();
this.removePackage(type, slug);
}
handleReinstallPackage(type, event) {
let target = $(event.target);
let slug = target.attr('data-package-slug');
let package_name = target.attr('data-package-name');
let current_version = target.attr('data-package-current-version');
event.preventDefault();
event.stopPropagation();
this.reinstallPackage(type, slug, package_name, current_version);
}
handleRemovingDependency(type, event) {
let slug = $(event.target).attr('data-dependency-slug');
let button = $(event.target);
event.preventDefault();
event.stopPropagation();
this.removeDependency(type, slug, button);
}
}
export default new Packages();

View File

@@ -0,0 +1,820 @@
/* Remodal from https://github.com/vodkabears/Remodal
* With Stackable option from https://github.com/antstorm/Remodal patch
*/
import $ from 'jquery';
!(function(root, factory) {
return factory(root, $);
})(this, function(global, $) {
'use strict';
/**
* Name of the plugin
* @private
* @const
* @type {String}
*/
var PLUGIN_NAME = 'remodal';
/**
* Namespace for CSS and events
* @private
* @const
* @type {String}
*/
var NAMESPACE = window.REMODAL_GLOBALS && window.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME;
/**
* Animationstart event with vendor prefixes
* @private
* @const
* @type {String}
*/
var ANIMATIONSTART_EVENTS = $.map(
['animationstart', 'webkitAnimationStart', 'MSAnimationStart', 'oAnimationStart'],
function(eventName) {
return eventName + '.' + NAMESPACE;
}
).join(' ');
/**
* Animationend event with vendor prefixes
* @private
* @const
* @type {String}
*/
var ANIMATIONEND_EVENTS = $.map(
['animationend', 'webkitAnimationEnd', 'MSAnimationEnd', 'oAnimationEnd'],
function(eventName) {
return eventName + '.' + NAMESPACE;
}
).join(' ');
/**
* Default settings
* @private
* @const
* @type {Object}
*/
var DEFAULTS = $.extend({
hashTracking: true,
closeOnConfirm: true,
closeOnCancel: true,
closeOnEscape: true,
closeOnOutsideClick: true,
modifier: '',
stack: false,
appendTo: null
}, window.REMODAL_GLOBALS && window.REMODAL_GLOBALS.DEFAULTS);
/**
* States of the Remodal
* @private
* @const
* @enum {String}
*/
var STATES = {
CLOSING: 'closing',
CLOSED: 'closed',
OPENING: 'opening',
OPENED: 'opened'
};
/**
* Reasons of the state change.
* @private
* @const
* @enum {String}
*/
var STATE_CHANGE_REASONS = {
CONFIRMATION: 'confirmation',
CANCELLATION: 'cancellation'
};
/**
* Is animation supported?
* @private
* @const
* @type {Boolean}
*/
var IS_ANIMATION = (function() {
var style = document.createElement('div').style;
return style.animationName !== undefined ||
style.WebkitAnimationName !== undefined ||
style.MozAnimationName !== undefined ||
style.msAnimationName !== undefined ||
style.OAnimationName !== undefined;
})();
/**
* Is iOS?
* @private
* @const
* @type {Boolean}
*/
var IS_IOS = /iPad|iPhone|iPod/.test(navigator.platform);
/**
* Current modal
* @private
* @type {Remodal}
*/
var openModals = [];
/**
* Scrollbar position
* @private
* @type {Number}
*/
var scrollTop;
/**
* Returns an animation duration
* @private
* @param {jQuery} $elem
* @returns {Number}
*/
function getAnimationDuration($elem) {
if (
IS_ANIMATION &&
$elem.css('animation-name') === 'none' &&
$elem.css('-webkit-animation-name') === 'none' &&
$elem.css('-moz-animation-name') === 'none' &&
$elem.css('-o-animation-name') === 'none' &&
$elem.css('-ms-animation-name') === 'none'
) {
return 0;
}
var duration = $elem.css('animation-duration') ||
$elem.css('-webkit-animation-duration') ||
$elem.css('-moz-animation-duration') ||
$elem.css('-o-animation-duration') ||
$elem.css('-ms-animation-duration') ||
'0s';
var delay = $elem.css('animation-delay') ||
$elem.css('-webkit-animation-delay') ||
$elem.css('-moz-animation-delay') ||
$elem.css('-o-animation-delay') ||
$elem.css('-ms-animation-delay') ||
'0s';
var iterationCount = $elem.css('animation-iteration-count') ||
$elem.css('-webkit-animation-iteration-count') ||
$elem.css('-moz-animation-iteration-count') ||
$elem.css('-o-animation-iteration-count') ||
$elem.css('-ms-animation-iteration-count') ||
'1';
var max;
var len;
var num;
var i;
duration = duration.split(', ');
delay = delay.split(', ');
iterationCount = iterationCount.split(', ');
// The 'duration' size is the same as the 'delay' size
for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) {
num = parseFloat(duration[i]) * parseInt(iterationCount[i], 10) + parseFloat(delay[i]);
if (num > max) {
max = num;
}
}
return max;
}
/**
* Returns a scrollbar width
* @private
* @returns {Number}
*/
function getScrollbarWidth() {
if ($(document).height() <= $(window).height()) {
return 0;
}
var outer = document.createElement('div');
var inner = document.createElement('div');
var widthNoScroll;
var widthWithScroll;
outer.style.visibility = 'hidden';
outer.style.width = '100px';
document.body.appendChild(outer);
widthNoScroll = outer.offsetWidth;
// Force scrollbars
outer.style.overflow = 'scroll';
// Add inner div
inner.style.width = '100%';
outer.appendChild(inner);
widthWithScroll = inner.offsetWidth;
// Remove divs
outer.parentNode.removeChild(outer);
return widthNoScroll - widthWithScroll;
}
/**
* Locks the screen
* @private
*/
function lockScreen() {
if (IS_IOS) {
return;
}
var $html = $('html');
var lockedClass = namespacify('is-locked');
var paddingRight;
var $body;
if (!$html.hasClass(lockedClass)) {
$body = $(document.body);
// Zepto does not support '-=', '+=' in the `css` method
paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth();
$body.css('padding-right', paddingRight + 'px');
$html.addClass(lockedClass);
}
}
/**
* Unlocks the screen
* @private
*/
function unlockScreen() {
if (IS_IOS) {
return;
}
var $html = $('html');
var lockedClass = namespacify('is-locked');
var paddingRight;
var $body;
if ($html.hasClass(lockedClass)) {
$body = $(document.body);
// Zepto does not support '-=', '+=' in the `css` method
paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth();
$body.css('padding-right', paddingRight + 'px');
$html.removeClass(lockedClass);
}
}
/**
* Sets a state for an instance
* @private
* @param {Remodal} instance
* @param {STATES} state
* @param {Boolean} isSilent If true, Remodal does not trigger events
* @param {String} Reason of a state change.
*/
function setState(instance, state, isSilent, reason) {
var newState = namespacify('is', state);
var allStates = [namespacify('is', STATES.CLOSING),
namespacify('is', STATES.OPENING),
namespacify('is', STATES.CLOSED),
namespacify('is', STATES.OPENED)].join(' ');
instance.$bg
.removeClass(allStates)
.addClass(newState);
instance.$overlay
.removeClass(allStates)
.addClass(newState);
instance.$wrapper
.removeClass(allStates)
.addClass(newState);
instance.$modal
.removeClass(allStates)
.addClass(newState);
instance.state = state;
!isSilent && instance.$modal.trigger({
type: state,
reason: reason
}, [{ reason: reason }]);
}
/**
* Synchronizes with the animation
* @param {Function} doBeforeAnimation
* @param {Function} doAfterAnimation
* @param {Remodal} instance
*/
function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) {
var runningAnimationsCount = 0;
var handleAnimationStart = function(e) {
if (e.target !== this) {
return;
}
runningAnimationsCount++;
};
var handleAnimationEnd = function(e) {
if (e.target !== this) {
return;
}
if (--runningAnimationsCount === 0) {
// Remove event listeners
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
});
doAfterAnimation();
}
};
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
instance[elemName]
.on(ANIMATIONSTART_EVENTS, handleAnimationStart)
.on(ANIMATIONEND_EVENTS, handleAnimationEnd);
});
doBeforeAnimation();
// If the animation is not supported by a browser or its duration is 0
if (
getAnimationDuration(instance.$bg) === 0 &&
getAnimationDuration(instance.$overlay) === 0 &&
getAnimationDuration(instance.$wrapper) === 0 &&
getAnimationDuration(instance.$modal) === 0
) {
// Remove event listeners
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
});
doAfterAnimation();
}
}
/**
* Closes immediately
* @private
* @param {Remodal} instance
*/
function halt(instance) {
if (instance.state === STATES.CLOSED) {
return;
}
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
});
removeModal(instance);
instance.$bg.removeClass(instance.settings.modifier);
instance.$overlay.removeClass(instance.settings.modifier).hide();
instance.$wrapper.hide();
if (openModals.length === 0) {
unlockScreen();
}
setState(instance, STATES.CLOSED, true);
}
/**
* Parses a string with options
* @private
* @param str
* @returns {Object}
*/
function parseOptions(str) {
var obj = {};
var arr;
var len;
var val;
var i;
// Remove spaces before and after delimiters
str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ',');
// Parse a string
arr = str.split(',');
for (i = 0, len = arr.length; i < len; i++) {
arr[i] = arr[i].split(':');
val = arr[i][1];
// Convert a string value if it is like a boolean
if (typeof val === 'string' || val instanceof String) {
val = val === 'true' || (val === 'false' ? false : val);
}
// Convert a string value if it is like a number
if (typeof val === 'string' || val instanceof String) {
val = !isNaN(val) ? +val : val;
}
obj[arr[i][0]] = val;
}
return obj;
}
/**
* Generates a string separated by dashes and prefixed with NAMESPACE
* @private
* @param {...String}
* @returns {String}
*/
function namespacify() {
var result = NAMESPACE;
for (var i = 0; i < arguments.length; ++i) {
result += '-' + arguments[i];
}
return result;
}
/**
* Handles the hashchange event
* @private
* @listens hashchange
*/
function handleHashChangeEvent() {
var id = location.hash.replace('#', '');
var instance;
var $elem;
var current = currentModal();
if (!id) {
// Check if we have currently opened modal and animation was completed
if (current && current.state === STATES.OPENED && current.settings.hashTracking) {
current.close();
}
} else {
if (!current || current.id !== id) {
// Catch syntax error if your hash is bad
try {
$elem = $(
'[data-' + PLUGIN_NAME + '-id="' + id + '"]'
);
} catch (err) {
}
if ($elem && $elem.length) {
instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
if (instance && instance.settings.hashTracking) {
instance.open();
}
}
}
}
}
function currentModal() {
return openModals[openModals.length - 1];
}
function removeModal(remodal) {
var index = openModals.indexOf(remodal);
if (index >= 0) {
openModals.slice(index, 1);
}
}
/**
* Remodal constructor
* @constructor
* @param {jQuery} $modal
* @param {Object} options
*/
function Remodal($modal, options) {
var $body = $(document.body);
var $appendTo = $body;
var remodal = this;
remodal.id = $modal.attr('data-' + PLUGIN_NAME + '-id');
remodal.settings = $.extend({}, DEFAULTS, options);
remodal.index = $[PLUGIN_NAME].lookup.push(remodal) - 1;
remodal.state = STATES.CLOSED;
// remodal.$overlay = $('.' + namespacify('overlay'));
if (remodal.settings.appendTo !== null && remodal.settings.appendTo.length) {
$appendTo = $(remodal.settings.appendTo);
}
if (!remodal.$overlay) {
remodal.$overlay = $('<div>').addClass(namespacify('overlay') + ' ' + namespacify('is', STATES.CLOSED)).hide();
$appendTo.append(remodal.$overlay);
}
remodal.$bg = $('.' + namespacify('bg')).addClass(namespacify('is', STATES.CLOSED));
remodal.$modal = $modal
.addClass(
NAMESPACE + ' ' +
namespacify('is-initialized') + ' ' +
remodal.settings.modifier + ' ' +
namespacify('is', STATES.CLOSED))
.attr('tabindex', '-1');
remodal.$wrapper = $('<div>')
.addClass(
namespacify('wrapper') + ' ' +
remodal.settings.modifier + ' ' +
namespacify('is', STATES.CLOSED))
.hide()
.append(remodal.$modal);
$appendTo.append(remodal.$wrapper);
// Add the event listener for the close button
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="close"]', function(e) {
e.preventDefault();
remodal.close();
});
// Add the event listener for the cancel button
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="cancel"]', function(e) {
e.preventDefault();
remodal.$modal.trigger(STATE_CHANGE_REASONS.CANCELLATION);
if (remodal.settings.closeOnCancel) {
remodal.close(STATE_CHANGE_REASONS.CANCELLATION);
}
});
// Add the event listener for the confirm button
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="confirm"]', function(e) {
e.preventDefault();
remodal.$modal.trigger(STATE_CHANGE_REASONS.CONFIRMATION);
if (remodal.settings.closeOnConfirm) {
remodal.close(STATE_CHANGE_REASONS.CONFIRMATION);
}
});
// Add the event listener for the overlay
remodal.$wrapper.on('click.' + NAMESPACE, function(e) {
var $target = $(e.target);
var isWrapper = $target.hasClass(namespacify('wrapper'));
var isWithin = $target.closest('.' + namespacify('is', STATES.OPENED)).length;
if (!isWrapper && isWithin) {
return;
}
if (remodal.settings.closeOnOutsideClick) {
remodal.close();
}
});
}
/**
* Opens a modal window
* @public
*/
Remodal.prototype.open = function() {
var remodal = this;
var current;
var modalCount;
// Check if the animation was completed
if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) {
return;
}
// id = remodal.$modal.attr('data-' + PLUGIN_NAME + '-id');
if (remodal.id && remodal.settings.hashTracking) {
scrollTop = $(window).scrollTop();
location.hash = remodal.id;
}
if (!remodal.settings.stack) {
current = currentModal();
if (current && current !== remodal) {
halt(current);
}
}
modalCount = openModals.push(remodal);
remodal.$overlay.css('z-index', function(_, value) { return parseInt(value, 10) + modalCount; });
remodal.$wrapper.css('z-index', function(_, value) { return parseInt(value, 10) + modalCount; });
lockScreen();
remodal.$bg.addClass(remodal.settings.modifier);
remodal.$overlay.addClass(remodal.settings.modifier).show();
remodal.$wrapper.show().scrollTop(0);
remodal.$modal.focus();
syncWithAnimation(
function() {
setState(remodal, STATES.OPENING);
},
function() {
setState(remodal, STATES.OPENED);
},
remodal);
};
/**
* Closes a modal window
* @public
* @param {String} reason
*/
Remodal.prototype.close = function(reason) {
var remodal = this;
var current;
// Check if the animation was completed
if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING || remodal.state === STATES.CLOSED) {
return;
}
removeModal(remodal);
if (
remodal.settings.hashTracking &&
remodal.id === location.hash.substr(1)
) {
current = currentModal();
if (current) {
location.hash = current.id;
} else {
location.hash = '';
$(window).scrollTop(scrollTop);
}
}
syncWithAnimation(
function() {
setState(remodal, STATES.CLOSING, false, reason);
},
function() {
remodal.$bg.removeClass(remodal.settings.modifier);
remodal.$overlay.removeClass(remodal.settings.modifier).hide();
remodal.$wrapper.hide();
if (openModals.length === 0) {
unlockScreen();
}
setState(remodal, STATES.CLOSED, false, reason);
},
remodal);
};
/**
* Returns a current state of a modal
* @public
* @returns {STATES}
*/
Remodal.prototype.getState = function() {
return this.state;
};
/**
* Destroys a modal
* @public
*/
Remodal.prototype.destroy = function() {
var lookup = $[PLUGIN_NAME].lookup;
var instanceCount;
halt(this);
this.$wrapper.remove();
delete lookup[this.index];
instanceCount = $.grep(lookup, function(instance) {
return !!instance;
}).length;
if (instanceCount === 0) {
this.$overlay.remove();
this.$bg.removeClass(
namespacify('is', STATES.CLOSING) + ' ' +
namespacify('is', STATES.OPENING) + ' ' +
namespacify('is', STATES.CLOSED) + ' ' +
namespacify('is', STATES.OPENED));
}
};
/**
* Special plugin object for instances
* @public
* @type {Object}
*/
$[PLUGIN_NAME] = {
lookup: []
};
/**
* Plugin constructor
* @constructor
* @param {Object} options
* @returns {JQuery}
*/
$.fn[PLUGIN_NAME] = function(opts) {
var instance;
var $elem;
this.each(function(index, elem) {
$elem = $(elem);
if ($elem.data(PLUGIN_NAME) == null) {
instance = new Remodal($elem, opts);
$elem.data(PLUGIN_NAME, instance.index);
if (
instance.settings.hashTracking &&
instance.id === location.hash.substr(1)
) {
instance.open();
}
} else {
instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
}
});
return instance;
};
$(document).ready(function() {
// data-remodal-target opens a modal window with the special Id
$(document).on('click', '[data-' + PLUGIN_NAME + '-target]', function(e) {
e.preventDefault();
var elem = e.currentTarget;
var id = elem.getAttribute('data-' + PLUGIN_NAME + '-target');
var $target = $('[data-' + PLUGIN_NAME + '-id="' + id + '"]');
$[PLUGIN_NAME].lookup[$target.data(PLUGIN_NAME)].open();
});
// Auto initialization of modal windows
// They should have the 'remodal' class attribute
// Also you can write the `data-remodal-options` attribute to pass params into the modal
$(document).find('.' + NAMESPACE).each(function(i, container) {
var $container = $(container);
var options = $container.data(PLUGIN_NAME + '-options');
if (!options) {
options = {};
} else if (typeof options === 'string' || options instanceof String) {
options = parseOptions(options);
}
$container[PLUGIN_NAME](options);
});
// Handles the keydown event
$(document).on('keydown.' + NAMESPACE, function(e) {
var current = currentModal();
if (current && current.settings.closeOnEscape && current.state === STATES.OPENED && e.keyCode === 27) {
current.close();
}
});
// Handles the hashchange event
$(window).on('hashchange.' + NAMESPACE, handleHashChangeEvent);
});
});

View File

@@ -0,0 +1,38 @@
import { parseStatus, parseJSON, userFeedback, userFeedbackError } from './response';
import { config } from 'grav-config';
let raw;
let request = function(url, options = {}, callback = () => true) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (options.method && options.method === 'post') {
let data = new FormData();
options.body = Object.assign({ 'admin-nonce': config.admin_nonce }, options.body || {});
Object.keys(options.body).map((key) => data.append(key, options.body[key]));
options.body = data;
}
options = Object.assign({
credentials: 'same-origin',
headers: {
'Accept': 'application/json'
}
}, options);
return fetch(url, options)
.then((response) => {
raw = response;
return response;
})
.then(parseStatus)
.then(parseJSON)
.then(userFeedback)
.then((response) => callback(response, raw))
.catch(userFeedbackError);
};
export default request;

View File

@@ -0,0 +1,101 @@
import $ from 'jquery';
import toastr from './toastr';
import isOnline from './offline';
import { config } from 'grav-config';
import trim from 'mout/string/trim';
let UNLOADING = false;
let error = function(response) {
let error = new Error(response.statusText || response || '');
error.response = response;
return error;
};
export function parseStatus(response) {
return response;
/* Whoops can handle JSON responses so we don't need this for now.
if (response.status >= 200 && response.status < 300) {
return response;
} else {
throw error(response);
}
*/
}
export function parseJSON(response) {
return response.text().then((text) => {
let parsed = text;
try {
parsed = JSON.parse(text);
} catch (error) {
let content = document.createElement('div');
content.innerHTML = text;
let the_error = new Error();
the_error.stack = trim(content.innerText);
throw the_error;
}
return parsed;
});
}
export function userFeedback(response) {
if (UNLOADING) { return true; }
let status = response.status || (response.error ? 'error' : '');
let message = response.message || (response.error ? response.error.message : null);
let settings = response.toastr || null;
let backup;
switch (status) {
case 'unauthenticated':
document.location.href = config.base_url_relative;
throw error('Logged out');
case 'unauthorized':
status = 'error';
message = message || 'Unauthorized.';
break;
case 'error':
status = 'error';
message = message || 'Unknown error.';
break;
case 'success':
status = 'success';
message = message || '';
break;
default:
status = 'error';
message = message || 'Invalid AJAX response.';
break;
}
if (settings) {
backup = Object.assign({}, toastr.options);
Object.keys(settings).forEach((key) => { toastr.options[key] = settings[key]; });
}
if (message && (isOnline || (!isOnline && status !== 'error'))) {
toastr[status === 'success' ? 'success' : 'error'](message);
}
if (settings) {
toastr.options = backup;
}
return response;
}
export function userFeedbackError(error) {
if (UNLOADING) { return true; }
let stack = error.stack ? `<pre><code>${error.stack}</code></pre>` : '';
toastr.error(`Fetch Failed: <br /> ${error.message} ${stack}`);
console.error(`${error.message} at ${error.stack}`);
}
$(global).on('beforeunload._ajax', () => {
UNLOADING = true;
});

View File

@@ -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);
}
});
};
});

View File

@@ -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');
}
};
});

View File

@@ -0,0 +1,180 @@
import $ from 'jquery';
import Cookies from '../utils/cookies';
const MOBILE_BREAKPOINT = 48 - 0.062;
const DESKTOP_BREAKPOINT = 75 + 0.063;
const EVENTS = 'touchstart._grav click._grav';
const TARGETS = '[data-sidebar-mobile-toggle], #overlay';
const MOBILE_QUERY = `(max-width: ${MOBILE_BREAKPOINT}em)`;
const DESKTOP_QUERY = `(min-width: ${DESKTOP_BREAKPOINT}em)`;
let map = new global.Map();
export default class Sidebar {
constructor() {
this.timeout = null;
this.isOpen = false;
this.body = $('body');
this.matchMedia = global.matchMedia(MOBILE_QUERY);
this.enable();
}
enable() {
const sidebar = $('#admin-sidebar');
this.matchMedia.addListener(this._getBound('checkMatch'));
this.checkMatch(this.matchMedia);
this.body.on(EVENTS, '[data-sidebar-toggle]', this._getBound('toggleSidebarState'));
if (sidebar.data('quickopen')) {
sidebar.hover(this._getBound('quickOpenIn'), this._getBound('quickOpenOut'));
}
}
disable() {
const sidebar = $('#admin-sidebar');
this.close();
this.matchMedia.removeListener(this._getBound('checkMatch'));
this.body.off(EVENTS, '[data-sidebar-toggle]', this._getBound('toggleSidebarState'));
if (sidebar.data('quickopen')) {
sidebar.off('mouseenter mouseleave');
}
}
attach() {
this.body.on(EVENTS, TARGETS, this._getBound('toggle'));
}
detach() {
this.body.off(EVENTS, TARGETS, this._getBound('toggle'));
}
quickOpenIn(/* event */) {
let isDesktop = global.matchMedia(DESKTOP_QUERY).matches;
let delay = $('#admin-sidebar').data('quickopen-delay') || 500;
if (this.body.hasClass('sidebar-mobile-open')) { return; }
let shouldQuickOpen = isDesktop ? this.body.hasClass('sidebar-closed') : !this.body.hasClass('sidebar-open');
if (!shouldQuickOpen && !this.body.hasClass('sidebar-quickopen')) { return this.quickOpenOut(); }
this.timeout = setTimeout(() => {
this.body.addClass('sidebar-open sidebar-quickopen');
$(global).trigger('sidebar_state._grav', isDesktop);
}, delay);
}
quickOpenOut(/* event */) {
clearTimeout(this.timeout);
if (this.body.hasClass('sidebar-quickopen')) {
this.body.removeClass('sidebar-open sidebar-quickopen');
}
return true;
}
open(event, quick = false) {
if (event) { event.preventDefault(); }
let overlay = $('#overlay');
let sidebar = $('#admin-sidebar');
this.body.addClass('sidebar-mobile-open');
overlay.css('display', 'block');
if (!quick) {
sidebar.css('display', 'block').animate({
opacity: 1
}, 200, () => {
this.isOpen = true;
});
} else {
sidebar.css({ display: 'block', opacity: 1 });
this.isOpen = true;
}
}
close(event, quick = false) {
if (event) { event.preventDefault(); }
let overlay = $('#overlay');
let sidebar = $('#admin-sidebar');
this.body.removeClass('sidebar-mobile-open');
overlay.css('display', 'none');
if (!quick) {
sidebar.animate({
opacity: 0
}, 200, () => {
sidebar.css('display', 'none');
this.isOpen = false;
});
} else {
sidebar.css({ opacity: 0, display: 'none' });
this.isOpen = false;
}
}
toggle(event) {
if (event) { event.preventDefault(); }
return this[this.isOpen ? 'close' : 'open'](event);
}
toggleSidebarState(event) {
if (event) { event.preventDefault(); }
clearTimeout(this.timeout);
let isDesktop = global.matchMedia(DESKTOP_QUERY).matches;
let cookie = null;
if (isDesktop) {
this.body.removeClass('sidebar-open');
}
if (!isDesktop) {
this.body.removeClass('sidebar-closed');
this.body.removeClass('sidebar-mobile-open');
}
this.body.toggleClass(`sidebar-${isDesktop ? 'closed' : 'open'}`);
$(global).trigger('sidebar_state._grav', isDesktop);
if (isDesktop) {
cookie = !this.body.hasClass('sidebar-closed');
} else {
cookie = this.body.hasClass('sidebar-open');
}
Cookies.set('grav-admin-sidebar', cookie, { expires: Infinity });
}
checkMatch(data) {
let sidebar = $('#admin-sidebar');
let overlay = $('#overlay');
this.isOpen = false;
overlay.css('display', 'none');
sidebar.css({
display: data.matches ? 'none' : 'inherit',
opacity: data.matches ? 0 : 1
});
if (data.matches) {
this.body.removeClass('sidebar-open sidebar-closed');
}
this[data.matches ? 'attach' : 'detach']();
}
_resetMap() {
return map.clear();
}
_getBound(fn) {
if (map.has(fn)) {
return map.get(fn);
}
return map.set(fn, this[fn].bind(this)).get(fn);
}
}
export let Instance = new Sidebar();

View File

@@ -0,0 +1,41 @@
// localStorage
(function() {
function isSupported() {
var item = 'localStoragePollyfill';
try {
localStorage.setItem(item, item);
localStorage.removeItem(item);
sessionStorage.setItem(item, item);
sessionStorage.removeItem(item);
return true;
} catch (e) {
return false;
}
}
if (!isSupported()) {
try {
Storage.prototype._data = {};
Storage.prototype.setItem = function(id, val) {
this._data[id] = String(val);
return this._data[id];
};
Storage.prototype.getItem = function(id) {
return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
};
Storage.prototype.removeItem = function(id) {
return delete this._data[id];
};
Storage.prototype.clear = function() {
this._data = {};
return this._data;
};
} catch (e) {
console.error('localStorage pollyfill error: ', e);
}
}
}());

View File

@@ -0,0 +1,29 @@
import $ from 'jquery';
import Cookies from '../utils/cookies';
import { Instance as Editors } from '../forms/fields/editor';
let Data = JSON.parse(Cookies.get('grav-tabs-state') || '{}');
$('body').on('touchstart click', '[data-tabid]', (event) => {
event && event.stopPropagation();
let target = $(event.currentTarget);
Data[target.data('tabkey')] = target.data('scope');
Cookies.set('grav-tabs-state', JSON.stringify(Data), { expires: Infinity });
const panel = $(`[id="${target.data('tabid')}"]`);
target.siblings('[data-tabid]').removeClass('active');
target.addClass('active');
panel.siblings('[id]').removeClass('active');
panel.addClass('active');
Editors.editors.each((index, editor) => {
let codemirror = $(editor).data('codemirror');
if (!codemirror) { return; }
if (codemirror.display.lastWrapWidth === 0) {
codemirror.refresh();
}
});
});

View File

@@ -0,0 +1,6 @@
import toastr from 'toastr';
toastr.options.positionClass = 'toast-top-right';
toastr.options.preventDuplicates = true;
export default toastr;

View File

@@ -0,0 +1,25 @@
import { config } from 'grav-config';
import request from '../utils/request';
export default ({ preview = false, exportScss = false, color_scheme = {}, fonts = {}, callback = () => {} } = {}) => {
let task = exportScss ? 'exportScss' : 'compileScss';
// console.log(config);
const URI = `${config.base_url_relative}.json/task:${task}`;
request(URI, {
method: 'post',
body: Object.assign({}, preview ? { preview } : null, color_scheme)
}, callback);
};
export const prepareElement = (element) => {
element.data('busy_right_now', true);
if (!element.data('current_icon')) {
element.data('current_icon', element.find('.fa').attr('class'));
}
element.find('.fa').attr('class', 'fa fa-fw fa-spin fa-refresh');
};
export const resetElement = (element) => {
element.data('busy_right_now', false);
element.find('.fa').attr('class', element.data('current_icon'));
};

View File

@@ -0,0 +1,93 @@
import $ from 'jquery';
import Compile, { prepareElement, resetElement } from './compile';
import Forms from '../forms';
import { hex2rgb } from '../utils/colors';
import './presets';
const body = $('body');
const FormState = Forms.FormState.Instance;
const compiler = (element, preview = false, exportScss = false, callback = () => {}) => {
prepareElement(element);
let fields = FormState.collect();
Compile({
preview,
exportScss,
color_scheme: !fields ? [] : fields.filter((value, key) => key.match(/^data\[whitelabel]\[color_scheme]/)).toJS(),
callback: (response) => {
callback.call(callback, response);
resetElement(element);
}
});
};
body.on('click', '[data-preview-scss]', (event) => {
event && event.preventDefault();
let element = $(event.currentTarget);
if (element.data('busy_right_now')) { return false; }
compiler(element, true, false, (response) => {
if (response.files) {
Object.keys(response.files).forEach((key) => {
let file = $(`#admin-pro-preview-${key}`);
let timestamp = Date.now();
if (!file.length) {
file = $(`<link id="admin-pro-preview-${key}" type="text/css" rel="stylesheet" />`);
$('head').append(file);
if (!$('[data-reset-scss]').length) {
let reset = $('<button class="button" data-reset-scss style="margin-left: 5px;"><i class="fa fa-fw fa-history"></i> Reset</button>');
reset.insertAfter(element);
}
}
file.attr('href', `${response.files[key]}?${timestamp}`);
});
}
});
});
body.on('click', '[data-recompile-scss]', (event) => {
event && event.preventDefault();
let element = $(event.currentTarget);
if (element.data('busy_right_now')) { return false; }
compiler(element, true, false);
});
body.on('click', '[data-export-scss]', (event) => {
event && event.preventDefault();
let element = $(event.currentTarget);
if (element.data('busy_right_now')) { return false; }
compiler(element, true, true, (response) => {
if (response.files) {
Object.keys(response.files).forEach((key) => {
if (key === 'download') {
let element = document.createElement('a');
element.setAttribute('href', response.files[key]);
element.setAttribute('download', '');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
});
}
});
});
body.on('change._grav_colorpicker', '[data-grav-colorpicker]', (event, input, hex, opacity) => {
let RGB = hex2rgb(hex);
let YIQ = ((RGB.r * 299) + (RGB.g * 587) + (RGB.b * 114)) / 1000;
let contrast = YIQ >= 128 || opacity <= 0.50 ? 'dark' : 'light';
input.parent().removeClass('dark-text light-text').addClass(`${contrast}-text`);
});
body.ready(() => {
$('[data-grav-colorpicker]').trigger('keyup');
});

View File

@@ -0,0 +1,169 @@
import $ from 'jquery';
import Forms from '../forms';
let body = $('body');
let fields = [];
const FormState = Forms.FormState.Instance;
const setField = (field, value) => {
let name = field.prop('name');
let tag = field.prop('tagName').toLowerCase();
let type = field.prop('type');
fields.push(name);
switch (tag) {
case 'select':
field.val(value);
field.data('selectize').setValue(value);
field.trigger('change');
break;
case 'input':
if (type === 'radio') {
let strValue = value ? '1' : '0';
field.filter((index, radio) => $(radio).val() === strValue).prop('checked', true);
break;
}
if (type === 'checkbox') {
field.prop('checked', value);
break;
}
field.val(value);
field.trigger('keyup');
}
};
body.on('click', '[data-preset-values]', (event) => {
let target = $(event.currentTarget);
let data = target.data('preset-values');
Object.keys(data).forEach((section) => {
if (typeof data[section] === 'string') {
return;
}
Object.keys(data[section]).forEach((key) => {
let field = $(`[name="data[whitelabel][color_scheme][${section}][${key}]"], [name="data[${section}][${key}]"]`);
let value = data[section][key];
setField(field, value);
});
});
});
body.on('click', '[data-reset-scss]', (event) => {
event && event.preventDefault();
let element = $(event.currentTarget);
let links = $('link[id^=admin-pro-preview-]');
element.remove();
links.remove();
fields.forEach((field) => {
let value = FormState.loadState.get(field);
setField($(`[name="${field}"]`), value);
});
fields = [];
});
// Horizontal Scroll Functionality
$.fn.hscrollarrows = function() {
return this.each(function() {
let navNext = $('<a class="nav-next hide"></a>');
let navPrev = $('<a class="nav-prev hide"></a>');
let scrollTime = null;
let resizeTime = null;
let scrolling = false;
let elm_w = 0;
let elem_data_w = 0;
let max_scroll = 0;
let inc_scroll = 0;
let calcData = function() {
elm_w = elem.width();
elem_data_w = elem_data.get(0).scrollWidth;
max_scroll = elem_data_w - elm_w;
inc_scroll = elm_w * 0.3; // 20%
};
let revalidate = function() {
calcData();
stateNavs();
};
let run = function() {
calcData();
setupNavs();
};
let setupNavs = function() {
elem.parent().prepend(navNext);
elem.parent().prepend(navPrev);
navNext.on('click', next);
navPrev.on('click', prev);
stateNavs();
$(elem).scroll(function() {
if (!scrolling) {
clearTimeout(scrollTime);
scrollTime = setTimeout(function() {
stateNavs();
}, 250);
}
});
$(window).resize(function() {
clearTimeout(resizeTime);
resizeTime = setTimeout(function() {
revalidate();
}, 250);
});
};
let stateNavs = function() {
let current_scroll = elem.scrollLeft();
if (current_scroll < max_scroll) {
navNext.removeClass('hide');
} else {
navNext.addClass('hide');
}
if (current_scroll > 0) {
navPrev.removeClass('hide');
} else {
navPrev.addClass('hide');
}
scrolling = false;
};
let next = function() {
let current_scroll = elem.scrollLeft();
if (current_scroll < max_scroll) {
scrolling = true;
elem.stop().animate({
scrollLeft: (current_scroll + inc_scroll)
}, stateNavs);
}
};
let prev = function() {
let current_scroll = elem.scrollLeft();
if (current_scroll > 0) {
scrolling = true;
elem.stop().animate({
scrollLeft: (current_scroll - inc_scroll)
}, stateNavs);
}
};
let elem = $(this);
let elem_data = $(':first-child', elem);
run();
});
};
$(document).ready(() => {
$('.jquery-horizontal-scroll').hscrollarrows();
});

View File

@@ -0,0 +1,625 @@
*, *::before, *::after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box; }
@-webkit-viewport {
width: device-width; }
@-moz-viewport {
width: device-width; }
@-ms-viewport {
width: device-width; }
@-o-viewport {
width: device-width; }
@viewport {
width: device-width; }
html {
font-size: 100%;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
body {
margin: 0; }
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block; }
audio,
canvas,
progress,
video {
display: inline-block;
vertical-align: baseline; }
audio:not([controls]) {
display: none;
height: 0; }
[hidden],
template {
display: none; }
a {
background: transparent;
text-decoration: none; }
a:active,
a:hover {
outline: 0; }
abbr[title] {
border-bottom: 1px dotted; }
b,
strong {
font-weight: bold; }
dfn {
font-style: italic; }
mark {
background: #ff0;
color: #000; }
sub,
sup {
font-size: 0.65rem;
line-height: 0;
position: relative;
vertical-align: baseline; }
sup {
top: -0.5em; }
sub {
bottom: -0.25em; }
img {
border: 0;
max-width: 100%; }
svg:not(:root) {
overflow: hidden; }
figure {
margin: 1em 40px; }
hr {
height: 0; }
pre {
overflow: auto; }
code,
kbd,
pre,
samp {
font-size: 0.9rem; }
button,
input,
optgroup,
select,
textarea {
color: inherit;
font: inherit;
margin: 0; }
button {
overflow: visible; }
button,
select {
text-transform: none; }
button,
html input[type="button"],
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button;
cursor: pointer; }
button[disabled],
html input[disabled] {
cursor: default; }
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0; }
input {
line-height: normal; }
input[type="checkbox"],
input[type="radio"] {
padding: 0; }
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto; }
input[type="search"] {
-webkit-appearance: textfield; }
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none; }
legend {
border: 0;
padding: 0; }
textarea {
overflow: auto; }
optgroup {
font-weight: bold; }
table {
border-collapse: collapse;
border-spacing: 0;
table-layout: fixed;
width: 100%; }
tr, td, th {
vertical-align: middle; }
th, td {
padding: 0.375rem 0; }
th {
text-align: left; }
.container {
width: 75em;
margin: 0 auto;
padding: 0; }
@media only all and (min-width: 60em) and (max-width: 74.938em) {
.container {
width: 60em; } }
@media only all and (min-width: 48em) and (max-width: 59.938em) {
.container {
width: 48em; } }
@media only all and (min-width: 30.063em) and (max-width: 47.938em) {
.container {
width: 30em; } }
@media only all and (max-width: 30em) {
.container {
width: 100%; } }
.grid {
display: -webkit-box;
display: -moz-box;
display: box;
display: -webkit-flex;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-flow: row;
-moz-flex-flow: row;
flex-flow: row;
list-style: none;
margin: 0;
padding: 0; }
@media only all and (max-width: 47.938em) {
.grid {
-webkit-flex-flow: row wrap;
-moz-flex-flow: row wrap;
flex-flow: row wrap; } }
.block {
-webkit-box-flex: 1;
-moz-box-flex: 1;
box-flex: 1;
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1; }
@media only all and (max-width: 47.938em) {
.block {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 100%;
-moz-flex: 0 100%;
-ms-flex: 0 100%;
flex: 0 100%; } }
.content {
margin: 0.625rem;
padding: 0.938rem; }
@media only all and (max-width: 47.938em) {
body [class*="size-"] {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 100%;
-moz-flex: 0 100%;
-ms-flex: 0 100%;
flex: 0 100%; } }
.size-1-2 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 50%;
-moz-flex: 0 50%;
-ms-flex: 0 50%;
flex: 0 50%; }
.size-1-3 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 33.33333%;
-moz-flex: 0 33.33333%;
-ms-flex: 0 33.33333%;
flex: 0 33.33333%; }
.size-1-4 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 25%;
-moz-flex: 0 25%;
-ms-flex: 0 25%;
flex: 0 25%; }
.size-1-5 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 20%;
-moz-flex: 0 20%;
-ms-flex: 0 20%;
flex: 0 20%; }
.size-1-6 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 16.66667%;
-moz-flex: 0 16.66667%;
-ms-flex: 0 16.66667%;
flex: 0 16.66667%; }
.size-1-7 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 14.28571%;
-moz-flex: 0 14.28571%;
-ms-flex: 0 14.28571%;
flex: 0 14.28571%; }
.size-1-8 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 12.5%;
-moz-flex: 0 12.5%;
-ms-flex: 0 12.5%;
flex: 0 12.5%; }
.size-1-9 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 11.11111%;
-moz-flex: 0 11.11111%;
-ms-flex: 0 11.11111%;
flex: 0 11.11111%; }
.size-1-10 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 10%;
-moz-flex: 0 10%;
-ms-flex: 0 10%;
flex: 0 10%; }
.size-1-11 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 9.09091%;
-moz-flex: 0 9.09091%;
-ms-flex: 0 9.09091%;
flex: 0 9.09091%; }
.size-1-12 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 8.33333%;
-moz-flex: 0 8.33333%;
-ms-flex: 0 8.33333%;
flex: 0 8.33333%; }
@media only all and (min-width: 48em) and (max-width: 59.938em) {
.size-tablet-1-2 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 50%;
-moz-flex: 0 50%;
-ms-flex: 0 50%;
flex: 0 50%; }
.size-tablet-1-3 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 33.33333%;
-moz-flex: 0 33.33333%;
-ms-flex: 0 33.33333%;
flex: 0 33.33333%; }
.size-tablet-1-4 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 25%;
-moz-flex: 0 25%;
-ms-flex: 0 25%;
flex: 0 25%; }
.size-tablet-1-5 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 20%;
-moz-flex: 0 20%;
-ms-flex: 0 20%;
flex: 0 20%; }
.size-tablet-1-6 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 16.66667%;
-moz-flex: 0 16.66667%;
-ms-flex: 0 16.66667%;
flex: 0 16.66667%; }
.size-tablet-1-7 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 14.28571%;
-moz-flex: 0 14.28571%;
-ms-flex: 0 14.28571%;
flex: 0 14.28571%; }
.size-tablet-1-8 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 12.5%;
-moz-flex: 0 12.5%;
-ms-flex: 0 12.5%;
flex: 0 12.5%; }
.size-tablet-1-9 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 11.11111%;
-moz-flex: 0 11.11111%;
-ms-flex: 0 11.11111%;
flex: 0 11.11111%; }
.size-tablet-1-10 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 10%;
-moz-flex: 0 10%;
-ms-flex: 0 10%;
flex: 0 10%; }
.size-tablet-1-11 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 9.09091%;
-moz-flex: 0 9.09091%;
-ms-flex: 0 9.09091%;
flex: 0 9.09091%; }
.size-tablet-1-12 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
-webkit-flex: 0 8.33333%;
-moz-flex: 0 8.33333%;
-ms-flex: 0 8.33333%;
flex: 0 8.33333%; } }
@media only all and (max-width: 47.938em) {
@supports not (flex-wrap: wrap) {
.grid {
display: block;
-webkit-box-lines: inherit;
-moz-box-lines: inherit;
box-lines: inherit;
-webkit-flex-wrap: inherit;
-moz-flex-wrap: inherit;
-ms-flex-wrap: inherit;
flex-wrap: inherit; }
.block {
display: block;
-webkit-box-flex: inherit;
-moz-box-flex: inherit;
box-flex: inherit;
-webkit-flex: inherit;
-moz-flex: inherit;
-ms-flex: inherit;
flex: inherit; } } }
.first-block {
-webkit-box-ordinal-group: 0;
-webkit-order: -1;
-ms-flex-order: -1;
order: -1; }
.last-block {
-webkit-box-ordinal-group: 2;
-webkit-order: 1;
-ms-flex-order: 1;
order: 1; }
.fixed-blocks {
-webkit-flex-flow: row wrap;
-moz-flex-flow: row wrap;
flex-flow: row wrap; }
.fixed-blocks .block {
-webkit-box-flex: inherit;
-moz-box-flex: inherit;
box-flex: inherit;
-webkit-flex: inherit;
-moz-flex: inherit;
-ms-flex: inherit;
flex: inherit;
width: 25%; }
@media only all and (min-width: 60em) and (max-width: 74.938em) {
.fixed-blocks .block {
width: 33.33333%; } }
@media only all and (min-width: 48em) and (max-width: 59.938em) {
.fixed-blocks .block {
width: 50%; } }
@media only all and (max-width: 47.938em) {
.fixed-blocks .block {
width: 100%; } }
@supports not (flex-wrap: wrap) {
.fixed-blocks {
display: block;
-webkit-flex-flow: inherit;
-moz-flex-flow: inherit;
flex-flow: inherit; } }
body {
font-size: 0.9rem;
line-height: 1.5; }
h1, h2, h3, h4, h5, h6 {
margin: 1.5rem 0 0.75rem 0;
text-rendering: optimizeLegibility; }
h1 {
font-size: 3.1rem; }
h2 {
font-size: 2.4rem; }
h3 {
font-size: 2rem; }
h4 {
font-size: 1.65rem; }
h5 {
font-size: 1.25rem; }
h6 {
font-size: 0.75rem; }
p {
margin: 0.75rem 0 1.5rem; }
ul, ol {
margin-top: 1.5rem;
margin-bottom: 1.5rem; }
ul ul, ul ol, ol ul, ol ol {
margin-top: 0;
margin-bottom: 0; }
blockquote {
margin: 1.5rem 0;
padding: 0.5rem; }
blockquote p:last-child {
margin: 0; }
cite {
display: block;
font-size: 0.775rem; }
cite:before {
content: "\2014 \0020"; }
pre {
margin: 1.5rem 0;
padding: 0.938rem; }
code {
vertical-align: bottom; }
small {
font-size: 0.775rem; }
hr {
border-left: none;
border-right: none;
border-top: none;
margin: 1.5rem 0; }
fieldset {
border: 0;
padding: 0.938rem;
margin: 0 0 1.5rem 0; }
input,
label,
select {
display: block; }
label {
margin-bottom: 0.375rem; }
label.required:after {
content: "*"; }
label abbr {
display: none; }
textarea, input[type="email"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="url"], input[type="color"], input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="week"], select[multiple=multiple] {
-webkit-transition: border-color;
-moz-transition: border-color;
transition: border-color;
border-radius: 0.1875rem;
padding: 0.375rem 0.375rem;
width: 100%; }
textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus {
outline: none; }
textarea {
resize: vertical; }
input[type="checkbox"], input[type="radio"] {
display: inline;
margin-right: 0.375rem; }
input[type="file"] {
width: 100%; }
select {
width: auto;
max-width: 100%;
margin-bottom: 1.5rem; }
button,
input[type="submit"] {
cursor: pointer;
user-select: none;
vertical-align: middle;
white-space: nowrap;
border: inherit; }
/*# sourceMappingURL=nucleus.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
body, h5, h6,
.badge, .note, .grav-mdeditor-preview,
input, select, textarea, button, .selectize-input,
h1, h2, h3, h4,
.fontfamily-sans .CodeMirror pre,
#admin-menu li, .form-tabs > label, .label {
font-family: "Helvetica Neue", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; }
.CodeMirror pre,
code, kbd, pre, samp, .mono {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; }
/*# sourceMappingURL=simple-fonts.css.map */

View File

@@ -0,0 +1,10 @@
{
"version": 3,
"file": "simple-fonts.css",
"sources": [
"../scss/simple-fonts.scss",
"../hdr0"
],
"names": [],
"mappings": "AAAA,AAAA,IAAI,EAAE,EAAE,EAAE,EAAE;AACZ,MAAM,EAAE,KAAK,EAAE,sBAAsB;AACrC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB;AACjD,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;AACd,gBAAgB,CAAC,WAAW,CAAC,GAAG;AAChC,WAAW,CAAC,EAAE,EAAE,UAAU,GAAG,KAAK,EAAE,MAAM,CAAE;EACxC,WAAW,EAAE,sEAAsE,GACtF;;AAED,AAAA,WAAW,CAAC,GAAG;AACf,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAE;EACzB,WAAW,EAAE,wEAAwE,GACxF"
}

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

View File

@@ -0,0 +1,334 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror .CodeMirror-cursor {
border-left: 1px solid black;
}
/* Shown when moving in bi-directional text */
.CodeMirror .CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
width: auto;
border: 0;
background: #7e7;
}
.CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actuall scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre { position: static; }
.CodeMirror div.CodeMirror-cursor {
position: absolute;
border-right: none;
width: 0;
}
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

View File

@@ -0,0 +1,41 @@
/*
Name: 3024 day
Author: Jan T. Sott (https://github.com/idleberg)
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-3024-day.CodeMirror { background: #f7f7f7; color: #3a3432; }
.cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4; }
.cm-s-3024-day .CodeMirror-line::selection, .cm-s-3024-day .CodeMirror-line > span::selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4; }
.cm-s-3024-day .CodeMirror-line::-moz-selection, .cm-s-3024-day .CodeMirror-line > span::-moz-selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9; }
.cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7; border-right: 0px; }
.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; }
.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; }
.cm-s-3024-day .CodeMirror-linenumber { color: #807d7c; }
.cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855; }
.cm-s-3024-day span.cm-comment { color: #cdab53; }
.cm-s-3024-day span.cm-atom { color: #a16a94; }
.cm-s-3024-day span.cm-number { color: #a16a94; }
.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute { color: #01a252; }
.cm-s-3024-day span.cm-keyword { color: #db2d20; }
.cm-s-3024-day span.cm-string { color: #fded02; }
.cm-s-3024-day span.cm-variable { color: #01a252; }
.cm-s-3024-day span.cm-variable-2 { color: #01a0e4; }
.cm-s-3024-day span.cm-def { color: #e8bbd0; }
.cm-s-3024-day span.cm-bracket { color: #3a3432; }
.cm-s-3024-day span.cm-tag { color: #db2d20; }
.cm-s-3024-day span.cm-link { color: #a16a94; }
.cm-s-3024-day span.cm-error { background: #db2d20; color: #5c5855; }
.cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important; }

View File

@@ -0,0 +1,39 @@
/*
Name: 3024 night
Author: Jan T. Sott (https://github.com/idleberg)
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-3024-night.CodeMirror { background: #090300; color: #d6d5d4; }
.cm-s-3024-night div.CodeMirror-selected { background: #3a3432; }
.cm-s-3024-night .CodeMirror-line::selection, .cm-s-3024-night .CodeMirror-line > span::selection, .cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99); }
.cm-s-3024-night .CodeMirror-line::-moz-selection, .cm-s-3024-night .CodeMirror-line > span::-moz-selection, .cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99); }
.cm-s-3024-night .CodeMirror-gutters { background: #090300; border-right: 0px; }
.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; }
.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; }
.cm-s-3024-night .CodeMirror-linenumber { color: #5c5855; }
.cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c; }
.cm-s-3024-night span.cm-comment { color: #cdab53; }
.cm-s-3024-night span.cm-atom { color: #a16a94; }
.cm-s-3024-night span.cm-number { color: #a16a94; }
.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute { color: #01a252; }
.cm-s-3024-night span.cm-keyword { color: #db2d20; }
.cm-s-3024-night span.cm-string { color: #fded02; }
.cm-s-3024-night span.cm-variable { color: #01a252; }
.cm-s-3024-night span.cm-variable-2 { color: #01a0e4; }
.cm-s-3024-night span.cm-def { color: #e8bbd0; }
.cm-s-3024-night span.cm-bracket { color: #d6d5d4; }
.cm-s-3024-night span.cm-tag { color: #db2d20; }
.cm-s-3024-night span.cm-link { color: #a16a94; }
.cm-s-3024-night span.cm-error { background: #db2d20; color: #807d7c; }
.cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F; }
.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }

View File

@@ -0,0 +1,32 @@
.cm-s-abcdef.CodeMirror { background: #0f0f0f; color: #defdef; }
.cm-s-abcdef div.CodeMirror-selected { background: #515151; }
.cm-s-abcdef .CodeMirror-line::selection, .cm-s-abcdef .CodeMirror-line > span::selection, .cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99); }
.cm-s-abcdef .CodeMirror-line::-moz-selection, .cm-s-abcdef .CodeMirror-line > span::-moz-selection, .cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99); }
.cm-s-abcdef .CodeMirror-gutters { background: #555; border-right: 2px solid #314151; }
.cm-s-abcdef .CodeMirror-guttermarker { color: #222; }
.cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure; }
.cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF; }
.cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00; }
.cm-s-abcdef span.cm-keyword { color: darkgoldenrod; font-weight: bold; }
.cm-s-abcdef span.cm-atom { color: #77F; }
.cm-s-abcdef span.cm-number { color: violet; }
.cm-s-abcdef span.cm-def { color: #fffabc; }
.cm-s-abcdef span.cm-variable { color: #abcdef; }
.cm-s-abcdef span.cm-variable-2 { color: #cacbcc; }
.cm-s-abcdef span.cm-variable-3, .cm-s-abcdef span.cm-type { color: #def; }
.cm-s-abcdef span.cm-property { color: #fedcba; }
.cm-s-abcdef span.cm-operator { color: #ff0; }
.cm-s-abcdef span.cm-comment { color: #7a7b7c; font-style: italic;}
.cm-s-abcdef span.cm-string { color: #2b4; }
.cm-s-abcdef span.cm-meta { color: #C9F; }
.cm-s-abcdef span.cm-qualifier { color: #FFF700; }
.cm-s-abcdef span.cm-builtin { color: #30aabc; }
.cm-s-abcdef span.cm-bracket { color: #8a8a8a; }
.cm-s-abcdef span.cm-tag { color: #FFDD44; }
.cm-s-abcdef span.cm-attribute { color: #DDFF00; }
.cm-s-abcdef span.cm-error { color: #FF0000; }
.cm-s-abcdef span.cm-header { color: aquamarine; font-weight: bold; }
.cm-s-abcdef span.cm-link { color: blueviolet; }
.cm-s-abcdef .CodeMirror-activeline-background { background: #314151; }

View File

@@ -0,0 +1,5 @@
.cm-s-ambiance.CodeMirror {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More