diff --git a/customize.dist/loading.js b/customize.dist/loading.js
index ce11c5b76..917439dcc 100644
--- a/customize.dist/loading.js
+++ b/customize.dist/loading.js
@@ -15,7 +15,7 @@ define([
elem.innerHTML = [
'
',
'
',
diff --git a/customize.dist/pages/index.js b/customize.dist/pages/index.js
index c65ecc93c..a3f03f96f 100644
--- a/customize.dist/pages/index.js
+++ b/customize.dist/pages/index.js
@@ -176,7 +176,7 @@ define([
h('div.row.cp-home-hero', [
h('div.cp-title.col-lg-6', [
h('img', {
- src: '/customize/CryptPad_logo_hero.svg?' + urlArgs,
+ src: '/api/logo?' + urlArgs,
'aria-hidden': 'true',
alt: ''
}),
diff --git a/customize.dist/pre-loading.js b/customize.dist/pre-loading.js
index a5015bae1..8b737922b 100644
--- a/customize.dist/pre-loading.js
+++ b/customize.dist/pre-loading.js
@@ -5,7 +5,7 @@
(function () {
var logoPath = '/customize/CryptPad_logo.svg';
if (location.pathname === '/' || location.pathname === '/index.html') {
- logoPath = '/customize/CryptPad_logo_hero.svg';
+ logoPath = '/api/logo';
}
var elem = document.createElement('div');
diff --git a/customize.dist/src/less2/include/colortheme-dark.less b/customize.dist/src/less2/include/colortheme-dark.less
index 488bc6444..07117b0f9 100644
--- a/customize.dist/src/less2/include/colortheme-dark.less
+++ b/customize.dist/src/less2/include/colortheme-dark.less
@@ -9,9 +9,16 @@
@colortheme_app-font-size-small: 13px;
@colortheme_app-font: @colortheme_app-font-size @colortheme_font;
+// Colors
+@cryptpad_color_brand: #0087FF;
+@cryptpad_color_brand_300: lighten(@cryptpad_color_brand, 30%);
+@cryptpad_color_brand_fade: fade(@cryptpad_color_brand, 75%);
+@cryptpad_color_brand_fader: fade(@cryptpad_color_brand, 50%);
+@cryptpad_color_brand_fadest: fade(@cryptpad_color_brand, 25%);
+
@colortheme_apps: {
- default: #0087FF;
- drive: #0087FF; // Used as icon color in index.js (index.html)
+ default: @cryptpad_color_brand;
+ drive: @cryptpad_color_brand; // Used as icon color in index.js (index.html)
pad: #256ad5;
code: #EAA000;
slide: #e57614;
@@ -27,18 +34,11 @@
}
@colortheme_static_apps: {
- default: #0087FF;
+ default: @cryptpad_color_brand;
teams: #4A3BBD;
contacts: #607B8D;
}
-// Colors
-@cryptpad_color_brand: #0087FF;
-@cryptpad_color_brand_300: lighten(@cryptpad_color_brand, 30%);
-@cryptpad_color_brand_fade: fade(@cryptpad_color_brand, 75%);
-@cryptpad_color_brand_fader: fade(@cryptpad_color_brand, 50%);
-@cryptpad_color_brand_fadest: fade(@cryptpad_color_brand, 25%);
-
@cryptpad_color_white: #FFF;
@cryptpad_color_grey_50: #FAFAFA;
@cryptpad_color_grey_100: #F5F5F5;
diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less
index 87a8cbb59..faaca5285 100644
--- a/customize.dist/src/less2/include/colortheme.less
+++ b/customize.dist/src/less2/include/colortheme.less
@@ -9,9 +9,16 @@
@colortheme_app-font-size-small: 13px;
@colortheme_app-font: @colortheme_app-font-size @colortheme_font;
+// Colors
+@cryptpad_color_brand: #0087FF;
+@cryptpad_color_brand_300: lighten(@cryptpad_color_brand, 30%);
+@cryptpad_color_brand_fade: fade(@cryptpad_color_brand, 75%);
+@cryptpad_color_brand_fader: fade(@cryptpad_color_brand, 50%);
+@cryptpad_color_brand_fadest: fade(@cryptpad_color_brand, 25%);
+
@colortheme_apps: {
- default: #0087FF;
- drive: #0087FF; // Used as icon color in index.js (index.html)
+ default: @cryptpad_color_brand;
+ drive: @cryptpad_color_brand; // Used as icon color in index.js (index.html)
pad: #256ad5;
code: #EAA000;
slide: #e57614;
@@ -27,18 +34,11 @@
}
@colortheme_static_apps: {
- default: #0087FF;
+ default: @cryptpad_color_brand;
teams: #4A3BBD;
contacts: #607B8D;
}
-// Colors
-@cryptpad_color_brand: #0087FF;
-@cryptpad_color_brand_300: lighten(@cryptpad_color_brand, 30%);
-@cryptpad_color_brand_fade: fade(@cryptpad_color_brand, 75%);
-@cryptpad_color_brand_fader: fade(@cryptpad_color_brand, 50%);
-@cryptpad_color_brand_fadest: fade(@cryptpad_color_brand, 25%);
-
@cryptpad_color_white: #FFF;
@cryptpad_color_grey_50: #FAFAFA;
@cryptpad_color_grey_100: #F5F5F5;
diff --git a/customize.dist/src/less2/include/loading.less b/customize.dist/src/less2/include/loading.less
index c58f9583a..dd89dff2b 100644
--- a/customize.dist/src/less2/include/loading.less
+++ b/customize.dist/src/less2/include/loading.less
@@ -75,7 +75,8 @@
margin-left: auto;
margin-right: auto;
max-width: 90vw;
- max-height: 300px;
+ max-height: ~"max(40vh, 250px)";
+ //max-height: 300px;
width: auto;
height: auto;
margin-bottom: 2em;
diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less
index 1b7917adb..838aa071c 100644
--- a/customize.dist/src/less2/include/sidebar-layout.less
+++ b/customize.dist/src/less2/include/sidebar-layout.less
@@ -179,6 +179,10 @@
margin: 0 !important;
}
}
+ a {
+ color: @cryptpad_color_link;
+ text-decoration: underline;
+ }
table {
.cp-strong {
font-weight: bold;
@@ -210,6 +214,11 @@
height: 40px;
box-sizing: border-box;
}
+ [type="file"] { // XXX hack, to fix with sidebar layout refactoring
+ height: auto;
+ box-sizing: border-box;
+ padding: 6.5px;
+ }
.cp-sidebarlayout-input-block {
display: inline-flex;
width: @sidebar_button-width;
diff --git a/customize.dist/src/less2/pages/page-index.less b/customize.dist/src/less2/pages/page-index.less
index 5c333fcba..7873f5f55 100644
--- a/customize.dist/src/less2/pages/page-index.less
+++ b/customize.dist/src/less2/pages/page-index.less
@@ -61,6 +61,9 @@
@media screen and (max-width: 990px) {
justify-content: center;
}
+ img {
+ max-height: ~"max(40vh, 250px)";
+ }
}
.cp-title {
display: flex;
diff --git a/customize.dist/src/pre-loading.css b/customize.dist/src/pre-loading.css
index e33a6c29a..c7ea7856b 100644
--- a/customize.dist/src/pre-loading.css
+++ b/customize.dist/src/pre-loading.css
@@ -72,7 +72,7 @@ html:not(.cp-app-noscroll) #placeholder.dark-theme {
margin-left: auto;
margin-right: auto;
max-width: 90vw;
- max-height: 300px;
+ max-height: max(40vh, 250px);
width: auto;
height: auto;
margin-bottom: 2em;
diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js
index 67c228eab..954c1c4c1 100644
--- a/lib/commands/admin-rpc.js
+++ b/lib/commands/admin-rpc.js
@@ -18,8 +18,9 @@ const BlockStore = require("../storage/block");
const MFA = require("../storage/mfa");
const ArchiveAccount = require('../archive-account');
const { Worker } = require('node:worker_threads');
+const Fse = require("fs-extra");
-var Fs = require("fs");
+const Fs = require("fs");
var Admin = module.exports;
@@ -955,6 +956,72 @@ var archiveSupport = (Env, Server, cb) => {
}, [null, supportPinKey]);
};
+const MAX_LOGO_SIZE = 200*1024; // 200KB
+var removeLogo = (Env, Server, cb) => {
+ Fse.unlink('./customize/CryptPad_logo_hero.svg', (err) => {
+ cb(err);
+ });
+};
+var uploadLogo = (Env, Server, cb, data, unsafeKey) => {
+ const args = Array.isArray(data) && data[1];
+ if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
+ let dataURL = args.dataURL;
+
+ // (size*4/3) + 24 ==> base64 and dataURL overhead
+ if (!dataURL || dataURL.length > ((MAX_LOGO_SIZE*4/3)+24)) {
+ return void cb('E_TOO_LARGE');
+ }
+
+ let s = dataURL.split(',');
+ let base64 = s[1];
+ let mime = s[0].slice(s[0].indexOf(":")+1, s[0].indexOf(";"));
+ if (!base64 || !mime) { return void cb('EINVAL'); }
+ let buf;
+ try {
+ buf = Buffer.from(base64, 'base64');
+ } catch (e) {
+ return void cb(e);
+ }
+
+ nThen(waitFor => {
+ Fse.mkdirp('customize', {}, waitFor((err) => {
+ if (!err) { return; }
+ waitFor.abort();
+ return void cb(err);
+ }));
+ }).nThen(waitFor => {
+ Fse.writeFile('./customize/CryptPad_logo_hero.svg', buf, waitFor((err) => {
+ if (!err) { return; }
+ waitFor.abort();
+ return void cb(err);
+ }));
+ }).nThen(() => {
+ adminDecree(Env, null, function (err) {
+ if (err) { return void cb(err); }
+ Env.flushCache();
+ cb(void 0, true);
+ }, ['UPLOAD_LOGO', [
+ 'SET_LOGO_MIME',
+ [mime]
+ ]], unsafeKey);
+ });
+};
+
+var changeColor = (Env, Server, cb, data, unsafeKey) => {
+ const args = Array.isArray(data) && data[1];
+ if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
+ let color = args.color;
+ adminDecree(Env, null, function (err) {
+ if (err) { return void cb(err); }
+ Env.flushCache();
+ cb(void 0, true);
+ }, ['CHANGE_COLOR', [
+ 'SET_ACCENT_COLOR',
+ [color]
+ ]], unsafeKey);
+};
+
+
var commands = {
ACTIVE_SESSIONS: getActiveSessions,
ACTIVE_PADS: getActiveChannelCount,
@@ -1027,6 +1094,10 @@ var commands = {
GET_MODERATORS: getModerators,
ADD_MODERATOR: addModerator,
REMOVE_MODERATOR: removeModerator,
+
+ UPLOAD_LOGO: uploadLogo,
+ REMOVE_LOGO: removeLogo,
+ CHANGE_COLOR: changeColor,
};
// addFirstAdmin is an anon_rpc command
diff --git a/lib/decrees.js b/lib/decrees.js
index 9fa5c01fb..79a978398 100644
--- a/lib/decrees.js
+++ b/lib/decrees.js
@@ -159,10 +159,16 @@ var makeGenericSetter = function (attr, validator) {
};
};
+var isString = (str) => {
+ return typeof(str) === "string";
+};
var isInteger = function (n) {
return !(typeof(n) !== 'number' || isNaN(n) || (n % 1) !== 0);
};
+var args_isString = function (args) {
+ return !(!Array.isArray(args) || !isString(args[0]));
+};
var args_isInteger = function (args) {
return !(!Array.isArray(args) || !isInteger(args[0]));
};
@@ -175,6 +181,12 @@ var arg_isPositiveInteger = function (args) {
return Array.isArray(args) && isInteger(args[0]) && args[0] > 0;
};
+// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_LOGO_MIME', ['image/png']]], console.log)
+commands.SET_LOGO_MIME = makeGenericSetter('logoMimeType', args_isString);
+
+// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_ACCENT_COLOR', ['#ff0073']]], console.log)
+commands.SET_ACCENT_COLOR = makeGenericSetter('accentColor', args_isString);
+
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['ENABLE_PROFILING', [true]]], console.log)
commands.ENABLE_PROFILING = makeBooleanSetter('enableProfiling');
diff --git a/lib/http-worker.js b/lib/http-worker.js
index 52cdfca6b..78cc050a4 100644
--- a/lib/http-worker.js
+++ b/lib/http-worker.js
@@ -506,6 +506,7 @@ app.use("/block", (req, res, next) => {
next();
});
+
app.use("/customize", Express.static('customize'));
app.use("/customize", Express.static('customize.dist'));
app.use("/customize.dist", Express.static('customize.dist'));
@@ -625,6 +626,7 @@ var Define = function (obj) {
app.get('/api/instance', function (req, res) {
res.setHeader('Content-Type', 'text/javascript');
res.send(Define({
+ color: Env.accentColor,
name: Env.instanceName,
description: Env.instanceDescription,
location: Env.instanceJurisdiction,
@@ -686,6 +688,20 @@ app.get('/api/profiling', function (req, res) {
});
});
+app.get('/api/logo', function (req, res) {
+ let path = Path.resolve('./customize/CryptPad_logo_hero.svg');
+ let base = Path.resolve('./customize.dist/CryptPad_logo_hero.svg');
+ Fs.exists(path, function (exists) {
+ res.setHeader('Content-Disposition', 'inline');
+ if (exists) {
+ let mime = Env.logoMimeType || 'image/svg+xml';
+ res.setHeader('Content-Type', mime);
+ return res.sendFile(path);
+ }
+ res.sendFile(base);
+ });
+});
+
// This endpoint handles authenticated RPCs over HTTP
// via an interactive challenge-response protocol
app.use(Express.json());
diff --git a/www/admin/inner.js b/www/admin/inner.js
index 166f7a36c..4efb51ffd 100644
--- a/www/admin/inner.js
+++ b/www/admin/inner.js
@@ -72,6 +72,9 @@ define([
'cp-admin-jurisdiction',
'cp-admin-notice',
],
+ 'customize': [
+ 'cp-admin-logo'
+ ],
'users': [ // Msg.admin_cat_quota
'cp-admin-registration',
'cp-admin-invitation',
@@ -3779,6 +3782,91 @@ Example
return $div;
};
+ // XXX
+ Messages.admin_logoTitle = "Upload Logo";
+ Messages.admin_logoHint = "Max 200KB, svg, png or jpg";
+ Messages.admin_logoButton = "Upload new";
+ Messages.admin_logoRemoveButton = "Restore default";
+ create['logo'] = function () {
+ var key = 'logo';
+ var $div = makeBlock(key, false); // Msg.admin_emailHint, Msg.admin_emailTitle
+
+ var input = h('input', {
+ type: 'file',
+ accept: 'image/*',
+ 'aria-labelledby': 'cp-admin-logo'
+ });
+
+ var currentContainer = h('div');
+ let redraw = () => {
+ var current = h('img', {src: '/api/logo?'+(+new Date())});
+ $(currentContainer).empty().append(current);
+ };
+ redraw();
+
+ var upload = h('button.btn.btn-primary', Messages.admin_logoButton);
+ var remove = h('button.btn.btn-danger', Messages.admin_logoRemoveButton);
+
+ $div.append([
+ currentContainer,
+ h('div', input),
+ h('nav', [upload, remove])
+ ]);
+
+ let $button = $(upload);
+ let $remove = $(remove);
+ var spinner = UI.makeSpinner($div);
+
+ Util.onClickEnter($button, function () {
+ let files = input.files;
+ if (files.length !== 1) {
+ UI.warn(Messages.error);
+ return;
+ }
+ spinner.spin();
+ $button.attr('disabled', 'disabled');
+ let reader = new FileReader();
+ reader.onloadend = function () {
+ let dataURL = this.result;
+ sframeCommand('UPLOAD_LOGO', {dataURL}, (err, response) => {
+ $button.removeAttr('disabled');
+ if (err) {
+ UI.warn(Messages.error);
+ $(input).val('');
+ console.error(err, response);
+ spinner.hide();
+ return;
+ }
+ redraw();
+ spinner.done();
+ UI.log(Messages.saved);
+ });
+ };
+ reader.readAsDataURL(files[0]);
+ });
+ UI.confirmButton($remove, {
+ classes: 'btn-danger',
+ multiple: true
+ }, function () {
+ spinner.spin();
+ $remove.attr('disabled', 'disabled');
+ sframeCommand('REMOVE_LOGO', {}, (err, response) => {
+ $remove.removeAttr('disabled');
+ if (err) {
+ UI.warn(Messages.error);
+ console.error(err, response);
+ spinner.hide();
+ return;
+ }
+ redraw();
+ spinner.done();
+ UI.log(Messages.saved);
+ });
+ });
+
+ return $div;
+ };
+
var hideCategories = function () {
APP.$rightside.find('> div').hide();
};
diff --git a/www/common/LessLoader.js b/www/common/LessLoader.js
index bf22d6126..8fac17fc1 100644
--- a/www/common/LessLoader.js
+++ b/www/common/LessLoader.js
@@ -9,8 +9,9 @@ const require = define;
*/
define([
'/api/config',
+ '/api/instance',
'/components/nthen/index.js'
-], function (Config, nThen) { /*::});module.exports = (function() {
+], function (Config, Instance, nThen) { /*::});module.exports = (function() {
const Config = (undefined:any);
const nThen = (undefined:any);
*/
@@ -153,6 +154,10 @@ define([
].join('\n');
text += '\n'+custom;
}
+ if (Instance && Instance.color) {
+ let pattern = /_color_brand: ([0-9a-zA-Z#]+);/gm;
+ text = text.replace(pattern, `_color_brand: ${Instance.color};`);
+ }
}
cached.res = [ text, lastModified ];
var queue = cached.queue;
diff --git a/www/common/inner/sidebar-layout.js b/www/common/inner/sidebar-layout.js
index 25a6ccfe4..7027db07a 100644
--- a/www/common/inner/sidebar-layout.js
+++ b/www/common/inner/sidebar-layout.js
@@ -78,8 +78,10 @@ define([
blocks.code = val => {
return h('code', val);
};
- blocks.inline = (value) => {
- return h('span', value);
+ blocks.inline = (value, className) => {
+ let attr = {};
+ if (className) { attr.class = className; }
+ return h('span', attr, value);
};
blocks.block = (content, className) => {
let attr = {};
diff --git a/www/newadmin/app-admin.less b/www/newadmin/app-admin.less
index 242f368a2..2fcf1499f 100644
--- a/www/newadmin/app-admin.less
+++ b/www/newadmin/app-admin.less
@@ -19,5 +19,12 @@
display: flex;
flex-flow: column;
font: @colortheme_app-font;
+
+ .cp-admin-color-current {
+ width: 30px;
+ height: 30px;
+ border-radius: 5px;
+ background: @cryptpad_color_brand;
+ }
}
diff --git a/www/newadmin/inner.js b/www/newadmin/inner.js
index f6f1fe68f..250213fb9 100644
--- a/www/newadmin/inner.js
+++ b/www/newadmin/inner.js
@@ -19,6 +19,7 @@ define([
'json.sortify',
'/customize/application_config.js',
'/api/config',
+ '/api/instance',
'/lib/datepicker/flatpickr.js',
'/common/hyperscript.js',
'css!/lib/datepicker/flatpickr.min.css',
@@ -42,6 +43,7 @@ define([
Sortify,
AppConfig,
ApiConfig,
+ Instance,
Flatpickr
) {
var APP = window.APP = {};
@@ -70,6 +72,13 @@ define([
'notice',
]
},
+ 'customize': {
+ icon: 'fa fa-paint-brush',
+ content: [
+ 'logo',
+ 'color'
+ ]
+ },
'users' : {
icon : 'fa fa-address-card-o',
content : [
@@ -745,6 +754,177 @@ define([
cb(form);
});
+ // XXX
+ Messages.admin_cat_customize = "Customize";
+ Messages.admin_logoTitle = "Upload Logo";
+ Messages.admin_logoHint = "Max 200KB, svg, png or jpg";
+ Messages.admin_logoButton = "Upload new";
+ Messages.admin_logoRemoveButton = "Restore default";
+ Messages.admin_colorTitle = "Main color";
+ Messages.admin_colorHint = "Change the main color of your CryptPad instance. Please pick a color with good contrast with the rest of CryptPad.";
+ Messages.admin_colorCurrent = "Current main color";
+ Messages.admin_colorChange = "Change color";
+ Messages.admin_colorPick = "Pick a color";
+ Messages.admin_colorPreview = "Preview color";
+
+ sidebar.addItem('logo', (cb) => {
+ // Msg.admin_logoHint, Msg.admin_logoTitle
+
+ let input = blocks.input({
+ type: 'file',
+ accept: 'image/*',
+ 'aria-labelledby': 'cp-admin-logo'
+ });
+
+ var currentContainer = blocks.block();
+ let redraw = () => {
+ var current = h('img', {src: '/api/logo?'+(+new Date())});
+ $(currentContainer).empty().append(current);
+ };
+ redraw();
+
+ var upload = blocks.button('primary', '', Messages.admin_logoButton);
+ var remove = blocks.button('danger', '', Messages.admin_logoRemoveButton);
+
+ let spinnerBlock = blocks.inline();
+ let spinner = UI.makeSpinner($(spinnerBlock));
+ let form = blocks.form([
+ currentContainer,
+ blocks.block(input),
+ blocks.nav([upload, remove, spinnerBlock])
+ ]);
+
+ let $button = $(upload);
+ let $remove = $(remove);
+
+ Util.onClickEnter($button, function () {
+ let files = input.files;
+ if (files.length !== 1) {
+ UI.warn(Messages.error);
+ return;
+ }
+ spinner.spin();
+ $button.attr('disabled', 'disabled');
+ let reader = new FileReader();
+ reader.onloadend = function () {
+ let dataURL = this.result;
+ sframeCommand('UPLOAD_LOGO', {dataURL}, (err, response) => {
+ $button.removeAttr('disabled');
+ if (err) {
+ UI.warn(Messages.error);
+ $(input).val('');
+ console.error(err, response);
+ spinner.hide();
+ return;
+ }
+ redraw();
+ spinner.done();
+ UI.log(Messages.saved);
+ });
+ };
+ reader.readAsDataURL(files[0]);
+ });
+ UI.confirmButton($remove, {
+ classes: 'btn-danger',
+ multiple: true
+ }, function () {
+ spinner.spin();
+ $remove.attr('disabled', 'disabled');
+ sframeCommand('REMOVE_LOGO', {}, (err, response) => {
+ $remove.removeAttr('disabled');
+ if (err) {
+ UI.warn(Messages.error);
+ console.error(err, response);
+ spinner.hide();
+ return;
+ }
+ redraw();
+ spinner.done();
+ UI.log(Messages.saved);
+ });
+ });
+
+ cb(form);
+ });
+
+ sidebar.addItem('color', cb => {
+ let input = blocks.input({
+ type: 'color',
+ value: (Instance && Instance.color) || '#0087FF'
+ });
+ let label = blocks.labelledInput(Messages.admin_colorPick, input);
+ let current = blocks.block([], 'cp-admin-color-current');
+ let labelCurrent = blocks.labelledInput(Messages.admin_colorCurrent, current);
+ let preview = blocks.block([
+ blocks.block([
+ blocks.link('CryptPad', '/admin/#customize')
+ ]),
+ blocks.nav([
+ blocks.button('primary', 'fa-floppy-o', Messages.settings_save),
+ blocks.button('secondary', 'fa-floppy-o', Messages.settings_save),
+ ])
+ ], 'cp-admin-color-preview');
+ let labelPreview = blocks.labelledInput(Messages.admin_colorPreview, preview);
+ let $preview = $(preview);
+
+ let remove = blocks.button('danger', '', Messages.admin_logoRemoveButton);
+ let $remove = $(remove);
+
+ let setColor = (color, done) => {
+ sframeCommand('CHANGE_COLOR', {color}, (err, response) => {
+ if (err) {
+ UI.warn(Messages.error);
+ console.error(err, response);
+ done(false);
+ return;
+ }
+ done(true);
+ flushCacheNotice();
+ UI.log(Messages.saved);
+ });
+ };
+
+ let btn = blocks.activeButton('primary', '',
+ Messages.admin_colorChange, (done) => {
+ let color = $input.val();
+ setColor(color, done);
+ });
+
+ let $input = $(input).on('change', () => {
+ require(['/lib/less.min.js'], (Less) => {
+ let color = $input.val();
+ let lColor = Less.color(color.slice(1));
+ let lighten = Less.functions.functionRegistry._data.lighten;
+ let lightColor = lighten(lColor, {value:30}).toRGB();
+ $preview.find('.btn-primary').css({
+ 'background-color': color
+ });
+ $preview.find('.btn-secondary').css({
+ 'border-color': lightColor,
+ 'color': lightColor,
+ });
+ $preview.find('a').css({
+ 'color': lightColor,
+ });
+ });
+ });
+
+ UI.confirmButton($remove, {
+ classes: 'btn-danger',
+ multiple: true
+ }, function () {
+ $remove.attr('disabled', 'disabled');
+ setColor('', () => {});
+ });
+
+ let form = blocks.form([
+ labelCurrent,
+ label
+ ], blocks.nav([btn, remove, btn.spinner]));
+
+ cb([form, labelPreview]);
+ });
+
sidebar.addItem('registration', function(cb){
var refresh = function () {};