Reworked Admin JS with ES6

This commit is contained in:
Djamil Legato
2016-02-01 11:27:34 -08:00
parent c62fb6bdb5
commit 35fa588438
85 changed files with 31905 additions and 824 deletions

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
# 2 space indentation
[*.yaml, *.yml]
indent_style = space
indent_size = 2

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
themes/grav/.sass-cache themes/grav/.sass-cache
.DS_Store .DS_Store
# Node Modules
**/node_modules/**

View File

@@ -571,7 +571,7 @@ class AdminPlugin extends Plugin
$this->theme = $this->config->get('plugins.admin.theme', 'grav'); $this->theme = $this->config->get('plugins.admin.theme', 'grav');
$assets = $this->grav['assets']; $assets = $this->grav['assets'];
$translations = 'if (!window.translations) window.translations = {}; ' . PHP_EOL . 'window.translations.PLUGIN_ADMIN = {};' . PHP_EOL; $translations = 'this.GravAdmin = this.GravAdmin || {}; if (!this.GravAdmin.translations) this.GravAdmin.translations = {}; ' . PHP_EOL . 'this.GravAdmin.translations.PLUGIN_ADMIN = {';
// Enable language translations // Enable language translations
$translations_actual_state = $this->config->get('system.languages.translations'); $translations_actual_state = $this->config->get('system.languages.translations');
@@ -597,13 +597,16 @@ class AdminPlugin extends Plugin
'DAYS', 'DAYS',
'PAGE_MODES', 'PAGE_MODES',
'PAGE_TYPES', 'PAGE_TYPES',
'ACCESS_LEVELS' 'ACCESS_LEVELS',
'NOTHING_TO_SAVE'
]; ];
foreach($strings as $string) { foreach($strings as $string) {
$translations .= 'translations.PLUGIN_ADMIN.' . $string .' = "' . $this->admin->translate('PLUGIN_ADMIN.' . $string) . '"; ' . PHP_EOL;; $separator = (end($strings) === $string) ? '' : ',';
$translations .= '"' . $string .'": "' . $this->admin->translate('PLUGIN_ADMIN.' . $string) . '"' . $separator;
} }
$translations .= '};';
// set the actual translations state back // set the actual translations state back
$this->config->set('system.languages.translations', $translations_actual_state); $this->config->set('system.languages.translations', $translations_actual_state);

View File

@@ -447,6 +447,7 @@ class AdminController
'message' => $this->admin->translate('PLUGIN_ADMIN.YOUR_BACKUP_IS_READY_FOR_DOWNLOAD') . '. <a href="'.$url.'" class="button">' . $this->admin->translate('PLUGIN_ADMIN.DOWNLOAD_BACKUP') .'</a>', 'message' => $this->admin->translate('PLUGIN_ADMIN.YOUR_BACKUP_IS_READY_FOR_DOWNLOAD') . '. <a href="'.$url.'" class="button">' . $this->admin->translate('PLUGIN_ADMIN.DOWNLOAD_BACKUP') .'</a>',
'toastr' => [ 'toastr' => [
'timeOut' => 0, 'timeOut' => 0,
'extendedTimeOut' => 0,
'closeButton' => true 'closeButton' => true
] ]
]; ];
@@ -576,7 +577,7 @@ class AdminController
foreach ($page->media()->all() as $name => $media) { foreach ($page->media()->all() as $name => $media) {
$media_list[$name] = ['url' => $media->cropZoom(150, 100)->url(), 'size' => $media->get('size')]; $media_list[$name] = ['url' => $media->cropZoom(150, 100)->url(), 'size' => $media->get('size')];
} }
$this->admin->json_response = ['status' => 'ok', 'results' => $media_list]; $this->admin->json_response = ['status' => 'success', 'results' => $media_list];
return true; return true;
} }
@@ -646,6 +647,7 @@ class AdminController
return false; return false;
} }
Cache::clearCache();
$this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY')]; $this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY')];
return true; return true;
@@ -675,6 +677,7 @@ class AdminController
if (file_exists($targetPath)) { if (file_exists($targetPath)) {
if (unlink($targetPath)) { if (unlink($targetPath)) {
Cache::clearCache();
$this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_DELETED') . ': '.$filename]; $this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_DELETED') . ': '.$filename];
} else { } else {
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': '.$filename]; $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': '.$filename];
@@ -701,6 +704,7 @@ class AdminController
} }
if ($deletedResponsiveImage) { if ($deletedResponsiveImage) {
Cache::clearCache();
$this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_DELETED') . ': '.$filename]; $this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_DELETED') . ': '.$filename];
} else { } else {
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_NOT_FOUND') . ': '.$filename]; $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_NOT_FOUND') . ': '.$filename];
@@ -887,9 +891,9 @@ class AdminController
$result = \Grav\Plugin\Admin\Gpm::selfupgrade(); $result = \Grav\Plugin\Admin\Gpm::selfupgrade();
if ($result) { if ($result) {
$this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_WAS_SUCCESSFULLY_UPDATED_TO') . ' ']; $this->admin->json_response = ['status' => 'success', 'type' => 'updategrav', 'version' => GRAV_VERSION, 'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_WAS_SUCCESSFULLY_UPDATED_TO') . ' ' . GRAV_VERSION];
} else { } else {
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_UPDATE_FAILED') . ' <br>' . Installer::lastErrorMsg()]; $this->admin->json_response = ['status' => 'error', 'type' => 'updategrav', 'version' => GRAV_VERSION, 'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_UPDATE_FAILED') . ' <br>' . Installer::lastErrorMsg()];
} }
return true; return true;
@@ -935,9 +939,9 @@ class AdminController
if ($this->view === 'update') { if ($this->view === 'update') {
if ($result) { if ($result) {
$this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.EVERYTHING_UPDATED')]; $this->admin->json_response = ['status' => 'success', 'type' => 'update', 'message' => $this->admin->translate('PLUGIN_ADMIN.EVERYTHING_UPDATED')];
} else { } else {
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.UPDATES_FAILED')]; $this->admin->json_response = ['status' => 'error', 'type' => 'update', 'message' => $this->admin->translate('PLUGIN_ADMIN.UPDATES_FAILED')];
} }
} else { } else {

View File

@@ -130,7 +130,7 @@ class Popularity
$data[] = $count; $data[] = $count;
} }
return array('labels' => json_encode($labels), 'data' => json_encode($data)); return array('labels' => $labels, 'data' => $data);
} }
/** /**

View File

@@ -467,11 +467,13 @@ PLUGIN_ADMIN:
HIDE_HOME_IN_URLS: "Hide home route in URLs" HIDE_HOME_IN_URLS: "Hide home route in URLs"
HIDE_HOME_IN_URLS_HELP: "Will ensure the default routes for any pages under home do not reference home's regular route" HIDE_HOME_IN_URLS_HELP: "Will ensure the default routes for any pages under home do not reference home's regular route"
TWIG_FIRST: "Process Twig First" TWIG_FIRST: "Process Twig First"
TWIG_FIRST_HELP: "If you enabled Twig page processing, then you can configure Twig to process before or after markdown" TWIG_FIRST_HELP: "If you enabled Twig page processing, then you can configure Twig to process before or after markdown"
SESSION_SECURE: "Secure" SESSION_SECURE: "Secure"
SESSION_SECURE_HELP: "If true, indicates that communication for this cookie must be over an encrypted transmission. WARNING: Enable this only on sites that run exclusively on HTTPS" SESSION_SECURE_HELP: "If true, indicates that communication for this cookie must be over an encrypted transmission. WARNING: Enable this only on sites that run exclusively on HTTPS"
SESSION_HTTPONLY: "HTTP Only" SESSION_HTTPONLY: "HTTP Only"
SESSION_HTTPONLY_HELP: "If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed" SESSION_HTTPONLY_HELP: "If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed"
REVERSE_PROXY: "Reverse Proxy" REVERSE_PROXY: "Reverse Proxy"
REVERSE_PROXY_HELP: "Enable this if you are behind a reverse proxy and you are having trouble with URLs containing incorrect ports" REVERSE_PROXY_HELP: "Enable this if you are behind a reverse proxy and you are having trouble with URLs containing incorrect ports"
INVALID_FRONTMATTER_COULD_NOT_SAVE: "Invalid frontmatter, could not save" INVALID_FRONTMATTER_COULD_NOT_SAVE: "Invalid frontmatter, could not save"
NOTHING_TO_SAVE: "Nothing to Save"

181
themes/grav/.eslintrc Normal file
View File

@@ -0,0 +1,181 @@
{
"env": {
"browser": true,
"node": true
},
"ecmaFeatures": {
"arrowFunctions": true,
"destructuring": true,
"classes": true,
"defaultParams": true,
"blockBindings": true,
"modules": true,
"objectLiteralComputedProperties": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"restParams": true,
"spread": true,
"forOf": true,
"generators": true,
"templateStrings": true,
"superInFunctions": true,
"experimentalObjectRestSpread": true
},
"rules": {
"accessor-pairs": 2,
"array-bracket-spacing": 0,
"block-scoped-var": 0,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"camelcase": 0,
"comma-dangle": [2, "never"],
"comma-spacing": [2, { "before": false, "after": true }],
"comma-style": [2, "last"],
"complexity": 0,
"computed-property-spacing": 0,
"consistent-return": 0,
"consistent-this": 0,
"constructor-super": 2,
"curly": [2, "multi-line"],
"default-case": 0,
"dot-location": [2, "property"],
"dot-notation": 0,
"eol-last": 2,
"eqeqeq": [2, "allow-null"],
"func-names": 0,
"func-style": 0,
"generator-star-spacing": [2, { "before": true, "after": true }],
"guard-for-in": 0,
"handle-callback-err": [2, "^(err|error)$" ],
"indent": [2, 4, { "SwitchCase": 1 }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"linebreak-style": 0,
"lines-around-comment": 0,
"max-nested-callbacks": 0,
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
"new-parens": 2,
"newline-after-var": 0,
"no-alert": 0,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 0,
"no-cond-assign": 2,
"no-console": 0,
"no-constant-condition": 0,
"no-continue": 0,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 0,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-else-return": 0,
"no-empty": 0,
"no-empty-character-class": 2,
"no-empty-label": 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,
"space-after-keywords": [2, "always"],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 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,24 @@
import $ from 'jquery';
import { translations } from 'grav-config';
import request from '../utils/request';
import { Instances as Charts } from './chart';
$('[data-ajax*="task:backup"]').on('click', function() {
let element = $(this);
let url = element.data('ajax');
element
.attr('disabled', 'disabled')
.find('> .fa').removeClass('fa-database').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>`);
}
element
.removeAttr('disabled')
.find('> .fa').removeClass('fa-spin fa-refresh').addClass('fa-database');
});
});

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:clearCache/${type}admin-nonce:${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(event);
this.disable();
request(url, () => this.enable());
}
enable() {
this.element
.removeAttr('disabled')
.find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-trash');
}
disable() {
this.element
.attr('disabled', 'disabled')
.find('> .fa').removeClass('fa-trash').addClass('fa-refresh fa-spin');
}
}
let Instance = new Cache();
export { Instance };

View File

@@ -0,0 +1,124 @@
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
},
Bar: {
height: 164,
chartPadding: !isFirefox ? 5 : 25,
axisX: {
showGrid: false,
labelOffset: {
x: 0,
y: 5
}
},
axisY: {
offset: 15,
showLabel: true,
showGrid: true,
labelOffset: {
x: 5,
y: 5
},
scaleMinSpace: 20
}
}
}
};
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')[0], this.data, this.options);
}
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) => {
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-maintenance-update]').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,146 @@
import $ from 'jquery';
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;
}
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 = '';
if (this.isValueOnly()) {
tpl += `
<div class="form-row array-field-value_only" data-grav-array-type="row">
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" value="" placeholder="${this.getValuePlaceholder()}" />
`;
} else {
tpl += `
<div class="form-row" data-grav-array-type="row">
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="key" type="text" value="" placeholder="${this.getKeyPlaceholder()}" />
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" name="" value="" placeholder="${this.getValuePlaceholder()}" />
`;
}
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]', (event) => this.actionEvent(event));
}
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 name = `${template.getName()}[${!template.isValueOnly() ? keyElement.val() : this.getIndexFor(element)}]`;
valueElement.attr('name', !valueElement.val() ? template.getName() : name);
this.refreshNames(template);
}
actionEvent(event) {
let element = $(event.target);
let action = element.data('grav-array-action');
this._setTemplate(element);
this[`${action}Action`](element);
}
addAction(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
row.after(template.getNewRow());
}
remAction(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
let isLast = !row.siblings().length;
if (isLast) {
let newRow = $(template.getNewRow());
row.after(newRow);
newRow.find('[data-grav-array-type="value"]:last').attr('name', template.getName());
}
row.remove();
this.refreshNames(template);
}
refreshNames(template) {
if (!template.isValueOnly()) { return; }
let row = template.container.find('> div > [data-grav-array-type="row"]');
let inputs = row.find('[name]:not([name=""])');
inputs.each((index, input) => {
input = $(input);
let name = input.attr('name');
name = name.replace(/\[\d+\]$/, `[${index}]`);
input.attr('name', name);
});
if (!inputs.length) {
row.find('[data-grav-array-type="value"]').attr('name', template.getName());
}
}
getIndexFor(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
return template.container.find(`${template.isValueOnly() ? '> div ' : ''} > [data-grav-array-type="row"]`).index(row);
}
_setTemplate(element) {
if (!element.data('array-template')) {
element.data('array-template', new Template(element.closest('[data-grav-array-name]')));
}
}
}
export let Instance = new ArrayField();

View File

@@ -0,0 +1,98 @@
import $ from 'jquery';
import Sortable from 'sortablejs';
import '../../utils/jquery-utils';
export default class CollectionsField {
constructor() {
this.lists = $();
$('[data-type="collection"]').each((index, list) => this.addList(list));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
addList(list) {
list = $(list);
this.lists = this.lists.add(list);
list.on('click', '> .collection-actions [data-action="add"]', (event) => this.addItem(event));
list.on('click', '> ul > li > .item-actions [data-action="delete"]', (event) => this.removeItem(event));
list.find('[data-collection-holder]').each((index, container) => {
container = $(container);
if (container.data('collection-sort')) { return; }
container.data('collection-sort', new Sortable(container.get(0), {
forceFallback: true,
animation: 150,
onUpdate: () => this.reindex(container)
}));
});
}
addItem(event) {
let button = $(event.currentTarget);
let list = button.closest('[data-type="collection"]');
let template = $(list.find('> [data-collection-template="new"]').data('collection-template-html'));
list.find('> [data-collection-holder]').append(template);
this.reindex(list);
// button.data('key-index', keyIndex + 1);
// process markdown editors
/* var field = template.find('[name]').filter('textarea');
if (field.length && field.data('grav-mdeditor') && typeof MDEditors !== 'undefined') {
MDEditors.add(field);
}*/
}
removeItem(event) {
let button = $(event.currentTarget);
let item = button.closest('[data-collection-item]');
let list = button.closest('[data-type="collection"]');
item.remove();
this.reindex(list);
}
reindex(list) {
list = $(list).closest('[data-type="collection"]');
let items = list.find('> ul > [data-collection-item]');
items.each((index, item) => {
item = $(item);
item.attr('data-collection-key', index);
['name', 'data-grav-field-name', 'id', 'for'].forEach((prop) => {
item.find('[' + prop + ']').each(function() {
let element = $(this);
let indexes = [];
element.parents('[data-collection-key]').map((idx, parent) => indexes.push($(parent).attr('data-collection-key')));
indexes.reverse();
let replaced = element.attr(prop).replace(/\[(\d+|\*)\]/g, (/* str, p1, offset */) => {
return `[${indexes.shift()}]`;
});
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);
}
});
}
}
export let Instance = new CollectionsField();

View File

@@ -0,0 +1,18 @@
import SelectizeField, { Instance as SelectizeFieldInstance } from './selectize';
import ArrayField, { Instance as ArrayFieldInstance } from './array';
import CollectionsField, { Instance as CollectionsFieldInstance } from './collections';
export default {
SelectizeField: {
SelectizeField,
Instance: SelectizeFieldInstance
},
ArrayField: {
ArrayField,
Instance: ArrayFieldInstance
},
CollectionsField: {
CollectionsField,
Instance: CollectionsFieldInstance
}
};

View File

@@ -0,0 +1,35 @@
import $ from 'jquery';
import 'selectize';
export default class SelectizeField {
constructor(options = {}) {
this.options = Object.assign({}, options);
this.elements = [];
$('[data-grav-selectize]').each((index, element) => this.add(element));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
add(element) {
element = $(element);
let tag = element.prop('tagName').toLowerCase();
let isInput = tag === 'input' || tag === 'select';
let data = (isInput ? element.closest('[data-grav-selectize]') : element).data('grav-selectize') || {};
let field = (isInput ? element : element.find('input, select'));
if (!field.length || field.get(0).selectize) { return; }
field.selectize(data);
this.elements.push(field.data('selectize'));
}
_onAddedNodes(event, target/* , record, instance */) {
let fields = $(target).find('select.fancy, input.fancy');
if (!fields.length) { return; }
fields.each((index, field) => this.add(field));
}
}
export let Instance = new SelectizeField();

View File

@@ -0,0 +1,104 @@
import $ from 'jquery';
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; }
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.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 = $('[name="task"][value="save"]').filter(function(index, element) {
element = $(element);
return !(element.parents('.remodal-overlay').length);
});
if (saveTask.length) {
$(window).on('keydown', function(event) {
var key = String.fromCharCode(event.which).toLowerCase();
if ((event.ctrlKey || 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.parents('.form-field');
let label = parent.find('label.toggleable');
let fields = parent.find('.form-data');
let inputs = fields.find('input, select, textarea');
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 target = $(event.target);
let input = 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');
});
}
addedNodes(mutations) {
mutations.forEach((mutation) => {
if (mutation.type !== 'childList' || !mutation.addedNodes) { return; }
$('body').trigger('mutation._grav', 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,122 @@
import $ from 'jquery';
import Immutable from 'immutable';
import '../utils/jquery-utils';
let FormLoadState = {};
const DOMBehaviors = {
attach() {
this.preventUnload();
this.preventClickAway();
},
preventUnload() {
if ($._data(window, 'events') && ($._data(window, 'events').beforeunload || []).filter((event) => event.namespace === '_grav')) {
return;
}
// Catch browser uri change / refresh attempt and stop it if the form state is dirty
$(window).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^=#])';
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') {
$(window).off('beforeunload');
window.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();
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 value;
switch (type) {
case 'checkbox':
case 'radio':
value = field.is(':checked');
break;
default:
value = field.val();
}
if (name && !~this.options.ignore.indexOf(name)) {
values[name] = value;
}
});
return Immutable.OrderedMap(values);
}
// 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 };

29
themes/grav/app/main.js Normal file
View File

@@ -0,0 +1,29 @@
import GPM, { Instance as gpm } from './utils/gpm';
import KeepAlive from './utils/keepalive';
import Updates, { Instance as updates } from './updates';
import Dashboard from './dashboard';
import Pages from './pages';
import Forms from './forms';
import './plugins';
import './themes';
// bootstrap jQuery extensions
import 'bootstrap/js/dropdown';
// starts the keep alive, auto runs every X seconds
KeepAlive.start();
export default {
GPM: {
GPM,
Instance: gpm
},
KeepAlive,
Dashboard,
Pages,
Forms,
Updates: {
Updates,
Instance: updates
}
};

View File

@@ -0,0 +1,134 @@
import $ from 'jquery';
import { config } from 'grav-config';
import request from '../utils/request';
import debounce from 'debounce';
import { Instance as pagesTree } from './tree';
import 'selectize';
/* @formatter:off */
/* eslint-disable */
const options = [
{ flag: 'Modular', key: 'Modular', cat: 'mode' },
{ flag: 'Visible', key: 'Visible', cat: 'mode' },
{ flag: 'Routable', key: 'Routable', cat: 'mode' },
{ flag: 'Published', key: 'Published', cat: 'mode' },
{ flag: 'Non-Modular', key: 'NonModular', cat: 'mode' },
{ flag: 'Non-Visible', key: 'NonVisible', cat: 'mode' },
{ flag: 'Non-Routable', key: 'NonRoutable', cat: 'mode' },
{ flag: '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;
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());
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) { this.filters[0].selectize.setValue(flagsArray, silent); }
if (this.search.val() !== query) { this.search.val(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']
});
}
}
let Instance = new PagesFilter('input[name="page-filter"]', 'input[name="page-search"]');
export { Instance };

View File

@@ -0,0 +1,26 @@
import $ from 'jquery';
import Sortable from 'sortablejs';
import PageFilters, { Instance as PageFiltersInstance } from './filter';
import './page';
// Pages Ordering
let Ordering = null;
let orderingElement = $('#ordering');
if (orderingElement.length) {
Ordering = new Sortable(orderingElement.get(0), {
filter: '.ignore',
onUpdate: function(event) {
let item = $(event.item);
let index = orderingElement.children().index(item) + 1;
$('[data-order]').val(index);
}
});
}
export default {
Ordering,
PageFilters: {
PageFilters,
Instance: PageFiltersInstance
}
};

View File

@@ -0,0 +1,31 @@
import $ from 'jquery';
let custom = false;
let folder = $('input[name="folder"]');
let title = $('input[name="title"]');
title.on('input focus blur', () => {
if (custom) { return true; }
let slug = $.slugify(title.val());
folder.val(slug);
});
folder.on('input', () => {
let input = folder.get(0);
let value = folder.val();
let selection = {
start: input.selectionStart,
end: input.selectionEnd
};
value = value.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
folder.val(value);
custom = !!value;
// restore cursor position
input.setSelectionRange(selection.start, selection.end);
});
folder.on('focus blur', () => title.trigger('input'));

View File

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

View File

@@ -0,0 +1,37 @@
import $ from 'jquery';
import './add';
import './move';
import './delete';
import './media';
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', () => {
$(window).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);
});
}

