diff --git a/customize.dist/src/less2/include/avatar.less b/customize.dist/src/less2/include/avatar.less index 725c7748f..acaa13351 100644 --- a/customize.dist/src/less2/include/avatar.less +++ b/customize.dist/src/less2/include/avatar.less @@ -4,7 +4,11 @@ @width: 30px ) { @avatar-width: @width; - @avatar-font-size: @width / 1.2; + @avatar-font-size: @width / 1.8; + // scale animal avatar to be somewhat larger, because: + // 1. emojis are wider than most latin characters + // 2. they should occupy the width of two average characters + @avatar-font-size-animal: @avatar-font-size * (6/5); } .avatar_main(@width: 30px) { --LessLoader_require: LessLoader_currentFile(); @@ -40,7 +44,9 @@ color: @cp_avatar-fg; font-size: @avatar-font-size; font-size: var(--avatar-font-size); - text-transform: capitalize; + .animal { + font-size: @avatar-font-size-animal; + } } media-tag { min-height: @avatar-width; diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index b0f9b5e42..2092d72cc 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -855,10 +855,15 @@ span { text-align: center; width: 100%; - font-size: 48px; + .avatar_vars(72px); + font-size: @avatar-font-size; display: inline-flex; justify-content: center; align-items: center; + .animal { + font-size: @avatar-font-size-animal; + + } } &.cp-avatar { .avatar_main(64px); diff --git a/www/code/markers.js b/www/code/markers.js index b070e68fc..48e5a25fb 100644 --- a/www/code/markers.js +++ b/www/code/markers.js @@ -3,7 +3,9 @@ define([ '/common/sframe-common-codemirror.js', '/customize/messages.js', '/bower_components/chainpad/chainpad.dist.js', -], function (Util, SFCodeMirror, Messages, ChainPad) { + '/common/inner/common-mediatag.js', + '/common/common-interface.js', +], function (Util, SFCodeMirror, Messages, ChainPad, MT, UI) { var Markers = {}; /* TODO Known Issues @@ -38,7 +40,17 @@ define([ }); } uid = Number(uid); - var name = Util.fixHTML(author.name || Messages.anonymous); + var name = Util.fixHTML(UI.getDisplayName(author.name)); + var animal; + if ((!name || name === Messages.anonymous) && typeof(author.uid) === 'string') { + animal = MT.getPseudorandomAnimal(author.uid); + if (animal) { + name = animal + ' ' + Messages.anonymous; + } else { + name = Messages.anonymous; + } + } + var col = Util.hexToRGB(author.color); var rgba = 'rgba('+col[0]+','+col[1]+','+col[2]+','+Env.opacity+');'; return Env.editor.markText(from, to, { @@ -520,7 +532,8 @@ define([ Env.authormarks.authors[Env.myAuthorId] = { name: userData.name, curvePublic: userData.curvePublic, - color: userData.color + color: userData.color, + uid: userData.uid, }; if (!old || (old.name === userData.name && old.color === userData.color)) { return; } return true; diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 3be979f17..2993edc52 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -208,5 +208,7 @@ define(function() { // the driveless mode by changing the following value to "false" AppConfig.allowDrivelessMode = true; + AppConfig.emojiAvatars = '🙈 đŸĻ€ 🐞 đŸĻ‹ đŸŦ 🐋 đŸĸ đŸĻ‰ đŸĻ† 🐧 đŸĻĄ đŸĻ˜ đŸĻ¨ đŸĻĻ đŸĻĨ đŸŧ đŸģ đŸĻ đŸĻ“ 🐄 🐷 🐐 đŸĻ™ đŸĻ’ 🐘 đŸĻ 🐁 🐹 🐰 đŸĻĢ đŸĻ” 🐨 🐱 đŸē đŸ‘ē 👹 đŸ‘Ŋ 👾 🤖'.split(/\s+/); + return AppConfig; }); diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 71a1ed8af..5c2431efd 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -41,6 +41,10 @@ define([ return e; }; + UI.getDisplayName = function (name) { + return (typeof(name) === 'string'? name: "").trim() || Messages.anonymous; + }; + // FIXME almost everywhere this is used would also be // a good candidate for sframe-common's getMediatagFromHref UI.mediaTag = function (src, key) { diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index 65f05961e..1e6e26ce3 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -17,7 +17,8 @@ define([ edPublic: proxy.edPublic, curvePublic: proxy.curvePublic, notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']), - avatar: proxy.profile && proxy.profile.avatar + avatar: proxy.profile && proxy.profile.avatar, + uid: proxy.uid, // XXX test without this and see if it breaks things }; if (hash === false) { delete data.channel; } return data; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 8315768f1..abf45b0bd 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -156,9 +156,11 @@ define([ var icons = Object.keys(users).map(function (key, i) { var data = users[key]; - var name = data.displayName || data.name || Messages.anonymous; - var avatar = h('span.cp-usergrid-avatar.cp-avatar'); - common.displayAvatar($(avatar), data.avatar, name); + var name = UI.getDisplayName(data.displayName || data.name); + var avatar = h('span.cp-usergrid-avatar.cp-avatar', { + 'aria-hidden': true, // XXX aria + }); + common.displayAvatar($(avatar), data.avatar, name, Util.noop, data.uid); var removeBtn, el; if (config.remove) { removeBtn = h('span.fa.fa-times'); @@ -1989,13 +1991,16 @@ define([ var $displayName = $userAdmin.find('.'+displayNameCls); - var $avatar = $userAdmin.find('> button .cp-dropdown-button-title'); + var $avatar = $userAdmin.find('> button .cp-dropdown-button-title'); // XXX alt="User menu" var loadingAvatar; var to; var oldUrl = ''; + var oldUid; + var oldName; var updateButton = function () { var myData = metadataMgr.getUserData(); var privateData = metadataMgr.getPrivateData(); + var uid = myData.uid; if (!priv.plan && privateData.plan) { config.$initBlock.empty(); metadataMgr.off('change', updateButton); @@ -2010,18 +2015,21 @@ define([ return; } loadingAvatar = true; - var newName = myData.name; + var newName = UI.getDisplayName(myData.name); var url = myData.avatar; - $displayName.text(newName || Messages.anonymous); - if (accountName && oldUrl !== url) { + $displayName.text(newName); + if ((accountName && oldUrl !== url) || !accountName && uid !== oldUid || oldName !== newName) { $avatar.html(''); - Common.displayAvatar($avatar, url, - newName || Messages.anonymous, function ($img) { + Common.displayAvatar($avatar, url, newName, function ($img) { oldUrl = url; + oldUid = uid; + oldName = newName; $userAdmin.find('> button').removeClass('cp-avatar'); if ($img) { $userAdmin.find('> button').addClass('cp-avatar'); } loadingAvatar = false; - }); + + // XXX alt="User menu" + }, uid); return; } loadingAvatar = false; @@ -2303,6 +2311,7 @@ define([ var teams = Object.keys(privateData.teams).map(function (id) { var data = privateData.teams[id]; var avatar = h('span.cp-creation-team-avatar.cp-avatar'); + // We assume that teams always have a non-empty name, so we don't need a UID common.displayAvatar($(avatar), data.avatar, data.name); return h('div.cp-creation-team', { 'data-id': id, @@ -3106,7 +3115,7 @@ define([ var sframeChan = common.getSframeChannel(); var msg = data.content.msg; - var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; + var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName)); var title = Util.fixHTML(msg.content.title); var text = Messages._getKey('owner_add', [name, title]); @@ -3238,7 +3247,7 @@ define([ var sframeChan = common.getSframeChannel(); var msg = data.content.msg; - var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; + var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName)); var title = Util.fixHTML(msg.content.title); var text = Messages._getKey('owner_team_add', [name, title]); @@ -3353,13 +3362,15 @@ define([ var verified = h('p'); var $verified = $(verified); + name = UI.getDisplayName(name); if (priv.friends && priv.friends[curve]) { $verified.addClass('cp-notifications-requestedit-verified'); var f = priv.friends[curve]; $verified.append(h('span.fa.fa-certificate')); var $avatar = $(h('span.cp-avatar')).appendTo($verified); - $verified.append(h('p', Messages._getKey('isContact', [f.displayName]))); - common.displayAvatar($avatar, f.avatar, f.displayName); + name = UI.getDisplayName(f.displayName); + $verified.append(h('p', Messages._getKey('isContact', [name]))); + common.displayAvatar($avatar, f.avatar, name, Util.noop, f.uid); } else { $verified.append(Messages._getKey('isNotContact', [name])); } @@ -3369,7 +3380,7 @@ define([ UIElements.displayInviteTeamModal = function (common, data) { var msg = data.content.msg; - var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; + var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName)); var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || ''); var verified = UIElements.getVerifiedFriend(common, msg.author, name); @@ -3453,7 +3464,8 @@ define([ name: f.displayName, curvePublic: f.curvePublic, profile: f.profile, - notifications: f.notifications + notifications: f.notifications, + uid: f.uid, }; }); }; @@ -3552,7 +3564,7 @@ define([ }; // Set the value to receive from the autocomplete var toInsert = function (data, key) { - var name = data.name.replace(/[^a-zA-Z0-9]+/g, "-"); + var name = UI.getDisplayName(data.name.replace(/[^a-zA-Z0-9]+/g, "-")); return "[@"+name+"|"+key+"]"; }; @@ -3605,18 +3617,20 @@ define([ var avatar = h('span.cp-avatar', { contenteditable: false }); - common.displayAvatar($(avatar), data.avatar, data.name); + + var displayName = UI.getDisplayName(data.name); + common.displayAvatar($(avatar), data.avatar, displayName); // XXX return h('span.cp-mentions', { 'data-curve': data.curvePublic, 'data-notifications': data.notifications, 'data-profile': data.profile, - 'data-name': Util.fixHTML(data.name), + 'data-name': Util.fixHTML(displayName), 'data-avatar': data.avatar || "", }, [ avatar, h('span.cp-mentions-name', { contenteditable: false - }, data.name) + }, displayName) ]); }; } @@ -3648,7 +3662,7 @@ define([ }).map(function (key) { var data = sources[key]; return { - label: data.name, + label: UI.getDisplayName(data.name), value: key }; }); @@ -3683,10 +3697,12 @@ define([ var obj = sources[key]; if (!obj) { return; } var avatar = h('span.cp-avatar'); - common.displayAvatar($(avatar), obj.avatar, obj.name); + var displayName = UI.getDisplayName(obj.name); + + common.displayAvatar($(avatar), obj.avatar, displayName, Util.noop, obj.uid); var li = h('li.cp-autocomplete-value', [ avatar, - h('span', obj.name) + h('span', displayName), ]); return $(li).appendTo(ul); }; diff --git a/www/common/inner/access.js b/www/common/inner/access.js index 3f281dc47..0ba857a0a 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -171,7 +171,7 @@ define([ if (!Object.keys(_friends).length) { var friendText; if (!friendKeys.length) { - console.error(UIElements.noContactsMessage(common)); + //console.error(UIElements.noContactsMessage(common)); var findContacts = UIElements.noContactsMessage(common); friendText = h('span.cp-app-prop-content', findContacts.content @@ -772,7 +772,8 @@ define([ if (friend.edPublic !== ed || c === 'me') { return; } _owners[friend.edPublic] = { name: friend.displayName, - avatar: friend.avatar + avatar: friend.avatar, + uid: friend.uid, }; return true; })) { @@ -782,6 +783,11 @@ define([ _owners[ed] = { avatar: '?', name: Messages.owner_unknownUser, + // TODO a possible enhancement is to use data from the context + // ie. if you have opened the access modal from within the pad + // its owner might be present or they might have left some data + // in the pad itself (as is the case of the uid in rich text comments) + // TODO or just implement "Acquaintances" }; strangers++; }); diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 1d88e1029..21cb25e82 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -6,12 +6,13 @@ define([ '/common/hyperscript.js', '/common/media-tag.js', '/customize/messages.js', + '/customize/application_config.js', '/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/croppie/croppie.min.js', '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/croppie/croppie.css', -], function ($, Util, Hash, UI, h, MediaTag, Messages) { +], function ($, Util, Hash, UI, h, MediaTag, Messages, AppConfig) { var MT = {}; var Nacl = window.nacl; @@ -43,9 +44,16 @@ define([ }); }; + var animal_avatars = {}; MT.getCursorAvatar = function (cursor) { + var uid = cursor.uid; + // TODO it would be nice to have "{0} is editing" instead of just their name var html = ''; - html += (cursor.avatar && avatars[cursor.avatar]) || ''; + if (cursor.avatar && avatars[cursor.avatar]) { + html += avatars[cursor.avatar]; + } else if (animal_avatars[uid]) { + html += animal_avatars[uid] + ' '; + } html += Util.fixHTML(cursor.name) + ''; return html; }; @@ -79,12 +87,68 @@ define([ }); }; - MT.displayAvatar = function (common, $container, href, name, _cb) { + // https://emojipedia.org/nature/ + var ANIMALS = AppConfig.emojiAvatars || []; + + var getRandomAnimal = function () { // XXX should never actually happen? + if (!ANIMALS.length) { return ''; } + return ANIMALS[Math.floor(Math.random() * ANIMALS.length)]; + }; + + var getPseudorandomAnimal = MT.getPseudorandomAnimal = function (seed) { + if (!ANIMALS.length) { return ''; } + if (typeof(seed) !== 'string') { return getRandomAnimal(); } + seed = seed.replace(/\D/g, '').slice(0, 10); // XXX possible optimization for on-wire uid + seed = parseInt(seed); + if (!seed) { return getRandomAnimal(); } + return ANIMALS[seed % ANIMALS.length] || ''; + }; + + var getPrettyInitials = MT.getPrettyInitials = function (name) { + var parts = name.split(/\s+/); + var text; + if (parts.length > 1) { + text = parts.slice(0, 2).map(Util.getFirstCharacter).join(''); + } else { + text = Util.getFirstCharacter(name); + var second = Util.getFirstCharacter(name.replace(text, '')); + if (second && second !== '?') { + text += second; + } + } + return text; + }; + + MT.displayAvatar = function (common, $container, href, name, _cb, uid) { var cb = Util.once(Util.mkAsync(_cb || function () {})); var displayDefault = function () { - var text = Util.getFirstCharacter(name || Messages.anonymous); - var $avatar = $('', {'class': 'cp-avatar-default'}).text(text); + var animal_avatar; + if (uid && animal_avatars[uid]) { + animal_avatar = animal_avatars[uid]; + } + + name = UI.getDisplayName(name); + var text; + if (ANIMALS.length && name === Messages.anonymous && uid) { + if (animal_avatar) { + text = animal_avatar; + } else { + text = animal_avatar = getPseudorandomAnimal(uid); + } + } else { + text = getPrettyInitials(name); + } + + var $avatar = $('', { + 'class': 'cp-avatar-default' + (animal_avatar? ' animal': ''), + // XXX prevents screenreaders from trying to describe this + alt: '', + 'aria-hidden': true, + }).text(text); $container.append($avatar); + if (uid && animal_avatar) { + animal_avatars[uid] = animal_avatar; + } if (cb) { cb(); } }; if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol @@ -97,6 +161,7 @@ define([ return void cb($el); } + var centerImage = function ($img, $image) { var img = $image[0]; var w = img.width; @@ -131,7 +196,7 @@ define([ var $img = $(mt).appendTo($container); MT.displayMediatagImage(common, $img, function (err, $image) { if (err) { return void console.error(err); } - centerImage($img, $image); + centerImage($img, $image); // XXX add alt="" (unless the media-tag has an alt attr) }); }); } diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 15b038724..d1a5ebcda 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -73,6 +73,7 @@ var factory = function () { * @param {object} cfg Object {Plugins, allowed, download, pdf} containing infos about plugins * @param {function} cb Callback function: (err, pluginElement) => {} */ + // XXX add alt attributes if present in metadata text: function (metadata, url, content, cfg, cb) { var plainText = document.createElement('div'); plainText.className = "plain-text-reader"; diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js index 76a756a11..494001624 100644 --- a/www/common/messenger-ui.js +++ b/www/common/messenger-ui.js @@ -190,9 +190,11 @@ define([ markup.message = function (msg) { if (msg.type !== 'MSG') { return; } var curvePublic = msg.author; + // FIXME this assignment looks like it has some holes in its logic + // but I'm scared to touch it because it looks like it was hacked to fix some bugs var name = (typeof msg.name !== "undefined" || !contactsData[msg.author]) ? (msg.name || Messages.anonymous) : - contactsData[msg.author].displayName; + contactsData[msg.author].displayName || Messages.anonymous; var d = msg.time ? new Date(msg.time) : undefined; var day = d ? d.toLocaleDateString() : ''; var hour = d ? d.toLocaleTimeString() : ''; @@ -239,7 +241,7 @@ define([ }); var chan = state.channels[id]; - var displayName = chan.name; + var displayName = UI.getDisplayName(chan.name || chan.displayName); var fetching = false; var $moreHistory = $(moreHistory).click(function () { @@ -364,7 +366,7 @@ define([ avatars[friend.avatar] = $img[0].outerHTML; } $(rightCol).insertAfter($avatar); - }); + }, friend.uid); } var sending = false; @@ -544,7 +546,7 @@ define([ title: Messages.contacts_online }); var rightCol = h('span.cp-app-contacts-right-col', [ - h('span.cp-app-contacts-name', [room.name]), + h('span.cp-app-contacts-name', [room.isFriendChat? UI.getDisplayName(room.name): room.name]), h('span.cp-app-contacts-icons', [ room.isFriendChat ? mute : undefined, room.isFriendChat ? unmute : undefined, @@ -609,7 +611,7 @@ define([ avatars[friendData.avatar] = $img[0].outerHTML; } $room.append(rightCol); - }); + }, friendData.uid); } $room.append(status); return $room; @@ -631,9 +633,9 @@ define([ var el_message = markup.message(message); if (message.type === 'MSG') { - var name = typeof message.name !== "undefined" ? - (message.name || Messages.anonymous) : - contactsData[message.author].displayName; + var name = UI.getDisplayName(typeof message.name !== "undefined" ? + message.name: + contactsData[message.author].displayName); common.notify({ title: name, msg: message.text, @@ -826,6 +828,11 @@ define([ } }; +/* The following block is for a disabled feature which allows users to switch + between pad chat (when in the context of a pad) and direct chats with their + contacts. +*/ +/* common.getMetadataMgr().onTitleChange(function () { var padChat = common.getPadChat(); var md = common.getMetadataMgr().getMetadata(); @@ -839,11 +846,14 @@ define([ $lAvatar.find('.cp-avatar-default, media-tag').remove(); var $div = $('
'); + // There should always be a title here (defaultTitle if nothing else) + // so we don't ever need to supply a uid for an animal avatar common.displayAvatar($div, null, name, function () { $mAvatar.html($div.html()); $lAvatar.find('.cp-app-contacts-right-col').before($div.html()); }); }); +*/ // TODO room // var onJoinRoom @@ -878,7 +888,7 @@ define([ h('i.fa.fa-bell'), Messages.contacts_unmute || 'unmute' ]); - common.displayAvatar($(avatar), data.avatar, data.name); + common.displayAvatar($(avatar), data.avatar, data.name, Util.noop, data.uid); $(button).click(function () { unmuteUser(curve, button); execCommand('UNMUTE_USER', curve, function (e, data) { @@ -894,7 +904,7 @@ define([ }); return h('div.cp-contacts-muted-user', [ h('span', avatar), - h('span', data.name), + h('span', UI.getDisplayName(data.name)), button ]); }); diff --git a/www/common/outer/cursor.js b/www/common/outer/cursor.js index 95b2b34c6..b1a5e7cd0 100644 --- a/www/common/outer/cursor.js +++ b/www/common/outer/cursor.js @@ -187,6 +187,7 @@ define([ data.color = Util.find(proxy, ['settings', 'general', 'cursor', 'color']); data.name = proxy[Constants.displayNameKey] || ctx.store.noDriveName || Messages.anonymous; data.avatar = Util.find(proxy, ['profile', 'avatar']); + data.uid = Util.find(proxy, ['uid']) || ctx.store.noDriveUid; c.cursor = data; sendMyCursor(ctx, client); cb(); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 206137c86..000d049b3 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -249,11 +249,20 @@ define([ if (existing.indexOf(n) !== -1) { n = 0; } return n; }; - funcs.getAuthorId = function(authors, curve) { + funcs.getAuthorId = function(authors, curve, tokenId) { var existing = Object.keys(authors || {}).map(Number); - if (!funcs.isLoggedIn()) { return authorUid(existing); } - var uid; + var loggedIn = funcs.isLoggedIn(); + if (!loggedIn && !tokenId) { return authorUid(existing); } + if (!loggedIn) { + existing.some(function (id) { + var author = authors[id]; + if (!author || author.uid !== tokenId) { return; } + uid = Number(id); + return true; + }); + return uid || authorUid(existing); + } existing.some(function(id) { var author = authors[id] || {}; if (author.curvePublic !== curve) { return; } diff --git a/www/common/toolbar.js b/www/common/toolbar.js index d103fe410..5b359a323 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -356,6 +356,8 @@ MessengerUI, Messages, Pages) { }); } if (data.profile) { + // XXX title to visit their profile "Visit {0}'s profile" + // Messages.contacts_info3 "Double-click their icon to view their profile", $span.addClass('cp-userlist-clickable'); $span.click(function () { Common.openURL(origin+'/profile/#' + data.profile); @@ -363,7 +365,7 @@ MessengerUI, Messages, Pages) { } Common.displayAvatar($span, data.avatar, name, function () { $span.append($rightCol); - }); + }, data.uid); $span.data('uid', data.uid); $editUsersList.append($span); }); @@ -1215,18 +1217,31 @@ MessengerUI, Messages, Pages) { } }; + var getFancyGuestName = function (name, uid) { + name = UI.getDisplayName(name); + if (name === Messages.anonymous && uid) { + var animal = MT.getPseudorandomAnimal(uid); + if (animal) { + name = animal + ' ' + name; + } + } + return name; + }; + // Notifications var initNotifications = function (toolbar, config) { // Display notifications when users are joining/leaving the session var oldUserData; if (!config.metadataMgr) { return; } var metadataMgr = config.metadataMgr; - var notify = function(type, name, oldname) { + var notify = function(type, name, oldname, uid) { if (toolbar.isAlone) { return; } // type : 1 (+1 user), 0 (rename existing user), -1 (-1 user) if (typeof name === "undefined") { return; } - name = name || Messages.anonymous; if (Config.disableUserlistNotifications) { return; } + name = getFancyGuestName(name, uid); + oldname = getFancyGuestName(oldname, uid); + switch(type) { case 1: UI.log(Messages._getKey("notifyJoined", [name])); @@ -1275,7 +1290,7 @@ MessengerUI, Messages, Pages) { delete oldUserData[u]; if (temp && newdata[userNetfluxId] && temp.uid === newdata[userNetfluxId].uid) { return; } if (userPresent(u, temp, newdata || oldUserData) < 1) { - notify(-1, temp.name); + notify(-1, temp.name, undefined, temp.uid); } } } @@ -1295,10 +1310,10 @@ MessengerUI, Messages, Pages) { if (typeof oldUserData[k] === "undefined") { // if the same uid is already present in the userdata, don't notify if (!userPresent(k, newdata[k], oldUserData)) { - notify(1, newdata[k].name); + notify(1, newdata[k].name, undefined, newdata[k].uid); } } else if (oldUserData[k].name !== newdata[k].name) { - notify(0, newdata[k].name, oldUserData[k].name); + notify(0, newdata[k].name, oldUserData[k].name, newdata[k].uid); } } } diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less index 69924ef77..5996940e4 100644 --- a/www/kanban/app-kanban.less +++ b/www/kanban/app-kanban.less @@ -159,6 +159,15 @@ margin-right: 5px; .tools_unselectable(); cursor: default; + &.cp-cursor.cp-tippy-html { + .avatar_vars(20px); + background-color: var(--red); + // XXX figure out how to inherit this from avatar.less + font-size: @avatar-font-size; //var(11px; // 20px / 1.8 as per avatar.less.. + &.animal { + font-size: @avatar-font-size-animal; //14px; // 20px / 1.8 * (6/5)... + } + } } } .kanban-item { diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 585c7c285..8225ddc86 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -97,16 +97,28 @@ define([ // Tippy var html = MT.getCursorAvatar(cursor); - var l = Util.getFirstCharacter(cursor.name || Messages.anonymous); + var name = UI.getDisplayName(cursor.name); + + var l; // label? + var animal = ''; + if (cursor.name === Messages.anonymous && typeof(cursor.uid) === 'string') { + l = MT.getPseudorandomAnimal(cursor.uid); + if (l) { + animal = '.animal'; + } + } + if (!l) { + l = MT.getPrettyInitials(name); + } var text = ''; if (cursor.color) { - text = 'color:'+getTextColor(cursor.color)+';'; + text = 'background-color:' + cursor.color + '; color:'+getTextColor(cursor.color)+';'; } - var avatar = h('span.cp-cursor.cp-tippy-html', { - style: "background-color: " + (cursor.color || 'red') + ";"+text, + var avatar = h('span.cp-cursor.cp-tippy-html' + animal, { + style: text, 'data-cptippy-html': true, - title: html + title: html, }, l); if (!noClear) { cursor.clear = function () { @@ -1295,12 +1307,12 @@ define([ // Add new cursor var avatar = getAvatar(cursor); var $item = $('.kanban-item[data-eid="'+cursor.item+'"]'); - var $board = $('.kanban-board[data-id="'+cursor.board+'"]'); if ($item.length) { remoteCursors[id] = cursor; $item.find('.cp-kanban-cursors').append(avatar); return; } + var $board = $('.kanban-board[data-id="'+cursor.board+'"]'); if ($board.length) { remoteCursors[id] = cursor; $board.find('header .cp-kanban-cursors').append(avatar); diff --git a/www/pad/comments.js b/www/pad/comments.js index 6069aa1d0..6280a72bf 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -43,18 +43,21 @@ define([ var canonicalize = function(t) { return t.replace(/\r\n/g, '\n'); }; - var getAuthorId = function(Env, curve) { - return Env.common.getAuthorId(Env.comments.authors, curve); + var getAuthorId = function(Env, curve, uid) { + return Env.common.getAuthorId(Env.comments.authors, curve, uid); }; - // Return the author ID and add/update the data for registered users - // Return the username for unregistered users + // Return the author ID and add/update user data + // associate data with a curvePublic for registered users and the uid otherwise var updateAuthorData = function(Env, onChange) { var userData = Env.metadataMgr.getUserData(); + var myAuthorId; if (!Env.common.isLoggedIn()) { - return userData.name; + myAuthorId = getAuthorId(Env, undefined, userData.uid); + } else { + myAuthorId = getAuthorId(Env, userData.curvePublic); } - var myAuthorId = getAuthorId(Env, userData.curvePublic); + var data = Env.comments.authors[myAuthorId] = Env.comments.authors[myAuthorId] || {}; var old = Sortify(data); data.name = userData.name; @@ -62,6 +65,8 @@ define([ data.profile = userData.profile; data.curvePublic = userData.curvePublic; data.notifications = userData.notifications; + data.uid = userData.uid; + if (typeof(onChange) === "function" && Sortify(data) !== old) { onChange(); } @@ -82,6 +87,9 @@ define([ var userData = Env.metadataMgr.getUserData(); var privateData = Env.metadataMgr.getPrivateData(); var others = {}; + + + // XXX mentioned users should be excluded from the list of notified recipients to avoid notifying them twice // Get all the other registered users with a mailbox thread.m.forEach(function(obj) { var u = obj.u; @@ -93,7 +101,8 @@ define([ curvePublic: author.curvePublic, comment: obj.m, content: obj.v, - notifications: author.notifications + notifications: author.notifications, + uid: author.uid, }; }); // Send the notification @@ -146,7 +155,7 @@ define([ 'aria-required': true, contenteditable: true, }); - Env.common.displayAvatar($(avatar), userData.avatar, name); + Env.common.displayAvatar($(avatar), userData.avatar, name, Util.noop, userData.uid); var cancel = h('button.btn.btn-cancel', { tabindex: 1 @@ -224,7 +233,9 @@ define([ if (Env.common.isLoggedIn()) { var authors = {}; - Object.keys((Env.comments && Env.comments.authors) ||  {}).forEach(function(id) { + Object.keys((Env.comments && Env.comments.authors) ||  {}) + .filter(function (id) { return Util.find(Env, ['commments', 'authors', id, 'curvePublic']); }) + .forEach(function(id) { var obj = Util.clone(Env.comments.authors[id]); authors[obj.curvePublic] = obj; }); @@ -369,7 +380,7 @@ define([ var name = Util.fixHTML(author.name || Messages.anonymous); var date = new Date(msg.t); var avatar = h('span.cp-avatar'); - Env.common.displayAvatar($(avatar), author.avatar, name); + Env.common.displayAvatar($(avatar), author.avatar, name, Util.noop, author.uid); if (author.profile) { $(avatar).click(function(e) { Env.common.openURL(Hash.hashToHref(author.profile, 'profile')); @@ -393,7 +404,7 @@ define([ } cleanMentions($el); var avatar = h('span.cp-avatar'); - Env.common.displayAvatar($(avatar), avatarUrl, name); + Env.common.displayAvatar($(avatar), avatarUrl, name, Util.noop, author.uid); $el.append([ avatar, h('span.cp-mentions-name', name) diff --git a/www/pad/cursor.js b/www/pad/cursor.js index 42569838f..ae91c80f1 100644 --- a/www/pad/cursor.js +++ b/www/pad/cursor.js @@ -3,7 +3,9 @@ define([ '/common/common-ui-elements.js', '/common/common-interface.js', '/bower_components/chainpad/chainpad.dist.js', -], function ($, UIElements, UI, ChainPad) { + '/customize/messages.js', + '/common/inner/common-mediatag.js', +], function ($, UIElements, UI, ChainPad, Messages, MT) { var Cursor = {}; Cursor.isCursor = function (el) { @@ -40,8 +42,17 @@ define([ var cursors = {}; + // XXX despite the name of this function this doesn't actually render as a tippy tooltip + // that means that emojis will use the system font that shows up in native tooltips + // so this might be of limited value/aesthetic appeal compared to other apps' cursors var makeTippy = function (cursor) { - return cursor.name; + if (typeof(cursor.uid) === 'string' && (!cursor.name || cursor.name === Messages.anonymous)) { + var animal = MT.getPseudorandomAnimal(cursor.uid); + if (animal) { + return animal + ' ' + Messages.anonymous; + } + } + return cursor.name || Messages.anonymous; }; var makeCursor = function (id, cursor) { diff --git a/www/profile/inner.js b/www/profile/inner.js index 11abd6ae8..c63b72e99 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -349,7 +349,7 @@ define([ $('', { src: '/customize/images/avatar.png', title: Messages.profile_avatar, - alt: 'Avatar' + alt: 'Avatar' // XXX translate this "Default profile picture" }).appendTo($span); return; } @@ -391,7 +391,7 @@ define([ }, function () { sframeChan.query("Q_PROFILE_AVATAR_ADD", data.url, function (err, err2) { if (err || err2) { return void UI.log(err || err2); } - displayAvatar(data.url); + displayAvatar(data.url); // XXX add "Profile picture" }); }); }; diff --git a/www/teams/inner.js b/www/teams/inner.js index 373a27998..bd7fca5ed 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -693,6 +693,8 @@ define([ redrawRoster(common); }); }; + + var getDisplayName = UI.getDisplayName; var makeMember = function (common, data, me, roster) { if (!data.curvePublic) { return; } @@ -701,11 +703,12 @@ define([ return user.role === "OWNER" && user.curvePublic !== me.curvePublic && !user.pendingOwner; }); + var displayName = getDisplayName(data.displayName); // Avatar var avatar = h('span.cp-avatar.cp-team-member-avatar'); - common.displayAvatar($(avatar), data.avatar, data.displayName); + common.displayAvatar($(avatar), data.avatar, displayName, Util.noop, data.uid); // Name - var name = h('span.cp-team-member-name', data.displayName); + var name = h('span.cp-team-member-name', displayName); if (data.pendingOwner) { $(name).append(h('em', { title: Messages.team_pendingOwnerTitle @@ -789,7 +792,7 @@ define([ title: Messages.team_rosterKick }); $(remove).click(function () { - UI.confirm(Messages._getKey('team_kickConfirm', [Util.fixHTML(data.displayName)]), function (yes) { + UI.confirm(Messages._getKey('team_kickConfirm', [Util.fixHTML(displayName)]), function (yes) { if (!yes) { return; } APP.module.execCommand('REMOVE_USER', { pending: data.pending, @@ -1073,6 +1076,9 @@ define([ metadata: obj }, function () { $avatar.empty(); + // the UI is not supposed to allow admins to remove team names + // so we expect that it will be there. Failing that the initials + // from the default name will be displayed common.displayAvatar($avatar, data.url); }); }); @@ -1191,10 +1197,11 @@ define([ var displayUser = function (common, data) { var avatar = h('span.cp-teams-invite-from-avatar.cp-avatar'); - common.displayAvatar($(avatar), data.avatar, data.displayName); + var name = getDisplayName(data.displayName); + common.displayAvatar($(avatar), data.avatar, name); return h('div.cp-teams-invite-from-author', [ avatar, - h('span.cp-teams-invite-from-name', data.displayName) + h('span.cp-teams-invite-from-name', name) ]); }; @@ -1319,20 +1326,21 @@ define([ nThen(function (waitFor) { // Get preview content. sframeChan.query('Q_ANON_GET_PREVIEW_CONTENT', { seeds: seeds }, waitFor(function (err, json) { - if (json && (json.error || !Object.keys(json).length)) { + if (json && (json.error || !Object.keys(json).length)) { // XXX team invite links are triggering this every time for me? $(errorBlock).text(Messages.team_inviteInvalidLinkError).show(); waitFor.abort(); $div.empty(); return; } + // XXX nothing guarantees that author, teamName, or message exist in json $div.empty(); $div.append(h('div.cp-teams-invite-from', [ - Messages.team_inviteFrom || 'From:', + Messages.team_inviteFrom, displayUser(common, json.author) ])); $div.append(UI.setHTML(h('p.cp-teams-invite-to'), Messages._getKey('team_inviteFromMsg', - [Util.fixHTML(json.author.displayName), + [Util.fixHTML(getDisplayName(json.author.displayName)), Util.fixHTML(json.teamName)]))); if (json.message) { $div.append(h('div.cp-teams-invite-message', json.message)); @@ -1449,10 +1457,10 @@ define([ // Update the name in the user menu var $displayName = $bar.find('.' + Toolbar.constants.username); metadataMgr.onChange(function () { - var name = metadataMgr.getUserData().name || Messages.anonymous; + var name = getDisplayName(metadataMgr.getUserData().name); $displayName.text(name); }); - $displayName.text(user.name || Messages.anonymous); + $displayName.text(getDisplayName(user.name)); // Load the Team module var onEvent = function (obj) {