diff --git a/.gitignore b/.gitignore index a59326e855b6..de5ce8ce6971 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,65 @@ +**/bin/** +**/build/* +**/node_modules/* +**/tmp/* +*.bak +*.iml +*.ipr +*.iws +*.launch +*.log +*.pydevproject +*.sublime-project +*.swp +*.tmp +*.tokens +*.un~ +*~ +*~.nib +.*.sw[a-z] +.\#* +._* +.buildpath +.classpath +.clover +.cproject +.DS_Store +.elasticbeanstalk +.elc +.emacs.desktop +.emacs.desktop.lock +.env +.externalToolBuilders +.idea +.loadpath +.map +.metadata .meteor/local .meteor/meteorite -.DS_Store -ecosystem.json +.mule +.pmd +.project +.sass-cache +.settings +.Spotlight-V100 +.Trashes +.wtpmodules +\#*\# +BuildInfo.js +Desktop.ini +ehthumbs.db +example.css +jrat.output +jrat.xml +local.properties +meteor-vulcanize +nb-configuration.xml +nbactions.xml +nbproject +profiles.xml +Session.vim +smart.lock +temp_* +Thumbs.db +thumbs.db +tramp diff --git a/.meteor/packages b/.meteor/packages index 59f5e9f5e6a8..068e3d67c36e 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -3,49 +3,54 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -meteor-platform accounts-facebook accounts-github accounts-google accounts-meteor-developer accounts-password -jquery coffeescript email +http +jquery less markdown +meteor-platform reactive-var service-configuration + chrismbeckett:toastr francocatena:status +iframely:oembed iron:router +jparker:gravatar konecty:autolinker konecty:delayed-task konecty:mongo-counter konecty:multiple-instances-status konecty:user-presence +meteorhacks:kadira mizzao:autocomplete mizzao:timesync momentjs:moment -monbro:mongodb-mapreduce-aggregation -mrt:mask -mrt:publish-with-relations mrt:reactive-store nooitaf:colors +pauli:accounts-linkedin +percolate:migrations percolatestudio:synced-cron -qnub:emojione +pierreeric:rxfavico raix:handlebar-helpers +rocketchat:autolinker +rocketchat:emojione +rocketchat:file +rocketchat:highlight +rocketchat:lib +rocketchat:markdown +rocketchat:me +rocketchat:mentions +rocketchat:tmpembed tap:i18n tmeasday:crypto-md5 tmeasday:errors todda00:friendly-slugs -simple:highlight.js -percolate:migrations underscorestring:underscore.string -meteorhacks:kadira yasaricli:slugify -jparker:gravatar -http -cfs:filesystem -cfs:standard-packages -cfs:gridfs diff --git a/.meteor/versions b/.meteor/versions index 073052963642..c5750b6513c3 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -13,24 +13,7 @@ blaze@2.1.2 blaze-tools@1.0.3 boilerplate-generator@1.0.3 callback-hook@1.0.3 -cfs:access-point@0.1.49 -cfs:base-package@0.0.30 -cfs:collection@0.5.5 -cfs:collection-filters@0.2.4 -cfs:data-man@0.0.6 -cfs:file@0.1.17 -cfs:filesystem@0.1.2 -cfs:gridfs@0.0.33 cfs:http-methods@0.0.29 -cfs:http-publish@0.0.13 -cfs:power-queue@0.9.11 -cfs:reactive-list@0.0.9 -cfs:reactive-property@0.0.4 -cfs:standard-packages@0.5.9 -cfs:storage-adapter@0.2.2 -cfs:tempstore@0.1.5 -cfs:upload-http@0.0.20 -cfs:worker@0.1.4 check@1.0.5 chrismbeckett:toastr@2.1.0 coffeescript@1.0.6 @@ -41,7 +24,7 @@ ejson@1.0.6 email@1.0.6 facebook@1.2.0 fastclick@1.0.3 -francocatena:status@1.2.2 +francocatena:status@1.2.3 geojson-utils@1.0.3 github@1.1.3 google@1.1.5 @@ -49,14 +32,15 @@ html-tools@1.0.4 htmljs@1.0.4 http@1.1.0 id-map@1.0.3 -iron:controller@1.0.7 -iron:core@1.0.7 -iron:dynamic-template@1.0.7 -iron:layout@1.0.7 -iron:location@1.0.7 -iron:middleware-stack@1.0.7 -iron:router@1.0.7 -iron:url@1.0.7 +iframely:oembed@0.0.2 +iron:controller@1.0.8 +iron:core@1.0.8 +iron:dynamic-template@1.0.8 +iron:layout@1.0.8 +iron:location@1.0.9 +iron:middleware-stack@1.0.9 +iron:router@1.0.9 +iron:url@1.0.9 jparker:crypto-core@0.1.0 jparker:crypto-md5@0.1.1 jparker:gravatar@0.3.1 @@ -85,11 +69,8 @@ mizzao:autocomplete@0.5.1 mizzao:timesync@0.3.1 mobile-status-bar@1.0.3 momentjs:moment@2.10.3 -monbro:mongodb-mapreduce-aggregation@1.0.1 mongo@1.1.0 mongo-livedata@1.0.8 -mrt:mask@0.0.1 -mrt:publish-with-relations@0.1.5 mrt:reactive-store@0.0.1 nooitaf:colors@0.0.2 npm-bcrypt@0.7.8_2 @@ -97,16 +78,27 @@ oauth@1.1.4 oauth2@1.1.3 observe-sequence@1.0.6 ordered-dict@1.0.3 +pauli:accounts-linkedin@1.1.2 +pauli:linkedin@1.1.2 percolate:migrations@0.7.5 percolatestudio:synced-cron@1.1.0 +pierreeric:rxfavico@0.3.5_1 qnub:emojione@0.0.3 -raix:eventemitter@0.1.2 raix:handlebar-helpers@0.2.4 random@1.0.3 reactive-dict@1.1.0 reactive-var@1.0.5 reload@1.1.3 retry@1.0.3 +rocketchat:autolinker@0.0.1 +rocketchat:emojione@0.0.1 +rocketchat:file@0.0.1 +rocketchat:highlight@0.0.1 +rocketchat:lib@0.0.1 +rocketchat:markdown@0.0.1 +rocketchat:me@0.0.1 +rocketchat:mentions@0.0.1 +rocketchat:tmpembed@0.0.1 routepolicy@1.0.5 service-configuration@1.0.4 session@1.1.0 @@ -124,7 +116,7 @@ todda00:friendly-slugs@0.3.0 tracker@1.0.7 ui@1.0.6 underscore@1.0.3 -underscorestring:underscore.string@3.0.3_1 +underscorestring:underscore.string@3.1.1 url@1.0.4 webapp@1.2.0 webapp-hashing@1.0.3 diff --git a/LICENSE b/LICENSE index 70ab56a56980..517d8220b706 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 ChatRocket +Copyright (c) 2015 RocketChat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4a6715f6c178..0a750df81c1c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + The Complete Open Source Chat Solution @@ -8,7 +8,8 @@ Checkout the latest version at http://rocket.chat ## About -[![Code Climate](https://codeclimate.com/github/RocketChat/Rocket.Chat/badges/gpa.svg)](https://codeclimate.com/github/RocketChat/Rocket.Chat) +[![Code Climate][codeclimate-image]][codeclimate-url] +[![MIT License][license-image]][license-url] Rocket.Chat is a Web Chat Server, developed in JavaScript, using the [Meteor](https://www.meteor.com/install) fullstack framework. @@ -33,6 +34,8 @@ It is a great solution for communities and companies wanting to privately host t ## Installation +### Development + Prerequisites: * [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git) @@ -46,30 +49,60 @@ cd Rocket.Chat meteor ``` -## One-Click Deploy +### Production + +#### One-Click Deploy -Deploy on Heroku: +##### Heroku [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) ## Features +### Current + +- BYOS (bring your own server) +- Multiple Rooms - Direct Messages - Private Groups -- New message alerts -- I18n +- Public Channels +- Desktop Notifications +- Mentions +- Avatars +- Markdown +- Emojis +- Transcripts / History +- I18n - [Internationalization with Lingohub](https://translate.lingohub.com/engelgabriel/rocket-dot-chat/dashboard) +- Hubot Friendly - [Hubot Integration Project](https://github.com/RocketChat/hubot-rocketchat) + +### Roadmap for v1.0 + +- Image embeds +- File uploads +- Full text search +- REST-like APIs +- Off-the-Record (OTR) Messaging +- LDAP / Kerberos Authentication +- XMPP Multi-user chat (MUC) +- WebRTC signalling +- Native Mobile App +- Native Desktop App -### Roadmap +### Issues -The development team uses [Trello](https://trello.com/b/dKS6CCbS/rocket-chat-roadmap) to track progress and discuss new features. +[Github Issues](https://github.com/RocketChat/Rocket.Chat/issues) are used to track todos, bugs, feature requests, and more. -#### Changelog +### Integrations -Changelog can be found in the HISTORY.md file. +#### Hubot -### Issues +The docker image is ready. +Everyone can start hacking the adapter code, or launch his/her own bot within a few minutes now. +Please head over to the [Hubot Integration Project](https://github.com/RocketChat/hubot-rocketchat) for more information. -[Github Issues](https://github.com/RocketChat/Rocket.Chat/issues) are used to track todos, bugs, feature requests, and more. +#### Many, many, many more to come! + +We are developing the APIs based on the competition, so stay tunned and you will see a lot happening here. ### Documentation @@ -85,16 +118,28 @@ Performance monitoring provided by [Kadira](https://kadira.io/) ### Contributions -##### We Need Your Help! +#### We Need Your Help! A lot of work has already gone into Rocket.Chat, but we have much bigger plans for it! So if you'd like to be part of the project, please check out the [roadmap](https://github.com/RocketChat/Rocket.Chat/milestones) and [issues](https://github.com/RocketChat/Rocket.Chat/issues) to see if there's anything you can help with. +### Translations + +We are experimenting [Lingohub](https://translate.lingohub.com/engelgabriel/rocket-dot-chat/dashboard). +If you want to help, send an email to support at rocket.chat to be invited to the translation project. + ### Community -Join the the converation at [Twitter](http://twitter.com/RocketChatApp), [Facebook](https://www.facebook.com/RocketChatApp) or [Google Plus](https://plus.google.com/+RocketChatApp) +Join the the conversation at [Twitter](http://twitter.com/RocketChatApp), [Facebook](https://www.facebook.com/RocketChatApp) or [Google Plus](https://plus.google.com/+RocketChatApp) ### License Note that Rocket.Chat is distributed under the [MIT License](http://opensource.org/licenses/MIT). + + +[license-image]: http://img.shields.io/badge/license-MIT-blue.svg?style=flat +[license-url]: LICENSE + +[codeclimate-image]: https://codeclimate.com/github/RocketChat/Rocket.Chat/badges/gpa.svg +[codeclimate-url]: https://codeclimate.com/github/RocketChat/Rocket.Chat diff --git a/app.json b/app.json index 002ebd01d652..6d11bbcb0913 100644 --- a/app.json +++ b/app.json @@ -5,18 +5,9 @@ "logo": "http://rocket.chat/images/logo/logo-dark.svg", "keywords": ["meteor", "social", "community", "chat"], "env": { - "BUILDPACK_URL": "https://github.com/AdmitHub/meteor-buildpack-horse.git", + "BUILDPACK_URL": "https://github.com/RocketChat/heroku-buildpack-meteor.git", "ROOT_URL": { "description": "The full URL of your Rocket.Chat app (https://.herokuapp.com)." - }, - "AWS_ACCESS_KEY_ID": { - "description": "Your AWS Access Key Id" - }, - "AWS_SECRET_ACCESS_KEY": { - "description": "Your AWS Secret Access Key" - }, - "AWS_BUCKET": { - "description": "The AWS Bucket" } }, "addons": [ diff --git a/build.sh b/build.sh index c742fed4ac25..7a527584cbdb 100644 --- a/build.sh +++ b/build.sh @@ -10,6 +10,6 @@ if [ $1 ]; then meteor build --server rocket.chat --directory /var/www/rocket.chat cd /var/www/rocket.chat/bundle/programs/server npm install - cd /var/www/rocket.chat + cd /var/www/rocket.chat/current pm2 startOrRestart /var/www/rocket.chat/current/pm2.$1.json fi diff --git a/client/lib/RoomHistoryManager.coffee b/client/lib/RoomHistoryManager.coffee index 369ca8e4234a..0fe6774b1c61 100644 --- a/client/lib/RoomHistoryManager.coffee +++ b/client/lib/RoomHistoryManager.coffee @@ -3,39 +3,34 @@ histories = {} - getRoom = (roomId) -> - if not histories[roomId]? - histories[roomId] = + getRoom = (rid) -> + if not histories[rid]? + histories[rid] = hasMore: ReactiveVar true isLoading: ReactiveVar false loaded: 0 - return histories[roomId] + return histories[rid] - initRoom = (roomId, from=new Date) -> - room = getRoom roomId + getMore = (rid, limit=defaultLimit) -> + room = getRoom rid - room.from = from - - getMore = (roomId, limit=defaultLimit) -> - room = getRoom roomId - - if room.hasMore.curValue isnt true or not room.from? + if room.hasMore.curValue isnt true return room.isLoading.set true $('.messages-box .wrapper').data('previous-height', $('.messages-box .wrapper').get(0)?.scrollHeight - $('.messages-box .wrapper').get(0)?.scrollTop) - lastMessage = ChatMessageHistory.findOne({rid: roomId}, {sort: {ts: 1}}) - lastMessage ?= ChatMessage.findOne({rid: roomId}, {sort: {ts: 1}}) + lastMessage = ChatMessageHistory.findOne({rid: rid}, {sort: {ts: 1}}) + lastMessage ?= ChatMessage.findOne({rid: rid}, {sort: {ts: 1}}) if lastMessage? ts = lastMessage.ts else ts = new Date - Meteor.call 'loadHistory', roomId, ts, limit, 0, (err, result) -> + Meteor.call 'loadHistory', rid, ts, limit, 0, (err, result) -> ChatMessageHistory.insert item for item in result room.isLoading.set false @@ -45,18 +40,17 @@ if result.length < limit room.hasMore.set false - hasMore = (roomId) -> - room = getRoom roomId + hasMore = (rid) -> + room = getRoom rid return room.hasMore.get() - isLoading = (roomId) -> - room = getRoom roomId + isLoading = (rid) -> + room = getRoom rid return room.isLoading.get() - initRoom: initRoom getMore: getMore hasMore: hasMore isLoading: isLoading diff --git a/client/lib/RoomManager.coffee b/client/lib/RoomManager.coffee index 67b7a6875341..75f27efc820e 100644 --- a/client/lib/RoomManager.coffee +++ b/client/lib/RoomManager.coffee @@ -1,36 +1,34 @@ @RoomManager = new class defaultTime = 600000 # 10 minutes openedRooms = {} - myRoomActivity = null + subscription = null Dep = new Tracker.Dependency init = -> - myRoomActivity = Meteor.subscribe('myRoomActivity') - return myRoomActivity + subscription = Meteor.subscribe('subscription') + return subscription - expireRoom = (roomId) -> - if openedRooms[roomId] - if openedRooms[roomId].sub? - for sub in openedRooms[roomId].sub + close = (rid) -> + if openedRooms[rid] + if openedRooms[rid].sub? + for sub in openedRooms[rid].sub sub.stop() - openedRooms[roomId].ready = false - openedRooms[roomId].active = false - delete openedRooms[roomId].timeout + + openedRooms[rid].ready = false + openedRooms[rid].active = false + delete openedRooms[rid].timeout + + ChatMessageHistory.remove rid: rid computation = Tracker.autorun -> - for roomId, record of openedRooms when record.active is true + for rid, record of openedRooms when record.active is true record.sub = [ - Meteor.subscribe 'dashboardRoom', roomId, moment().subtract(2, 'hour').startOf('day').toDate() + Meteor.subscribe 'room', rid + Meteor.subscribe 'messages', rid ] - # @TODO talvez avaliar se todas as subscriptions do array estão 'ready', mas por enquanto, as mensagens são o mais importante - record.ready = record.sub[0].ready() - if record.ready is true and record.historyCalled isnt true - record.historyCalled = true - RoomHistoryManager.initRoom roomId, moment().subtract(2, 'hour').startOf('day').toDate() - Tracker.nonreactive -> - if Session.get('roomData' + roomId)?.msgs > 9 and ChatMessage.find({ rid: roomId }).count() < 10 and ChatMessageHistory.find({ rid: roomId }).count() is 0 - RoomHistoryManager.getMore roomId + + record.ready = record.sub[0].ready() and record.sub[1].ready() Dep.changed() @@ -40,39 +38,30 @@ clearTimeout openedRooms[except].timeout delete openedRooms[except].timeout - for roomId of openedRooms - if roomId isnt except and not openedRooms[roomId].timeout? - openedRooms[roomId].timeout = setTimeout expireRoom, defaultTime, roomId + for rid of openedRooms + if rid isnt except and not openedRooms[rid].timeout? + openedRooms[rid].timeout = setTimeout close, defaultTime, rid + + open = (rid) -> - open = (roomId) -> - if not openedRooms[roomId]? - openedRooms[roomId] = + if not openedRooms[rid]? + openedRooms[rid] = active: false ready: false - if myRoomActivity.ready() - if ChatSubscription.findOne { rid: roomId, uid: Meteor.userId() }, { reactive: false } - openedRooms[roomId].active = true - setRoomExpireExcept roomId + if subscription.ready() + # if ChatSubscription.findOne { rid: rid }, { reactive: false } + if openedRooms[rid].active isnt true + openedRooms[rid].active = true + setRoomExpireExcept rid computation.invalidate() - else - Meteor.call 'canAccessRoom', roomId, (error, result) -> - if result - openedRooms[roomId].active = true - setRoomExpireExcept roomId - computation.invalidate() - else - if error.error is 'without-permission' - toastr.error t('RoomManager.No_permission_to_view_room') - - Router.go 'home' return { ready: -> Dep.depend() - return openedRooms[roomId].ready + return openedRooms[rid].ready } open: open - close: expireRoom + close: close init: init diff --git a/client/lib/UserManager.coffee b/client/lib/UserManager.coffee index be3dcfd4a837..23ed43a62a3a 100644 --- a/client/lib/UserManager.coffee +++ b/client/lib/UserManager.coffee @@ -3,16 +3,17 @@ dep = new Tracker.Dependency - addUser = (userIds) -> - # console.log 'addUser', userIds if window.rocketUserDebug - userIds = [].concat userIds - for userId in userIds - unless users[userId] - users[userId] = 1 + addUser = (usernames) -> + # console.log 'addUser', usernames if window.rocketUserDebug + usernames = [].concat usernames + for username in usernames + unless users[username] + users[username] = 1 dep.changed() subscribeFn = -> - Meteor.subscribe 'selectiveUsers', users + return true + # Meteor.subscribe 'selectiveUsers', users subscribe = new DelayedTask subscribeFn, 100, 1000 @@ -21,7 +22,7 @@ dep.depend() subscribe.run() - init() + # init() addUser: addUser users: users diff --git a/client/lib/accountBox.coffee b/client/lib/accountBox.coffee index 3d0d97ef8c8c..86f1c483eb9f 100644 --- a/client/lib/accountBox.coffee +++ b/client/lib/accountBox.coffee @@ -2,13 +2,6 @@ status = 0 self = {} - toggleArrow = (status) -> - if self.arrow.hasClass "left" or status? is -1 - self.arrow.removeClass "left" - return - if not self.arrow.hasClass "left" or status? is 1 - self.arrow.addClass "left" - setStatus = (status) -> Meteor.call('UserPresence:setDefaultStatus', status) @@ -16,29 +9,27 @@ if status then close() else open() open = -> - if self.arrow.hasClass "left" + if SideNav.flexStatus() SideNav.closeFlex() return; status = 1 self.options.removeClass("_hidden") self.box.addClass("active") - toggleArrow 1 + SideNav.toggleArrow 1 close = -> status = 0 self.options.addClass("_hidden") self.box.removeClass("active") - toggleArrow -1 + SideNav.toggleArrow -1 init = -> self.box = $(".account-box") self.options = self.box.find(".options") - self.arrow = self.box.find(".arrow") setStatus: setStatus toggle: toggle open: open close: close init: init - toggleArrow: toggleArrow )() \ No newline at end of file diff --git a/client/lib/chatMessages.coffee b/client/lib/chatMessages.coffee index 99d59037324d..d3ea9962e699 100644 --- a/client/lib/chatMessages.coffee +++ b/client/lib/chatMessages.coffee @@ -37,31 +37,31 @@ send = (rid, input) -> if _.trim(input.value) isnt '' KonchatNotification.removeRoomNotification(rid) - message = input.value + msg = input.value input.value = '' stopTyping() - Meteor.call 'sendMessage', {rid: rid, message: message, day: window.day } + Meteor.call 'sendMessage', { rid: rid, msg: msg, day: window.day } update = (id, input) -> if _.trim(input.value) isnt '' - message = input.value + msg = input.value input.value = '' - Meteor.call 'updateMessage', {id: id, message: message } + Meteor.call 'updateMessage', { id: id, msg: msg } startTyping = (rid, input) -> - unless self.typingTimeout - if Meteor.userId()? - Meteor.call 'typingStatus', { rid: rid }, true - - self.typingTimeout = Meteor.setTimeout -> - stopTyping() - , 30000 + if _.trim(input.value) isnt '' + unless self.typingTimeout + if Meteor.userId()? + Meteor.call 'typingStatus', rid, true + self.typingTimeout = Meteor.setTimeout -> + stopTyping() + , 30000 stopTyping = -> self.typingTimeout = null startEditingLastMessage = (rid, imput) -> - lastMessage = ChatMessage.findOne { rid: rid, t: {$exists: false}, uid: Meteor.userId() }, { sort: { ts: -1 } } + lastMessage = ChatMessage.findOne { rid: rid, t: {$exists: false}, 'u._id': Meteor.userId() }, { sort: { ts: -1 } } if not lastMessage? return @@ -131,4 +131,4 @@ stopEditingLastMessage: stopEditingLastMessage send: send init: init -)() \ No newline at end of file +)() diff --git a/client/lib/collections.coffee b/client/lib/collections.coffee index 852c02c85b50..8684039e406b 100644 --- a/client/lib/collections.coffee +++ b/client/lib/collections.coffee @@ -1,7 +1,22 @@ @UserAndRoom = new Meteor.Collection null @ChatMessageHistory = new Meteor.Collection null +@ChatRoom = new Meteor.Collection 'data.ChatRoom' +@ChatSubscription = new Meteor.Collection 'data.ChatSubscription' +@ChatMessage = new Meteor.Collection 'data.ChatMessage', + transform: (message) -> + message.html = message.msg + message = RocketChat.callbacks.run 'renderMessage', message + # console.log 'transform' + return message + Meteor.startup -> ChatMessage.find().observe + added: (record) -> + if ChatMessageHistory._collection._docs._map[record._id]? + ChatMessageHistory.remove record._id + removed: (record) -> - ChatMessageHistory.insert record \ No newline at end of file + if ChatRoom._collection._docs._map[record.rid]? and not ChatMessageHistory._collection._docs._map[record._id]? + ChatMessageHistory.insert record + diff --git a/client/lib/constallation.js b/client/lib/constallation.js index 49c4778f78e3..1675d76e2ba4 100644 --- a/client/lib/constallation.js +++ b/client/lib/constallation.js @@ -19,6 +19,25 @@ if (!window.requestAnimationFrame) { * Makes a nice constellation on canvas * @constructor Constellation */ + + function checkRatio(ctx, canvas) { + // finally query the various pixel ratios + var devicePixelRatio = window.devicePixelRatio || 1, + backingStoreRatio = ctx.webkitBackingStorePixelRatio || + ctx.mozBackingStorePixelRatio || + ctx.msBackingStorePixelRatio || + ctx.oBackingStorePixelRatio || + ctx.backingStorePixelRatio || 1, + ratio = devicePixelRatio / backingStoreRatio; + var oldWidth = canvas.width; + var oldHeight = canvas.height; + canvas.width = oldWidth * ratio; + canvas.height = oldHeight * ratio; + canvas.style.width = oldWidth + 'px'; + canvas.style.height = oldHeight + 'px'; + ctx.scale(ratio, ratio); + } + function Constellation (canvas, options) { var $canvas = $(canvas), context = canvas.getContext('2d'), @@ -38,7 +57,7 @@ if (!window.requestAnimationFrame) { width: window.innerWidth, height: window.innerHeight, velocity: 0.1, - length: 100, + length: 150, distance: 120, radius: 150, stars: [] @@ -50,8 +69,8 @@ if (!window.requestAnimationFrame) { var startTime; function Star () { - this.x = Math.random() * canvas.width; - this.y = Math.random() * canvas.height; + this.x = Math.random() * (window.innerWidth || canvas.width); + this.y = Math.random() * (window.innerHeight || canvas.height); this.vx = (config.velocity - (Math.random() * 0.5)); this.vy = (config.velocity - (Math.random() * 0.5)); @@ -72,10 +91,10 @@ if (!window.requestAnimationFrame) { var star = config.stars[i]; - if (star.y < 0 || star.y > canvas.height) { + if (star.y < 0 || star.y > (window.innerHeight || canvas.height) ) { star.vx = star.vx; star.vy = - star.vy; - } else if (star.x < 0 || star.x > canvas.width) { + } else if (star.x < 0 || star.x > (window.innerWidth || canvas.width) ) { star.vx = - star.vx; star.vy = star.vy; } @@ -131,7 +150,6 @@ if (!window.requestAnimationFrame) { for (i = 0; i < length; i++) { config.stars.push(new Star()); star = config.stars[i]; - star.create(); } @@ -173,8 +191,9 @@ if (!window.requestAnimationFrame) { this.bind = function () { $canvas.on('mousemove', function(e){ - config.position.x = e.pageX - $canvas.offset().left; - config.position.y = e.pageY - $canvas.offset().top; + console.log($canvas.offset().left); + config.position.x = e.pageX; + config.position.y = e.pageY; }); }; @@ -183,6 +202,7 @@ if (!window.requestAnimationFrame) { then = Date.now(); startTime = then; this.setCanvas(); + checkRatio(context, canvas); this.setContext(); this.setInitialPosition(); this.loop(this.createStars); diff --git a/client/lib/modal.coffee b/client/lib/modal.coffee index 86821a104ac7..6493aebcc5d1 100644 --- a/client/lib/modal.coffee +++ b/client/lib/modal.coffee @@ -28,7 +28,7 @@ open = (template, params) -> params = params or {} - Rocket.animeBack self.$modal, -> + RocketChat.animeBack self.$modal, -> focus() self.opened = 1 startListening() if params.listening diff --git a/client/lib/notification.coffee b/client/lib/notification.coffee index 5d6c7199253f..9fbc33789237 100644 --- a/client/lib/notification.coffee +++ b/client/lib/notification.coffee @@ -37,7 +37,7 @@ Session.set('newRoomSound', newRoomSound) - $('.link-room-' + rid).addClass('new-room-highlight') + # $('.link-room-' + rid).addClass('new-room-highlight') removeRoomNotification: (rid) -> Tracker.nonreactive -> @@ -55,4 +55,4 @@ Tracker.autorun -> else $('#chatNewRoomNotification').each -> this.pause() - this.currentTime = 0 \ No newline at end of file + this.currentTime = 0 diff --git a/client/lib/rocket.coffee b/client/lib/rocket.coffee index ad00ba330622..2ff6190ef8fb 100644 --- a/client/lib/rocket.coffee +++ b/client/lib/rocket.coffee @@ -1,120 +1,112 @@ -@Rocket = (-> +RocketChat.Login = (-> + onClick = (el) -> + $el = $(el) + if $el.length + $el.addClass "active" + $el.find("input").focus() + onBlur = (input) -> + $input = $(input) + if $input.length + if input.value == "" + $input.parents(".input-text").removeClass "active" + check = (form) -> + $form = $(form) + if $form.length + inputs = $form.find("input") + inputs.each -> + if @.value != "" + console.log @.value + $(@).parents(".input-text").addClass "active" + check: check + onClick: onClick + onBlur: onBlur + )() +RocketChat.Button = (-> + time = undefined + loading = (el) -> + $el = $(el) + next = el.attr("data-loading-text") + html = el.find("span").html() + el.addClass("-progress").attr("data-def-text",html).find("span").html(next) + time = setTimeout -> + el.addClass("going") + , 1 + done = (el) -> + $el = $(el) + el.addClass("done") + reset = (el) -> + clearTimeout(time) if time + $el = $(el) + html= $el.attr("data-def-text") + $el.find("span").html(html) if html + $el.removeClass("-progress going done") + done: done + loading: loading + reset: reset + )() - @Login = (-> - onClick = (el) -> - $el = $(el) - if $el.length - $el.addClass "active" - $el.find("input").focus() - onBlur = (input) -> - $input = $(input) - if $input.length - if input.value == "" - $input.parents(".input-text").removeClass "active" - check = (form) -> - $form = $(form) - if $form.length - inputs = $form.find("input") - inputs.each -> - if @.value != "" - console.log @.value - $(@).parents(".input-text").addClass "active" - check: check - onClick: onClick - onBlur: onBlur - )() +RocketChat.animationSupport = -> + animeEnd = + WebkitAnimation: "webkitAnimationEnd" + OAnimation: "oAnimationEnd" + msAnimation: "MSAnimationEnd" + animation: "animationend" - @Button = (-> - time = undefined - loading = (el) -> - $el = $(el) - next = el.attr("data-loading-text") - html = el.find("span").html() - el.addClass("-progress").attr("data-def-text",html).find("span").html(next) - time = setTimeout -> - el.addClass("going") - , 1 - done = (el) -> - $el = $(el) - el.addClass("done") - reset = (el) -> - clearTimeout(time) if time - $el = $(el) - html= $el.attr("data-def-text") - $el.find("span").html(html) if html - $el.removeClass("-progress going done") - done: done - loading: loading - reset: reset - )() + transEndEventNames = + WebkitTransition: "webkitTransitionEnd" + MozTransition: "transitionend" + OTransition: "oTransitionEnd otransitionend" + msTransition: "MSTransitionEnd" + transition: "transitionend" + prefixB = transEndEventNames[Modernizr.prefixed("transition")] + prefixA = animeEnd[Modernizr.prefixed("animation")] + support = Modernizr.cssanimations + support: support + animation: prefixA + transition: prefixB - animationSupport = -> - animeEnd = - WebkitAnimation: "webkitAnimationEnd" - OAnimation: "oAnimationEnd" - msAnimation: "MSAnimationEnd" - animation: "animationend" +RocketChat.animeBack = (el, callback, type) -> + el = $(el) + if not el.length > 0 + callback el if callback + return + s = animationSupport() + p = ((if type then s.animation else s.transition)) + el.one p, (e) -> - transEndEventNames = - WebkitTransition: "webkitTransitionEnd" - MozTransition: "transitionend" - OTransition: "oTransitionEnd otransitionend" - msTransition: "MSTransitionEnd" - transition: "transitionend" - prefixB = transEndEventNames[Modernizr.prefixed("transition")] - prefixA = animeEnd[Modernizr.prefixed("animation")] - support = Modernizr.cssanimations - support: support - animation: prefixA - transition: prefixB + #el.off(p); + callback e + return - animeBack = (el, callback, type) -> - el = $(el) - if not el.length > 0 - callback el if callback - return - s = animationSupport() - p = ((if type then s.animation else s.transition)) - el.one p, (e) -> + return - #el.off(p); - callback e - return +RocketChat.preLoadImgs = (urls, callback) -> + L_ = (x) -> + if x.width > 0 + $(x).addClass("loaded").removeClass "loading" + loaded = $(".loaded", preLoader) + if loaded.length is urls.length and not ended + ended = 1 + imgs = preLoader.children() + callback imgs + preLoader.remove() + return + im = new Array() + preLoader = $("
").attr(id: "perverter-preloader") + loaded = undefined + ended = undefined + i = 0 - return + while i < urls.length + im[i] = new Image() + im[i].onload = -> + L_ this + return - preLoadImgs = (urls, callback) -> - L_ = (x) -> - if x.width > 0 - $(x).addClass("loaded").removeClass "loading" - loaded = $(".loaded", preLoader) - if loaded.length is urls.length and not ended - ended = 1 - imgs = preLoader.children() - callback imgs - preLoader.remove() - return - im = new Array() - preLoader = $("
").attr(id: "perverter-preloader") - loaded = undefined - ended = undefined - i = 0 + $(im[i]).appendTo(preLoader).addClass "loading" + im[i].src = urls[i] + L_ im[i] if im[i].width > 0 + i++ - while i < urls.length - im[i] = new Image() - im[i].onload = -> - L_ this - return - - $(im[i]).appendTo(preLoader).addClass "loading" - im[i].src = urls[i] - L_ im[i] if im[i].width > 0 - i++ - return - - preLoadImgs: preLoadImgs - animeBack: animeBack - Button: Button - Login: Login -)() + return diff --git a/client/lib/sideNav.coffee b/client/lib/sideNav.coffee index 521625fdb3bd..dbc69fafc0d4 100644 --- a/client/lib/sideNav.coffee +++ b/client/lib/sideNav.coffee @@ -2,6 +2,30 @@ sideNav = {} flexNav = {} + arrow = {} + animating = false + + toggleArrow = (status) -> + if arrow.hasClass "left" or status? is -1 + arrow.removeClass "left" + return + if not arrow.hasClass "left" or status? is 1 + arrow.addClass "left" + + toggleCurrent = -> + if flexNav.opened then closeFlex() else AccountBox.toggle() + + overArrow = -> + arrow.addClass "hover" + + leaveArrow = -> + arrow.removeClass "hover" + + arrowBindHover = -> + arrow.on "mouseenter", -> + sideNav.find("header").addClass "hover" + arrow.on "mouseout", -> + sideNav.find("header").removeClass "hover" focusInput = -> setTimeout -> @@ -19,23 +43,40 @@ return false; toggleFlex = (status) -> + return if animating == true + animating = true if flexNav.opened or status? is -1 flexNav.opened = false flexNav.addClass "hidden" + setTimeout -> + animating = false + , 350 return if not flexNav.opened or status? is 1 flexNav.opened = true - flexNav.removeClass "hidden" + # added a delay to make sure the template is already rendered before animating it + setTimeout -> + flexNav.removeClass "hidden" + , 50 + setTimeout -> + animating = false + , 500 + openFlex = -> - AccountBox.toggleArrow 1 + return if animating == true + toggleArrow 1 toggleFlex 1 focusInput() closeFlex = -> - AccountBox.toggleArrow -1 + return if animating == true + toggleArrow -1 toggleFlex -1 + flexStatus = -> + return flexNav.opened + setFlex = (template, data={}) -> Session.set "flex-nav-template", template Session.set "flex-nav-data", data @@ -49,7 +90,9 @@ init = -> sideNav = $(".side-nav") flexNav = sideNav.find ".flex-nav" + arrow = sideNav.children ".arrow" setFlex "" + arrowBindHover() init: init setFlex: setFlex @@ -57,5 +100,9 @@ openFlex: openFlex closeFlex: closeFlex validate: validate - -)() \ No newline at end of file + flexStatus: flexStatus + toggleArrow: toggleArrow + toggleCurrent: toggleCurrent + overArrow: overArrow + leaveArrow: leaveArrow +)() diff --git a/client/lib/tapi18n.coffee b/client/lib/tapi18n.coffee index 3a5e4ea05386..5f43accaa008 100644 --- a/client/lib/tapi18n.coffee +++ b/client/lib/tapi18n.coffee @@ -1,2 +1,5 @@ @t = (key, replaces...) -> - return TAPi18n.__ key, { postProcess: 'sprintf', sprintf: replaces } + if _.isObject replaces[0] + return TAPi18n.__ key, replaces + else + return TAPi18n.__ key, { postProcess: 'sprintf', sprintf: replaces } diff --git a/client/lib/textarea-autogrow.js b/client/lib/textarea-autogrow.js index 521fbcc2cd9b..b38be93d114d 100644 --- a/client/lib/textarea-autogrow.js +++ b/client/lib/textarea-autogrow.js @@ -1,76 +1,77 @@ -(function($) -{ - /** - * Auto-growing textareas; technique ripped from Facebook - * - * - * http://github.com/jaz303/jquery-grab-bag/tree/master/javascripts/jquery.autogrow-textarea.js - */ - $.fn.autogrow = function(options) -{ - return this.filter('textarea').each(function() -{ - var self = this; - var $self = $(self); - var minHeight = $self.height(); - var noFlickerPad = $self.hasClass('autogrow-short') ? 0 : parseInt($self.css('lineHeight')) || 0; - var settings = $.extend({ - preGrowCallback: null, - postGrowCallback: null - }, options ); +(function($) { + /** + * Auto-growing textareas; technique ripped from Facebook + * + * + * http://github.com/jaz303/jquery-grab-bag/tree/master/javascripts/jquery.autogrow-textarea.js + */ + $.fn.autogrow = function(options) { + return this.filter('textarea').each(function() { + var self = this; + var $self = $(self); + var minHeight = $self.height(); + var noFlickerPad = $self.hasClass('autogrow-short') ? 0 : parseInt($self.css('lineHeight')) || 0; + var settings = $.extend({ + preGrowCallback: null, + postGrowCallback: null + }, options); - var shadow = $('
').css({ - position: 'absolute', - top: -10000, - left: -10000, - width: $self.width(), - fontSize: $self.css('fontSize'), - fontFamily: $self.css('fontFamily'), - fontWeight: $self.css('fontWeight'), - lineHeight: $self.css('lineHeight'), - resize: 'none', - 'word-wrap': 'break-word' - }).appendTo(document.body); + var shadow = $("div.autogrow-shadow"); + if(!shadow.length){ + shadow = $('
').addClass("autogrow-shadow").appendTo(document.body); + } - var update = function(event) -{ - var times = function(string, number) -{ - for (var i=0, r=''; i/g, '>') -.replace(/&/g, '&') -.replace(/\n$/, '
 ') -.replace(/\n/g, '
') -.replace(/ {2,}/g, function(space){ return times(' ', space.length - 1) + ' ' }); + var update = function(event) { + var times = function(string, number) { + for (var i = 0, r = ''; i < number; i++) r += string; + return r; + }; -// Did enter get pressed? Resize in this keydown event so that the flicker doesn't occur. -if (event && event.data && event.data.event === 'keydown' && event.keyCode === 13 && event.shiftKey) { - val += '
'; -} + var val = self.value.replace(//g, '>') + .replace(/&/g, '&') + .replace(/\n$/, '
 ') + .replace(/\n/g, '
') + .replace(/ {2,}/g, function(space) { + return times(' ', space.length - 1) + ' ' + }); -shadow.css('width', $self.width()); -shadow.html(val + (noFlickerPad === 0 ? '...' : '')); // Append '...' to resize pre-emptively. + // Did enter get pressed? Resize in this keydown event so that the flicker doesn't occur. + if (event && event.data && event.data.event === 'keydown' && event.keyCode === 13 && event.shiftKey) { + val += '
'; + } -var newHeight=Math.max(shadow.height() + noFlickerPad, minHeight); -if(settings.preGrowCallback!=null){ - newHeight=settings.preGrowCallback($self,shadow,newHeight,minHeight); -} + shadow.css('width', $self.width()); + shadow.html(val + (noFlickerPad === 0 ? '...' : '')); // Append '...' to resize pre-emptively. -$self.height(newHeight); + var newHeight = Math.max(shadow.height() + noFlickerPad, minHeight); + if (settings.preGrowCallback != null) { + newHeight = settings.preGrowCallback($self, shadow, newHeight, minHeight); + } -if(settings.postGrowCallback!=null){ - settings.postGrowCallback($self); -} -} + $self.height(newHeight); -$self.change(update).keyup(update).keydown({event:'keydown'},update); -$(window).resize(update); + if (settings.postGrowCallback != null) { + settings.postGrowCallback($self); + } + } -update(); -}); -}; -})(jQuery); + $self.change(update).keyup(update).keydown({ + event: 'keydown' + }, update); + $(window).resize(update); + + update(); + }); + }; +})(jQuery); \ No newline at end of file diff --git a/client/methods/hideRoom.coffee b/client/methods/hideRoom.coffee new file mode 100644 index 000000000000..4c09115416e5 --- /dev/null +++ b/client/methods/hideRoom.coffee @@ -0,0 +1,11 @@ +Meteor.methods + hideRoom: (rid) -> + if not Meteor.userId() + throw new Meteor.Error 203, t('general.User_logged_out') + + ChatSubscription.update + rid: rid + , + $set: + alert: false + open: false diff --git a/client/methods/leaveRoom.coffee b/client/methods/leaveRoom.coffee index 728bb75b3d21..8d85b94212c7 100644 --- a/client/methods/leaveRoom.coffee +++ b/client/methods/leaveRoom.coffee @@ -1,11 +1,13 @@ Meteor.methods - leaveRoom: (roomId) -> - room = ChatRoom.findOne roomId + leaveRoom: (rid) -> + if not Meteor.userId() + throw new Meteor.Error 203, t('general.User_logged_out') - update = - $pull: - uids: Meteor.userId() + ChatSubscription.remove + rid: rid + 'u._id': Meteor.userId() - ChatSubscription.remove { rid: roomId, uid: Meteor.userId() } + ChatRoom.update rid, + $pull: + usernames: Meteor.user().username - ChatRoom.update roomId, update diff --git a/client/methods/saveRoomName.coffee b/client/methods/saveRoomName.coffee new file mode 100644 index 000000000000..b05bc08597a1 --- /dev/null +++ b/client/methods/saveRoomName.coffee @@ -0,0 +1,26 @@ +Meteor.methods + saveRoomName: (rid, name) -> + if not Meteor.userId() + throw new Meteor.Error 203, t('general.User_logged_out') + + room = ChatRoom.findOne rid + + if room.u._id isnt Meteor.userId() or room.t not in ['c', 'p'] + throw new Meteor.Error 403, 'Not allowed' + + name = _.slugify name + + if name is room.name + return + + ChatRoom.update rid, + $set: + name: name + + ChatSubscription.update + rid: rid + , + $set: + name: name + + return true diff --git a/client/methods/sendMessage.coffee b/client/methods/sendMessage.coffee index 4cee976d5d00..877477f6e07e 100644 --- a/client/methods/sendMessage.coffee +++ b/client/methods/sendMessage.coffee @@ -1,21 +1,26 @@ Meteor.methods - sendMessage: (msg) -> + sendMessage: (message) -> + if not Meteor.userId() + throw new Meteor.Error 203, t('general.User_logged_out') + Tracker.nonreactive -> - now = new Date(Date.now() + TimeSync.serverOffset()) - ChatMessage.upsert { rid: msg.rid, uid: Meteor.userId(), t: 't' }, - $set: - ts: now - msg: msg.message + message.ts = new Date(Date.now() + TimeSync.serverOffset()) + message.u = + _id: Meteor.userId() + username: Meteor.user().username + + message.html = message.msg + if _.trim(message.html) isnt '' + message.html = _.escapeHTML message.html + message = RocketChat.callbacks.run 'beforeSaveMessage', message + message.html = message.html.replace /\n/gm, '
' + + ChatMessage.upsert + rid: message.rid + t: 't' + , + $set: message $unset: t: 1 expireAt: 1 - - updateMessage: (msg) -> - Tracker.nonreactive -> - now = new Date(Date.now() + TimeSync.serverOffset()) - - ChatMessage.update { _id: msg.id, uid: Meteor.userId() }, - $set: - ets: now - msg: msg.message diff --git a/client/methods/toogleFavorite.coffee b/client/methods/toogleFavorite.coffee new file mode 100644 index 000000000000..26da1135face --- /dev/null +++ b/client/methods/toogleFavorite.coffee @@ -0,0 +1,11 @@ +Meteor.methods + toogleFavorite: (rid, f) -> + if not Meteor.userId() + throw new Meteor.Error 203, t('general.User_logged_out') + + ChatSubscription.update + rid: rid + 'u._id': Meteor.userId() + , + $set: + f: f diff --git a/client/methods/typingStatus.coffee b/client/methods/typingStatus.coffee index 8d2536819f5e..f61897ca5135 100644 --- a/client/methods/typingStatus.coffee +++ b/client/methods/typingStatus.coffee @@ -1,22 +1,25 @@ Meteor.methods - typingStatus: (typingData, start) -> + typingStatus: (rid, start) -> if not Meteor.userId() throw new Meteor.Error 203, t('general.User_logged_out') filter = t: 't' - rid: typingData.rid - uid: Meteor.userId() + rid: rid + $and: [{'u._id': Meteor.userId()}] if start + msgData = '$set': expireAt: moment().add(30, 'seconds').toDate() '$setOnInsert': msg: '...' + 'u._id': Meteor.userId() + 'u.username': Meteor.user().username ts: moment().add(1, 'years').toDate() - ChatMessage.upsert(filter, msgData) + else ChatMessage.remove(filter) diff --git a/client/methods/updateMessage.coffee b/client/methods/updateMessage.coffee new file mode 100644 index 000000000000..4698ff65c41f --- /dev/null +++ b/client/methods/updateMessage.coffee @@ -0,0 +1,17 @@ +Meteor.methods + updateMessage: (message) -> + if not Meteor.userId() + throw new Meteor.Error 203, t('general.User_logged_out') + + Tracker.nonreactive -> + + message.ets = new Date(Date.now() + TimeSync.serverOffset()) + message = RocketChat.callbacks.run 'beforeSaveMessage', message + + ChatMessage.update + _id: message.id + 'u._id': Meteor.userId() + , + $set: + ets: message.ets + message: message.msg diff --git a/client/routes/router.coffee b/client/routes/router.coffee index 3a77ba6d8026..324403001f2a 100644 --- a/client/routes/router.coffee +++ b/client/routes/router.coffee @@ -8,7 +8,7 @@ Router.configure return [Meteor.subscribe('userData'), RoomManager.init()] onBeforeAction: -> - Session.set('flexOpened', false) + Session.setDefault('flexOpened', false) Session.set('openedRoom', null) this.next() @@ -70,22 +70,19 @@ Router.route '/room/:_id', name: 'room' waitOn: -> - if Meteor.userId() - return RoomManager.open @params._id + RoomManager.open @params._id onBeforeAction: -> - Session.set('flexOpened', true) - Session.set('openedRoom', this.params._id) + unless ChatRoom.find(@params._id).count() + Router.go 'home' - # zera a showUserInfo pra garantir que vai estar com a listagem do grupo aberta + Session.set('openedRoom', this.params._id) Session.set('showUserInfo', null) - #correção temporária para a versão mobile - if Modernizr.touch - Session.set('flexOpened', false) this.next() action: -> + self = this Session.set('editRoomTitle', false) Meteor.call 'readMessages', self.params._id @@ -110,4 +107,4 @@ Router.route '/history/private', this.render 'privateHistory' waitOn: -> - return [ Meteor.subscribe('privateHistoryRooms') ] + return [ Meteor.subscribe('privateHistory') ] diff --git a/client/startup/roomObserve.coffee b/client/startup/roomObserve.coffee new file mode 100644 index 000000000000..2cbee3b767af --- /dev/null +++ b/client/startup/roomObserve.coffee @@ -0,0 +1,8 @@ +Meteor.startup -> + ChatRoom.find().observe + added: (data) -> + Session.set('roomData' + data._id, data) + changed: (data) -> + Session.set('roomData' + data._id, data) + removed: (data) -> + Session.set('roomData' + data._id, undefined) diff --git a/client/startup/startup.coffee b/client/startup/startup.coffee index cfff27c696e8..0a191d386cf7 100644 --- a/client/startup/startup.coffee +++ b/client/startup/startup.coffee @@ -1,12 +1,16 @@ Meteor.startup -> UserPresence.awayTime = 300000 UserPresence.start() + Meteor.subscribe("activeUsers") + + Session.setDefault('flexOpened', false) + Session.setDefault('AvatarRandom', 0) window.lastMessageWindow = {} + window.lastMessageWindowHistory = {} - @defaultUserLanguage = -> + @defaultUserLanguage = -> lng = window.navigator.userLanguage || window.navigator.language || 'en' - # Fix browsers having all-lowercase language settings eg. pt-br, en-us re = /([a-z]{2}-)([a-z]{2})/ if re.test lng @@ -17,74 +21,30 @@ Meteor.startup -> userLanguage = localStorage.getItem("userLanguage") else userLanguage = defaultUserLanguage() - localStorage.setItem("userLanguage", userLanguage) + userLanguage = userLanguage.split('-').shift() TAPi18n.setLanguage(userLanguage) - moment.locale(userLanguage) - - Meteor.users.find({}, { fields: { name: 1, pictures: 1, status: 1, emails: 1, phone: 1, services: 1 } }).observe - added: (user) -> - Session.set('user_' + user._id + '_name', user.name) - Session.set('user_' + user._id + '_status', user.status) - Session.set('user_' + user._id + '_emails', user.emails) - Session.set('user_' + user._id + '_phone', user.phone) - - UserAndRoom.insert({ type: 'u', uid: user._id, name: user.name}) - changed: (user) -> - Session.set('user_' + user._id + '_name', user.name) - Session.set('user_' + user._id + '_status', user.status) - Session.set('user_' + user._id + '_emails', user.emails) - Session.set('user_' + user._id + '_phone', user.phone) - - UserAndRoom.update({ uid: user._id }, { $set: { name: user.name } }) - removed: (user) -> - Session.set('user_' + user._id + '_name', null) - Session.set('user_' + user._id + '_status', null) - Session.set('user_' + user._id + '_emails', null) - Session.set('user_' + user._id + '_phone', null) - UserAndRoom.remove({ uid: user._id }) + filename = "/moment-locales/#{userLanguage.toLowerCase()}.js" + if filename isnt '/moment-locales/en.js' + $.getScript filename, (data) -> + moment.locale(userLanguage) - ChatRoom.find({ t: { $ne: 'd' } }, { fields: { t: 1, name: 1 } }).observe - added: (room) -> - roomData = { type: 'r', t: room.t, rid: room._id, name: room.name } + # Add ascii support to emojione + emojione?.ascii = true - UserAndRoom.insert(roomData) - changed: (room) -> - UserAndRoom.update({ rid: room._id }, { $set: { t: room.t, name: room.name } }) - removed: (room) -> - UserAndRoom.remove({ rid: room._id }) Tracker.autorun -> - rooms = [] - ChatSubscription.find({ uid: Meteor.userId() }, { fields: { rid: 1 } }).forEach (sub) -> - rooms.push sub.rid - - ChatRoom.find({ _id: $in: rooms }).observe - added: (data) -> - Session.set('roomData' + data._id, data) - changed: (data) -> - # @TODO alterar a sessão adiciona uma reatividade talvez desnecessária, avaliar melhor - Session.set('roomData' + data._id, data) - removed: (data) -> - Session.set('roomData' + data._id, undefined) - - ChatSubscription.find({}, { fields: { ls: 1, ts: 1, rid: 1 } }).observe - changed: (data) -> - if (data.ls? and moment(data.ls).add(1, 'days').startOf('day') >= moment(data.ts).startOf('day')) - KonchatNotification.removeRoomNotification(data.rid) - removed: (data) -> - KonchatNotification.removeRoomNotification(data.rid) + unreadCount = 0 + subscriptions = ChatSubscription.find({}, { fields: { unread: 1 } }) + (unreadCount += r.unread) for r in subscriptions.fetch() - ChatSubscription.find({}, { fields: { unread: 1 } }).observeChanges - changed: (id, fields) -> - if fields.unread and fields.unread > 0 + rxFavico.set 'type', 'warn' + rxFavico.set 'count', unreadCount - # @TODO testar se não é a sala aberta atual e fazer funcionar (não tenho mais os dados da msg) - # KonchatNotification.showDesktop(roomData, self.data.uid + ': ' + self.data.msg) + if unreadCount > 0 + document.title = '(' + unreadCount + ') Rocket.Chat' + else + document.title = 'Rocket.Chat' - KonchatNotification.newMessage() - - # Add ascii support to emojione - emojione?.ascii = true \ No newline at end of file diff --git a/client/startup/subscriptionObserveChanges.coffee b/client/startup/subscriptionObserveChanges.coffee new file mode 100644 index 000000000000..0628da35d026 --- /dev/null +++ b/client/startup/subscriptionObserveChanges.coffee @@ -0,0 +1,5 @@ +Meteor.startup -> + ChatSubscription.find({}, { fields: { unread: 1 } }).observeChanges + changed: (id, fields) -> + if fields.unread and fields.unread > 0 + KonchatNotification.newMessage() diff --git a/client/startup/usersObserve.coffee b/client/startup/usersObserve.coffee new file mode 100644 index 000000000000..3d5be4ffd8ef --- /dev/null +++ b/client/startup/usersObserve.coffee @@ -0,0 +1,8 @@ +Meteor.startup -> + Meteor.users.find({}, { fields: { name: 1, username: 1, pictures: 1, status: 1, emails: 1, phone: 1, services: 1 } }).observe + added: (user) -> + Session.set('user_' + user.username + '_status', user.status) + changed: (user) -> + Session.set('user_' + user.username + '_status', user.status) + removed: (user) -> + Session.set('user_' + user.username + '_status', null) diff --git a/client/stylesheets/base.less b/client/stylesheets/base.less index 6d8b11eab09e..a9f31c0f7c18 100644 --- a/client/stylesheets/base.less +++ b/client/stylesheets/base.less @@ -1,26 +1,13 @@ +// +// ---------------- +// +@import "global/_variables.less"; @import "utils/_lesshat.import.less"; @import "utils/_reset.import.less"; @import "utils/_keyframes.import.less"; @import "utils/_preloader.import.less"; -@import url(http://fonts.googleapis.com/css?family=Roboto:300,400,500,700,900); -@import url(http://fonts.googleapis.com/css?family=Muli:400,300,500); -@footer-min-height: 70px; -@header-min-height: 60px; -@rooms-box-width: 260px; -@flex-tab-width: 400px; -//@primary-background-color: #045080; -//@primary-background-color: #38393d; -@primary-background-color: #04436a; -@secondary-background-color: #F4F4F4; -@tertiary-background-color: #EAEAEA; -@primary-font-color: #444444; -@secondary-font-color: #7f7f7f; -@tertiary-font-color: rgba(255, 255, 255, 0.6); -@quaternary-font-color: rgba(255, 255, 255, 0.85); -@status-online: #35AC19; -@status-offline: #CCCCCC; -@status-busy: #D30230; -@status-away: #fcb316; +@import url(//fonts.googleapis.com/css?family=Roboto:300,400,500,700,900); +@import url(//fonts.googleapis.com/css?family=Muli:400,300,500); @import "utils/_emojione.import.less"; .cf_ { display: inline-block; @@ -53,15 +40,33 @@ a { } code { - color: #f8f8f2; + background: #f8f8f8; + border-radius: 4px; + border: 1px solid #ccc; + color: #333; display: block; - padding: .5em; - background: #23241f; - border-radius: 6px; - border: none; + font-weight: bold; overflow-x: auto; + padding: 0.5em; word-wrap: break-word; - font-weight: bold; + &.inline { + display: inline-block; + padding: 0 10px; + margin: 0; + vertical-align: bottom; + } +} + +q { + padding-left: 10px; + &:before { + content: ' '; + background-color: #ccc; + height: 22px; + width: 3px; + position: absolute; + left: 50px; + } } .text-center { @@ -268,7 +273,7 @@ input.search { } } -form.search-form { +.search-form { position: relative; } @@ -551,13 +556,13 @@ label.required:after { } } -.a-plus{ +.a-plus { display: block; width: 25px; height: 25px; z-index: 100; position: relative; - &:before, &:after{ + &:before, &:after { content: " "; display: block; background-color: #aaa; @@ -570,16 +575,16 @@ label.required:after { } &:after { .transform(rotate(90deg)); - .transition(transform .315s ease-out .2s, background .15s ease-out); + .transition(transform .315s ease-out .1s, background .15s ease-out); } &:before { .transition(transform .315s ease-out .3s, background .15s ease-out); } - &:hover{ - &:before{ + &:hover { + &:before { .transform(rotate(180deg)); } - &:after{ + &:after { .transform(rotate(-90deg)); } } @@ -630,6 +635,9 @@ a.github-fork { width: 100%; height: 1px; } + @media all and (max-width: 600px) { + display: none; + } } .mac-bar { @@ -664,16 +672,35 @@ a.github-fork { width: 100%; overflow: hidden; position: relative; + border-radius: 4px; .avatar-image { height: 100%; width: 100%; - min-height: 40px; - min-width: 40px; + min-height: 20px; + min-width: 20px; display: block; + position: relative; background-color: transparent; background-size: cover; background-repeat: no-repeat; background-position: center; + border-radius: 4px; + } + &[initials]:before { + content: attr(initials); + position: absolute; + position: absolute; + font-size: 22px; + text-align: center; + width: 100%; + height: 100%; + color: #FFF; + display: flex; + align-items: center; + justify-content: center; + font-family: Helvetica; + text-transform: uppercase; + font-weight: bold; } } @@ -710,20 +737,24 @@ a.github-fork { .info { position: relative; height: 100%; + padding: 10px 0px 10px 18px; .thumb { float: left; height: 100%; position: relative; - width: 60px; - padding: 8px; + width: 42px; + padding: 0; + height: 42px; &:after { content: " "; display: block; - width: 44px; - height: 2px; + width: 8px; + height: 8px; z-index: 10; position: absolute; - bottom: 5px; + border-radius: 4px; + top: 18px; + left: -14px; } .avatar-initials { line-height: 44px; @@ -732,7 +763,7 @@ a.github-fork { .data { float: left; position: relative; - padding: 0 25px 0 5px; + padding: 0 25px 0 10px; height: 100%; display: table; .calc(width, ~"100% - 60px"); @@ -793,7 +824,7 @@ a.github-fork { .transform(translateX(-100%)); } .status { - padding-left: 30px; + padding-left: 38px; position: relative; &:after { content: " "; @@ -804,8 +835,8 @@ a.github-fork { border-radius: 50%; z-index: 5; position: absolute; - left: 12px; - .calc(top, ~"50% - 6px"); + left: 18px; + .calc(top, ~"50% - 8px"); } &.offline { &:after { @@ -842,7 +873,10 @@ a.github-fork { top: 17px; } i { - width: 18px; + width: 26px; + display: inline-block; + text-align: center; + margin-left: 0 -1px 0 1px; } a { position: relative; @@ -863,32 +897,38 @@ a.github-fork { text-decoration: none; } } + .icon-logout { + &:before { + margin-right: 0px; + } + } + .icon-camera { + &:before { + margin-left: 1px; + } + } } &.active .info, .info:hover { h4 { color: rgba(255, 255, 255, 0.85); } - .arrow { - &:before, &:after { - background-color: rgba(255, 255, 255, 0.85); - } - } } - .arrow { - position: absolute; - right: 5px; + .hover & { + .info h4 { + color: rgba(255, 255, 255, 0.85); + } } } // rooms-box .flex-nav { - background-color: @primary-background-color; + //background-color: @primary-background-color; + background-color: transparent; position: fixed; top: 0; left: 0; height: 100%; - padding-top: 15px; z-index: 1000; overflow-y: auto; overflow-x: hidden; @@ -898,14 +938,40 @@ a.github-fork { .custom-scroll(transparent, rgba(255, 255, 255, 0.05)); &.hidden { .transform(translateX(-100%)); + header, footer, .content { + .transform(translateX(-100%)); + } + } + header, + footer, + .content { + .transition(transform .425s cubic-bezier(0, .8, .05, 1)); + } + > section { + &:extend(.fill-all); } header { - padding: 0 20px; - margin-bottom: 30px; + display: table; + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 110; + cursor: pointer; + min-height: @header-min-height; + height: @header-min-height; + padding-left: 15px; + .transition-delay(.05s); + background-color: @primary-background-color; + > div { + display: table-cell; + vertical-align: middle; + text-align: left; + } h4 { line-height: 18px; - font-size: 21px; - margin-top: 3px; + font-size: 20px; + margin-top: 2px; font-weight: 300; overflow: hidden; text-overflow: ellipsis; @@ -913,11 +979,54 @@ a.github-fork { } p { line-height: 18px; - margin-top: 6px; + margin-top: 4px; + font-weight: 400; + font-size: 13px; + } + } + footer { + display: table; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + padding: 0 10px; + z-index: 120; + text-align: left; + background: #fff; + min-height: @footer-min-height; + background-color: @primary-background-color; + .transition-delay(.22s); + > div { + display: table-cell; + vertical-align: middle; + text-align: left; } } .content { - padding: 0 20px 20px; + direction: rtl; + position: absolute; + top: @header-min-height; + .calc(height, ~"100% - " @header-min-height + @footer-min-height); + width: 100%; + overflow-x: hidden; + overflow-y: scroll; + display: block; + -webkit-overflow-scrolling: touch; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.4) inset; + padding: 35px 10px; + .custom-scroll(transparent, rgba(255, 255, 255, 0.05)); + .transition-delay(.135s); + background-color: @primary-background-color; + > .wrapper { + direction: ltr; + } + h4 { + margin-bottom: 30px; + font-weight: 400; + text-transform: uppercase; + font-size: 13px; + } } .input-line { margin-bottom: 25px; @@ -988,7 +1097,7 @@ a.github-fork { .calc(height, ~"100% - " @header-min-height + @footer-min-height); width: 100%; overflow-x: hidden; - overflow-y: scroll; + overflow-y: auto; display: block; -webkit-overflow-scrolling: touch; box-shadow: 0 0 5px rgba(0, 0, 0, 0.4) inset; @@ -1000,6 +1109,16 @@ a.github-fork { padding-bottom: 1em; } } + .more { + display: block; + color: @tertiary-font-color; + font-size: 11px; + padding: 4px 0 4px 10px; + margin-top: 2px; + &:hover { + background-color: rgba(0, 0, 0, 0.1); + } + } .input-error { text-align: center; color: #f09286; @@ -1031,13 +1150,25 @@ a.github-fork { height: @header-min-height; background-color: @primary-background-color; } + > .arrow { + position: absolute; + top: 18px; + right: 8px; + z-index: 1000; + cursor: pointer; + &.hover, &:hover { + &:before, &:after { + background-color: rgba(255, 255, 255, 0.85); + } + } + } .footer { position: absolute; bottom: 0; left: 0; width: 100%; padding: 10px 15px 0px 15px; - z-index: 100; + z-index: 120; text-align: right; background: #fff; min-height: @footer-min-height; @@ -1062,7 +1193,7 @@ a.github-fork { } img { display: inline-block; - width: 90%; + width: 85%; } } .search-form { @@ -1084,7 +1215,6 @@ a.github-fork { color: @tertiary-font-color; line-height: 28px; padding-left: 10px; - a { color: inherit; display: block; @@ -1092,21 +1222,20 @@ a.github-fork { &:hover { background-color: rgba(0, 0, 0, 0.1); } - &.add-room { &:hover { - i{ - &:before{ + i { + &:before { .transform(rotate(180deg)); } - &:after{ + &:after { .transform(rotate(-90deg)); } } } i { position: absolute; - right: 5px; + right: 6px; top: 1px; } } @@ -1165,6 +1294,12 @@ a.github-fork { opacity: 0; } } + &.has-alert { + .name { + color: #ffffff; + font-weight: bold; + } + } &.away { a { color: #666; @@ -1174,7 +1309,7 @@ a.github-fork { a { display: block; border-radius: 2px 0 0 2px; - padding: 6px 25px 7px 8px; + padding: 6px 25px 7px 6px; font-size: 15px; position: relative; line-height: 16px; @@ -1195,7 +1330,7 @@ a.github-fork { position: absolute; right: 0; width: 50px; - padding-right: 6px; + padding-right: 10px; text-align: right; opacity: 0; background-color: transparent; @@ -1210,6 +1345,12 @@ a.github-fork { color: rgba(255, 255, 255, 0.75); } } + .icon-cancel-circled:before { + margin-left: 2px; + } + .icon-logout { + margin-left: 1px; + } } i { color: rgba(255, 255, 255, 0.35); @@ -1249,14 +1390,25 @@ a.github-fork { .fixed-title { position: absolute; - display: block; - padding: 15px 10px 15px 20px; + display: table; + padding: 0 10px 0 20px; background: #fff; border-bottom: 1px solid @tertiary-background-color; z-index: 100; top: 0; left: 0; width: 100%; + height: @header-min-height; + &.visible { + h2 { + overflow: visible; + } + } + > div { + display: table-cell; + vertical-align: middle; + padding-top: 4px; + } h2 { max-width: 90%; overflow: hidden; @@ -1265,24 +1417,32 @@ a.github-fork { font-size: 22px; font-weight: 500; line-height: 29px; - .icon-star { - margin-right: -4px; - } - .icon-at, - .icon-hash { + .icon-at, .icon-hash { margin-right: -7px; color: #CCCCCC; } + .icon-star, + .icon-star-empty { + margin-right: -4px; + } } .hidden { visibility: hidden; display: none; } - // input[type='text']{ - // .calc(width, ~'100% - 100px'); - // vertical-align: top; - // margin-top: 0; - // } + input[type='text'] { + .calc(width, ~'100% - 100px'); + vertical-align: top; + margin-top: -4px; + margin-left: -3px; + font-size: 20px; + } + .icon-pencil { + vertical-align: text-top; + margin-top: -7px; + display: inline-block; + font-size: 16px; + } } // MAIN CONTENT + MAIN PAGES // @@ -1323,17 +1483,17 @@ a.github-fork { } .page-home { - .fixed-title{ - h2{ + .fixed-title { + h2 { overflow: visible; } } .logo { display: inline-block; - width: 240px; + width: 190px; margin-right: 6px; vertical-align: text-top; - margin-top: -5px; + margin-top: -3px; margin-left: 10px; } .info { @@ -1448,13 +1608,15 @@ a.github-fork { -moz-user-select: text; -ms-user-select: text; user-select: text; + .input-message-editing-container { + .calc(width, ~"100% - 16px"); + } .input-message-editing { - display: block; resize: none; + display: block; padding-top: 9px; padding-bottom: 9px; overflow-y: hidden; - .calc(width, ~"100% - 16px"); } .edit-room-title { color: @secondary-font-color; @@ -1470,7 +1632,7 @@ a.github-fork { height: 100%; top: 0; left: 0; - overflow-y: scroll; + overflow-y: auto; overflow-x: hidden; word-wrap: break-word; -webkit-overflow-scrolling: touch; @@ -1478,20 +1640,23 @@ a.github-fork { } .footer { position: absolute; - padding: 14px 20px 0px 20px; + padding: 8px 20px 0px 20px; background: #fff; border-top: 1px solid @tertiary-background-color; z-index: 100; bottom: 0; left: 0; width: 100%; - height: @footer-min-height; + min-height: @footer-min-height; + } + .message-popup-position { + position: relative; } .message-popup { position: absolute; background: #FAFAFA; z-index: 101; - bottom: @footer-min-height; + bottom: 0px; left: 0px; right: 0px; overflow: hidden; @@ -1536,11 +1701,31 @@ a.github-fork { } } > .users-typing { + float: left; height: 20px; font-size: 12px; color: #888; padding: 3px 0px 0px 5px; } + > .formatting-tips { + float: right; + height: 20px; + font-size: 11px; + color: #888; + padding: 4px 0px 0px 5px; + display: inline-block; + white-space: nowrap; + q { + padding: 0 0 0 3px; + border-left: 3px solid #ccc; + &:before { + content: none !important; + } + } + code { + color: #888; + } + } } .add-user-search { height: 100%; @@ -1553,9 +1738,9 @@ a.github-fork { .messages-box { position: relative; - margin: 60px 20px 0px; + margin: 60px 20px 0px 0px; overflow: hidden; - .calc(width, ~'100% - 20px'); + width: 100%; .calc(height, ~'100% - 120px'); ul { padding: 21px 0 10px; @@ -1585,6 +1770,7 @@ a.github-fork { padding-left: 50px; position: relative; line-height: 20px; + margin: 5px 20px; &.with-thumb { margin-top: 12px; min-height: 40px; @@ -1609,13 +1795,14 @@ a.github-fork { } > div { a { - color: @primary-background-color; - font-weight: 500; + color: @link-font-color; + font-weight: 400; &:hover { - color: darken(@primary-background-color, 10%); + color: darken(@link-font-color, 10%); + text-decoration: underline; } } - ul{ + ul { margin: 0; padding: 0; } @@ -1640,13 +1827,14 @@ a.github-fork { .date { text-align: center; position: relative; - margin-top: 12px; + margin: 15px 20px; &:before { content: " "; height: 1px; - width: 80%; + width: 100%; + left: 0; + right: 0; position: absolute; - left: 10%; top: 11px; background-color: #DFDFDF; } @@ -1677,7 +1865,8 @@ a.github-fork { } } } - .time { + .time, + .time-single { font-size: 12px; position: absolute; display: inline-block; @@ -1685,6 +1874,31 @@ a.github-fork { left: 6px; opacity: 0; } + .time-single { + opacity: 1; + left: -15px; + width: 64px; + .time-single-time { + display: none; + } + .time-single-edited { + text-align: center; + } + } + .message.own:hover { + .time-single { + .time-single-time { + display: block; + } + .time-single-edited { + display: none; + text-align: center; + } + } + .edit-message { + display: inline-block; + } + } .thumb { position: absolute; left: 0; @@ -1692,11 +1906,6 @@ a.github-fork { display: block; width: 40px; height: 40px; - background-color: #FFF; - border-radius: 2px; - background-image: url(/images/no_picture.png); - background-size: 100% auto; - background-position: 50% 50%; } } @@ -1776,17 +1985,28 @@ a.github-fork { position: absolute; left: 0; top: 0px; - background-color: @secondary-background-color; + background-color: @tertiary-background-color; border: none; height: 60px; - width: 28px; + width: 30px; border-bottom: 1px solid @tertiary-background-color; color: @secondary-font-color; cursor: pointer; .transform(translateX(-27px)); - .transition(transform .3s ease-out .4s); + .transition(transform .25s ease-out .475s, background .075s ease-out .5s); &:hover { - color: @primary-font-color; + .arrow { + .arrow { + &:before, &:after { + background-color: #4a4a4a; + } + } + } + } + .arrow { + &:before, &:after { + background-color: #7a7a7a; + } } i { .transform-origin(50%, 50%, 0); @@ -1795,9 +2015,17 @@ a.github-fork { vertical-align: top; margin-top: 1px; } - .flex-opened &{ + .flex-opened & { + background-color: @secondary-background-color; .transition-delay(0s); .transform(translateX(0)); + &:hover { + .arrow { + &:before, &:after { + background-color: #7a7a7a; + } + } + } } } .search-form { @@ -1819,8 +2047,8 @@ a.github-fork { &:extend(.fill-all); .custom-scroll(transparent, #DADADA); margin-top: 60px; - overflow-y: scroll; overflow-x: hidden; + overflow-y: auto; -webkit-overflow-scrolling: touch; .calc(height, ~"100% - " @footer-min-height + @header-min-height); > div { @@ -1838,7 +2066,7 @@ a.github-fork { height: 100%; } } - footer{ + footer { position: absolute; bottom: 0; left: 0; @@ -1848,9 +2076,9 @@ a.github-fork { text-align: right; height: @footer-min-height; } - .social{ + .social { text-align: center; - h4{ + h4 { font-weight: 300; position: absolute; width: 100%; @@ -2026,14 +2254,14 @@ a.github-fork { } .user-image-status(@color) { - .avatar{ - &:after, &:before { + .avatar { + &:after { background-color: darken(@color, 5%); } } } -@user-image-square: 40px; +@user-image-square: 20px; .user-image { margin: 4px; height: @user-image-square; @@ -2043,83 +2271,81 @@ a.github-fork { font-size: 12px; position: relative; display: inline-table; + .avatar { + &:before { + font-size: 10px; + } + } &:hover, &.selected { - .avatar{ + .avatar { &:after { .transform(scaleX(1)) } - .status-offline & { - &:after, &:before { + .status-offline { + &:after { background-color: @status-offline; } } - .status-online & { - &:after, &:before { + .status-online { + &:after { background-color: @status-online; } } - .status-away & { - &:after, &:before { + .status-away { + &:after { background-color: @status-away; } } - .status-busy & { - &:after, &:before { + .status-busy { + &:after { background-color: @status-busy; } } - p{ + p { color: @primary-font-color; } } } .avatar { overflow: visible; - &:after, &:before { + &:after { content: " "; - width: 100%; - height: 2px; + height: 6px; + width: 6px; position: absolute; z-index: 1; - left: 0px; - } - &:after{ - bottom: -4px; - .transform(scaleX(0)); - .transform-origin(50%, 50%); - .transition(transform .15s ease-out .02s); - } - &:before{ - bottom: -3px; + left: -12px; + top: 8px; + border-radius: 3px; } .avatar-initials { line-height: @user-image-square; } } - p{ + p { display: none; } - .lines &{ + .lines & { width: 100%; margin: 0; background-color: transparent; - &:after{ + &:after { display: none; } - a{ + a { .cf_; padding: 5px 0; - height: auto; + height: 30px; background-color: transparent; display: block; - > div{ + > div { float: left; width: @user-image-square; height: @user-image-square; } } - p{ + p { float: left; display: block; line-height: @user-image-square; @@ -2346,7 +2572,7 @@ a.github-fork { } #login-card { - width: 100%; + width: 90%; max-width: 520px; padding: 22px 30px 30px; margin: 25px auto; @@ -2355,10 +2581,21 @@ a.github-fork { border-radius: 2px; position: relative; z-index: 1; + header { + padding: 18px 0; + p { + //font-family: "Muli"; + margin: 8px 0 0; + font-size: 14px; + line-height: 22px; + font-weight: 300; + } + } h2 { &:extend(.rocket-h2); color: @primary-font-color; - margin-bottom: 30px; + line-height: 24px; + margin: 0; &.error { color: #b40202; } @@ -2372,14 +2609,14 @@ a.github-fork { img { width: 200px; } - a{ + a { color: @primary-background-color; margin: 4px 0; display: inline-block; - &:active{ + &:active { color: @primary-background-color; } - &:hover{ + &:hover { color: darken(@primary-background-color, 10%); } } @@ -2389,8 +2626,7 @@ a.github-fork { font-size: 10px; } .submit { - margin-bottom: 12px; - padding-top: 4px; + margin: 12px 0; } .remember { float: left; @@ -2402,19 +2638,6 @@ a.github-fork { float: right; line-height: 20px; } - .submit button {} - .fields { - .transform-style(preserve-3d); - .perspective(600px); - margin-bottom: 26px; - label { - font-family: "Roboto"; - font-size: 13px; - margin-top: 10px; - color: #AAA; - margin-left: 12px; - } - } .input-text { margin: 0 0 14px 0; position: relative; @@ -2431,42 +2654,9 @@ a.github-fork { &:before { visibility: hidden; } - .field { - .transform(rotateX(0)); - } - } - &:hover { - i { - background-color: #BFBFBF; - } - label { - color: #999; - } } } - .field { - display: block; - .transform-origin(50% 100% 0); - .transform(rotateX(100deg)); - .transform-style(preserve-3d); - .backface-visibility(hidden); - .transition(transform .38s cubic-bezier(.5, 0, .1, 1)); - i { - display: block; - width: 100%; - height: 3px; - background-color: #EAEAEA; - position: absolute; - bottom: 0; - left: 0; - .transform-origin(50% 100% 0); - .transform(rotateX(-90deg) translateY(4px)); - .transition(background .2s ease-out); - } - span { - display: block; - .transform-style(flat); - } + .input-text { input { box-shadow: 0 0 0; background-color: transparent; @@ -2681,31 +2871,46 @@ a.github-fork { } .avatar-suggestion-item { - height: 80px; margin: 10px 0px; text-align: left; - - > div { - height: 80px; - width: 80px; + display: table; + width: 100%; + padding: 12px; + background-color: @secondary-background-color; + border: 1px solid darken(@secondary-background-color, 10%); + .transition(background-color .15s ease-out, border-color .15s ease-out); + .avatar { + height: 55px; + width: 55px; background-size: cover; - display: inline-block; - border: 1px solid #DDD; - font-size: 80px; + display: inline-table; + border: 1px solid darken(@tertiary-background-color, 10%); + font-size: 40px; text-align: center; - line-height: 80px; - background-color: #EEE; - color: #CCC; - &.question-mark:before { - content: "?"; + background-color: @tertiary-background-color; + position: relative; + vertical-align: middle; + } + .question-mark { + &::before { position: absolute; left: 0; - width: 80px; + top: 0; + width: 100%; + height: 100%; + margin: 0; + line-height: 55px; + color: darken(@tertiary-background-color, 10%); } } + .action { + text-align: right; + display: inline-table; + vertical-align: middle; + padding-left: 20px; + } button { - display: inline-block; - top: -35px; + min-width: 120px; } input[type=file] { position: absolute !important; diff --git a/client/stylesheets/global/_variables.less b/client/stylesheets/global/_variables.less new file mode 100644 index 000000000000..5517cc726e18 --- /dev/null +++ b/client/stylesheets/global/_variables.less @@ -0,0 +1,27 @@ +@header-min-height: 60px; +@footer-min-height: 70px; + +@rooms-box-width: 260px; +@flex-tab-width: 400px; + +// Colors +// -------------- + +//@primary-background-color: #045080; +//@primary-background-color: #38393d; + +@primary-background-color: #04436a; +@secondary-background-color: #F4F4F4; +@tertiary-background-color: #EAEAEA; + +@link-font-color: #008CE3; + +@primary-font-color: #444444; +@secondary-font-color: #7f7f7f; +@tertiary-font-color: rgba(255, 255, 255, 0.6); +@quaternary-font-color: rgba(255, 255, 255, 0.85); + +@status-online: #35AC19; +@status-offline: #CCCCCC; +@status-busy: #D30230; +@status-away: #fcb316; \ No newline at end of file diff --git a/client/views/app/chatMessageDashboard.coffee b/client/views/app/chatMessageDashboard.coffee index 76d97bfd6af6..639ded8c417f 100644 --- a/client/views/app/chatMessageDashboard.coffee +++ b/client/views/app/chatMessageDashboard.coffee @@ -1,10 +1,12 @@ Template.chatMessageDashboard.helpers own: -> - return 'own' if this.data.uid is Meteor.userId() + return 'own' if this.data.u?._id is Meteor.userId() username: -> - if this.uid? - return Session.get('user_' + this.uid + '_name') + return this.u.username + + messageDate: (date) -> + return moment(date).format('LL') isSystemMessage: -> return this.t in ['s', 'p', 'f', 'r', 'au', 'ru', 'ul', 'nu', 'wm'] @@ -12,41 +14,33 @@ Template.chatMessageDashboard.helpers isEditing: -> return this._id is Session.get('editingMessageId') - message: (preventAutoLinker) -> - if this.by - UserManager.addUser(this.by) - else if this.uid - UserManager.addUser(this.uid) + renderMessage: -> + this.html = this.msg + if _.trim(this.html) isnt '' + this.html = _.escapeHTML this.html + message = RocketChat.callbacks.run 'renderMessage', this + this.html = message.html.replace /\n/gm, '
' + return this.html + + message: -> switch this.t - when 'p' then "#{this.msg}" - when 'r' then t('chatMessageDashboard.Room_name_changed', this.msg, Session.get('user_' + this.by + '_name')) + '.' - when 'au' then t('chatMessageDashboard.User_added_by', this.msg, Session.get('user_' + this.by + '_name')) - when 'ru' then t('chatMessageDashboard.User_removed_by', this.msg, Session.get('user_' + this.by + '_name')) + when 'r' then t('chatMessageDashboard.Room_name_changed', { room_name: this.msg, user_by: Session.get('user_' + this.u._id + '_name') }) + '.' + when 'au' then t('chatMessageDashboard.User_added_by', { user_added: this.msg, user_by: Session.get('user_' + this.u._id + '_name') }) + when 'ru' then t('chatMessageDashboard.User_removed_by', { user_removed: this.msg, user_by: Session.get('user_' + this.u._id + '_name') }) when 'ul' then t('chatMessageDashboard.User_left', this.msg) when 'nu' then t('chatMessageDashboard.User_added', this.msg) when 'wm' then t('chatMessageDashboard.Welcome', this.msg) - else - if preventAutoLinker? - return this.msg - else - return Autolinker.link(_.stripTags(this.msg), { stripPrefix: false }) + else this.msg time: -> return moment(this.ts).format('HH:mm') - newMessage: -> - # @TODO pode melhorar, acho que colocando as salas abertas na sessão - # if $('#chat-window-' + this.rid + '.opened').length == 0 - # return 'new' - - preMD: Template 'preMD', -> - self = this - text = "" - if self.templateContentBlock - text = Blaze._toText(self.templateContentBlock, HTML.TEXTMODE.STRING) - - text = text.replace(/#/g, '\\#') - return text + getPupupConfig: -> + template = Template.instance() + return { + getInput: -> + return template.find('.input-message-editing') + } Template.chatMessageDashboard.events 'mousedown .edit-message': -> @@ -58,11 +52,15 @@ Template.chatMessageDashboard.events Meteor.defer -> $('.input-message-editing').select() + 'click .mention-link': (e) -> + Session.set('flexOpened', true) + Session.set('showUserInfo', $(e.currentTarget).data('username')) + Template.chatMessageDashboard.onRendered -> chatMessages = $('.messages-box .wrapper') message = $(this.firstNode) - if message.data('scroll-to-bottom')? + if this.data.scroll? and message.data('scroll-to-bottom')? if message.data('scroll-to-bottom') and (this.parentTemplate().scrollOnBottom or this.data.data.uid is Meteor.userId()) chatMessages.stop().animate({scrollTop: 99999}, 1000 ) else diff --git a/client/views/app/chatMessageDashboard.html b/client/views/app/chatMessageDashboard.html index 5572cbf92264..58262053cf32 100644 --- a/client/views/app/chatMessageDashboard.html +++ b/client/views/app/chatMessageDashboard.html @@ -1,13 +1,19 @@