View File

@@ -0,0 +1,380 @@
import $ from 'jquery';
import Dropzone from 'dropzone';
import request from '../../utils/request';
import { config } from 'grav-config';
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('confirm', modalSelector, accept);
doc.off('cancel', modalSelector, reject);
};
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 = {
createImageThumbnails: { thumbnailWidth: 150 },
addRemoveLinks: false,
dictRemoveFileConfirmation: '[placeholder]',
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" href="javascript:undefined;" data-dz-remove>Delete</a>
<a class="dz-insert" href="javascript:undefined;" data-dz-insert>Insert</a>
</div>`.trim()
};
export default class PageMedia {
constructor({form = '[data-media-url]', container = '#grav-dropzone', options = {}} = {}) {
this.form = $(form);
this.container = $(container);
if (!this.form.length || !this.container.length) { return; }
this.options = Object.assign({}, DropzoneMediaConfig, {
url: `${this.form.data('media-url')}/task${config.param_sep}addmedia`,
acceptedFiles: this.form.data('media-types')
}, options);
this.dropzone = new Dropzone(container, this.options);
this.dropzone.on('complete', this.onDropzoneComplete.bind(this));
this.dropzone.on('success', this.onDropzoneSuccess.bind(this));
this.dropzone.on('removedfile', this.onDropzoneRemovedFile.bind(this));
this.dropzone.on('sending', this.onDropzoneSending.bind(this));
this.fetchMedia();
}
fetchMedia() {
let url = `${this.form.data('media-url')}/task${config.param_sep}listmedia/admin-nonce${config.param_sep}${config.admin_nonce}`;
request(url, (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);
if (name.match(/\.(jpg|jpeg|png|gif)$/i)) {
this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
}
});
this.container.find('.dz-preview').prop('draggable', 'true');
});
}
onDropzoneSending(file, xhr, formData) {
formData.append('admin-nonce', config.admin_nonce);
}
onDropzoneSuccess(file, response, xhr) {
return this.handleError({
file,
data: response,
mode: 'removeFile',
msg: `<p>An error occurred while trying to upload the file <strong>${file.name}</strong></p>
<pre>${response.message}</pre>`
});
}
onDropzoneComplete(file) {
if (!file.accepted) {
let data = {
status: 'error',
message: `Unsupported file type: ${file.name.match(/\..+/).join('')}`
};
return this.handleError({
file,
data,
mode: 'removeFile',
msg: `<p>An error occurred while trying to add the file <strong>${file.name}</strong></p>
<pre>${data.message}</pre>`
});
}
// accepted
$('.dz-preview').prop('draggable', 'true');
}
onDropzoneRemovedFile(file, ...extra) {
if (!file.accepted || file.rejected) { return; }
let url = `${this.form.data('media-url')}/task${config.param_sep}delmedia`;
request(url, {
method: 'post',
body: {
filename: file.name
}
}, (response) => {
return this.handleError({
file,
data: response,
mode: 'addBack',
msg: `<p>An error occurred while trying to remove the file <strong>${file.name}</strong></p>
<pre>${response.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(file);
} else {
this.dropzone.files.push(file);
this.dropzone.options.addedfile.call(this, file);
this.dropzone.options.thumbnail.call(this, file, file.extras.url);
}
break;
case 'removeFile':
file.rejected = true;
this.dropzone.removeFile(file);
break;
default:
}
let modal = $('[data-remodal-id="generic"]');
modal.find('.error-content').html(msg);
$.remodal.lookup[modal.data('remodal')].open();
}
}
export let Instance = new PageMedia();
// let container = $('[data-media-url]');
// if (container.length) {
/* let URI = container.data('media-url');
let dropzone = new Dropzone('#grav-dropzone', {
url: `${URI}/task${config.param_sep}addmedia`,
createImageThumbnails: { thumbnailWidth: 150 },
addRemoveLinks: false,
dictRemoveFileConfirmation: '[placeholder]',
acceptedFiles: container.data('media-types'),
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" href="javascript:undefined;" data-dz-remove>Delete</a>
<a class="dz-insert" href="javascript:undefined;" data-dz-insert>Insert</a>
</div>`
});*/
/* $.get(URI + '/task{{ config.system.param_sep }}listmedia/admin-nonce{{ config.system.param_sep }}' + GravAdmin.config.admin_nonce, function(data) {
$.proxy(modalError, this, {
data: data,
msg: '<p>An error occurred while trying to list files</p><pre>'+data.message+'</pre>'
})();
if (data.results) {
$.each(data.results, function(filename, data){
var mockFile = { name: filename, size: data.size, accepted: true, extras: data };
thisDropzone.files.push(mockFile);
thisDropzone.options.addedfile.call(thisDropzone, mockFile);
if (filename.toLowerCase().match(/\.(jpg|jpeg|png|gif)$/)) {
thisDropzone.options.thumbnail.call(thisDropzone, mockFile, data.url);
}
});
}
$('.dz-preview').prop('draggable', 'true');
});*/
// console.log(dropzone);
// }
/*
<script>
$(function(){
var URI = $('[data-media-url]').data('media-url'), thisDropzone,
modalError = function(args){
if (args.data.status == 'error' || args.data.status == 'unauthorized'){
if (args.mode == 'addBack'){
// let's add back the file
if (args.file instanceof File) this.addFile(args.file);
else {
this.files.push(args.file);
this.options.addedfile.call(this, args.file);
this.options.thumbnail.call(this, args.file, args.file.extras.url);
}
} else if (args.mode == 'removeFile') {
args.file.rejected = true;
this.removeFile(args.file);
}
// fire up the modal
var modalContainer = $('[data-remodal-id=generic]');
modalContainer.find('.error-content').html(args.msg);
$.remodal.lookup[modalContainer.data('remodal')].open();
}
};
Dropzone.autoDiscover = false;
Dropzone.confirm = function(question, accepted, rejected) {
var modalContainer = $('[data-remodal-id=delete-media]'),
acceptHandler = function () {
if (accepted) {
accepted();
}
$(document).off('confirm', '[data-remodal-id=delete-media]', acceptHandler);
$(document).off('cancel', '[data-remodal-id=delete-media]', rejectHandler);
},
rejectHandler = function () {
if (rejected) {
rejected();
}
$(document).off('confirm', '[data-remodal-id=delete-media]', acceptHandler);
$(document).off('cancel', '[data-remodal-id=delete-media]', rejectHandler);
};
$.remodal.lookup[modalContainer.data('remodal')].open();
$(document).on('confirm', '[data-remodal-id=delete-media]', acceptHandler);
$(document).on('cancel', '[data-remodal-id=delete-media]', rejectHandler);
};
Dropzone.options.gravDropzone = {
addRemoveLinks: false,
dictRemoveFileConfirmation: '[placeholder]',
acceptedFiles: $('[data-media-types]').data('media-types'),
previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n <div class=\"dz-details\">\n " +
"<div class=\"dz-filename\"><span data-dz-name></span></div>\n " +
"<div class=\"dz-size\" data-dz-size></div>\n <img data-dz-thumbnail />\n </div>\n " +
"<div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress></span></div>\n "+
"<div class=\"dz-success-mark\"><span>✔</span></div>\n <div class=\"dz-error-mark\"><span>✘</span></div>\n " +
"<div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n" +
"<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>Delete</a>\n" +
"<a class=\"dz-insert\" href=\"javascript:undefined;\" data-dz-insert>Insert</a>\n</div>",
init: function() {
thisDropzone = this;
$.get(URI + '/task{{ config.system.param_sep }}listmedia/admin-nonce{{ config.system.param_sep }}' + GravAdmin.config.admin_nonce, function(data) {
$.proxy(modalError, this, {
data: data,
msg: '<p>An error occurred while trying to list files</p><pre>'+data.message+'</pre>'
})();
if (data.results) {
$.each(data.results, function(filename, data){
var mockFile = { name: filename, size: data.size, accepted: true, extras: data };
thisDropzone.files.push(mockFile);
thisDropzone.options.addedfile.call(thisDropzone, mockFile);
if (filename.toLowerCase().match(/\.(jpg|jpeg|png|gif)$/)) {
thisDropzone.options.thumbnail.call(thisDropzone, mockFile, data.url);
}
});
}
$('.dz-preview').prop('draggable', 'true');
});
this.on("complete", function(file) {
if (file.accepted) {
$('.dz-preview').prop('draggable', 'true');
return;
}
var data = {status: 'error', message: 'Unsupported file type: ' + file.name.match(/\..+/).join('')};
$.proxy(modalError, this, {
file: file,
data: data,
mode: 'removeFile',
msg: '<p>An error occurred while trying to add the file <strong>'+file.name+'</strong></p><pre>'+data.message+'</pre>'
})();
});
this.on('success', function(file, response){
thisDropzone = this;
$.proxy(modalError, this, {
file: file,
data: response,
mode: 'removeFile',
msg: '<p>An error occurred while trying to upload the file <strong>'+file.name+'</strong></p><pre>'+response.message+'</pre>'
})();
});
this.on('removedfile', function(file) {
if (!file.accepted || file.rejected) return;
thisDropzone = this;
$.post(URI + '/task{{ config.system.param_sep }}delmedia', {filename: file.name, 'admin-nonce': GravAdmin.config.admin_nonce}, function(data){
$.proxy(modalError, thisDropzone, {
file: file,
data: data,
mode: 'addBack',
msg: '<p>An error occurred while trying to remove the file <strong>'+file.name+'</strong></p><pre>'+data.message+'</pre>'
})();
});
});
this.on('sending', function(file, xhr, formData){
formData.append('admin-nonce', GravAdmin.config.admin_nonce);
});
}
};
var dropzone = new Dropzone("#gravDropzone", { url: URI + '/task{{ config.system.param_sep }}addmedia', createImageThumbnails: { thumbnailWidth: 150} });
$("#gravDropzone").delegate('.dz-preview', 'dragstart', function(e){
var uri = encodeURI($(this).find('.dz-filename').text());
uri = uri.replace(/\(/g, '%28');
uri = uri.replace(/\)/g, '%29');
var shortcode = '![](' + uri + ')';
if (!uri.match(/\.(jpg|jpeg|png|gif)$/)) {
shortcode = '[' + decodeURI(uri) + '](' + uri + ')';
}
dropzone.disable();
$(this).addClass('hide-backface');
e.originalEvent.dataTransfer.effectAllowed = 'copy';
e.originalEvent.dataTransfer.setData('text', shortcode);
});
$("#gravDropzone").delegate('.dz-preview', 'dragend', function(e){
dropzone.enable();
$(this).removeClass('hide-backface');
});
});
</script>*/

View File

@@ -0,0 +1,13 @@
import $ from 'jquery';
$('[data-page-move] button[name="task"][value="save"]').on('click', function() {
let route = $('form#blueprints:first select[name="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);
}
});

View File

@@ -0,0 +1,116 @@
import $ from 'jquery';
const sessionKey = 'grav:admin:pages';
if (!sessionStorage.getItem(sessionKey)) {
sessionStorage.setItem(sessionKey, '{}');
}
export default class PagesTree {
constructor(elements) {
this.elements = $(elements);
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));
$('[data-page-toggleall]').on('click', (event) => {
let element = $(event.target).closest('[data-page-toggleall]');
let action = element.data('page-toggleall');
this[action]();
});
}
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,24 @@
import $ from 'jquery';
// 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) { 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'));
}
});
});

View File

@@ -0,0 +1,10 @@
import $ from 'jquery';
// 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'));
});

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,134 @@
import $ from 'jquery';
import { config, translations } from 'grav-config';
import formatBytes from '../utils/formatbytes';
import { Instance as gpm } from '../utils/gpm';
import './check';
import './update';
export default class Updates {
constructor(payload = {}) {
this.setPayload(payload);
this.task = `task${config.param_sep}`;
}
setPayload(payload = {}) {
this.payload = payload;
return this;
}
fetch(force = false) {
gpm.fetch((response) => this.setPayload(response), force);
return this;
}
maintenance(mode = 'hide') {
let element = $('#updates [data-maintenance-update]');
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.isUpdatable) {
let task = this.task;
let bar = `
<i class="fa fa-bullhorn"></i>
Grav <b>v${payload.available}</b> ${translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE}! <span class="less">(${translations.PLUGIN_ADMIN.CURRENT}v${payload.version})</span>
`;
if (!payload.isSymlink) {
bar += `<button data-maintenance-update="${config.base_url_relative}/update.json/${task}updategrav/admin-nonce${config.param_sep}${config.admin_nonce}" class="button button-small secondary" id="grav-update-button">${translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW}</button>`;
} else {
bar += `<span class="hint--left" style="float: right;" data-hint="${translations.PLUGIN_ADMIN.GRAV_SYMBOLICALLY_LINKED}"><i class="fa fa-fw fa-link"></i></span>`;
}
$('[data-gpm-grav]').addClass('grav').html(`<p>${bar}</p>`);
}
$('#grav-update-button').on('click', function() {
$(this).html(`${translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT} ${formatBytes(payload.assets['grav-update'].size)}..`);
});
return this;
}
resources() {
if (!this.payload.resources.total) { return this.maintenance('hide'); }
let map = ['plugins', 'themes'];
let singles = ['plugin', 'theme'];
let task = this.task;
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);
// update all
let title = type.charAt(0).toUpperCase() + type.substr(1).toLowerCase();
let updateAll = $(`.grav-update.${type}`);
updateAll.html(`
<p>
<i class="fa fa-bullhorn"></i>
${length} ${translations.PLUGIN_ADMIN.OF_YOUR} ${type} ${translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE}
<a href="${config.base_url_relative}/${type}/${task}update/admin-nonce${config.param_sep}${config.admin_nonce}" class="button button-small secondary">${translations.PLUGIN_ADMIN.UPDATE} All ${title}</a>
</p>
`);
Object.keys(resources).forEach(function(item) {
// listing page
let element = $(`[data-gpm-${singles[index]}="${item}"] .gpm-name`);
let url = element.find('a');
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>`);
} else if (type === 'themes') {
element.append(`<div class="gpm-ribbon"><a href="${url.attr('href')}">${translations.PLUGIN_ADMIN.UPDATE.toUpperCase()}</a></div>`);
}
// details page
let details = $(`.grav-update.${singles[index]}`);
if (details.length) {
details.html(`
<p>
<i class="fa fa-bullhorn"></i>
<strong>v${resources[item].available}</strong> ${translations.PLUGIN_ADMIN.OF_THIS} ${singles[index]} ${translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE}!
<a href="${config.base_url_relative}/${type}/${item}/${task}update/admin-nonce${config.param_sep}${config.admin_nonce}" class="button button-small secondary">${translations.PLUGIN_ADMIN.UPDATE} ${singles[index].charAt(0).toUpperCase() + singles[index].substr(1).toLowerCase()}</a>
</p>
`);
}
});
});
}
}
let Instance = new Updates();
export { Instance };
// 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,19 @@
import $ from 'jquery';
import request from '../utils/request';
// Dashboard update and Grav update
$('body').on('click', '[data-maintenance-update]', function() {
let element = $(this);
let url = element.data('maintenanceUpdate');
element.attr('disabled', 'disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin');
request(url, (response) => {
if (response.type === 'updategrav') {
$('[data-gpm-grav]').remove();
$('#footer .grav-version').html(response.version);
}
element.removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-cloud-download');
});
});

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,58 @@
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('task', 'GPM');
data.append('action', this.action);
if (flush) {
data.append('flush', true);
}
this.emit('fetching', this);
fetch(config.base_url_relative, {
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();

4
themes/grav/app/utils/jquery-utils.js vendored Normal file
View File

@@ -0,0 +1,4 @@
import $ from 'jquery';
// jQuery no parents filter
$.expr[':']['noparents'] = $.expr.createPseudo((text) => (element) => $(element).parents(text).length < 1);

View File

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

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' && options.body) {
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,68 @@
import toastr from './toastr';
import { config } from 'grav-config';
let error = function(response) {
let error = new Error(response.statusText || response || '');
error.response = response;
return error;
};
export function parseStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
throw error(response);
}
}
export function parseJSON(response) {
return response.json();
}
export function userFeedback(response) {
let status = response.status;
let message = response.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) { toastr[status === 'success' ? 'success' : 'error'](message); }
if (settings) {
toastr.options = backup;
}
return response;
}
export function userFeedbackError(error) {
toastr.error(`Fetch Failed: <br /> ${error.message} <pre><code>${error.stack}</code></pre>`);
console.error(`${error.message} at ${error.stack}`);
}

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

73
themes/grav/gulpfile.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
var gulp = require('gulp'),
path = require('path'),
immutable = require('immutable'),
merge = require('merge-stream'),
gulpWebpack = require('gulp-webpack'),
webpack = require('webpack');
var plugins = {
'Promise': 'imports?this=>global!exports?global.Promise!babel-polyfill',
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
},
base = immutable.fromJS(require('./webpack.conf.js')),
options = {
dev: base.mergeDeep({
devtool: 'source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': { NODE_ENV: '"development"' }
}),
new webpack.ProvidePlugin(plugins),
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
],
output: {
filename: 'admin.js'
}
}),
prod: base.mergeDeep({
plugins: [
new webpack.DefinePlugin({
'process.env': { NODE_ENV: '"production"' }
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: false,
compress: {
warnings: false
}
}),
new webpack.ProvidePlugin(plugins),
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.min.js", Infinity)
],
output: {
filename: 'admin.min.js'
}
})
};
var compileJS = function(watch) {
var devOpts = options.dev.set('watch', watch),
prodOpts = options.prod.set('watch', watch);
var prod = gulp.src('app/main.js')
.pipe(gulpWebpack(prodOpts.toJS()))
.pipe(gulp.dest('js/'));
var dev = gulp.src('app/main.js')
.pipe(gulpWebpack(devOpts.toJS()))
.pipe(gulp.dest('js/'));
return merge(prod, dev);
};
gulp.task('js', function() {
compileJS(false);
});
gulp.task('watch', function() {
compileJS(true);
});
gulp.task('default', ['js']);

View File

@@ -1,4 +1,4 @@
var getState = function(){ /*var getState = function(){
var loadValues = [], var loadValues = [],
ignoreNames = ['page-filter', 'page-search']; ignoreNames = ['page-filter', 'page-search'];
$('input, select, textarea').each(function(index, element){ $('input, select, textarea').each(function(index, element){
@@ -9,37 +9,37 @@ var getState = function(){
}); });
return loadValues.toString(); return loadValues.toString();
}; };*/
var bytesToSize = function(bytes) { /*var bytesToSize = function(bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == 0) return '0 Byte'; if (bytes == 0) return '0 Byte';
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
}; };*/
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; /*var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
var keepAlive = function keepAlive() { var keepAlive = function keepAlive() {
$.post(GravAdmin.config.base_url_relative + '/task' + GravAdmin.config.param_sep + 'keepAlive', { $.post(GravAdmin.config.base_url_relative + '/task' + GravAdmin.config.param_sep + 'keepAlive', {
'admin-nonce': GravAdmin.config.admin_nonce 'admin-nonce': GravAdmin.config.admin_nonce
}); });
}; };*/
$(function () { $(function () {
jQuery.substitute = function(str, sub) { /*jQuery.substitute = function(str, sub) {
return str.replace(/\{(.+?)\}/g, function($0, $1) { return str.replace(/\{(.+?)\}/g, function($0, $1) {
return $1 in sub ? sub[$1] : $0; return $1 in sub ? sub[$1] : $0;
}); });
}; };*/
// Set Toastr defaults // Set Toastr defaults
toastr.options = { /*toastr.options = {
"positionClass": "toast-top-right" "positionClass": "toast-top-right"
} }*/
// dashboard // dashboard
var chart = $('.updates-chart'), UpdatesChart; /*var chart = $('.updates-chart'), UpdatesChart;
if (chart.length) { if (chart.length) {
var data = { var data = {
series: [100, 0] series: [100, 0]
@@ -60,17 +60,17 @@ $(function () {
if (data.index) { return; } if (data.index) { return; }
chart.find('.numeric span').text(Math.round(data.value) + '%'); chart.find('.numeric span').text(Math.round(data.value) + '%');
var text = translations.PLUGIN_ADMIN.UPDATES_AVAILABLE; var text = GravAdmin.translations.PLUGIN_ADMIN.UPDATES_AVAILABLE;
if (data.value == 100) { if (data.value == 100) {
text = translations.PLUGIN_ADMIN.FULLY_UPDATED; text = GravAdmin.translations.PLUGIN_ADMIN.FULLY_UPDATED;
} }
$('.js__updates-available-description').html(text) $('.js__updates-available-description').html(text)
$('.updates-chart .hidden').removeClass('hidden'); $('.updates-chart .hidden').removeClass('hidden');
}); });
} }
*/
// Cache Clear // Cache Clear
$('[data-clear-cache]').on('click', function(e) { /*$('[data-clear-cache]').on('click', function(e) {
$(this).attr('disabled','disabled').find('> .fa').removeClass('fa-trash').addClass('fa-refresh fa-spin'); $(this).attr('disabled','disabled').find('> .fa').removeClass('fa-trash').addClass('fa-refresh fa-spin');
var url = $(this).data('clearCache'); var url = $(this).data('clearCache');
@@ -85,10 +85,10 @@ $(function () {
}).always(function() { }).always(function() {
$('[data-clear-cache]').removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-trash'); $('[data-clear-cache]').removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-trash');
}); });
}); });*/
// Plugins list details sliders // Plugins list details sliders
$('.gpm-name, .gpm-actions').on('click', function(e){ /*$('.gpm-name, .gpm-actions').on('click', function(e){
var target = $(e.target); var target = $(e.target);
if (target.prop('tagName') == 'A' || target.parent('a').length) { return true; } if (target.prop('tagName') == 'A' || target.parent('a').length) { return true; }
@@ -105,10 +105,10 @@ $(function () {
.addClass('fa-chevron-' + (isVisible ? 'up' : 'down')); .addClass('fa-chevron-' + (isVisible ? 'up' : 'down'));
} }
}); });
}); });*/
// Update plugins/themes // Update plugins/themes
$(document).on('click', '[data-maintenance-update]', function(e) { /*$(document).on('click', '[data-maintenance-update]', function(e) {
$(this).attr('disabled','disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin'); $(this).attr('disabled','disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin');
var url = $(this).data('maintenanceUpdate'); var url = $(this).data('maintenanceUpdate');
@@ -125,9 +125,9 @@ $(function () {
toastr.success(result.message + window.grav_available_version); toastr.success(result.message + window.grav_available_version);
$('#footer .grav-version').html(window.grav_available_version); $('#footer .grav-version').html(window.grav_available_version);
/*// hide the update button after successfull update and update the badges /!*!// hide the update button after successfull update and update the badges
$('[data-maintenance-update]').fadeOut(); $('[data-maintenance-update]').fadeOut();
$('.badges.with-updates').removeClass('with-updates').find('.badge.updates').remove();*/ $('.badges.with-updates').removeClass('with-updates').find('.badge.updates').remove();*!/
} else { } else {
toastr.success(result.message); toastr.success(result.message);
} }
@@ -139,10 +139,10 @@ $(function () {
GPMRefresh(); GPMRefresh();
$('[data-maintenance-update]').removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-cloud-download'); $('[data-maintenance-update]').removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-cloud-download');
}); });
}); });*/
// Update plugins/themes // Update plugins/themes
$('[data-ajax]').on('click', function(e) { /*$('[data-ajax]').on('click', function(e) {
var button = $(this), var button = $(this),
icon = button.find('> .fa'), icon = button.find('> .fa'),
@@ -182,7 +182,7 @@ $(function () {
} }
} }
toastr.success(result.message || translations.PLUGIN_ADMIN.TASK_COMPLETED); toastr.success(result.message || GravAdmin.translations.PLUGIN_ADMIN.TASK_COMPLETED);
for (var setting in toastrBackup) { if (toastrBackup.hasOwnProperty(setting)) { for (var setting in toastrBackup) { if (toastrBackup.hasOwnProperty(setting)) {
toastr.options[setting] = toastrBackup[setting]; toastr.options[setting] = toastrBackup[setting];
@@ -191,7 +191,7 @@ $(function () {
if (url.indexOf(task + 'backup') !== -1) { if (url.indexOf(task + 'backup') !== -1) {
//Reset backup days count //Reset backup days count
$('.backups-chart .numeric').html("0 <em>" + translations.PLUGIN_ADMIN.DAYS + "</em>"); $('.backups-chart .numeric').html("0 <em>" + GravAdmin.translations.PLUGIN_ADMIN.DAYS + "</em>");
var data = { var data = {
series: [0,100] series: [0,100]
@@ -214,9 +214,9 @@ $(function () {
button.removeAttr('disabled'); button.removeAttr('disabled');
icon.removeClass('fa-refresh fa-spin').addClass(iconClasses.join(' ')); icon.removeClass('fa-refresh fa-spin').addClass(iconClasses.join(' '));
}); });
}); });*/
$('[data-gpm-checkupdates]').on('click', function(){ /*$('[data-gpm-checkupdates]').on('click', function(){
var element = $(this); var element = $(this);
element.find('i').addClass('fa-spin'); element.find('i').addClass('fa-spin');
GPMRefresh({ GPMRefresh({
@@ -227,21 +227,21 @@ $(function () {
if (payload) { if (payload) {
if (!payload.grav.isUpdatable && !payload.resources.total) { if (!payload.grav.isUpdatable && !payload.resources.total) {
toastr.success(translations.PLUGIN_ADMIN.EVERYTHING_UP_TO_DATE); toastr.success(GravAdmin.translations.PLUGIN_ADMIN.EVERYTHING_UP_TO_DATE);
} else { } else {
var grav = payload.grav.isUpdatable ? 'Grav v' + payload.grav.available : ''; var grav = payload.grav.isUpdatable ? 'Grav v' + payload.grav.available : '';
var resources = payload.resources.total ? payload.resources.total + ' ' + translations.PLUGIN_ADMIN.UPDATES_ARE_AVAILABLE: ''; var resources = payload.resources.total ? payload.resources.total + ' ' + GravAdmin.translations.PLUGIN_ADMIN.UPDATES_ARE_AVAILABLE: '';
if (!resources) { grav += ' ' + translations.PLUGIN_ADMIN.IS_AVAILABLE_FOR_UPDATE } if (!resources) { grav += ' ' + GravAdmin.translations.PLUGIN_ADMIN.IS_AVAILABLE_FOR_UPDATE }
toastr.info(grav + (grav && resources ? ' ' + translations.PLUGIN_ADMIN.AND + ' ' : '') + resources); toastr.info(grav + (grav && resources ? ' ' + GravAdmin.translations.PLUGIN_ADMIN.AND + ' ' : '') + resources);
} }
} }
} }
}); });
}); });*/
var GPMRefresh = function (options) { var GPMRefresh = function (options) {
options = options || {}; /*options = options || {};
var data = { var data = {
task: 'GPM', task: 'GPM',
@@ -261,19 +261,19 @@ $(function () {
return; return;
} }
var grav = response.payload.grav, /!*var grav = response.payload.grav,
installed = response.payload.installed, installed = response.payload.installed,
resources = response.payload.resources, resources = response.payload.resources,
task = 'task' + GravAdmin.config.param_sep; task = 'task' + GravAdmin.config.param_sep;
*!/
// grav updatable // grav updatable
if (grav.isUpdatable) { /!*if (grav.isUpdatable) {
var icon = '<i class="fa fa-bullhorn"></i> '; var icon = '<i class="fa fa-bullhorn"></i> ';
content = 'Grav <b>v{available}</b> ' + translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE + '! <span class="less">(' + translations.PLUGIN_ADMIN.CURRENT + ': v{version})</span> ', content = 'Grav <b>v{available}</b> ' + GravAdmin.translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE + '! <span class="less">(' + GravAdmin.translations.PLUGIN_ADMIN.CURRENT + ': v{version})</span> ',
button = '<button data-maintenance-update="' + GravAdmin.config.base_url_relative + '/update.json/' + task + 'updategrav/admin-nonce' + GravAdmin.config.param_sep + GravAdmin.config.admin_nonce + '" class="button button-small secondary" id="grav-update-button">' + translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW + '</button>'; button = '<button data-maintenance-update="' + GravAdmin.config.base_url_relative + '/update.json/' + task + 'updategrav/admin-nonce' + GravAdmin.config.param_sep + GravAdmin.config.admin_nonce + '" class="button button-small secondary" id="grav-update-button">' + GravAdmin.translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW + '</button>';
if (grav.isSymlink) { if (grav.isSymlink) {
button = '<span class="hint--left" style="float: right;" data-hint="' + translations.PLUGIN_ADMIN.GRAV_SYMBOLICALLY_LINKED + '"><i class="fa fa-fw fa-link"></i></span>'; button = '<span class="hint--left" style="float: right;" data-hint="' + GravAdmin.translations.PLUGIN_ADMIN.GRAV_SYMBOLICALLY_LINKED + '"><i class="fa fa-fw fa-link"></i></span>';
} }
content = jQuery.substitute(content, {available: grav.available, version: grav.version}); content = jQuery.substitute(content, {available: grav.available, version: grav.version});
@@ -282,33 +282,33 @@ $(function () {
} }
$('#grav-update-button').on('click', function() { $('#grav-update-button').on('click', function() {
$(this).html(translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT + ' ' + bytesToSize(grav.assets['grav-update'].size) + '..'); $(this).html(GravAdmin.translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT + ' ' + bytesToSize(grav.assets['grav-update'].size) + '..');
}); });*!/
// dashboard // dashboard
if ($('.updates-chart').length) { /!*if ($('.updates-chart').length) {
var missing = (resources.total + (grav.isUpdatable ? 1 : 0)) * 100 / (installed + (grav.isUpdatable ? 1 : 0)), var missing = (resources.total + (grav.isUpdatable ? 1 : 0)) * 100 / (installed + (grav.isUpdatable ? 1 : 0)),
updated = 100 - missing; updated = 100 - missing;
UpdatesChart.update({series: [updated, missing]}); UpdatesChart.update({series: [updated, missing]});
if (resources.total) { if (resources.total) {
$('#updates [data-maintenance-update]').fadeIn(); $('#updates [data-maintenance-update]').fadeIn();
} }
} }*!/
if (!resources.total) { /!*if (!resources.total) {
$('#updates [data-maintenance-update]').fadeOut(); $('#updates [data-maintenance-update]').fadeOut();
$('.badges.with-updates').removeClass('with-updates').find('.badge.updates').remove(); $('.badges.with-updates').removeClass('with-updates').find('.badge.updates').remove();
} else { } else {
var length, var length,
icon = '<i class="fa fa-bullhorn"></i>', icon = '<i class="fa fa-bullhorn"></i>',
content = '{updates} ' + translations.PLUGIN_ADMIN.OF_YOUR + ' {type} ' + translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE, content = '{updates} ' + GravAdmin.translations.PLUGIN_ADMIN.OF_YOUR + ' {type} ' + GravAdmin.translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE,
button = '<a href="{location}/' + task + 'update/admin-nonce' + GravAdmin.config.param_sep + GravAdmin.config.admin_nonce + '" class="button button-small secondary">' + translations.PLUGIN_ADMIN.UPDATE + ' {Type}</a>', button = '<a href="{location}/' + task + 'update/admin-nonce' + GravAdmin.config.param_sep + GravAdmin.config.admin_nonce + '" class="button button-small secondary">' + GravAdmin.translations.PLUGIN_ADMIN.UPDATE + ' {Type}</a>',
plugins = $('.grav-update.plugins'), plugins = $('.grav-update.plugins'),
themes = $('.grav-update.themes'), themes = $('.grav-update.themes'),
sidebar = {plugins: $('#admin-menu a[href$="/plugins"]'), themes: $('#admin-menu a[href$="/themes"]')}; sidebar = {plugins: $('#admin-menu a[href$="/plugins"]'), themes: $('#admin-menu a[href$="/themes"]')};
// sidebar // sidebar
if (sidebar.plugins.length || sidebar.themes.length) { /!*if (sidebar.plugins.length || sidebar.themes.length) {
var length, badges; var length, badges;
if (sidebar.plugins.length && (length = Object.keys(resources.plugins).length)) { if (sidebar.plugins.length && (length = Object.keys(resources.plugins).length)) {
badges = sidebar.plugins.find('.badges'); badges = sidebar.plugins.find('.badges');
@@ -321,7 +321,7 @@ $(function () {
badges.addClass('with-updates'); badges.addClass('with-updates');
badges.find('.badge.updates').text(length); badges.find('.badge.updates').text(length);
} }
} }*!/
// list page // list page
if (plugins[0] && (length = Object.keys(resources.plugins).length)) { if (plugins[0] && (length = Object.keys(resources.plugins).length)) {
@@ -334,7 +334,7 @@ $(function () {
plugin = $('[data-gpm-plugin="' + key + '"] .gpm-name'); plugin = $('[data-gpm-plugin="' + key + '"] .gpm-name');
url = plugin.find('a'); url = plugin.find('a');
if (!plugin.find('.badge.update').length) { if (!plugin.find('.badge.update').length) {
plugin.append('<a class="plugin-update-button" href="' + url.attr('href') + '"><span class="badge update">' + translations.PLUGIN_ADMIN.UPDATE_AVAILABLE + '!</span></a>'); plugin.append('<a class="plugin-update-button" href="' + url.attr('href') + '"><span class="badge update">' + GravAdmin.translations.PLUGIN_ADMIN.UPDATE_AVAILABLE + '!</span></a>');
} }
}); });
@@ -349,12 +349,12 @@ $(function () {
$.each(resources.themes, function (key, value) { $.each(resources.themes, function (key, value) {
theme = $('[data-gpm-theme="' + key + '"]'); theme = $('[data-gpm-theme="' + key + '"]');
url = theme.find('.gpm-name a'); url = theme.find('.gpm-name a');
theme.append('<div class="gpm-ribbon"><a href="' + url.attr('href') + '">' + translations.PLUGIN_ADMIN.UPDATE.toUpperCase() + '</a></div>'); theme.append('<div class="gpm-ribbon"><a href="' + url.attr('href') + '">' + GravAdmin.translations.PLUGIN_ADMIN.UPDATE.toUpperCase() + '</a></div>');
}); });
} }*!/
// details page // details page
var type = 'plugin', /!*var type = 'plugin',
details = $('.grav-update.plugin')[0]; details = $('.grav-update.plugin')[0];
if (!details) { if (!details) {
@@ -368,7 +368,7 @@ $(function () {
resource = resources[type + 's'][slug]; resource = resources[type + 's'][slug];
if (resource) { if (resource) {
content = '<strong>v{available}</strong> ' + translations.PLUGIN_ADMIN.OF_THIS + ' ' + type + ' ' + translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE + '!'; content = '<strong>v{available}</strong> ' + GravAdmin.translations.PLUGIN_ADMIN.OF_THIS + ' ' + type + ' ' + GravAdmin.translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE + '!';
content = jQuery.substitute(content, { available: resource.available }); content = jQuery.substitute(content, { available: resource.available });
button = jQuery.substitute(button, { button = jQuery.substitute(button, {
Type: Type, Type: Type,
@@ -376,21 +376,21 @@ $(function () {
}); });
$(details).html('<p>' + icon + content + button + '</p>'); $(details).html('<p>' + icon + content + button + '</p>');
} }
} }*!/
} //}
if (options.callback && typeof options.callback == 'function') options.callback(response); if (options.callback && typeof options.callback == 'function') options.callback(response);
} }
}).always(function() { }).always(function() {
$('[data-gpm-checkupdates]').find('i').removeClass('fa-spin'); $('[data-gpm-checkupdates]').find('i').removeClass('fa-spin');
}); });*/
}; };
if (GravAdmin.config.enable_auto_updates_check === '1') { /*if (GravAdmin.config.enable_auto_updates_check === '1') {
GPMRefresh(); GPMRefresh();
} }*/
function reIndex (collection) { /*function reIndex (collection) {
var holder = collection.find('[data-collection-holder]'), var holder = collection.find('[data-collection-holder]'),
addBtn = collection.find('[data-action="add"]'), addBtn = collection.find('[data-action="add"]'),
prefix = holder.data('collection-holder'), prefix = holder.data('collection-holder'),
@@ -469,10 +469,10 @@ $(function () {
MDEditors.add(field); MDEditors.add(field);
} }
}); });
}); });*/
// enable the toggleable checkbox when typing in the corresponding textarea/input element // enable the toggleable checkbox when typing in the corresponding textarea/input element
jQuery(document).on('input propertychange click', '.form-data textarea, .form-data input, .form-data label, .form-data .selectize-input', function() { /*jQuery(document).on('input propertychange click', '.form-data textarea, .form-data input, .form-data label, .form-data .selectize-input', function() {
var item = this; var item = this;
var checkbox = $(item).parents('.form-field').find('.toggleable input[type="checkbox"]'); var checkbox = $(item).parents('.form-field').find('.toggleable input[type="checkbox"]');
@@ -501,26 +501,26 @@ $(function () {
input.siblings('label').css('opacity', on ? 1 : 0.7); input.siblings('label').css('opacity', on ? 1 : 0.7);
$(this).parents('.form-label').siblings('.form-data').css('opacity', on ? 1 : 0.7); $(this).parents('.form-label').siblings('.form-data').css('opacity', on ? 1 : 0.7);
}); });*/
// Themes Switcher Warning // Themes Switcher Warning
$(document).on('mousedown', '[data-remodal-target="theme-switch-warn"]', function(e){ /*$(document).on('mousedown', '[data-remodal-target="theme-switch-warn"]', function(e){
var name = $(e.target).closest('[data-gpm-theme]').find('.gpm-name a').text(), var name = $(e.target).closest('[data-gpm-theme]').find('.gpm-name a').text(),
remodal = $('.remodal.theme-switcher'); remodal = $('.remodal.theme-switcher');
remodal.find('strong').text(name); remodal.find('strong').text(name);
remodal.find('.button.continue').attr('href', $(e.target).attr('href')); remodal.find('.button.continue').attr('href', $(e.target).attr('href'));
}); });*/
// Setup keep-alive on pages that have at least one element with data-grav-keepalive="true" set // Setup keep-alive on pages that have at least one element with data-grav-keepalive="true" set
if ($(document).find('[data-grav-keepalive="true"]').length > 0) { /*if ($(document).find('[data-grav-keepalive="true"]').length > 0) {
setInterval(function() { setInterval(function() {
keepAlive(); keepAlive();
}, (GravAdmin.config.admin_timeout/2)*1000); }, (GravAdmin.config.admin_timeout/2)*1000);
} }*/
// CTRL + S / CMD + S - shortcut for [Save] when available // CTRL + S / CMD + S - shortcut for [Save] when available
var saveTask = $('[name="task"][value="save"]').filter(function(index, element) { /*var saveTask = $('[name="task"][value="save"]').filter(function(index, element) {
return !($(element).parents('.remodal-overlay').length); return !($(element).parents('.remodal-overlay').length);
}); });
@@ -532,5 +532,5 @@ $(function () {
saveTask.click(); saveTask.click();
} }
}); });
} }*/
}); });

13993
themes/grav/js/admin.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

6
themes/grav/js/admin.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -204,7 +204,7 @@
this.scanned = true; this.scanned = true;
//Refresh root.currentValues as toggleables have been initialized //Refresh root.currentValues as toggleables have been initialized
root.currentValues = getState(); // root.currentValues = getState();
}; };
Form.factories = {}; Form.factories = {};

View File

@@ -1,5 +1,5 @@
((function(){ ((function() {
var toolbarIdentifiers = [ 'bold', 'italic', 'strike', 'link', 'image', 'blockquote', 'listUl', 'listOl' ]; var toolbarIdentifiers = ['bold', 'italic', 'strike', 'link', 'image', 'blockquote', 'listUl', 'listOl'];
if (typeof window.customToolbarElements !== 'undefined') { if (typeof window.customToolbarElements !== 'undefined') {
window.customToolbarElements.forEach(function(customToolbarElement) { window.customToolbarElements.forEach(function(customToolbarElement) {
toolbarIdentifiers.push(customToolbarElement.identifier); toolbarIdentifiers.push(customToolbarElement.identifier);
@@ -8,40 +8,40 @@
var toolbarButtons = { var toolbarButtons = {
fullscreen: { fullscreen: {
title : 'Fullscreen', title: 'Fullscreen',
label : '<i class="fa fa-fw fa-expand"></i>' label: '<i class="fa fa-fw fa-expand"></i>'
}, },
bold : { bold: {
title : 'Bold', title: 'Bold',
label : '<i class="fa fa-fw fa-bold"></i>' label: '<i class="fa fa-fw fa-bold"></i>'
}, },
italic : { italic: {
title : 'Italic', title: 'Italic',
label : '<i class="fa fa-fw fa-italic"></i>' label: '<i class="fa fa-fw fa-italic"></i>'
}, },
strike : { strike: {
title : 'Strikethrough', title: 'Strikethrough',
label : '<i class="fa fa-fw fa-strikethrough"></i>' label: '<i class="fa fa-fw fa-strikethrough"></i>'
}, },
blockquote : { blockquote: {
title : 'Blockquote', title: 'Blockquote',
label : '<i class="fa fa-fw fa-quote-right"></i>' label: '<i class="fa fa-fw fa-quote-right"></i>'
}, },
link : { link: {
title : 'Link', title: 'Link',
label : '<i class="fa fa-fw fa-link"></i>' label: '<i class="fa fa-fw fa-link"></i>'
}, },
image : { image: {
title : 'Image', title: 'Image',
label : '<i class="fa fa-fw fa-picture-o"></i>' label: '<i class="fa fa-fw fa-picture-o"></i>'
}, },
listUl : { listUl: {
title : 'Unordered List', title: 'Unordered List',
label : '<i class="fa fa-fw fa-list-ul"></i>' label: '<i class="fa fa-fw fa-list-ul"></i>'
}, },
listOl : { listOl: {
title : 'Ordered List', title: 'Ordered List',
label : '<i class="fa fa-fw fa-list-ol"></i>' label: '<i class="fa fa-fw fa-list-ol"></i>'
} }
}; };
@@ -68,20 +68,34 @@
var template = ''; var template = '';
var MDEditor = function(editor, options){ var MDEditor = function(editor, options) {
var $this = this, var $this = this,
task = 'task' + GravAdmin.config.param_sep; task = 'task' + GravAdmin.config.param_sep;
var tpl = '' var tpl = ''
this.defaults = { this.defaults = {
markdown : false, markdown: false,
autocomplete : true, autocomplete: true,
height : 500, height: 500,
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"} }, codemirror: {
toolbar : toolbarIdentifiers, mode: 'htmlmixed',
lblPreview : '<i class="fa fa-fw fa-eye"></i>', theme: 'paper',
lblCodeview : '<i class="fa fa-fw fa-code"></i>', lineWrapping: true,
dragDrop: true,
autoCloseTags: true,
matchTags: true,
autoCloseBrackets: true,
matchBrackets: true,
indentUnit: 4,
indentWithTabs: false,
tabSize: 4,
hintOptions: { completionSingle: false },
extraKeys: { "Enter": "newlineAndIndentContinueMarkdownList" }
},
toolbar: toolbarIdentifiers,
lblPreview: '<i class="fa fa-fw fa-eye"></i>',
lblCodeview: '<i class="fa fa-fw fa-code"></i>',
lblMarkedview: '<i class="fa fa-fw fa-code"></i>' lblMarkedview: '<i class="fa fa-fw fa-code"></i>'
}; };
@@ -89,14 +103,14 @@
this.options = $.extend({}, this.defaults, options); this.options = $.extend({}, this.defaults, options);
this.CodeMirror = CodeMirror; this.CodeMirror = CodeMirror;
this.buttons = {}; this.buttons = {};
template = [ template = [
'<div class="grav-mdeditor clearfix" data-mode="tab" data-active-tab="code">', '<div class="grav-mdeditor clearfix" data-mode="tab" data-active-tab="code">',
'<div class="grav-mdeditor-navbar">', '<div class="grav-mdeditor-navbar">',
'<ul class="grav-mdeditor-navbar-nav grav-mdeditor-toolbar"></ul>', '<ul class="grav-mdeditor-navbar-nav grav-mdeditor-toolbar"></ul>',
'<div class="grav-mdeditor-navbar-flip">', '<div class="grav-mdeditor-navbar-flip">',
'<ul class="grav-mdeditor-navbar-nav">']; '<ul class="grav-mdeditor-navbar-nav">'];
if ($this.element.data('grav-preview-enabled')) { if ($this.element.data('grav-preview-enabled')) {
template.push('<li class="grav-mdeditor-button-code mdeditor-active"><a>{:lblCodeview}</a></li>'); template.push('<li class="grav-mdeditor-button-code mdeditor-active"><a>{:lblCodeview}</a></li>');
@@ -104,15 +118,15 @@
} }
template.push( template.push(
'<li><a data-mdeditor-button="fullscreen"><i class="fa fa-fw fa-expand"></i></a></li>', '<li><a data-mdeditor-button="fullscreen"><i class="fa fa-fw fa-expand"></i></a></li>',
'</ul>', '</ul>',
'</div>', '</div>',
'<p class="grav-mdeditor-preview-text" style="display: none;">Preview</p>', '<p class="grav-mdeditor-preview-text" style="display: none;">Preview</p>',
'</div>', '</div>',
'<div class="grav-mdeditor-content">', '<div class="grav-mdeditor-content">',
'<div class="grav-mdeditor-code"></div>', '<div class="grav-mdeditor-code"></div>',
'<div class="grav-mdeditor-preview"><div></div></div>', '<div class="grav-mdeditor-preview"><div></div></div>',
'</div>', '</div>',
'</div>' '</div>'
); );
@@ -123,10 +137,10 @@
tpl = tpl.replace(/\{:lblCodeview\}/g, this.options.lblCodeview); tpl = tpl.replace(/\{:lblCodeview\}/g, this.options.lblCodeview);
this.mdeditor = $(tpl); this.mdeditor = $(tpl);
this.content = this.mdeditor.find('.grav-mdeditor-content'); this.content = this.mdeditor.find('.grav-mdeditor-content');
this.toolbar = this.mdeditor.find('.grav-mdeditor-toolbar'); this.toolbar = this.mdeditor.find('.grav-mdeditor-toolbar');
this.preview = this.mdeditor.find('.grav-mdeditor-preview').children().eq(0); this.preview = this.mdeditor.find('.grav-mdeditor-preview').children().eq(0);
this.code = this.mdeditor.find('.grav-mdeditor-code'); this.code = this.mdeditor.find('.grav-mdeditor-code');
this.element.before(this.mdeditor).appendTo(this.code); this.element.before(this.mdeditor).appendTo(this.code);
this.editor = this.CodeMirror.fromTextArea(this.element[0], this.options.codemirror); this.editor = this.CodeMirror.fromTextArea(this.element[0], this.options.codemirror);
@@ -170,7 +184,7 @@
method: 'post', method: 'post',
data: $this.element.parents('form').serialize(), data: $this.element.parents('form').serialize(),
toastErrors: true, toastErrors: true,
success: function (response) { success: function(response) {
$this.preview.container.html(response.message); $this.preview.container.html(response.message);
} }
}); });
@@ -222,11 +236,11 @@
}, 100)); }, 100));
} }
this.debouncedRedraw = debounce(function () { $this.redraw(); }, 5); this.debouncedRedraw = debounce(function() { $this.redraw(); }, 5);
/*this.element.attr('data-grav-check-display', 1).on('grav-check-display', function(e) { /*this.element.attr('data-grav-check-display', 1).on('grav-check-display', function(e) {
if($this.mdeditor.is(":visible")) $this.fit(); if($this.mdeditor.is(":visible")) $this.fit();
});*/ });*/
MDEditors.editors[this.element.attr('name')] = this; MDEditors.editors[this.element.attr('name')] = this;
this.element.data('mdeditor_initialized', true); this.element.data('mdeditor_initialized', true);
@@ -256,7 +270,7 @@
var title = $this.buttons[button].title ? $this.buttons[button].title : button; var title = $this.buttons[button].title ? $this.buttons[button].title : button;
var buttonClass = $this.buttons[button].class ? 'class="' + $this.buttons[button].class + '"' : ''; var buttonClass = $this.buttons[button].class ? 'class="' + $this.buttons[button].class + '"' : '';
bar.push('<li><a data-mdeditor-button="'+button+'" title="'+title+'" '+buttonClass+' data-uk-tooltip>'+$this.buttons[button].label+'</a></li>'); bar.push('<li><a data-mdeditor-button="' + button + '" title="' + title + '" ' + buttonClass + ' data-uk-tooltip>' + $this.buttons[button].label + '</a></li>');
}); });
this.toolbar.html(bar.join('\n')); this.toolbar.html(bar.join('\n'));
@@ -298,7 +312,7 @@
}; };
this.getCursorMode = function() { this.getCursorMode = function() {
var param = { mode: 'html'}; var param = { mode: 'html' };
this.element.trigger('cursorMode', [param]); this.element.trigger('cursorMode', [param]);
return param.mode; return param.mode;
}; };
@@ -361,7 +375,13 @@
var curWord = start != end && curLine.slice(start, end); var curWord = start != end && curLine.slice(start, end);
if (curWord) { if (curWord) {
this.editor.setSelection({ line: cur.line, ch: start}, { line: cur.line, ch: end }); this.editor.setSelection({
line: cur.line,
ch: start
}, {
line: cur.line,
ch: end
});
text = curWord; text = curWord;
} else { } else {
indexOf = replace.indexOf('$1'); indexOf = replace.indexOf('$1');
@@ -372,10 +392,16 @@
this.editor.replaceSelection(html, 'end'); this.editor.replaceSelection(html, 'end');
if (indexOf !== -1) { if (indexOf !== -1) {
this.editor.setCursor({ line: cur.line, ch: start + indexOf }); this.editor.setCursor({
line: cur.line,
ch: start + indexOf
});
} else { } else {
if (action == 'link' || action == 'image') { if (action == 'link' || action == 'image') {
this.editor.setCursor({ line: cur.line, ch: html.length -1 }); this.editor.setCursor({
line: cur.line,
ch: html.length - 1
});
} }
} }
@@ -387,8 +413,17 @@
text = this.editor.getLine(pos.line), text = this.editor.getLine(pos.line),
html = replace.replace('$1', text); html = replace.replace('$1', text);
this.editor.replaceRange(html , { line: pos.line, ch: 0 }, { line: pos.line, ch: text.length }); this.editor.replaceRange(html, {
this.editor.setCursor({ line: pos.line, ch: html.length }); line: pos.line,
ch: 0
}, {
line: pos.line,
ch: text.length
});
this.editor.setCursor({
line: pos.line,
ch: html.length
});
this.editor.focus(); this.editor.focus();
}; };
@@ -410,15 +445,24 @@
if (editor.getCursorMode() == 'markdown') { if (editor.getCursorMode() == 'markdown') {
var cm = editor.editor, var cm = editor.editor,
pos = cm.getDoc().getCursor(true), pos = cm.getDoc().getCursor(true),
posend = cm.getDoc().getCursor(false); posend = cm.getDoc().getCursor(false);
for (var i=pos.line; i<(posend.line+1);i++) { for (var i = pos.line; i < (posend.line + 1); i++) {
cm.replaceRange('* '+cm.getLine(i), { line: i, ch: 0 }, { line: i, ch: cm.getLine(i).length }); cm.replaceRange('* ' + cm.getLine(i), {
line: i,
ch: 0
}, {
line: i,
ch: cm.getLine(i).length
});
} }
cm.setCursor({ line: posend.line, ch: cm.getLine(posend.line).length }); cm.setCursor({
line: posend.line,
ch: cm.getLine(posend.line).length
});
cm.focus(); cm.focus();
} }
}); });
@@ -427,25 +471,34 @@
if (editor.getCursorMode() == 'markdown') { if (editor.getCursorMode() == 'markdown') {
var cm = editor.editor, var cm = editor.editor,
pos = cm.getDoc().getCursor(true), pos = cm.getDoc().getCursor(true),
posend = cm.getDoc().getCursor(false), posend = cm.getDoc().getCursor(false),
prefix = 1; prefix = 1;
if (pos.line > 0) { if (pos.line > 0) {
var prevline = cm.getLine(pos.line-1), matches; var prevline = cm.getLine(pos.line - 1), matches;
if(matches = prevline.match(/^(\d+)\./)) { if (matches = prevline.match(/^(\d+)\./)) {
prefix = Number(matches[1])+1; prefix = Number(matches[1]) + 1;
} }
} }
for (var i=pos.line; i<(posend.line+1);i++) { for (var i = pos.line; i < (posend.line + 1); i++) {
cm.replaceRange(prefix+'. '+cm.getLine(i), { line: i, ch: 0 }, { line: i, ch: cm.getLine(i).length }); cm.replaceRange(prefix + '. ' + cm.getLine(i), {
line: i,
ch: 0
}, {
line: i,
ch: cm.getLine(i).length
});
prefix++; prefix++;
} }
cm.setCursor({ line: posend.line, ch: cm.getLine(posend.line).length }); cm.setCursor({
line: posend.line,
ch: cm.getLine(posend.line).length
});
cm.focus(); cm.focus();
} }
}); });
@@ -485,8 +538,8 @@
// switch markdown mode on event // switch markdown mode on event
editor.element.on({ editor.element.on({
enableMarkdown : function() { editor.enableMarkdown(); }, enableMarkdown: function() { editor.enableMarkdown(); },
disableMarkdown : function() { editor.disableMarkdown(); } disableMarkdown: function() { editor.disableMarkdown(); }
}); });
function enableMarkdown() { function enableMarkdown() {
@@ -501,16 +554,22 @@
if (editor.mdeditor.hasClass('grav-mdeditor-fullscreen')) { if (editor.mdeditor.hasClass('grav-mdeditor-fullscreen')) {
editor.editor.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset, width: wrap.style.width, height: wrap.style.height}; editor.editor.state.fullScreenRestore = {
wrap.style.width = ''; scrollTop: window.pageYOffset,
wrap.style.height = editor.content.height()+'px'; scrollLeft: window.pageXOffset,
width: wrap.style.width,
height: wrap.style.height
};
wrap.style.width = '';
wrap.style.height = editor.content.height() + 'px';
document.documentElement.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden';
} else { } else {
document.documentElement.style.overflow = ''; document.documentElement.style.overflow = '';
var info = editor.editor.state.fullScreenRestore; var info = editor.editor.state.fullScreenRestore;
wrap.style.width = info.width; wrap.style.height = info.height; wrap.style.width = info.width;
wrap.style.height = info.height;
window.scrollTo(info.scrollLeft, info.scrollTop); window.scrollTo(info.scrollLeft, info.scrollTop);
} }
@@ -525,7 +584,7 @@
editor.addShortcutAction('italic', ['Ctrl-I', 'Cmd-I']); editor.addShortcutAction('italic', ['Ctrl-I', 'Cmd-I']);
function addAction(name, replace, mode) { function addAction(name, replace, mode) {
editor.element.on('action.'+name, function() { editor.element.on('action.' + name, function() {
if (editor.getCursorMode() == 'markdown') { if (editor.getCursorMode() == 'markdown') {
editor[mode == 'replaceLine' ? 'replaceLine' : 'replaceSelection'](replace, name); editor[mode == 'replaceLine' ? 'replaceLine' : 'replaceSelection'](replace, name);
} }
@@ -553,20 +612,24 @@
}); });
}, },
add: function(editor) { add: function(editors) {
editor = $(editor); editors = $(editors);
var mdeditor; var mdeditor = [];
if (!editor.data('mdeditor_initialized')) {
mdeditor = new MDEditor(editor, JSON.parse(editor.attr('data-grav-mdeditor') || '{}'));
}
return mdeditor || MDEditors.editors[editor.attr('name')]; editors.each(function(index, editor) {
editor = $(editor);
if (!editor.data('mdeditor_initialized')) {
mdeditor.push(new MDEditor(editor, JSON.parse(editor.attr('data-grav-mdeditor') || '{}')));
}
});
return mdeditor || MDEditors.editors[editors.attr('name')];
} }
}; };
// init // init
$(function(){ $(function() {
MDEditors.init(); MDEditors.init();
}); });

File diff suppressed because one or more lines are too long

View File

@@ -3,189 +3,189 @@ $(function(){
root = root.GravJS = root.GravJS || {}; root = root.GravJS = root.GravJS || {};
//Make it global because used by ./forms/form.js //Make it global because used by ./forms/form.js
root.currentValues = getState(); //root.currentValues = getState();
var clickedLink; //var clickedLink;
// selectize // selectize
var pageFilter = $('input.page-filter'), /*var pageFilter = $('input.page-filter'),
pageTypes = pageFilter.data('template-types'), pageTypes = pageFilter.data('template-types'),
accessLevels = pageFilter.data('template-access-levels'), accessLevels = pageFilter.data('template-access-levels'),
options = [ options = [
{flag: 'Modular', key: 'Modular', cat: 'mode'}, {flag: 'Modular', key: 'Modular', cat: 'mode'},
{flag: 'Visible', key: 'Visible', cat: 'mode'}, {flag: 'Visible', key: 'Visible', cat: 'mode'},
{flag: 'Routable', key: 'Routable', cat: 'mode'}, {flag: 'Routable', key: 'Routable', cat: 'mode'},
{flag: 'Published', key: 'Published', cat: 'mode'}, {flag: 'Published', key: 'Published', cat: 'mode'},
{flag: 'Non-Modular', key: 'NonModular', cat: 'mode'}, {flag: 'Non-Modular', key: 'NonModular', cat: 'mode'},
{flag: 'Non-Visible', key: 'NonVisible', cat: 'mode'}, {flag: 'Non-Visible', key: 'NonVisible', cat: 'mode'},
{flag: 'Non-Routable', key: 'NonRoutable', cat: 'mode'}, {flag: 'Non-Routable', key: 'NonRoutable', cat: 'mode'},
{flag: 'Non-Published', key: 'NonPublished', cat: 'mode'}, {flag: 'Non-Published', key: 'NonPublished', cat: 'mode'},
]; ];
if (pageFilter && pageTypes) { if (pageFilter && pageTypes) {
jQuery.each(pageTypes, function(key, name){ jQuery.each(pageTypes, function(key, name){
options.push({flag: name, key: key, cat: 'type'}); options.push({flag: name, key: key, cat: 'type'});
}) })
jQuery.each(accessLevels, function(key, name){ jQuery.each(accessLevels, function(key, name){
options.push({flag: name, key: name, cat: 'access'}); options.push({flag: name, key: name, cat: 'access'});
}) })
pageFilter.selectize({ pageFilter.selectize({
maxItems: null, maxItems: null,
valueField: 'key', valueField: 'key',
labelField: 'flag', labelField: 'flag',
searchField: ['flag', 'key'], searchField: ['flag', 'key'],
options: options, options: options,
optgroups: [ optgroups: [
{id: 'mode', name: translations.PLUGIN_ADMIN.PAGE_MODES}, {id: 'mode', name: translations.PLUGIN_ADMIN.PAGE_MODES},
{id: 'type', name: translations.PLUGIN_ADMIN.PAGE_TYPES}, {id: 'type', name: translations.PLUGIN_ADMIN.PAGE_TYPES},
{id: 'access', name: translations.PLUGIN_ADMIN.ACCESS_LEVELS}, {id: 'access', name: translations.PLUGIN_ADMIN.ACCESS_LEVELS},
], ],
optgroupField: 'cat', optgroupField: 'cat',
optgroupLabelField: 'name', optgroupLabelField: 'name',
optgroupValueField: 'id', optgroupValueField: 'id',
optgroupOrder: ['mode', 'type', 'access'], optgroupOrder: ['mode', 'type', 'access'],
plugins: ['optgroup_columns'] plugins: ['optgroup_columns']
}); });
} }
try { try {
sessionStorage.setItem('sessionStorage', 1); sessionStorage.setItem('sessionStorage', 1);
sessionStorage.removeItem('sessionStorage'); sessionStorage.removeItem('sessionStorage');
} catch (e) { } catch (e) {
Storage.prototype._setItem = Storage.prototype.setItem; Storage.prototype._setItem = Storage.prototype.setItem;
Storage.prototype.setItem = function() {}; Storage.prototype.setItem = function() {};
} }
var childrenToggles = $('[data-toggle="children"]'), var childrenToggles = $('[data-toggle="children"]'),
storage = sessionStorage.getItem('grav:admin:pages'), storage = sessionStorage.getItem('grav:admin:pages'),
collapseAll = function(store) { collapseAll = function(store) {
childrenToggles.each(function(i, element){ childrenToggles.each(function(i, element){
var icon = $(element).find('.page-icon'), var icon = $(element).find('.page-icon'),
open = icon.hasClass('children-open'), open = icon.hasClass('children-open'),
key = $(element).closest('[data-nav-id]').data('nav-id'), key = $(element).closest('[data-nav-id]').data('nav-id'),
children = $(element).closest('li.page-item').find('ul:first'); children = $(element).closest('li.page-item').find('ul:first');
if (open) { if (open) {
children.hide(); children.hide();
if (store) delete storage[key]; if (store) delete storage[key];
icon.removeClass('children-open').addClass('children-closed'); icon.removeClass('children-open').addClass('children-closed');
} }
if (store) sessionStorage.setItem('grav:admin:pages', JSON.stringify(storage)); if (store) sessionStorage.setItem('grav:admin:pages', JSON.stringify(storage));
}); });
}, },
expandAll = function(store) { expandAll = function(store) {
childrenToggles.each(function(i, element){ childrenToggles.each(function(i, element){
var icon = $(element).find('.page-icon'), var icon = $(element).find('.page-icon'),
open = icon.hasClass('children-open'), open = icon.hasClass('children-open'),
key = $(element).closest('[data-nav-id]').data('nav-id'), key = $(element).closest('[data-nav-id]').data('nav-id'),
children = $(element).closest('li.page-item').find('ul:first'); children = $(element).closest('li.page-item').find('ul:first');
if (!open) { if (!open) {
children.show(); children.show();
if (store) storage[key] = 1; if (store) storage[key] = 1;
icon.removeClass('children-closed').addClass('children-open'); icon.removeClass('children-closed').addClass('children-open');
} }
if (store) sessionStorage.setItem('grav:admin:pages', JSON.stringify(storage)); if (store) sessionStorage.setItem('grav:admin:pages', JSON.stringify(storage));
}); });
}, },
restoreStates = function() { restoreStates = function() {
collapseAll(); collapseAll();
for (var key in storage) { for (var key in storage) {
var element = $('[data-nav-id="' + key + '"]'), var element = $('[data-nav-id="' + key + '"]'),
icon = element.find('.page-icon').first(), icon = element.find('.page-icon').first(),
open = icon.hasClass('children-open'), open = icon.hasClass('children-open'),
children = element.closest('li.page-item').find('ul:first'); children = element.closest('li.page-item').find('ul:first');
children.show(); children.show();
icon.removeClass('children-closed').addClass('children-open'); icon.removeClass('children-closed').addClass('children-open');
} }
}; };
if (!storage) { if (!storage) {
sessionStorage.setItem('grav:admin:pages', (storage = '{}')); sessionStorage.setItem('grav:admin:pages', (storage = '{}'));
} }
storage = JSON.parse(storage); storage = JSON.parse(storage);
restoreStates(); restoreStates();
var startFilterPages = function () { var startFilterPages = function () {
var task = 'task' + GravAdmin.config.param_sep; var task = 'task' + GravAdmin.config.param_sep;
$('input[name="page-search"]').focus(); $('input[name="page-search"]').focus();
var flags = $('input[name="page-filter"]').val(), var flags = $('input[name="page-filter"]').val(),
query = $('input[name="page-search"]').val(); query = $('input[name="page-search"]').val();
if (!flags.length && !query.length) { if (!flags.length && !query.length) {
GravAjax.jqxhr.abort(); GravAjax.jqxhr.abort();
return finishFilterPages([], true); return finishFilterPages([], true);
} }
GravAjax({ GravAjax({
dataType: 'json', dataType: 'json',
method: 'POST', method: 'POST',
url: GravAdmin.config.base_url_relative + '/pages-filter.json/' + task + 'filterPages', url: GravAdmin.config.base_url_relative + '/pages-filter.json/' + task + 'filterPages',
data: { data: {
flags: flags, flags: flags,
query: query, query: query,
'admin-nonce': GravAdmin.config.admin_nonce 'admin-nonce': GravAdmin.config.admin_nonce
}, },
toastErrors: true, toastErrors: true,
success: function (result, status) { success: function (result, status) {
finishFilterPages(result.results); finishFilterPages(result.results);
} }
}); });
}; };
var finishFilterPages = function (pages, reset) { var finishFilterPages = function (pages, reset) {
var items = $('[data-nav-id]'); var items = $('[data-nav-id]');
items.removeClass('search-match'); items.removeClass('search-match');
if (reset) { if (reset) {
items.addClass('search-match'); items.addClass('search-match');
restoreStates(); restoreStates();
} else { } else {
pages.forEach(function (id) { pages.forEach(function (id) {
var match = items.filter('[data-nav-id="' + id + '"]'), var match = items.filter('[data-nav-id="' + id + '"]'),
parents = match.parents('[data-nav-id]'); parents = match.parents('[data-nav-id]');
match.addClass('search-match'); match.addClass('search-match');
match.find('[data-nav-id]').addClass('search-match'); match.find('[data-nav-id]').addClass('search-match');
parents.addClass('search-match'); parents.addClass('search-match');
parents.find('[data-toggle="children"]').each(function(index, element){ parents.find('[data-toggle="children"]').each(function(index, element){
var icon = $(this).find('.page-icon'), var icon = $(this).find('.page-icon'),
open = icon.hasClass('children-open'), open = icon.hasClass('children-open'),
children = $(this).closest('li.page-item').find('ul:first'); children = $(this).closest('li.page-item').find('ul:first');
if (!open) { if (!open) {
children.show(); children.show();
icon.removeClass('children-closed').addClass('children-open'); icon.removeClass('children-closed').addClass('children-open');
} }
}); });
}); });
} }
items.each(function (key, item) { items.each(function (key, item) {
if ($(item).hasClass('search-match')) { if ($(item).hasClass('search-match')) {
$(item).show(); $(item).show();
} else { } else {
$(item).hide(); $(item).hide();
} }
}); });
}; };*/
// selectize // selectize
$('input[name="page-search"]').on('input', startFilterPages); /*$('input[name="page-search"]').on('input', startFilterPages);
$('input[name="page-filter"]').on('change', startFilterPages); $('input[name="page-filter"]').on('change', startFilterPages);*/
// auto generate folder based on title // auto generate folder based on title
// on user input on folder, autogeneration stops // on user input on folder, autogeneration stops
// if user empties the folder, autogeneration restarts // if user empties the folder, autogeneration restarts
$('input[name="folder"]').on('input', function(){ /*$('input[name="folder"]').on('input', function(){
$(this).data('user-custom-folder', true); $(this).data('user-custom-folder', true);
if (!$(this).val()) $(this).data('user-custom-folder', false); if (!$(this).val()) $(this).data('user-custom-folder', false);
}); });
@@ -209,105 +209,108 @@ $(function(){
// restore cursor position // restore cursor position
this.setSelectionRange(start, end); this.setSelectionRange(start, end);
}); });*/
childrenToggles.on('click', function () { /* childrenToggles.on('click', function () {
var icon = $(this).find('.page-icon'), var icon = $(this).find('.page-icon'),
open = icon.hasClass('children-open'), open = icon.hasClass('children-open'),
key = $(this).closest('[data-nav-id]').data('nav-id'), key = $(this).closest('[data-nav-id]').data('nav-id'),
children = $(this).closest('li.page-item').find('ul:first'); children = $(this).closest('li.page-item').find('ul:first');
if (open) { if (open) {
children.hide(); children.hide();
delete storage[key]; delete storage[key];
icon.removeClass('children-open').addClass('children-closed'); icon.removeClass('children-open').addClass('children-closed');
} else { } else {
children.show(); children.show();
storage[key] = true; storage[key] = true;
icon.removeClass('children-closed').addClass('children-open'); icon.removeClass('children-closed').addClass('children-open');
} }
sessionStorage.setItem('grav:admin:pages', JSON.stringify(storage));
});
$('[data-page-toggleall]').on('click', function() {
var state = $(this).data('page-toggleall');
if (state == 'collapse') collapseAll(true);
else expandAll(true);
});
sessionStorage.setItem('grav:admin:pages', JSON.stringify(storage));
});
*/
/*$('[data-page-toggleall]').on('click', function() {
var state = $(this).data('page-toggleall');
if (state == 'collapse') collapseAll(true);
else expandAll(true);
});
*/
/*
UNKNOWN
$('#admin-main button').on('click', function(){ $('#admin-main button').on('click', function(){
$(window).off('beforeunload'); $(window).off('beforeunload');
}); });
$('[data-remodal-id] form').on('submit', function(){ $('[data-remodal-id] form').on('submit', function(){
$(window).off('beforeunload'); $(window).off('beforeunload');
}); });*/
$("#admin-mode-toggle input[name=mode-switch]").on('change', function(e){ /*$("#admin-mode-toggle input[name=mode-switch]").on('change', function(e){
var value = $(this).val(), var value = $(this).val(),
uri = $(this).data('leave-url'); uri = $(this).data('leave-url');
if (root.currentValues == getState()) { if (root.currentValues == getState()) {
setTimeout(function(){ setTimeout(function(){
window.location.href = uri; window.location.href = uri;
}, 200) }, 200)
return true; return true;
} }
e.preventDefault(); e.preventDefault();
var confirm = $.remodal.lookup[$('[data-remodal-id=changes]').data('remodal')], var confirm = $.remodal.lookup[$('[data-remodal-id=changes]').data('remodal')],
buttons = $('[data-remodal-id=changes] a.button'), buttons = $('[data-remodal-id=changes] a.button'),
action; action;
buttons.on('click', function(e){ buttons.on('click', function(e){
e.preventDefault(); e.preventDefault();
action = $(this).data('leave-action'); action = $(this).data('leave-action');
buttons.off('click'); buttons.off('click');
confirm.close(); confirm.close();
if (action == 'continue') { if (action == 'continue') {
$(window).off('beforeunload'); $(window).off('beforeunload');
window.location.href = $("#admin-mode-toggle input[name=mode-switch]:checked").data('leave-url'); window.location.href = $("#admin-mode-toggle input[name=mode-switch]:checked").data('leave-url');
} else { } else {
$('input[name=mode-switch][checked]').prop('checked', true); $('input[name=mode-switch][checked]').prop('checked', true);
} }
}); });
confirm.open(); confirm.open();
}); });*/
$('a[href]:not([href^="#"])').on('click', function(e){ /*$('a[href]:not([href^=#])').on('click', function(e){
if (root.currentValues != getState()){ if (root.currentValues != getState()){
e.preventDefault(); e.preventDefault();
clickedLink = $(this).attr('href'); clickedLink = $(this).attr('href');
var confirm = $.remodal.lookup[$('[data-remodal-id=changes]').data('remodal')], var confirm = $.remodal.lookup[$('[data-remodal-id=changes]').data('remodal')],
buttons = $('[data-remodal-id=changes] a.button'), buttons = $('[data-remodal-id=changes] a.button'),
action; action;
buttons.on('click', function(e){ buttons.on('click', function(e){
e.preventDefault(); e.preventDefault();
action = $(this).data('leave-action'); action = $(this).data('leave-action');
buttons.off('click'); buttons.off('click');
confirm.close(); confirm.close();
if (action == 'continue') { if (action == 'continue') {
$(window).off('beforeunload'); $(window).off('beforeunload');
window.location.href = clickedLink; window.location.href = clickedLink;
} }
}); });
confirm.open(); confirm.open();
} }
}); });*/
// deletion // deletion
/*
$('[data-remodal-target="delete"]').on('click', function(){ $('[data-remodal-target="delete"]').on('click', function(){
var okdelete = $('[data-remodal-id=delete] a.button'); var okdelete = $('[data-remodal-id=delete] a.button');
@@ -320,26 +323,26 @@ $(function(){
window.location.href = okdelete; window.location.href = okdelete;
confirm.close(); confirm.close();
}); });*/
$(window).on('beforeunload', function(){ /*$(window).on('beforeunload', function(){
if (root.currentValues != getState()){ if (root.currentValues != getState()){
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"; 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";
} }
}); });*/
// Move dropdown sync (on dropdown change) // Move dropdown sync (on dropdown change)
/*$('body').on('change', '[data-page-move] select', function(){ /*$('body').on('change', '[data-page-move] select', function(){
var route = jQuery('form#blueprints').first().find('select[name="route"]'), var route = jQuery('form#blueprints').first().find('select[name="route"]'),
value = $(this).val(); value = $(this).val();
if (route.length && route.val() !== value) { if (route.length && route.val() !== value) {
route.val(value); route.val(value);
route.data('selectize').setValue(value); route.data('selectize').setValue(value);
} }
});*/ });*/
// Move dropdown sync (on continue) // Move dropdown sync (on continue)
$('[data-page-move] button').on('click', function(){ /*$('[data-page-move] button').on('click', function(){
var route = jQuery('form#blueprints').first().find('select[name="route"]'), var route = jQuery('form#blueprints').first().find('select[name="route"]'),
value = $('[data-page-move] select').val(); value = $('[data-page-move] select').val();
if (route.length && route.val() !== value) { if (route.length && route.val() !== value) {
@@ -347,5 +350,5 @@ $(function(){
route.val(value); route.val(value);
if (selectize) selectize.setValue(value); if (selectize) selectize.setValue(value);
} }
}); });*/
}); });

14642
themes/grav/js/vendor.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

13
themes/grav/js/vendor.min.js vendored Normal file

File diff suppressed because one or more lines are too long

43
themes/grav/package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "grav-admin",
"version": "1.0.0",
"description": "Grav Admin",
"repository": "https://github.com/getgrav/grav-admin",
"main": "app/main.js",
"scripts": {
"watch": "webpack --watch --progress --colors --config webpack.conf.js",
"dev": "webpack --progress --colors --config webpack.conf.js",
"prod": "NODE_ENV=production webpack -p --config webpack.conf.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "RocketTheme, LLC",
"license": "MIT",
"dependencies": {
"bootstrap": "^3.3.6",
"chartist": "^0.9.5",
"debounce": "^1.0.0",
"dropzone": "^4.2.0",
"eonasdan-bootstrap-datetimepicker": "^4.15.35",
"immutable": "^3.7.6",
"jquery-slugify": "^1.2.3",
"remodal": "^1.0.6",
"selectize": "^0.12.1",
"sortablejs": "^1.4.2",
"toastr": "^2.1.2",
"whatwg-fetch": "^0.10.1"
},
"devDependencies": {
"babel-core": "^6.3.26",
"babel-loader": "^6.2.0",
"babel-polyfill": "^6.3.14",
"babel-preset-es2015": "^6.3.13",
"eslint": "^1.10.3",
"eslint-loader": "^1.1.1",
"exports-loader": "^0.6.2",
"gulp": "^3.9.0",
"gulp-webpack": "^1.5.0",
"imports-loader": "^0.6.5",
"merge-stream": "^1.0.0",
"webpack": "^1.12.9"
}
}

View File

@@ -13,101 +13,146 @@
/* Hide scroll bar */ /* Hide scroll bar */
html.remodal_lock, body.remodal_lock { html.remodal-is-locked {
overflow: hidden; overflow: hidden;
touch-action: none;
} }
/* Anti FOUC */ /* Anti FOUC */
.remodal, [data-remodal-id] { .remodal, [data-remodal-id] {
visibility: hidden; display: none;
} }
/* Overlay necessary styles */ /* Overlay necessary styles */
.remodal-overlay { .remodal-overlay {
position: fixed; position: fixed;
z-index: 99999;
top: -5000px;
right: -5000px;
bottom: -5000px;
left: -5000px;
display: none;
}
/* Necessary styles of the wrapper */
.remodal-wrapper {
position: fixed;
z-index: 100000;
top: 0; top: 0;
left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
z-index: 10000; left: 0;
display: none; display: none;
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch;
text-align: center; text-align: center;
-webkit-overflow-scrolling: touch;
&:after { &:after {
display: inline-block; display: inline-block;
height: 100%; height: 100%;
margin-left: -0.05em; margin-left: -0.05em;
content: '';
}
/* Fix iPad, iPhone glitches */ content: '';
> * {
-webkit-transform: translateZ(0px);
} }
} }
/* Fix iPad, iPhone glitches */
.remodal-overlay,
.remodal-wrapper {
backface-visibility: hidden;
}
/* Modal dialog necessary styles */ /* Modal dialog necessary styles */
.remodal { .remodal {
position: relative; position: relative;
outline: none;
text-size-adjust: 100%;
}
.remodal-is-initialized {
/* Disable Anti-FOUC */
display: inline-block; display: inline-block;
text-align: left;
} }
/* Background for effects */ /* ==========================================================================
Remodal's default mobile first theme
========================================================================== */
.remodal-bg { /* Default theme styles for the background */
@include transition-property (filter);
@include transition-duration(0.2s); .remodal-bg.remodal-is-opening,
@include transition-timing-function(linear); .remodal-bg.remodal-is-opened {
@include filter(blur(3px));
} }
// body.remodal_active .remodal-bg { /* Default theme styles of the overlay */
// @include filter(blur(5px));
// }
/* Overlay default theme styles */
.remodal-overlay { .remodal-overlay {
opacity: 0; background: rgba(43, 46, 56, 0.9);
background: rgba(33, 36, 46, 0.8);
@include transition(opacity 0.2s linear);
} }
body.remodal_active .remodal-overlay { .remodal-overlay.remodal-is-opening,
opacity: 1; .remodal-overlay.remodal-is-closing {
animation-duration: 0.3s;
animation-fill-mode: forwards;
} }
/* Modal dialog default theme styles */ .remodal-overlay.remodal-is-opening {
animation-name: remodal-overlay-opening-keyframes;
}
.remodal-overlay.remodal-is-closing {
animation-name: remodal-overlay-closing-keyframes;
}
/* Default theme styles of the wrapper */
.remodal-wrapper {
padding: 10px 10px 0;
}
/* Default theme styles of the modal dialog */
.remodal { .remodal {
box-sizing: border-box;
width: 100%; width: 100%;
min-height: 100%; margin-bottom: 10px;
padding-top: 2rem; padding: 35px;
@include box-sizing(border-box);
font-size: 16px;
background: $content-bg;
background-clip: padding-box;
color: $content-fg;
box-shadow: 0 10px 20px rgba(0,0,0,0.5);
@include transform(scale(0.95));
@include transition-property (transform);
@include transition-duration(0.2s);
@include transition-timing-function(linear);
transform: translate3d(0, 0, 0);
color: #2b2e38;
background: #fff;
} }
body.remodal_active .remodal { .remodal.remodal-is-opening,
@include transform(scale(1)); .remodal.remodal-is-closing {
animation-duration: 0.3s;
animation-fill-mode: forwards;
} }
/* Modal dialog vertical align */ .remodal.remodal-is-opening {
.remodal, .remodal-overlay:after { animation-name: remodal-opening-keyframes;
}
.remodal.remodal-is-closing {
animation-name: remodal-closing-keyframes;
}
/* Vertical align of the modal dialog */
.remodal,
.remodal-wrapper:after {
vertical-align: middle; vertical-align: middle;
} }
@@ -115,42 +160,174 @@ body.remodal_active .remodal {
.remodal-close { .remodal-close {
position: absolute; position: absolute;
top: 10px; top: 0;
right: 10px; left: 0;
color: $content-fg;
text-decoration: none;
text-align: center;
@include transition(background 0.2s linear);
}
.remodal-close:after {
display: block; display: block;
font-family: FontAwesome; overflow: visible;
content: "\f00d";
width: 35px;
height: 35px;
margin: 0;
padding: 0;
font-size: 28px;
line-height: 28px;
cursor: pointer; cursor: pointer;
transition: color 0.2s;
text-decoration: none; text-decoration: none;
color: #95979c;
border: 0;
outline: 0;
background: transparent;
} }
.remodal-close:hover, .remodal-close:active { .remodal-close:hover,
color: darken($content-fg, 20%); .remodal-close:focus {
color: #2b2e38;
} }
.remodal-close:before {
font-family: Arial, "Helvetica CY", "Nimbus Sans L", sans-serif !important;
font-size: 25px;
line-height: 35px;
position: absolute;
top: 0;
left: 0;
display: block;
width: 35px;
content: "\00d7";
text-align: center;
}
/* Dialog buttons */
/*.remodal-confirm,
.remodal-cancel {
font: inherit;
display: inline-block;
overflow: visible;
min-width: 110px;
margin: 0;
padding: 12px 0;
cursor: pointer;
transition: background 0.2s;
text-align: center;
vertical-align: middle;
text-decoration: none;
border: 0;
outline: 0;
}
.remodal-confirm {
color: #fff;
background: #81c784;
}
.remodal-confirm:hover,
.remodal-confirm:focus {
background: #66bb6a;
}
.remodal-cancel {
color: #fff;
background: #e57373;
}
.remodal-cancel:hover,
.remodal-cancel:focus {
background: #ef5350;
}
!* Remove inner padding and border in Firefox 4+ for the button tag. *!
.remodal-confirm::-moz-focus-inner,
.remodal-cancel::-moz-focus-inner,
.remodal-close::-moz-focus-inner {
padding: 0;
border: 0;
}*/
/* Keyframes
========================================================================== */
@keyframes remodal-opening-keyframes {
from {
transform: scale(1.05);
opacity: 0;
}
to {
transform: none;
opacity: 1;
}
}
@keyframes remodal-closing-keyframes {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0.95);
opacity: 0;
}
}
@keyframes remodal-overlay-opening-keyframes {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes remodal-overlay-closing-keyframes {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/* Media queries /* Media queries
========================================================================== */ ========================================================================== */
@media only screen and (min-width: 40.063em) /* min-width 641px */ { @media only screen and (min-width: 641px) {
.remodal { .remodal {
max-width: 700px; max-width: 700px;
margin: 20px auto;
min-height: 0;
border-radius: 6px;
} }
} }
/* IE8
========================================================================== */
.lt-ie9 .remodal-overlay {
background: #2b2e38;
}
.lt-ie9 .remodal {
width: 700px;
}
/********* GRAV CUSTOM ********/
.remodal {
padding: 35px 0 0;
text-align: left;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
border-radius: 3px;
}

View File

@@ -15,8 +15,8 @@
{% endblock %} {% endblock %}
{% block javascripts %} {% block javascripts %}
{% do assets.addJs(theme_url~'/js/codemirror-compressed.js') %} {#{% do assets.addJs(theme_url~'/js/codemirror-compressed.js') %}
{% do assets.addJs(theme_url~'/js/mdeditor.js') %} {% do assets.addJs(theme_url~'/js/mdeditor.js') %}#}
{{ parent() }} {{ parent() }}
{% endblock %} {% endblock %}
@@ -30,7 +30,7 @@
{% block content_top %} {% block content_top %}
<div class="alert notice">{{ "PLUGIN_ADMIN.SAVE_LOCATION"|tu }}: <b>{{ data.file.filename|replace({(base_path):''}) }}</b></div> <div class="alert notice">{{ "PLUGIN_ADMIN.SAVE_LOCATION"|tu }}: <b>{{ data.file.filename|replace({(base_path):''}) }}</b></div>
<ul class="tab-bar"> <ul class="tab-bar">
<li {% if config_slug == 'system' %}class="active"{% endif %}> <li {% if config_slug == 'system' %}class="active"{% endif %}>
{% if config_slug == 'system' %}<span>{% else %}<a href="{{ base_url_relative }}/config/system">{% endif %} {% if config_slug == 'system' %}<span>{% else %}<a href="{{ base_url_relative }}/config/system">{% endif %}
{{ "PLUGIN_ADMIN.SYSTEM"|tu }} {{ "PLUGIN_ADMIN.SYSTEM"|tu }}
@@ -41,7 +41,7 @@
{{ "PLUGIN_ADMIN.SITE"|tu }} {{ "PLUGIN_ADMIN.SITE"|tu }}
{% if config_slug == 'site' %}</span>{% else %}</a>{% endif %} {% if config_slug == 'site' %}</span>{% else %}</a>{% endif %}
</li> </li>
{% for configuration in admin.configurations %} {% for configuration in admin.configurations %}
{% set current_blueprints = admin.data('config/' ~ configuration).blueprints.toArray() %} {% set current_blueprints = admin.data('config/' ~ configuration).blueprints.toArray() %}
{% if configuration != 'system' and configuration != 'site' and not current_blueprints.form.hidden and current_blueprints.form.fields is not empty %} {% if configuration != 'system' and configuration != 'site' and not current_blueprints.form.hidden and current_blueprints.form.fields is not empty %}
@@ -69,5 +69,19 @@
{% else %} {% else %}
{% include 'partials/blueprints.html.twig' with { blueprints: data.blueprints, data: data } %} {% include 'partials/blueprints.html.twig' with { blueprints: data.blueprints, data: data } %}
{% endif %} {% endif %}
<div class="remodal" data-remodal-id="changes">
<form>
<h1>{{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_TITLE"|tu }}</h1>
<p class="bigger">
{{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_DESC"|tu }}
</p>
<br>
<div class="button-bar">
<a class="button secondary" data-leave-action="cancel" href="#"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</a>
<a class="button" data-leave-action="continue" href="#"><i class="fa fa-fw fa-check"></i> {{ "PLUGIN_ADMIN.CONTINUE"|tu }}</a>
</div>
</form>
</div>
{% endblock %} {% endblock %}

View File

@@ -6,15 +6,15 @@
<div class="button-bar"> <div class="button-bar">
{% if authorize(['admin.maintenance', 'admin.super']) %} {% if authorize(['admin.maintenance', 'admin.super']) %}
<div class="button-group"> <div class="button-group">
<button data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache', 'admin-form', 'admin-nonce') }}" class="button"><i class="fa fa-trash"></i> {{ "PLUGIN_ADMIN.CLEAR_CACHE"|tu }}</button> <button data-clear-cache-type="" data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache', 'admin-form', 'admin-nonce') }}" class="button"><i class="fa fa-trash"></i> {{ "PLUGIN_ADMIN.CLEAR_CACHE"|tu }}</button>
<button type="button" class="button dropdown-toggle" data-toggle="dropdown"> <button type="button" class="button dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache/cleartype' ~ config.system.param_sep ~ 'all', 'admin-form', 'admin-nonce') }}" href="#">{{ "PLUGIN_ADMIN.CLEAR_CACHE_ALL_CACHE"|tu }}</a></li> <li><a data-clear-cache-type="all" data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache/cleartype' ~ config.system.param_sep ~ 'all', 'admin-form', 'admin-nonce') }}" href="#">{{ "PLUGIN_ADMIN.CLEAR_CACHE_ALL_CACHE"|tu }}</a></li>
<li><a data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache/cleartype' ~ config.system.param_sep ~ 'assets-only', 'admin-form', 'admin-nonce') }}" href="#">{{ "PLUGIN_ADMIN.CLEAR_CACHE_ASSETS_ONLY"|tu }}</a></li> <li><a data-clear-cache-type="assets-only" data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache/cleartype' ~ config.system.param_sep ~ 'assets-only', 'admin-form', 'admin-nonce') }}" href="#">{{ "PLUGIN_ADMIN.CLEAR_CACHE_ASSETS_ONLY"|tu }}</a></li>
<li><a data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache/cleartype' ~ config.system.param_sep ~ 'images-only', 'admin-form', 'admin-nonce') }}" href="#">{{ "PLUGIN_ADMIN.CLEAR_CACHE_IMAGES_ONLY"|tu }}</a></li> <li><a data-clear-cache-type="images-only" data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache/cleartype' ~ config.system.param_sep ~ 'images-only', 'admin-form', 'admin-nonce') }}" href="#">{{ "PLUGIN_ADMIN.CLEAR_CACHE_IMAGES_ONLY"|tu }}</a></li>
<li><a data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache/cleartype' ~ config.system.param_sep ~ 'cache-only', 'admin-form', 'admin-nonce') }}" href="#">{{ "PLUGIN_ADMIN.CLEAR_CACHE_CACHE_ONLY"|tu }}</a></li> <li><a data-clear-cache-type="cache-only" data-clear-cache="{{ uri.addNonce(base_url_relative ~ '/cache.json/task' ~ config.system.param_sep ~ 'clearCache/cleartype' ~ config.system.param_sep ~ 'cache-only', 'admin-form', 'admin-nonce') }}" href="#">{{ "PLUGIN_ADMIN.CLEAR_CACHE_CACHE_ONLY"|tu }}</a></li>
</ul> </ul>
</div> </div>

View File

@@ -1,4 +1,6 @@
{% set originalValue = originalValue is defined ? originalValue : value %} {% set originalValue = originalValue is defined ? originalValue : value %}
{% set toggleableChecked = field.toggleable and (originalValue is not null and originalValue is not empty) %}
{% set isDisabledToggleable = field.toggleable and not toggleableChecked %}
{% set value = (value is null ? field.default : value) %} {% set value = (value is null ? field.default : value) %}
{% set vertical = field.style == 'vertical' %} {% set vertical = field.style == 'vertical' %}
@@ -7,21 +9,21 @@
{% endif %} {% endif %}
{% block field %} {% block field %}
<div class="form-field grid{% if vertical %} vertical{% endif %}"> <div class="form-field grid{% if vertical %} vertical{% endif %}{% if field.toggleable %} form-field-toggleable{% endif %}">
{% block contents %} {% block contents %}
<div class="form-label{% if not vertical %} block size-1-3{% endif %}"> <div class="form-label{% if not vertical %} block size-1-3{% endif %}">
{% if field.toggleable %} {% if field.toggleable %}
<span class="checkboxes toggleable" data-grav-field="toggleable" data-grav-field-name="{{ field.name|fieldName }}"> <span class="checkboxes toggleable" data-grav-field="toggleable" data-grav-field-name="{{ field.name|fieldName }}">
<input type="checkbox" <input type="checkbox"
id="toggleable_{{ field.name }}" id="toggleable_{{ field.name }}"
{% if originalValue is not null and originalValue is not empty %}value="1"{% endif %} {% if toggleableChecked %}value="1"{% endif %}
name="toggleable_{{ (scope ~ field.name)|fieldName }}" name="toggleable_{{ (scope ~ field.name)|fieldName }}"
{% if originalValue is not null and originalValue is not empty %}checked="checked"{% endif %} {% if toggleableChecked %}checked="checked"{% endif %}
> >
<label for="toggleable_{{ field.name }}"></label> <label for="toggleable_{{ field.name }}"></label>
</span> </span>
{% endif %} {% endif %}
<label class="{{ field.toggleable ? 'toggleable' : '' }}"> <label class="{{ field.toggleable ? 'toggleable' : '' }}"{{ field.toggleable ? 'for="toggleable_' ~ field.name ~'"' : ''}}>
{% block label %} {% block label %}
{% if field.help %} {% if field.help %}
<span class="hint--bottom" data-hint="{{ field.help|e|tu }}">{{ field.label|tu }}</span> <span class="hint--bottom" data-hint="{{ field.help|e|tu }}">{{ field.label|tu }}</span>
@@ -35,7 +37,7 @@
<div class="form-data{% if not vertical %} block size-2-3{% endif %}" <div class="form-data{% if not vertical %} block size-2-3{% endif %}"
{% block global_attributes %} {% block global_attributes %}
data-grav-field="{{ field.type }}" data-grav-field="{{ field.type }}"
data-grav-disabled="{{ originalValue is null ? 'true' : 'false' }}" data-grav-disabled="{{ toggleableChecked }}"
data-grav-default="{{ field.default|json_encode()|e('html_attr') }}" data-grav-default="{{ field.default|json_encode()|e('html_attr') }}"
{% endblock %} {% endblock %}
> >
@@ -51,7 +53,7 @@
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %} {% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %} {% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %} {% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
{% if field.disabled %}disabled="disabled"{% endif %} {% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.placeholder %}placeholder="{{ field.placeholder|tu }}"{% endif %} {% if field.placeholder %}placeholder="{{ field.placeholder|tu }}"{% endif %}
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %} {% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %} {% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}

View File

@@ -7,55 +7,62 @@
{{ parent() }} {{ parent() }}
{% endblock %} {% endblock %}
{% macro renderer(key, text, field) %}
<div class="form-row{% if field.value_only %} array-field-value_only{% endif %}"
data-grav-array-type="row">
{% if field.value_only != true %}
{% if key == '0' and text == '' %}
{% set key = '' %}
{% endif %}
<input
data-grav-array-type="key"
type="text" value="{{ key }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_key|e|tu }}" />
{% endif %}
<input
data-grav-array-type="value"
type="text"
name="{{ (field.name|fieldName) ~ '[' ~ key ~ ']' }}"
placeholder="{{ field.placeholder_value|e|tu }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
value={% if text == 'true' %}true{% elseif text == 'false' %}false{% else %}"{{ text|join(', ')|e }}"{% endif %} />
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>
{% endmacro %}
{% block input %} {% block input %}
{% import _self as array_field %}
<div data-grav-array-type="container"{% if field.value_only %} data-grav-array-mode="value_only"{% endif %}> <div data-grav-array-type="container"{% if field.value_only %} data-grav-array-mode="value_only"{% endif %}>
{% if value|length %} {% if value|length %}
{% for key, text in value -%} {% for key, text in value -%}
<div class="form-row{% if field.value_only %} array-field-value_only{% endif %}" data-grav-array-type="row"> {% if text is not iterable %}
{{ array_field.renderer(key, text, field) }}
{% if text is iterable %} {% else %}
<div class="grid" style="display: flex"> {# Backward compatibility for nested arrays (metas) which are not supported anymore #}
<div class="block size-1-4"> {% for subkey, subtext in text -%}
<input data-grav-array-type="keyArray" type="text" value="{{ key }}" placeholder="{{ field.placeholder_key|e|tu }}" style="float: none; width: 90%" /> {{ array_field.renderer(key ~ ':' ~ subkey, subtext, field) }}
</div> {% endfor %}
{% endif %}
<div class="block size-3-4">
{% for subkey, subtext in text -%}
<div class="form-row" data-grav-array-type="subrow">
<input data-grav-array-type="keyArraySubelement" type="text" value="{{ subkey }}" />
<input data-grav-array-type="value" type="text" subkey="{{ subkey }}" name="{{ (field.name|fieldName) ~ '[' ~ key ~ '][' ~ subkey ~ ']' }}" value="{{ subtext|join(', ')|e }}" />
<span data-grav-array-action="remArrayItem" class="fa fa-minus-square"></span>
<span data-grav-array-action="addArrayItem" class="fa fa-plus-square"></span>
</div>
{% endfor %}
<div class="form-row{% if field.value_only %} array-field-value_only{% endif %}" data-grav-array-type="subrow">
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>
</div>
</div>
{% else %}
{% if field.value_only != true %}
{% if key == '0' and text == '' %}
{% set key = '' %}
{% endif %}
<input data-grav-array-type="key" type="text" value="{{ key }}" placeholder="{{ field.placeholder_key|e|tu }}" />
{% endif %}
<input data-grav-array-type="value" type="text" name="{{ (field.name|fieldName) ~ '[' ~ key ~ ']' }}" value={% if text == 'true' %}true{% elseif text == 'false' %}false{% else %}"{{ text|join(', ')|e }}"{% endif %} placeholder="{{ field.placeholder_value|e|tu }}" />
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
{% endif %}
</div>
{% endfor %} {% endfor %}
{%- else -%} {%- else -%}
{# Empty value, mock the entry field#}
<div class="form-row" data-grav-array-type="row"> <div class="form-row" data-grav-array-type="row">
<input data-grav-array-type="key" type="text" placeholder="{{ field.placeholder_key|e|tu }}" /> <input
<input data-grav-array-type="value" type="text" placeholder="{{ field.placeholder_value|e|tu }}" /> data-grav-array-type="key"
type="text"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_key|e|tu }}" />
<input
data-grav-array-type="value"
type="text"
name="{{ field.name|fieldName }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_value|e|tu }}" />
<span data-grav-array-action="rem" class="fa fa-minus"></span> <span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span> <span data-grav-array-action="add" class="fa fa-plus"></span>
</div> </div>

View File

@@ -1 +1,8 @@
<input data-grav-field="hidden" data-grav-disabled="false" type="hidden" class="input" name="{{ (scope ~ field.name)|fieldName }}" value="{{ blueprints.name }}" /> <input
data-grav-field="hidden"
data-grav-disabled="false"
type="hidden"
class="input"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
name="{{ (scope ~ field.name)|fieldName }}"
value="{{ blueprints.name }}" />

View File

@@ -13,6 +13,7 @@
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %} {% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %} {% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %} {% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.form %}form="{{ field.form }}"{% endif %} {% if field.form %}form="{{ field.form }}"{% endif %}
> >
{% for key, text in field.options %} {% for key, text in field.options %}

View File

@@ -27,9 +27,10 @@
{% block input_attributes %} {% block input_attributes %}
type="file" type="file"
{% if files.accept %}accept="{{ files.accept|join(',') }}"{% endif %} {% if files.accept %}accept="{{ files.accept|join(',') }}"{% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{{ parent() }} {{ parent() }}
{% endblock %} {% endblock %}
/> />
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@@ -13,7 +13,10 @@
</div> </div>
<div class="form-data"> <div class="form-data">
<div class="form-frontmatter-wrapper {{ field.size }}"> <div class="form-frontmatter-wrapper {{ field.size }}">
<textarea name="{{ (scope ~ field.name)|fieldName }}" id="frontmatter">{{ value|join("\n") }}</textarea> <textarea
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
name="{{ (scope ~ field.name)|fieldName }}"
id="frontmatter">{{ value|join("\n") }}</textarea>
<div class="dragbar"></div> <div class="dragbar"></div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,7 @@
{% set value = (value is null ? field.default : value) %} {% set value = (value is null ? field.default : value) %}
{% set name = scope ~ field.name %} {% set name = scope ~ field.name %}
{% set array = field.array is defined ? field.array : true %}
{% set lastKey = null %}
<div class="form-field grid pure-g"> <div class="form-field grid pure-g BLABLA">
<div class="form-label block size-1-4 pure-u-1-4"> <div class="form-label block size-1-4 pure-u-1-4">
<label> <label>
{% if field.help %} {% if field.help %}
@@ -19,12 +17,6 @@
<ul data-collection-holder="{{ name }}"> <ul data-collection-holder="{{ name }}">
{% if field.fields %} {% if field.fields %}
{% for key, val in value %} {% for key, val in value %}
{% if array and (key matches '/^\\d+$/') == 0 %}
{% set array = false %}
{% set lastKey = -1 %}
{% elseif array %}
{% set lastKey = key %}
{% endif %}
{% set itemName = name ~ '.' ~ key %} {% set itemName = name ~ '.' ~ key %}
<li data-collection-item="{{ itemName }}" data-collection-key="{{ key }}"> <li data-collection-item="{{ itemName }}" data-collection-key="{{ key }}">
{% for childName, child in field.fields %} {% for childName, child in field.fields %}
@@ -63,48 +55,47 @@
{% endif %} {% endif %}
</ul> </ul>
<div class="collection-actions"> <div class="collection-actions">
{% set lastKey = lastKey + 1 %} <button class="button" type="button" data-action="add"><i class="fa fa-plus"></i> Add item</button>
<button class="button" type="button" data-action="add" data-key-index="{{ lastKey }}"><i class="fa fa-plus"></i> Add item</button>
</div> </div>
<script type="text/html" data-collection-template="new"> <div style="display: none;" data-collection-template="new" data-collection-template-html="{%- filter replace({' ': ' ', '\n': ' '})|e('html_attr') -%}
<li data-collection-item="{{ name ~ '.*' }}"> <li data-collection-item="{{ name ~ '.*' }}">
{% if field.fields %} {%- if field.fields -%}
{% set itemName = name ~ '.*' %} {%- set itemName = name ~ '.*' -%}
{% for childName, child in field.fields %} {%- for childName, child in field.fields -%}
{% if childName starts with '.' %} {%- if childName starts with '.' -%}
{% set childKey = childName|trim('.') %} {%- set childKey = childName|trim('.') -%}
{% set childName = itemName ~ childName %} {%- set childName = itemName ~ childName -%}
{% else %} {%- else %}
{% set childKey = childName %} {%- set childKey = childName -%}
{% set childName = childName|replace({'*': key}) %} {%- set childName = childName|replace({'*': key}) -%}
{% endif %} {%- endif %}
{% set child = child|merge({ name: childName }) %} {%- set child = child|merge({ name: childName }) -%}
{% if child.type == 'key' %} {%- if child.type == 'key' -%}
{% {%-
include 'forms/fields/text/text.html.twig' include 'forms/fields/text/text.html.twig'
with { field: child, value: null } with { field: child, value: null }
%} -%}
{% elseif child.type %} {%- elseif child.type -%}
{% {%-
include [ include [
"forms/fields/#{child.type}/#{child.type}.html.twig", "forms/fields/#{child.type}/#{child.type}.html.twig",
'forms/fields/text/text.html.twig' 'forms/fields/text/text.html.twig'
] with { field: child, value: null } ] with { field: child, value: null }
%} -%}
{% endif %} {%- endif -%}
{% endfor %} {%- endfor %}
<div class="item-actions"> <div class="item-actions">
<i class="fa fa-bars"></i> <i class="fa fa-bars"></i>
<br /> <br />
<i class="fa fa-trash-o" data-action="delete"></i> <i class="fa fa-trash-o" data-action="delete"></i>
</div> </div>
{% endif %} {%- endif -%}
</li> </li>
</script> {%- endfilter -%}"></div>
<div style="display: none;" data-collection-config="{{ name }}" data-collection-array="{{ array ? 'true' : 'false' }}"></div> <div style="display: none;" data-collection-config="{{ name }}"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -9,7 +9,12 @@
{% block field %} {% block field %}
<div class="form-field"> <div class="form-field">
<div class="form-data form-markdown-wrapper cm-s-paper"> <div class="form-data form-markdown-wrapper cm-s-paper">
<textarea data-grav-mdeditor="{{ { 'markdown': true }|json_encode|e('html_attr') }}" name="{{ (scope ~ field.name)|fieldName }}" {% if field.showPreview %}data-grav-preview-enabled="true"{% endif %} data-grav-urlpreview="{{ base_url }}/media/{{ admin.route|trim('/') }}.json">{{ value|join("\n")|e('html') }}</textarea> <textarea
data-grav-mdeditor="{{ { 'markdown': true }|json_encode|e('html_attr') }}"
name="{{ (scope ~ field.name)|fieldName }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.showPreview %}data-grav-preview-enabled="true"{% endif %}
data-grav-urlpreview="{{ base_url }}/media/{{ admin.route|trim('/') }}.json">{{ value|join("\n")|e('html') }}</textarea>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -15,7 +15,12 @@
</div> </div>
<div class="form-data block size-2-3 pure-u-2-3"> <div class="form-data block size-2-3 pure-u-2-3">
<div class="form-order-wrapper {{ field.size }}"> <div class="form-order-wrapper {{ field.size }}">
<input type="hidden" data-order name="{{ (scope ~ field.name)|fieldName }}" value="{{ value }}" /> <input
type="hidden"
data-order
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
name="{{ (scope ~ field.name)|fieldName }}"
value="{{ value }}" />
{% if data.parent.header.content.items %} {% if data.parent.header.content.items %}
<span class="note">Parent setting order, ordering disabled</span> <span class="note">Parent setting order, ordering disabled</span>
{% elseif not data.visible %} {% elseif not data.visible %}
@@ -33,17 +38,4 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<script>
jQuery(function(){
var el = jQuery('#ordering');
new Sortable(el[0], {
filter: ".ignore",
onUpdate: function(evt){
var index = el.children().index(jQuery(evt.item)) + 1;
jQuery('[data-order]').val(index);
}
// draggable: ".drag-handle"
});
});
</script>
</div> </div>

View File

@@ -4,158 +4,8 @@
<div class="form-field"> <div class="form-field">
<div class="form-data form-uploads-wrapper"> <div class="form-data form-uploads-wrapper">
<h3>{{ field.label|tu }}</h3> <h3>{{ field.label|tu }}</h3>
<div id="gravDropzone" class="dropzone"></div> <div id="grav-dropzone" class="dropzone"></div>
<span>{{ value|join("\n") }}</span> <span>{{ value|join("\n") }}</span>
<script>
$(function(){
var URI = $('[data-media-url]').data('media-url'), thisDropzone,
modalError = function(args){
if (args.data.status == 'error' || args.data.status == 'unauthorized'){
if (args.mode == 'addBack'){
// let's add back the file
if (args.file instanceof File) this.addFile(args.file);
else {
this.files.push(args.file);
this.options.addedfile.call(this, args.file);
this.options.thumbnail.call(this, args.file, args.file.extras.url);
}
} else if (args.mode == 'removeFile') {
args.file.rejected = true;
this.removeFile(args.file);
}
// fire up the modal
var modalContainer = $('[data-remodal-id=generic]');
modalContainer.find('.error-content').html(args.msg);
$.remodal.lookup[modalContainer.data('remodal')].open();
}
};
Dropzone.autoDiscover = false;
Dropzone.confirm = function(question, accepted, rejected) {
var modalContainer = $('[data-remodal-id=delete-media]'),
acceptHandler = function () {
if (accepted) {
accepted();
}
$(document).off('confirm', '[data-remodal-id=delete-media]', acceptHandler);
$(document).off('cancel', '[data-remodal-id=delete-media]', rejectHandler);
},
rejectHandler = function () {
if (rejected) {
rejected();
}
$(document).off('confirm', '[data-remodal-id=delete-media]', acceptHandler);
$(document).off('cancel', '[data-remodal-id=delete-media]', rejectHandler);
};
$.remodal.lookup[modalContainer.data('remodal')].open();
$(document).on('confirm', '[data-remodal-id=delete-media]', acceptHandler);
$(document).on('cancel', '[data-remodal-id=delete-media]', rejectHandler);
};
Dropzone.options.gravDropzone = {
addRemoveLinks: false,
dictRemoveFileConfirmation: '[placeholder]',
acceptedFiles: $('[data-media-types]').data('media-types'),
previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n <div class=\"dz-details\">\n " +
"<div class=\"dz-filename\"><span data-dz-name></span></div>\n " +
"<div class=\"dz-size\" data-dz-size></div>\n <img data-dz-thumbnail />\n </div>\n " +
"<div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress></span></div>\n "+
"<div class=\"dz-success-mark\"><span>✔</span></div>\n <div class=\"dz-error-mark\"><span>✘</span></div>\n " +
"<div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n" +
"<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>Delete</a>\n" +
"<a class=\"dz-insert\" href=\"javascript:undefined;\" data-dz-insert>Insert</a>\n</div>",
init: function() {
thisDropzone = this;
$.get(URI + '/task{{ config.system.param_sep }}listmedia/admin-nonce{{ config.system.param_sep }}' + GravAdmin.config.admin_nonce, function(data) {
$.proxy(modalError, this, {
data: data,
msg: '<p>An error occurred while trying to list files</p><pre>'+data.message+'</pre>'
})();
if (data.results) {
$.each(data.results, function(filename, data){
var mockFile = { name: filename, size: data.size, accepted: true, extras: data };
thisDropzone.files.push(mockFile);
thisDropzone.options.addedfile.call(thisDropzone, mockFile);
if (filename.toLowerCase().match(/\.(jpg|jpeg|png|gif)$/)) {
thisDropzone.options.thumbnail.call(thisDropzone, mockFile, data.url);
}
});
}
$('.dz-preview').prop('draggable', 'true');
});
this.on("complete", function(file) {
if (file.accepted) {
$('.dz-preview').prop('draggable', 'true');
return;
}
var data = {status: 'error', message: 'Unsupported file type: ' + file.name.match(/\..+/).join('')};
$.proxy(modalError, this, {
file: file,
data: data,
mode: 'removeFile',
msg: '<p>An error occurred while trying to add the file <strong>'+file.name+'</strong></p><pre>'+data.message+'</pre>'
})();
});
this.on('success', function(file, response){
thisDropzone = this;
$.proxy(modalError, this, {
file: file,
data: response,
mode: 'removeFile',
msg: '<p>An error occurred while trying to upload the file <strong>'+file.name+'</strong></p><pre>'+response.message+'</pre>'
})();
});
this.on('removedfile', function(file) {
if (!file.accepted || file.rejected) return;
thisDropzone = this;
$.post(URI + '/task{{ config.system.param_sep }}delmedia', {filename: file.name, 'admin-nonce': GravAdmin.config.admin_nonce}, function(data){
$.proxy(modalError, thisDropzone, {
file: file,
data: data,
mode: 'addBack',
msg: '<p>An error occurred while trying to remove the file <strong>'+file.name+'</strong></p><pre>'+data.message+'</pre>'
})();
});
});
this.on('sending', function(file, xhr, formData){
formData.append('admin-nonce', GravAdmin.config.admin_nonce);
});
}
};
var dropzone = new Dropzone("#gravDropzone", { url: URI + '/task{{ config.system.param_sep }}addmedia', createImageThumbnails: { thumbnailWidth: 150} });
$("#gravDropzone").delegate('.dz-preview', 'dragstart', function(e){
var uri = encodeURI($(this).find('.dz-filename').text());
uri = uri.replace(/\(/g, '%28');
uri = uri.replace(/\)/g, '%29');
var shortcode = '![](' + uri + ')';
if (!uri.match(/\.(jpg|jpeg|png|gif)$/)) {
shortcode = '[' + decodeURI(uri) + '](' + uri + ')';
}
dropzone.disable();
$(this).addClass('hide-backface');
e.originalEvent.dataTransfer.effectAllowed = 'copy';
e.originalEvent.dataTransfer.setData('text', shortcode);
});
$("#gravDropzone").delegate('.dz-preview', 'dragend', function(e){
dropzone.enable();
$(this).removeClass('hide-backface');
});
});
</script>
</div> </div>
</div> </div>
{% else %} {% else %}

View File

@@ -24,7 +24,7 @@
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %} {% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %} {% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %} {% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
{% if field.disabled or files is empty %}disabled="disabled"{% endif %} {% if field.disabled or files is empty or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %} {% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %} {% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %} {% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}

View File

@@ -7,7 +7,7 @@
<option value="{{ key|e('html_attr') }}">{{ value|t }}</option> <option value="{{ key|e('html_attr') }}">{{ value|t }}</option>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if field.show_root and depth == 0 %} {% if field.show_root and depth == 0 %}
<option value="/">/ (Root)</option> <option value="/">/ (Root)</option>
{% set depth = depth +1 %} {% set depth = depth +1 %}
@@ -43,6 +43,7 @@
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %} {% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %} {% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %} {% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
> >
{{ _self.options(field,pages,value, 0) }} {{ _self.options(field,pages,value, 0) }}
</select> </select>

View File

@@ -9,7 +9,7 @@
{% endif %} {% endif %}
{% if field.fields %} {% if field.fields %}
{% for tab in field.fields %}<input type="radio" name="tab" id="tab{{ loop.index }}" class="tab-head" {{ active == loop.index ? 'checked="checked"' : '' }}/><label for="tab{{ loop.index }}">{% if grav.twig.twig.filters['tu'] is defined %}{{ tab.title|tu }}{% else %}{{ tab.title|t }}{% endif %}</label>{% endfor %} {% for tab in field.fields %}<input type="radio" name="tab" id="tab{{ loop.index }}" class="tab-head no-form" {{ active == loop.index ? 'checked="checked"' : '' }}/><label for="tab{{ loop.index }}">{% if grav.twig.twig.filters['tu'] is defined %}{{ tab.title|tu }}{% else %}{{ tab.title|t }}{% endif %}</label>{% endfor %}
<div class="tab-body-wrapper"> <div class="tab-body-wrapper">
{% for field in field.fields %} {% for field in field.fields %}
{% set value = data.value(field.name) %} {% set value = data.value(field.name) %}

View File

@@ -20,7 +20,7 @@
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %} {% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %} {% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %} {% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
> {% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}>
{% for key, text in options %} {% for key, text in options %}
<option {% if key == value or text in value %}selected="selected"{% endif %} value="{{ field.multiple ? text : key }}">{{ text }}</option> <option {% if key == value or text in value %}selected="selected"{% endif %} value="{{ field.multiple ? text : key }}">{{ text }}</option>
{% endfor %} {% endfor %}

View File

@@ -39,6 +39,7 @@
{% if field.highlight is defined %} {% if field.highlight is defined %}
class="{{ field.highlight == '' ~ key ? 'highlight' : '' }}" class="{{ field.highlight == '' ~ key ? 'highlight' : '' }}"
{% endif %} {% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.toggleable %} {% if field.toggleable %}
{% if '' ~ key == '' ~ value %}checked="checked" {% endif %} {% if '' ~ key == '' ~ value %}checked="checked" {% endif %}
{% if value is defined and (key == 1 or key == '1') %}checked="checked" {% endif %} {% if value is defined and (key == 1 or key == '1') %}checked="checked" {% endif %}

View File

@@ -40,14 +40,14 @@
{% endblock %} {% endblock %}
{% block javascripts %} {% block javascripts %}
{% do assets.addJs(theme_url~'/js/pages-all.js') %} {#{% do assets.addJs(theme_url~'/js/pages-all.js') %}
{% do assets.addJs(theme_url~'/js/speakingurl.min.js') %} {% do assets.addJs(theme_url~'/js/speakingurl.min.js') %}
{% do assets.addJs(theme_url~'/js/slugify.min.js') %} {% do assets.addJs(theme_url~'/js/slugify.min.js') %}
{% if mode == 'edit' %} {% if mode == 'edit' %}
{% do assets.addJs(theme_url~'/js/codemirror-compressed.js') %} {% do assets.addJs(theme_url~'/js/codemirror-compressed.js') %}
{% do assets.addJs(theme_url~'/js/mdeditor.js') %} {% do assets.addJs(theme_url~'/js/mdeditor.js') %}
{% do assets.addJs(theme_url~'/js/dropzone.min.js') %} {% do assets.addJs(theme_url~'/js/dropzone.min.js') %}
{% endif %} {% endif %}#}
{{ parent() }} {{ parent() }}
{% endblock %} {% endblock %}
@@ -260,7 +260,7 @@
{% else %} {% else %}
<form id="page-filtering"> <form id="page-filtering">
<div class="page-filters"> <div class="page-filters">
<input type="text" data-template-types="{{ admin.types|merge(admin.modularTypes)|json_encode|e('html_attr') }}" data-template-access-levels="{{ admin.accessLevels|json_encode|e('html_attr') }}" placeholder="{{ "PLUGIN_ADMIN.ADD_FILTERS"|tu }}" class="page-filter" name="page-filter" /> <input type="text" data-filter-labels="{{ [{'id': 'mode', 'name': 'PLUGIN_ADMIN.PAGE_MODES'|tu}, {'id': 'type', 'name': 'PLUGIN_ADMIN.PAGE_TYPES'|tu}, {'id': 'access', 'name': 'PLUGIN_ADMIN.PAGE_ACCESS_LEVELS'|tu}] |json_encode|e('html_attr')}}" data-filter-types="{{ admin.types|merge(admin.modularTypes)|json_encode|e('html_attr') }}" data-filter-access-levels="{{ admin.accessLevels|json_encode|e('html_attr') }}" placeholder="{{ "PLUGIN_ADMIN.ADD_FILTERS"|tu }}" class="page-filter" name="page-filter" />
</div> </div>
<div class="page-search"> <div class="page-search">
<input type="text" placeholder="{{ "PLUGIN_ADMIN.SEARCH_PAGES"|tu }}" name="page-search" /> <input type="text" placeholder="{{ "PLUGIN_ADMIN.SEARCH_PAGES"|tu }}" name="page-search" />
@@ -314,7 +314,7 @@
</p> </p>
<br> <br>
<div class="button-bar"> <div class="button-bar">
<a class="button secondary remodal-cancel" href="#"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</a> <button data-remodal-action="cancel" class="button secondary remodal-cancel"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</button>
<a class="button" data-delete-action href="#"><i class="fa fa-fw fa-check"></i> {{ "PLUGIN_ADMIN.CONTINUE"|tu }}</a> <a class="button" data-delete-action href="#"><i class="fa fa-fw fa-check"></i> {{ "PLUGIN_ADMIN.CONTINUE"|tu }}</a>
</div> </div>
</form> </form>
@@ -343,8 +343,8 @@
</p> </p>
<br> <br>
<div class="button-bar"> <div class="button-bar">
<a class="button secondary remodal-cancel" href="#"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</a> <button data-remodal-action="cancel" class="button secondary remodal-cancel"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</button>
<a class="button remodal-confirm" href="#"><i class="fa fa-fw fa-check"></i> {{ "PLUGIN_ADMIN.CONTINUE"|tu }}</a> <button data-remodal-action="confirm" class="button remodal-confirm"><i class="fa fa-fw fa-check"></i> {{ "PLUGIN_ADMIN.CONTINUE"|tu }}</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -37,7 +37,9 @@
{% include 'partials/javascript-config.html.twig' %} {% include 'partials/javascript-config.html.twig' %}
{% block javascripts %} {% block javascripts %}
{% do assets.add('jquery',101) %} {% do assets.add('jquery',101) %}
{% do assets.addJs(theme_url~'/js/modernizr.custom.71422.js') %} {% do assets.addJs(theme_url~'/js/vendor.js', { 'loading':'defer' }) %}
{% do assets.addJs(theme_url~'/js/admin.js' , { 'loading':'defer' }) %}
{#{% do assets.addJs(theme_url~'/js/modernizr.custom.71422.js') %}
{% do assets.addJs(theme_url~'/js/chartist.min.js') %} {% do assets.addJs(theme_url~'/js/chartist.min.js') %}
{% do assets.addJs(theme_url~'/js/selectize.min.js') %} {% do assets.addJs(theme_url~'/js/selectize.min.js') %}
{% do assets.addJS(theme_url~'/js/sortable.min.js') %} {% do assets.addJS(theme_url~'/js/sortable.min.js') %}
@@ -58,11 +60,11 @@
{% do assets.addJs(theme_url~'/js/forms/fields/selectize.js') %} {% do assets.addJs(theme_url~'/js/forms/fields/selectize.js') %}
{% do assets.addJs(theme_url~'/js/forms/fields/checkboxes.js') %} {% do assets.addJs(theme_url~'/js/forms/fields/checkboxes.js') %}
{% do assets.addJs(theme_url~'/js/forms/fields/toggle.js') %} {% do assets.addJs(theme_url~'/js/forms/fields/toggle.js') %}
{% do assets.addJs(theme_url~'/js/forms.js') %} {% do assets.addJs(theme_url~'/js/forms.js') %}#}
{% if browser.getBrowser == 'msie' or browser.getBrowser == 'edge' %} {#{% if browser.getBrowser == 'msie' or browser.getBrowser == 'edge' %}
{% do assets.addJs(theme_url~'/js/form-attr.polyfill.js') %} {% do assets.addJs(theme_url~'/js/form-attr.polyfill.js') %}
{% endif %} {% endif %}#}
{{ assets.js() }} {{ assets.js() }}
{% endblock %} {% endblock %}

View File

@@ -17,7 +17,7 @@
</div> </div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{{ dump(data.extra) }}
{% if data.extra %} {% if data.extra %}
{% for name, value in data.extra %} {% for name, value in data.extra %}
{% if name not in ['_json','task','admin-nonce'] %} {% if name not in ['_json','task','admin-nonce'] %}

View File

@@ -5,40 +5,25 @@
<div class="tertiary-accent default-box-shadow"> <div class="tertiary-accent default-box-shadow">
<h1>{{ "PLUGIN_ADMIN.MAINTENANCE"|tu }}</h1> <h1>{{ "PLUGIN_ADMIN.MAINTENANCE"|tu }}</h1>
<div class="admin-update-charts"> <div class="admin-update-charts">
<div class="updates-chart"> <div class="updates-chart" data-chart-name="updates" data-chart-type="pie" data-chart-data="{{ {'series': [100, 0]}|json_encode|e('html_attr') }}">
<div class="chart-wrapper"> <div class="chart-wrapper">
<div class="ct-chart"></div> <div class="ct-chart"></div>
<span class="numeric hidden"><span>-</span><em>{{ "PLUGIN_ADMIN.UPDATED"|tu|lower }}</em></span> <span class="numeric hidden"><span>-</span><em>{{ "PLUGIN_ADMIN.UPDATED"|tu|lower }}</em></span>
</div> </div>
<p class="js__updates-available-description">&nbsp;</p> <p class="js__updates-available-description">&nbsp;</p>
</div> </div>
<div class="backups-chart"> <div class="backups-chart" data-chart-name="backups" data-chart-type="pie" data-chart-data="{{ {'series': [backup.chart_fill, backup.chart_empty]}|json_encode|e('html_attr') }}">
<div class="chart-wrapper"> <div class="chart-wrapper">
<div class="ct-chart"></div> <div class="ct-chart"></div>
<script>
var data = {
series: [{{ backup.chart_fill }}, {{ backup.chart_empty }}]
};
var options = {
donut: true,
donutWidth: 10,
startAngle: 0,
total: 100,
showLabel: false,
height: 150,
chartPadding: !isFirefox ? 5 : 10
};
Chartist.Pie('.backups-chart .ct-chart', data, options);
</script>
<span class="numeric">{{ backup.days }}<em>{{ "PLUGIN_ADMIN.DAYS"|tu|lower }}</em></span> <span class="numeric">{{ backup.days }}<em>{{ "PLUGIN_ADMIN.DAYS"|tu|lower }}</em></span>
</div> </div>
<p>{{ "PLUGIN_ADMIN.LAST_BACKUP"|tu }}</p> <p>{{ "PLUGIN_ADMIN.LAST_BACKUP"|tu }}</p>
</div> </div>
</div> </div>
<div class="flush-bottom button-bar"> <div class="flush-bottom button-bar">
<button data-maintenance-update="{{ uri.addNonce(base_url_relative ~ '/update.json/task' ~ config.system.param_sep ~ 'update', 'admin-form', 'admin-nonce') }}" class="button" style="display: none"><i class="fa fa-cloud-download"></i> {{ "PLUGIN_ADMIN.UPDATE"|tu }}</button> <button data-maintenance-update="{{ uri.addNonce(base_url_relative ~ '/update.json/task' ~ config.system.param_sep ~ 'update', 'admin-form', 'admin-nonce') }}" class="button"><i class="fa fa-cloud-download"></i> {{ "PLUGIN_ADMIN.UPDATE"|tu }}</button>
<button data-ajax="{{ uri.addNonce(base_url_relative ~ '/backup.json/task' ~ config.system.param_sep ~ 'backup', 'admin-form', 'admin-nonce') }}" class="button"><i class="fa fa-database"></i> {{ "PLUGIN_ADMIN.BACKUP"|tu }}</button> <button data-ajax="{{ uri.addNonce(base_url_relative ~ '/backup.json/task' ~ config.system.param_sep ~ 'backup', 'admin-form', 'admin-nonce') }}" class="button"><i class="fa fa-database"></i> {{ "PLUGIN_ADMIN.BACKUP"|tu }}</button>
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View File

@@ -1,39 +1,8 @@
{% if authorize(['admin.statistics', 'admin.super']) %} {% if authorize(['admin.statistics', 'admin.super']) %}
<div id="popularity" class="dashboard-item dashboard-right"> <div id="popularity" class="dashboard-item dashboard-right" data-chart-name="popularity" data-chart-type="bar" data-chart-data="{{ {'series': [popularity.getDailyChartData['data']], 'labels': popularity.getDailyChartData['labels']}|json_encode|e('html_attr') }}">
<div class="secondary-accent default-box-shadow"> <div class="secondary-accent default-box-shadow">
<h1>{{ "PLUGIN_ADMIN.STATISTICS"|tu }}</h1> <h1>{{ "PLUGIN_ADMIN.STATISTICS"|tu }}</h1>
<div class="ct-chart"></div> <div class="ct-chart"></div>
<script>
var data = {
labels: {{ popularity.getDailyChartData['labels'] }},
series: [
{{ popularity.getDailyChartData['data'] }}
]
};
var options = {
height: 164,
chartPadding: !isFirefox ? 5 : 25,
axisX: {
showGrid: false,
labelOffset: {
x: 0,
y: 5
}
},
axisY: {
offset: 15,
showLabel: true,
showGrid: true,
labelOffset: {
x: 5,
y: 5
},
scaleMinSpace: 20
}
};
Chartist.Bar('#popularity .ct-chart', data, options);
</script>
<div class="flush-bottom button-bar stats-bar"> <div class="flush-bottom button-bar stats-bar">
<span class="stat"> <span class="stat">
<b>{{ popularity.getDailyTotal }}</b> <b>{{ popularity.getDailyTotal }}</b>
@@ -50,4 +19,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View File

@@ -11,7 +11,7 @@
{% endfor %} {% endfor %}
<div class="button-bar"> <div class="button-bar">
<a class="button secondary remodal-cancel" href="#"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</a> <button data-remodal-action="cancel" class="button secondary remodal-cancel"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</button>
<button class="button primary" name="task" value="save" form="blueprints">{{ "PLUGIN_ADMIN.CONTINUE"|tu }}</button> <button class="button primary" name="task" value="save" form="blueprints">{{ "PLUGIN_ADMIN.CONTINUE"|tu }}</button>
</div> </div>
</form> </form>

View File

@@ -42,3 +42,17 @@
<a class="button" href="{{ uri.addNonce(base_url_relative ~ '/plugins/' ~ plugin.slug ~ '/task' ~ config.system.param_sep ~ 'install', 'admin-form', 'admin-nonce') }}"><i class="fa fa-fw fa-plus"></i>{{ "PLUGIN_ADMIN.INSTALL_PLUGIN"|tu }}</a> <a class="button" href="{{ uri.addNonce(base_url_relative ~ '/plugins/' ~ plugin.slug ~ '/task' ~ config.system.param_sep ~ 'install', 'admin-form', 'admin-nonce') }}"><i class="fa fa-fw fa-plus"></i>{{ "PLUGIN_ADMIN.INSTALL_PLUGIN"|tu }}</a>
</div> </div>
{% endif %} {% endif %}
<div class="remodal" data-remodal-id="changes">
<form>
<h1>{{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_TITLE"|tu }}</h1>
<p class="bigger">
{{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_DESC"|tu }}
</p>
<br>
<div class="button-bar">
<a class="button secondary" data-leave-action="cancel" href="#"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</a>
<a class="button" data-leave-action="continue" href="#"><i class="fa fa-fw fa-check"></i> {{ "PLUGIN_ADMIN.CONTINUE"|tu }}</a>
</div>
</form>
</div>

View File

@@ -111,6 +111,16 @@
</div> </div>
{% endif %} {% endif %}
<div class="remodal" data-remodal-id="changes">
<form>
<h1>{{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_TITLE"|tu }}</h1>
<p class="bigger">
{{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_DESC"|tu }}
</p>
<br>
<div class="button-bar">
<a class="button secondary" data-leave-action="cancel" href="#"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</a>
<a class="button" data-leave-action="continue" href="#"><i class="fa fa-fw fa-check"></i> {{ "PLUGIN_ADMIN.CONTINUE"|tu }}</a>
</div>
</form>
</div>

View File

@@ -58,7 +58,7 @@
</p> </p>
<br> <br>
<div class="button-bar"> <div class="button-bar">
<a class="button secondary remodal-cancel" href="#"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</a> <button data-remodal-action="cancel" class="button secondary remodal-cancel"><i class="fa fa-fw fa-close"></i> {{ "PLUGIN_ADMIN.CANCEL"|tu }}</button>
<a class="button continue" href="#"><i class="fa fa-fw fa-check"></i>{{ "PLUGIN_ADMIN.CONTINUE"|tu }}</a> <a class="button continue" href="#"><i class="fa fa-fw fa-check"></i>{{ "PLUGIN_ADMIN.CONTINUE"|tu }}</a>
</div> </div>
</form> </form>

View File

@@ -24,8 +24,8 @@
{% endblock %} {% endblock %}
{% block javascripts %} {% block javascripts %}
{% do assets.addJs(theme_url~'/js/codemirror-compressed.js') %} {#{% do assets.addJs(theme_url~'/js/codemirror-compressed.js') %}
{% do assets.addJs(theme_url~'/js/mdeditor.js') %} {% do assets.addJs(theme_url~'/js/mdeditor.js') %}#}
{{ parent() }} {{ parent() }}
{% endblock %} {% endblock %}
{% endif %} {% endif %}

View File

@@ -24,8 +24,8 @@
{% endblock %} {% endblock %}
{% block javascripts %} {% block javascripts %}
{% do assets.addJs(theme_url~'/js/codemirror-compressed.js') %} {#{% do assets.addJs(theme_url~'/js/codemirror-compressed.js') %}
{% do assets.addJs(theme_url~'/js/mdeditor.js') %} {% do assets.addJs(theme_url~'/js/mdeditor.js') %}#}
{{ parent() }} {{ parent() }}
{% endblock %} {% endblock %}
{% endif %} {% endif %}

View File

@@ -0,0 +1,44 @@
var path = require('path'),
webpack = require('webpack');
module.exports = {
entry: {
app: './app/main.js',
vendor: [
'chartist',
'selectize',
'remodal',
'toastr',
'bootstrap',
'sortablejs',
'jquery-slugify',
'dropzone',
'eonasdan-bootstrap-datetimepicker'
]
},
output: {
path: path.resolve(__dirname, 'js'),
library: 'Grav'
},
externals: {
jquery: 'jQuery',
'grav-config': 'GravAdmin'
},
module: {
preLoaders: [
{
test: /\.js$/,
loader: 'eslint',
exclude: /node_modules/
}
],
loaders: [
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
query: { presets: ['es2015'] }
}
]
}
};