diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js
index 9e2c574aa..ab29b56fa 100644
--- a/customize.dist/application_config.js
+++ b/customize.dist/application_config.js
@@ -9,12 +9,14 @@
* If you want to check all the configurable values, you can open the internal configuration file
but you should not change it directly (/common/application_config_internal.js)
*/
+
define(['/common/application_config_internal.js'], function (AppConfig) {
+
// Example: If you want to remove the survey link in the menu:
// AppConfig.surveyURL = "";
// To inform users of the support ticket panel which languages your admins speak:
//AppConfig.supportLanguages = [ 'en', 'fr' ];
-
+
return AppConfig;
});
diff --git a/customize.dist/messages.js b/customize.dist/messages.js
index b527d24e0..b644fd936 100755
--- a/customize.dist/messages.js
+++ b/customize.dist/messages.js
@@ -132,6 +132,28 @@ define(req, function(AppConfig, Default, Language) {
}
};
+Messages.install_token = "Install token";
+Messages.install_header = "Installation";
+Messages.install_instance = "Create the first admin account, then proceed to customize this instance";
+Messages.install_launch = "Instance setup";
+Messages.install_notes = `
+- Create your first administrator account on this page. Administrators manage instance settings including storage quotas, and have access to moderation tools.
+- Your password is the secret key that encrypts all of your documents and administrator privileges on this instance. If you lose it there is no way we can recover your data.
+- If you are using a shared computer, remember to log out when you are done. Only closing the browser window leaves your account exposed.
`;
+
+Messages.admin_appSelection = 'App configuration';
+Messages.admin_appsTitle = "Instance applications";
+Messages.admin_appsHint = "Choose which apps to enable on this instance.";
+Messages.admin_cat_apps = "Applications";
+
+Messages.onboarding_save_error = "Some options could not be saved properly. Please visit the administration panel to check the values.";
+Messages.onboarding_upload = "Select logo";
+Messages.admin_onboardingNameTitle = 'Welcome to your CryptPad instance';
+Messages.admin_onboardingNameHint = 'Please choose a title, description, accent color and logo (all are optional)';
+Messages.admin_onboardingOptionsTitle = "Instance options";
+Messages.admin_onboardingOptionsHint = "Please select the apropriate option for your instance. These settings can be changed later in the admin panel.";
+Messages.admin_onboardingNamePlaceholder = 'Instance title';
+Messages.admin_onboardingDescPlaceholder = 'Instance description text';
return Messages;
diff --git a/customize.dist/pages/install.js b/customize.dist/pages/install.js
index d6e8a1713..018d221b9 100644
--- a/customize.dist/pages/install.js
+++ b/customize.dist/pages/install.js
@@ -18,8 +18,6 @@ define([
return;
}
-Msg.install_token = "Install token";
-
document.title = Msg.install_header;
var frame = function (content) {
@@ -27,8 +25,7 @@ Msg.install_token = "Install token";
h('div#cp-main', [
//Pages.infopageTopbar(),
h('div.container.cp-container', [
- //h('div.row.cp-page-title', h('h1', Msg.install_header)),
- h('div.row.cp-page-title', h('h1', Msg.register_header)),
+ h('div.row.cp-page-title', h('h1', Msg.install_header)),
].concat(content)),
Pages.infopageFooter(),
]),
@@ -39,17 +36,12 @@ Msg.install_token = "Install token";
h('div.row.cp-register-det', [
h('div#data.hidden.col-md-6', [
h('h2', Msg.register_notes_title),
- //Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes)
- Pages.setHTML(h('div.cp-register-notes'), Msg.register_notes)
+ Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes)
]),
h('div.cp-reg-form.col-md-6', [
h('div#userForm.form-group.hidden', [
h('div.cp-register-instance', [
- Msg._getKey('register_instance', [ Pages.Instance.name ]),
- /*h('br'),
- h('a', {
- href: '/features.html'
- }, Msg.register_whyRegister)*/
+ Msg.install_instance,
]),
h('input.form-control#installtoken', {
type: 'text',
@@ -75,7 +67,7 @@ Msg.install_token = "Install token";
/*h('div.checkbox-container', [
UI.createCheckbox('import-recent', Msg.register_importRecent, true)
]),*/
- h('button#register', Msg.login_register)
+ h('button#register', Msg.install_launch)
])
]),
])
diff --git a/customize.dist/src/less2/include/admin.less b/customize.dist/src/less2/include/admin.less
new file mode 100644
index 000000000..13fe5360e
--- /dev/null
+++ b/customize.dist/src/less2/include/admin.less
@@ -0,0 +1,97 @@
+/*
+ * SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+@import (reference) "./colortheme-all.less";
+@import (reference) "./forms.less";
+@import (reference) './icon-colors.less';
+
+.admin_main() {
+ --LessLoader_require: LessLoader_currentFile();
+}
+& {
+ // XXX Instance accent color presets
+ @palette-colors:
+ @cryptpad_color_brand,
+ #ffee00,
+ #FF0073;
+
+ div.cp-palette-container {
+
+ .cp-palette-nocolor {
+ display: none;
+ }
+ .instance-colors(@palette-colors; @index) when (@index > 0){
+ // loop through the @colors
+ .instance-colors(@palette-colors; (@index - 1));
+
+ @color: extract(@palette-colors, @index);
+ // make a numbered class selector for each color
+ .cp-palette-color@{index}{
+ background-color: @color !important;
+ color: contrast(@color, @cryptpad_color_grey_800, @cryptpad_color_grey_200) !important;
+ }
+ }
+ .instance-colors(@palette-colors; length(@palette-colors));
+ }
+
+ .cp-admin-customize-apps-grid, .cp-admin-customize-options-grid {
+ display: grid;
+ gap: 0.5rem;
+ }
+
+ .cp-admin-customize-apps-grid {
+ grid-template-columns: 1fr 1fr 1fr;
+ .cp-appblock {
+ padding: 0.5rem;
+ border-radius: @variables_radius;
+ font-size: 1.2em;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.75rem;
+ .iconColors_main();
+ &:hover {
+ cursor: pointer;
+ }
+ i.cp-icon {
+ font-size: 2.8rem;
+ }
+ .cp-app-name {
+ flex-grow: 1;
+ }
+ }
+ .cp-inactive-app {
+ background-color: transparent;
+ opacity: 0.75;
+ .cp-on-enabled {
+ visibility: hidden;
+ }
+ }
+ .cp-active-app {
+ background-color: fade(@cryptpad_text_col, 10%);
+ .cp-on-enabled {
+ visibility: visible;
+ }
+ }
+ }
+
+ .cp-admin-customize-options-grid {
+ grid-template-columns: 1fr 1fr;
+ .cp-optionblock {
+ padding: 0.5rem;
+ border-radius: @variables_radius;
+ background-color: fade(@cryptpad_text_col, 10%);
+ align-self: start;
+ .cp-checkmark-label {
+ font-weight: bold;
+ }
+ .cp-option-hint {
+ margin-left: 30px;
+ display: inline-block;
+ }
+ }
+ }
+}
diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less
index 3c018e365..9f0ec1c15 100644
--- a/customize.dist/src/less2/include/forms.less
+++ b/customize.dist/src/less2/include/forms.less
@@ -119,6 +119,31 @@
}
}
+ // The following palette container is just for the UI components
+ // The specific colors you want to show have to be defined in your app
+ // using the classes .cp-palette-nocolor .cp-palette-color1 .cp-palette-color2 etc.
+ div.cp-palette-container {
+ display: flex;
+ justify-content: space-between;
+ .cp-palette-color {
+ display: inline-block;
+ border-radius: 50%;
+ height: 30px;
+ width: 30px;
+ text-align: center;
+ line-height: 30px;
+ color: @cp_kanban-fg;
+ border: 1px solid fade(@cp_kanban-fg, 40%);
+ &.fa-check { // tick on selected color
+ color: @cryptpad_text_col;
+ }
+ outline: none;
+ &:focus{
+ outline: @cryptpad_color_brand solid 2px;
+ }
+ }
+ }
+
button.btn {
background-color: @cp_buttons-cancel;
box-sizing: border-box;
diff --git a/lib/api.js b/lib/api.js
index 220e954d8..b3beddf3d 100644
--- a/lib/api.js
+++ b/lib/api.js
@@ -10,6 +10,7 @@ const nThen = require("nthen");
const Fs = require("fs");
const Path = require("path");
const Nacl = require("tweetnacl/nacl-fast");
+const Hash = require('./common-hash');
module.exports.create = function (Env) {
var log = Env.Log;
@@ -25,6 +26,39 @@ nThen(function (w) {
console.error(err);
}
}));
+}).nThen(function (w) {
+ let admins = Env.admins || [];
+
+ // If we don't have any admin on this instance, print an onboarding link
+ if (Array.isArray(admins) && admins.length) { return; }
+ let token = Env.installToken;
+ let printLink = () => {
+ let url = `${Env.httpUnsafeOrigin}/install/#${token}`;
+ console.log('=============================');
+ console.log('Create your first admin account and customize your instance by visiting');
+ console.log(url);
+ console.log('=============================');
+
+ };
+
+ // If we already have a token, print it
+ if (token) { return void printLink(); }
+
+ // Otherwise create a new token
+ let decreeName = Path.join(Env.paths.decree, 'decree.ndjson');
+ token = Hash.createChannelId() + Hash.createChannelId();
+ let decree = ["ADD_INSTALL_TOKEN",[token],"",+new Date()];
+ Fs.appendFile(decreeName, JSON.stringify(decree) + '\n', w(function (err) {
+ if (err) { console.log(err); return; }
+ printLink();
+ }));
+}).nThen(function (w) {
+ if (!Env.admins.length) {
+ Env.Log.info('NO_ADMIN_CONFIGURED', {
+ message: `Your instance is not correctly configured for production usage. Review its checkup page for more information.`,
+ details: new URL('/checkup/', Env.httpUnsafeOrigin).href,
+ });
+ }
}).nThen(function (w) {
// we assume the server has generated a secret used to validate JWT tokens
if (typeof(Env.bearerSecret) === 'string') { return; }
diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js
index bce956147..4f5f8af6e 100644
--- a/lib/commands/admin-rpc.js
+++ b/lib/commands/admin-rpc.js
@@ -466,6 +466,8 @@ var setLastEviction = function (Env, Server, cb, data, unsafeKey) {
// CryptPad_AsyncStore.rpc.send('ADMIN', ['INSTANCE_STATUS], console.log)
var instanceStatus = function (Env, Server, cb) {
cb(void 0, {
+
+ appsToDisable: Env.appsToDisable,
restrictRegistration: Env.restrictRegistration,
restrictSsoRegistration: Env.restrictSsoRegistration,
dontStoreSSOUsers: Env.dontStoreSSOUsers,
diff --git a/lib/common-hash.js b/lib/common-hash.js
new file mode 100644
index 000000000..7a3beddab
--- /dev/null
+++ b/lib/common-hash.js
@@ -0,0 +1,6 @@
+// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+module.exports = require("../www/common/common-hash");
+
diff --git a/lib/decrees.js b/lib/decrees.js
index 317d492df..6eea60250 100644
--- a/lib/decrees.js
+++ b/lib/decrees.js
@@ -220,6 +220,15 @@ commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (arg
return args_isString(args) && Core.isValidPublicKey(args[0]);
});
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SUPPORT_KEYS', ["Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA=", "Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA="]]], console.log)
+
+
+commands.DISABLE_APPS = function (Env, args) {
+ if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
+ const appsToDisable = args;
+ Env.appsToDisable = appsToDisable;
+ return true;
+};
+
commands.SET_SUPPORT_KEYS = function (Env, args) {
const curvePublic = args[0]; // Support mailbox key
const edPublic = args[1]; // Support pin log
@@ -232,7 +241,7 @@ commands.SET_SUPPORT_KEYS = function (Env, args) {
Env.supportMailboxKey = curvePublic;
Env.supportPinKey = edPublic;
return true;
-};
+ };
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_PURPOSE', ["development"]]], console.log)
commands.SET_INSTANCE_PURPOSE = makeGenericSetter('instancePurpose', args_isString);
@@ -290,7 +299,7 @@ var args_isMaintenance = function (args) {
// whenever that happens we can relax validation a bit to support more formats
var makeBroadcastSetter = function (attr, validation) {
return function (Env, args) {
- if ((validation && !validation(args)) && !args_isString(args)) {
+ if ((validation && !validation(args)) && !args_isString(args)) {
throw new Error('INVALID_ARGS');
}
var str = args[0];
diff --git a/lib/http-worker.js b/lib/http-worker.js
index f84658a34..227122765 100644
--- a/lib/http-worker.js
+++ b/lib/http-worker.js
@@ -590,6 +590,7 @@ var serveConfig = makeRouteCache(function () {
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
restrictRegistration: Env.restrictRegistration,
+ appsToDisable: Env.appsToDisable,
restrictSsoRegistration: Env.restrictSsoRegistration,
httpSafeOrigin: Env.httpSafeOrigin,
enableEmbedding: Env.enableEmbedding,
diff --git a/lib/stats.js b/lib/stats.js
index d67a2ee71..e3c8e502d 100644
--- a/lib/stats.js
+++ b/lib/stats.js
@@ -82,7 +82,6 @@ Stats.instanceData = function (Env) {
// check how many instances provide stats before we put more work into it
data.providesAggregateStatistics = true;
}
-
return data;
};
diff --git a/scripts/install.js b/scripts/install.js
index 86a0d0b75..4fb06a4ed 100644
--- a/scripts/install.js
+++ b/scripts/install.js
@@ -40,5 +40,4 @@ nThen(function (w) {
console.log(token);
var url = config.httpUnsafeOrigin + '/install/';
console.log(`Please visit ${url} to create your first admin user`);
-
});
diff --git a/server.js b/server.js
index e84bec70b..f240b1cd9 100644
--- a/server.js
+++ b/server.js
@@ -68,13 +68,6 @@ nThen(function (w) {
Env.Log.info("WEBSERVER_LISTENING", {
origin: url,
});
-
- if (!Env.admins.length) {
- Env.Log.info('NO_ADMIN_CONFIGURED', {
- message: `Your instance is not correctly configured for production usage. Review its checkup page for more information.`,
- details: new URL('/checkup/', Env.httpUnsafeOrigin).href,
- });
- }
} catch (err) {
Env.Log.error("INVALID_ORIGIN", {
httpUnsafeOrigin: Env.httpUnsafeOrigin,
diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less
index 2e49e3dec..fbaaf4b5e 100644
--- a/www/admin/app-admin.less
+++ b/www/admin/app-admin.less
@@ -9,12 +9,14 @@
@import (reference) "../../customize/src/less2/include/creation.less";
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/export.less';
+@import (reference) '../../customize/src/less2/include/admin.less';
&.cp-app-admin {
.framework_min_main();
.sidebar-layout_main();
.limit-bar_main();
.creation_main();
+ .admin_main();
display: flex;
flex-flow: column;
@@ -32,6 +34,16 @@
border-radius: 5px;
background-color: @cryptpad_color_brand;
}
+ input.cp-admin-color-picker {
+ vertical-align: middle;
+ }
+ .cp-palette-container {
+ display: inline-flex;
+ width: ~"calc(100% - 5rem)";
+ padding-left: 0.5rem;
+ vertical-align: middle;
+ }
+
.cp-admin-color-preview {
& > div {
margin-top: @sidebar_base-margin;
diff --git a/www/admin/inner.js b/www/admin/inner.js
index 595dbf4dd..bbb92d3db 100644
--- a/www/admin/inner.js
+++ b/www/admin/inner.js
@@ -5,6 +5,7 @@
define([
'jquery',
'/common/toolbar.js',
+ '/common/pad-types.js',
'/components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-interface.js',
@@ -17,11 +18,11 @@ define([
'/common/hyperscript.js',
'/common/clipboard.js',
'json.sortify',
- '/customize/application_config.js',
'/api/config',
'/api/instance',
'/lib/datepicker/flatpickr.js',
- '/common/hyperscript.js',
+ '/install/onboardscreen.js',
+
'css!/lib/datepicker/flatpickr.min.css',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
'css!/components/components-font-awesome/css/font-awesome.min.css',
@@ -29,6 +30,7 @@ define([
], function(
$,
Toolbar,
+ PadTypes,
nThen,
SFCommon,
UI,
@@ -41,11 +43,12 @@ define([
h,
Clipboard,
Sortify,
- AppConfig,
ApiConfig,
Instance,
- Flatpickr
+ Flatpickr,
+ Onboarding,
) {
+
var APP = window.APP = {};
var Nacl = window.nacl;
@@ -90,6 +93,12 @@ define([
'forcemfa',
]
},
+ 'apps': { // Msg.admin_cat_apps
+ icon: 'fa fa-wrench',
+ content: [
+ 'apps',
+ ]
+ },
'users' : { // Msg.admin_cat_users
icon : 'fa fa-address-card-o',
content : [
@@ -160,7 +169,7 @@ define([
}
};
- const blocks = sidebar.blocks;
+ const blocks = Sidebar.blocks('admin');
const flushCache = (cb) => {
cb = cb || function () {};
@@ -190,6 +199,7 @@ define([
// Msg.admin_flushCacheHint, .admin_flushCacheTitle, .admin_flushCacheButton
sidebar.addItem('flush-cache', function (cb) {
+ const blocks = Sidebar.blocks('admin');
var button = blocks.activeButton('primary', '',
Messages.admin_flushCacheButton, done => {
flushCache(function (e, data) {
@@ -609,7 +619,6 @@ define([
UI.log(Messages._getKey('ui_saved', [Messages.admin_emailTitle]));
});
});
-
var nav = blocks.nav([button]);
var form = blocks.form([
@@ -621,6 +630,35 @@ define([
cb(form);
});
+ sidebar.addItem('apps', function (cb) {
+ const appsToDisable = ApiConfig.appsToDisable || [];
+ const grid = Onboarding.createAppsGrid(appsToDisable);
+
+ var save = blocks.activeButton('primary', '', Messages.settings_save, function (done) {
+ sFrameChan.query('Q_ADMIN_RPC', {
+ cmd: 'ADMIN_DECREE',
+ data: ['DISABLE_APPS', appsToDisable]
+ }, function (e, response) {
+ if (e || response.error) {
+ UI.warn(Messages.error);
+ console.error(e, response);
+ done(false);
+ return;
+ }
+ flushCache();
+ done(true);
+ UI.log(Messages._getKey('ui_saved', [Messages.admin_appSelection]));
+ });
+ });
+
+ let form = blocks.form([
+ grid
+ ], blocks.nav([save]));
+
+ cb(form);
+ });
+
+
sidebar.addItem('instance-info-notice', function(cb){
var key = 'instance-info-notice';
var notice = blocks.alert('info', key, [Messages.admin_infoNotice1, ' ', Messages.admin_infoNotice2]);
@@ -905,7 +943,7 @@ define([
setColor(color, done);
});
- let $input = $(input).on('change', () => {
+ let onColorPicked = () => {
require(['/lib/less.min.js'], (Less) => {
let color = $input.val();
let lColor = Less.color(color.slice(1));
@@ -925,7 +963,8 @@ define([
$preview.find('.cp-admin-color-preview-dark a').attr('style', `color: ${lightColor} !important`);
$preview.find('.cp-admin-color-preview-light a').attr('style', `color: ${color} !important`);
});
- });
+ };
+ let $input = $(input).on('change', onColorPicked).addClass('cp-admin-color-picker');
UI.confirmButton($remove, {
classes: 'btn-danger',
@@ -935,9 +974,19 @@ define([
setColor('', () => {});
});
+ var colors = UIElements.makePalette(8, (color, $color) => {
+ // onselect
+ let rgb = $color.css('background-color');
+ let hex = Util.rgbToHex(rgb);
+ $input.val(hex);
+ onColorPicked();
+ });
+ var $colors = $(colors);
+
+ $(label).append(colors);
let form = blocks.form([
labelCurrent,
- label
+ label,
], blocks.nav([btn, remove, btn.spinner]));
cb([form, labelPreview]);
diff --git a/www/common/common-interface.js b/www/common/common-interface.js
index 26584da30..d4f9ea7c5 100644
--- a/www/common/common-interface.js
+++ b/www/common/common-interface.js
@@ -1119,6 +1119,21 @@ define([
}
};
+ UI.getNewIcon = function (type) {
+ var icon = h('i.fa.fa-file-text-o');
+
+ if (AppConfig.applicationsIcon && AppConfig.applicationsIcon[type]) {
+ var icon = AppConfig.applicationsIcon[type];
+ var font = icon.indexOf('cptools') === 0 ? 'cptools' : 'fa';
+ if (type === 'fileupload') { type = 'file'; }
+ if (type === 'folderupload') { type = 'file'; }
+ if (type === 'link') { type = 'drive'; }
+ var appClass = ' cp-icon cp-icon-color-'+type;
+ icon = h('i', {'class': font + ' ' + icon + appClass});
+ }
+
+ return icon;
+ };
var $defaultIcon = $('', {"class": "fa fa-file-text-o"});
UI.getIcon = function (type) {
var $icon = $defaultIcon.clone();
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 8fc65140c..4326cd534 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -4297,5 +4297,76 @@ define([
return UI.errorLoadingScreen(msg, false, false);
};
+ UIElements.makePalette = (maxColors, onSelect) => {
+ let palette = [''];
+ for (var i=1; i<=maxColors; i++) { palette.push('color'+i); }
+
+ let offline = false;
+ let selectedColor = '';
+ let container = h('div.cp-palette-container');
+ let $container = $(container);
+
+ var all = [];
+ palette.forEach(function (color, i) {
+ var $color = $(h('button.cp-palette-color.fa'));
+ all.push($color);
+ $color.addClass('cp-palette-'+(color || 'nocolor'));
+ $color.keydown(function (e) {
+ if (e.which === 13) {
+ e.stopPropagation();
+ e.preventDefault();
+ $color.click();
+ }
+ });
+ $color.click(function () {
+ if (offline) { return; }
+ if (color === selectedColor) { return; }
+ selectedColor = color;
+ $container.find('.cp-palette-color').removeClass('fa-check');
+ $color.addClass('fa-check');
+ onSelect(color, $color);
+ }).appendTo($container);
+ $color.keydown(e => {
+ if (e.which === 37) {
+ e.preventDefault();
+ if (i === 0) {
+ all[all.length - 1].focus();
+ } else {
+ all[i - 1].focus();
+ }
+ }
+ if (e.which === 39) {
+ e.preventDefault();
+ if (i === (all.length - 1)) {
+ all[0].focus();
+ } else {
+ all[i + 1].focus();
+ }
+ }
+ if (e.which === 9) {
+ if (e.shiftKey) {
+ all[0].focus();
+ return;
+ }
+ all[all.length - 1].focus();
+ }
+ });
+ });
+
+ container.disable = state => {
+ offline = !!state;
+ };
+ container.getValue = () => {
+ return selectedColor;
+ };
+ container.setValue = color => {
+ $container.find('.cp-palette-color').removeClass('fa-check');
+ let $color = $container.find('.cp-palette-'+(color || 'nocolor'));
+ $color.addClass('fa-check');
+ selectedColor = color;
+ };
+ return container;
+ };
+
return UIElements;
});
diff --git a/www/common/common-util.js b/www/common/common-util.js
index fd96f12c4..5faa9171e 100644
--- a/www/common/common-util.js
+++ b/www/common/common-util.js
@@ -606,6 +606,9 @@
parseInt(h.slice(4,6), 16),
];
};
+ Util.rgbToHex = function (rgb) {
+ return `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`;
+ };
Util.isSmallScreen = function () {
return window.innerHeight < 800 || window.innerWidth < 800;
diff --git a/www/common/inner/sidebar-layout.js b/www/common/inner/sidebar-layout.js
index f6adcb66c..9df1ac220 100644
--- a/www/common/inner/sidebar-layout.js
+++ b/www/common/inner/sidebar-layout.js
@@ -22,17 +22,14 @@ define([
h
) {
const Sidebar = {};
+ const keyToCamlCase = (key) => {
+ return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
+ };
- Sidebar.create = function (common, app, $container) {
- const $leftside = $(h('div#cp-sidebarlayout-leftside')).appendTo($container);
- const $rightside = $(h('div#cp-sidebarlayout-rightside')).appendTo($container);
- const sidebar = {
- $leftside,
- $rightside
- };
- const items = {};
+ Sidebar.blocks = function (app) {
+
+ let blocks = {};
- let blocks = sidebar.blocks = {};
blocks.labelledInput = (label, input, inputBlock) => {
let uid = Util.uid();
let id = `cp-${app}-item-${uid}`;
@@ -109,7 +106,7 @@ define([
element
]);
};
- blocks.pre = (value) => {
+ blocks.pre = (value) => {
return h('pre', value);
};
@@ -220,9 +217,7 @@ define([
return button;
};
- const keyToCamlCase = (key) => {
- return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
- };
+
blocks.activeCheckbox = (data) => {
const state = data.getState();
const key = data.key;
@@ -246,6 +241,20 @@ define([
return box;
};
+ return blocks;
+
+ };
+
+ Sidebar.create = function (common, app, $container) {
+ const $leftside = $(h('div#cp-sidebarlayout-leftside')).appendTo($container);
+ const $rightside = $(h('div#cp-sidebarlayout-rightside')).appendTo($container);
+ const sidebar = {
+ $leftside,
+ $rightside
+ };
+ const items = {};
+ sidebar.blocks = Sidebar.blocks(app);
+
sidebar.addItem = (key, get, options) => {
const safeKey = keyToCamlCase(key);
get((content) => {
@@ -274,6 +283,7 @@ define([
sidebar.addCheckboxItem = (data) => {
const key = data.key;
+ let blocks = sidebar.blocks;
let box = blocks.activeCheckbox(data);
sidebar.addItem(key, function (cb) {
cb(box);
diff --git a/www/common/pad-types.js b/www/common/pad-types.js
index 86390e885..78adb56df 100644
--- a/www/common/pad-types.js
+++ b/www/common/pad-types.js
@@ -12,12 +12,22 @@ define([
OOCurrentVersion.currentVersion,
);
- let availableTypes = AppConfig.availablePadTypes.filter(
- (t) => ooEnabled || !OO_APPS.includes(t),
+ let availablePadTypes = AppConfig.availablePadTypes.filter(
+ (t) => ooEnabled || !OO_APPS.includes(t)
);
+ let availableTypes;
+ if (ApiConfig.appsToDisable) {
+ availableTypes = availablePadTypes.filter(value => !ApiConfig.appsToDisable.includes(value));
+ } else {
+ availableTypes = availablePadTypes;
+ }
+
+ var appsToSelect = availablePadTypes.filter(value => !['drive', 'teams', 'file', 'contacts', 'convert'].includes(value));
+
return {
availableTypes,
+ appsToSelect,
isAvailable: function (type) {
return availableTypes.includes(type);
diff --git a/www/install/main.js b/www/install/main.js
index fffb7343d..b7735a321 100644
--- a/www/install/main.js
+++ b/www/install/main.js
@@ -16,13 +16,17 @@ define([
'/common/hyperscript.js',
'/customize/pages.js',
'/common/rpc.js',
+ 'onboardscreen.js',
+ '/common/inner/sidebar-layout.js',
+ 'less!/install/onboarding.less',
'css!/components/components-font-awesome/css/font-awesome.min.css',
-], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages, Rpc) {
+], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages, Rpc, OnboardScreen, Sidebar) {
if (window.top !== window) { return; }
var Messages = Cryptpad.Messages;
$(function () {
- if (LocalStore.isLoggedIn()) {
+
+ if (LocalStore.isLoggedIn() && !localStorage.CP_dev) {
// already logged in, redirect to drive
document.location.href = '/drive/';
return;
@@ -55,10 +59,14 @@ define([
}
}
+ var showTitleScreen = function (sendAdminDecree, sendAdminRpc) {
+ OnboardScreen.create(sendAdminDecree, sendAdminRpc);
+ };
+
var registerClick = function () {
var uname = $uname.val().trim();
- // trim whitespace surrounding the username since it is otherwise included in key derivation
- // most people won't realize that its presence is significant
+ // trim whitespace surrounding the username since it is otherwise included in key derivation
+ // most people won't realize that its presence is significant
$uname.val(uname);
var passwd = $passwd.val();
@@ -112,6 +120,27 @@ define([
return void UI.alert(Messages.register_mustAcceptTerms);
}
+ let startOnboarding = function (network, proxy) {
+ Rpc.create(network, proxy.edPrivate, proxy.edPublic, function (e, rpc) {
+ if (e) {
+ // TODO: handle error
+ return;
+ }
+
+ let sendAdminDecree = function (command, data, callback) {
+ var params = ['ADMIN_DECREE', [command, data]];
+ rpc.send('ADMIN', params, callback);
+ };
+
+ let sendAdminRpc = function (command, data, callback) {
+ var params = [command, data];
+ rpc.send('ADMIN', params, callback);
+ };
+
+ showTitleScreen(sendAdminDecree, sendAdminRpc);
+ });
+ };
+
setTimeout(function () {
var span = h('span', [
h('h2', [
@@ -145,7 +174,7 @@ define([
edPublic: proxy.edPublic
}, function (e) {
if (e) { UI.alert(Messages.error); return console.error(e); }
- window.location.href = '/drive/';
+ startOnboarding(data.network, proxy);
});
});
diff --git a/www/install/onboarding.less b/www/install/onboarding.less
new file mode 100644
index 000000000..d0f2c33ac
--- /dev/null
+++ b/www/install/onboarding.less
@@ -0,0 +1,119 @@
+@import (reference) "../../customize/src/less2/include/browser.less";
+@import (reference) "../../customize/src/less2/include/framework.less";
+@import (reference) "../../customize/src/less2/include/tools.less";
+@import (reference) "../../customize/src/less2/include/markdown.less";
+@import (reference) "../../customize/src/less2/include/avatar.less";
+@import (reference) '../../customize/src/less2/include/admin.less';
+@import (reference) '../../customize/src/less2/include/icon-colors.less';
+
+&.cp-page-install {
+ .admin_main();
+ div.cp-palette-container {
+ max-width: 400px;
+ }
+ #cp-onboarding {
+ position: fixed;
+ z-index: 1000;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ background-color: @cp_loading-bg;
+ color: @cp_loading-fg;
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+ .cp-onboarding-box {
+ width: 850px;
+ background-color: @cp_loading-progress-bg;
+ border-radius: @variables_radius_L;
+ box-shadow: 0px 0px 10px 0px @cp_shadow-color;
+ padding: 30px 110px 20px 110px;
+ }
+ .cp-onboarding-logo-input {
+ display: none;
+ }
+ .cp-onboardscreen-logo {
+ min-width: 200px;
+ max-width: 200px;
+ nav {
+ display: flex;
+ margin-top: 0.5rem;
+ justify-content: center;
+ }
+ }
+ }
+}
+
+.configscreen {
+ //width: 70%;
+ //height: 75%;
+ background-color: @cp_loading-progress-bg;
+ border-radius: @variables_radius_L;
+ box-shadow: 0px 0px 10px 0px @cp_shadow-color;
+ padding: 30px 110px 20px 110px;
+}
+
+.cp-onboardscreen-screentitle {
+ text-align: center;
+ font-size: 1rem;
+ margin-bottom: 1rem;
+ h1.cp-onboardscreen-title {
+ font-family: "IBM Plex Mono";
+ color: @cryptpad_color_link;
+ font-size: 1.5rem;
+ font-weight: 500;
+ }
+}
+
+.cp-instance-form {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ input, textarea {
+ border: 0px transparent !important;
+ }
+ .cp-onboardscreen-logo {
+ .cp-sidebar-form {
+ background-color: @cp_forms-bg;
+ border-radius: @variables_radius;
+ padding: 0.5rem;
+ .cp-admin-customize-logo {
+ img {
+ max-width: 100%;
+ }
+ }
+ }
+ }
+ .cp-instance-text-form {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ .cp-onboardscreen-name {
+ input {
+ font-family: "IBM Plex Mono";
+ font-size: 1.2em;
+ }
+ }
+ .cp-onboardscreen-colorpick {
+ div.cp-palette-container {
+ display: flex;
+ justify-content: flex-start;
+ .cp-palette-color {
+ margin-right: 1rem;
+ }
+ }
+ }
+ }
+}
+
+
+nav.cp-onboardscreen-nav {
+ width: 100%;
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ margin-top: 1rem;
+}
\ No newline at end of file
diff --git a/www/install/onboardscreen.js b/www/install/onboardscreen.js
new file mode 100644
index 000000000..6272da418
--- /dev/null
+++ b/www/install/onboardscreen.js
@@ -0,0 +1,498 @@
+define([
+ 'jquery',
+ '/common/inner/sidebar-layout.js',
+ '/customize/messages.js',
+ '/customize/application_config.js',
+ '/common/hyperscript.js',
+ '/common/common-interface.js',
+ '/common/common-util.js',
+ '/common/common-ui-elements.js',
+ '/common/pad-types.js',
+ '/components/nthen/index.js',
+
+ 'css!/components/bootstrap/dist/css/bootstrap.min.css',
+ 'css!/components/components-font-awesome/css/font-awesome.min.css',
+
+], function(
+ $,
+ Sidebar,
+ Messages,
+ AppConfig,
+ h,
+ UI,
+ Util,
+ UIElements,
+ PadTypes,
+ nThen
+) {
+
+ let pages = [];
+ const gotoPage = function (Env, page) {
+ if (typeof(page) !== "number" || !page) { page = 0; }
+ if (page > (pages.length - 1)) { page = pages.length - 1; }
+
+ var nextPageFunction = pages[page];
+ var nextPageForm = nextPageFunction(Env);
+ let frame = h('div.cp-onboarding-box', nextPageForm);
+ Env.overlay.empty().append(frame);
+ };
+ const blocks = Sidebar.blocks('admin');
+
+ var flushCache = (Env, cb) => {
+ const { sendAdminRpc } = Env;
+ sendAdminRpc('FLUSH_CACHE', {}, function (e, response) {
+ if (e || response.error) {
+ console.error(e || response.error);
+ }
+ cb();
+ });
+ };
+
+ var selections = {
+ title: '',
+ description: '',
+ logoURL: '',
+ color: '',
+ appsToDisable: [],
+ mfa: false,
+ closeRegistration: false
+ };
+
+ const saveAndRedirect = (Env) => {
+ const { sendAdminDecree, sendAdminRpc } = Env;
+
+ let hasError = false;
+ let error = () => {
+ hasError = true;
+ };
+ nThen(waitFor => {
+ var name = selections.title;
+ if (!name) { return; }
+ sendAdminDecree('SET_INSTANCE_NAME', [name], waitFor((e, response) => {
+ if (e || response.error) {
+ console.error(e || response.error);
+ return void error();
+ }
+ }));
+ }).nThen(waitFor => {
+ var description = selections.description;
+ if (!description) { return; }
+ sendAdminDecree('SET_INSTANCE_DESCRIPTION', [
+ description
+ ], waitFor((e, response) => {
+ if (e || response.error) {
+ console.error(e || response.error);
+ return void error();
+ }
+
+ }));
+ }).nThen(waitFor => {
+ var dataURL = selections.logoURL;
+ if (!dataURL) { return; }
+ sendAdminRpc('UPLOAD_LOGO', {dataURL}, waitFor(function (e, response) {
+ if (e || response.error) {
+ console.error(e || response.error);
+ return void error();
+ }
+ }));
+ }).nThen(waitFor => {
+ var color = selections.color;
+ if (!color) { return; }
+ sendAdminRpc('CHANGE_COLOR', {color}, waitFor(function (e, response) {
+ if (e || response.error) {
+ console.error(e || response.error);
+ return void error();
+ }
+ }));
+ }).nThen(waitFor => {
+ var apps = selections.appsToDisable;
+ if (!apps.length) { return; }
+ sendAdminDecree('DISABLE_APPS', apps, waitFor(function (e, response) {
+ if (e || response.error) {
+ UI.warn(Messages.error);
+ return;
+ }
+ }));
+ }).nThen(waitFor => {
+ if (!selections.mfa) { return; }
+ sendAdminDecree('ENFORCE_MFA', [selections.mfa], waitFor((e, response) => {
+ if (e || response.error) {
+ UI.warn(Messages.error);
+ return;
+ }
+ }));
+ }).nThen(waitFor => {
+ if (!selections.closeRegistration) { return; }
+ sendAdminDecree('RESTRICT_REGISTRATION', [
+ selections.closeRegistration
+ ], waitFor(function (e, response) {
+ if (e || response.error) {
+ UI.warn(Messages.error);
+ return;
+ }
+ }));
+ }).nThen(() => {
+ flushCache(Env, function () {
+ if (hasError) {
+ UI.alert(Messages.onboarding_save_error, function () {
+ document.location.href = '/drive/';
+ })
+ return;
+ }
+ document.location.href = '/drive/';
+ });
+ });
+ };
+
+ const titleConfig = function (Env) {
+ let titleInput = blocks.input({
+ type: 'text',
+ value: selections.title,
+ placeholder: Messages.admin_onboardingNamePlaceholder,
+ 'aria-labelledby': 'cp-admin-name'
+ });
+ let description = blocks.textarea({
+ placeholder: Messages.admin_onboardingDescPlaceholder,
+ 'aria-labelledby': 'cp-admin-description'
+ }, selections.description);
+ $(description).addClass('cp-onboardscreen-desc');
+
+ let dataURL = selections.logoURL;
+ var getLogoBlock = function() {
+ let inputLogo = blocks.input({
+ type: 'file',
+ accept: 'image/*',
+ 'aria-labelledby': 'cp-admin-logo'
+ });
+
+ var currentContainer = blocks.block([], 'cp-admin-customize-logo');
+
+ var upload = blocks.button('secondary', '', Messages.onboarding_upload);
+
+ let formLogo = blocks.form([
+ currentContainer,
+ blocks.block(inputLogo, 'cp-onboarding-logo-input'),
+ blocks.nav([upload])
+ ]);
+
+ let $button = $(upload);
+
+ let state = false;
+ let redraw = () => {
+ var current = h('img', {src: dataURL || '/api/logo?'+(+new Date())});
+ $(currentContainer).empty().append(current);
+ $button.removeAttr('disabled');
+ state = !!dataURL;
+ if (dataURL) {
+ $button.text(Messages.admin_logoRemoveButton);
+ $button.removeClass('btn-secondary').addClass('btn-danger-alt');
+ } else {
+ $button.text(Messages.onboarding_upload);
+ $button.removeClass('btn-danger-alt').addClass('btn-secondary');
+ }
+ };
+ redraw();
+
+ let $input = $(inputLogo);
+ $input.on('change', function () {
+ let files = inputLogo.files;
+ if (files.length !== 1) {
+ UI.warn(Messages.error);
+ return;
+ }
+ let reader = new FileReader();
+ reader.onloadend = function () {
+ dataURL = this.result;
+ redraw(dataURL);
+ };
+ reader.readAsDataURL(files[0]);
+ });
+ Util.onClickEnter($button, function () {
+ $button.attr('disabled', 'disabled');
+ if (!state) {
+ return void $input.click();
+ }
+ dataURL = '';
+ redraw();
+ });
+
+ return formLogo;
+ };
+
+ var getColorBlock = function () {
+
+ // XXX Number of accent color presets
+ var colors = UIElements.makePalette(5, (color, $color) => {
+ let rgb = $color.css('background-color');
+ let hex = Util.rgbToHex(rgb);
+ if (hex) {
+ selections.color = color ? hex : '';
+ selections.colorId = color;
+ }
+ });
+ if (selections.colorId) {
+ colors.setValue(selections.colorId);
+ }
+ var $colors = $(colors).attr('id', 'cp-install-color');
+ var content = h('div.cp-onboardscreen-colorpick', [
+ h('label', {for:'cp-install-color'}, Messages.kanban_color),
+ colors
+ ]);
+
+ return content;
+ };
+
+ var button = blocks.activeButton('primary', '', Messages.continue, function (done) {
+ selections.title = $(titleInput).val() || '';
+ selections.description = $(description).val() || '';
+ if (dataURL) {
+ selections.logoURL = dataURL;
+ }
+
+ gotoPage(Env, 1);
+ });
+
+ var titleBlock = h('div.cp-onboardscreen-name', titleInput);
+ var descriptionBlock = h('div', description);
+ var logoBlock = h('div.cp-onboardscreen-logo', getLogoBlock());
+ var colorBlock = h('div.cp-onboardscreen-color', getColorBlock());
+
+ var screenTitle = h('div.cp-onboardscreen-screentitle');
+ $(screenTitle).append(('div.cp-onboardscreen-maintitle', [
+ h('h1.cp-onboardscreen-title', Messages.admin_onboardingNameTitle),
+ h('span', Messages.admin_onboardingNameHint)
+ ]));
+ var nav = blocks.nav([h('span'), button]);
+
+ $(button).addClass('cp-onboardscreen-save');
+ $(nav).addClass('cp-onboardscreen-nav');
+
+ var textForm = h('div.cp-instance-text-form', [
+ titleBlock,
+ descriptionBlock,
+ colorBlock,
+ ]);
+
+ var instanceForm = h('div.cp-instance-form', [logoBlock, textForm]);
+
+ var form = blocks.form([
+ screenTitle,
+ instanceForm
+ ], nav);
+
+ $(form).addClass('cp-onboardscreen-form');
+
+ return form;
+
+ };
+
+ const createAppsGrid = appsToDisable => {
+ const grid = blocks.block([], 'cp-admin-customize-apps-grid');
+ const $grid = $(grid);
+ const allApps = PadTypes.appsToSelect;
+
+ let select = function (app, $app) {
+ if (appsToDisable.indexOf(app) === -1) {
+ appsToDisable.push(app);
+ $app.toggleClass('cp-inactive-app', true);
+ $app.toggleClass('cp-active-app', false);
+ } else {
+ appsToDisable.splice(appsToDisable.indexOf(app), 1);
+ $app.toggleClass('cp-inactive-app', false);
+ $app.toggleClass('cp-active-app', true);
+ }
+ };
+
+ allApps.forEach(app => {
+ let name = Messages.type[app] || app;
+ let icon = UI.getNewIcon(app);
+ let appBlock = h('div.cp-appblock',
+ {tabindex:0, role:"button"},
+ [
+ icon,
+ h('span.cp-app-name', name),
+ h('i.fa.fa-check.cp-on-enabled')
+ ]);
+ let $app = $(appBlock).appendTo($grid);
+ if (appsToDisable.includes(app)) {
+ $app.addClass('cp-inactive-app');
+ } else {
+ $app.addClass('cp-active-app');
+ }
+ $app.on('click', () => select(app, $app));
+ });
+
+ return grid;
+ };
+
+ const appConfig = function (Env) {
+ const appsToDisable = selections.appsToDisable;
+ const grid = createAppsGrid(appsToDisable);
+
+
+ var save = blocks.activeButton('primary', '', Messages.continue, function (done) {
+ gotoPage(Env, 2);
+ });
+
+ var prev = blocks.activeButton('secondary', '', Messages.form_backButton, function () {
+ gotoPage(Env, 0);
+ });
+
+ var screenTitle = h('div.cp-onboardscreen-screentitle');
+ $(screenTitle).append(h('div.cp-onboardscreen-maintitle', h('h1.cp-onboardscreen-title', Messages.admin_appsTitle), h('span', Messages.admin_appsHint)));
+ $(save).addClass('cp-onboardscreen-save');
+ $(prev).addClass('cp-onboardscreen-prev');
+ var nav = blocks.nav([prev, save]);
+ $(nav).addClass('cp-onboardscreen-nav');
+ let form = blocks.form([
+ screenTitle,
+ grid
+ ], nav);
+
+ $(form).addClass('cp-onboardscreen-form');
+
+ return form;
+ };
+
+ const mfaRegistrationScreen = function (Env) {
+ var restrict = blocks.activeCheckbox({
+ key: 'registration',
+ getState: function () {
+ return selections.closeRegistration;
+ },
+ label: 'registration',
+ query: function (val, setState) {
+ selections.closeRegistration = val;
+ setState(val)
+ },
+ });
+
+ var forceMFA = blocks.activeCheckbox({
+ key: 'forcemfa',
+ getState: function () {
+ return selections.mfa;
+ },
+ label: 'forcemfa',
+ query: function (val, setState) {
+ selections.mfa = val;
+ setState(val)
+ },
+ });
+
+
+ let mfaOption = h('div.cp-optionblock', [
+ forceMFA,
+ h('span.cp-option-hint', Messages.admin_forcemfaHint)
+ ]);
+ let registrationOption = h('div.cp-optionblock', [
+ restrict,
+ h('span.cp-option-hint', Messages.admin_registrationHint)
+ ]);
+ const grid = blocks.block([
+ mfaOption,
+ registrationOption
+ ], 'cp-admin-customize-options-grid');
+
+ var save = blocks.activeButton('primary', '', Messages.settings_save, function () {
+
+ saveAndRedirect(Env);
+ });
+
+ var prev = blocks.activeButton('secondary', '', Messages.form_backButton, function () {
+ gotoPage(Env, 1);
+ });
+
+ var screenTitle = h('div.cp-onboardscreen-screentitle');
+ $(screenTitle).append(h('div.cp-onboardscreen-maintitle', h('h1.cp-onboardscreen-title', Messages.admin_onboardingOptionsTitle), h('span', Messages.admin_onboardingOptionsHint)));
+ $(save).addClass('cp-onboardscreen-save');
+ $(prev).addClass('cp-onboardscreen-prev');
+ var nav = blocks.nav([prev, save]);
+ $(nav).addClass('cp-onboardscreen-nav');
+
+ var form = blocks.form([
+ screenTitle,
+ grid], nav
+ );
+
+ $(form).addClass('cp-onboardscreen-form');
+
+ return form;
+ };
+
+ pages = [
+ titleConfig,
+ appConfig,
+ mfaRegistrationScreen
+ ];
+ const create = (sendAdminDecree, sendAdminRpc) => {
+ let Env = {
+ sendAdminDecree,
+ sendAdminRpc
+ };
+ Env.overlay = $(h('div#cp-onboarding'));
+ gotoPage(Env, 0);
+ $('body').append(Env.overlay);
+ };
+
+ // XXX test functions to remove
+ window.CP_onboarding_test = () => {
+ create(() => {}, () => {});
+ };
+ window.CP_onboarding_test_full = () => {
+ require([
+ '/common/cryptpad-common.js',
+ '/common/outer/local-store.js',
+ '/common/outer/login-block.js',
+ '/common/rpc.js',
+ '/common/common-constants.js',
+ '/common/cryptget.js',
+ ], function (CryptPad, LocalStore, Block, Rpc, Constants, CryptGet) {
+ var blockHash = LocalStore.getBlockHash();
+ if (!blockHash) { return void console.error('NOT LOGGED IN'); }
+ var parsed = Block.parseBlockHash(blockHash);
+ var sessionToken = LocalStore.getSessionToken() || undefined;
+ let userHash;
+ nThen(w => {
+ // Get user keys
+ Util.getBlock(parsed.href, {
+ bearer: sessionToken
+ }, w((err, response) => {
+ if (err) {
+ w.abort();
+ return void console.error('Please login in and try again');
+ }
+ response.arrayBuffer().then(w(arraybuffer => {
+ arraybuffer = new Uint8Array(arraybuffer);
+ var block_info = Block.decrypt(arraybuffer, parsed.keys);
+ userHash = block_info[Constants.userHashKey];
+ }));
+ }));
+ }).nThen(() => {
+ // Make RPC
+ if (!userHash) { return void console.error('AUTH FAILED'); }
+ CryptPad.makeNetwork((err, network) => {
+ if (err) { return void console.error(err); }
+ CryptGet.get(userHash, (err, val) => {
+ let p = Util.tryParse(val);
+ Rpc.create(network, p.edPrivate, p.edPublic, function (e, rpc) {
+ let sendAdminDecree = function (command, data, callback) {
+ var params = ['ADMIN_DECREE', [command, data]];
+ rpc.send('ADMIN', params, callback);
+ };
+
+ let sendAdminRpc = function (command, data, callback) {
+ var params = [command, data];
+ rpc.send('ADMIN', params, callback);
+ };
+
+ create(sendAdminDecree, sendAdminRpc);
+ });
+ }, {network: network});
+ });
+ });
+ });
+ };
+ return { create, createAppsGrid };
+
+});
+
diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less
index f871964da..ffe0a0da8 100644
--- a/www/kanban/app-kanban.less
+++ b/www/kanban/app-kanban.less
@@ -63,11 +63,13 @@
.kanban-colors(@kanban-colors; (@index - 1));
@color: extract(@kanban-colors, @index);
// make a numbered class selector for each color
- .cp-kanban-palette-color@{index}{
- background-color: @color !important;
+ .cp-palette-color@{index}{
&.cp-kanban-palette-board {
background-color: @color !important;
}
+ }
+ .cp-kanban-palette-color@{index}{
+ background-color: @color !important;
&.kanban-board-inner {
background-color: fade(@color, 50%) !important;
}
@@ -81,10 +83,12 @@
// .cp-kanban-card-color@{index}{
// background-color: @color !important;
// }
- .cp-kanban-palette-color@{index}{
+ .cp-palette-color@{index}{
&.cp-kanban-palette-card {
background-color: @color !important;
}
+ }
+ .cp-kanban-palette-color@{index}{
&.kanban-item {
background-color: @color !important;
}
@@ -140,31 +144,7 @@
}
margin-bottom: 15px;
}
- #cp-kanban-edit-colors {
- display: flex;
- justify-content: space-between;
- .cp-kanban-palette {
- display: inline-block;
- border-radius: 50%;
- height: 30px;
- width: 30px;
- text-align: center;
- line-height: 30px;
- color: @cp_kanban-fg;
- border: 1px solid fade(@cp_kanban-fg, 40%);
- outline: none;
- // tick on selected color
- &.cp-kanban-palette-card.fa-check {
- color: @cryptpad_text_col;
- }
- &.cp-kanban-palette-board.fa-check {
- color: @cryptpad_text_col_inv;
- }
- &:focus{
- outline: @cryptpad_color_brand solid 2px;
- }
- }
- }
+
#cp-kanban-edit-tags {
.tokenfield {
margin: 0;
diff --git a/www/kanban/inner.js b/www/kanban/inner.js
index 6b2519c95..5ac017b8d 100644
--- a/www/kanban/inner.js
+++ b/www/kanban/inner.js
@@ -185,6 +185,11 @@ define([
update();
};
+ var colors = UIElements.makePalette(8, color => {
+ dataObject.color = color;
+ commit();
+ });
+
var conflicts, conflictContainer, titleInput, tagsDiv, colors, text;
var content = h('div', [
conflictContainer = h('div#cp-kanban-edit-conflicts', [
@@ -200,7 +205,7 @@ define([
h('label', {for:'cp-kanban-edit-tags'}, Messages.fm_tagsName),
tagsDiv = h('div#cp-kanban-edit-tags'),
h('label', {for:'cp-kanban-edit-color'}, Messages.kanban_color),
- colors = h('div#cp-kanban-edit-colors'),
+ colors,
]);
var $tags = $(tagsDiv);
@@ -362,11 +367,9 @@ define([
// Colors
var $colors = $(colors);
- var palette = [''];
- for (var i=1; i<=8; i++) { palette.push('color'+i); }
var selectedColor = '';
var resetThemeClass = function () {
- $colors.find('.cp-kanban-palette').each(function (i, el) {
+ $colors.find('.cp-palette-color').each(function (i, el) {
var $c = $(el);
$c.removeClass('cp-kanban-palette-card');
$c.removeClass('cp-kanban-palette-board');
@@ -377,38 +380,13 @@ define([
}
});
};
- palette.forEach(function (color) {
- var $color = $(h('button.cp-kanban-palette.fa'));
- $color.addClass('cp-kanban-palette-'+(color || 'nocolor'));
- $color.keydown(function (e) {
- if (e.which === 13) {
- e.stopPropagation();
- e.preventDefault();
- $color.click();
- }
- });
- $color.click(function () {
- if (offline) { return; }
- if (color === selectedColor) { return; }
- selectedColor = color;
- $colors.find('.cp-kanban-palette').removeClass('fa-check');
- var $col = $colors.find('.cp-kanban-palette-'+(color || 'nocolor'));
- $col.addClass('fa-check');
-
- dataObject.color = color;
- commit();
- }).appendTo($colors);
- });
var color = {
getValue: function () {
- return selectedColor;
+ return colors.getValue();
},
setValue: function (color) {
resetThemeClass();
- $colors.find('.cp-kanban-palette').removeClass('fa-check');
- var $col = $colors.find('.cp-kanban-palette-'+(color || 'nocolor'));
- $col.addClass('fa-check');
- selectedColor = color;
+ colors.setValue(color);
}
};
@@ -460,6 +438,7 @@ define([
$modal.find('nav button.danger').prop('disabled', unlocked ? '' : 'disabled');
offline = !unlocked;
+ palette.disable(offline);
});
diff --git a/www/moderation/inner.js b/www/moderation/inner.js
index 4fd9fa99f..a93dc5e98 100644
--- a/www/moderation/inner.js
+++ b/www/moderation/inner.js
@@ -56,7 +56,7 @@ define([
var andThen = function (common, $container, linkedTicket) {
const sidebar = Sidebar.create(common, 'support', $container);
- const blocks = sidebar.blocks;
+ const blocks = sidebar.blocks();
APP.recorded = {};
APP.allTags = [];
APP.openTicketCategory = Util.mkEvent();