commit
c3d268461d
@ -0,0 +1,76 @@ |
||||
### DEFINITIONS |
||||
- If window loses focus user needs to scroll or click/touch some place |
||||
- On hit ESC enable read, force read of current room and remove unread mark |
||||
- When user change room disable read until user interaction |
||||
- Only read if mark of *first-unread* is visible for user or if flag *force* was passed |
||||
- Always read the opened room |
||||
- The default method *read* has a delay of 2000ms to prevent multiple reads and to user be able to see the mark |
||||
### |
||||
|
||||
@readMessage = new class |
||||
constructor: -> |
||||
@canReadMessage = false |
||||
|
||||
readNow: (force=false) -> |
||||
return if @canReadMessage is false |
||||
|
||||
rid = Session.get 'openedRoom' |
||||
if rid? |
||||
subscription = ChatSubscription.findOne rid: rid |
||||
if subscription? and (subscription.alert is true or subscription.unread > 0) |
||||
# Only read messages if user saw the first unread message |
||||
position = $('.message.first-unread').position() |
||||
if force is true or not position? or position.top >= 0 |
||||
Meteor.call 'readMessages', rid |
||||
|
||||
read: _.debounce (force) -> |
||||
@readNow(force) |
||||
, 2000 |
||||
|
||||
disable: -> |
||||
@canReadMessage = false |
||||
|
||||
enable: -> |
||||
@canReadMessage = document.hasFocus() |
||||
|
||||
isEnable: -> |
||||
return @canReadMessage is true |
||||
|
||||
refreshUnreadMark: (rid) -> |
||||
rid ?= Session.get 'openedRoom' |
||||
if rid? |
||||
subscription = ChatSubscription.findOne rid: rid |
||||
room = RoomManager.openedRooms[subscription.t + subscription.name] |
||||
if room? |
||||
$roomDom = $(room.dom) |
||||
$roomDom.find('.message.first-unread').addClass('first-unread-opaque') |
||||
if (subscription.rid isnt rid or readMessage.isEnable() is false) and (subscription.alert or subscription.unread > 0) |
||||
$roomDom.find('.message.first-unread').removeClass('first-unread').removeClass('first-unread-opaque') |
||||
firstUnreadId = ChatMessage.findOne({rid: subscription.rid, ts: {$gt: subscription.ls}, 'u._id': {$ne: Meteor.userId()}}, {sort: {ts: 1}})?._id |
||||
if firstUnreadId? |
||||
$roomDom.find('.message#'+firstUnreadId).addClass('first-unread') |
||||
|
||||
|
||||
Meteor.startup -> |
||||
$(window).on 'blur', -> |
||||
readMessage.disable() |
||||
|
||||
$(window).on 'focus', -> |
||||
readMessage.enable() |
||||
readMessage.read() |
||||
|
||||
$(window).on 'click', (e) -> |
||||
readMessage.enable() |
||||
readMessage.read() |
||||
|
||||
$(window).on 'touchend', (e) -> |
||||
readMessage.enable() |
||||
readMessage.read() |
||||
|
||||
$(window).on 'keyup', (e) -> |
||||
key = event.which |
||||
|
||||
if key is 27 |
||||
readMessage.enable() |
||||
readMessage.readNow(true) |
||||
$('.message.first-unread').removeClass('first-unread') |
||||
@ -1,8 +0,0 @@ |
||||
Meteor.startup -> |
||||
$(window).on 'focus', -> |
||||
if FlowRouter.getRouteName() in ['channel', 'group', 'direct'] |
||||
rid = Session.get 'openedRoom' |
||||
if rid? |
||||
subscription = ChatSubscription.findOne rid: rid |
||||
if subscription? and (subscription.alert is true or subscription.unread > 0) |
||||
Meteor.call 'readMessages', rid |
||||
@ -0,0 +1,60 @@ |
||||
Template.userInfo.helpers |
||||
isAdmin: -> |
||||
return Meteor.user()?.admin is true |
||||
utc: -> |
||||
if @utcOffset? |
||||
if @utcOffset > 0 |
||||
return '+' + @utcOffset |
||||
return @utcOffset |
||||
|
||||
phoneNumber: -> |
||||
return '' unless @phoneNumber |
||||
if @phoneNumber.length > 10 |
||||
return "(#{@phoneNumber.substr(0,2)}) #{@phoneNumber.substr(2,5)}-#{@phoneNumber.substr(7)}" |
||||
else |
||||
return "(#{@phoneNumber.substr(0,2)}) #{@phoneNumber.substr(2,4)}-#{@phoneNumber.substr(6)}" |
||||
|
||||
lastLogin: -> |
||||
if @lastLogin |
||||
return moment(@lastLogin).format('LLL') |
||||
|
||||
canDirectMessage: -> |
||||
return Meteor.user()?.username isnt this.username |
||||
|
||||
linkedinUsername: -> |
||||
return s.strRight @services.linkedin.publicProfileUrl, '/in/' |
||||
|
||||
servicesMeteor: -> |
||||
return @services?['meteor-developer'] |
||||
|
||||
videoActive: -> |
||||
return (Session.get('remoteVideoUrl') || Session.get('selfVideoUrl')) |
||||
|
||||
rtcLayout1: -> |
||||
return (Session.get('rtcLayoutmode') == 1 ? true: false) |
||||
|
||||
rtcLayout2: -> |
||||
return (Session.get('rtcLayoutmode') == 2 ? true: false) |
||||
|
||||
rtcLayout3: -> |
||||
return (Session.get('rtcLayoutmode') == 3 ? true: false) |
||||
|
||||
noRtcLayout: -> |
||||
return (!Session.get('rtcLayoutmode') || (Session.get('rtcLayoutmode') == 0) ? true: false) |
||||
|
||||
remoteVideoUrl: -> |
||||
return Session.get('remoteVideoUrl') |
||||
|
||||
selfVideoUrl: -> |
||||
return Session.get('selfVideoUrl') |
||||
|
||||
userTime: -> |
||||
if @utcOffset? |
||||
return Template.instance().now.get().utcOffset(@utcOffset).format('HH:mm') |
||||
|
||||
Template.userInfo.onCreated -> |
||||
@now = new ReactiveVar moment() |
||||
self = @ |
||||
Meteor.setInterval -> |
||||
self.now.set moment() |
||||
, 30000 |
||||
@ -0,0 +1,75 @@ |
||||
<template name="userInfo"> |
||||
{{#if $.Session.get 'showUserInfo'}} |
||||
{{#with user}} |
||||
<div class="about clearfix"> |
||||
{{#if ../video}} |
||||
{{#if videoActive}} |
||||
{{#if rtcLayout3}} |
||||
<div id='fullscreendiv' style="width: 100%"> |
||||
<video id='videoremote' class="video-remote" src="{{remoteVideoUrl}}" style="width: 100%; align-items: center; margin-bottom: 10px; background-color: #000; transition: width 2s, height 2s, top 2s, left 2s, transform 2s;" autoplay></video> |
||||
<video id='videoself' class="video-self" src="{{selfVideoUrl}}" style="width: 250px; position: absolute; top: 21px; right: 31px; border: 1px solid #FFF; background-color: #000; transition: width 2s, height 2s, top 2s, left 2s, transform 2s;" autoplay muted></video> |
||||
</div> |
||||
{{/if}} |
||||
{{#if rtcLayout2}} |
||||
<div style="display: flex; flex-direction: row; align-items: center; width: 800px; height: 350px"> |
||||
<video id="videoremote" class="video-remote" src="{{remoteVideoUrl}}" style="flex: 1 0; min-width: 380px; height: 285px; margin: 10px; background-color: #000;" autoplay></video> |
||||
<video id="videoself" class="video-self" src="{{selfVideoUrl}}" style="flex: 1 0; min-width: 380px; height: 285px; margin: 10px; background-color: #000;" autoplay muted></video> |
||||
</div> |
||||
{{/if}} |
||||
{{#if rtcLayout1}} |
||||
<div style="display: flex; flex-direction: column; align-items: center; width: 360px; height: 500px"> |
||||
<video class="video-remote" src="{{remoteVideoUrl}}" style="flex: 1 1; width: 320px; margin: 10px; background-color: #000;" autoplay></video> |
||||
<video class="video-self" src="{{selfVideoUrl}}" style="flex: 1 1; width: 320px; margin: 10px; background-color: #000;" autoplay muted></video> |
||||
</div> |
||||
{{/if}} |
||||
{{#if noRtcLayout}} |
||||
<div> |
||||
<video class="video-remote" src="{{remoteVideoUrl}}" style="width: 100%; margin-bottom: 10px; background-color: #000; transition: width 2s, height 2s, top 2s, left 2s, transform 2s;" autoplay></video> |
||||
<video class="video-self" src="{{selfVideoUrl}}" style="width: 100px; position: absolute; top: 21px; right: 21px; border: 1px solid #FFF; background-color: #000; transition: width 2s, height 2s, top 2s, left 2s, transform 2s;" autoplay muted></video> |
||||
</div> |
||||
{{/if}} |
||||
{{/if}} |
||||
{{/if}} |
||||
<div class="thumb"> |
||||
{{> avatar username=username}} |
||||
</div> |
||||
<div class="info"> |
||||
<h3 title="{{username}}"><i class="status-{{status}}"></i> {{username}}</h3> |
||||
<p>{{name}}</p> |
||||
{{#if utc}}<p><i class="icon-clock"></i>{{userTime}} (UTC {{utc}})</p>{{/if}} |
||||
{{#if isAdmin}} |
||||
{{#each emails}} <p><i class="icon-mail"></i> {{address}}{{#if verified}} <i class="icon-ok"></i>{{/if}}</p> {{/each}} |
||||
{{#each phone}} <p><i class="icon-phone"></i> {{phoneNumber}}</p> {{/each}} |
||||
{{#if lastLogin}} <p><i class="icon-calendar"></i> {{_ "Last_login"}}: {{lastLogin}}</p> {{/if}} |
||||
{{#if services.facebook.id}} <p><i class="icon-facebook"></i><a href="{{services.facebook.link}}" target="_blank">{{services.facebook.name}}</a></p> {{/if}} |
||||
{{#if services.github.id}} <p><i class="icon-github-circled"></i><a href="https://www.github.com/{{services.github.username}}" target="_blank">{{services.github.username}}</a></p> {{/if}} |
||||
{{#if services.gitlab.id}} <p><i class="icon-gitlab"></i>{{services.gitlab.username}}</p> {{/if}} |
||||
{{#if services.google.id}} <p><i class="icon-gplus"></i><a href="https://plus.google.com/{{services.google.id}}" target="_blank">{{services.google.name}}</a></p> {{/if}} |
||||
{{#if services.linkedin.id}} <p><i class="icon-linkedin"></i><a href="{{services.linkedin.publicProfileUrl}}" target="_blank">{{linkedinUsername}}</a></p> {{/if}} |
||||
{{#if servicesMeteor.id}} <p><i class="icon-meteor"></i>{{servicesMeteor.username}}</p> {{/if}} |
||||
{{#if services.twitter.id}} <p><i class="icon-twitter"></i><a href="https://twitter.com/{{services.twitter.screenName}}" target="_blank">{{services.twitter.screenName}}</a></p> {{/if}} |
||||
{{/if}} |
||||
</div> |
||||
</div> |
||||
{{/with}} |
||||
<nav> |
||||
{{#if video}} |
||||
{{#unless videoActive}} |
||||
<button class='button start-video'><span><i class='icon-videocam'></i> {{_ "Video_Chat"}}</span></button> |
||||
{{#if remoteMonitoring}} |
||||
<button class='button short monitor-video'><span><i class='icon-videocam'></i> {{_ "Remote"}}</span></button> |
||||
<button class='button lightblue setup-video'><span><i class='icon-videocam'></i> {{_ "Setup"}}</span></button> |
||||
{{/if}} |
||||
{{else}} |
||||
<button class='button red stop-video'><span><i class='icon-videocam'></i> {{_ "Stop_Video"}}</span></button> |
||||
{{/unless}} |
||||
{{/if}} |
||||
{{#if showAll}} |
||||
<button class='button secondary back'><span>{{_ "View_All"}} <i class='icon-angle-right'></i></span></button> |
||||
{{#if canDirectMessage}} |
||||
<button class='button pvt-msg'><span><i class='icon-chat'></i> {{_ "Conversation"}}</span></button> |
||||
{{/if}} |
||||
{{/if}} |
||||
</nav> |
||||
{{/if}} |
||||
</template> |
||||
@ -0,0 +1,44 @@ |
||||
Meteor.publish 'fullUserData', (filter, limit) -> |
||||
unless @userId |
||||
return @ready() |
||||
|
||||
user = Meteor.users.findOne @userId |
||||
|
||||
fields = |
||||
name: 1 |
||||
username: 1 |
||||
status: 1 |
||||
utcOffset: 1 |
||||
|
||||
if user.admin is true |
||||
fields = _.extend fields, |
||||
emails: 1 |
||||
phone: 1 |
||||
statusConnection: 1 |
||||
admin: 1 |
||||
lastLogin: 1 |
||||
active: 1 |
||||
services: 1 |
||||
else |
||||
limit = 1 |
||||
|
||||
filter = s.trim filter |
||||
|
||||
if not filter and limit is 1 |
||||
return @ready() |
||||
|
||||
if filter |
||||
if limit is 1 |
||||
query = { username: filter } |
||||
else |
||||
filterReg = new RegExp filter, "i" |
||||
query = { $or: [ { username: filterReg }, { name: filterReg }, { "emails.address": filterReg } ] } |
||||
else |
||||
query = {} |
||||
|
||||
console.log '[publish] fullUserData'.green, filter, limit |
||||
|
||||
Meteor.users.find query, |
||||
fields: fields |
||||
limit: limit |
||||
sort: { username: 1 } |
||||
@ -1,37 +0,0 @@ |
||||
Meteor.publish 'fullUsers', (filter, limit) -> |
||||
unless this.userId |
||||
return this.ready() |
||||
|
||||
user = Meteor.users.findOne this.userId |
||||
if user.admin isnt true |
||||
return this.ready() |
||||
|
||||
filter = _.trim filter |
||||
if filter |
||||
if limit is 1 |
||||
query = { username: filter } |
||||
else |
||||
filterReg = new RegExp filter, "i" |
||||
query = { $or: [ { username: filterReg }, { name: filterReg }, { "emails.address": filterReg } ] } |
||||
else |
||||
query = {} |
||||
|
||||
console.log '[publish] fullUsers'.green, filter, limit |
||||
|
||||
Meteor.users.find query, |
||||
fields: |
||||
name: 1 |
||||
username: 1 |
||||
emails: 1 |
||||
phone: 1 |
||||
status: 1 |
||||
statusDefault: 1 |
||||
statusConnection: 1 |
||||
avatarOrigin: 1 |
||||
admin: 1 |
||||
utcOffset: 1 |
||||
language: 1 |
||||
lastLogin: 1 |
||||
active: 1 |
||||
limit: limit |
||||
sort: { username: 1 } |
||||
@ -0,0 +1,6 @@ |
||||
Meteor.startup -> |
||||
Migrations.add |
||||
version: 16 |
||||
up: -> |
||||
rawMessage = ChatMessage.rawCollection() |
||||
Meteor.wrapAsync(rawMessage.dropIndex, rawMessage)({ _hidden: 1 }) |
||||
@ -0,0 +1,6 @@ |
||||
Meteor.startup -> |
||||
Migrations.add |
||||
version: 17 |
||||
up: -> |
||||
rawMessage = ChatMessage.rawCollection() |
||||
Meteor.wrapAsync(rawMessage.dropIndex, rawMessage)({ _hidden: 1 }) |
||||
Loading…
Reference in new issue