Pārlūkot izejas kodu

First implementation of group chats.
Fix different bugs, implement paste of images from clipboard and images viewer.

Valeria Nogina 6 gadi atpakaļ
vecāks
revīzija
144260844e

+ 1 - 1
Makefile

@@ -14,7 +14,7 @@ httpserver: npm-ready
 	./node_modules/.bin/http-server -p 8000
 
 minify: npm-ready
-	./node_modules/.bin/cleancss -o dist/xabber.min.css css/materialdesignicons.css node_modules/perfect-scrollbar/dist/css/perfect-scrollbar.css css/materialize.css css/color-scheme.css css/xabber.css
+	./node_modules/.bin/cleancss -o dist/xabber.min.css css/materialdesignicons.css node_modules/perfect-scrollbar/dist/css/perfect-scrollbar.css node_modules/magnific-popup/dist/magnific-popup.css css/materialize.css css/color-scheme.css css/xabber.css
 	./node_modules/.bin/r.js -o name=node_modules/requirejs/require.js mainConfigFile=config.js
 
 release: npm-ready

+ 3 - 0
config.js

@@ -18,6 +18,9 @@ require.config({
         "strophe.rsm":              "node_modules/strophejs-plugins/rsm/strophe.rsm",
         "text":                     "node_modules/requirejs-text/text",
         "wavesurfer":               "node_modules/wavesurfer/dist/wavesurfer",
+        "slug":                     "node_modules/slug/slug",
+        "magnific-popup":           "node_modules/magnific-popup/dist/jquery.magnific-popup",
+
         // modified libs and plugins
         "backbone.localsync":       "src/lib/backbone.localsync",
         "hammerjs":                 "src/lib/hammer.min",

+ 240 - 28
css/xabber.css

@@ -318,6 +318,10 @@ input[type=text].simple-input-field:hover {
   margin: 0 5px;
 }
 
+.modal-footer .errors {
+  color: #f44336;
+  line-height: 18px;
+}
 
 .main-modal {
   top: 100px !important;
@@ -471,6 +475,13 @@ input[type=file] {
   line-height: 20px;
 }
 
+.mdi.mdi-18px {
+  width: 18px;
+  height: 18px;
+  font-size: 18px;
+  line-height: 18px;
+}
+
 .mdi.mdi-16px {
   width: 16px;
   height: 16px;
@@ -573,6 +584,10 @@ body {
   z-index: 500;
 }
 
+.roster-right-container .mdi-account-multiple {
+  color: #9E9E9E;
+}
+
 .main-wrap {
   position: relative;
   width: 1050px;
@@ -705,15 +720,14 @@ body {
   background-color: #E0E0E0;
 }
 
-.toolbar .msg-indicator {
+.toolbar .msg-indicator,
+.toolbar .group-msg-indicator,
+.toolbar .archive-msg-indicator,
+.toolbar .all-msg-indicator {
   position: absolute;
   display: none;
-  top: 7px;
-  left: 35px;
-  padding: 0 4px;
-  min-width: 16px;
-  height: 16px;
   border-radius: 8px;
+  padding: 0 4px;
   color: #FFFFFF !important;
   font-size: 12px;
   font-weight: bold;
@@ -727,15 +741,36 @@ body {
   transform: translateX(-50%);
 }
 
-.toolbar .msg-indicator.unread {
+.toolbar .all-msg-indicator {
+  top: 7px;
+  left: 35px;
+  min-width: 16px;
+  height: 16px;
+}
+
+.toolbar .msg-indicator,
+.toolbar .group-msg-indicator,
+.toolbar .archive-msg-indicator {
+  top: 8px;
+  left: 38px;
+  min-width: 8px;
+  height: 8px;
+}
+
+.toolbar .msg-indicator.unread,
+.toolbar .group-msg-indicator.unread,
+.toolbar .archive-msg-indicator.unread,
+.toolbar .all-msg-indicator.unread {
   display: inline-block;
 }
 
-.toolbar .show-chats.active .msg-indicator {
+.toolbar .show-chats.active .msg-indicator,
+.toolbar .show-chats.active .group-msg-indicator,
+.toolbar .show-chats.active .archive-msg-indicator,
+.toolbar .show-chats.active .all-msg-indicator {
   display: none !important;
 }
 
-
 .toolbar-bottom {
   position: absolute;
   bottom: 0;
@@ -755,7 +790,7 @@ body {
 .toolbar .add-variants {
   top: -21px !important;
   left: 50px !important;
-  min-width: 150px !important;
+  min-width: 170px !important;
   background-color: transparent;
   box-shadow: none;
   z-index: 1100;
@@ -794,7 +829,7 @@ body {
 
 .toolbar .accounts-wrap {
   position: absolute;
-  top: 300px;
+  top: 350px;
   bottom: 48px;
   width: 100%;
 }
@@ -1684,6 +1719,87 @@ body {
   padding: 10px 0;
 }
 
+/* ----------------- ADD GROUP CHAT VIEW ------------------------ */
+
+input#new_chat_anonymous,
+input#new_chat_searchable {
+  line-height: 25px;
+}
+
+input#new_chat_jid {
+  width: 45%;
+}
+
+#new_chat_model {
+  display: block;
+  width: max-content;
+  padding-right: 10px;
+}
+
+.input-field .field-jid {
+    display: inline;
+    vertical-align: bottom;
+    margin-left: 4px;
+    color: #616161;
+    line-height: 3rem;
+}
+
+.add-user-group-chat .group-title, .add-user-group-chat .arrow {
+  color: #616161;
+}
+
+.add-user-group-chat .roster-contact:hover,
+.add-user-group-chat .group-head:hover {
+  background-color: transparent;
+}
+
+.add-user-group-chat .list-item.selected {
+  background-color: #FFEBEE;
+}
+
+.add-user-group-chat .roster-contact .name {
+  height: 18px;
+}
+
+.group-chat-members {
+  padding: 18px 7px 11px 65px !important;
+}
+
+.group-chat-members .member-info {
+  position: absolute;
+  top: 10px;
+}
+
+.group-chat-members .member-info .role {
+  line-height: 14px;
+  font-size: 14px;
+  color: #9E9E9E;
+}
+
+.members-list-wrap {
+  max-height: calc(56px * 8);
+  overflow-y: auto;
+}
+
+.btn-chat-member-more, .btn-delete-user , .btn-make-user-admin {
+    position: absolute;
+    right: 15px;
+    top: 18px;
+    width: 20px;
+    height: 20px;
+    color: #9E9E9E;
+}
+
+.btn-make-user-admin {
+  right: 50px;
+}
+
+.btn-chat-member-more:hover,
+.btn-delete-user:hover,
+.btn-make-user-admin:hover {
+  color: #616161;
+}
+
 /* ----------------- GROUPS CHECKBOX LIST -------------------- */
 
 .groups.checkbox-list .input-field {
@@ -1971,6 +2087,10 @@ body {
   cursor: pointer;
 }
 
+.name-is-jid {
+  color: #616161 !important;
+}
+
 .chat-head-wrap .contact-info .contact-status-message {
   height: 14px;
   font-size: 12px;
@@ -2019,7 +2139,6 @@ body {
   min-width: 200px !important;
 }
 
-
 .chat-content-wrap {
   width: 100%;
   height: 100%;
@@ -2056,7 +2175,7 @@ body {
   cursor: pointer;
 }
 
-.chat-message.selected {
+.chat-message.selected, .list-item.selected {
   background-color: #FFEBEE;
 }
 
@@ -2142,7 +2261,8 @@ body {
 
 .chat-message.system .chat-msg-content {
   color: #757575;
-  font-style: italic;
+    font-style: italic;
+    text-align: center;
 }
 
 .chat-message .chat-msg-content a:hover {
@@ -2175,14 +2295,22 @@ img.fwd-img-preview {
 
 .chat-message.auth-request .accept-request,
 .chat-message.auth-request .block-request,
-.chat-message.auth-request .decline-request {
+.chat-message.auth-request .decline-request,
+.chat-message.auth-request .block-request-group,
+.chat-message.auth-request .decline-request-group  {
   margin-left: 7px;
   color: #D32F2F;
 }
-
+.chat-message.auth-request .accept-request-group {
+  margin-left: 0px;
+  color: #D32F2F;
+}
 .chat-message.auth-request .accept-request:hover,
 .chat-message.auth-request .block-request:hover,
-.chat-message.auth-request .decline-request:hover {
+.chat-message.auth-request .decline-request:hover,
+.chat-message.auth-request .accept-request-group:hover,
+.chat-message.auth-request .block-request-group:hover,
+.chat-message.auth-request .decline-request-group:hover  {
   text-decoration: underline;
   cursor: pointer;
 }
@@ -2272,6 +2400,12 @@ img.fwd-img-preview {
   overflow: hidden;
 }
 
+.mdi-account-multiple {
+  float: left;
+  color: #616161;
+  margin-right: 2px;
+}
+
 .chat-message .mdi-link-variant {
   position: absolute;
     text-align: center;
@@ -2280,7 +2414,6 @@ img.fwd-img-preview {
     width: 30px;
     height: 30px;
     border-radius: 50px;
-    z-index: 100;
 }
 
 .msg-copy-link {
@@ -3708,17 +3841,17 @@ input[type='range'].voice-message-volume:focus {
 
 /* ------------------- VCARD INFO ---------------------- */
 
-.vcard .info-wrap {
+.vcard .info-wrap, .group-info .info-wrap  {
   position: relative;
   width: 100%;
   padding: 0 0 6px 36px;
 }
 
-.vcard .details-icon {
+.vcard .details-icon, .group-info .details-icon {
   top: 2px;
 }
 
-.vcard .info {
+.vcard .info, .group-info .info {
   width: 100%;
   padding-bottom: 6px;
   -webkit-touch-callout: default;/* iOS Safari */
@@ -3729,7 +3862,7 @@ input[type='range'].voice-message-volume:focus {
                                   not supported by any browser */
 }
 
-.vcard .info .value {
+.vcard .info .value, .group-info .info .value {
   max-width: 100%;
   height: 16px;
   font-size: 14px;
@@ -3737,7 +3870,7 @@ input[type='range'].voice-message-volume:focus {
   color: #616161;
 }
 
-.vcard .info .label {
+.vcard .info .label, .group-info .info .label {
   height: 14px;
   margin-top: 3px;
   font-size: 12px;
@@ -3746,11 +3879,11 @@ input[type='range'].voice-message-volume:focus {
   color: #9E9E9E;
 }
 
-.vcard .block-header {
+.vcard .block-header, .group-info .block-header {
   width: 30%;
 }
 
-.vcard .block-name {
+.vcard .block-name, .group-info .block-name {
   float: left;
 }
 
@@ -3790,6 +3923,16 @@ input[type='range'].voice-message-volume:focus {
   border-width: 2px;
 }
 
+.chat-members-info span {
+    cursor: pointer;
+}
+
+.chat-members-info span:hover {
+    padding: 2px 0px;
+    border-bottom: 2px dashed #9E9E9E;
+}
+
+
 /* ------------------ VCARD EDIT ---------------------- */
 
 .account-vcard-edit-wrap .panel-content-wrap {
@@ -3888,7 +4031,8 @@ input[type='range'].voice-message-volume:focus {
 }
 
 .details-panel .main-info .contact-name,
-.details-panel .main-info .contact-name-input {
+.details-panel .main-info .contact-name-input,
+.details-panel .main-info .name-wrap {
   max-width: 100%;
   height: 35px;
   margin: 0;
@@ -3903,7 +4047,8 @@ input[type='range'].voice-message-volume:focus {
 }
 
 
-.details-panel .main-info .status-message {
+.details-panel .main-info .status-message,
+.right-panel-wrap .invitation .main-info .jid {
   clear: both;
   max-width: 100%;
   height: 21px;
@@ -3989,6 +4134,34 @@ input[type='range'].voice-message-volume:focus {
   color: #9E9E9E;
 }
 
+.right-panel-wrap .invitation .invite-msg {
+  position: relative;
+  margin: 0 auto;
+  width: 100%;
+  text-align: center;
+  display: inline-block;
+}
+
+.right-panel-wrap .invitation .invite-msg .invite-msg-text {
+  color: #616161;
+  font-style: italic;
+  width: 80%;
+  margin: 0 auto;
+}
+.right-panel-wrap .invitation .panel-content {
+    position: relative !important;
+    top: 0px;
+}
+.right-panel-wrap .invitation .panel-footer {
+    position: relative !important;
+}
+.right-panel-wrap .invitation .panel-footer .buttons-wrap {
+    margin: 0 auto;
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+    justify-content: center;
+}
 /* ---------------- DIALOG MODALS --------------------- */
 .dialog-modal {
   top: 180px !important;
@@ -4004,6 +4177,16 @@ input[type='range'].voice-message-volume:focus {
   font-weight: bold;
 }
 
+.dialog-modal .img-from-clipboard {
+    max-width: 380px;
+    max-height: 380px;
+}
+
+.dialog-modal .container-for-img {
+    margin: 20px auto;
+    width: max-content;
+}
+
 .dialog-modal .modal-content {
   padding: 0 24px;
 }
@@ -4159,7 +4342,6 @@ input[type='range'].voice-message-volume:focus {
   margin: 0;
 }
 
-
 /* ------------------ RECENT CHATS MODAL ------------------ */
 
 .forward-panel-modal {
@@ -4218,6 +4400,36 @@ input[type='range'].voice-message-volume:focus {
   background-color: #EEEEEE;
 }
 
+.add-user-group-chat .roster-contact {
+  height: 48px;
+}
+.add-user-group-chat .roster-contact .blocked-indicator {
+  display: none;
+}
+
+.add-user-group-chat .roster-contact .name {
+  color: #212121;
+}
+
+.add-user-group-chat .roster-contact .jid {
+  color: #616161;
+}
+
+.add-user-group-chat .chat-list {
+  max-height: calc(48px * 8);
+  overflow-y: auto;
+  margin: 5px 0px;
+}
+
+.add-user-group-chat .modal-footer .btn-add .counter {
+    margin-right: 4px;
+    opacity: 0.7;
+}
+
+.add-user-group-chat .modal-footer .btn-flat {
+    margin: 0 10px;
+    min-width:  50px;
+}
 /* ----------------- SYNC SETTTINGS MODAL ---------------- */
 
 .sync-settings-modal {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/xabber.min.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2073 - 0
dist/xabber.min.js


+ 4 - 3
firebase-messaging-sw.js

@@ -1,9 +1,10 @@
 importScripts('https://www.gstatic.com/firebasejs/4.3.1/firebase-app.js');
 importScripts('https://www.gstatic.com/firebasejs/4.3.1/firebase-messaging.js');
-importScripts('src/constants.js')
+
+const msg_sender_id = '868637702480';
 
 firebase.initializeApp({
-    'messagingSenderId': constants.GCM_SENDER_ID
+    'messagingSenderId': msg_sender_id
 });
 
 const messaging = firebase.messaging();
@@ -21,7 +22,7 @@ messaging.setBackgroundMessageHandler(function (message) {
         })
     });
 
-    if (message.from === constants.GCM_SENDER_ID) {
+    if (message.from === msg_sender_id) {
         var title, body,
             icon = 'images/xabber-logo-48.png';
 

+ 3 - 1
package.json

@@ -27,6 +27,8 @@
     "strophe.js": "~1.2.13",
     "strophejs-plugins": "0.0.7",
     "underscore": "~1.8.3",
-    "wavesurfer": ">0.0.0"
+    "wavesurfer": ">0.0.0",
+    "slug": ">0.0.0",
+    "magnific-popup": ">0.0.0"
   }
 }

+ 5 - 5
src/accounts.js

@@ -355,7 +355,7 @@ define("xabber-accounts", function () {
                         vcard: vcard,
                         vcard_updated: moment.now()
                     };
-                    attrs.name = vcard.fullname || (vcard.first_name + ' ' + vcard.last_name).trim() || jid;
+                    attrs.name = vcard.nickname || vcard.fullname || (vcard.first_name + ' ' + vcard.last_name).trim() || jid;
                     attrs.image = vcard.photo.image || Images.getDefaultAvatar(attrs.name);
                     this.cached_image = Images.getCachedImage(attrs.image);
                     this.save(attrs);
@@ -482,8 +482,8 @@ define("xabber-accounts", function () {
             var $presence = $(presence),
                 type = presence.getAttribute('type');
             if (type === 'error') { return; }
-            if (($presence.find('x').attr('xmlns') || '').indexOf(Strophe.NS.MUC) === 0) {
-                return;
+            if (($presence.find('x').attr('xmlns') || '').indexOf(Strophe.NS.GROUP_CHAT) === 0) {
+                chat_type = 'group_chat';
             }
             var jid = presence.getAttribute('from'),
                 bare_jid = Strophe.getBareJidFromJid(jid);
@@ -594,7 +594,7 @@ define("xabber-accounts", function () {
                 } else if (account.show_settings_after_delete) {
                     xabber.body.setScreen('settings');
                 } else {
-                    xabber.body.setScreen('chats');
+                    xabber.body.setScreen('all-chats');
                 }
             }
         },
@@ -1657,7 +1657,7 @@ define("xabber-accounts", function () {
         successFeedback: function (account) {
             account.auth_view = null;
             this.data.set('authentication', false);
-            xabber.body.setScreen('chats');
+            xabber.body.setScreen('all-chats');
         }
     });
 

+ 2 - 2
src/api-service.js

@@ -553,7 +553,7 @@ define("xabber-api-service", function () {
         successFeedback: function () {
             this.authFeedback({});
             this.data.set('authentication', false);
-            xabber.body.setScreen('chats');
+            xabber.body.setScreen('all-chats');
         },
 
         changeLoginType: function () {
@@ -827,7 +827,7 @@ define("xabber-api-service", function () {
         onHide: function () {
             this.$el.detach();
             if (xabber.body.isScreen('blank')) {
-                xabber.body.setScreen('chats');
+                xabber.body.setScreen('all-chats');
             }
         },
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 810 - 74
src/chats.js


+ 394 - 6
src/contacts.js

@@ -20,7 +20,9 @@ define("xabber-contacts", function () {
             status: "offline",
             status_message: "",
             subscription: null,
-            groups: []
+            groups: [],
+            group_chat: false,
+            group_chat_owner: false
         },
 
         initialize: function (_attrs, options) {
@@ -36,6 +38,7 @@ define("xabber-contacts", function () {
             this.hash_id = env.b64_sha1(this.account.get('jid') + '-' + attrs.jid);
             this.resources = new xabber.ContactResources(null, {contact: this});
             this.details_view = new xabber.ContactDetailsView({model: this});
+            this.invitation = new xabber.ContactInvitationView({model: this});
             this.on("change:photo_hash", this.getVCard, this);
             this.account.dfd_presence.done(function () {
                 this.getVCard();
@@ -57,8 +60,8 @@ define("xabber-contacts", function () {
                         vcard_updated: moment.now(),
                         name: this.get('roster_name')
                     };
-                    if (!attrs.name) {
-                        attrs.name = vcard.fullname || (vcard.first_name + ' ' + vcard.last_name).trim() || jid;
+                    if (attrs.name == jid || !attrs.name) {
+                        attrs.name = vcard.nickname || vcard.fullname || (vcard.first_name + ' ' + vcard.last_name).trim() || jid;
                     }
                     attrs.image = vcard.photo.image || Images.getDefaultAvatar(attrs.name);
                     this.cached_image = Images.getCachedImage(attrs.image);
@@ -78,6 +81,7 @@ define("xabber-contacts", function () {
                     var last_seen = this.getLastSeenStatus(iq);
                     if (this.get('status') == 'offline')
                         this.set({status_message: last_seen });
+                    return this;
             }.bind(this));
             }
         },
@@ -125,6 +129,11 @@ define("xabber-contacts", function () {
             callback && callback();
         },
 
+        acceptGroupRequest: function (callback) {
+            this.pres('subscribe');
+            callback && callback();
+        },
+
         blockRequest: function (callback) {
             this.pres('unsubscribed');
             this.removeFromRoster().block(callback);
@@ -149,6 +158,17 @@ define("xabber-contacts", function () {
         },
 
         handlePresence: function (presence) {
+            if (($(presence).find('x').attr('xmlns') == Strophe.NS.GROUP_CHAT)&&!($(presence).attr('type') == 'unavailable')) {
+                this.details_view = new xabber.GroupChatDetailsView({model: this});
+                this.set('group_chat', true);
+                var group_chat_info = this.parseGroupInfo($(presence));
+                this.set('group_info', group_chat_info);
+                this.set('roster_name', group_chat_info.name);
+            }
+            else {
+                this.set('group_chat', false);
+            }
+
             var $presence = $(presence),
                 type = presence.getAttribute('type'),
                 $vcard_update = $presence.find('x[xmlns="'+Strophe.NS.VCARD_UPDATE+'"]');
@@ -200,6 +220,23 @@ define("xabber-contacts", function () {
             }
         },
 
+        parseGroupInfo: function ($presence) {
+            var jid = $presence.attr('from'),
+                $group_chat = $presence.find('x'),
+                name = $group_chat.find('name').text(),
+                anonymous = $group_chat.find('anonymous').text(),
+                searchable = $group_chat.find('searchable').text(),
+                description = $group_chat.find('description').text(),
+                info = {
+                    jid: jid,
+                    name: name,
+                    anonymous: anonymous,
+                    searchable: searchable,
+                    description: description
+                };
+            return info;
+        },
+
         resetStatus: function (timeout) {
             clearTimeout(this._reset_status_timeout);
             this._reset_status_timeout = setTimeout(function () {
@@ -230,6 +267,7 @@ define("xabber-contacts", function () {
         }
     });
 
+
     xabber.ContactItemView = xabber.BasicView.extend({
         className: 'roster-contact list-item',
 
@@ -241,11 +279,13 @@ define("xabber-contacts", function () {
             this.updateName();
             this.updateStatus();
             this.updateAvatar();
+            this.selectView();
             this.model.on("change:name", this.updateName, this);
             this.model.on("change:image", this.updateAvatar, this);
             this.model.on("change:status_updated", this.updateStatus, this);
             this.model.on("change:status_message", this.updateStatusMsg, this);
             this.model.on("change:last_seen", this.lastSeenUpdated, this);
+            this.model.on("change:group_chat", this.updateGroupChat, this);
         },
 
         updateName: function () {
@@ -266,6 +306,13 @@ define("xabber-contacts", function () {
             }
         },
 
+        selectView: function () {
+            if (this.model.get('group_chat')) {
+                this.$('.private-chat').addClass('hidden');
+                this.$('.group_chat').removeClass('hidden');
+            }
+        },
+
         lastSeenUpdated: function () {
             if ((this.model.get('status') == 'offline')&&(this.model.get('last_seen'))&&(_.isUndefined(this.interval_last))) {
                 this.interval_last = setInterval(function() {
@@ -280,6 +327,10 @@ define("xabber-contacts", function () {
             }
         },
 
+        updateGroupChat: function () {
+            this.$('.mdi-account-multiple').showIf(this.model.get('group_chat'));
+        },
+
         updateStatusMsg: function() {
             this.$('.status-message').text(this.model.getStatusMessage());
         }
@@ -327,6 +378,7 @@ define("xabber-contacts", function () {
             this.model.on("change:display", this.updateDisplayStatus, this);
             this.model.on("change:blocked", this.updateBlockedState, this);
             this.model.on("change:muted", this.updateMutedState, this);
+            this.model.on("change:group_chat", this.updateGroupChat, this);
         },
 
         updateDisplayStatus: function () {
@@ -477,7 +529,11 @@ define("xabber-contacts", function () {
         },
 
         updateName: function () {
-            this.$('.main-info .name').text(this.model.get('name'));
+            this.$('.main-info .contact-name').text(this.model.get('name'));
+            if (this.model.get('name') == this.model.get('jid'))
+                this.$('.main-info .contact-name').addClass('name-is-jid');
+            else
+                this.$('.main-info .contact-name').removeClass('name-is-jid');
         },
 
         updateStatus: function () {
@@ -532,7 +588,7 @@ define("xabber-contacts", function () {
             utils.dialogs.ask("Block contact", "Do you want to block "+
                     contact.get('name')+"?").done(function (result) {
                 if (result) {
-                    contact.block();
+                    contact.blockRequest();
                     xabber.trigger("clear_search");
                 }
             });
@@ -555,6 +611,339 @@ define("xabber-contacts", function () {
         }
     });
 
+    xabber.GroupChatDetailsView = xabber.BasicView.extend({
+        className: 'details-panel contact-details-panel groupchat-details-panel',
+        template: templates.group_chat_details,
+        ps_selector: '.panel-content',
+        avatar_size: constants.AVATAR_SIZES.CONTACT_DETAILS,
+
+        events: {
+            "click .btn-escape": "openChat",
+            "click .btn-chat": "openChat",
+            "click .btn-add": "addContact",
+            "click .btn-delete": "deleteContact",
+            "click .btn-block": "blockContact",
+            "click .btn-unblock": "unblockContact",
+            "click .btn-view-group-members": "viewMembers"
+        },
+
+        _initialize: function () {
+            this.account = this.model.account;
+            this.name_field = new xabber.ContactNameWidget({
+                el: this.$('.name-wrap')[0],
+                model: this.model
+            });
+            this.edit_groups_view = this.addChild('groups',
+                xabber.ContactEditGroupsView, {el: this.$('.groups-block-wrap')[0]});
+            this.updateName();
+            this.updateStatus();
+            this.updateAvatar();
+            this.updateButtons();
+            this.model.on("change", this.update, this);
+        },
+
+        render: function (options) {
+            this.$('.btn-escape').showIf(options.name === 'chats');
+        },
+
+        onChangedVisibility: function () {
+            this.model.set('display', this.isVisible());
+        },
+
+        update: function () {
+            var changed = this.model.changed;
+            if (_.has(changed, 'name')) this.updateName();
+            if (_.has(changed, 'group_info')) this.updateGroupInfo();
+            if (_.has(changed, 'image')) this.updateAvatar();
+            if (_.has(changed, 'status_updated')) this.updateStatus();
+            if (_.has(changed, 'status_message')) this.updateStatusMsg();
+            if (_.has(changed, 'in_roster') || _.has(changed, 'blocked') ||
+                _.has(changed, 'subscription')) {
+                this.updateButtons();
+            }
+        },
+
+        updateName: function () {
+            this.$('.main-info .contact-name').text(this.model.get('name'));
+            if (this.model.get('name') == this.model.get('jid'))
+                this.$('.main-info .contact-name').addClass('name-is-jid');
+            else
+                this.$('.main-info .contact-name').removeClass('name-is-jid');
+        },
+
+        setGroupChatName: function (new_name) {
+            var iq = $iq({
+                from: this.account.get('jid'),
+                type: 'set',
+                to: this.model.get('jid')
+            }).c('update', {xmlns: Strophe.NS.GROUP_CHAT}).c('name').t(new_name);
+            this.account.sendIQ(iq);
+        },
+
+        updateGroupInfo: function () {
+            var info = this.model.get('group_info');
+            this.model.set('name', info.name);
+            this.group_info_view = this.addChild('group_info', xabber.GroupInfo,
+                {model: this.model, el: this.$('.group-info')[0]});
+
+        },
+
+        viewMembers: function () {
+            xabber.show_members_group_chat.open(this.account, this.model);
+        },
+
+        updateStatus: function () {
+            this.$('.status').attr('data-status', this.model.get('status'));
+            this.$('.status-message').text(this.model.getStatusMessage());
+        },
+
+        updateStatusMsg: function () {
+            this.$('.status-message').text(this.model.getStatusMessage());
+        },
+
+        updateAvatar: function () {
+            var image = this.model.cached_image;
+            this.$('.circle-avatar').setAvatar(image, this.avatar_size);
+        },
+
+        updateButtons: function () {
+            var in_roster = this.model.get('in_roster'),
+                is_blocked = this.model.get('blocked'),
+                subscription = this.model.get('subscription');
+            this.$('.btn-add').hideIf(in_roster);
+            this.$('.btn-delete').showIf(in_roster);
+            this.$('.btn-block').hideIf(is_blocked);
+            this.$('.btn-unblock').showIf(is_blocked);
+            this.$('.buttons-wrap button').addClass('btn-dark')
+                .filter(':not(.hidden)').first().removeClass('btn-dark');
+        },
+
+        openChat: function () {
+            this.model.trigger("open_chat", this.model);
+        },
+
+        addContact: function () {
+            xabber.add_contact_view.show({account: this.account, jid: this.model.get('jid')});
+        },
+
+        deleteContact: function (ev) {
+            var contact = this.model;
+            utils.dialogs.ask("Remove contact", "Do you want to remove "+
+                contact.get('name')+" from contacts?").done(function (result) {
+                if (result) {
+                    contact.removeFromRoster();
+                    xabber.trigger("clear_search");
+                }
+            });
+        },
+
+        blockContact: function (ev) {
+            var contact = this.model;
+            utils.dialogs.ask("Block contact", "Do you want to block "+
+                contact.get('name')+"?").done(function (result) {
+                if (result) {
+                    contact.blockRequest();
+                    xabber.trigger("clear_search");
+                }
+            });
+        },
+
+        unblockContact: function (ev) {
+            var contact = this.model;
+            utils.dialogs.ask("Unblock contact", "Do you want to unblock "+
+                contact.get('name')+"?").done(function (result) {
+                if (result) {
+                    contact.unblock();
+                    xabber.trigger("clear_search");
+                }
+            });
+        }
+    });
+
+      xabber.GroupInfo = xabber.BasicView.extend({
+          template: templates.group_info,
+
+
+          _initialize: function () {
+              this.$el.html(this.template());
+          },
+
+          render: function (options) {
+              this.update();
+          },
+
+          update: function () {
+              var $group_info_view, info = this.model.get('group_info');
+              $group_info_view = this.$('.jid-info-wrap').showIf(info.jid);
+              $group_info_view.find('.jabber-id').find('.value').text(info.jid);
+
+              $group_info_view = this.$('.name-info-wrap').showIf(info.name);
+              $group_info_view.find('.name').find('.value').text(info.name);
+
+              $group_info_view = this.$('.description-info-wrap').showIf(info.description);
+              $group_info_view.find('.description').find('.value').text(info.description);
+
+              var value = info.anonymous == 'true' ? 'Yes' : 'No';
+              $group_info_view = this.$('.anonymous-info-wrap').showIf(info.anonymous);
+              $group_info_view.find('.anonymous').find('.value').text(value);
+
+              value = info.searchable == 'true' ? 'Yes' : 'No';
+              $group_info_view = this.$('.searchable-info-wrap').showIf(info.searchable);
+              $group_info_view.find('.searchable').find('.value').text(value);
+          }
+      });
+
+
+
+    xabber.ContactInvitationView = xabber.BasicView.extend({
+        className: 'details-panel contact-details-panel invitation',
+        template: templates.group_chat_invitation,
+        ps_selector: '.panel-content',
+        avatar_size: constants.AVATAR_SIZES.CONTACT_DETAILS,
+
+        events: {
+            "click .btn-chat": "openChat",
+            "click .btn-accept": "addContact",
+            "click .btn-join": "joinGroupChat",
+            "click .btn-decline": "declineContact",
+            "click .btn-block": "blockContact",
+            "click .btn-escape": "closeInvitationView"
+        },
+
+        _initialize: function () {
+            this.account = this.model.account;
+            this.name_field = this.model.get('name');
+            this.updateName();
+            this.updateStatus();
+            this.updateAvatar();
+            this.$('.invite-msg .invite-msg-text')
+                .text('User requests permission to add you to his contact list. If you accept, '+ this.model.get('jid') + ' will also be added to ' +  this.account.get('jid') + ' contacts');
+            this.model.on("change", this.update, this);
+            this.on("change: invite_message", this.onChangedInviteMessage, this);
+        },
+
+        render: function (options) {
+            this.$('.btn-escape').showIf(!this.model.get('group_chat'));
+        },
+
+        onChangedVisibility: function () {
+            if (this.isVisible()) {
+                this.model.set({display: true, active: true});
+            } else {
+                this.model.set({display: false});
+            }
+        },
+
+        update: function () {
+            var changed = this.model.changed;
+            if (_.has(changed, 'name')) this.updateName();
+            if (_.has(changed, 'image')) this.updateAvatar();
+            if (_.has(changed, 'status_updated')) this.updateStatus();
+            if (_.has(changed, 'group_chat')) this.updateGroupChat();
+        },
+
+        updateName: function () {
+            this.$('.main-info  .name-wrap').text(this.model.get('name'));
+            if (this.model.get('name-wrap') == this.model.get('jid')) {
+                this.$('.main-info .name-wrap').addClass('name-is-jid');
+                this.$('.main-info  .jid').text('');
+            }
+            else {
+                this.$('.main-info .name-wrap').removeClass('name-is-jid');
+                this.$('.main-info  .jid').text(this.model.get('jid'));
+            }
+        },
+
+        updateStatus: function () {
+            this.$('.status').attr('data-status', this.model.get('status'));
+            this.$('.status-message').text(this.model.getStatusMessage());
+        },
+
+        updateAvatar: function () {
+            var image = this.model.cached_image;
+            this.$('.circle-avatar').setAvatar(image, this.avatar_size);
+        },
+
+        updateGroupChat: function () {
+            this.$('.buttons-wrap .btn-accept').hideIf(this.model.get('group_chat'));
+            this.$('.buttons-wrap .btn-join').showIf(this.model.get('group_chat'));
+            if (this.model.get('group_chat')) {
+                this.updateInviteMsg('User invites you to join group chat. If you accept, ' + this.account.get('jid') + ' username shall be visible to groupchat members');
+            }
+        },
+
+        updateInviteMsg: function (msg) {
+            this.$('.invite-msg .invite-msg-text').text(msg);
+        },
+
+        openChat: function () {
+            this.model.set('in_roster', true);
+            this.model.trigger("open_chat", this.model);
+        },
+
+        addContact: function () {
+            var contact = this.model;
+            contact.acceptRequest();
+            this.changeInviteStatus();
+            contact.trigger('remove_invite', contact);
+            contact.showDetails('chats');
+        },
+
+        changeInviteStatus: function() {
+            var contact = this.model;
+            var chat = this.account.chats.get(contact.hash_id);
+            chat.set('is_accepted', true);
+            chat.item_view.content.readMessages();
+            var invites = chat.item_view.content.$('.auth-request');
+            if (invites.length > 0) {
+                invites.each(function (idx, item) {
+                    var msg = chat.messages.get(item.dataset.msgid);
+                    msg.set('is_accepted', true);
+                }.bind(this));
+            }
+        },
+
+        joinGroupChat: function () {
+            var contact = this.model;
+            contact.acceptGroupRequest();
+            //contact.acceptRequest();
+            this.changeInviteStatus();
+            contact.set('in_roster', true);
+            contact.trigger('remove_invite', contact);
+            this.openChat();
+        },
+
+        declineContact: function (ev) {
+            var contact = this.model;
+            this.changeInviteStatus();
+            contact.declineRequest();
+            contact.trigger('remove_invite', contact);
+            var declined_chat =  xabber.chats_view.active_chat;
+            declined_chat.model.set('active', false);
+            declined_chat.content.head.closeChat();
+            xabber.body.setScreen('all-chats', {right: null});
+        },
+
+        closeInvitationView: function () {
+            this.changeInviteStatus();
+            this.openChat();
+        },
+
+        blockContact: function (ev) {
+            var contact = this.model;
+            this.changeInviteStatus();
+            utils.dialogs.ask("Block contact", "Do you want to block "+
+                    contact.get('name')+"?").done(function (result) {
+                if (result) {
+                    contact.trigger('remove_invite', contact);
+                    contact.block();
+                    xabber.trigger("clear_search");
+                }
+            });
+            this.openChat();
+        }
+    });
+
     xabber.ContactNameWidget = xabber.InputWidget.extend({
         field_name: 'contact-name',
         placeholder: 'Set contact name',
@@ -1924,7 +2313,6 @@ define("xabber-contacts", function () {
         this.details_container = this.right_panel.addChild('details', this.Container);
         this.contact_placeholder = this.right_panel.addChild('contact_placeholder',
                 this.ContactPlaceholderView);
-
         this.add_contact_view = new this.AddContactView();
         this.on("add_contact", function () {
             this.add_contact_view.show();

+ 7 - 0
src/core.js

@@ -265,10 +265,17 @@
         setUpPushNotifications: function () {
             var result = new $.Deferred(),
                 self = this;
+
+            navigator.serviceWorker.register('./firebase-messaging-sw.js').then((registration) => {
+                firebase.messaging().useServiceWorker(registration);
+            });
+
             firebase.initializeApp({
                 apiKey: constants.GCM_API_KEY,
                 messagingSenderId: constants.GCM_SENDER_ID
             });
+
+
             self.messaging = firebase.messaging();
 
             self.messaging.requestPermission().then(function () {

+ 5 - 1
src/dependencies.js

@@ -5,6 +5,8 @@ define([
     "jquery",
     "moment",
     "wavesurfer",
+    "slug",
+    "magnific-popup",
     "strophe",
     "strophe.disco",
     "strophe.ping",
@@ -13,12 +15,14 @@ define([
     "backbone.localsync",
     "materialize",
     "perfectScrollbarJQuery"
-], function(Backbone, _, $, moment, WaveSurfer, Strophe) {
+], function(Backbone, _, $, moment, WaveSurfer, slug, magnificPopup, Strophe) {
     return _.extend({
         $: $,
         _: _,
         moment: moment,
         WaveSurfer: WaveSurfer,
+        slug: slug,
+        magnificPopup: magnificPopup,
         Strophe: Strophe
     }, Strophe);
 });

+ 1 - 0
src/strophe.js

@@ -86,6 +86,7 @@ define("xabber-strophe", function () {
     Strophe.addNamespace('OOB', 'jabber:x:oob');
     Strophe.addNamespace('MEDIA', 'urn:xmpp:media-element');
     Strophe.addNamespace('LAST', 'jabber:iq:last');
+    Strophe.addNamespace('GROUP_CHAT', 'http://xabber.com/protocol/groupchat');
 
     return xabber;
   };

+ 22 - 0
src/templates.js

@@ -39,13 +39,22 @@ define("xabber-templates", [
     "text!templates/contacts/contact_left_item.html",
     "text!templates/contacts/contact_blocked_item.html",
     "text!templates/contacts/contact_details.html",
+    "text!templates/contacts/group_chat_details.html",
     "text!templates/contacts/roster_settings.html",
     "text!templates/contacts/group_settings.html",
     "text!templates/contacts/groups_checkbox_list.html",
     "text!templates/contacts/add_contact_account_item.html",
     "text!templates/contacts/contact_placeholder.html",
+    "text!templates/contacts/group_chat_invitation.html",
+    "text!templates/contacts/group_info.html",
 
     "text!templates/chats/chats_panel.html",
+    "text!templates/chats/chat_members.html",
+    "text!templates/chats/group_chats_panel.html",
+    "text!templates/chats/add_group_chat.html",
+    "text!templates/chats/add_user_group_chat.html",
+    "text!templates/chats/group_member_item.html",
+    "text!templates/chats/add_chat_account_item.html",
     "text!templates/chats/chat_item.html",
     "text!templates/chats/chat_head.html",
     "text!templates/chats/chat_content.html",
@@ -58,6 +67,8 @@ define("xabber-templates", [
     "text!templates/chats/messages/system.html",
     "text!templates/chats/messages/file_upload.html",
     "text!templates/chats/messages/auth_request.html",
+    "text!templates/chats/messages/group_request.html",
+    "text!templates/chats/archive_placeholder.html",
 
     "text!templates/svg/ic-jabber.html",
     "text!templates/svg/xmpp.html",
@@ -150,13 +161,22 @@ define("xabber-templates", [
     addTemplate('contacts.contact_left_item');
     addTemplate('contacts.contact_blocked_item');
     addTemplate('contacts.contact_details');
+    addTemplate('contacts.group_chat_details');
     addTemplate('contacts.roster_settings');
     addTemplate('contacts.group_settings');
     addTemplate('contacts.groups_checkbox_list');
     addTemplate('contacts.add_contact_account_item');
     addTemplate('contacts.contact_placeholder');
+    addTemplate('contacts.group_chat_invitation');
+    addTemplate('contacts.group_info');
 
     addTemplate('chats.chats_panel');
+    addTemplate('chats.chat_members');
+    addTemplate('chats.group_chats_panel'),
+    addTemplate('chats.add_group_chat'),
+    addTemplate('chats.add_user_group_chat'),
+    addTemplate('chats.group_member_item'),
+    addTemplate('chats.add_chat_account_item'),
     addTemplate('chats.chat_item');
     addTemplate('chats.chat_head');
     addTemplate('chats.chat_content');
@@ -169,6 +189,8 @@ define("xabber-templates", [
     addTemplate('chats.messages.system');
     addTemplate('chats.messages.file_upload');
     addTemplate('chats.messages.auth_request');
+    addTemplate('chats.messages.group_request');
+    addTemplate('chats.archive_placeholder');
 
     addSvgTemplate('svg.ic-jabber');
     addSvgTemplate('svg.xmpp');

+ 26 - 3
src/ui.js

@@ -143,7 +143,8 @@ define("xabber-ui", function () {
         var path_chat_head = new this.ViewPath('chat_item.content.head'),
             path_chat_body = new this.ViewPath('chat_item.content'),
             path_chat_bottom = new this.ViewPath('chat_item.content.bottom'),
-            path_contact_details = new this.ViewPath('contact.details_view');
+            path_contact_details = new this.ViewPath('contact.details_view'),
+            path_group_invitation = new this.ViewPath('contact.invitation');
 
         this.body.addScreen('contacts', {
             toolbar: null,
@@ -154,6 +155,15 @@ define("xabber-ui", function () {
             roster: null
         });
 
+        this.body.addScreen('all-chats', {
+            toolbar: null,
+            main: {
+                left: { chats: null },
+                right: { chat_placeholder: null }
+            },
+            roster: null
+        });
+
         this.body.addScreen('chats', {
             toolbar: null,
             main: {
@@ -166,12 +176,25 @@ define("xabber-ui", function () {
         this.body.addScreen('group-chats', {
             toolbar: null,
             main: {
-                wide: { group_chat_placeholder: null }
+                left: { group_chats: null },
+                right: { group_chat_placeholder: null }
+            },
+            roster: null
+        });
+
+        this.body.addScreen('archive-chats', {
+            toolbar: null,
+            main: {
+                left: { archive_chats: null },
+                right: { archive_placeholder: null }
             },
             roster: null
         });
 
         this.right_panel.patchTree = function (tree, options) {
+            if (options.right === 'group_invitation') {
+                return { details: path_group_invitation };
+            }
             if (options.right === 'contact_details') {
                 return { details: path_contact_details };
             }
@@ -191,7 +214,7 @@ define("xabber-ui", function () {
             if (result === null && !this.accounts.length) {
                 this.body.setScreen('login');
             } else if (this.body.isScreen('blank')) {
-                this.body.setScreen('chats');
+                this.body.setScreen('all-chats');
             }
         }, this);
 

+ 5 - 0
src/utils/modals.js

@@ -121,6 +121,11 @@ define(["xabber-dependencies", "xabber-templates"], function (deps, templates) {
                         dialog_options: dialog_options
                     });
                 }, {use_queue: true});
+                if (dialog_options.blob_image_from_clipboard) {
+                    dialog.$modal.find('.dialog-options-wrap').html('');
+                    dialog.$modal.find('.img-from-clipboard').get(0).src = dialog_options.blob_image_from_clipboard;
+                    dialog.$modal.find('.container-for-img').removeClass('hidden');
+                }
                 dialog.$modal.find('.modal-footer button').click(function (ev) {
                     var option = $(ev.target).data('option'),
                         $options = dialog.$modal.find('.dialog-option');

+ 160 - 32
src/views.js

@@ -312,7 +312,19 @@ define("xabber-views", function () {
                 if (query) {
                    this.search(query.toLowerCase());
                 } else {
-                   this.searchAll();
+                    if (xabber.toolbar_view.$('.active').hasClass('archive-chats')) {
+                        this.showArchiveChats();
+                    }
+                    if (xabber.toolbar_view.$('.active').hasClass('all-chats')) {
+                        this.showAllChats();
+                    }
+                    if (xabber.toolbar_view.$('.active').hasClass('group-chats')) {
+                        this.showGroupChats();
+                    }
+                    if (xabber.toolbar_view.$('.active').hasClass('chats')) {
+                        this.showChats();
+                    }
+                   //this.searchAll();
                 }
                 this.updateScrollBar();
                 this.query = false;
@@ -342,7 +354,15 @@ define("xabber-views", function () {
 
         search: function () {},
 
-        onEnterPressed: function () {}
+        onEnterPressed: function () {},
+
+        showGroupChats: function () {},
+
+        showChats: function () {},
+
+        showArchiveChats: function () {},
+
+        showAllChats: function () {}
     });
 
     xabber.InputWidget = Backbone.View.extend({
@@ -412,7 +432,11 @@ define("xabber-views", function () {
             var value = this.getValue(),
                 new_value = this.$input.removeClass('changed').val();
             new_value !== value && this.setValue(new_value);
+            if ((new_value !== value)&&(this.model.get('group_chat'))&&(this.$el.hasClass('name-wrap'))) {
+                this.model.details_view.setGroupChatName(new_value);
+            }
             this.data.set('input_mode', false);
+
         },
 
         updateValue: function () {
@@ -480,9 +504,11 @@ define("xabber-views", function () {
             "click .chats":                 "showChats",
             "click .group-chats":           "showGroupChats",
             "click .contacts":              "showContacts",
+            "click .archive-chats":         "showArchive",
             "click .settings":              "showSettings",
             "click .add-variant.contact":   "showAddContactView",
             "click .add-variant.account":   "showAddAccountView",
+            "click .add-variant.groupchat": "showAddGroupChatView",
             "click .about":                 "showAbout"
         },
 
@@ -502,8 +528,12 @@ define("xabber-views", function () {
 
             xabber.on("update_screen", this.onUpdatedScreen, this);
             this.data.on("change:add_menu_state", this.onChangedAddMenuState, this);
+            this.data.on("change:all_msg_counter", this.onChangedAllMessageCounter, this);
+            this.data.on("change:group_msg_counter", this.onChangedGroupMessageCounter, this);
             this.data.on("change:msg_counter", this.onChangedMessageCounter, this);
             this.data.set({msg_counter: 0});
+            this.data.set({group_msg_counter: 0});
+            this.data.set({all_msg_counter: 0});
         },
 
         render: function () {
@@ -517,38 +547,61 @@ define("xabber-views", function () {
         },
 
         onUpdatedScreen: function (name) {
-            // TODO: fix after 'all-chats' screen implementation
-            if (name === 'chats' &&
+            if ((name === 'chats' || name === 'all-chats' || name === 'group-chats' || name === 'archive-chats') &&
                     (this.$('.toolbar-item.all-chats').hasClass('active') ||
-                     this.$('.toolbar-item.chats').hasClass('active'))) {
+                    this.$('.toolbar-item.group-chats').hasClass('active') ||
+                    this.$('.toolbar-item.chats').hasClass('active')||
+                    this.$('.toolbar-item.archive-chats').hasClass('active'))) {
                 return;
             }
-            // end TODO
             this.$('.toolbar-item').removeClass('active');
-            if (_.contains(['chats', 'group-chats', 'contacts',
+            if (_.contains(['all-chats', 'contacts',
                             'settings', 'about'], name)) {
                 this.$('.toolbar-item.'+name).addClass('active');
             }
         },
 
         showAllChats: function (ev) {
-            // TODO: fix after 'all-chats' screen implementation
             this.$('.toolbar-item').removeClass('active')
                 .filter('.all-chats').addClass('active');
-            // end TODO
-            xabber.body.setScreen('chats', {right: null});
+            xabber.body.setScreen('all-chats', {right: null});
+            if ((ev)&&(xabber.chats_view.active_chat)) {
+                xabber.chats_view.active_chat.model.set('active', false);
+                xabber.chats_view.active_chat = null;
+            }
         },
 
         showChats: function (ev) {
-            // TODO: fix after 'all-chats' screen implementation
             this.$('.toolbar-item').removeClass('active')
                 .filter('.chats').addClass('active');
-            // end TODO
-            xabber.body.setScreen('chats', {right: null});
+            xabber.body.setScreen('all-chats', {right: null});
+            xabber.trigger('show_chats');
+            if ((ev)&&(xabber.chats_view.active_chat)) {
+                xabber.chats_view.active_chat.model.set('active', false);
+                xabber.chats_view.active_chat = null;
+            }
         },
 
         showGroupChats: function (ev) {
-            xabber.body.setScreen('group-chats', {right: null});
+            this.$('.toolbar-item').removeClass('active')
+                .filter('.group-chats').addClass('active');
+            xabber.body.setScreen('all-chats', {right: null});
+            xabber.trigger('show_group_chats');
+            if ((ev)&&(xabber.chats_view.active_chat)) {
+                xabber.chats_view.active_chat.model.set('active', false);
+                xabber.chats_view.active_chat = null;
+            }
+        },
+
+        showArchive: function (ev) {
+            this.$('.toolbar-item').removeClass('active')
+                .filter('.archive-chats').addClass('active');
+            xabber.body.setScreen('all-chats', {right: null});
+            xabber.trigger('show_archive_chats');
+            if ((ev)&&(xabber.chats_view.active_chat)) {
+                xabber.chats_view.active_chat.model.set('active', false);
+                xabber.chats_view.active_chat = null;
+            }
         },
 
         showContacts: function (ev) {
@@ -567,23 +620,87 @@ define("xabber-views", function () {
             xabber.trigger('add_account');
         },
 
+        showAddGroupChatView: function () {
+            xabber.trigger('add_group_chat');
+        },
+
         showAbout: function () {
             xabber.body.setScreen('about');
         },
 
-        increaseMessageCounter: function (num) {
-            var c = this.data.get('msg_counter');
-            this.data.set('msg_counter', c + (num || 1));
+        setAllMessageCounter: function () {
+            var count_msg = 0;
+            xabber.accounts.each(function(idx) {
+                xabber.accounts.get(idx).chats.each(function (idx1) {
+                    var $chat = xabber.accounts.get(idx).chats.get(idx1);
+                    if (!$chat.contact.get('archived'))
+                        count_msg += xabber.accounts.get(idx).chats.get(idx1).get('unread');
+                }.bind(this));
+            }.bind(this));
+            return count_msg;
         },
 
-        decreaseMessageCounter: function (num) {
-            var c = this.data.get('msg_counter');
-            this.data.set('msg_counter', c > (num || (num = 1)) ? c - num : 0);
+        setGroupMessageCounter: function () {
+            var count_msg = 0;
+            xabber.accounts.each(function(idx) {
+                xabber.accounts.get(idx).chats.each(function (idx1) {
+                    var $chat = xabber.accounts.get(idx).chats.get(idx1)
+                    if ($chat.contact.get('group_chat'))
+                        count_msg += $chat.get('unread');
+                }.bind(this));
+            }.bind(this));
+            return count_msg;
+        },
+
+        setMessageCounter: function () {
+            var count_msg = 0;
+            xabber.accounts.each(function(idx) {
+                xabber.accounts.get(idx).chats.each(function (idx1) {
+                    var $chat = xabber.accounts.get(idx).chats.get(idx1)
+                    if (!$chat.contact.get('group_chat'))
+                        count_msg += $chat.get('unread');
+                }.bind(this));
+            }.bind(this));
+            return count_msg;
+        },
+
+        increaseMessageCounter: function () {
+            this.data.set('msg_counter', this.setMessageCounter());
+        },
+
+        increaseGroupMessageCounter: function () {
+            this.data.set('group_msg_counter', this.setGroupMessageCounter());
+        },
+
+        increaseAllMessageCounter: function () {
+            this.data.set('all_msg_counter', this.setAllMessageCounter());
+        },
+
+        decreaseMessageCounter: function () {
+            this.data.set('msg_counter', this.setMessageCounter());
+        },
+
+        decreaseGroupMessageCounter: function () {
+            this.data.set('group_msg_counter', this.setGroupMessageCounter());
+        },
+
+        decreaseAllMessageCounter: function () {
+            this.data.set('all_msg_counter', this.setAllMessageCounter());
         },
 
         onChangedMessageCounter: function () {
             var c = this.data.get('msg_counter');
-            this.$('.msg-indicator').switchClass('unread', c).text(c);
+            this.$('.msg-indicator').switchClass('unread', c).text();
+        },
+
+        onChangedGroupMessageCounter: function () {
+            var c = this.data.get('group_msg_counter');
+            this.$('.group-msg-indicator').switchClass('unread', c).text();
+        },
+
+        onChangedAllMessageCounter: function () {
+            var c = this.data.get('all_msg_counter');
+            this.$('.all-msg-indicator').switchClass('unread', c).text(c);
         },
     });
 
@@ -860,27 +977,38 @@ define("xabber-views", function () {
             }
         },
 
-        onChangedMessageCounter: function () {
-            if (this.get('msg_counter')) {
+        onChangedAllMessageCounter: function () {
+            if (this.get('all_msg_counter')) {
                 this.startBlinkingFavicon();
-                window.document.title = "Messages (" + this.get('msg_counter') + ")";
+                window.document.title = "Messages (" + this.get('all_msg_counter') + ")";
             } else {
                 this.stopBlinkingFavicon();
                 window.document.title = 'Xabber Web';
             }
         },
 
-        increaseMessageCounter: function (num) {
-            this.set('msg_counter', this.get('msg_counter') + (num || 1));
+        setAllMessageCounter: function () {
+            var count_msg = 0;
+            xabber.accounts.each(function(idx) {
+                xabber.accounts.get(idx).chats.each(function (idx1) {
+                    var $chat = xabber.accounts.get(idx).chats.get(idx1);
+                    if (!$chat.contact.get('archived'))
+                        count_msg += xabber.accounts.get(idx).chats.get(idx1).get('unread');
+                }.bind(this));
+            }.bind(this));
+            return count_msg;
+        },
+
+        increaseAllMessageCounter: function () {
+            this.set('all_msg_counter', this.setAllMessageCounter());
         },
 
-        decreaseMessageCounter: function (num) {
-            var c = this.get('msg_counter');
-            this.set('msg_counter', c > (num || (num = 1)) ? c - num : 0);
+        decreaseAllMessageCounter: function () {
+            this.set('all_msg_counter', this.setAllMessageCounter());
         },
 
         resetMessageCounter: function () {
-            this.set('msg_counter', 0);
+            this.set('all_msg_counter', 0);
         },
 
         onChangedFocusState: function () {
@@ -938,8 +1066,8 @@ define("xabber-views", function () {
     });
 
     xabber.once("start", function () {
-        this.set('msg_counter', 0);
-        this.on("change:msg_counter", this.onChangedMessageCounter, this);
+        this.set('all_msg_counter', 0);
+        this.on("change:all_msg_counter", this.onChangedAllMessageCounter, this);
         this.on("change:focused", this.onChangedFocusState, this);
         this.set({
             focused: window.document.hasFocus(),

+ 1 - 0
templates/base/dialog.html

@@ -12,6 +12,7 @@
             </div>
         {[ } ]}
         </div>
+        <div class="container-for-img hidden"><img class="img-from-clipboard"></div>
     </div>
     <div class="modal-footer">
         {[ if (ok_button) { ]}

+ 15 - 3
templates/base/toolbar.html

@@ -1,9 +1,10 @@
-<div class="toolbar-item all-chats" title="All chats">
+<div class="toolbar-item all-chats active" title="All chats">
     <div class="border"></div>
     <img class="logo" src="images/xabber-logo.png">
+    <span class="all-msg-indicator"></span>
 </div>
 
-<div class="toolbar-item chats active" title="Chats">
+<div class="toolbar-item chats" title="Chats">
     <div class="border"></div>
     <i class="toolbar-icon mdi mdi-24px mdi-message-text"></i>
     <span class="msg-indicator"></span>
@@ -12,6 +13,13 @@
 <div class="toolbar-item group-chats" title="Group chats">
     <div class="border"></div>
     <svg class="toolbar-icon mdi mdi-24px mdi-svg-template" data-svgname="message-group"></svg>
+    <span class="group-msg-indicator"></span>
+</div>
+
+<div class="toolbar-item archive-chats" title="Archive chats">
+    <div class="border"></div>
+    <i class="toolbar-icon mdi mdi-24px mdi-archive"></i>
+    <span class="archive-msg-indicator hidden"></span>
 </div>
 
 <div class="toolbar-item contacts" title="Contacts">
@@ -25,11 +33,15 @@
 </div>
 
 <div class="add-something-wrap">
-    <div class="toolbar-item dropdown-button add-something" data-activates="{{view.cid}}-add" title="Add contact or account">
+    <div class="toolbar-item dropdown-button add-something" data-activates="{{view.cid}}-add" title="Add group chat, contact or account">
         <i class="toolbar-icon mdi mdi-24px mdi-plus"></i>
     </div>
 
     <div id="{{view.cid}}-add" class="add-variants dropdown-content noselect">
+        <div class="add-variant groupchat">
+            <i class="mdi mdi-20px mdi-account-multiple-plus"></i>
+            <span class="text">Add group chat</span>
+        </div>
         <div class="add-variant contact">
             <i class="mdi mdi-20px mdi-account-plus"></i>
             <span class="text">Add contact</span>

+ 6 - 0
templates/chats/add_chat_account_item.html

@@ -0,0 +1,6 @@
+<div class="account-item-wrap" data-jid="{{jid}}">
+    <div class="circle-avatar noselect">
+        <img>
+    </div>
+    <div class="name one-line">{{jid}}</div>
+</div>

+ 65 - 0
templates/chats/add_group_chat.html

@@ -0,0 +1,65 @@
+<div class="modal-content-wrap">
+    <div class="modal-header">
+        <span>Add new group chat</span>
+    </div>
+    <div class="modal-content">
+        <div class="row account-field">
+            <div class="field-header">Account</div>
+            <div class="multiple-acc">
+                <div class="account-dropdown-wrap">
+                    <div class="dropdown-button" data-activates="select-account-for-add-contact">
+                        <div class="account-item-wrap">
+                        </div>
+                        <div class="caret">
+                            <i class="mdi mdi-20px mdi-menu-down"></i>
+                        </div>
+                    </div>
+                    <div id="select-account-for-add-contact" class="dropdown-content noselect">
+                    </div>
+                </div>
+            </div>
+            <div class="single-acc">
+                <div class="dropdown-button">
+                    <div class="account-item-wrap">
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="row name-field">
+            <div class="input-field input-group-chat-name">
+                <input id="new_chat_name" type="text" name="chat_name" required>
+                <label for="new_chat_name">Group chat name</label>
+            </div>
+            <div class="input-field input-group-chat-jid">
+                <input id="new_chat_jid" type="text" name="chat_jid">
+                <label for="new_chat_jid">Group Jid</label>
+                <div class="field-jid"></div>
+            </div>
+            <div class="input-field input-group-chat-anonymous">
+                <input id="new_chat_anonymous" class="filled-in" type="checkbox" name="chat_anonymous">
+                <label for="new_chat_anonymous">Anonymous</label>
+            </div>
+            <div class="input-field input-group-chat-searchable">
+                <input id="new_chat_searchable" class="filled-in" type="checkbox" name="chat_searchable">
+                <label for="new_chat_searchable">Searchable</label>
+            </div>
+            <div class="input-field input-group-chat-description">
+                <input id="new_chat_description" type="text" name="chat_description">
+                <label for="new_chat_description">Description</label>
+            </div>
+            <div class="input-field input-group-chat-model">
+                <!--<input id="new_chat_model" type="text" name="chat_model">-->
+                <select size="1" id="new_chat_model" name="chat_model">
+                    <option value="open" selected>Open</option>
+                    <option value="member-only">Member-only</option>
+                    <option value="domain-only">Domain-only</option>
+                </select>
+            </div>
+        </div>
+    </div>
+    <div class="modal-footer">
+        <span class="errors"></span>
+        <button class="btn-flat btn-main btn-add">Add</button>
+        <button class="btn-flat btn-main btn-dark btn-cancel">Cancel</button>
+    </div>
+</div>

+ 20 - 0
templates/chats/add_user_group_chat.html

@@ -0,0 +1,20 @@
+    <div class="modal-header noselect">
+        <div class="panel-header">
+            <span class="header">Invite user to group chat</span>
+            <span class="btn-in-header close-button right">Close</span>
+        </div>
+    </div>
+    <div class="chats-search-form search-form panel-head">
+        <i class="search-icon mdi mdi-20px mdi-magnify"></i>
+        <input type="text" class="search-input simple-input-field" placeholder="Search">
+        <i class="close-search-icon mdi mdi-20px mdi-close"></i>
+    </div>
+    <div class="modal-content noselect">
+        <div class="chat-list-wrap left-panel-list-wrap">
+            <div class="chat-list item-list"></div>
+        </div>
+    </div>
+    <div class="modal-footer">
+        <button class="btn-flat btn-main btn-add"><span class="counter hidden"></span>Invite</button>
+        <span class="errors"></span>
+    </div>

+ 1 - 0
templates/chats/archive_placeholder.html

@@ -0,0 +1 @@
+<div class="text">Archive chats are not implemented yet</div>

+ 9 - 0
templates/chats/chat_head.html

@@ -7,6 +7,12 @@
         <p class="contact-status-message one-line"></p>
     </div>
     <div class="chat-tools-wrap">
+        <div class="chat-tool btn-add-user hidden">
+            <i class="mdi mdi-20px mdi-account-plus"></i>
+        </div>
+        <div class="chat-tool btn-archive-chat">
+            <i class="mdi mdi-20px mdi-package-down"></i>
+        </div>
         <div class="chat-tool btn-notifications">
             <i class="no-muted mdi mdi-20px mdi-volume-medium"></i>
             <svg class="muted mdi mdi-16px mdi-svg-template" data-svgname="volume-off-variant" viewBox="0 0 13 16"></svg>
@@ -30,5 +36,8 @@
             <li class="btn-close-chat">
                 <span class="one-line">Close chat</span>
             </li>
+            <li class="btn-members-chat hidden">
+                <span class="one-line">Chat members</span>
+            </li>
         </ul>
     </div>

+ 1 - 0
templates/chats/chat_item.html

@@ -5,6 +5,7 @@
 <div class="status hide-offline"></div>
 <div class="recent-chat-info">
     <div class="chat-title-wrap">
+        <i class="mdi mdi-20px mdi-account-multiple hidden"></i>
         <p class="chat-title one-line"></p>
         <svg class="muted-icon mdi mdi-12px mdi-svg-template" data-svgname="volume-off-variant" viewBox="0 0 13 16"></svg>
     </div>

+ 11 - 0
templates/chats/chat_members.html

@@ -0,0 +1,11 @@
+<div class="modal-header noselect">
+        <div class="panel-header">
+            <span class="header">Chat members</span>
+            <span class="btn-in-header close-button right">Close</span>
+        </div>
+</div>
+
+<div class="modal-content noselect">
+    <div class="members-list-wrap">
+    </div>
+</div>

+ 32 - 0
templates/chats/group_chat_invitation.html

@@ -0,0 +1,32 @@
+    <div class="panel-content-wrap noselect">
+        <div class="main-info">
+            <div class="circle-avatar"><img></div>
+            <div class="status hide-offline"></div>
+            <div class="text-info">
+                <div class="name-wrap"></div>
+                <div class="status-message one-line"></div>
+            </div>
+            <div class="btn-escape">
+                <i class="mdi mdi-24px mdi-close"></i>
+                <span class="btn-text">Esc</span>
+            </div>
+        </div>
+
+        <div class="panel-content">
+            <div class="left-column">
+                <div class="block-wrap vcard">
+                </div>
+            </div>
+            <div class="right-column"></div>
+            <div class="invite-msg"><p class="invite-msg-text"></p></div>
+        </div>
+    </div>
+    <div class="panel-footer noselect">
+        <div class="buttons-wrap">
+            <button class="btn-accept btn-flat btn-main">Accept</button>
+            <button class="btn-join btn-flat btn-main hidden">Join</button>
+            <button class="btn-decline btn-flat btn-main">Decline</button>
+            <button class="btn-block btn-flat btn-main">Block</button>
+            <button class="btn-chat btn-flat btn-main hidden">Chat</button>
+        </div>
+    </div>

+ 15 - 0
templates/chats/group_chats_panel.html

@@ -0,0 +1,15 @@
+<div class="recent-chats-panel noselect">
+    <div class="chats-search-form search-form panel-head">
+        <div class="account-indicator"></div>
+        <i class="search-icon mdi mdi-20px mdi-magnify"></i>
+        <input type="text" class="search-input simple-input-field" tabindex="1" placeholder="Search recent chats">
+        <i class="close-search-icon mdi mdi-20px mdi-close"></i>
+    </div>
+    <div class="chats-add-button">
+        <i class="toolbar-icon mdi mdi-24px mdi-plus"></i>
+    </div>
+    <div class="chat-list-wrap left-panel-list-wrap">
+        <div class="chat-list item-list">
+        </div>
+    </div>
+</div>

+ 12 - 0
templates/chats/group_member_item.html

@@ -0,0 +1,12 @@
+<div class="chat-item list-item group-chat-members" data-jid="{{jid}}" data-role="{{role}}">
+	<div class="circle-avatar">
+    	<img>
+	</div>
+	<div class="member-info">
+    <p class="jid one-line">{{jid}}</p>
+    <p class="role one-line">{{role}}</p>
+    </div>
+    <div class="chat-tool btn-delete-user" title="Delete member"><i class="mdi mdi-20px mdi-account-minus"></i></div>
+    <div class="chat-tool btn-make-user-admin" title="Make member admin"><i class="mdi mdi-20px mdi-account-star"></i></div>
+    <!--<div class="btn-chat-member-more"><i class="mdi mdi-20px mdi-dots-vertical"></i></div>-->
+</div>

+ 16 - 0
templates/chats/messages/group_request.html

@@ -0,0 +1,16 @@
+<div class="chat-message system auth-request" data-time="{{timestamp}}" data-msgid="{{msgid}}" data-from="{{from_jid}}">
+    <div class="left-side noselect">
+        <div class="circle-avatar"><img></div>
+    </div>
+
+    <div class="msg-wrap">
+        <div class="chat-msg-author-wrap">
+            <div class="chat-msg-author text-color-700 one-line">{{username}}</div>
+        </div>
+        <div class="chat-msg-content chat-text-content">{{message}}<div><span class="accept-request-group">Accept</span><span class="decline-request-group">Decline</span><span class="block-request-group">Block</span></div></div>
+    </div>
+
+    <div class="right-side noselect">
+        <div class="msg-time selectable-text" title="{{time}}">{{short_time}}</div>
+    </div>
+</div>

+ 1 - 2
templates/contacts/contact_details.html

@@ -12,7 +12,7 @@
             </div>
         </div>
 
-        <div class="panel-content">
+        <div class="panel-content private-chat">
             <div class="left-column">
                 <div class="block-wrap vcard">
                 </div>
@@ -39,7 +39,6 @@
                     </div>
                 </div>
             </div>
-
         </div>
     </div>
     <div class="panel-footer noselect">

+ 1 - 0
templates/contacts/contact_left_item.html

@@ -3,6 +3,7 @@
 </div>
 <div class="text-info-wrap">
     <div class="name-wrap">
+        <i class="mdi mdi-20px mdi-account-multiple hidden"></i>
         <p class="name one-line"></p>
         <svg class="muted-icon mdi mdi-12px mdi-svg-template" data-svgname="volume-off-variant" viewBox="0 0 13 16"></svg>
     </div>

+ 1 - 0
templates/contacts/contact_right_item.html

@@ -2,6 +2,7 @@
     <img>
 </div>
 <div class="text-info-wrap">
+	<i class="mdi mdi-18px mdi-account-multiple hidden"></i>
     <p class="name one-line"></p>
     <p class="jid one-line hidden"></p>
     <p class="status-message one-line"></p>

+ 47 - 0
templates/contacts/group_chat_details.html

@@ -0,0 +1,47 @@
+<div class="panel-content-wrap noselect">
+    <div class="main-info">
+        <div class="circle-avatar"><img></div>
+        <div class="status hide-offline"></div>
+        <div class="text-info">
+            <div class="name-wrap"></div>
+            <div class="status-message one-line"></div>
+        </div>
+        <div class="btn-escape">
+            <i class="mdi mdi-24px mdi-close"></i>
+            <span class="btn-text">Esc</span>
+        </div>
+    </div>
+
+    <div class="panel-content private-chat">
+        <div class="left-column">
+            <div class="block-wrap group-info">
+            </div>
+            <div class="block-wrap chat-members-info">
+                <div class="block-header">
+                    <span class="block-name btn-view-group-members">View chat members</span>
+                </div>
+            </div>
+        </div>
+
+        <div class="right-column">
+            <div class="block-wrap groups-block-wrap">
+                <div class="block-header">
+                    <span class="block-name">Groups</span>
+                </div>
+                <div class="groups-wrap">
+                    <div class="groups checkbox-list">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="panel-footer noselect">
+    <div class="buttons-wrap">
+        <button class="btn-add btn-flat btn-main">Join</button>
+        <button class="btn-chat btn-flat btn-main">Chat</button>
+        <button class="btn-block btn-flat btn-main">Block</button>
+        <button class="btn-unblock btn-flat btn-main">Unblock</button>
+        <button class="btn-delete btn-flat btn-main">Delete</button>
+    </div>
+</div>

+ 32 - 0
templates/contacts/group_chat_invitation.html

@@ -0,0 +1,32 @@
+    <div class="panel-content-wrap noselect">
+        <div class="main-info">
+            <div class="circle-avatar"><img></div>
+            <div class="status hide-offline"></div>
+            <div class="text-info">
+                <div class="name-wrap"></div>
+                <div class="jid one-line"></div>
+            </div>
+            <div class="btn-escape">
+                <i class="mdi mdi-24px mdi-close"></i>
+                <span class="btn-text">Esc</span>
+            </div>
+        </div>
+
+        <div class="panel-content">
+            <div class="left-column">
+                <div class="block-wrap vcard">
+                </div>
+            </div>
+            <div class="right-column"></div>
+            <div class="invite-msg"><p class="invite-msg-text"></p></div>
+            <div class="panel-footer noselect">
+                <div class="buttons-wrap">
+                    <button class="btn-accept btn-flat btn-main">Accept</button>
+                    <button class="btn-join btn-flat btn-main hidden">Join</button>
+                    <button class="btn-decline btn-flat btn-main">Decline</button>
+                    <button class="btn-block btn-flat btn-main">Block</button>
+                    <button class="btn-chat btn-flat btn-main hidden">Chat</button>
+                </div>
+            </div>
+        </div>
+    </div>

+ 41 - 0
templates/contacts/group_info.html

@@ -0,0 +1,41 @@
+<div class="block-header">
+    <span class="block-name">Group info</span>
+
+</div>
+<div class="vcard-wrap">
+    <div class="info-wrap jid-info-wrap hidden">
+        <svg class="details-icon mdi mdi-24px mdi-svg-template" data-svgname="xmpp"></svg>
+        <div class="info jabber-id">
+            <div class="value one-line"></div>
+            <div class="label">Jabber ID</div>
+        </div>
+    </div>
+    <div class="info-wrap name-info-wrap hidden">
+        <i class="details-icon mdi mdi-24px mdi-account-box-outline"></i>
+        <div class="info name">
+            <div class="value one-line"></div>
+            <div class="label">Name</div>
+        </div>
+    </div>
+    <div class="info-wrap description-info-wrap hidden">
+        <i class="details-icon mdi mdi-24px mdi-file-document-box"></i>
+        <div class="info description">
+            <div class="value one-line"></div>
+            <div class="label">Description</div>
+        </div>
+    </div>
+    <div class="info-wrap anonymous-info-wrap hidden">
+        <i class="details-icon mdi mdi-24px mdi-comment-question-outline"></i>
+        <div class="info anonymous">
+            <div class="value one-line"></div>
+            <div class="label">Anonymous</div>
+        </div>
+    </div>
+    <div class="info-wrap searchable-info-wrap hidden">
+        <i class="details-icon mdi mdi-24px mdi-magnify"></i>
+        <div class="info searchable">
+            <div class="value one-line"></div>
+            <div class="label">Searchable</div>
+        </div>
+    </div>
+</div>

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels