Merge branch 'appconfig_ui' into 2024.6-test
This commit is contained in:
commit
67549561b8
26 changed files with 1069 additions and 113 deletions
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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 = `<ul class="cp-notes-list">
|
||||
<li>Create your first administrator account on this page. Administrators manage instance settings including storage quotas, and have access to moderation tools.</li>
|
||||
<li>Your password is the secret key that encrypts all of your documents and administrator privileges on this instance. <span class="red">If you lose it there is no way we can recover your data.</span></li>
|
||||
<li>If you are using a shared computer, <span class="red">remember to log out</span> when you are done. Only closing the browser window leaves your account exposed. </li></ul>`;
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
])
|
||||
]),
|
||||
])
|
||||
|
|
97
customize.dist/src/less2/include/admin.less
Normal file
97
customize.dist/src/less2/include/admin.less
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
34
lib/api.js
34
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; }
|
||||
|
|
|
@ -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,
|
||||
|
|
6
lib/common-hash.js
Normal file
6
lib/common-hash.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
module.exports = require("../www/common/common-hash");
|
||||
|
|
@ -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];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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`);
|
||||
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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 = $('<span>', {"class": "fa fa-file-text-o"});
|
||||
UI.getIcon = function (type) {
|
||||
var $icon = $defaultIcon.clone();
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
119
www/install/onboarding.less
Normal file
119
www/install/onboarding.less
Normal file
|
@ -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;
|
||||
}
|
498
www/install/onboardscreen.js
Normal file
498
www/install/onboardscreen.js
Normal file
|
@ -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 };
|
||||
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue