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
2014-02-08 02:21:25 +00:00
# @copyright 2010-2014 Tim Düsterhus
2013-05-14 18:29:44 +00:00
# @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) ->
2014-03-12 22:31:21 +00:00
window.console.log "[be.bastelstu.Chat] #{message}" unless production?
2013-04-13 14:38:50 +00:00
warn: (message) ->
2014-03-12 22:31:21 +00:00
window.console.warn "[be.bastelstu.Chat] #{message}" unless production?
2013-04-13 14:38:50 +00:00
error: (message) ->
2014-03-12 22:31:21 +00:00
window.console.error "[be.bastelstu.Chat] #{message}" unless production?
2014-02-08 02:21:25 +00:00
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()
2014-03-02 16:53:02 +00:00
2013-07-12 14:49:40 +00:00
userList =
current: {}
allTime: {}
2014-03-02 16:53:02 +00:00
roomList =
active: {}
available: {}
2014-03-13 19:56:46 +00:00
hiddenTopics = {}
2014-03-17 21:04:46 +00:00
hidePrivateChannelTopic = no
2014-03-13 20:00:57 +00:00
isJoining = no
2013-10-06 00:59:13 +00:00
fileUploaded = no
2013-05-24 13:55:52 +00:00
errorVisible = false
2013-07-09 19:42:01 +00:00
inputErrorHidingTimer = null
2013-06-23 17:46:50 +00:00
lastMessage = null
2013-07-09 19:39:31 +00:00
openChannel = 0
2014-02-23 00:17:15 +00:00
messageContainerSize = 0
2014-02-27 14:40:34 +00:00
userListSize = 0
2013-05-15 19:55:51 +00:00
remainingFailures = 3
2014-02-27 21:16:08 +00:00
overlaySmileyList = null
2014-03-01 23:33:45 +00:00
markedMessages = {}
2014-02-27 21:16:08 +00:00
2013-05-15 19:55:51 +00:00
events =
newMessage: $.Callbacks()
userMenu: $.Callbacks()
submit: $.Callbacks()
2014-03-02 16:53:02 +00:00
2013-05-15 19:55:51 +00:00
pe =
getMessages: null
refreshRoomList: null
fish: null
2014-03-02 16:53:02 +00:00
2013-05-15 19:55:51 +00:00
loading = false
2014-03-02 16:53:02 +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
2014-03-02 16:53:02 +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
2014-02-08 02:21:25 +00:00
init = (roomID, config, titleTemplate, messageTemplate, userTemplate, userMenuTemplate) ->
2013-05-15 19:55:51 +00:00
return false if initialized
initialized = true
2013-09-15 20:53:53 +00:00
2014-02-27 14:40:34 +00:00
userListSize = $('#timsChatUserList').height()
2014-02-23 00:17:15 +00:00
2013-05-15 19:55:51 +00:00
v.config = config
v.titleTemplate = titleTemplate
v.messageTemplate = messageTemplate
v.userTemplate = userTemplate
2014-02-08 02:21:25 +00:00
v.userMenuTemplate = userMenuTemplate
2013-05-15 19:55:51 +00:00
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 ->
2014-03-02 16:53:02 +00:00
document.title = v.titleTemplate.fetch(roomList.active) if roomList.active?.title? and roomList.active.topic.trim() isnt ''
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-09-15 20:53:53 +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
2014-02-27 21:33:51 +00:00
2014-02-23 00:17:15 +00:00
$(window).resize ->
2014-02-27 21:33:51 +00:00
if $('html').hasClass 'fullscreen'
do ->
verticalContentPadding = $('#content').innerHeight() - $('#content').height()
verticalSizeOfContentElements = do ->
height = 0
$('#content > *:visible').each (k, v) -> height += $(v).outerHeight()
height
2014-03-02 19:02:50 +00:00
return if verticalSizeOfContentElements is 0
2014-02-27 21:33:51 +00:00
freeSpace = $('body').height() - verticalContentPadding - verticalSizeOfContentElements
$('.timsChatMessageContainer').height $('.timsChatMessageContainer').height() + freeSpace
2014-03-02 19:02:50 +00:00
2014-02-27 21:33:51 +00:00
do ->
2014-03-02 19:02:50 +00:00
verticalSidebarPadding = $('.sidebar').innerHeight() - $('.sidebar').height()
2014-02-27 21:33:51 +00:00
verticalUserListContainerPadding = $('#timsChatUserListContainer').innerHeight() - $('#timsChatUserListContainer').height()
sidebarHeight = $('.sidebar > div').height()
2014-03-02 19:02:50 +00:00
freeSpace = $('body').height() - verticalSidebarPadding - verticalUserListContainerPadding - sidebarHeight
2014-02-27 21:33:51 +00:00
$('#timsChatUserList').height $('#timsChatUserList').height() + freeSpace
if $('#timsChatAutoscroll').data 'status'
$('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop 'scrollHeight'
2014-03-02 19:02:50 +00:00
$('.mobileSidebarToggleButton').on 'click', ->
do $(window).resize
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-09-15 20:53:53 +00:00
$('#smilies').on 'click', 'img', -> insertText " #{$(@).attr('alt')} "
2013-05-30 17:03:37 +00:00
2014-02-27 21:16:08 +00:00
Copy the first loaded category of smilies so it won't get detached by wcfDialog
overlaySmileyList = $('<ul class="smileyList">').append $('#smilies .smileyList').clone().children()
Add click event to smilies in the overlay
overlaySmileyList.on 'click', 'img', ->
insertText " #{$(@).attr('alt')} "
overlaySmileyList.wcfDialog 'close'
Open the smiley wcfDialog
$('#timsChatSmileyPopupButton').on 'click', ->
overlaySmileyList.wcfDialog
2014-03-01 23:44:34 +00:00
title: WCF.Language.get 'chat.global.smilies'
2014-02-27 21:16:08 +00:00
overlaySmileyList.css
'max-height': $(window).height() - overlaySmileyList.parent().siblings('.dialogTitlebar').outerHeight()
'overflow': 'auto'
2013-07-12 14:49:40 +00:00
Handle private channel menu
2014-02-08 02:21:25 +00:00
$('#timsChatMessageTabMenu > .tabMenu').on 'click', '.timsChatMessageTabMenuAnchor', ->
openPrivateChannel $(@).data 'userID'
2013-07-12 14:49:40 +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) ->
2013-07-09 19:48:40 +00:00
do event.preventDefault
2013-05-30 17:03:37 +00:00
2013-07-09 19:48:40 +00:00
text = do $('#timsChatInput').val().trim
2013-11-19 19:24:06 +00:00
$('#timsChatInput').val('').focus().change()
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-09-15 20:53:53 +00:00
text = "/whisper #{userList.allTime[openChannel].username}, #{text}" unless openChannel is 0
2013-07-09 19:39:31 +00:00
2013-05-15 19:55:51 +00:00
# Free the fish!
2013-07-09 19:48:40 +00:00
do 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: ->
2013-07-09 19:48:40 +00:00
do hideInputError
2013-07-09 19:42:01 +00:00
2013-07-09 19:48:40 +00:00
do getMessages
2013-05-15 19:55:51 +00:00
failure: (data) ->
return true unless (data?.returnValues?.errorType?) or (data?.message?)
2013-07-09 19:42:01 +00:00
showInputError (data?.returnValues?.errorType) ? data.message
2013-05-15 19:55:51 +00:00
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
2013-07-09 19:48:40 +00:00
do event.preventDefault
input = $ @
2013-05-30 17:03:37 +00:00
2013-07-09 19:48:40 +00:00
autocomplete.value ?= do input.val
autocomplete.caret ?= do input.getCaret
2013-05-15 19:55:51 +00:00
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-07-09 19:48:40 +00:00
if beforeComplete is '' and (toComplete.substring 0, 1) is '/'
2013-06-01 12:06:55 +00:00
regex = new RegExp "^#{WCF.String.escapeRegExp toComplete.substring 1}", "i"
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"
2013-07-12 14:49:40 +00:00
users = [ ]
for userID, user of userList.current
users.push user.username if regex.test user.username
2013-06-01 12:06:55 +00:00
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
2013-07-09 19:48:40 +00:00
do $('#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
2014-02-08 02:21:25 +00:00
Bind user menu functions
$('#dropdownMenuContainer').on 'click', '.jsTimsChatUserMenuQuery', -> openPrivateChannel $(@).parents('ul').data 'userID'
2014-03-10 17:25:03 +00:00
$('#dropdownMenuContainer').on 'click', '.jsTimsChatUserMenuCommand', ->
command = "/#{$(@).data 'command'} #{userList.current[$(@).parents('ul').data 'userID'].username}, "
2014-02-08 02:21:25 +00:00
return if $('#timsChatInput').val().match(new RegExp WCF.String.escapeRegExp("^#{command}"), 'i')
insertText command, prepend: yes
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
2014-02-08 02:21:25 +00:00
$('#timsChatRoomListReloadButton').click -> do 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) ->
2013-07-09 19:48:40 +00:00
do event.preventDefault
2014-06-19 18:51:48 +00:00
clearChannel openChannel
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'
2013-07-09 19:48:40 +00:00
do $('#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'
2014-02-08 02:21:25 +00:00
$('#smilies').removeClass 'invisible'
2013-05-15 19:55:51 +00:00
else
2014-02-08 02:21:25 +00:00
$('#smilies').addClass 'invisible'
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-09-15 20:53:53 +00:00
if $(@).data 'status'
2014-02-27 22:32:16 +00:00
messageContainerSize = $('.timsChatMessageContainer').height()
2013-05-15 19:55:51 +00:00
$('html').addClass 'fullscreen'
2014-02-23 00:17:15 +00:00
do $(window).resize
2013-05-15 19:55:51 +00:00
else
2014-02-23 00:17:15 +00:00
$('.timsChatMessageContainer').height messageContainerSize
2014-02-27 14:40:34 +00:00
$('#timsChatUserList').height userListSize
2013-05-15 19:55:51 +00:00
$('html').removeClass 'fullscreen'
2014-02-27 21:33:51 +00:00
do $(window).resize
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.
2014-02-23 00:17:15 +00:00
$('#timsChatTopicCloser').on 'click', ->
2014-03-17 21:04:46 +00:00
if openChannel is 0
2014-03-13 19:56:46 +00:00
hiddenTopics[roomList.active.roomID] = true
2014-03-17 21:04:46 +00:00
else
hidePrivateChannelTopic = yes
2014-03-13 19:56:46 +00:00
2014-03-17 21:04:46 +00:00
$('#timsChatTopic').addClass 'invisible'
do $(window).resize
2014-02-08 02:21:25 +00:00
Close private channels
$('#timsChatMessageTabMenu').on 'click', '.jsChannelCloser', -> closePrivateChannel $(@).parent().data 'userID'
2013-05-15 19:55:51 +00:00
Visibly mark the message once the associated checkbox is checked.
2014-03-01 23:33:45 +00:00
$(document).on 'click', '.timsChatMessage .timsChatMessageMarker', (event) ->
elem = $(event.target)
parent = elem.parent()
messageID = elem.attr('value')
if elem.is ':checked'
markedMessages[messageID] = messageID
checked = true
parent.addClass 'checked'
parent.siblings().each (key, value) ->
checked = $(value).find('.timsChatMessageMarker').is ':checked'
checked
if checked
elem.parents('.timsChatMessage').addClass 'checked'
elem.parents('.timsChatTextContainer').siblings('.timsChatMessageBlockMarker').prop 'checked', true
2013-05-15 19:55:51 +00:00
else
2014-03-01 23:33:45 +00:00
delete markedMessages[messageID]
parent.removeClass 'checked'
elem.parents('.timsChatMessage').removeClass 'checked'
elem.parents('.timsChatTextContainer').siblings('.timsChatMessageBlockMarker').prop 'checked', false
$(document).on 'click', '.timsChatMessageBlockMarker', (event) ->
$(event.target).siblings('.timsChatTextContainer').children('li').each (key, value) ->
elem = $(value).find '.timsChatMessageMarker'
if $(event.target).is ':checked'
do elem.click unless elem.is ':checked'
else
do elem.click if elem.is ':checked'
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) ->
2014-02-08 02:21:25 +00:00
if $(@).data 'status'
2013-07-09 19:50:19 +00:00
$('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop 'scrollHeight'
2014-02-08 02:21:25 +00:00
scrollUpNotifications = off
$("#timsChatMessageTabMenu > .tabMenu > ul > li.ui-state-active").removeClass 'notify'
$(".timsChatMessageContainer.active").removeClass 'notify'
else
scrollUpNotifications = on
Bind scroll event on predefined message containers
2013-05-30 17:03:37 +00:00
2013-07-09 17:41:14 +00:00
$('.timsChatMessageContainer.active').on 'scroll', (event) ->
2014-02-08 02:21:25 +00:00
do event.stopPropagation
handleScroll event
2013-04-13 14:38:50 +00:00
2013-05-24 13:30:18 +00:00
Enable duplicate tab detection.
2013-09-15 20:44:50 +00:00
try
window.localStorage.setItem 'be.bastelstu.chat.session', chatSession
$(window).on 'storage', (event) ->
if event.originalEvent.key is 'be.bastelstu.chat.session'
2013-09-15 20:46:14 +00:00
showError WCF.Language.get 'chat.error.duplicateTab' unless parseInt(event.originalEvent.newValue) is chatSession
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'
2013-09-15 20:53:53 +00:00
unless window.Notification.permission is 'granted'
2013-05-15 19:55:51 +00:00
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'
2013-07-09 19:48:40 +00:00
do 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'
2013-07-09 19:48:40 +00:00
do getMessages
2014-03-02 15:04:45 +00:00
do pe.getMessages.resume
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
2014-02-27 18:14:32 +00:00
be.bastelstu.wcf.nodePush.onMessage 'be.bastelstu.chat.roomChange', refreshRoomList
2014-02-27 19:15:30 +00:00
be.bastelstu.wcf.nodePush.onMessage 'be.bastelstu.chat.join', refreshRoomList
be.bastelstu.wcf.nodePush.onMessage 'be.bastelstu.chat.leave', refreshRoomList
2013-04-13 14:38:50 +00:00
2014-03-02 19:02:50 +00:00
Switch to fullscreen mode on mobile devices
do $('#timsChatFullscreen').click if WCF.System.Mobile.UX._enabled
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-07-09 19:48:40 +00:00
do $('#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-07-09 19:42:01 +00:00
Shows an error message below the input.
showInputError = (message) ->
$('#timsChatInputContainer').addClass('formError').find('.innerError').show().html message
clearTimeout inputErrorHidingTimer if inputErrorHidingTimer?
inputErrorHidingTimer = setTimeout ->
2013-07-09 19:48:40 +00:00
do hideInputError
2013-07-09 19:42:01 +00:00
, 5e3
Hides the error message below the input.
hideInputError = ->
clearTimeout inputErrorHidingTimer if inputErrorHidingTimer?
inputErrorHidingTimer = null
2013-07-09 19:48:40 +00:00
do $('#timsChatInputContainer').removeClass('formError').find('.innerError').hide
2013-07-09 19:42:01 +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'
2014-02-08 02:21:25 +00:00
fish = $ """<div id="fish"><span></span></div>"""
fish.direction = 'right'
2013-07-09 19:48:40 +00:00
2013-05-15 19:55:51 +00:00
fish.css
2013-08-02 19:34:28 +00:00
position: 'fixed'
top: '50%'
left: '50%'
2014-02-08 02:21:25 +00:00
zIndex: 0x7FFFFFFF
textShadow: '1px 1px rgb(0, 0, 0)'
2013-04-13 14:38:50 +00:00
2014-02-08 02:21:25 +00:00
fish.appendTo $ 'body'
fish.colors = ['78C5D6', '459ba8', '79C267', 'C5D647', 'F5D63D', 'F28C33', 'E868A2', 'BF62A6']
fish.colorIndex = 0
fish.texts =
right: '><((((\u00B0>'
left: '<\u00B0))))><'
fish.fishes = {}
Pre build fishes, this allows for faster animation
$.each fish.texts, (key, value) ->
fish.fishes[key] = []
index = 0
2013-05-15 19:55:51 +00:00
2014-02-08 02:21:25 +00:00
while index < value.length
html = $ '<span/>'
i = 0
$(value.split '').each (key, value) ->
$("<span>#{value}</span>").css
color: '#' + fish.colors[(i++ + index) % fish.colors.length]
textShadow: '1px 1px rgb(0, 0, 0)'
.appendTo html
fish.fishes[key][index++] = html
return
fish.find('> span').replaceWith fish.fishes[fish.direction][0]
fish.updateRainbowText = (key, value) ->
key = key || fish.direction
return unless fish.fishes[key]? || not fish.texts[key]?
value = value || fish.colorIndex++ % fish.texts[key].length
fish.find('> span').replaceWith fish.fishes[key][value]
fish.pePos = new WCF.PeriodicalExecuter ->
loops = 0
loop
++loops
left = Math.random() * 300 - 150
top = Math.random() * 300 - 150
if (fish.position().top + top) > 0 and (fish.position().left + left + fish.width()) < $(window).width() and (fish.position().top + top + fish.height()) < $(window).height() and (fish.position().left + left) > 0
break
else if loops is 10
console.log 'Magicarp used Splash for the 10th time in a row - it fainted!'
fish.css
'top': '50%'
'left': '50%'
break
if left > 0 and fish.text() isnt '><((((\u00B0>'
fish.direction = 'right'
fish.updateRainbowText null, fish.colorIndex % fish.texts.right.length
else if left < 0 and fish.text() isnt '<\u00B0))))><'
fish.direction = 'left'
fish.updateRainbowText null, fish.colorIndex % fish.texts.left.length
2013-05-15 19:55:51 +00:00
fish.animate
2013-08-02 19:34:28 +00:00
top: (fish.position().top + top)
left: (fish.position().left + left)
2013-05-15 19:55:51 +00:00
, 1e3
2014-02-08 02:21:25 +00:00
, 1.2e3
fish.peColor = new WCF.PeriodicalExecuter ->
do fish.updateRainbowText
, .125e3
2013-05-15 19:55:51 +00:00
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
2013-07-09 19:48:40 +00:00
do 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) ->
for message in messages
2014-02-09 23:57:01 +00:00
message.isInPrivateChannel = (message.type is v.config.messageTypes.WHISPER) and ($.wcfIsset("timsChatMessageContainer#{message.receiver}") or $.wcfIsset("timsChatMessageContainer#{message.sender}"))
2013-07-24 10:55:47 +00:00
2013-05-15 19:55:51 +00:00
events.newMessage.fire message
2013-05-30 17:03:37 +00:00
2013-06-23 17:46:50 +00:00
createNewMessage = yes
2014-03-01 23:33:45 +00:00
if $('.timsChatMessage:last-child .timsChatTextContainer').is('ul') and lastMessage isnt null and lastMessage.type in [ v.config.messageTypes.NORMAL, v.config.messageTypes.WHISPER ]
2013-07-09 18:45:35 +00:00
if lastMessage.type is message.type and lastMessage.sender is message.sender and lastMessage.receiver is message.receiver and lastMessage.isInPrivateChannel is message.isInPrivateChannel
2013-06-23 17:46:50 +00:00
createNewMessage = no
2014-06-19 18:51:48 +00:00
if message.type is v.config.messageTypes.CLEAR
createNewMessage = yes
clearChannel 0
2013-06-23 17:46:50 +00:00
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
2013-07-09 18:45:35 +00:00
if message.isInPrivateChannel and message.sender is WCF.User.userID
2013-07-09 18:30:46 +00:00
li.appendTo $ "#timsChatMessageContainer#{message.receiver} > ul"
2013-07-09 18:45:35 +00:00
else if message.isInPrivateChannel
2013-07-09 18:30:46 +00:00
li.appendTo $ "#timsChatMessageContainer#{message.sender} > ul"
2013-07-09 17:41:14 +00:00
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-07-09 18:45:35 +00:00
if message.isInPrivateChannel and message.sender is WCF.User.userID
2013-07-09 18:30:46 +00:00
messageContainerID = message.receiver
2013-07-09 18:45:35 +00:00
else if message.isInPrivateChannel
2013-07-09 18:30:46 +00:00
messageContainerID = message.sender
else
messageContainerID = 0
2014-03-01 23:33:45 +00:00
$("#timsChatMessageContainer#{messageContainerID} .timsChatMessage:last-child .timsChatTextContainer").append $(output).find('.timsChatTextContainer li:last-child')
2013-06-23 17:46:50 +00:00
lastMessage = message
2014-02-08 02:21:25 +00:00
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
2014-02-08 02:21:25 +00:00
Handles scroll event of message containers
handleScroll = (event) ->
element = $ event.target
if element.hasClass 'active'
scrollTop = element.scrollTop()
scrollHeight = element.prop 'scrollHeight'
height = element.innerHeight()
if scrollTop < scrollHeight - height - 25
if $('#timsChatAutoscroll').data('status') is 1
scrollUpNotifications = on
do $('#timsChatAutoscroll').click
if scrollTop > scrollHeight - height - 10
if $('#timsChatAutoscroll').data('status') is 0
scrollUpNotifications = off
$("#timsChatMessageTabMenu > .tabMenu > ul > li.ui-state-active").removeClass 'notify'
$(".timsChatMessageContainer.active").removeClass 'notify'
do $('#timsChatAutoscroll').click
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-07-12 14:49:40 +00:00
userList.current = { }
2013-05-15 19:55:51 +00:00
for user in users
2013-07-09 20:02:47 +00:00
do (user) ->
2013-07-12 14:49:40 +00:00
userList.current[user.userID] = userList.allTime[user.userID] = user
2013-07-09 20:02: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-07-09 20:02:47 +00:00
if $.wcfIsset id
console.log "Moving User: '#{user.username}'"
element = $("##{id}").detach()
if user.awayStatus?
element.addClass 'away'
element.attr 'title', user.awayStatus
else
element.removeClass 'away'
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-07-09 20:02:47 +00:00
else
console.log "Inserting User: '#{user.username}'"
li = $ '<li></li>'
li.attr 'id', id
li.addClass 'timsChatUser'
li.addClass 'jsTooltip'
li.addClass 'you' if user.userID is WCF.User.userID
li.addClass 'suspended' if user.suspended
if user.awayStatus?
li.addClass 'away'
li.attr 'title', user.awayStatus
li.data 'username', user.username
li.append v.userTemplate.fetch user
2014-03-02 16:53:02 +00:00
menu = $ v.userMenuTemplate.fetch
user: user
room: roomList.active
2013-07-09 20:02:47 +00:00
if menu.find('li').length
li.append menu
menu.addClass 'dropdownMenu'
2013-11-19 20:29:12 +00:00
li.addClass 'dropdown'
2013-07-09 20:02:47 +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')}'"
2013-08-06 18:48:28 +00:00
WCF.Dropdown.removeDropdown $(@).attr 'id'
2013-07-09 19:48:40 +00:00
do $(@).remove
2013-05-15 19:55:51 +00:00
$('#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 = { }) ->
2014-02-08 02:21:25 +00:00
options.append = false if options.prepend? and options.prepend and not options.append?
2013-05-15 19:55:51 +00:00
options = $.extend
2014-02-08 02:21:25 +00:00
prepend: false
2013-05-15 19:55:51 +00:00
append: true
submit: false
, options
2014-02-08 02:21:25 +00:00
text = text + $('#timsChatInput').val() if options.prepend
2013-05-15 19:55:51 +00:00
text = $('#timsChatInput').val() + text if options.append
2014-02-08 02:21:25 +00:00
2014-04-29 20:04:52 +00:00
# do not insert text if it would exceed the allowed length
maxLength = $('#timsChatInput').attr 'maxlength'
return if maxLength? and text.length > maxLength
2013-05-15 19:55:51 +00:00
$('#timsChatInput').val text
2014-04-28 11:59:08 +00:00
$('#timsChatInput').trigger 'change'
2014-04-29 20:04:52 +00:00
2013-07-09 19:48:40 +00:00
if options.submit
do $('#timsChatForm').submit
2013-05-15 19:55:51 +00:00
else
2013-07-09 19:48:40 +00:00
do $('#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) ->
2014-02-08 02:21:25 +00:00
return if message.sender is WCF.User.userID
2013-05-30 17:03:37 +00:00
2014-02-08 02:21:25 +00:00
if scrollUpNotifications
$("#timsChatMessageTabMenu > .tabMenu > ul > li.ui-state-active").addClass 'notify'
$(".timsChatMessageContainer.active").addClass 'notify'
2013-07-24 10:55:47 +00:00
if message.isInPrivateChannel
2014-02-08 02:21:25 +00:00
id = if message.sender is WCF.User.userID then message.receiver else message.sender
2013-07-24 10:55:47 +00:00
2014-02-08 02:21:25 +00:00
if $('.timsChatMessageContainer.active').data('userID') isnt id
$("#timsChatMessageTabMenuAnchor#{id}").parent().addClass 'notify'
$("#timsChatMessageContainer#{id}").addClass 'notify'
2013-07-24 10:55:47 +00:00
else if $('.timsChatMessageContainer.active').data('userID') isnt 0
2014-02-08 02:21:25 +00:00
$("#timsChatMessageTabMenuAnchor0").parent().addClass 'notify'
$("#timsChatMessageContainer0").addClass 'notify'
2013-07-24 10:55:47 +00:00
2013-05-15 19:55:51 +00:00
return if isActive or $('#timsChatNotify').data('status') is 0
2014-03-02 16:53:02 +00:00
document.title = v.titleTemplate.fetch $.extend {}, roomList.active,
2013-08-21 18:36:22 +00:00
newMessageCount: ++newMessageCount
2013-05-15 19:55:51 +00:00
2014-02-27 17:55:53 +00:00
title = WCF.Language.get 'chat.global.notify.title'
2013-05-15 19:55:51 +00:00
content = "#{message.username}#{message.separator} #{message.message}"
if window.Notification?.permission is 'granted'
do ->
notification = new window.Notification title,
body: content
onclick: ->
2013-07-09 19:48:40 +00:00
do notification.close
2013-05-15 19:55:51 +00:00
setTimeout ->
2013-07-09 19:48:40 +00:00
do notification.close
2013-05-15 19:55:51 +00:00
, 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) ->
2014-03-02 16:53:02 +00:00
roomList =
active: {}
available: {}
2013-07-09 19:48:40 +00:00
do $('.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
2014-03-02 16:53:02 +00:00
roomList.available[room.roomID] = room
roomList.active = room if room.active
2013-05-15 19:55:51 +00:00
li = $ '<li></li>'
2014-02-08 02:21:25 +00:00
li.addClass('timsChatRoom').data('roomID', room.roomID)
2013-05-15 19:55:51 +00:00
li.addClass 'active' if room.active
2014-02-08 02:21:25 +00:00
$("""<a href="#{room.link}">#{WCF.String.escapeHTML(room.title)}</a> <span class="badge">#{WCF.String.formatNumeric room.userCount}</span>""").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) ->
2013-07-09 19:48:40 +00:00
do event.preventDefault
2014-03-13 20:00:57 +00:00
2013-07-09 19:48:40 +00:00
target = $ @
2014-03-13 20:00:57 +00:00
return if target.data('roomID') is roomList.active.roomID
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
2013-07-09 19:48:40 +00:00
do pe.refreshRoomList.stop
do pe.getMessages.stop
2013-05-24 13:55:52 +00:00
errorDialog = $("""
<div id="timsChatLoadingErrorDialog">
<p>#{text}</p>
</div>
""").appendTo 'body'
formSubmit = $("""<div class="formSubmit"></div>""").appendTo errorDialog
2013-09-15 20:53:53 +00:00
2013-05-24 17:26:29 +00:00
reloadButton = $("""<button class="buttonPrimary">#{WCF.Language.get 'chat.error.reload'}</button>""").appendTo formSubmit
2013-09-15 20:53:53 +00:00
reloadButton.on 'click', -> do window.location.reload
2013-05-24 13:55:52 +00:00
$('#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) ->
2014-03-13 20:00:57 +00:00
return if isJoining or roomID is roomList.active.roomID
isJoining = yes
do $('#timsChatInput').disable
2013-05-26 15:19:04 +00:00
loading = true
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'join'
className: 'chat\\data\\room\\RoomAction'
parameters:
roomID: roomID
success: (data) ->
loading = false
2014-03-02 16:53:02 +00:00
roomList.active = data.returnValues
2014-03-17 21:04:46 +00:00
if openChannel is 0
$('#timsChatTopic > .topic').text roomList.active.topic
if roomList.active.topic.trim() is '' or hiddenTopics[roomList.active.roomID]?
$('#timsChatTopic').addClass 'invisible'
else
$('#timsChatTopic').removeClass 'invisible'
2013-05-26 15:19:04 +00:00
$('.timsChatMessage').addClass 'unloaded'
2014-03-02 16:53:02 +00:00
document.title = v.titleTemplate.fetch roomList.active
handleMessages roomList.active.messages
2013-07-09 19:48:40 +00:00
do getMessages
do refreshRoomList
2014-03-13 20:00:57 +00:00
do $('#timsChatInput').enable().focus
2013-11-19 19:33:27 +00:00
failure: (data) ->
showError WCF.Language.get 'chat.error.join', data
2014-03-13 20:00:57 +00:00
after: ->
isJoining = no
2013-05-30 17:03:37 +00:00
2013-07-09 17:41:14 +00:00
Open private channel
2013-07-12 18:52:54 +00:00
2013-07-09 17:41:14 +00:00
openPrivateChannel = (userID) ->
2013-07-12 14:49:40 +00:00
userID = parseInt userID
console.log "Opening private channel #{userID}"
2013-07-12 18:52:54 +00:00
2013-07-09 19:50:19 +00:00
unless $.wcfIsset "timsChatMessageContainer#{userID}"
2013-07-12 14:49:40 +00:00
return unless userList.allTime[userID]?
2013-07-09 19:39:31 +00:00
2013-07-12 14:49:40 +00:00
div = $ '<div>'
2013-07-09 17:41:14 +00:00
div.attr 'id', "timsChatMessageContainer#{userID}"
2013-07-24 10:55:47 +00:00
div.data 'userID', userID
2014-02-08 02:21:25 +00:00
div.addClass 'tabMenuContent'
2013-07-09 17:41:14 +00:00
div.addClass 'timsChatMessageContainer'
div.addClass 'container'
2014-02-08 02:21:25 +00:00
div.addClass 'containerPadding'
div.wrapInner "<ul></ul>"
div.on 'scroll', (event) ->
do event.stopPropagation
handleScroll event
2013-07-09 17:41:14 +00:00
$('#timsChatMessageContainer0').after div
2014-02-27 15:14:11 +00:00
$('.timsChatMessageContainer').height $('.timsChatMessageContainer').height()
2013-09-15 20:53:53 +00:00
2013-07-12 14:49:40 +00:00
if userID isnt 0
2014-03-17 21:04:46 +00:00
if hidePrivateChannelTopic
$('#timsChatTopic').addClass 'invisible'
else
$('#timsChatTopic').removeClass 'invisible'
2014-02-27 17:55:53 +00:00
$('#timsChatTopic > .topic').html WCF.Language.get 'chat.global.privateChannelTopic', {username: userList.allTime[userID].username}
2014-02-08 02:21:25 +00:00
$('#timsChatMessageTabMenu').removeClass 'singleTab'
2013-07-24 10:55:47 +00:00
2014-02-08 02:21:25 +00:00
unless $.wcfIsset "timsChatMessageTabMenuAnchor#{userID}"
2013-07-12 14:49:40 +00:00
li = $ '<li>'
2013-07-12 18:52:54 +00:00
2014-02-08 02:21:25 +00:00
anchor = $ """<a id="timsChatMessageTabMenuAnchor#{userID}" class="timsChatMessageTabMenuAnchor" href="#{window.location.toString().replace /#.+$/, ''}#timsChatMessageContainer#{userID}" />"""
anchor.data 'userID', userID
2013-07-12 14:49:40 +00:00
2013-07-12 19:29:17 +00:00
avatar = $ userList.allTime[userID].avatar[16]
2014-02-08 02:21:25 +00:00
avatar = $('<span class="userAvatar framed" />').wrapInner avatar
avatar.append "<span>#{userList.allTime[userID].username}</span>"
2013-07-12 19:29:17 +00:00
2014-02-08 02:21:25 +00:00
anchor.wrapInner avatar
anchor.prepend '<span class="icon icon16 icon-warning-sign notifyIcon"></span>'
anchor.append """<span class="jsChannelCloser icon icon16 icon-remove jsTooltip" title="#{WCF.Language.get('chat.global.closePrivateChannel')}" />"""
2013-07-12 18:52:54 +00:00
2014-02-08 02:21:25 +00:00
li.append anchor
2013-07-12 18:52:54 +00:00
2014-02-08 02:21:25 +00:00
$('#timsChatMessageTabMenu > .tabMenu > ul').append li
$('#timsChatMessageTabMenu').wcfTabs 'refresh'
WCF.System.FlexibleMenu.rebuild $('#timsChatMessageTabMenu > .tabMenu').attr 'id'
2013-07-24 10:55:47 +00:00
else
2014-03-02 16:53:02 +00:00
$('#timsChatTopic > .topic').text roomList.active.topic
2014-03-17 21:04:46 +00:00
if roomList.active.topic.trim() is '' or hiddenTopics[roomList.active.roomID]?
2014-02-27 19:43:54 +00:00
$('#timsChatTopic').addClass 'invisible'
2013-07-24 10:55:47 +00:00
else
2014-02-27 19:43:54 +00:00
$('#timsChatTopic').removeClass 'invisible'
2013-07-24 10:55:47 +00:00
2013-07-09 19:39:31 +00:00
$('.timsChatMessageContainer').removeClass 'active'
$("#timsChatMessageContainer#{userID}").addClass 'active'
2014-02-08 02:21:25 +00:00
$("#timsChatMessageTabMenuAnchor#{userID}").parent().removeClass 'notify'
$("#timsChatMessageContainer#{userID}").removeClass 'notify'
2014-03-17 17:42:30 +00:00
$("#timsChatMessageContainer#{userID}").trigger 'scroll'
2014-02-08 02:21:25 +00:00
$('#timsChatMessageTabMenu').wcfTabs 'select', $("#timsChatMessageTabMenuAnchor#{userID}").parent().index()
do WCF.DOMNodeInsertedHandler.execute
2014-02-27 15:14:11 +00:00
do $(window).resize
2014-02-08 02:21:25 +00:00
2013-07-09 19:39:31 +00:00
openChannel = userID
2013-07-09 17:41:14 +00:00
Close private channel
closePrivateChannel = (userID) ->
2013-07-12 14:49:40 +00:00
unless userID is 0
2014-02-08 02:21:25 +00:00
do $("#timsChatMessageTabMenuAnchor#{userID}").parent().remove
2013-07-12 14:49:40 +00:00
do $("#timsChatMessageContainer#{userID}").remove
2014-02-08 02:21:25 +00:00
$('#timsChatMessageTabMenu').wcfTabs 'refresh'
WCF.System.FlexibleMenu.rebuild $('#timsChatMessageTabMenu > .tabMenu').wcfIdentify()
2013-07-12 18:52:54 +00:00
2014-02-08 02:21:25 +00:00
if $('#timsChatMessageTabMenu > .tabMenu > ul > li').length <= 1
$('#timsChatMessageTabMenu').addClass 'singleTab'
2013-07-12 14:49:40 +00:00
2013-07-09 18:45:35 +00:00
openPrivateChannel 0
2013-07-09 17:41:14 +00:00
2014-06-19 18:51:48 +00:00
Clears a channel
clearChannel = (userID) ->
do $("#timsChatMessageContainer#{userID} .timsChatMessage").remove
$("#timsChatMessageContainer#{userID}").scrollTop $("#timsChatMessageContainer#{userID}").prop 'scrollHeight'
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]?
events[event].add callback
2013-07-09 19:48:40 +00:00
true
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]?
events[event].remove callback
2013-07-09 19:48:40 +00:00
true
2013-10-06 00:59:13 +00:00
The following code handles attachment uploads
Enable attachment code if `WCF.Attachment.Upload` is defined
if WCF?.Attachment?.Upload? and $('#timsChatUploadContainer').length
2013-09-14 21:45:34 +00:00
Attachment = WCF.Attachment.Upload.extend
2013-10-06 00:59:13 +00:00
fileUploaded: no
Initialize WCF.Attachment.Upload
See WCF.Attachment.Upload.init()
2013-09-14 21:45:34 +00:00
init: ->
2013-10-06 00:59:13 +00:00
@_super $('#timsChatUploadContainer'), $(false), 'be.bastelstu.chat.message', 0, 0, 0, 1, null
unless @_supportsAJAXUpload
$('#timsChatUploadDropdownMenu .uploadButton').click => do @_showOverlay
label = $ '#timsChatUploadDropdownMenu li > span > label'
parent = do label.parent
css = parent.css ['padding-top', 'padding-right', 'padding-bottom', 'padding-left']
label.css css
label.css 'margin', "-#{css['padding-top']} -#{css['padding-right']} -#{css['padding-bottom']} -#{css['padding-left']}"
$('#timsChatUpload').click ->
$('#timsChatUpload > span.icon-ban-circle').removeClass('icon-ban-circle').addClass 'icon-paper-clip'
do $('#timsChatUploadContainer .innerError').remove
Overwrite WCF.Attachment.Upload._createButton() to create the upload button as small button into a button group
2013-09-14 21:45:34 +00:00
_createButton: ->
if @_supportsAJAXUpload
2013-10-06 00:59:13 +00:00
@_fileUpload = $ """<input id="timsChatUploadInput" type="file" name="#{@_name}" />"""
@_fileUpload.change => do @_upload
@_fileUpload.appendTo 'body'
2013-09-14 21:45:34 +00:00
2013-10-06 00:59:13 +00:00
_removeButton: ->
do @_fileUpload.remove
2013-09-14 21:45:34 +00:00
2013-10-06 00:59:13 +00:00
See WCF.Attachment.Upload._getParameters()
2013-09-14 21:45:34 +00:00
2013-10-06 00:59:13 +00:00
_getParameters: ->
2013-09-14 21:45:34 +00:00
@_tmpHash = do Math.random
2014-03-02 16:53:02 +00:00
@_parentObjectID = roomList.active.roomID
2013-10-06 00:59:13 +00:00
2013-09-14 21:45:34 +00:00
do @_super
2013-10-06 00:59:13 +00:00
_upload: ->
files = @_fileUpload.prop 'files'
if files.length
$('#timsChatUpload > span.icon').removeClass('icon-paper-clip icon-ban-circle').addClass('icon-spinner')
do @_super
Create a message containing the uploaded attachment
_insert: (event) ->
objectID = $(event.currentTarget).data 'objectID'
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'sendAttachment'
className: 'chat\\data\\message\\MessageAction'
parameters:
objectID: objectID
tmpHash: @_tmpHash
parentObjectID: 1#@_parentObjectID
showLoadingOverlay: false
success: ->
do $('#timsChatUploadDropdownMenu .jsDeleteButton').parent().remove
do $('#timsChatUploadDropdownMenu .sendAttachmentButton').remove
do $('#timsChatUploadDropdownMenu .uploadButton').show
$('#timsChatUpload > span.icon').removeClass('icon-ok-sign').addClass 'icon-paper-clip'
fileUploaded = no
failure: (data) ->
false
_initFile: (file) ->
li = $("""<li class="uploadProgress">
<span>
<progress max="100"></progress>
</span>
</li>"""
).data('filename', file.name)
$('#timsChatUploadDropdownMenu').append li
do $('#timsChatUploadDropdownMenu .uploadButton').hide
# validate file size
if @_buttonSelector.data('maxSize') < file.size
# remove progress bar
do li.find('progress').remove
# upload icon
$('#timsChatUpload > span.icon-spinner').removeClass('icon-spinner').addClass 'icon-ban-circle'
# error message
2013-10-16 13:40:29 +00:00
$('#timsChatUpload').addClass('uploadFailed').after """<small class="innerError">#{WCF.Language.get('wcf.attachment.upload.error.tooLarge')}</small>"""
2013-10-06 00:59:13 +00:00
do @_error
li.addClass 'uploadFailed'
li
_validateLimit: ->
innerError = @_buttonSelector.next 'small.innerError'
if fileUploaded
# reached limit
unless innerError.length
innerError = $('<small class="innerError" />').insertAfter '#timsChatUpload'
innerError.html WCF.Language.get('wcf.attachment.upload.error.reachedLimit')
innerError.css 'position', 'absolute'
return false
# remove previous errors
do innerError.remove
true
_success: (uploadID, data) ->
for li in @_uploadMatrix[uploadID]
do li.find('progress').remove
li.removeClass('uploadProgress').addClass 'sendAttachmentButton'
li.find('span').addClass('box32').append """
<div class="framed attachmentImageContainer">
<span class="attachmentTinyThumbnail icon icon32 icon-paper-clip"></span>
</div>
<div class="containerHeaderline">
<p></p>
<small></small>
<p>#{WCF.Language.get('wcf.global.button.submit')}</p>
</div>"""
li.click (event) => @_insert(event)
filename = li.data 'filename'
2013-10-06 15:34:58 +00:00
internalFileID = li.data 'internalFileID'
2013-10-06 00:59:13 +00:00
2013-10-06 15:34:58 +00:00
if data.returnValues and data.returnValues.attachments[internalFileID]
if data.returnValues.attachments[internalFileID].tinyURL
li.find('.box32 > div.attachmentImageContainer > .icon-paper-clip').replaceWith $("""<img src="#{data.returnValues.attachments[internalFileID].tinyURL}'" alt="" class="attachmentTinyThumbnail" style="width: 32px; height: 32px;" />""")
2013-10-06 00:59:13 +00:00
link = $ '<a href="" class="jsTooltip"></a>'
2013-10-06 15:34:58 +00:00
link.attr {'href': data.returnValues.attachments[internalFileID].url, 'title': filename}
2013-10-06 00:59:13 +00:00
2013-10-06 15:34:58 +00:00
unless parseInt(data.returnValues.attachments[internalFileID].isImage) is 0
2013-10-06 00:59:13 +00:00
link.addClass('jsImageViewer')
2014-02-08 02:21:25 +00:00
unless data.returnValues.attachments[internalFileID].tinyURL
2013-10-06 15:34:58 +00:00
li.find('.box32 > div.attachmentImageContainer > .icon-paper-clip').replaceWith $("""<img src="#{data.returnValues.attachments[internalFileID].url}'" alt="" class="attachmentTinyThumbnail" style="width: 32px; height: 32px;" />""")
2013-10-06 00:59:13 +00:00
li.find('.attachmentTinyThumbnail').wrap link
2013-10-06 15:34:58 +00:00
li.find('small').append data.returnValues.attachments[internalFileID].formattedFilesize
2013-10-06 00:59:13 +00:00
2013-10-06 15:34:58 +00:00
li.data 'objectID', data.returnValues.attachments[internalFileID].attachmentID
2013-10-06 00:59:13 +00:00
deleteButton = $ """
<li>
2013-10-06 15:34:58 +00:00
<span class="jsDeleteButton" data-object-id="#{data.returnValues.attachments[internalFileID].attachmentID}" data-confirm-message="#{WCF.Language.get('wcf.attachment.delete.sure')}">
2013-10-06 00:59:13 +00:00
<span class="icon icon16 icon-remove pointer jsTooltip" />
<span>#{WCF.Language.get('wcf.global.button.delete')}</span>
</span>
</li>"""
li.parent().append deleteButton
2013-10-16 13:40:29 +00:00
fileUploaded = yes
2013-10-06 00:59:13 +00:00
else
$('#timsChatUpload .icon-spinner').removeClass('icon-spinner').addClass 'icon-ban-circle'
2013-10-06 15:34:58 +00:00
if data.returnValues and data.returnValues.errors[internalFileID]
errorMessage = data.returnValues.errors[internalFileID].errorType
2013-10-06 00:59:13 +00:00
else
errorMessage = 'uploadFailed'
$('#timsChatUpload').addClass('uploadFailed').after """<small class="innerError">#{WCF.Language.get('wcf.attachment.upload.error.' + errorMessage)}</small>"""
do $('#timsChatUploadDropdownMenu .sendAttachmentButton').remove
do $('#timsChatUploadDropdownMenu .uploadButton').show
2013-10-16 13:40:29 +00:00
fileUploaded = no
2013-10-06 00:59:13 +00:00
do WCF.DOMNodeInsertedHandler.execute
$('#timsChatUpload > span.icon').removeClass('icon-spinner').addClass 'icon-ok-sign'
do $('#timsChatUploadDropdownMenu .uploadProgress').remove
do $('#timsChatUploadDropdownMenu .sendAttachmentButton').show
_error: (jqXHR, textStatus, errorThrown) ->
$('#timsChatUpload > .icon-spinner').removeClass('icon-spinner').addClass 'icon-ban-circle'
2013-10-16 13:40:29 +00:00
unless $('#timsChatUpload').hasClass('uploadFailed')
$('#timsChatUpload').addClass('uploadFailed').after """<small class="innerError">#{WCF.Language.get('wcf.attachment.upload.error.uploadFailed')}</small>"""
2013-10-06 00:59:13 +00:00
do $('#timsChatUploadDropdownMenu .uploadProgress').remove
do $('#timsChatUploadDropdownMenu .uploadButton').show
2013-10-16 13:40:29 +00:00
fileUploaded = no
2013-10-06 00:59:13 +00:00
Action = {}
Action.Delete = WCF.Action.Delete.extend
triggerEffect: (objectIDs) ->
for index in @_containers
container = $ "##{index}"
if WCF.inArray container.find(@_buttonSelector).data('objectID'), objectIDs
self = @
container.wcfBlindOut 'up', (event) ->
parent = do $(@).parent
do $(@).remove
do parent.find('.sendAttachmentButton').remove
do parent.find('.uploadButton').show
$('#timsChatUpload > .icon-ok-sign').removeClass('icon-ok-sign').addClass 'icon-paper-clip'
self._containers.splice(self._containers.indexOf $(@).wcfIdentify(), 1)
self._didTriggerEffect($ @)
fileUploaded = no
return
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
2014-03-01 23:33:45 +00:00
Return a copy of the object containing the IDs of the marked messages
getMarkedMessages: -> JSON.parse JSON.stringify markedMessages
2014-03-02 16:53:02 +00:00
getUserList: -> JSON.parse JSON.stringify userList
getRoomList: -> JSON.parse JSON.stringify roomList
2014-03-01 23:33:45 +00:00
2013-05-15 19:55:51 +00:00
refreshRoomList: refreshRoomList
insertText: insertText
freeTheFish: freeTheFish
2013-05-26 15:19:04 +00:00
join: join
2013-05-15 19:55:51 +00:00
listener:
add: addListener
remove: removeListener
2013-09-14 21:45:34 +00:00
Chat.Attachment = Attachment if Attachment?
2013-10-06 00:59:13 +00:00
Chat.Action = Action if Attachment?
2013-09-14 21:45:34 +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, @)