Move profile edition to the settings page

This commit is contained in:
yflory 2025-06-20 14:19:34 +02:00
parent 2b74659604
commit b0fb25d590
8 changed files with 365 additions and 130 deletions

View File

@ -145,6 +145,10 @@ define(req, function(AppConfig, Default, Language) {
Messages.hide_password = "Hide password";
Messages.next_templateList = "Next template list";
Messages.previous_templateList = "Previous template list";
Messages.settings_profileLinkLabel = "Edit your profile link";
Messages.settings_profileDescLabel = "Edit your profile description";
Messages.settings_profileAvatarLabel = "Profile picture";
return Messages;
});

View File

@ -499,8 +499,8 @@ define([
common.drive.onRemove = Util.mkEvent();
common.drive.onDeleted = Util.mkEvent();
// Profile
common.getProfileEditUrl = function (cb) {
postMessage("GET", { key: ['profile', 'edit'] }, function (obj) {
common.getProfileViewUrl = function (cb) {
postMessage("GET", { key: ['profile', 'view'] }, function (obj) {
cb(obj);
});
};

View File

@ -28,29 +28,6 @@
margin-top: 1rem;
}
.cp-profile-badges {
display: flex;
align-items: center;
& > span {
margin-right: 1em;
}
.cp-profile-badges-list {
.badges_main(40px);
i {
cursor: pointer;
&:focus-visible {
outline: @variables_focus_style;
}
&:not(:last-child) {
margin-right: 0.5em;
}
&.cp-selected {
border-color: @cryptpad_color_brand;
}
}
}
}
#cp-app-profile-header {
display: flex;
flex-flow: row wrap;

View File

@ -94,7 +94,6 @@ define([
var LINK_ID = "cp-app-profile-link";
var AVATAR_ID = "cp-app-profile-avatar";
var DESCRIPTION_ID = "cp-app-profile-description";
var BADGES_ID = "cp-app-profile-badges";
var CREATE_ID = "cp-app-profile-create";
var HEADER_ID = "cp-app-profile-header";
var HEADER_RIGHT_ID = "cp-app-profile-rightside";
@ -381,6 +380,9 @@ define([
$span.empty();
const badge = data?.badge;
if (badge && !badgeOK) {
if (!data.proof || !data.edPublic) {
return displayAvatar(val);
}
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
APP.badge.execCommand('CHECK_BADGE', {
@ -445,10 +447,9 @@ define([
APP.module.execCommand("SET", {
key: 'avatar',
value: data.url
}, function (err, newData) {
}, function (newData) {
sframeChan.query("Q_PROFILE_AVATAR_ADD", data.url, function (err, err2) {
if (err || err2) { return void UI.log(err || err2); }
// XXX badge
displayAvatar(data.url, newData);
});
});
@ -474,48 +475,6 @@ define([
displayAvatar(data.avatar, data);
};
const addBadges = $container => {
var $block = $('<div>', {id: BADGES_ID, class:'cp-sidebarlayout-element'}).appendTo($container);
APP.$badges = $(h('span')).appendTo($block);
};
const refreshBadges = (obj) => {
if (!APP.$badges) { return; }
const metadataMgr = APP.common.getMetadataMgr();
const privateData = metadataMgr.getPrivateData();
let args = {};
if (APP.readOnly) { return; }
if (!privateData.isOwnProfile) { args.edPublic = obj.edPublic; }
APP.badge.execCommand('LIST_BADGES', args, data => {
APP.$badges.empty();
let spinner;
APP.$badges.toggle(!!data.length);
let all = data.map(str => {
const i = Badges.render(str);
const $i = $(i).attr('tabindex', 0);
if (APP.readOnly) { return i; }
const selected = obj?.badge === str;
if (selected) { $i.addClass('cp-selected'); }
Util.onClickEnter($i, () => {
let value = selected ? '' : str;
spinner.spin();
APP.module.execCommand('SET', {
key: 'badge',
value
}, function (data) {
spinner.hide();
APP.updateValues(data);
});
});
return i;
});
let content = h('div.cp-profile-badges', [
h('span', Messages.profile_badges),
h('div.cp-profile-badges-list', all)
]);
APP.$badges.append(content);
spinner = UI.makeSpinner(APP.$badges.find('> div'));
});
};
var addDescription = function ($container) {
var $block = $('<div>', {id: DESCRIPTION_ID, class: PROFILE_SECTION}).appendTo($container);
@ -679,7 +638,6 @@ define([
addLink($rightside);
addFriendRequest($rightside);
addMuteButton($rightside);
addBadges($rightside);
addPublicKey($rightside);
addCopyData($rightside);
addViewButton($rightside);
@ -689,18 +647,14 @@ define([
}
};
var updateValues = APP.updateValues = function (data) {
var updateValues = APP.updateValues = function (_data) {
const data = Util.clone(_data);
// Only update avatar if it has changed
if (!APP._lastUpdate
|| APP._lastUpdate.avatar !== data.avatar
|| APP._lastUpdate.badge !== data.badge) {
refreshAvatar(data);
}
refreshAvatar(data);
// Always update other profile information
refreshName(data);
refreshLink(data);
refreshDescription(data);
refreshBadges(data);
refreshFriendRequest(data);
refreshMute(data);
setPublicKeyButton(data);
@ -778,7 +732,6 @@ define([
onEvent: onEvent
});
if (privateData.isOwnProfile) {
APP.module = common.makeUniversal('profile', {
onEvent: onEvent
});
@ -786,7 +739,6 @@ define([
init();
console.log('POST SUBSCRIBE');
execCommand('SUBSCRIBE', null, function (obj) {
updateValues(obj);
UI.removeLoadingScreen();

View File

@ -18,40 +18,15 @@ define([
}).nThen(function (/*waitFor*/) {
var getSecrets = function (Cryptpad, Utils, cb) {
var Hash = Utils.Hash;
// 1st case: visiting someone else's profile with hash in the URL
// hash in the URL: visit someone else's profile
if (window.location.hash) {
// No password for profiles
return void cb(null, Hash.getSecrets('profile', window.location.hash.slice(1)));
const hash = window.location.hash.slice(1);
return cb(null, Hash.getSecrets('profile', hash));
}
nThen(function (waitFor) {
// 2nd case: visiting our own existing profile
Cryptpad.getProfileEditUrl(waitFor(function (hash) {
waitFor.abort();
return void cb(null, Hash.getSecrets('profile', hash));
}));
}).nThen(function () {
if (!Utils.LocalStore.isLoggedIn()) {
// Unregistered users can't create a profile
window.location.href = '/drive/';
return void cb();
}
// No password for profile
var hash = Hash.createRandomHash('profile');
var secret = Hash.getSecrets('profile', hash);
Cryptpad.pinPads([secret.channel], function (e) {
if (e) {
if (e === 'E_OVER_LIMIT') {
// TODO
}
return;
//return void UI.log(Messages._getKey('profile_error', [e])) // TODO
}
var profile = {};
profile.edit = Utils.Hash.getEditHashFromKeys(secret);
profile.view = Utils.Hash.getViewHashFromKeys(secret);
Cryptpad.setNewProfile(profile);
});
cb(null, secret);
// open our own profile
Cryptpad.getProfileViewUrl(function (hash) {
cb(null, Hash.getSecrets('profile', hash));
});
};
var addData = function (meta, Cryptpad, user) {
@ -59,21 +34,6 @@ define([
window.location.hash.slice(1) === user.profile;
};
var addRpc = function (sframeChan, Cryptpad, Utils) {
// Adding a new avatar from the profile: pin it and store it in the object
sframeChan.on('Q_PROFILE_AVATAR_ADD', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data, null);
Cryptpad.pinPads([chanId], function (e) {
if (e) { return void cb(e); }
Cryptpad.setAvatar(data, cb);
});
});
// Removing the avatar from the profile: unpin it
sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data, null);
Cryptpad.unpinPads([chanId], function () {
Cryptpad.setAvatar(undefined, cb);
});
});
sframeChan.on('EV_PROFILE_CORRUPTED_CACHE', function () {
Utils.Cache.clearChannel(Utils.secret.channel, function () {

View File

@ -9,6 +9,8 @@
@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/badges.less';
@import (reference) '../../customize/src/less2/include/avatar.less';
&.cp-app-settings {
.framework_min_main();
@ -229,6 +231,82 @@
margin: 0 2px 0 0;
}
}
.cp-settings-badges {
display: flex;
flex-flow: column;
align-items: flex-start;
.cp-settings-badges-list {
display: flex;
align-items: center;
font-size: 24px;
margin-top: 10px;
.badges_main(40px);
i {
cursor: pointer;
&:focus-visible {
outline: @variables_focus_style;
}
&:not(:last-child) {
margin-right: 0.5em;
}
&.cp-selected {
border-color: @cryptpad_color_brand;
}
}
}
}
.cp-settings-profile-avatar {
.cp-settings-avatar-container {
width: 320px;
background: @cryptpad_text_col;
padding: 10px;
margin-top: 10px;
border-radius: 10px;
}
.cp-avatar {
box-sizing: content-box;
.avatar_main(300px);
display: flex;
text-align: center;
border-radius: 4px;
overflow: visible;
position: relative;
.cp-settings-avatar-delete {
right: 0;
top: 0;
position: absolute;
opacity: 1;
&:hover {
opacity: 0.7;
}
}
}
img {
max-width: 100%;
max-height: 100%;
vertical-align: top;
border-radius: @variables_radius_L;
}
media-tag {
height: 100%;
width: 100%;
display: inline-flex;
justify-content: center;
align-items: center;
img {
min-width: 100%;
min-height: 100%;
max-width: none;
max-height: none;
flex: 1;
}
}
button {
min-width: 40px;
height: 40px;
margin: 5px !important;
}
}
}
}

View File

@ -11,6 +11,9 @@ define([
'/common/common-ui-elements.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/inner/sidebar-layout.js',
'/common/inner/badges.js',
'/common/inner/common-mediatag.js',
'/customize/messages.js',
'/common/hyperscript.js',
'/common/common-credential.js',
@ -35,6 +38,9 @@ define([
UIElements,
Util,
Hash,
Sidebar,
Badges,
MT,
Messages,
h,
Cred,
@ -52,17 +58,23 @@ define([
var metadataMgr;
var privateData;
var sframeChan;
var onProfileEvt = Util.mkEvent();
var categories = {
'account': [ // Msg.settings_cat_account
'cp-settings-own-drive',
'cp-settings-info-block',
'cp-settings-displayname',
'cp-settings-language-selector',
'cp-settings-mediatag-size',
'cp-settings-delete'
],
'profile': [ // Msg.settings_cat_profile
'cp-settings-displayname',
'cp-settings-profile-avatar',
'cp-settings-profile-badges',
'cp-settings-profile-link',
'cp-settings-profile-description',
],
'security': [ // Msg.settings_cat_security
'cp-settings-logout-everywhere',
'cp-settings-mfa',
@ -146,11 +158,14 @@ define([
.text(Messages['settings_' + safeKey + 'Hint'] || 'Coming soon...');
};
var makeBlock = function(key, getter, full) {
var makeBlock = function(key, getter, full, isNew) {
var safeKey = key.replace(/-([a-z])/g, function(g) { return g[1].toUpperCase(); });
create[key] = function() {
var $div = $('<div>', { 'class': 'cp-settings-' + key + ' cp-sidebarlayout-element' });
if (isNew) {
$div.attr('data-item', key);
}
if (full) {
$('<label>').text(Messages['settings_' + safeKey + 'Title'] || key).appendTo($div);
@ -1876,6 +1891,216 @@ define([
cb($cbox[0]);
}, true);
// Profile
makeBlock('profile-link', function(cb) {
if (!common.isLoggedIn()) { return cb(false); }
const input = APP.blocks.input();
const button = APP.blocks.button('primary', '',
Messages.settings_save);
const inputButton = APP.blocks.inputButton(input, button, {
onEnterDelegate: true
});
const labelled = APP.blocks.labelledInput(
Messages.settings_profileLinkLabel, input, inputButton);
const $input = $(input).val(APP.profileData?.url || '');
Util.onClickEnter($(button), () => {
const value = $(input).val();
APP.profile.execCommand('SET', {
key: 'url',
value
}, function (data) {
UI.log(Messages.saved);
APP.profileData = data;
onProfileEvt.fire();
});
});
onProfileEvt.reg(() => {
$input.val(APP.profileData?.url || '');
});
cb(labelled);
}, false, true);
const redrawBadges = ($badges) => {
const old = APP.profileData;
APP.badge.execCommand('LIST_BADGES', {}, data => {
let spinner;
$badges.toggle(!!data.length);
let all = data.map(str => {
const i = Badges.render(str);
const $i = $(i).attr('tabindex', 0);
const selected = old?.badge === str;
if (selected) { $i.addClass('cp-selected'); }
Util.onClickEnter($i, () => {
let value = selected ? '' : str;
spinner.spin();
APP.profile.execCommand('SET', {
key: 'badge',
value
}, function (data) {
APP.profileData = data;
spinner.done();
onProfileEvt.fire();
});
});
return i;
});
if (!all.length) {
return $badges.empty();
}
let content = h('div.cp-settings-badges', [
h('span', Messages.profile_badges),
h('div.cp-settings-badges-list', all)
]);
$badges.empty().append(content);
spinner = UI.makeSpinner($badges.find('.cp-settings-badges-list'));
});
};
makeBlock('profile-badges', function(cb) {
if (!common.isLoggedIn()) { return cb(false); }
const badges = h('div');
const $badges = $(badges);
redrawBadges($badges);
onProfileEvt.reg(() => {
redrawBadges($badges);
});
cb(badges);
}, false, true);
const redrawAvatar = ($avatar) => {
const val = APP.profileData?.avatar;
const badge = APP.profileData?.badge;
console.error('REDRAW', val, badge);
if (!val) {
$('<img>', {
src: '/customize/images/avatar.png',
title: Messages.profile_defaultAlt,
alt: Messages.profile_defaultAlt,
}).appendTo($avatar.empty());
$avatar.append(Badges.render(badge));
return;
}
common.displayAvatar($avatar, val, void 0, () => {
// avatar cb: append delete button
const delButton = h('button.cp-settings-avatar-delete.btn.btn-danger.fa.fa-times', {
title: Messages.profile_remove_avatar
});
$avatar.append(delButton);
$(delButton).click(() => {
const old = APP.profileData?.avatar;
APP.profile.execCommand("SET", {
key: 'avatar',
value: ""
}, (newData) => {
APP.profileData = newData;
sframeChan.query("Q_PROFILE_AVATAR_REMOVE", old,
(err, err2) => {
if (err || err2) {
return void UI.warn(err || err2);
}
onProfileEvt.fire();
});
});
});
}, void 0, badge);
};
makeBlock('profile-avatar', function(cb) {
if (!common.isLoggedIn()) { return cb(false); }
const avatar = h('div.cp-avatar');
const $avatar = $(avatar);
redrawAvatar($avatar);
onProfileEvt.reg(() => {
redrawAvatar($avatar);
});
// Upload
const data = MT.addAvatar(common, (ev, data) => {
const old = APP.profileData?.avatar;
const todo = () => {
APP.profile.execCommand("SET", {
key: 'avatar',
value: data.url
}, (newData) => {
console.error(newData?.avatar, newData);
sframeChan.query("Q_PROFILE_AVATAR_ADD",
data.url, (err, err2) => {
if (err || err2) {
return void UI.warn(err || err2);
}
APP.profileData = newData;
onProfileEvt.fire();
});
});
};
if (old) {
sframeChan.query("Q_PROFILE_AVATAR_REMOVE",
old, (err, err2) => {
if (err || err2) {
return void UI.warn(err || err2);
}
todo();
});
return;
}
todo();
});
const $upButton = common.createButton('upload', false, data);
$upButton.removeClass('btn-primary').addClass('btn-secondary');
$upButton.removeProp('title');
$upButton.text(Messages.profile_upload);
$upButton.prepend($('<i>', {'class': 'fa fa-upload', 'aria-hidden': 'true'}));
cb([
h('label', Messages.settings_profileAvatarLabel),
h('div.cp-settings-avatar-container', avatar),
$upButton[0]
]);
}, false, true);
makeBlock('profile-description', function(cb) {
if (!common.isLoggedIn()) { return cb(false); }
const input = APP.blocks.textarea();
const button = APP.blocks.button('primary', '',
Messages.settings_save);
const labelled = APP.blocks.labelledInput(
Messages.settings_profileDescLabel, input);
$(labelled).append(button);
const $input = $(input).val(APP.profileData?.description || '');
Util.onClickEnter($(button), () => {
const value = $(input).val();
APP.profile.execCommand('SET', {
key: 'description',
value
}, function (data) {
UI.log(Messages.saved);
APP.profileData = data;
onProfileEvt.fire();
});
});
onProfileEvt.reg(() => {
$input.val(APP.profileData?.description || '');
});
cb(labelled);
}, false, true);
// Settings app
var createUsageButton = function() {
@ -1897,6 +2122,7 @@ define([
var SIDEBAR_ICONS = {
account: 'fa fa-user-o',
profile: 'fa fa-user-circle',
drive: 'fa fa-hdd-o',
cursor: 'fa fa-i-cursor',
code: 'fa fa-file-code-o',
@ -1909,6 +2135,7 @@ define([
};
Messages.settings_cat_notifications = Messages.notificationsPage;
Messages.settings_cat_profile = Messages.profileButton;
var createLeftside = function() {
var $categories = $('<div>', { 'class': 'cp-sidebarlayout-categories' })
.appendTo(APP.$leftside);
@ -1955,7 +2182,15 @@ define([
common.setHash(active);
};
var onProfileEvent = function (obj) {
var ev = obj.ev;
var data = obj.data;
if (ev === 'UPDATE') {
APP.profileData = data;
onProfileEvt.fire();
return;
}
};
nThen(function(waitFor) {
$(waitFor(UI.addLoadingScreen));
@ -1967,6 +2202,16 @@ define([
APP.$rightside = $('<div>', { id: 'cp-sidebarlayout-rightside' }).appendTo(APP.$container);
sframeChan = common.getSframeChannel();
sframeChan.onReady(waitFor());
}).nThen(function(waitFor) {
APP.profile = common.makeUniversal('profile', {
onEvent: onProfileEvent
});
APP.badge = common.makeUniversal('badge', {
onEvent: onProfileEvent
});
APP.profile.execCommand('SUBSCRIBE', null, waitFor(obj => {
APP.profileData = obj;
}));
}).nThen(function( /*waitFor*/ ) {
metadataMgr = common.getMetadataMgr();
privateData = metadataMgr.getPrivateData();
@ -1985,6 +2230,8 @@ define([
APP.toolbar.$rightside.hide();
APP.history = common.makeUniversal('history');
APP.blocks = Sidebar.blocks('settings', common);
// Content
var $rightside = APP.$rightside;
/*for (var f in create) {

View File

@ -108,6 +108,23 @@ define([
sframeChan.on('Q_SET_DRIVE_REDIRECT_PREFERENCE', function (data, cb) {
Cryptpad.setDriveRedirectPreference(data, cb);
});
// Adding a new avatar from the profile: pin it
// and store it in the profile and user objects
sframeChan.on('Q_PROFILE_AVATAR_ADD', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data, null);
Cryptpad.pinPads([chanId], function (e) {
if (e) { return void cb(e); }
Cryptpad.setAvatar(data, cb);
});
});
// Removing the avatar from the profile: unpin it
sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data, null);
Cryptpad.unpinPads([chanId], function () {
Cryptpad.setAvatar(undefined, cb);
});
});
};
var category;
if (window.location.hash) {