From bbf2e3a9aef2c7e742af0d3751118feac0523fc2 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 6 Dec 2019 16:10:19 +0100 Subject: [PATCH 01/21] Mute and unmute users --- .../src/less2/include/mediatag.less | 7 ++ .../src/less2/include/messenger.less | 4 ++ www/code/app-code.less | 7 ++ www/common/media-tag.js | 2 - www/common/messenger-ui.js | 64 ++++++++++++++++--- www/common/outer/messenger.js | 32 ++++++++++ 6 files changed, 106 insertions(+), 10 deletions(-) diff --git a/customize.dist/src/less2/include/mediatag.less b/customize.dist/src/less2/include/mediatag.less index 704fd71bd..f8255aab3 100644 --- a/customize.dist/src/less2/include/mediatag.less +++ b/customize.dist/src/less2/include/mediatag.less @@ -7,6 +7,13 @@ text-align: center; } + media-tag:empty { + width: 100px; + height: 100px; + display: inline-block; + border: 1px solid #BBB; + } + media-tag img { flex: 1; max-height: 100% !important; diff --git a/customize.dist/src/less2/include/messenger.less b/customize.dist/src/less2/include/messenger.less index 7f4f7c342..4b89b967d 100644 --- a/customize.dist/src/less2/include/messenger.less +++ b/customize.dist/src/less2/include/messenger.less @@ -121,6 +121,10 @@ } } } + .cp-app-contacts-muted-button { + display: none; + order: 3; + } } } #cp-app-contacts-container.cp-app-contacts-inapp { diff --git a/www/code/app-code.less b/www/code/app-code.less index f07ef534d..219143a34 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -98,6 +98,13 @@ max-height: 90vh; } } + media-tag:empty { + width: 100px; + height: 100px; + display: inline-block; + border: 1px solid #BBB; + } + .markdown_main(); .cp-app-code-preview-empty { display: none; diff --git a/www/common/media-tag.js b/www/common/media-tag.js index a2beb3b4a..27683c54e 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -435,8 +435,6 @@ return mediaObject; } - mediaObject.tag.innerHTML = ''; - // Download the encrypted blob download(src, function (err, u8Encrypted) { if (err) { diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js index 1979bae66..7c437969b 100644 --- a/www/common/messenger-ui.js +++ b/www/common/messenger-ui.js @@ -3,9 +3,10 @@ define([ '/customize/messages.js', '/common/common-util.js', '/common/common-interface.js', + '/common/common-ui-elements.js', '/common/hyperscript.js', '/common/diffMarked.js', -], function ($, Messages, Util, UI, h, DiffMd) { +], function ($, Messages, Util, UI, UIElements, h, DiffMd) { 'use strict'; var debug = console.log; @@ -67,8 +68,9 @@ define([ h('div.cp-app-contacts-category-content') ]), h('div.cp-app-contacts-friends.cp-app-contacts-category', [ - h('div.cp-app-contacts-category-content'), + h('div.cp-app-contacts-category-content.cp-contacts-friends'), h('h2.cp-app-contacts-category-title', Messages.contacts_friends), + h('button.cp-app-contacts-muted-button', Messages.contacts_manageMuted || 'MANAGE MUTED') // XXX ]), h('div.cp-app-contacts-rooms.cp-app-contacts-category', [ h('div.cp-app-contacts-category-content'), @@ -486,6 +488,15 @@ define([ } }; + var muteUser = function (data) { + execCommand('MUTE_USER', { + curvePublic: data.curvePublic, + name: data.displayName || data.name, + avatar: data.avatar + }, function (e /*, removed */) { + if (e) { return void console.error(e); } + }); + }; var removeFriend = function (curvePublic) { execCommand('REMOVE_FRIEND', curvePublic, function (e /*, removed */) { if (e) { return void console.error(e); } @@ -529,13 +540,16 @@ define([ if (!channel.isFriendChat) { return; } var curvePublic = channel.curvePublic; var friend = contactsData[curvePublic] || friendData; - UI.confirm(Messages._getKey('contacts_confirmRemove', [ - Util.fixHTML(friend.name) - ]), function (yes) { + var muteBox = UI.createCheckbox('cp-contacts-mute', Messages.contacts_mute, false); + var content = h('div', [ + h('p', Messages._getKey('contacts_confirmRemove', [Util.fixHTML(friend.name)])), + muteBox + ]); + UI.confirm(content, function (yes) { if (!yes) { return; } - removeFriend(curvePublic, function (e) { - if (e) { return void console.error(e); } - }); + var mute = Util.isChecked($(content).find('#cp-contacts-mute')); + muteUser(friend); + removeFriend(curvePublic); // TODO remove friend from userlist ui // FIXME seems to trigger EJOINED from netflux-websocket (from server); // (tried to join a channel in which you were already present) @@ -806,6 +820,40 @@ define([ rooms.forEach(initializeRoom); }); + execCommand('GET_MUTED_USERS', null, function (err, muted) { + if (err) { return void console.error(err); } + + if (!muted || Object.keys(muted).length === 0) { return; } + + var $button = $userlist.find('.cp-app-contacts-muted-button'); + var rows = Object.keys(muted).map(function (curve) { + var data = muted[curve]; + var avatar = h('div.cp-avatar'); + var button = h('td', h('i.fa.fa-times', {title: Messages.contacts_unmute})); + UIElements.displayAvatar(common, $(avatar), data.avatar, data.name); + $(button).click(function () { + execCommand('UNMUTE_USER', { + curvePublic: curve, + }, function (e /*, removed */) { + if (e) { return void console.error(e); } + $(button).closest('tr').remove(); + }); + }); + return h('tr', [ + h('td', avatar), + h('td', data.name), + button + ]); + }); + var content = h('div', [ + h('p', Messages.contacts_mutedUsers), + h('table', rows) + ]); + $button.click(function () { + UI.alert(content); + }).show(); + }); + $container.removeClass('cp-app-contacts-initializing'); }; diff --git a/www/common/outer/messenger.js b/www/common/outer/messenger.js index d0e200deb..90884198e 100644 --- a/www/common/outer/messenger.js +++ b/www/common/outer/messenger.js @@ -458,6 +458,29 @@ define([ } }; + var muteUser = function (ctx, data, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + var proxy = ctx.store.proxy; + var muted = proxy.mutedUsers = proxy.mutedUsers || {}; + if (muted[data.curvePublic]) { return void cb(); } + muted[data.curvePublic] = data; + cb(); + }; + var unmuteUser = function (ctx, curvePublic, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + var proxy = ctx.store.proxy; + var muted = proxy.mutedUsers = proxy.mutedUsers || {}; + delete muted[curvePublic]; + cb(); + }; + var getMutedUsers = function (ctx, cb) { + var proxy = ctx.store.proxy; + if (cb) { + return void cb(proxy.mutedUsers || {}); + } + return proxy.mutedUsers || {}; + }; + var openChannel = function (ctx, data) { var proxy = ctx.store.proxy; var network = ctx.store.network; @@ -990,6 +1013,9 @@ define([ if (cmd === 'GET_ROOMS') { return void getRooms(ctx, data, cb); } + if (cmd === 'GET_MUTED_USERS') { + return void getMutedUsers(ctx, cb); + } if (cmd === 'GET_USERLIST') { return void getUserList(ctx, data, cb); } @@ -1002,6 +1028,12 @@ define([ if (cmd === 'REMOVE_FRIEND') { return void removeFriend(ctx, data, cb); } + if (cmd === 'MUTE_USER') { + return void muteUser(ctx, data, cb); + } + if (cmd === 'UNMUTE_USER') { + return void unmuteUser(ctx, data, cb); + } if (cmd === 'GET_STATUS') { return void getStatus(ctx, data, cb); } From 11ddb96422d7ce6b675046a88a84eafcbfec80b7 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 10 Dec 2019 13:07:57 +0100 Subject: [PATCH 02/21] Mute friends: dismiss notifications and fix UI issues --- www/common/common-interface.js | 1 + www/common/common-messaging.js | 1 + www/common/common-ui-elements.js | 37 +++++++++++++++++-- www/common/messenger-ui.js | 53 +++++++++++++++++----------- www/common/outer/async-store.js | 4 +++ www/common/outer/mailbox-handlers.js | 34 ++++++++++++++++++ www/common/outer/messenger.js | 40 +++++++++++++++------ 7 files changed, 136 insertions(+), 34 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 8efc7397a..3c758a88b 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -580,6 +580,7 @@ define([ }]; var modal = dialog.customModal(content, {buttons: buttons}); UI.openCustomModal(modal); + return modal; }; UI.log = function (msg) { diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index 91322f526..feb3d79d3 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -84,6 +84,7 @@ define([ var myData = createData(store.proxy, false); if (store.proxy.friends) { store.proxy.friends.me = myData; + delete store.proxy.friends.me.channel; } if (store.modules['team']) { store.modules['team'].updateMyData(myData); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index eb8bb21a2..992428899 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3803,10 +3803,20 @@ define([ }); }; + var modal; + var mute = UIElements.createMuteButton(common, msg.content, function () { + // Mute = auto-reject friend request + var $modal = modal && $(modal) && $(modal).closest('div.alertify'); + if ($modal && $modal.length && $modal[0].closeModal) { + $modal[0].closeModal(function () {}); + } + return void todo(false); // XXX false is reject. We can also "dismiss"... + }); var content = h('div.cp-share-modal', [ - setHTML(h('p'), text) + setHTML(h('p'), text), + h('p', mute) ]); - UI.proposal(content, todo); + modal = UI.proposal(content, todo); }; UIElements.displayAddOwnerModal = function (common, data) { @@ -4148,5 +4158,28 @@ define([ UI.proposal(div, todo); }; + UIElements.createMuteButton = function (common, data, cb) { + cb = cb || function () {}; + var button = h('i.fa.fa-bell-slash-o', { + title: Messages.notifications_muteUserTitle + }); + var module = common.makeUniversal('messenger'); + $(button).click(function () { + UI.confirm(Messages.notifications_muteUserConfirm, function (yes) { + if (!yes) { return; } + module.execCommand('MUTE_USER', { + curvePublic: data.curvePublic, + name: data.displayName || data.name, + avatar: data.avatar + }, function (e) { + cb(e); + if (e) { return void UI.warn(Messages.error); } + UI.log(Messages.success); + }); + }); + }); + return button; + }; + return UIElements; }); diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js index 7c437969b..9ce795f4e 100644 --- a/www/common/messenger-ui.js +++ b/www/common/messenger-ui.js @@ -186,7 +186,7 @@ define([ markup.message = function (msg) { if (msg.type !== 'MSG') { return; } var curvePublic = msg.author; - var name = typeof msg.name !== "undefined" ? + var name = (typeof msg.name !== "undefined" || !contactsData[msg.author]) ? (msg.name || Messages.anonymous) : contactsData[msg.author].displayName; var d = msg.time ? new Date(msg.time) : undefined; @@ -548,7 +548,7 @@ define([ UI.confirm(content, function (yes) { if (!yes) { return; } var mute = Util.isChecked($(content).find('#cp-contacts-mute')); - muteUser(friend); + if (mute) { muteUser(friend); } removeFriend(curvePublic); // TODO remove friend from userlist ui // FIXME seems to trigger EJOINED from netflux-websocket (from server); @@ -806,37 +806,27 @@ define([ // var onJoinRoom // var onLeaveRoom - - var ready = false; - var onMessengerReady = function () { - if (isApp) { return; } - if (ready) { return; } - ready = true; - - execCommand('GET_ROOMS', null, function (err, rooms) { - if (err) { return void console.error(err); } - - debug('rooms: ' + JSON.stringify(rooms)); - rooms.forEach(initializeRoom); - }); - + var updateMutedList = function () { execCommand('GET_MUTED_USERS', null, function (err, muted) { if (err) { return void console.error(err); } - if (!muted || Object.keys(muted).length === 0) { return; } - var $button = $userlist.find('.cp-app-contacts-muted-button'); + + if (!muted || Object.keys(muted).length === 0) { + $button.hide(); + return; + } + var rows = Object.keys(muted).map(function (curve) { var data = muted[curve]; var avatar = h('div.cp-avatar'); var button = h('td', h('i.fa.fa-times', {title: Messages.contacts_unmute})); UIElements.displayAvatar(common, $(avatar), data.avatar, data.name); $(button).click(function () { - execCommand('UNMUTE_USER', { - curvePublic: curve, - }, function (e /*, removed */) { + execCommand('UNMUTE_USER', curve, function (e, data) { if (e) { return void console.error(e); } $(button).closest('tr').remove(); + if (!data) { $button.hide(); } }); }); return h('tr', [ @@ -849,10 +839,27 @@ define([ h('p', Messages.contacts_mutedUsers), h('table', rows) ]); + $button.off('click'); $button.click(function () { UI.alert(content); }).show(); }); + }; + + var ready = false; + var onMessengerReady = function () { + if (isApp) { return; } + if (ready) { return; } + ready = true; + + execCommand('GET_ROOMS', null, function (err, rooms) { + if (err) { return void console.error(err); } + + debug('rooms: ' + JSON.stringify(rooms)); + rooms.forEach(initializeRoom); + }); + + updateMutedList(); $container.removeClass('cp-app-contacts-initializing'); }; @@ -930,6 +937,10 @@ define([ onUpdateData(data); return; } + if (cmd === 'UPDATE_MUTED') { + updateMutedList(); + return; + } if (cmd === 'MESSAGE') { onMessage(data); return; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 0cf362e5d..0a44dfc76 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1253,6 +1253,10 @@ define([ if (friend) { return void cb({error: 'ALREADY_FRIEND'}); } if (!data.notifications || !data.curvePublic) { return void cb({error: 'INVALID_USER'}); } + // Unmute this user when we send them a friend request + var muted = store.proxy.mutedUsers || {}; + delete muted[data.curvePublic]; + store.proxy.friends_pending = store.proxy.friends_pending || {}; var twoDaysAgo = +new Date() - (2 * 24 * 3600 * 1000); diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 00f990eb3..6e10cd5a2 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -12,6 +12,13 @@ define([ var handlers = {}; var removeHandlers = {}; + var isMuted = function (ctx, data) { + var muted = ctx.store.proxy.mutedUsers || {}; + var curvePublic = Util.find(data, ['msg', 'author']); + if (!curvePublic) { return false; } + return Boolean(muted[curvePublic]); + }; + // Store the friend request displayed to avoid duplicates var friendRequest = {}; handlers['FRIEND_REQUEST'] = function (ctx, box, data, cb) { @@ -21,6 +28,8 @@ define([ return void cb(true); } + if (isMuted(ctx, data)) { return void cb(true); } + // Don't show duplicate friend request: if we already have a friend request // in memory from the same user, dismiss the new one if (friendRequest[data.msg.author]) { return void cb(true); } @@ -30,10 +39,22 @@ define([ // If the user is already in our friend list, automatically accept the request if (Messaging.getFriend(ctx.store.proxy, data.msg.author) || ctx.store.proxy.friends_pending[data.msg.author]) { + delete ctx.store.proxy.friends_pending[data.msg.author]; Messaging.acceptFriendRequest(ctx.store, data.msg.content, function (obj) { if (obj && obj.error) { return void cb(); } + Messaging.addToFriendList({ + proxy: ctx.store.proxy, + realtime: ctx.store.realtime, + pinPads: ctx.pinPads + }, data.msg.content, function (err) { + if (err) { console.error(err); } + if (ctx.store.messenger) { + ctx.store.messenger.onFriendAdded(data.msg.content); + } + }); + ctx.updateMetadata(); cb(true); }); return; @@ -170,6 +191,8 @@ define([ var content = msg.content; // content.name, content.title, content.href, content.password + if (isMuted(ctx, data)) { return void cb(true); } + var channel = Hash.hrefToHexChannelId(content.href, content.password); var parsed = Hash.parsePadUrl(content.href); var mode = parsed.hashData && parsed.hashData.mode || 'n/a'; @@ -212,6 +235,9 @@ define([ supportMessage = true; cb(); }; + removeHandlers['SUPPORT_MESSAGE'] = function () { + supportMessage = false; + }; // Incoming edit rights request: add data before sending it to inner handlers['REQUEST_PAD_ACCESS'] = function (ctx, box, data, cb) { @@ -220,6 +246,8 @@ define([ if (msg.author !== content.user.curvePublic) { return void cb(true); } + if (isMuted(ctx, data)) { return void cb(true); } + var channel = content.channel; var res = ctx.store.manager.findChannel(channel); @@ -270,6 +298,9 @@ define([ var content = msg.content; if (msg.author !== content.user.curvePublic) { return void cb(true); } + + if (isMuted(ctx, data)) { return void cb(true); } + if (!content.teamChannel && !(content.href && content.title && content.channel)) { console.log('Remove invalid notification'); return void cb(true); @@ -327,6 +358,9 @@ define([ var content = msg.content; if (msg.author !== content.user.curvePublic) { return void cb(true); } + + if (isMuted(ctx, data)) { return void cb(true); } + if (!content.team) { console.log('Remove invalid notification'); return void cb(true); diff --git a/www/common/outer/messenger.js b/www/common/outer/messenger.js index 90884198e..5d4c67135 100644 --- a/www/common/outer/messenger.js +++ b/www/common/outer/messenger.js @@ -458,12 +458,22 @@ define([ } }; + var getAllClients = function (ctx) { + var all = []; + Array.prototype.push.apply(all, ctx.friendsClients); + Object.keys(ctx.channels).forEach(function (id) { + Array.prototype.push.apply(all, ctx.channels[id].clients); + }); + return Util.deduplicateString(all); + }; + var muteUser = function (ctx, data, _cb) { var cb = Util.once(Util.mkAsync(_cb)); var proxy = ctx.store.proxy; var muted = proxy.mutedUsers = proxy.mutedUsers || {}; if (muted[data.curvePublic]) { return void cb(); } muted[data.curvePublic] = data; + ctx.emit('UPDATE_MUTED', null, getAllClients(ctx)); cb(); }; var unmuteUser = function (ctx, curvePublic, _cb) { @@ -471,7 +481,8 @@ define([ var proxy = ctx.store.proxy; var muted = proxy.mutedUsers = proxy.mutedUsers || {}; delete muted[curvePublic]; - cb(); + ctx.emit('UPDATE_MUTED', null, getAllClients(ctx)); + cb(Object.keys(muted).length); }; var getMutedUsers = function (ctx, cb) { var proxy = ctx.store.proxy; @@ -687,7 +698,14 @@ define([ nThen(function (waitFor) { // Load or get all friends channels Object.keys(friends).forEach(function (key) { - if (key === 'me') { return; } + if (key === 'me') { + // At some point a bug inserted a friend's channel into our "me" data. + // This led to displaying our name instead of our friend's name in the + // contacts app. The following line is here to prevent this issue to happen + // again. + delete friends.me.channel; + return; + } var friend = clone(friends[key]); if (typeof(friend) !== 'object') { return; } if (!friend.channel) { return; } @@ -910,15 +928,6 @@ define([ }); }; - var getAllClients = function (ctx) { - var all = []; - Array.prototype.push.apply(all, ctx.friendsClients); - Object.keys(ctx.channels).forEach(function (id) { - Array.prototype.push.apply(all, ctx.channels[id].clients); - }); - return Util.deduplicateString(all); - }; - Msg.init = function (cfg, waitFor, emit) { var messenger = {}; var store = cfg.store; @@ -934,6 +943,9 @@ define([ range_requests: {} }; + store.proxy.on('change', ['mutedUsers'], function () { + ctx.emit('UPDATE_MUTED', null, getAllClients(ctx)); + }); ctx.store.network.on('message', function(msg, sender) { onDirectMessage(ctx, msg, sender); @@ -965,6 +977,12 @@ define([ var channel = friend.channel; if (!channel) { return; } + // Already friend? don't load the channel a second time + var chanId = friend.channel; + var chan = ctx.channels[chanId]; + if (chan) { return; } + + // Load the channel and add the friend to the contacts app loadFriend(ctx, null, friend, function () { emit('FRIEND', { curvePublic: friend.curvePublic, From aa8dd95310ee8d2c411936d58abe4bd9e4bcd62b Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 11 Dec 2019 13:28:07 +0100 Subject: [PATCH 03/21] Add unfriend, mute and unmute buttons in profile --- www/common/common-ui-elements.js | 1 - www/common/messenger-ui.js | 4 +- www/common/outer/messenger.js | 7 ++-- www/profile/inner.js | 66 ++++++++++++++++++++++++++++++-- 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 992428899..459f4319d 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -4174,7 +4174,6 @@ define([ }, function (e) { cb(e); if (e) { return void UI.warn(Messages.error); } - UI.log(Messages.success); }); }); }); diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js index 9ce795f4e..f157f2a08 100644 --- a/www/common/messenger-ui.js +++ b/www/common/messenger-ui.js @@ -542,7 +542,7 @@ define([ var friend = contactsData[curvePublic] || friendData; var muteBox = UI.createCheckbox('cp-contacts-mute', Messages.contacts_mute, false); var content = h('div', [ - h('p', Messages._getKey('contacts_confirmRemove', [Util.fixHTML(friend.name)])), + UI.setHTML(h('p'), Messages._getKey('contacts_confirmRemove', [Util.fixHTML(friend.name)])), muteBox ]); UI.confirm(content, function (yes) { @@ -553,7 +553,7 @@ define([ // TODO remove friend from userlist ui // FIXME seems to trigger EJOINED from netflux-websocket (from server); // (tried to join a channel in which you were already present) - }, undefined, true); + }); }); if (friendData.avatar && avatars[friendData.avatar]) { diff --git a/www/common/outer/messenger.js b/www/common/outer/messenger.js index 5d4c67135..4d5e42973 100644 --- a/www/common/outer/messenger.js +++ b/www/common/outer/messenger.js @@ -428,20 +428,21 @@ define([ } var channel = ctx.channels[data.channel]; - if (!channel) { - return void cb({error: "NO_SUCH_CHANNEL"}); - } // Unfriend with mailbox if (ctx.store.mailbox && data.curvePublic && data.notifications) { Messaging.removeFriend(ctx.store, curvePublic, function (obj) { if (obj && obj.error) { return void cb({error:obj.error}); } + ctx.updateMetadata(); cb(obj); }); return; } // Unfriend with channel + if (!channel) { + return void cb({error: "NO_SUCH_CHANNEL"}); + } try { var msg = [Types.unfriend, proxy.curvePublic, +new Date()]; var msgStr = JSON.stringify(msg); diff --git a/www/profile/inner.js b/www/profile/inner.js index 4635743f6..301fa0808 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -178,9 +178,6 @@ define([ var addFriendRequest = function ($container) { if (!APP.readOnly || !APP.common.isLoggedIn()) { return; } - APP.$friend = $('