diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 0bb596ec8..281f213f6 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -103,7 +103,7 @@ define([ ])*/ ]) ]), - h('div.cp-version-footer', "CryptPad v3.7.0 (HimalayanQuail)") + h('div.cp-version-footer', "CryptPad v3.8.0 (IsolobodonPortoricensis)") ]); }; diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index d8b220975..a2b787e1c 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -3,6 +3,7 @@ @import (reference) "./variables.less"; @import (reference) "./avatar.less"; @import (reference) "./tools.less"; +@import (reference) "./buttons.less"; .alertify_main() { --LessLoader_require: LessLoader_currentFile(); @@ -225,28 +226,6 @@ ::-ms-input-placeholder { /* Microsoft Edge */ color: @cryptpad_color_grey; } - input:not(.form-control), textarea { - background-color: @alertify-input-fg; - color: @cryptpad_text_col; - border: 1px solid @alertify-input-bg; - margin-bottom: @alertify_padding-base; - width: 100%; - font-size: 100%; - padding: @alertify_padding-base; - &[readonly] { - background-color: @alertify-light-bg; - color: @cryptpad_text_col; - border-color: @alertify-light-bg; - } - } - - textarea { - overflow: hidden; - padding: 8px; - &[readonly] { - resize: none; - } - } span.cp-password-container { display: flex; @@ -281,99 +260,17 @@ } } - button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) { + .buttons_main(); + input:not(.form-control), textarea { + margin-bottom: 15px; + } - background-color: @colortheme_alertify-cancel; - box-sizing: border-box; - position: relative; - outline: 0; + button { display: inline-block; - align-items: center; - padding: 0 6px; + position: relative; margin: 6px 8px; - line-height: 36px; min-height: 36px; - white-space: nowrap; min-width: 88px; - text-align: center; - text-transform: uppercase; - font-size: 14px; - text-decoration: none; - cursor: pointer; - border-radius: 0; - - color: @alertify-btn-fg; - border: 1px solid @alertify-btn-fg; - - &.no-margin { - margin: 0; - } - - &:hover, &:active { - background-color: @alertify-light-bg; - } - - &.safe, &.danger { - color: @colortheme_old-base; - white-space: normal; - font-weight: bold; - } - &.danger { - background-color: @colortheme_alertify-red; - border-color: @colortheme_alertify-red-border; - color: @colortheme_alertify-red-color; - &:hover, &:active { - background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%)); - } - } - - &.safe { - background-color: @colortheme_alertify-green; - border-color: @colortheme_alertify-green-border; - color: @colortheme_alertify-green-color; - &:hover, &:active { - background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%)); - } - } - - &.primary { - background-color: @colortheme_alertify-primary; - color: @colortheme_alertify-primary-text; - border-color: @colortheme_alertify-primary-border; - font-weight: bold; - &:hover, &:active { - background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%)); - } - } - - &.cancel { - border-color: @colortheme_alertify-cancel-border; - color: @colortheme_alertify-cancel-border; - &:hover, &:hover { - background-color: fade(@colortheme_alertify-cancel-border, 25%); - } - } - - - - &:focus { - //border: 1px dotted @alertify-base; - box-shadow: 0px 0px 5px @colortheme_alertify-primary; - outline: none; - } - &::-moz-focus-inner { - border: 0; - } - - &:disabled { - cursor: not-allowed !important; - background-color: @colortheme_alertify-disabled; - color: @colortheme_alertify-disabled-text; - border-color: @colortheme_alertify-disabled-border; - &:hover, &:active { - background-color: @colortheme_alertify-disabled; - } - } } nav { diff --git a/customize.dist/src/less2/include/buttons.less b/customize.dist/src/less2/include/buttons.less new file mode 100644 index 000000000..f3cb50e17 --- /dev/null +++ b/customize.dist/src/less2/include/buttons.less @@ -0,0 +1,129 @@ +@import (reference) "./colortheme-all.less"; +@import (reference) "./variables.less"; + +.buttons_main() { + @alertify-fore: @colortheme_modal-fg; + @alertify-btn-fg: @alertify-fore; + @alertify-light-bg: fade(@alertify-fore, 25%); + @alertify_padding-base: @variables_padding; + @alertify-input-bg: @colortheme_modal-input; + @alertify-input-fg: @colortheme_modal-input-fg; + + input:not(.form-control), textarea { + background-color: @alertify-input-fg; + color: @cryptpad_text_col; + border: 1px solid @alertify-input-bg; + width: 100%; + font-size: 100%; + padding: @alertify_padding-base; + &[readonly] { + background-color: @alertify-light-bg; + color: @cryptpad_text_col; + border-color: @alertify-input-fg; + } + } + + textarea { + overflow: hidden; + padding: 8px; + &[readonly] { + resize: none; + } + } + + + button:not(.pure-button):not(.md-button):not(.mdl-button) { + + background-color: @colortheme_alertify-cancel; + box-sizing: border-box; + outline: 0; + align-items: center; + padding: 0 6px; + line-height: 36px; + white-space: nowrap; + text-align: center; + text-transform: uppercase; + font-size: 14px; + text-decoration: none; + cursor: pointer; + border-radius: 0; + + .fa { + margin-right: 0.2em; + } + + color: @alertify-btn-fg; + border: 1px solid @alertify-btn-fg; + + &.no-margin { + margin: 0; + } + + &:hover, &:active { + background-color: lighten(@alertify-fore, 35%); + } + + &.safe, &.danger, &.btn-safe, &.btn-danger { + color: @colortheme_old-base; + white-space: normal; + font-weight: bold; + } + &.danger, &.btn-danger { + background-color: @colortheme_alertify-red; + border-color: @colortheme_alertify-red-border; + color: @colortheme_alertify-red-color; + &:hover, &:active { + background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%)); + } + } + + &.safe, &.btn-safe { + background-color: @colortheme_alertify-green; + border-color: @colortheme_alertify-green-border; + color: @colortheme_alertify-green-color; + &:hover, &:active { + background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%)); + } + } + + &.primary, &.btn-primary { + background-color: @colortheme_alertify-primary; + color: @colortheme_alertify-primary-text; + border-color: @colortheme_alertify-primary-border; + font-weight: bold; + &:hover, &:active { + background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%)); + } + } + + &.cancel, &.btn-cancel { + border-color: @colortheme_alertify-cancel-border; + color: @colortheme_alertify-cancel-border; + &:hover, &:hover { + background-color: fade(@colortheme_alertify-cancel-border, 25%); + } + } + + + + &:focus { + //border: 1px dotted @alertify-base; + box-shadow: 0px 0px 5px @colortheme_alertify-primary; + outline: none; + } + &::-moz-focus-inner { + border: 0; + } + + &:disabled { + cursor: not-allowed !important; + background-color: @colortheme_alertify-disabled; + color: @colortheme_alertify-disabled-text; + border-color: @colortheme_alertify-disabled-border; + &:hover, &:active { + background-color: @colortheme_alertify-disabled; + } + } + } + +} 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..ddf1540c9 100644 --- a/customize.dist/src/less2/include/messenger.less +++ b/customize.dist/src/less2/include/messenger.less @@ -78,6 +78,9 @@ .cp-app-contacts-name { white-space: nowrap; } + .cp-app-contacts-icons { + text-align: right; + } } &:hover { background-color: rgba(0,0,0,0.3); @@ -89,6 +92,7 @@ .cp-app-contacts-remove { cursor: pointer; width: 20px; + text-align: center; &:hover { color: darken(@color, 20%); } @@ -121,8 +125,30 @@ } } } + .cp-app-contacts-muted-button { + margin: 10px; + border: 0; + display: none; + order: 3; + .fa-bell-slash { + margin-right: 10px; + } + } } } + + .cp-contacts-muted-table { + .cp-contacts-muted-user { + margin-bottom: 5px; + .cp-avatar { + margin-right: 10px; + } + button { + margin-right: 0px; + } + } + } + #cp-app-contacts-container.cp-app-contacts-inapp { #cp-app-contacts-friendlist { display: none; diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index 24228cfaf..1f9c92457 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -1,5 +1,6 @@ @import (reference) "/customize/src/less2/include/colortheme-all.less"; @import (reference) "/customize/src/less2/include/leftside-menu.less"; +@import (reference) "/customize/src/less2/include/buttons.less"; @sidebar_button-width: 400px; @@ -95,9 +96,11 @@ } } margin-bottom: 20px; + .buttons_main(); } [type="text"], [type="password"], button { vertical-align: middle; + min-width: 40px; height: 40px; box-sizing: border-box; } @@ -106,12 +109,12 @@ width: @sidebar_button-width; input { flex: 1; - border-radius: 0.25em 0 0 0.25em; + //border-radius: 0.25em 0 0 0.25em; border: 1px solid #adadad; border-right: 0px; } button { - border-radius: 0 0.25em 0.25em 0; + //border-radius: 0 0.25em 0.25em 0; //border: 1px solid #adadad; border-left: 0px; } @@ -119,6 +122,13 @@ &>div { margin: 10px 0; } + button.btn { + margin: 0 5px 0 0; + } + span.cp-password-container { + margin-bottom: 1px; + } +/* button.btn { @button-bg: @colortheme_sidebar-button-bg; @button-red-bg: @colortheme_sidebar-button-red-bg; @@ -126,6 +136,9 @@ background-color: @button-bg; border-color: darken(@button-bg, 10%); color: white; + .fa { + margin-right: 0.2em; + } &:hover { background-color: darken(@button-bg, 10%); } @@ -146,6 +159,7 @@ } } } +*/ } } } diff --git a/package-lock.json b/package-lock.json index 6643e22c7..08328fcb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cryptpad", - "version": "3.7.0", + "version": "3.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ea1431153..835641d6b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "3.7.0", + "version": "3.8.0", "license": "AGPL-3.0+", "repository": { "type": "git", 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/common-interface.js b/www/common/common-interface.js index 4c78d2488..4528b7a1b 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -592,6 +592,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 c1c8bf2b1..42ee58f93 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -4157,7 +4157,7 @@ define([ }; var content = h('div.cp-share-modal', [ - setHTML(h('p'), text) + setHTML(h('p'), text), ]); UI.proposal(content, todo); }; 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..7643939e5 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; @@ -13,6 +14,8 @@ define([ var MessengerUI = {}; + var mutedUsers = {}; + var dataQuery = function (id) { return '[data-key="' + id + '"]'; }; @@ -67,8 +70,11 @@ 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('h2.cp-app-contacts-category-title', Messages.contacts_friends), + h('button.cp-app-contacts-muted-button',[ + h('i.fa.fa-bell-slash'), + Messages.contacts_manageMuted + ]), + h('div.cp-app-contacts-category-content.cp-contacts-friends') ]), h('div.cp-app-contacts-rooms.cp-app-contacts-category', [ h('div.cp-app-contacts-category-content'), @@ -184,7 +190,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; @@ -486,6 +492,20 @@ define([ } }; + var unmuteUser = function (curve) { + execCommand('UNMUTE_USER', curve, function (e) { + if (e) { return void console.error(e); } + }); + }; + 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); } @@ -499,6 +519,21 @@ define([ title: room.name }); + + var curve; + if (room.isFriendChat) { + var __channel = state.channels[id]; + curve = __channel.curvePublic; + } + + var unmute = h('span.cp-app-contacts-remove.fa.fa-bell.cp-unmute-icon', { + title: Messages.contacts_unmute || 'unmute', + style: (curve && mutedUsers[curve]) ? undefined : 'display: none;' + }); + var mute = h('span.cp-app-contacts-remove.fa.fa-bell-slash.cp-mute-icon', { + title: Messages.contacts_mute || 'mute', + style: (curve && mutedUsers[curve]) ? 'display: none;' : undefined + }); var remove = h('span.cp-app-contacts-remove.fa.fa-user-times', { title: Messages.contacts_remove }); @@ -511,8 +546,12 @@ define([ }); var rightCol = h('span.cp-app-contacts-right-col', [ h('span.cp-app-contacts-name', [room.name]), - room.isFriendChat ? remove : - (room.isPadChat || room.isTeamChat) ? undefined : leaveRoom, + h('span.cp-app-contacts-icons', [ + room.isFriendChat ? mute : undefined, + room.isFriendChat ? unmute : undefined, + room.isFriendChat ? remove : + (room.isPadChat || room.isTeamChat) ? undefined : leaveRoom, + ]) ]); var friendData = room.isFriendChat ? userlist[0] : {}; @@ -523,23 +562,43 @@ define([ if (friendData.profile) { window.open(origin + '/profile/#' + friendData.profile); } }); + $(unmute).on('click dblclick', function (e) { + e.stopPropagation(); + var channel = state.channels[id]; + if (!channel.isFriendChat) { return; } + var curvePublic = channel.curvePublic; + $(mute).show(); + $(unmute).hide(); + unmuteUser(curvePublic); + }); + + $(mute).on('click dblclick', function (e) { + e.stopPropagation(); + var channel = state.channels[id]; + if (!channel.isFriendChat) { return; } + var curvePublic = channel.curvePublic; + var friend = contactsData[curvePublic] || friendData; + $(mute).hide(); + $(unmute).show(); + muteUser(friend); + }); + $(remove).click(function (e) { e.stopPropagation(); var channel = state.channels[id]; 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 content = h('div', [ + UI.setHTML(h('p'), Messages._getKey('contacts_confirmRemove', [Util.fixHTML(friend.name)])), + ]); + UI.confirm(content, function (yes) { if (!yes) { return; } - removeFriend(curvePublic, function (e) { - if (e) { return void console.error(e); } - }); + 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) - }, undefined, true); + }); }); if (friendData.avatar && avatars[friendData.avatar]) { @@ -792,6 +851,62 @@ define([ // var onJoinRoom // var onLeaveRoom + var updateMutedList = function () { + execCommand('GET_MUTED_USERS', null, function (err, muted) { + if (err) { return void console.error(err); } + mutedUsers = muted; + + var $button = $userlist.find('.cp-app-contacts-muted-button'); + + $('.cp-app-contacts-friend[data-user]') + .find('.cp-mute-icon').show(); + $('.cp-app-contacts-friend[data-user]') + .find('.cp-unmute-icon').hide(); + if (!muted || Object.keys(muted).length === 0) { + $button.hide(); + return; + } + + var rows = Object.keys(muted).map(function (curve) { + $('.cp-app-contacts-friend[data-user="'+curve+'"]') + .find('.cp-mute-icon').hide(); + $('.cp-app-contacts-friend[data-user="'+curve+'"]') + .find('.cp-unmute-icon').show(); + var data = muted[curve]; + var avatar = h('span.cp-avatar'); + var button = h('button', { + 'data-user': curve + }, [ + h('i.fa.fa-bell'), + Messages.contacts_unmute || 'unmute' + ]); + UIElements.displayAvatar(common, $(avatar), data.avatar, data.name); + $(button).click(function () { + unmuteUser(curve, button); + execCommand('UNMUTE_USER', curve, function (e, data) { + if (e) { return void console.error(e); } + $(button).closest('div').remove(); + if (!data) { $button.hide(); } + $('.cp-app-contacts-friend[data-user="'+curve+'"]') + .find('.cp-mute-icon').show(); + }); + }); + return h('div.cp-contacts-muted-user', [ + h('span', avatar), + h('span', data.name), + button + ]); + }); + var content = h('div', [ + h('h4', Messages.contacts_mutedUsers || 'Muted users...'), + h('div.cp-contacts-muted-table', rows) + ]); + $button.off('click'); + $button.click(function () { + UI.alert(content); + }).show(); + }); + }; var ready = false; var onMessengerReady = function () { @@ -806,6 +921,8 @@ define([ rooms.forEach(initializeRoom); }); + updateMutedList(); + $container.removeClass('cp-app-contacts-initializing'); }; @@ -882,6 +999,10 @@ define([ onUpdateData(data); return; } + if (cmd === 'UPDATE_MUTED') { + updateMutedList(); + return; + } if (cmd === 'MESSAGE') { onMessage(data); return; 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 d0e200deb..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); @@ -458,6 +459,40 @@ 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) { + var cb = Util.once(Util.mkAsync(_cb)); + var proxy = ctx.store.proxy; + var muted = proxy.mutedUsers = proxy.mutedUsers || {}; + delete muted[curvePublic]; + ctx.emit('UPDATE_MUTED', null, getAllClients(ctx)); + cb(Object.keys(muted).length); + }; + 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; @@ -664,7 +699,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; } @@ -887,15 +929,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; @@ -911,6 +944,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); @@ -942,6 +978,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, @@ -990,6 +1032,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 +1047,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); } diff --git a/www/contacts/app-contacts.less b/www/contacts/app-contacts.less index 32753f136..88a93ae9d 100644 --- a/www/contacts/app-contacts.less +++ b/www/contacts/app-contacts.less @@ -1,5 +1,7 @@ @import (reference) '../../customize/src/less2/include/framework.less'; @import (reference) '../../customize/src/less2/include/messenger.less'; +@import (reference) '../../customize/src/less2/include/avatar.less'; +@import (reference) '../../customize/src/less2/include/buttons.less'; // body &.cp-app-contacts { @@ -17,6 +19,22 @@ display: flex; // We need this to remove a 3px border at the bottom of the toolbar } + .cp-app-contacts-friends { + .buttons_main(); + } + .cp-contacts-muted-table { + .avatar_main(50px); + .cp-contacts-muted-user { + display: flex; + align-items: center; + span:nth-child(2) { + flex: 1; + } + } + } + + + .messenger_main(); } diff --git a/www/profile/app-profile.less b/www/profile/app-profile.less index 1284f31c4..61b3bbbe6 100644 --- a/www/profile/app-profile.less +++ b/www/profile/app-profile.less @@ -10,8 +10,15 @@ ); .sidebar-layout_main(); + @cp-profile-is-your-friend: #777; + display: flex; flex-flow: column; + + #cp-sidebarlayout-leftside { + display: none !important; + } + #cp-app-profile-header { display: flex; #cp-app-profile-rightside { @@ -64,8 +71,9 @@ } } button { + min-width: 40px; height: 40px; - margin: 5px; + margin: 5px !important; } } .cp-app-profile-resizer { @@ -108,10 +116,18 @@ display: none; } & > button:empty { - margin-left: 25px; + margin-left: 25px !important; } } + .cp-app-profile-friend { + display: flex; + align-items: center; + color: @cp-profile-is-your-friend; + .fa { + margin-right: 0.2em; + } + } .cp-app-profile-friend-request { flex: 0; width: 400px; @@ -124,11 +140,7 @@ } .cp-app-profile-viewprofile-button { margin-bottom: 20px; - float: right; - margin-left: 5px; - &> span { - margin-left: 10px; - } + width: 300px; } #cp-app-profile-description { position: relative; @@ -166,13 +178,6 @@ line-height: inherit; } } - .cp-app-profile-description-edit { - & > button { - span { - margin-left: 10px; - } - } - } } #cp-app-profile-create { height: 100%; @@ -181,5 +186,12 @@ align-items: center; justify-content: center; } + .cp-app-profile-mute-container { + margin-top: 5px; + p { + margin-top: 5px; + font-size: 13px; + } + } } diff --git a/www/profile/inner.js b/www/profile/inner.js index 054144342..a1d070313 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 = $('