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;
}
},
getName: function () {
return this.displayName;
},
getNickname: function () {
return this.displayName != this.nick ? this.nick : '';
},
getAvatar: function () {
return this.avatar;
},
setAvatar: function (id, type, source) {
var self = this;
fetchAvatar('', id, type, source, function (avatar) {
@ -102,7 +111,10 @@ module.exports = HumanModel.define({
this.soundEnabled = enable;
},
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 (this.isMe(jid)) {

View File

@ -76,6 +76,27 @@ module.exports = HumanModel.define({
resources: Resources,
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) {
message.owner = me.jid.bare;

View File

@ -3,7 +3,6 @@
var BaseCollection = require('./baseCollection');
var Resource = require('./resource');
module.exports = BaseCollection.extend({
type: 'resources',
model: Resource,
@ -42,5 +41,20 @@ module.exports = BaseCollection.extend({
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.$chatBox = this.$('.chatBox');
this.$messageList = this.$('.messages');
this.$autoComplete = this.$('.autoComplete');
this.staydown = new StayDown(this.$messageList[0], 500);
this.renderMessages();
this.renderCollection(this.model.resources, MUCRosterItem, this.$('.groupRoster'));
this.listenTo(this, 'rosterItemClicked', this.rosterItemSelected);
this.listenTo(this.model.messages, 'add', this.handleChatAdded);
$(window).on('resize', _.bind(this.resizeInput, this));
@ -117,19 +120,47 @@ module.exports = BasePage.extend({
},
handleKeyDown: function (e) {
if (e.which === 13 && !e.shiftKey) {
app.composing[this.model.jid] = '';
this.sendChat();
if (this.$autoComplete.css('display') != 'none') {
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();
return false;
} else if (e.which === 38 && this.$chatInput.val() === '' && this.model.lastSentMessage) {
this.editMode = true;
this.$chatInput.addClass('editing');
this.$chatInput.val(this.model.lastSentMessage.body);
} else if (e.which === 38) { // Up arrow
if (this.$autoComplete.css('display') != 'none') {
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();
return false;
} else if (e.which === 40 && this.editMode) {
this.editMode = false;
this.$chatInput.removeClass('editing');
} else if (e.which === 40) { // Down arrow
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();
return false;
} else if (!e.ctrlKey && !e.metaKey) {
@ -158,6 +189,43 @@ module.exports = BasePage.extend({
} else if (this.typing) {
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 () {
var height;

View File

@ -313,14 +313,14 @@ exports.includes.mucWrappedMessage = function anonymous(locals) {
with (locals || {}) {
var messageDate = Date.create(message.timestamp);
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,
"data-placement": "below"
}, {
src: true,
alt: 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}"),
"class": "date"
}, {
@ -503,7 +503,7 @@ exports.pages.chat = function anonymous(locals) {
exports.pages.groupchat = function anonymous(locals) {
var buf = [];
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("");
};

View File

@ -2,9 +2,10 @@
li
.sender
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
.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}')}
include mucBareMessage

View File

@ -12,5 +12,6 @@ section.page.chat
span#members_toggle_count
ul.groupRoster
.chatBox
ul.autoComplete
form.formwrap
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({
template: templates.includes.mucRosterItem,
events: {
'click': 'handleClick'
},
classBindings: {
show: '',
chatState: '',
@ -19,5 +22,8 @@ module.exports = HumanView.extend({
render: function () {
this.renderAndBind({contact: this.model});
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;
-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 {
transition: none;
-webkit-transition: none;
@ -1312,15 +1346,32 @@ button.secondary:hover:not(:disabled) {
}
.messages .messageWrapper .message_header .name {
display: inline-block;
margin-right: 0.25rem;
font-weight: 900;
font-size: 15px;
color: #3d3c40;
line-height: 22px;
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 {
display: inline-block;
margin-left: 0.25rem;
color: #babbbf;
font-size: 12px;
width: 60px;

View File

@ -194,6 +194,37 @@
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
transition: none
-webkit-transition: none
@ -344,15 +375,29 @@
.name
display: inline-block
margin-right: 0.25rem
font-weight: $font-weight-bolder
font-size: $font-size-message
color: $black
line-height: 22px
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
display: inline-block
margin-left: 0.25rem
color: $gray-light
font-size: $font-size-small
width: 60px