mirror of
https://github.com/getgrav/grav-plugin-admin.git
synced 2026-05-05 18:46:47 +02:00
Merge branch 'develop' into feature/admin-gpm-dependencies
This commit is contained in:
@@ -535,6 +535,9 @@ class AdminPlugin extends Plugin
|
||||
'tabs' => [
|
||||
'input@' => false
|
||||
],
|
||||
'key' => [
|
||||
'input@' => false
|
||||
],
|
||||
'list' => [
|
||||
'array' => true
|
||||
]
|
||||
|
||||
32
blueprints/config/media.yaml
Normal file
32
blueprints/config/media.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
title: PLUGIN_ADMIN.MEDIA
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
'':
|
||||
name: media
|
||||
type: list
|
||||
label: Media Types
|
||||
key: extension
|
||||
sort: false
|
||||
|
||||
fields:
|
||||
.extension:
|
||||
type: key
|
||||
label: File Extension
|
||||
.type:
|
||||
type: text
|
||||
label: Type
|
||||
.thumb:
|
||||
type: text
|
||||
label: Thumb
|
||||
.mime:
|
||||
type: text
|
||||
label: Mime Type
|
||||
.image:
|
||||
type: textarea
|
||||
yaml: true
|
||||
label: Image options
|
||||
validate:
|
||||
type: yaml
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ form:
|
||||
fields:
|
||||
- name: username
|
||||
type: text
|
||||
placeholder: Username
|
||||
placeholder: PLUGIN_ADMIN.USERNAME
|
||||
autofocus: true
|
||||
---
|
||||
|
||||
|
||||
@@ -9,12 +9,10 @@ form:
|
||||
fields:
|
||||
- name: username
|
||||
type: text
|
||||
placeholder: Username
|
||||
placeholder: PLUGIN_ADMIN.USERNAME
|
||||
autofocus: true
|
||||
|
||||
- name: password
|
||||
type: password
|
||||
placeholder: Password
|
||||
placeholder: PLUGIN_ADMIN.PASSWORD
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ form:
|
||||
fields:
|
||||
- name: username
|
||||
type: text
|
||||
placeholder: Username
|
||||
placeholder: PLUGIN_ADMIN.USERNAME
|
||||
autofocus: true
|
||||
|
||||
- name: password
|
||||
type: password
|
||||
placeholder: Password
|
||||
placeholder: PLUGIN_ADMIN.PASSWORD
|
||||
---
|
||||
|
||||
@@ -5,13 +5,12 @@ form:
|
||||
fields:
|
||||
- name: username
|
||||
type: text
|
||||
placeholder: Username
|
||||
placeholder: PLUGIN_ADMIN.USERNAME
|
||||
readonly: true
|
||||
- name: password
|
||||
type: password
|
||||
placeholder: Password
|
||||
placeholder: PLUGIN_ADMIN.PASSWORD
|
||||
autofocus: true
|
||||
- name: token
|
||||
type: hidden
|
||||
---
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export default class CollectionsField {
|
||||
|
||||
list.find('[data-collection-holder]').each((index, container) => {
|
||||
container = $(container);
|
||||
if (container.data('collection-sort')) { return; }
|
||||
if (container.data('collection-sort') || container[0].hasAttribute('data-collection-nosort')) { return; }
|
||||
|
||||
container.data('collection-sort', new Sortable(container.get(0), {
|
||||
forceFallback: false,
|
||||
@@ -38,13 +38,9 @@ export default class CollectionsField {
|
||||
|
||||
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);
|
||||
}*/
|
||||
// refresh toggleables in a list
|
||||
$('[data-grav-field="toggleable"] input[type="checkbox"]').trigger('change');
|
||||
}
|
||||
|
||||
removeItem(event) {
|
||||
@@ -65,7 +61,7 @@ export default class CollectionsField {
|
||||
item = $(item);
|
||||
item.attr('data-collection-key', index);
|
||||
|
||||
['name', 'data-grav-field-name', 'id', 'for'].forEach((prop) => {
|
||||
['name', 'data-grav-field-name', 'for', 'id'].forEach((prop) => {
|
||||
item.find('[' + prop + ']').each(function() {
|
||||
let element = $(this);
|
||||
let indexes = [];
|
||||
|
||||
570
themes/grav/app/forms/fields/colorpicker.js
Normal file
570
themes/grav/app/forms/fields/colorpicker.js
Normal file
@@ -0,0 +1,570 @@
|
||||
import $ from 'jquery';
|
||||
import clamp from 'mout/math/clamp';
|
||||
import bind from 'mout/function/bind';
|
||||
import { rgbstr2hex, hsb2hex, hex2hsb, hex2rgb, parseHex } from '../../utils/colors';
|
||||
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
const body = $('body');
|
||||
|
||||
const MOUSEDOWN = 'mousedown touchstart MSPointerDown pointerdown';
|
||||
const MOUSEMOVE = 'mousemove touchmove MSPointerMove pointermove';
|
||||
const MOUSEUP = 'mouseup touchend MSPointerUp pointerup';
|
||||
const FOCUSIN = isFirefox ? 'focus' : 'focusin';
|
||||
|
||||
export default class ColorpickerField {
|
||||
constructor(selector) {
|
||||
this.selector = selector;
|
||||
this.field = $(this.selector);
|
||||
this.options = Object.assign({}, this.field.data('grav-colorpicker'));
|
||||
this.built = false;
|
||||
this.attach();
|
||||
|
||||
if (this.options.update) {
|
||||
this.field.on('change._grav_colorpicker', (event, field, hex, opacity) => {
|
||||
let backgroundColor = hex;
|
||||
let rgb = hex2rgb(hex);
|
||||
|
||||
if (opacity < 1) {
|
||||
backgroundColor = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + opacity + ')';
|
||||
}
|
||||
|
||||
let target = field.closest(this.options.update);
|
||||
if (!target.lenght) {
|
||||
target = field.siblings(this.options.update);
|
||||
}
|
||||
|
||||
target.css({ backgroundColor });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
attach() {
|
||||
body.on(FOCUSIN, this.selector, (event) => this.show(event, event.currentTarget));
|
||||
body.on(MOUSEDOWN, '.g-colorpicker i', this.bound('iconClick'));
|
||||
body.on('keydown', this.selector, (event) => {
|
||||
switch (event.keyCode) {
|
||||
case 9: // tab
|
||||
this.hide();
|
||||
break;
|
||||
case 13: // enter
|
||||
case 27: // esc
|
||||
this.hide();
|
||||
event.currentTarget.blur();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Update on keyup
|
||||
body.on('keyup', this.selector, (event) => {
|
||||
this.updateFromInput(true, event.currentTarget);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Update on paste
|
||||
body.on('paste', this.selector, (event) => {
|
||||
setTimeout(() => this.updateFromInput(true, event.currentTarget), 1);
|
||||
});
|
||||
}
|
||||
|
||||
show(event, target) {
|
||||
target = $(target);
|
||||
|
||||
if (!this.built) {
|
||||
this.build();
|
||||
}
|
||||
|
||||
this.element = target;
|
||||
this.reposition();
|
||||
this.wrapper.addClass('cp-visible');
|
||||
this.updateFromInput();
|
||||
|
||||
let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
|
||||
this.wrapper.on(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
|
||||
body.on(MOUSEMOVE, this.bound('bodyMove'));
|
||||
body.on(MOUSEDOWN, this.bound('bodyClick'));
|
||||
body.on(MOUSEUP, this.bound('targetReset'));
|
||||
$(mainContainer).on('scroll', this.bound('reposition'));
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.built) { return; }
|
||||
this.wrapper.removeClass('cp-visible');
|
||||
|
||||
let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
|
||||
this.wrapper.undelegate(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
|
||||
body.off(MOUSEMOVE, this.bound('bodyMove'));
|
||||
body.off(MOUSEDOWN, this.bound('bodyClick'));
|
||||
body.off(MOUSEUP, this.bound('targetReset'));
|
||||
$(mainContainer).off('scroll', this.bound('reposition'));
|
||||
}
|
||||
|
||||
build() {
|
||||
this.wrapper = $('<div class="cp-wrapper cp-with-opacity cp-mode-hue" />');
|
||||
this.slider = $('<div class="cp-slider cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-picker" />'));
|
||||
this.opacitySlider = $('<div class="cp-opacity-slider cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-picker" />'));
|
||||
this.grid = $('<div class="cp-grid cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-grid-inner" />')).append($('<div class="cp-picker" />'));
|
||||
|
||||
$('<div />').appendTo(this.grid.find('.cp-picker'));
|
||||
|
||||
let tabs = $('<div class="cp-tabs" />').appendTo(this.wrapper);
|
||||
this.tabs = {
|
||||
hue: $('<div class="cp-tab-hue active" />').text('HUE').appendTo(tabs),
|
||||
brightness: $('<div class="cp-tab-brightness" />').text('BRI').appendTo(tabs),
|
||||
saturation: $('<div class="cp-tab-saturation" />').text('SAT').appendTo(tabs),
|
||||
wheel: $('<div class="cp-tab-wheel" />').text('WHEEL').appendTo(tabs),
|
||||
transparent: $('<div class="cp-tab-transp" />').text('TRANSPARENT').appendTo(tabs)
|
||||
};
|
||||
|
||||
tabs.on(MOUSEDOWN, '> div', (event) => {
|
||||
let element = $(event.currentTarget);
|
||||
if (element.is(this.tabs.transparent)) {
|
||||
let sliderHeight = this.opacitySlider.height();
|
||||
|
||||
this.opacity = 0;
|
||||
this.opacitySlider.find('.cp-picker').css({ 'top': clamp(sliderHeight - (sliderHeight * this.opacity), 0, sliderHeight) });
|
||||
this.move(this.opacitySlider, { manualOpacity: true });
|
||||
return;
|
||||
}
|
||||
|
||||
let active = tabs.find('.active');
|
||||
let mode = active.attr('class').replace(/\s|active|cp-tab-/g, '');
|
||||
let newMode = element.attr('class').replace(/\s|active|cp-tab-/g, '');
|
||||
|
||||
this.wrapper.removeClass('cp-mode-' + mode).addClass('cp-mode-' + newMode);
|
||||
active.removeClass('active');
|
||||
element.addClass('active');
|
||||
|
||||
this.mode = newMode;
|
||||
this.updateFromInput();
|
||||
});
|
||||
|
||||
this.wrapper.appendTo('.content-wrapper');
|
||||
|
||||
this.built = true;
|
||||
this.mode = 'hue';
|
||||
}
|
||||
|
||||
reposition() {
|
||||
let offset = this.element[0].getBoundingClientRect();
|
||||
let ct = $('.content-wrapper')[0].getBoundingClientRect();
|
||||
|
||||
this.wrapper.css({
|
||||
top: offset.top + offset.height - ct.top,
|
||||
left: offset.left - ct.left
|
||||
});
|
||||
}
|
||||
|
||||
iconClick(event, element) {
|
||||
event && event.preventDefault();
|
||||
|
||||
let input = $(event.currentTarget).siblings('input');
|
||||
input.focus();
|
||||
|
||||
this.show(event, input);
|
||||
}
|
||||
|
||||
bodyMove(event) {
|
||||
event && event.preventDefault();
|
||||
|
||||
if (this.target) { this.move(this.target, event); }
|
||||
}
|
||||
|
||||
bodyClick(event) {
|
||||
let target = $(event.target);
|
||||
|
||||
if (!target.closest('.cp-wrapper').length && !target.is(this.selector)) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
bodyDown(event) {
|
||||
event && event.preventDefault();
|
||||
|
||||
this.target = $(event.currentTarget);
|
||||
this.move(this.target, event, true);
|
||||
}
|
||||
|
||||
targetReset(event) {
|
||||
event && event.preventDefault();
|
||||
|
||||
this.target = null;
|
||||
}
|
||||
|
||||
move(target, event) {
|
||||
let input = this.element;
|
||||
let picker = target.find('.cp-picker');
|
||||
let clientRect = target[0].getBoundingClientRect();
|
||||
let offsetX = clientRect.left + window.scrollX;
|
||||
let offsetY = clientRect.top + window.scrollY;
|
||||
let x = Math.round((event ? event.pageX : 0) - offsetX);
|
||||
let y = Math.round((event ? event.pageY : 0) - offsetY);
|
||||
let wx;
|
||||
let wy;
|
||||
let r;
|
||||
let phi;
|
||||
|
||||
// Touch support
|
||||
if (event && event.changedTouches) {
|
||||
x = (event.changedTouches ? event.changedTouches[0].pageX : 0) - offsetX;
|
||||
y = (event.changedTouches ? event.changedTouches[0].pageY : 0) - offsetY;
|
||||
}
|
||||
|
||||
if (event && event.manualOpacity) {
|
||||
y = clientRect.height;
|
||||
}
|
||||
|
||||
// Constrain picker to its container
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
if (x > clientRect.width) x = clientRect.width;
|
||||
if (y > clientRect.height) y = clientRect.height;
|
||||
|
||||
// Constrain color wheel values to the wheel
|
||||
if (target.parent('.cp-mode-wheel').length && picker.parent('.cp-grid').length) {
|
||||
wx = 75 - x;
|
||||
wy = 75 - y;
|
||||
r = Math.sqrt(wx * wx + wy * wy);
|
||||
phi = Math.atan2(wy, wx);
|
||||
|
||||
if (phi < 0) phi += Math.PI * 2;
|
||||
if (r > 75) {
|
||||
x = 75 - (75 * Math.cos(phi));
|
||||
y = 75 - (75 * Math.sin(phi));
|
||||
}
|
||||
|
||||
x = Math.round(x);
|
||||
y = Math.round(y);
|
||||
}
|
||||
|
||||
// Move the picker
|
||||
if (target.hasClass('cp-grid')) {
|
||||
picker.css({
|
||||
top: y,
|
||||
left: x
|
||||
});
|
||||
|
||||
this.updateFromPicker(input, target);
|
||||
} else {
|
||||
picker.css({
|
||||
top: y
|
||||
});
|
||||
this.updateFromPicker(input, target);
|
||||
}
|
||||
}
|
||||
|
||||
updateFromInput(dontFireEvent, element) {
|
||||
element = element ? $(element) : this.element;
|
||||
let value = element.val();
|
||||
let opacity = value.replace(/\s/g, '').match(/^rgba?\([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},(.+)\)/);
|
||||
let hex;
|
||||
let hsb;
|
||||
|
||||
value = rgbstr2hex(value) || value;
|
||||
opacity = opacity ? clamp(opacity[1], 0, 1) : 1;
|
||||
|
||||
if (!(hex = parseHex(value))) { hex = '#ffffff'; }
|
||||
hsb = hex2hsb(hex);
|
||||
|
||||
if (this.built) {
|
||||
// opacity
|
||||
this.opacity = opacity;
|
||||
var sliderHeight = this.opacitySlider.height();
|
||||
this.opacitySlider.find('.cp-picker').css({ 'top': clamp(sliderHeight - (sliderHeight * this.opacity), 0, sliderHeight) });
|
||||
|
||||
// bg color
|
||||
let gridHeight = this.grid.height();
|
||||
let gridWidth = this.grid.width();
|
||||
let r;
|
||||
let phi;
|
||||
let x;
|
||||
let y;
|
||||
|
||||
sliderHeight = this.slider.height();
|
||||
|
||||
switch (this.mode) {
|
||||
case 'wheel':
|
||||
// Set grid position
|
||||
r = clamp(Math.ceil(hsb.s * 0.75), 0, gridHeight / 2);
|
||||
phi = hsb.h * Math.PI / 180;
|
||||
x = clamp(75 - Math.cos(phi) * r, 0, gridWidth);
|
||||
y = clamp(75 - Math.sin(phi) * r, 0, gridHeight);
|
||||
this.grid.css({ backgroundColor: 'transparent' }).find('.cp-picker').css({
|
||||
top: y,
|
||||
left: x
|
||||
});
|
||||
|
||||
// Set slider position
|
||||
y = 150 - (hsb.b / (100 / gridHeight));
|
||||
if (hex === '') y = 0;
|
||||
this.slider.find('.cp-picker').css({ top: y });
|
||||
|
||||
// Update panel color
|
||||
this.slider.css({
|
||||
backgroundColor: hsb2hex({
|
||||
h: hsb.h,
|
||||
s: hsb.s,
|
||||
b: 100
|
||||
})
|
||||
});
|
||||
break;
|
||||
|
||||
case 'saturation':
|
||||
// Set grid position
|
||||
x = clamp((5 * hsb.h) / 12, 0, 150);
|
||||
y = clamp(gridHeight - Math.ceil(hsb.b / (100 / gridHeight)), 0, gridHeight);
|
||||
this.grid.find('.cp-picker').css({
|
||||
top: y,
|
||||
left: x
|
||||
});
|
||||
|
||||
// Set slider position
|
||||
y = clamp(sliderHeight - (hsb.s * (sliderHeight / 100)), 0, sliderHeight);
|
||||
this.slider.find('.cp-picker').css({ top: y });
|
||||
|
||||
// Update UI
|
||||
this.slider.css({
|
||||
backgroundColor: hsb2hex({
|
||||
h: hsb.h,
|
||||
s: 100,
|
||||
b: hsb.b
|
||||
})
|
||||
});
|
||||
this.grid.find('.cp-grid-inner').css({ opacity: hsb.s / 100 });
|
||||
break;
|
||||
|
||||
case 'brightness':
|
||||
// Set grid position
|
||||
x = clamp((5 * hsb.h) / 12, 0, 150);
|
||||
y = clamp(gridHeight - Math.ceil(hsb.s / (100 / gridHeight)), 0, gridHeight);
|
||||
this.grid.find('.cp-picker').css({
|
||||
top: y,
|
||||
left: x
|
||||
});
|
||||
|
||||
// Set slider position
|
||||
y = clamp(sliderHeight - (hsb.b * (sliderHeight / 100)), 0, sliderHeight);
|
||||
this.slider.find('.cp-picker').css({ top: y });
|
||||
|
||||
// Update UI
|
||||
this.slider.css({
|
||||
backgroundColor: hsb2hex({
|
||||
h: hsb.h,
|
||||
s: hsb.s,
|
||||
b: 100
|
||||
})
|
||||
});
|
||||
this.grid.find('.cp-grid-inner').css({ opacity: 1 - (hsb.b / 100) });
|
||||
break;
|
||||
case 'hue':
|
||||
default:
|
||||
// Set grid position
|
||||
x = clamp(Math.ceil(hsb.s / (100 / gridWidth)), 0, gridWidth);
|
||||
y = clamp(gridHeight - Math.ceil(hsb.b / (100 / gridHeight)), 0, gridHeight);
|
||||
this.grid.find('.cp-picker').css({
|
||||
top: y,
|
||||
left: x
|
||||
});
|
||||
|
||||
// Set slider position
|
||||
y = clamp(sliderHeight - (hsb.h / (360 / sliderHeight)), 0, sliderHeight);
|
||||
this.slider.find('.cp-picker').css({ top: y });
|
||||
|
||||
// Update panel color
|
||||
this.grid.css({
|
||||
backgroundColor: hsb2hex({
|
||||
h: hsb.h,
|
||||
s: 100,
|
||||
b: 100
|
||||
})
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dontFireEvent) { element.val(this.getValue(hex)); }
|
||||
|
||||
this.element.trigger('change._grav_colorpicker', [element, hex, opacity]);
|
||||
|
||||
}
|
||||
|
||||
updateFromPicker(input, target) {
|
||||
var getCoords = function(picker, container) {
|
||||
|
||||
var left, top;
|
||||
if (!picker.length || !container) return null;
|
||||
left = picker[0].getBoundingClientRect().left;
|
||||
top = picker[0].getBoundingClientRect().top;
|
||||
|
||||
return {
|
||||
x: left - container[0].getBoundingClientRect().left + (picker[0].offsetWidth / 2),
|
||||
y: top - container[0].getBoundingClientRect().top + (picker[0].offsetHeight / 2)
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
let hex;
|
||||
let hue;
|
||||
let saturation;
|
||||
let brightness;
|
||||
let x;
|
||||
let y;
|
||||
let r;
|
||||
let phi;
|
||||
|
||||
// Panel objects
|
||||
let grid = this.wrapper.find('.cp-grid');
|
||||
let slider = this.wrapper.find('.cp-slider');
|
||||
let opacitySlider = this.wrapper.find('.cp-opacity-slider');
|
||||
|
||||
// Picker objects
|
||||
let gridPicker = grid.find('.cp-picker');
|
||||
let sliderPicker = slider.find('.cp-picker');
|
||||
let opacityPicker = opacitySlider.find('.cp-picker');
|
||||
|
||||
// Picker positions
|
||||
let gridPos = getCoords(gridPicker, grid);
|
||||
let sliderPos = getCoords(sliderPicker, slider);
|
||||
let opacityPos = getCoords(opacityPicker, opacitySlider);
|
||||
|
||||
// Sizes
|
||||
let gridWidth = grid[0].getBoundingClientRect().width;
|
||||
let gridHeight = grid[0].getBoundingClientRect().height;
|
||||
let sliderHeight = slider[0].getBoundingClientRect().height;
|
||||
let opacitySliderHeight = opacitySlider[0].getBoundingClientRect().height;
|
||||
|
||||
let value = this.element.val();
|
||||
value = rgbstr2hex(value) || value;
|
||||
if (!(hex = parseHex(value))) { hex = '#ffffff'; }
|
||||
|
||||
// Handle colors
|
||||
if (target.hasClass('cp-grid') || target.hasClass('cp-slider')) {
|
||||
|
||||
// Determine HSB values
|
||||
switch (this.mode) {
|
||||
case 'wheel':
|
||||
// Calculate hue, saturation, and brightness
|
||||
x = (gridWidth / 2) - gridPos.x;
|
||||
y = (gridHeight / 2) - gridPos.y;
|
||||
r = Math.sqrt(x * x + y * y);
|
||||
phi = Math.atan2(y, x);
|
||||
if (phi < 0) phi += Math.PI * 2;
|
||||
if (r > 75) {
|
||||
r = 75;
|
||||
gridPos.x = 69 - (75 * Math.cos(phi));
|
||||
gridPos.y = 69 - (75 * Math.sin(phi));
|
||||
}
|
||||
saturation = clamp(r / 0.75, 0, 100);
|
||||
hue = clamp(phi * 180 / Math.PI, 0, 360);
|
||||
brightness = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
|
||||
hex = hsb2hex({
|
||||
h: hue,
|
||||
s: saturation,
|
||||
b: brightness
|
||||
});
|
||||
|
||||
// Update UI
|
||||
slider.css({
|
||||
backgroundColor: hsb2hex({
|
||||
h: hue,
|
||||
s: saturation,
|
||||
b: 100
|
||||
})
|
||||
});
|
||||
break;
|
||||
|
||||
case 'saturation':
|
||||
// Calculate hue, saturation, and brightness
|
||||
hue = clamp(parseInt(gridPos.x * (360 / gridWidth), 10), 0, 360);
|
||||
saturation = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
|
||||
brightness = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
|
||||
hex = hsb2hex({
|
||||
h: hue,
|
||||
s: saturation,
|
||||
b: brightness
|
||||
});
|
||||
|
||||
// Update UI
|
||||
slider.css({
|
||||
backgroundColor: hsb2hex({
|
||||
h: hue,
|
||||
s: 100,
|
||||
b: brightness
|
||||
})
|
||||
});
|
||||
grid.find('.cp-grid-inner').css({ opacity: saturation / 100 });
|
||||
break;
|
||||
|
||||
case 'brightness':
|
||||
// Calculate hue, saturation, and brightness
|
||||
hue = clamp(parseInt(gridPos.x * (360 / gridWidth), 10), 0, 360);
|
||||
saturation = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
|
||||
brightness = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
|
||||
hex = hsb2hex({
|
||||
h: hue,
|
||||
s: saturation,
|
||||
b: brightness
|
||||
});
|
||||
|
||||
// Update UI
|
||||
slider.css({
|
||||
backgroundColor: hsb2hex({
|
||||
h: hue,
|
||||
s: saturation,
|
||||
b: 100
|
||||
})
|
||||
});
|
||||
grid.find('.cp-grid-inner').css({ opacity: 1 - (brightness / 100) });
|
||||
break;
|
||||
|
||||
default:
|
||||
// Calculate hue, saturation, and brightness
|
||||
hue = clamp(360 - parseInt(sliderPos.y * (360 / sliderHeight), 10), 0, 360);
|
||||
saturation = clamp(Math.floor(gridPos.x * (100 / gridWidth)), 0, 100);
|
||||
brightness = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
|
||||
hex = hsb2hex({
|
||||
h: hue,
|
||||
s: saturation,
|
||||
b: brightness
|
||||
});
|
||||
|
||||
// Update UI
|
||||
grid.css({
|
||||
backgroundColor: hsb2hex({
|
||||
h: hue,
|
||||
s: 100,
|
||||
b: 100
|
||||
})
|
||||
});
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Handle opacity
|
||||
if (target.hasClass('cp-opacity-slider')) {
|
||||
this.opacity = parseFloat(1 - (opacityPos.y / opacitySliderHeight)).toFixed(2);
|
||||
}
|
||||
|
||||
// Adjust case
|
||||
input.val(this.getValue(hex));
|
||||
|
||||
// Handle change event
|
||||
this.element.trigger('change._grav_colorpicker', [this.element, hex, this.opacity]);
|
||||
|
||||
}
|
||||
|
||||
getValue(hex) {
|
||||
if (this.opacity === 1) { return hex; }
|
||||
let rgb = hex2rgb(hex);
|
||||
|
||||
return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + this.opacity + ')';
|
||||
}
|
||||
|
||||
bound(name) {
|
||||
let bound = this._bound || (this._bound = {});
|
||||
return bound[name] || (bound[name] = bind(this[name], this));
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new ColorpickerField('[data-grav-colorpicker]');
|
||||
@@ -3,7 +3,7 @@ import ArrayField, { Instance as ArrayFieldInstance } from './array';
|
||||
import CollectionsField, { Instance as CollectionsFieldInstance } from './collections';
|
||||
import DateTimeField, { Instance as DateTimeFieldInstance } from './datetime';
|
||||
import EditorField, { Instance as EditorFieldInstance } from './editor';
|
||||
// import ColorpickerField, { Instance as ColorpickerFieldInstance } from './colorpicker';
|
||||
import ColorpickerField, { Instance as ColorpickerFieldInstance } from './colorpicker';
|
||||
|
||||
export default {
|
||||
SelectizeField: {
|
||||
@@ -25,9 +25,9 @@ export default {
|
||||
EditorField: {
|
||||
EditorField,
|
||||
Instance: EditorFieldInstance
|
||||
},
|
||||
ColorpickerField: {
|
||||
ColorpickerField,
|
||||
Instance: ColorpickerFieldInstance
|
||||
}
|
||||
// ColorpickerField: {
|
||||
// ColorpickerField,
|
||||
// Instance: ColorpickerFieldInstance
|
||||
// }
|
||||
};
|
||||
|
||||
@@ -20,6 +20,13 @@ export default class Form {
|
||||
}
|
||||
}); */
|
||||
|
||||
// clear out any `noform` field from its name
|
||||
this.form.on('submit', () => {
|
||||
$('.no-form').attr('name', null);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
this._attachShortcuts();
|
||||
this._attachToggleables();
|
||||
this._attachDisabledFields();
|
||||
@@ -52,7 +59,7 @@ export default class Form {
|
||||
this.form.on('change', query, (event) => {
|
||||
let toggle = $(event.target);
|
||||
let enabled = toggle.is(':checked');
|
||||
let parent = toggle.parents('.form-field');
|
||||
let parent = toggle.closest('.form-field');
|
||||
let label = parent.find('label.toggleable');
|
||||
let fields = parent.find('.form-data');
|
||||
let inputs = fields.find('input, select, textarea');
|
||||
@@ -82,8 +89,7 @@ export default class Form {
|
||||
});
|
||||
|
||||
this.form.on('mousedown', query.join(', '), (event) => {
|
||||
let target = $(event.target);
|
||||
let input = target;
|
||||
let input = $(event.target);
|
||||
let isFor = input.prop('for');
|
||||
let isSelectize = (input.hasClass('selectize-control') || input.parents('.selectize-control')).length;
|
||||
|
||||
|
||||
152
themes/grav/app/utils/colors.js
Normal file
152
themes/grav/app/utils/colors.js
Normal file
@@ -0,0 +1,152 @@
|
||||
// Parses a string and returns a valid hex string when possible
|
||||
// parseHex('#fff') => '#ffffff'
|
||||
export const parseHex = (string) => {
|
||||
string = string.replace(/[^A-F0-9]/ig, '');
|
||||
if (string.length !== 3 && string.length !== 6) return '';
|
||||
if (string.length === 3) {
|
||||
string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2];
|
||||
}
|
||||
|
||||
return '#' + string.toLowerCase();
|
||||
};
|
||||
|
||||
// Converts an HSB object to an RGB object
|
||||
// hsb2rgb({h: 0, s: 0, b: 100}) => {r: 255, g: 255, b: 255}
|
||||
export const hsb2rgb = (hsb) => {
|
||||
let rgb = {};
|
||||
let h = Math.round(hsb.h);
|
||||
let s = Math.round(hsb.s * 255 / 100);
|
||||
let v = Math.round(hsb.b * 255 / 100);
|
||||
if (s === 0) {
|
||||
rgb.r = rgb.g = rgb.b = v;
|
||||
} else {
|
||||
var t1 = v;
|
||||
var t2 = (255 - s) * v / 255;
|
||||
var t3 = (t1 - t2) * (h % 60) / 60;
|
||||
if (h === 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3;
|
||||
} else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3;
|
||||
} else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3;
|
||||
} else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3;
|
||||
} else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3;
|
||||
} else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3;
|
||||
} else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
}
|
||||
}
|
||||
return {
|
||||
r: Math.round(rgb.r),
|
||||
g: Math.round(rgb.g),
|
||||
b: Math.round(rgb.b)
|
||||
};
|
||||
};
|
||||
|
||||
// Converts an RGB object to a HEX string
|
||||
// rgb2hex({r: 255, g: 255, b: 255}) => #ffffff
|
||||
export const rgb2hex = (rgb) => {
|
||||
var hex = [
|
||||
rgb.r.toString(16),
|
||||
rgb.g.toString(16),
|
||||
rgb.b.toString(16)
|
||||
];
|
||||
|
||||
hex.forEach((val, nr) => {
|
||||
if (val.length === 1) hex[nr] = '0' + val;
|
||||
});
|
||||
|
||||
return '#' + hex.join('');
|
||||
};
|
||||
|
||||
// Converts and RGB(a) string to a HEX string
|
||||
// rgbstr2hex('rgba(255, 255, 255, 0.5)') => #ffffff
|
||||
export const rgbstr2hex = (rgb) => {
|
||||
rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
|
||||
|
||||
return (rgb && rgb.length === 4) ? '#' +
|
||||
('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) +
|
||||
('0' + parseInt(rgb[2], 10).toString(16)).slice(-2) +
|
||||
('0' + parseInt(rgb[3], 10).toString(16)).slice(-2) : '';
|
||||
};
|
||||
|
||||
// Converts an HSB object to a HEX string
|
||||
// hsb2hex({h: 0, s: 0, b: 100}) => #ffffff
|
||||
export const hsb2hex = (hsb) => {
|
||||
return rgb2hex(hsb2rgb(hsb));
|
||||
};
|
||||
|
||||
// Converts a HEX string to an HSB object
|
||||
// hex2hsb('#ffffff') => {h: 0, s: 0, b: 100}
|
||||
export const hex2hsb = (hex) => {
|
||||
let hsb = rgb2hsb(hex2rgb(hex));
|
||||
if (hsb.s === 0) hsb.h = 360;
|
||||
|
||||
return hsb;
|
||||
};
|
||||
|
||||
// Converts an RGB object to an HSB object
|
||||
// rgb2hsb({r: 255, g: 255, b: 255}) => {h: 0, s: 0, b: 100}
|
||||
export const rgb2hsb = (rgb) => {
|
||||
let hsb = {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
};
|
||||
let min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
let max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
let delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max !== 0 ? 255 * delta / max : 0;
|
||||
if (hsb.s !== 0) {
|
||||
if (rgb.r === max) {
|
||||
hsb.h = (rgb.g - rgb.b) / delta;
|
||||
} else if (rgb.g === max) {
|
||||
hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
} else {
|
||||
hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
}
|
||||
} else {
|
||||
hsb.h = -1;
|
||||
}
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) {
|
||||
hsb.h += 360;
|
||||
}
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
|
||||
return hsb;
|
||||
};
|
||||
|
||||
// Converts a HEX string to an RGB object
|
||||
// hex2rgb('#ffffff') => {r: 255, g: 255, b: 255}
|
||||
export const hex2rgb = (hex) => {
|
||||
hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
|
||||
|
||||
return {
|
||||
/* jshint ignore:start */
|
||||
r: hex >> 16,
|
||||
g: (hex & 0x00FF00) >> 8,
|
||||
b: (hex & 0x0000FF)
|
||||
/* jshint ignore:end */
|
||||
};
|
||||
};
|
||||
2
themes/grav/css-compiled/template.css
vendored
2
themes/grav/css-compiled/template.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
24
themes/grav/js/admin.min.js
vendored
24
themes/grav/js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -24,6 +24,7 @@
|
||||
"gemini-scrollbar": "^1.3.2",
|
||||
"immutable": "^3.7.6",
|
||||
"js-yaml": "^3.5.3",
|
||||
"mout": "^0.12.0",
|
||||
"remodal": "^1.0.6",
|
||||
"selectize": "^0.12.1",
|
||||
"sortablejs": "^1.4.2",
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
@import "toggle-switch";
|
||||
@import "datetimepicker";
|
||||
@import "scrollbars";
|
||||
@import "colorpicker";
|
||||
|
||||
236
themes/grav/scss/template/modules/_colorpicker.scss
Normal file
236
themes/grav/scss/template/modules/_colorpicker.scss
Normal file
File diff suppressed because one or more lines are too long
26
themes/grav/templates/forms/fields/key/key.html.twig
Normal file
26
themes/grav/templates/forms/fields/key/key.html.twig
Normal file
@@ -0,0 +1,26 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input %}
|
||||
<div class="form-input-wrapper {{ field.size }}">
|
||||
<input
|
||||
type="text"
|
||||
value="{{ value|e('html_attr')|join(', ') }}"
|
||||
data-list-key="{{ scope|fieldName }}"
|
||||
{% block input_attributes %}
|
||||
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
|
||||
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
|
||||
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.placeholder %}placeholder="{{ field.placeholder }}"{% endif %}
|
||||
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
|
||||
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
|
||||
{% if field.readonly in ['on', 'true', 1] %}readonly="readonly"{% endif %}
|
||||
{% if field.autocomplete in ['on', 'off'] %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
|
||||
{% if field.validate.pattern %}pattern="{{ field.validate.pattern }}"{% endif %}
|
||||
{% if field.validate.message %}title="{{ field.validate.message|e|t }}"
|
||||
{% elseif field.title is defined %}title="{{ field.title|e|t }}" {% endif %}
|
||||
{% endblock %}
|
||||
/>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -2,6 +2,7 @@
|
||||
{% set name = scope ~ field.name %}
|
||||
{% set btnLabel = field.btnLabel is defined ? field.btnLabel : "PLUGIN_ADMIN.ADD_ITEM" %}
|
||||
|
||||
|
||||
<div class="form-field grid pure-g">
|
||||
<div class="form-label block size-1-4 pure-u-1-4">
|
||||
<label>
|
||||
@@ -15,10 +16,13 @@
|
||||
</div>
|
||||
<div class="form-data block size-3-4 pure-u-3-4">
|
||||
<div class="form-list-wrapper {{ field.size }}" data-type="collection">
|
||||
<ul data-collection-holder="{{ name }}">
|
||||
<ul data-collection-holder="{{ name }}"
|
||||
{% if field.sort is same as(false) %}
|
||||
data-collection-nosort
|
||||
{% endif %}>
|
||||
{% if field.fields %}
|
||||
{% for key, val in value %}
|
||||
{% set itemName = name ~ '.' ~ key %}
|
||||
{% set itemName = name ? name ~ '.' ~ key : key %}
|
||||
<li data-collection-item="{{ itemName }}" data-collection-key="{{ key }}">
|
||||
{% for childName, child in field.fields %}
|
||||
{% if childName starts with '.' %}
|
||||
@@ -34,7 +38,7 @@
|
||||
|
||||
{% if child.type == 'key' %}
|
||||
{%
|
||||
include 'forms/fields/text/text.html.twig'
|
||||
include 'forms/fields/key/key.html.twig'
|
||||
with { field: child, value: key }
|
||||
%}
|
||||
{% elseif child.type %}
|
||||
@@ -47,8 +51,10 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="item-actions">
|
||||
{% if field.sort is not same as(false) %}
|
||||
<i class="fa fa-bars"></i>
|
||||
<br />
|
||||
{% endif %}
|
||||
<i class="fa fa-trash-o" data-action="delete"></i>
|
||||
</div>
|
||||
</li>
|
||||
@@ -59,10 +65,10 @@
|
||||
<button class="button" type="button" data-action="add"><i class="fa fa-plus"></i> {{ btnLabel|e|tu }}</button>
|
||||
</div>
|
||||
|
||||
{%- set itemName = name ? name ~ '.*' : '*' -%}
|
||||
<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="{{ itemName }}">
|
||||
{%- if field.fields -%}
|
||||
{%- set itemName = name ~ '.*' -%}
|
||||
{%- for childName, child in field.fields -%}
|
||||
{%- if childName starts with '.' -%}
|
||||
{%- set childKey = childName|trim('.') -%}
|
||||
@@ -75,7 +81,7 @@
|
||||
|
||||
{%- if child.type == 'key' -%}
|
||||
{%-
|
||||
include 'forms/fields/text/text.html.twig'
|
||||
include 'forms/fields/key/key.html.twig'
|
||||
with { field: child, value: null }
|
||||
-%}
|
||||
{%- elseif child.type -%}
|
||||
@@ -88,8 +94,10 @@
|
||||
{%- endif -%}
|
||||
{%- endfor %}
|
||||
<div class="item-actions">
|
||||
{% if field.sort is not same as(false) %}
|
||||
<i class="fa fa-bars"></i>
|
||||
<br />
|
||||
{% endif %}
|
||||
<i class="fa fa-trash-o" data-action="delete"></i>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<form id="{{ form_id }}" method="post" data-grav-form="{{ form_id }}" data-grav-keepalive="true"{{ multipart }}>
|
||||
{% for field in blueprints.fields %}
|
||||
{% if field.type %}
|
||||
{% set value = data.value(field.name) %}
|
||||
{% set value = field.name ? data.value(field.name) : data.toArray %}
|
||||
|
||||
<div class="block block-{{ field.type }}">
|
||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||
|
||||
Reference in New Issue
Block a user