2013-05-15 19:55:51 +00:00
Tims Chat 3
===========
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
This is the main javascript file for [**Tims Chat**](https://github.com/wbbaddons/Tims-Chat). It handles
everything that happens in the GUI of **Tims Chat**.
### Copyright Information
2013-05-14 18:29:44 +00:00
# @author Tim Düsterhus
# @copyright 2010-2013 Tim Düsterhus
# @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
2013-05-15 19:55:51 +00:00
# @package be.bastelstu.chat
2013-05-14 18:29:44 +00:00
###
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
## Code
We start by setting up our environment by ensuring some sane values for both `$` and `window`,
2013-05-15 20:11:47 +00:00
enabling EMCAScript 5 strict mode and overwriting console to prepend the name of the class.
2013-04-13 14:38:50 +00:00
(($, window) ->
"use strict";
console =
log: (message) ->
window.console.log "[be.bastelstu.Chat] #{message}"
warn: (message) ->
window.console.warn "[be.bastelstu.Chat] #{message}"
error: (message) ->
window.console.error "[be.bastelstu.Chat] #{message}"
2013-05-15 20:11:47 +00:00
Continue with defining the needed variables. All variables are local to our closure and will be
2013-05-15 19:55:51 +00:00
exposed by a function if necessary.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
isActive = true
newMessageCount = 0
2013-05-30 17:03:37 +00:00
scrollUpNotifications = off
2013-05-24 13:30:18 +00:00
chatSession = Date.now()
2013-05-24 13:55:52 +00:00
errorVisible = false
2013-04-13 14:38:50 +00:00
2013-06-23 17:46:50 +00:00
lastMessage = null
2013-05-15 19:55:51 +00:00
remainingFailures = 3
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
events =
newMessage: $.Callbacks()
userMenu: $.Callbacks()
submit: $.Callbacks()
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
pe =
getMessages: null
refreshRoomList: null
fish: null
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
loading = false
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
autocomplete =
2013-05-10 15:51:48 +00:00
offset: 0
value: null
caret: 0
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
v =
titleTemplate: null
messageTemplate: null
userTemplate: null
config: null
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Initialize **Tims Chat**. Bind needed DOM events and initialize data structures.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
initialized = false
2013-05-26 15:19:04 +00:00
init = (roomID, config, titleTemplate, messageTemplate, userTemplate) ->
2013-05-15 19:55:51 +00:00
return false if initialized
initialized = true
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
v.config = config
v.titleTemplate = titleTemplate
v.messageTemplate = messageTemplate
v.userTemplate = userTemplate
console.log 'Initializing'
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
When **Tims Chat** becomes focused mark the chat as active and remove the number of new messages from the title.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$(window).focus ->
document.title = v.titleTemplate.fetch
title: $('#timsChatRoomList .active a').text()
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
newMessageCount = 0
isActive = true
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
When **Tims Chat** loses the focus mark the chat as inactive.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$(window).blur ->
isActive = false
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Make the user leave the chat when **Tims Chat** is about to be unloaded.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$(window).on 'beforeunload', ->
2013-05-24 17:26:29 +00:00
return undefined if errorVisible
2013-05-15 19:55:51 +00:00
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'leave'
className: 'chat\\data\\room\\RoomAction'
showLoadingOverlay: false
async: false
suppressErrors: true
undefined
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Insert the appropriate smiley code into the input when a smiley is clicked.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$('#smilies').on 'click', 'img', ->
insertText ' ' + $(@).attr('alt') + ' '
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Handle submitting the form. The message will be validated by some basic checks, passed to the `submit` eventlisteners
and afterwards sent to the server by an AJAX request.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatForm').submit (event) ->
event.preventDefault()
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
text = $('#timsChatInput').val().trim()
$('#timsChatInput').val('').focus().keyup()
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
return false if text.length is 0
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
# Free the fish!
freeTheFish() if text.toLowerCase() is '/free the fish'
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
text = do (text) ->
obj =
text: text
events.submit.fire obj
obj.text
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'send'
className: 'chat\\data\\message\\MessageAction'
parameters:
text: text
enableSmilies: $('#timsChatSmilies').data 'status'
showLoadingOverlay: false
success: ->
$('#timsChatInputContainer').removeClass('formError').find('.innerError').hide()
getMessages()
failure: (data) ->
return true unless (data?.returnValues?.errorType?) or (data?.message?)
$('#timsChatInputContainer').addClass('formError').find('.innerError').show().html (data?.returnValues?.errorType) ? data.message
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
setTimeout ->
$('#timsChatInputContainer').removeClass('formError').find('.innerError').hide()
, 5e3
2013-05-18 19:42:27 +00:00
false
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Autocomplete a username when TAB is pressed. The name to autocomplete is based on the current caret position.
The the word the caret is in will be passed to `autocomplete` and replaced if a match was found.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatInput').keydown (event) ->
if event.keyCode is $.ui.keyCode.TAB
input = $(event.currentTarget)
2013-04-13 14:38:50 +00:00
event.preventDefault()
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
autocomplete.value ?= input.val()
autocomplete.caret ?= input.getCaret()
beforeCaret = autocomplete.value.substring 0, autocomplete.caret
lastSpace = beforeCaret.lastIndexOf ' '
beforeComplete = autocomplete.value.substring 0, lastSpace + 1
toComplete = autocomplete.value.substring lastSpace + 1
nextSpace = toComplete.indexOf ' '
if nextSpace is -1
afterComplete = '';
else
afterComplete = toComplete.substring nextSpace + 1
toComplete = toComplete.substring 0, nextSpace
return if toComplete.length is 0
console.log "Autocompleting '#{toComplete}'"
2013-05-23 23:47:15 +00:00
2013-06-01 12:06:55 +00:00
if beforeComplete is '' and toComplete.substring(0, 1) is '/'
regex = new RegExp "^#{WCF.String.escapeRegExp toComplete.substring 1}", "i"
# TODO: Proper command list
2013-06-01 12:37:18 +00:00
commands = (command for command in v.config.installedCommands when regex.test command)
2013-06-01 12:06:55 +00:00
toComplete = '/' + commands[autocomplete.offset++ % commands.length] + ' ' if commands.length isnt 0
else
regex = new RegExp "^#{WCF.String.escapeRegExp toComplete}", "i"
users = (username for user in $('.timsChatUser') when regex.test(username = $(user).data('username')))
toComplete = users[autocomplete.offset++ % users.length] + ', ' if users.length isnt 0
2013-05-15 19:55:51 +00:00
input.val "#{beforeComplete}#{toComplete}#{afterComplete}"
input.setCaret (beforeComplete + toComplete).length
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Reset autocompleter to default status, when a key is pressed that is not TAB.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
else
$('#timsChatInput').click()
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Reset autocompleter to default status, when the input is `click`ed, as the position of the caret may have changed.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatInput').click ->
autocomplete =
offset: 0
value: null
caret: null
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Refresh the room list when the associated button is `click`ed.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatRoomList button').click ->
2013-05-17 18:23:59 +00:00
refreshRoomList()
2013-04-27 11:04:36 +00:00
2013-05-15 19:55:51 +00:00
Clear the chat by removing every single message once the clear button is `clicked`.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatClear').click (event) ->
event.preventDefault()
2013-07-09 17:41:14 +00:00
$('.timsChatMessageContainer.active .timsChatMessage').remove()
$('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop('scrollHeight')
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Handle toggling of the toggleable buttons.
$('.timsChatToggle').click (event) ->
element = $ @
if element.data('status') is 1
element.data 'status', 0
element.removeClass 'active'
element.attr 'title', element.data 'enableMessage'
else
element.data 'status', 1
element.addClass 'active'
element.attr 'title', element.data 'disableMessage'
$('#timsChatInput').focus()
2013-04-22 16:52:05 +00:00
2013-05-15 19:55:51 +00:00
Mark smilies as disabled when they are disabled.
2013-04-22 16:52:05 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatSmilies').click (event) ->
if $(@).data 'status'
$('#smilies').removeClass 'disabled'
else
$('#smilies').addClass 'disabled'
2013-04-22 16:52:05 +00:00
2013-04-13 14:38:50 +00:00
Toggle fullscreen mode.
2013-05-15 19:55:51 +00:00
$('#timsChatFullscreen').click (event) ->
2013-05-30 19:50:47 +00:00
# Force dropdowns to reorientate
$('.dropdownMenu').data 'orientationX', ''
2013-05-15 19:55:51 +00:00
if $('#timsChatFullscreen').data 'status'
$('html').addClass 'fullscreen'
else
$('html').removeClass 'fullscreen'
2013-04-26 21:00:48 +00:00
2013-07-05 14:15:07 +00:00
Toggle checkboxes.
2013-04-26 21:00:48 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatMark').click (event) ->
if $(@).data 'status'
$('.timsChatMessageContainer').addClass 'markEnabled'
else
$('.timsChatMessageContainer').removeClass 'markEnabled'
2013-04-26 21:00:48 +00:00
2013-07-05 14:15:07 +00:00
Hide topic container.
$('.jsTopicCloser').on 'click', ->
$('#timsChatTopic').addClass 'hidden'
2013-05-15 19:55:51 +00:00
Visibly mark the message once the associated checkbox is checked.
$(document).on 'click', '.timsChatMessage :checkbox', (event) ->
if $(@).is ':checked'
2013-05-26 15:19:04 +00:00
$(@).parents('.timsChatMessage').addClass 'jsMarked'
2013-05-15 19:55:51 +00:00
else
2013-05-26 15:19:04 +00:00
$(@).parents('.timsChatMessage').removeClass 'jsMarked'
2013-05-30 17:03:37 +00:00
2013-04-22 16:52:05 +00:00
Scroll down when autoscroll is being activated.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatAutoscroll').click (event) ->
if $('#timsChatAutoscroll').data 'status'
2013-07-09 17:41:14 +00:00
$('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop('scrollHeight')
2013-05-30 17:03:37 +00:00
2013-07-09 17:41:14 +00:00
$('.timsChatMessageContainer.active').on 'scroll', (event) ->
2013-05-23 21:36:47 +00:00
element = $ @
scrollTop = element.scrollTop()
scrollHeight = element.prop 'scrollHeight'
height = element.height()
if scrollTop < scrollHeight - height - 25
2013-05-15 19:55:51 +00:00
if $('#timsChatAutoscroll').data('status') is 1
2013-05-30 17:03:37 +00:00
scrollUpNotifications = on
2013-05-23 21:36:47 +00:00
$('#timsChatAutoscroll').click()
if scrollTop > scrollHeight - height - 10
if $('#timsChatAutoscroll').data('status') is 0
2013-05-30 17:03:37 +00:00
scrollUpNotifications = off
$(@).removeClass 'notification'
2013-05-23 21:36:47 +00:00
$('#timsChatAutoscroll').click()
2013-04-13 14:38:50 +00:00
2013-05-24 13:30:18 +00:00
Enable duplicate tab detection.
window.localStorage.setItem 'be.bastelstu.chat.session', chatSession
$(window).on 'storage', (event) ->
if event.originalEvent.key is 'be.bastelstu.chat.session'
2013-05-25 12:57:27 +00:00
if parseInt(event.originalEvent.newValue) isnt chatSession
2013-05-24 17:26:29 +00:00
showError WCF.Language.get 'chat.error.duplicateTab'
2013-05-30 17:03:37 +00:00
2013-04-13 14:38:50 +00:00
Ask for permissions to use Desktop notifications when notifications are activated.
2013-05-15 19:55:51 +00:00
if window.Notification?
$('#timsChatNotify').click (event) ->
return unless $(@).data 'status'
if window.Notification.permission isnt 'granted'
window.Notification.requestPermission (permission) ->
window.Notification.permission ?= permission
2013-05-30 17:03:37 +00:00
events.newMessage.add notify
2013-05-26 15:19:04 +00:00
Initialize the `PeriodicalExecuter`s
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
pe.refreshRoomList = new WCF.PeriodicalExecuter refreshRoomList, 60e3
pe.getMessages = new WCF.PeriodicalExecuter getMessages, v.config.reloadTime * 1e3
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Initialize the [**nodePush**](https://github.com/wbbaddons/nodePush) integration of **Tims Chat**. Once
the browser is connected to **nodePush** periodic message loading will be disabled and **Tims Chat** will
load messages if the appropriate event arrives.
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
do ->
be.bastelstu.wcf.nodePush.onConnect ->
console.log 'Disabling periodic loading'
pe.getMessages.stop()
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
be.bastelstu.wcf.nodePush.onDisconnect ->
console.log 'Enabling periodic loading'
getMessages()
pe.getMessages = new WCF.PeriodicalExecuter getMessages, v.config.reloadTime * 1e3
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
be.bastelstu.wcf.nodePush.onMessage 'be.bastelstu.chat.newMessage', getMessages
be.bastelstu.wcf.nodePush.onMessage 'be.bastelstu.wcf.nodePush.tick60', getMessages
2013-04-13 14:38:50 +00:00
2013-05-26 15:19:04 +00:00
Finished! Enable the input now and join the chat.
2013-04-13 14:38:50 +00:00
2013-05-26 15:19:04 +00:00
join roomID
2013-05-15 19:55:51 +00:00
$('#timsChatInput').enable().jCounter().focus();
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
console.log "Finished initializing"
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
true
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Free the fish.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
freeTheFish = ->
return if $.wcfIsset 'fish'
console.warn 'Freeing the fish'
fish = $ """<div id="fish">#{WCF.String.escapeHTML('><((((\u00B0>')}</div>"""
fish.css
position: 'absolute'
top: '150px'
left: '400px'
color: 'black'
textShadow: '1px 1px white'
zIndex: 9999
fish.appendTo $ 'body'
pe.fish = new WCF.PeriodicalExecuter ->
left = Math.random() * 100 - 50
top = Math.random() * 100 - 50
fish = $ '#fish'
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
left *= -1 unless fish.width() < (fish.position().left + left) < ($(document).width() - fish.width())
top *= -1 unless fish.height() < (fish.position().top + top) < ($(document).height() - fish.height())
if left > 0
2013-04-13 14:38:50 +00:00
fish.text '><((((\u00B0>' if left > 0
2013-05-15 19:55:51 +00:00
else if left < 0
fish.text '<\u00B0))))><'
fish.animate
top: "+=#{top}"
left: "+=#{left}"
, 1e3
, 1.5e3
Fetch new messages from the server and pass them to `handleMessages`. The userlist will be passed to `handleUsers`.
`remainingFailures` will be decreased on failure and message loading will be entirely disabled once it reaches zero.
getMessages = ->
$.ajax v.config.messageURL,
dataType: 'json'
type: 'POST'
success: (data) ->
remainingFailures = 3
handleMessages data.messages
handleUsers data.users
2013-05-27 20:20:31 +00:00
WCF.DOMNodeInsertedHandler.execute()
2013-05-15 19:55:51 +00:00
error: ->
console.error "Message loading failed, #{--remainingFailures} remaining"
if remainingFailures <= 0
freeTheFish()
2013-05-24 13:55:52 +00:00
console.error 'To many failures, aborting'
2013-05-15 19:55:51 +00:00
2013-05-24 17:26:29 +00:00
showError WCF.Language.get 'chat.error.onMessageLoad'
2013-05-15 19:55:51 +00:00
complete: ->
loading = false
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Prevent loading messages in parallel.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
beforeSend: ->
return false if loading
loading = true
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Insert the given messages into the chat stream.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
handleMessages = (messages) ->
2013-07-09 17:41:14 +00:00
$('.timsChatMessageContainer.active').trigger 'scroll'
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
for message in messages
events.newMessage.fire message
2013-05-30 17:03:37 +00:00
2013-06-23 17:46:50 +00:00
createNewMessage = yes
if $('.timsChatMessage:last-child .text').is('ul') and lastMessage isnt null and lastMessage.type in [ 0, 7 ]
if lastMessage.type is message.type and lastMessage.sender is message.sender and lastMessage.receiver is message.receiver
createNewMessage = no
if createNewMessage
2013-06-23 17:54:39 +00:00
message.isFollowUp = no
2013-06-24 15:45:46 +00:00
output = v.messageTemplate.fetch
2013-06-24 16:11:55 +00:00
message: message
messageTypes: v.config.messageTypes
2013-06-24 15:45:46 +00:00
2013-06-23 17:46:50 +00:00
li = $ '<li></li>'
li.addClass 'timsChatMessage'
li.addClass "timsChatMessage#{message.type}"
li.addClass "user#{message.sender}"
li.addClass 'ownMessage' if message.sender is WCF.User.userID
li.append output
2013-07-09 17:41:14 +00:00
if message.type is parseInt v.config.messageTypes.WHISPER
if message.sender is WCF.User.userID && $.wcfIsset "timsChatMessageContainer#{message.receiver}"
li.appendTo $ "#timsChatMessageContainer#{message.receiver} > ul"
else if $.wcfIsset "timsChatMessageContainer#{message.sender}"
li.appendTo $ "#timsChatMessageContainer#{message.sender} > ul"
else
li.appendTo $ '#timsChatMessageContainer0 > ul'
else
li.appendTo $ '#timsChatMessageContainer0 > ul'
2013-06-23 17:46:50 +00:00
else
2013-06-23 17:54:39 +00:00
message.isFollowUp = yes
2013-06-24 15:45:46 +00:00
output = v.messageTemplate.fetch
2013-06-24 16:11:55 +00:00
message: message
messageTypes: v.config.messageTypes
2013-06-24 15:45:46 +00:00
2013-06-23 17:46:50 +00:00
$('.timsChatMessage:last-child .text').append $(output).find('.text li:last-child')
lastMessage = message
2013-07-09 17:41:14 +00:00
$('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop('scrollHeight') if $('#timsChatAutoscroll').data('status') is 1
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Rebuild the userlist based on the given `users`.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
handleUsers = (users) ->
foundUsers = { }
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
for user in users
2013-05-15 20:11:47 +00:00
id = "timsChatUser#{user.userID}"
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Move the user to the new position if he was found in the old list.
2013-04-13 14:38:50 +00:00
2013-05-15 20:11:47 +00:00
if $.wcfIsset id
2013-05-15 19:55:51 +00:00
console.log "Moving User: '#{user.username}'"
2013-05-15 20:11:47 +00:00
element = $("##{id}").detach()
2013-05-15 19:55:51 +00:00
if user.awayStatus?
element.addClass 'away'
element.attr 'title', user.awayStatus
else
2013-05-28 21:46:22 +00:00
element.removeClass 'away'
2013-05-15 19:55:51 +00:00
element.removeAttr 'title'
element.data 'tooltip', ''
if user.suspended
element.addClass 'suspended'
else
element.removeClass 'suspended'
$('#timsChatUserList > ul').append element
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Build HTML of the user and insert it into the list, if the users was not found in the chat before.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
else
console.log "Inserting User: '#{user.username}'"
2013-04-13 14:38:50 +00:00
li = $ '<li></li>'
2013-05-15 19:55:51 +00:00
li.attr 'id', id
li.addClass 'timsChatUser'
li.addClass 'jsTooltip'
li.addClass 'dropdown'
li.addClass 'you' if user.userID is WCF.User.userID
li.addClass 'suspended' if user.suspended
if user.awayStatus?
2013-05-28 21:46:22 +00:00
li.addClass 'away'
2013-05-15 19:55:51 +00:00
li.attr 'title', user.awayStatus
li.data 'username', user.username
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
li.append v.userTemplate.fetch user
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
menu = $ '<ul></ul>'
menu.addClass 'dropdownMenu'
menu.append $ "<li><a>#{WCF.Language.get('chat.general.query')}</a></li>"
menu.append $ "<li><a>#{WCF.Language.get('chat.general.kick')}</a></li>"
menu.append $ "<li><a>#{WCF.Language.get('chat.general.ban')}</a></li>"
menu.append $ """<li><a href="#{user.link}">#{WCF.Language.get('chat.general.profile')}</a></li>"""
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
events.userMenu.fire user, menu
2013-05-15 20:11:47 +00:00
li.append menu
2013-05-15 19:55:51 +00:00
li.appendTo $ '#timsChatUserList > ul'
foundUsers[id] = true
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Remove all users that left the chat.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
$('.timsChatUser').each ->
unless foundUsers[$(@).attr('id')]?
console.log "Removing User: '#{$(@).data('username')}'"
$(@).remove();
$('#toggleUsers .badge').text $('.timsChatUser').length
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Insert the given `text` into the input. If `options.append` is true the given `text` will be appended, otherwise it will replaced
the existing text. If `options.submit` is true the message will be sent to the server afterwards.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
insertText = (text, options = { }) ->
options = $.extend
append: true
submit: false
, options
text = $('#timsChatInput').val() + text if options.append
$('#timsChatInput').val text
$('#timsChatInput').keyup()
if (options.submit)
$('#timsChatForm').submit()
else
$('#timsChatInput').focus()
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Send out notifications for the given `message`. The number of unread messages will be prepended to `document.title` and if available desktop notifications will be sent.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
notify = (message) ->
2013-05-30 17:03:37 +00:00
if scrollUpNotifications
2013-07-09 17:41:14 +00:00
$('.timsChatMessageContainer.active').addClass 'notification'
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
return if isActive or $('#timsChatNotify').data('status') is 0
document.title = v.titleTemplate.fetch
title: $('#timsChatRoomList .active a').text()
newMessageCount: ++newMessageCount
title = WCF.Language.get 'chat.general.notify.title'
content = "#{message.username}#{message.separator} #{message.message}"
if window.Notification?.permission is 'granted'
do ->
notification = new window.Notification title,
body: content
onclick: ->
notification.close()
setTimeout ->
notification.close()
, 5e3
Fetch the roomlist from the server and update it in the GUI.
refreshRoomList = ->
console.log 'Refreshing the roomlist'
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'getRoomList'
className: 'chat\\data\\room\\RoomAction'
showLoadingOverlay: false
suppressErrors: true
success: (data) ->
2013-05-26 15:19:04 +00:00
$('.timsChatRoom').remove()
2013-05-15 19:55:51 +00:00
$('#toggleRooms .badge').text data.returnValues.length
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
for room in data.returnValues
li = $ '<li></li>'
li.addClass 'active' if room.active
2013-05-26 15:19:04 +00:00
$("""<a href="#{room.link}">#{room.title}</a>""").addClass('timsChatRoom').data('roomID', room.roomID).appendTo li
2013-05-15 19:55:51 +00:00
$('#timsChatRoomList ul').append li
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
if window.history?.replaceState?
$('.timsChatRoom').click (event) ->
event.preventDefault()
target = $(@)
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
window.history.replaceState {}, '', target.attr 'href'
2013-05-26 15:19:04 +00:00
join target.data 'roomID'
$('#timsChatRoomList .active').removeClass 'active'
target.parent().addClass 'active'
2013-05-15 19:55:51 +00:00
console.log "Found #{data.returnValues.length} rooms"
2013-04-13 14:38:50 +00:00
2013-05-24 13:55:52 +00:00
Shows an unrecoverable error with the given text.
showError = (text) ->
return if errorVisible
errorVisible = true
loading = true
pe.refreshRoomList.stop()
pe.getMessages.stop()
errorDialog = $("""
<div id="timsChatLoadingErrorDialog">
<p>#{text}</p>
</div>
""").appendTo 'body'
formSubmit = $("""<div class="formSubmit"></div>""").appendTo errorDialog
2013-05-24 17:26:29 +00:00
reloadButton = $("""<button class="buttonPrimary">#{WCF.Language.get 'chat.error.reload'}</button>""").appendTo formSubmit
2013-05-24 13:55:52 +00:00
reloadButton.on 'click', ->
window.location.reload()
$('#timsChatLoadingErrorDialog').wcfDialog
closable: false
2013-05-26 15:19:04 +00:00
title: WCF.Language.get 'wcf.global.error.title'
Joins a room.
2013-05-24 13:55:52 +00:00
2013-05-26 15:19:04 +00:00
join = (roomID) ->
loading = true
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'join'
className: 'chat\\data\\room\\RoomAction'
parameters:
roomID: roomID
success: (data) ->
loading = false
2013-07-05 14:15:07 +00:00
$('#timsChatTopic').removeClass 'hidden'
$('#timsChatTopic > .topic').text data.returnValues.topic
2013-05-26 16:59:38 +00:00
if data.returnValues.topic.trim() is ''
2013-05-26 15:19:04 +00:00
$('#timsChatTopic').addClass 'empty'
else
$('#timsChatTopic').removeClass 'empty'
$('.timsChatMessage').addClass 'unloaded'
document.title = v.titleTemplate.fetch data.returnValues
handleMessages data.returnValues.messages
getMessages()
refreshRoomList()
failure: ->
showError WCF.Language.get 'chat.error.join'
2013-05-30 17:03:37 +00:00
2013-07-09 17:41:14 +00:00
Open private channel
openPrivateChannel = (userID) ->
$('.timsChatMessageContainer').removeClass 'active'
if !$.wcfIsset "timsChatMessageContainer#{userID}"
div = $('<div>')
div.attr 'id', "timsChatMessageContainer#{userID}"
div.addClass 'timsChatMessageContainer'
div.addClass 'marginTop'
div.addClass 'container'
div.wrapInner '<ul>'
$('#timsChatMessageContainer0').after div
$("#timsChatMessageContainer#{userID}").addClass('active')
Close private channel
closePrivateChannel = (userID) ->
$("#timsChatMessageContainer#{userID}").remove() unless userID isnt 0
$("#timsChatMessageContainer0").addClass('active')
2013-05-15 19:55:51 +00:00
Bind the given callback to the given event.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
addListener = (event, callback) ->
return false unless events[event]?
2013-05-24 13:55:52 +00:00
2013-05-15 19:55:51 +00:00
events[event].add callback
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
Remove the given callback from the given event.
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
removeListener = (event, callback) ->
return false unless events[event]?
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
events[event].remove callback
2013-04-13 14:38:50 +00:00
2013-05-15 19:55:51 +00:00
And finally export the public methods and variables.
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
Chat =
init: init
getMessages: getMessages
refreshRoomList: refreshRoomList
insertText: insertText
freeTheFish: freeTheFish
2013-05-26 15:19:04 +00:00
join: join
2013-07-09 17:41:14 +00:00
closePrivateChannel: closePrivateChannel # TODO: REMOVE AFTER DEBUGGING
openPrivateChannel: openPrivateChannel # TODO: REMOVE AFTER DEBUGGING
2013-05-15 19:55:51 +00:00
listener:
add: addListener
remove: removeListener
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
window.be ?= {}
be.bastelstu ?= {}
window.be.bastelstu.Chat = Chat
2013-04-13 14:38:50 +00:00
)(jQuery, @)