1
0
mirror of https://github.com/moparisthebest/kaiwa synced 2024-08-13 17:03:51 -04:00

Groupchat with avatars and nicknames autocomplete

This commit is contained in:
Sébastien Hut 2015-02-26 22:59:05 +01:00 committed by Sébastien Hut
parent 44c9235ea2
commit e3460cec6e
10 changed files with 237 additions and 18 deletions

View File

@ -91,6 +91,15 @@ module.exports = HumanModel.define({
this._activeContact = curr.id; this._activeContact = curr.id;
} }
}, },
getName: function () {
return this.displayName;
},
getNickname: function () {
return this.displayName != this.nick ? this.nick : '';
},
getAvatar: function () {
return this.avatar;
},
setAvatar: function (id, type, source) { setAvatar: function (id, type, source) {
var self = this; var self = this;
fetchAvatar('', id, type, source, function (avatar) { fetchAvatar('', id, type, source, function (avatar) {
@ -102,7 +111,10 @@ module.exports = HumanModel.define({
this.soundEnabled = enable; this.soundEnabled = enable;
}, },
getContact: function (jid, alt) { getContact: function (jid, alt) {
if (typeof jid === 'string') jid = new client.JID(jid); if (typeof jid === 'string') {
if (SERVER_CONFIG.domain && jid.indexOf('@') == -1) jid += '@' + SERVER_CONFIG.domain;
jid = new client.JID(jid);
}
if (typeof alt === 'string') alt = new client.JID(alt); if (typeof alt === 'string') alt = new client.JID(alt);
if (this.isMe(jid)) { if (this.isMe(jid)) {

View File

@ -76,6 +76,27 @@ module.exports = HumanModel.define({
resources: Resources, resources: Resources,
messages: Messages messages: Messages
}, },
getName: function (jid) {
var nickname = jid.split('/')[1];
var name = nickname;
var xmppContact = me.getContact(nickname);
if (xmppContact) {
name = xmppContact.displayName;
}
return name != '' ? name : nickname;
},
getNickname: function (jid) {
var nickname = jid.split('/')[1];
return nickname != this.getName(jid) ? nickname : '';
},
getAvatar: function (jid) {
var avatar = "";
var xmppContact = me.getContact(jid.split('/')[1]);
if (xmppContact) {
avatar = xmppContact.avatar;
}
return avatar || 'https://gravatar.com/avatar';
},
addMessage: function (message, notify) { addMessage: function (message, notify) {
message.owner = me.jid.bare; message.owner = me.jid.bare;

View File

@ -3,7 +3,6 @@
var BaseCollection = require('./baseCollection'); var BaseCollection = require('./baseCollection');
var Resource = require('./resource'); var Resource = require('./resource');
module.exports = BaseCollection.extend({ module.exports = BaseCollection.extend({
type: 'resources', type: 'resources',
model: Resource, model: Resource,
@ -42,5 +41,20 @@ module.exports = BaseCollection.extend({
return -1; return -1;
} }
return 1; return 1;
},
search : function (letters, removeMe, addAll) {
if(letters == "" && !removeMe) return this;
var collection = new module.exports(this.models);
if (addAll)
collection.add({id: this.parent.jid.bare + '/all'});
var pattern = new RegExp('^' + letters + '.*$', "i");
var filtered = collection.filter(function(data) {
var nick = data.get("mucDisplayName");
if (nick === me.nick) return false;
return pattern.test(nick);
});
return new module.exports(filtered);
} }
}); });

View File

@ -80,12 +80,15 @@ module.exports = BasePage.extend({
this.$chatInput.val(app.composing[this.model.jid] || ''); this.$chatInput.val(app.composing[this.model.jid] || '');
this.$chatBox = this.$('.chatBox'); this.$chatBox = this.$('.chatBox');
this.$messageList = this.$('.messages'); this.$messageList = this.$('.messages');
this.$autoComplete = this.$('.autoComplete');
this.staydown = new StayDown(this.$messageList[0], 500); this.staydown = new StayDown(this.$messageList[0], 500);
this.renderMessages(); this.renderMessages();
this.renderCollection(this.model.resources, MUCRosterItem, this.$('.groupRoster')); this.renderCollection(this.model.resources, MUCRosterItem, this.$('.groupRoster'));
this.listenTo(this, 'rosterItemClicked', this.rosterItemSelected);
this.listenTo(this.model.messages, 'add', this.handleChatAdded); this.listenTo(this.model.messages, 'add', this.handleChatAdded);
$(window).on('resize', _.bind(this.resizeInput, this)); $(window).on('resize', _.bind(this.resizeInput, this));
@ -117,19 +120,47 @@ module.exports = BasePage.extend({
}, },
handleKeyDown: function (e) { handleKeyDown: function (e) {
if (e.which === 13 && !e.shiftKey) { if (e.which === 13 && !e.shiftKey) {
app.composing[this.model.jid] = ''; if (this.$autoComplete.css('display') != 'none') {
this.sendChat(); var nickname = this.$autoComplete.find(">:nth-child(" + this.autoCompletePos + ")>:first-child").text();
this.rosterItemSelected(nickname);
} else {
app.composing[this.model.jid] = '';
this.sendChat();
}
e.preventDefault(); e.preventDefault();
return false; return false;
} else if (e.which === 38 && this.$chatInput.val() === '' && this.model.lastSentMessage) { } else if (e.which === 38) { // Up arrow
this.editMode = true;
this.$chatInput.addClass('editing'); if (this.$autoComplete.css('display') != 'none') {
this.$chatInput.val(this.model.lastSentMessage.body); var count = this.$autoComplete.find(">li").length;
var oldPos = this.autoCompletePos;
this.autoCompletePos = (oldPos - 1) < 1 ? count : oldPos - 1;
this.$autoComplete.find(">:nth-child(" + oldPos + ")").removeClass('selected');
this.$autoComplete.find(">:nth-child(" + this.autoCompletePos + ")").addClass('selected');
}
else if (this.$chatInput.val() === '' && this.model.lastSentMessage) {
this.editMode = true;
this.$chatInput.addClass('editing');
this.$chatInput.val(this.model.lastSentMessage.body);
}
e.preventDefault(); e.preventDefault();
return false; return false;
} else if (e.which === 40 && this.editMode) { } else if (e.which === 40) { // Down arrow
this.editMode = false;
this.$chatInput.removeClass('editing'); if (this.$autoComplete.css('display') != 'none') {
var count = this.$autoComplete.find(">li").length;
var oldPos = this.autoCompletePos;
this.autoCompletePos = (oldPos + 1) > count ? 1 : oldPos + 1;
this.$autoComplete.find(">:nth-child(" + oldPos + ")").removeClass('selected');
this.$autoComplete.find(">:nth-child(" + this.autoCompletePos + ")").addClass('selected');
}
else if (this.editMode) {
this.editMode = false;
this.$chatInput.removeClass('editing');
}
e.preventDefault(); e.preventDefault();
return false; return false;
} else if (!e.ctrlKey && !e.metaKey) { } else if (!e.ctrlKey && !e.metaKey) {
@ -158,6 +189,43 @@ module.exports = BasePage.extend({
} else if (this.typing) { } else if (this.typing) {
this.pausedTyping(); this.pausedTyping();
} }
if (([38, 40, 13]).indexOf(e.which) === -1) {
var lastWord = this.$chatInput.val().split(' ').pop();
if (lastWord.charAt(0) === '@') {
var models = this.model.resources.search(lastWord.substr(1) || '', true, true);
if (models.length) {
this.renderCollection(models, MUCRosterItem, this.$autoComplete);
this.autoCompletePos = 1;
this.$autoComplete.find(">:nth-child(" + this.autoCompletePos + ")").addClass('selected');
this.$autoComplete.show();
}
else
this.$autoComplete.hide();
}
if (this.$autoComplete.css('display') != 'none') {
if( lastWord === '') {
this.$autoComplete.hide();
return;
}
}
}
},
rosterItemSelected: function (nickName) {
if (nickName == me.nick)
nickName = 'me';
var val = this.$chatInput.val();
var splited = val.split(' ');
var length = splited.length-1;
var lastWord = splited.pop();
if (('@' + nickName).indexOf(lastWord) > -1)
splited[length] = '@' + nickName + ' ';
else
splited.push('@' + nickName + ' ');
this.$chatInput.val(splited.join(' '));
this.$autoComplete.hide();
this.$chatInput.focus();
}, },
resizeInput: _.throttle(function () { resizeInput: _.throttle(function () {
var height; var height;

View File

@ -313,14 +313,14 @@ exports.includes.mucWrappedMessage = function anonymous(locals) {
with (locals || {}) { with (locals || {}) {
var messageDate = Date.create(message.timestamp); var messageDate = Date.create(message.timestamp);
buf.push('<li><div class="sender"><a href="#" class="messageAvatar"><img' + jade.attrs({ buf.push('<li><div class="sender"><a href="#" class="messageAvatar"><img' + jade.attrs({
src: "https://gravatar.com/avatar", src: message.sender.getAvatar(message.from.full),
alt: message.from.resource, alt: message.from.resource,
"data-placement": "below" "data-placement": "below"
}, { }, {
src: true, src: true,
alt: true, alt: true,
"data-placement": true "data-placement": true
}) + '/></a></div><div class="messageWrapper"><div class="message_header"><div class="name">' + jade.escape((jade.interp = message.from.resource) == null ? "" : jade.interp) + "</div><div" + jade.attrs({ }) + '/></a></div><div class="messageWrapper"><div class="message_header"><div class="name">' + jade.escape((jade.interp = message.sender.getName(message.from.full)) == null ? "" : jade.interp) + '</div><div class="nickname">' + jade.escape((jade.interp = message.sender.getNickname(message.from.full)) == null ? "" : jade.interp) + "</div><div" + jade.attrs({
title: messageDate.format("{Dow}, {MM}/{dd}/{yyyy} - {h}:{mm} {Tt}"), title: messageDate.format("{Dow}, {MM}/{dd}/{yyyy} - {h}:{mm} {Tt}"),
"class": "date" "class": "date"
}, { }, {
@ -503,7 +503,7 @@ exports.pages.chat = function anonymous(locals) {
exports.pages.groupchat = function anonymous(locals) { exports.pages.groupchat = function anonymous(locals) {
var buf = []; var buf = [];
with (locals || {}) { with (locals || {}) {
buf.push('<section class="page chat"><section class="group conversation"><header class="online"><div class="title"><span class="prefix">#</span><span class="name"></span><i class="channel_actions fa fa-comments-o"></i><span contenteditable="true" spellcheck="false" class="status"></span></div></header><ul class="messages"></ul><a id="members_toggle"><i class="fa fa-user"></i><span id="members_toggle_count"></span></a><ul class="groupRoster"></ul><div class="chatBox"><form class="formwrap"><textarea name="chatInput" type="text" placeholder="Send a message..." autocomplete="off"></textarea></form></div></section></section>'); buf.push('<section class="page chat"><section class="group conversation"><header class="online"><div class="title"><span class="prefix">#</span><span class="name"></span><i class="channel_actions fa fa-comments-o"></i><span contenteditable="true" spellcheck="false" class="status"></span></div></header><ul class="messages"></ul><a id="members_toggle"><i class="fa fa-user"></i><span id="members_toggle_count"></span></a><ul class="groupRoster"></ul><div class="chatBox"><ul class="autoComplete"></ul><form class="formwrap"><textarea name="chatInput" type="text" placeholder="Send a message..." autocomplete="off"></textarea></form></div></section></section>');
} }
return buf.join(""); return buf.join("");
}; };

View File

@ -2,9 +2,10 @@
li li
.sender .sender
a.messageAvatar(href='#') a.messageAvatar(href='#')
img(src="https://gravatar.com/avatar", alt=message.from.resource, data-placement="below") img(src=message.sender.getAvatar(message.from.full), alt=message.from.resource, data-placement="below")
.messageWrapper .messageWrapper
.message_header .message_header
.name #{message.from.resource} .name #{message.sender.getName(message.from.full)}
.nickname #{message.sender.getNickname(message.from.full)}
.date(title=messageDate.format('{Dow}, {MM}/{dd}/{yyyy} - {h}:{mm} {Tt}')) #{messageDate.format('{h}:{mm} {tt}')} .date(title=messageDate.format('{Dow}, {MM}/{dd}/{yyyy} - {h}:{mm} {Tt}')) #{messageDate.format('{h}:{mm} {tt}')}
include mucBareMessage include mucBareMessage

View File

@ -12,5 +12,6 @@ section.page.chat
span#members_toggle_count span#members_toggle_count
ul.groupRoster ul.groupRoster
.chatBox .chatBox
ul.autoComplete
form.formwrap form.formwrap
textarea(name='chatInput', type='text', placeholder='Send a message...', autocomplete='off') textarea(name='chatInput', type='text', placeholder='Send a message...', autocomplete='off')

View File

@ -8,6 +8,9 @@ var templates = require('../templates');
module.exports = HumanView.extend({ module.exports = HumanView.extend({
template: templates.includes.mucRosterItem, template: templates.includes.mucRosterItem,
events: {
'click': 'handleClick'
},
classBindings: { classBindings: {
show: '', show: '',
chatState: '', chatState: '',
@ -19,5 +22,8 @@ module.exports = HumanView.extend({
render: function () { render: function () {
this.renderAndBind({contact: this.model}); this.renderAndBind({contact: this.model});
return this; return this;
},
handleClick: function (e) {
this.parent.trigger('rosterItemClicked', this.model.mucDisplayName);
} }
}); });

View File

@ -1154,6 +1154,40 @@ button.secondary:hover:not(:disabled) {
transition: none; transition: none;
-webkit-transition: none; -webkit-transition: none;
} }
.conversation .chatBox .autoComplete {
display: none;
position: fixed;
left: 245px;
bottom: 64px;
border: 2px solid #ddd;
border-bottom: 0;
border-radius: 0.25rem 0.25rem 0 0;
background-color: #fff;
z-index: 100;
list-style-type: none;
padding: 3px;
margin: 0px;
}
.conversation .chatBox .autoComplete li {
padding: 3px 25px;
margin: 0px;
border-radius: 0.25rem;
position: relative;
cursor: pointer;
}
.conversation .chatBox .autoComplete li:hover,
.conversation .chatBox .autoComplete li.selected {
background-color: #439fe0;
}
.conversation .chatBox .autoComplete li:hover .name,
.conversation .chatBox .autoComplete li.selected .name {
color: #fff;
}
.conversation .chatBox .autoComplete li .name {
color: #9e9ea6;
font-size: 14px;
font-weight: bold;
}
.conversation .chatBox .formwrap { .conversation .chatBox .formwrap {
transition: none; transition: none;
-webkit-transition: none; -webkit-transition: none;
@ -1312,15 +1346,32 @@ button.secondary:hover:not(:disabled) {
} }
.messages .messageWrapper .message_header .name { .messages .messageWrapper .message_header .name {
display: inline-block; display: inline-block;
margin-right: 0.25rem;
font-weight: 900; font-weight: 900;
font-size: 15px; font-size: 15px;
color: #3d3c40; color: #3d3c40;
line-height: 22px; line-height: 22px;
cursor: pointer; cursor: pointer;
} }
.messages .messageWrapper .message_header .name:empty {
margin-right: 0;
}
.messages .messageWrapper .message_header .nickname {
display: inline-block;
font-weight: 500;
font-size: 14px;
color: #9e9ea6;
line-height: 22px;
cursor: pointer;
}
.messages .messageWrapper .message_header .nickname:not(:empty) {
margin-right: 0.25rem;
}
.messages .messageWrapper .message_header .nickname:not(:empty):before {
content: '@';
}
.messages .messageWrapper .message_header .date { .messages .messageWrapper .message_header .date {
display: inline-block; display: inline-block;
margin-left: 0.25rem;
color: #babbbf; color: #babbbf;
font-size: 12px; font-size: 12px;
width: 60px; width: 60px;

View File

@ -194,6 +194,37 @@
transition: none transition: none
-webkit-transition: none -webkit-transition: none
.autoComplete
display: none
position: fixed
left: 245px
bottom: 64px
border: 2px solid #ddd
border-bottom: 0
border-radius: 0.25rem 0.25rem 0 0
background-color: #fff
z-index: 100
list-style-type: none
padding: 3px
margin: 0px
li
padding: 3px 25px
margin: 0px
border-radius: 0.25rem
position: relative
cursor: pointer
&:hover, &.selected
background-color: #439FE0
.name
color: #fff
.name
color: $gray
font-size: 14px
font-weight: bold
.formwrap .formwrap
transition: none transition: none
-webkit-transition: none -webkit-transition: none
@ -344,15 +375,29 @@
.name .name
display: inline-block display: inline-block
margin-right: 0.25rem
font-weight: $font-weight-bolder font-weight: $font-weight-bolder
font-size: $font-size-message font-size: $font-size-message
color: $black color: $black
line-height: 22px line-height: 22px
cursor: pointer cursor: pointer
&:empty
margin-right: 0
.nickname
display: inline-block
font-weight: $font-weight-classic
font-size: $font-size-base
color: $gray
line-height: 22px
cursor: pointer
&:not(:empty)
margin-right: 0.25rem
&:before
content: '@'
.date .date
display: inline-block display: inline-block
margin-left: 0.25rem
color: $gray-light color: $gray-light
font-size: $font-size-small font-size: $font-size-small
width: 60px width: 60px