mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
Merge branch 'sidebar' into admin-customize
This commit is contained in:
commit
0d213c82f7
@ -91,10 +91,71 @@
|
||||
pre {
|
||||
color: @cryptpad_text_col;
|
||||
}
|
||||
label:not(.noTitle), .cp-default-label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:not([data-item]) { // Old sidebar-layout blocks
|
||||
label:not(.noTitle), .cp-default-label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
&[data-item] { // New sidebar-layout blocks
|
||||
label.cp-item-label, .cp-default-label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.cp-sidebar-form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: baseline;
|
||||
}
|
||||
input {
|
||||
max-width: 25rem;
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
.btn.btn-primary {
|
||||
margin: 5px 0.5rem 0 0;
|
||||
}
|
||||
}
|
||||
.cp-sidebar-bigger-alert {
|
||||
font-size: 16px;
|
||||
}
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
th {
|
||||
max-width: 60vw;
|
||||
border: 1px solid #777;
|
||||
padding: 7px;
|
||||
}
|
||||
td {
|
||||
padding: 0.3rem;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.cp-checkmark {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.cp-broadcast-container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
.cp-broadcast-lang {
|
||||
order: 4;
|
||||
margin: 30px;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: baseline;
|
||||
}
|
||||
table {
|
||||
.cp-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-sidebarlayout-description {
|
||||
display: block;
|
||||
|
||||
@ -1452,7 +1452,7 @@ Example
|
||||
return function () {
|
||||
var state = data.getState();
|
||||
var key = data.key;
|
||||
var $div = makeBlock(key);
|
||||
var $div = makeBlock(key); //sidebar.addItem(data.key);
|
||||
var $hint;
|
||||
if (data.hintElement) {
|
||||
$hint = $div.find('.cp-sidebarlayout-description');
|
||||
|
||||
@ -26,7 +26,7 @@ define(['/customize/application_config.js'], function (AppConfig) {
|
||||
MAX_PREMIUM_TEAMS_SLOTS: Math.max(AppConfig.maxTeamsSlots || 0, AppConfig.maxPremiumTeamsSlots || 0) || 5,
|
||||
MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxTeamsOwned || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5,
|
||||
// Apps
|
||||
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar'],
|
||||
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'newadmin'], // XXX
|
||||
earlyAccessApps: ['doc', 'presentation']
|
||||
};
|
||||
});
|
||||
|
||||
324
www/common/inner/sidebar-layout.js
Normal file
324
www/common/inner/sidebar-layout.js
Normal file
@ -0,0 +1,324 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'/components/nthen/index.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/customize/messages.js',
|
||||
'/common/hyperscript.js',
|
||||
], function(
|
||||
$,
|
||||
nThen,
|
||||
UI,
|
||||
UIElements,
|
||||
Util,
|
||||
Hash,
|
||||
Messages,
|
||||
h
|
||||
) {
|
||||
const Sidebar = {};
|
||||
|
||||
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 = {};
|
||||
const items = {};
|
||||
|
||||
let blocks = sidebar.blocks = {};
|
||||
blocks.labelledInput = (label, input) => {
|
||||
let uid = Util.uid();
|
||||
let id = `cp-${app}-item-${uid}`;
|
||||
input.setAttribute('id', id);
|
||||
let labelElement = h('label', { for: id }, label);
|
||||
return h('div', { class: 'cp-labelled-input' }, [labelElement, input]);
|
||||
};
|
||||
|
||||
blocks.button = (type, icon, text) => {
|
||||
type = type || 'primary';
|
||||
if (icon && icon.indexOf('-') !== -1) {
|
||||
let prefix = icon.slice(0, icon.indexOf('-'));
|
||||
icon = `${prefix} ${icon}`;
|
||||
}
|
||||
return h(`button.btn.btn-${type}`, [
|
||||
icon ? h('i', { 'class': icon }) : undefined,
|
||||
h('span', text)
|
||||
]);
|
||||
};
|
||||
blocks.nav = (buttons) => {
|
||||
return h('nav', buttons);
|
||||
};
|
||||
blocks.form = (content, nav) => {
|
||||
return h('div.cp-sidebar-form', [content, nav]);
|
||||
};
|
||||
blocks.input = (attr) => {
|
||||
return h('input', attr);
|
||||
};
|
||||
blocks.code = val => {
|
||||
return h('code', val);
|
||||
};
|
||||
blocks.inline = (value) => {
|
||||
return h('span', value);
|
||||
};
|
||||
blocks.block = (content, className) => {
|
||||
return h('div', { class: className }, content);
|
||||
};
|
||||
blocks.paragraph = (content) => {
|
||||
return h('p', content);
|
||||
};
|
||||
|
||||
blocks.alert = function (type, big, content) {
|
||||
var isBigClass = big ? '.cp-sidebar-bigger-alert' : ''; // Add the class if we want a bigger font-size
|
||||
return h('div.alert.alert-' + type + isBigClass, content);
|
||||
};
|
||||
|
||||
blocks.alertHTML = function (message, element) {
|
||||
return h('span', [
|
||||
UIElements.setHTML(h('p'), message),
|
||||
element
|
||||
]);
|
||||
};
|
||||
blocks.pre = (value) => {
|
||||
return h('pre', value);
|
||||
};
|
||||
|
||||
blocks.textarea = function (attributes, value) {
|
||||
return h('textarea', attributes, value || '');
|
||||
};
|
||||
|
||||
blocks.unorderedList = function (entries) {
|
||||
const ul = h('ul');
|
||||
|
||||
ul.updateContent = (entries) => {
|
||||
ul.innerHTML = '';
|
||||
entries.forEach(entry => {
|
||||
const li = h('li', entry);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
};
|
||||
ul.updateContent(entries);
|
||||
|
||||
return ul;
|
||||
};
|
||||
|
||||
blocks.checkbox = (key, label, state, opts, onChange) => {
|
||||
var box = UI.createCheckbox(`cp-${app}-${key}`, label, state, { label: { class: 'noTitle' } });
|
||||
if (opts && opts.spinner) {
|
||||
box.spinner = UI.makeSpinner($(box));
|
||||
}
|
||||
if (typeof(onChange) === "function"){
|
||||
$(box).find('input').on('change', function() {
|
||||
onChange(this.checked);
|
||||
});
|
||||
}
|
||||
return box;
|
||||
};
|
||||
|
||||
blocks.table = function (header, entries) {
|
||||
const table = h('table.cp-sidebar-list');
|
||||
if (header) {
|
||||
const headerValues = header.map(value => {
|
||||
const lastWord = value.split(' ').pop(); // Extracting the last word
|
||||
return h('th', { class: lastWord.toLowerCase() }, value); // Modified to use the last word
|
||||
});
|
||||
const headerRow = h('thead', h('tr', headerValues));
|
||||
table.appendChild(headerRow);
|
||||
}
|
||||
|
||||
let getRow = line => {
|
||||
return h('tr', line.map(value => {
|
||||
if (typeof(value) === "object" && value.content) {
|
||||
return h('td', value.attr || {}, value.content);
|
||||
}
|
||||
return h('td', value);
|
||||
}));
|
||||
};
|
||||
table.updateContent = (newEntries) => {
|
||||
$(table).find('tbody').remove();
|
||||
let bodyContent = [];
|
||||
newEntries.forEach(line => {
|
||||
const row = getRow(line);
|
||||
bodyContent.push(row);
|
||||
});
|
||||
table.appendChild(h('tbody', bodyContent));
|
||||
};
|
||||
table.updateContent(entries);
|
||||
table.addLine = (line) => {
|
||||
const row = getRow(line);
|
||||
$(table).find('tbody').append(row);
|
||||
};
|
||||
|
||||
return table;
|
||||
};
|
||||
|
||||
blocks.link = function (text, url, isSafe) {
|
||||
var link = h('a', { href: url }, text);
|
||||
$(link).click(function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (isSafe) {
|
||||
common.openURL(url);
|
||||
} else {
|
||||
common.openUnsafeURL(url);
|
||||
}
|
||||
});
|
||||
return link;
|
||||
};
|
||||
|
||||
blocks.activeButton = function (type, icon, text, callback, keepEnabled) {
|
||||
var button = blocks.button(type, icon, text);
|
||||
var $button = $(button);
|
||||
button.spinner = h('span');
|
||||
var spinner = UI.makeSpinner($(button.spinner));
|
||||
|
||||
Util.onClickEnter($button, function () {
|
||||
spinner.spin();
|
||||
if (!keepEnabled) { $button.attr('disabled', 'disabled'); }
|
||||
let done = success => {
|
||||
$button.removeAttr('disabled');
|
||||
if (success) { return void spinner.done(); }
|
||||
spinner.hide();
|
||||
};
|
||||
// The callback can be synchrnous or async, handle "done" in both ways
|
||||
let success = callback(done); // Async
|
||||
if (typeof(success) === "boolean") { done(success); } // Sync
|
||||
});
|
||||
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;
|
||||
const safeKey = keyToCamlCase(key);
|
||||
var labelKey = `${app}_${safeKey}Label`;
|
||||
var titleKey = `${app}_${safeKey}Title`;
|
||||
var label = Messages[labelKey] || Messages[titleKey];
|
||||
var box = blocks.checkbox(key, label, state, { spinner: true }, checked => {
|
||||
var $cbox = $(box);
|
||||
var $checkbox = $cbox.find('input');
|
||||
let spinner = box.spinner;
|
||||
spinner.spin();
|
||||
$checkbox.attr('disabled', 'disabled');
|
||||
var val = !!checked;
|
||||
data.query(val, function (state) {
|
||||
spinner.done();
|
||||
$checkbox[0].checked = state;
|
||||
$checkbox.removeAttr('disabled');
|
||||
});
|
||||
});
|
||||
return box;
|
||||
};
|
||||
|
||||
sidebar.addItem = (key, get, options) => {
|
||||
const safeKey = keyToCamlCase(key);
|
||||
get((content) => {
|
||||
options = options || {};
|
||||
const title = options.noTitle ? undefined : h('label.cp-item-label', {
|
||||
id: `cp-${app}-${key}`
|
||||
}, Messages[`${app}_${safeKey}Title`] || key);
|
||||
const hint = options.noHint ? undefined : h('span.cp-sidebarlayout-description',
|
||||
Messages[`${app}_${safeKey}Hint`] || 'Coming soon...');
|
||||
if (hint && options.htmlHint) {
|
||||
hint.innerHTML = Messages[`${app}_${safeKey}Hint`];
|
||||
}
|
||||
const div = h(`div.cp-sidebarlayout-element`, {
|
||||
'data-item': key,
|
||||
style: 'display:none;'
|
||||
}, [
|
||||
title,
|
||||
hint,
|
||||
content
|
||||
]);
|
||||
items[key] = div;
|
||||
$rightside.append(div);
|
||||
});
|
||||
};
|
||||
|
||||
sidebar.addCheckboxItem = (data) => {
|
||||
const key = data.key;
|
||||
let box = blocks.activeCheckbox(data);
|
||||
sidebar.addItem(key, function (cb) {
|
||||
cb(box);
|
||||
}, data.options);
|
||||
};
|
||||
|
||||
var hideCategories = function () {
|
||||
Object.keys(items).forEach(key => { $(items[key]).hide(); });
|
||||
};
|
||||
var showCategories = function (cat) {
|
||||
if (!cat || !Array.isArray(cat.content)) {
|
||||
console.error("Invalid category", cat);
|
||||
return UI.warn(Messages.error);
|
||||
}
|
||||
hideCategories();
|
||||
cat.content.forEach(function (c) { $(items[c]).show(); });
|
||||
};
|
||||
/*
|
||||
categories = {
|
||||
key1: {
|
||||
icon: 'fa fa-user',
|
||||
content: [ 'item1', 'item2' ]
|
||||
}
|
||||
key2: {
|
||||
icon: 'fa fa-bell',
|
||||
onClick: function () {}
|
||||
}
|
||||
}
|
||||
*/
|
||||
sidebar.makeLeftside = (categories) => {
|
||||
$leftside.html('');
|
||||
let container = h('div.cp-sidebarlayout-categories', { role: 'menu' });
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var active = privateData.category || '';
|
||||
if (active.indexOf('-') !== -1) { active = active.split('-')[0]; }
|
||||
|
||||
Object.keys(categories).forEach(function (key, i) {
|
||||
if (!active && !i) { active = key; }
|
||||
var category = categories[key];
|
||||
var icon;
|
||||
if (category.icon) { icon = h('span', { class: category.icon }); }
|
||||
var isActive = key === active ? '.cp-leftside-active' : '';
|
||||
var item = h('li.cp-sidebarlayout-category'+isActive, {
|
||||
'role': 'menuitem',
|
||||
'tabindex': 0
|
||||
}, [
|
||||
icon,
|
||||
Messages[`${app}_cat_${key}`] || key,
|
||||
]);
|
||||
var $item = $(item).appendTo(container);
|
||||
|
||||
Util.onClickEnter($item, function () {
|
||||
if (!Array.isArray(category.content) && category.onClick) {
|
||||
category.onClick();
|
||||
return;
|
||||
}
|
||||
active = key;
|
||||
common.setHash(key);
|
||||
$(container).find('.cp-leftside-active').removeClass('cp-leftside-active');
|
||||
$(item).addClass('cp-leftside-active');
|
||||
showCategories(category);
|
||||
});
|
||||
|
||||
});
|
||||
showCategories(categories[active]);
|
||||
$leftside.append(container);
|
||||
};
|
||||
|
||||
sidebar.disableItem = (key) => {
|
||||
$(items[key]).remove();
|
||||
delete items[key];
|
||||
};
|
||||
|
||||
return sidebar;
|
||||
};
|
||||
|
||||
return Sidebar;
|
||||
});
|
||||
23
www/newadmin/app-admin.less
Normal file
23
www/newadmin/app-admin.less
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (reference) "../../customize/src/less2/include/limit-bar.less";
|
||||
@import (reference) "../../customize/src/less2/include/creation.less";
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/export.less';
|
||||
|
||||
&.cp-app-admin {
|
||||
.framework_min_main();
|
||||
.sidebar-layout_main();
|
||||
.limit-bar_main();
|
||||
.creation_main();
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
font: @colortheme_app-font;
|
||||
}
|
||||
|
||||
21
www/newadmin/index.html
Normal file
21
www/newadmin/index.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script src="/customize/pre-loading.js?ver=1.1"></script>
|
||||
<link href="/customize/src/pre-loading.css?ver=1.0" rel="stylesheet" type="text/css">
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<link href="/customize/src/outer.css?ver=1.3.2" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript></noscript>
|
||||
<iframe-placeholder>
|
||||
22
www/newadmin/inner.html
Normal file
22
www/newadmin/inner.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script src="/customize/pre-loading.js?ver=1.1"></script>
|
||||
<link href="/customize/src/pre-loading.css?ver=1.0" rel="stylesheet" type="text/css">
|
||||
<script async data-bootload="/newadmin/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="cp-app-admin">
|
||||
<div id="cp-toolbar" class="cp-toolbar-container"></div>
|
||||
<div id="cp-sidebarlayout-container"></div>
|
||||
</body>
|
||||
</html>
|
||||
3386
www/newadmin/inner.js
Normal file
3386
www/newadmin/inner.js
Normal file
File diff suppressed because it is too large
Load Diff
47
www/newadmin/main.js
Normal file
47
www/newadmin/main.js
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
// Load #1, load as little as possible because we are in a race to get the loading screen up.
|
||||
define([
|
||||
'/components/nthen/index.js',
|
||||
'/api/config',
|
||||
'/common/dom-ready.js',
|
||||
'/common/sframe-common-outer.js'
|
||||
], function (nThen, ApiConfig, DomReady, SFCommonO) {
|
||||
|
||||
// Loaded in load #2
|
||||
nThen(function (waitFor) {
|
||||
DomReady.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
SFCommonO.initIframe(waitFor);
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
var addRpc = function (sframeChan, Cryptpad/*, Utils*/) {
|
||||
// Adding a new avatar from the profile: pin it and store it in the object
|
||||
sframeChan.on('Q_ADMIN_MAILBOX', function (data, cb) {
|
||||
Cryptpad.addAdminMailbox(data, cb);
|
||||
});
|
||||
sframeChan.on('Q_ADMIN_RPC', function (data, cb) {
|
||||
Cryptpad.adminRpc(data, cb);
|
||||
});
|
||||
sframeChan.on('Q_UPDATE_LIMIT', function (data, cb) {
|
||||
Cryptpad.updatePinLimit(function (e) {
|
||||
cb({error: e});
|
||||
});
|
||||
});
|
||||
};
|
||||
var category;
|
||||
if (window.location.hash) {
|
||||
category = window.location.hash.slice(1);
|
||||
window.location.hash = '';
|
||||
}
|
||||
var addData = function (obj) {
|
||||
if (category) { obj.category = category; }
|
||||
};
|
||||
SFCommonO.start({
|
||||
noRealtime: true,
|
||||
addRpc: addRpc,
|
||||
addData: addData
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user