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
2014-12-16 22:45:28 +00:00
awayStatus = null
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-08-09 22:23:29 +00:00
loadingAwaiting = 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-12-12 22:15:58 +00:00
init = (roomID, config, titleTemplate, messageTemplate, userTemplate, userMenuTemplate, userInviteDialogTemplate) ->
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
2014-12-12 22:15:58 +00:00
v.userInviteDialogTemplate = userInviteDialogTemplate
v.userInviteDialogUserListEntryTemplate = new WCF.Template """
<dl>
<dt></dt>
<dd>
<label>
<input type="checkbox" id="userInviteDialogUserID-{$user.objectID}" value="{$user.objectID}" checked="checked" /> {$user.label}
</label>
</dd>
</dl>
"""
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-12-09 22:57:25 +00:00
document.title = v.titleTemplate.fetch(getRoomList().active) if roomList.active?.title? and roomList.active.title.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
2015-01-09 19:16:47 +00:00
$('#timsChatSmileyContainer').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
2015-01-09 19:16:47 +00:00
overlaySmileyList = $('<ul class="smileyList">').append $('#timsChatSmileyContainer .smileyList').clone().children()
2014-02-27 21:16:08 +00:00
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'
2014-12-09 22:57:25 +00:00
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', ->
2014-12-14 00:17:45 +00:00
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
2015-09-26 21:27:41 +00:00
input = $ '#timsChatInput'
input.val ''
do input.focus
do input.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
2014-12-16 22:45:28 +00:00
2014-12-18 22:43:53 +00:00
sendMessage text,
2013-05-15 19:55:51 +00:00
failure: (data) ->
2014-12-18 22:43:53 +00:00
if data.returnValues?.errorType?
error = data.returnValues.errorType
else if data.returnValues?.errorMessage
error = data.returnValues.errorMessage
else if data.message?
error = data.message
showInputError error if error
2013-05-15 19:55:51 +00:00
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) ->
2014-08-02 19:40:38 +00:00
switch event.keyCode
when 229
return
when $.ui.keyCode.TAB
do event.preventDefault
input = $ @
2013-06-01 12:06:55 +00:00
2014-08-02 19:40:38 +00:00
autocomplete.value ?= do input.val
autocomplete.caret ?= do input.getCaret
2013-07-12 14:49:40 +00:00
2014-08-02 19:40:38 +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
2014-12-12 22:15:58 +00:00
afterComplete = ''
2014-08-02 19:40:38 +00:00
else
afterComplete = toComplete.substring nextSpace + 1
toComplete = toComplete.substring 0, nextSpace
2013-06-01 12:06:55 +00:00
2014-08-02 19:40:38 +00:00
return if toComplete.length is 0
console.log "Autocompleting '#{toComplete}'"
if beforeComplete is '' and (toComplete.substring 0, 1) is '/'
regex = new RegExp "^#{WCF.String.escapeRegExp toComplete.substring 1}", "i"
commands = (command for command in v.config.installedCommands when regex.test command)
toComplete = '/' + commands[autocomplete.offset++ % commands.length] + ' ' if commands.length isnt 0
else
regex = new RegExp "^#{WCF.String.escapeRegExp toComplete}", "i"
users = [ ]
for userID, user of userList.current
users.push user.username if regex.test user.username
toComplete = users[autocomplete.offset++ % users.length] + ', ' if users.length isnt 0
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
2014-08-02 19:40:38 +00:00
else
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
2014-12-16 22:45:28 +00:00
$('#timsChatInput').on 'input change', (event) ->
do pe.autoAway?.resume unless userList.current?[WCF.User.userID]?.awayStatus?
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
2014-12-14 00:17:45 +00:00
Handle saving of persistent toggleable buttons
$('.timsChatToggle.persists').click (event) ->
do event.preventDefault
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'updateOption'
className: 'chat\\data\\user\\UserAction'
parameters:
optionName: "chatButton#{$(@).attr('id').replace /^timsChat/, ''}"
optionValue: $(@).data 'status'
showLoadingOverlay: false
suppressErrors: true
2013-05-15 19:55:51 +00:00
Mark smilies as disabled when they are disabled.
2013-04-22 16:52:05 +00:00
2014-12-14 00:17:45 +00:00
if $('#timsChatSmilies').data('status') is 0
2015-01-09 19:16:47 +00:00
$('#timsChatSmileyContainer').addClass 'invisible'
2014-12-14 00:17:45 +00:00
else
2015-01-09 19:16:47 +00:00
$('#timsChatSmileyContainer').removeClass 'invisible'
2014-12-14 00:17:45 +00:00
2013-05-15 19:55:51 +00:00
$('#timsChatSmilies').click (event) ->
if $(@).data 'status'
2015-01-09 19:16:47 +00:00
$('#timsChatSmileyContainer').removeClass 'invisible'
2013-05-15 19:55:51 +00:00
else
2015-01-09 19:16:47 +00:00
$('#timsChatSmileyContainer').addClass 'invisible'
2013-04-22 16:52:05 +00:00
2013-04-13 14:38:50 +00:00
Toggle fullscreen mode.
2014-12-14 00:17:45 +00:00
do ->
fullscreen = (status = true) ->
if status
messageContainerSize = $('.timsChatMessageContainer').height()
$('html').addClass 'fullscreen'
do $(window).resize
else
$('.timsChatMessageContainer').height messageContainerSize
$('#timsChatUserList').height userListSize
$('html').removeClass 'fullscreen'
do $(window).resize
$('#timsChatFullscreen').click (event) ->
# Force dropdowns to reorientate
$('.dropdownMenu').data 'orientationX', ''
2014-02-27 22:32:16 +00:00
2014-12-14 00:17:45 +00:00
if $(@).data 'status'
fullscreen on
else
fullscreen off
Switch to fullscreen mode on mobile devices or if fullscreen is active on boot
if $('#timsChatFullscreen').data('status') is 1
fullscreen on
2013-05-15 19:55:51 +00:00
else
2014-12-14 00:17:45 +00:00
do $('#timsChatFullscreen').click if WCF.System.Mobile.UX._enabled
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
2014-12-09 22:57:25 +00:00
Show invite dialog.
$('#timsChatInvite').click (event) ->
do event.preventDefault
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'prepareInvite'
className: 'chat\\data\\user\\UserAction'
success: (data) ->
$('<div id="timsChatInviteDialog"></div>').appendTo 'body' unless $.wcfIsset 'timsChatInviteDialog'
2014-12-12 22:15:58 +00:00
timsChatInviteDialog = $ '#timsChatInviteDialog'
# Remove old event listeners
do timsChatInviteDialog.find('#userInviteDialogUsernameInput').off().remove
timsChatInviteDialog.html v.userInviteDialogTemplate.fetch
users: data.returnValues.users
new WCF.Search.User '#userInviteDialogUsernameInput', (user) ->
if $.wcfIsset "userInviteDialogUserID-#{user.objectID}"
$("#userInviteDialogUserID-#{user.objectID}").prop 'checked', true
else
$('#userInviteDialogUserList').append v.userInviteDialogUserListEntryTemplate.fetch
user: user
$('#userInviteDialogUsernameInput').val ""
, false, [ WCF.User.username ], false
$('#userInviteDialogFormSubmit').on 'click', (event) ->
checked = $ '#userInviteDialogUserList input[type=checkbox]:checked, #userInviteDialogFollowingList input[type=checkbox]:checked'
inviteUserList = [ ]
checked.each (k, v) -> inviteUserList.push do $(v).val
if inviteUserList.length
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'invite'
className: 'chat\\data\\user\\UserAction'
parameters:
recipients: inviteUserList
success: (data) ->
do new WCF.System.Notification(WCF.Language.get 'wcf.global.success').show
2015-03-20 23:54:03 +00:00
failure: (data) ->
return true unless (data?.returnValues?.errorType?) or (data?.message?)
$("""<div class="ajaxDebugMessage">#{(data?.returnValues?.errorType) ? data.message}</div>""").wcfDialog title: WCF.Language.get 'wcf.global.error.title'
return false
2014-12-12 22:15:58 +00:00
$('#timsChatInviteDialog').wcfDialog 'close'
timsChatInviteDialog.wcfDialog
2014-12-09 22:57:25 +00:00
title: WCF.Language.get 'chat.global.invite'
2015-03-20 23:54:03 +00:00
onShow: -> do $('#userInviteDialogUsernameInput').focus
2014-12-09 22:57:25 +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'
2014-06-21 16:54:04 +00:00
elem.parents('.timsChatTextContainer').siblings('.timsChatMessageGroupMarker').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'
2014-06-21 16:54:04 +00:00
elem.parents('.timsChatTextContainer').siblings('.timsChatMessageGroupMarker').prop 'checked', false
2014-03-01 23:33:45 +00:00
2014-06-21 16:54:04 +00:00
# This function can be used to toggle the marking state of every “submessage” of one message container (speech bubble)
# The according element with the class “.timsChatMessageGroupMarker” has to be made visible via CSS.
$(document).on 'click', '.timsChatMessageGroupMarker', (event) ->
2014-03-01 23:33:45 +00:00
$(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?
2014-12-14 00:17:45 +00:00
do ->
askForPermission = ->
unless window.Notification.permission is 'granted'
window.Notification.requestPermission (permission) ->
window.Notification.permission ?= permission
if $('#timsChatNotify').data('status') is 1
do askForPermission
$('#timsChatNotify').click (event) ->
return unless $(@).data 'status'
do askForPermission
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
2014-12-16 22:45:28 +00:00
pe.autoAway = new WCF.PeriodicalExecuter autoAway, v.config.autoAwayTime * 1e3 if v.config.autoAwayTime > 0
2013-04-13 14:38:50 +00:00
2014-09-19 21:34:17 +00:00
Initialize the [**Push**](https://github.com/wbbaddons/Push) integration of **Tims Chat**. Once
the browser is connected to **Push** periodic message loading will be disabled and **Tims Chat** will
2013-05-15 19:55:51 +00:00
load messages if the appropriate event arrives.
2013-05-30 17:03:37 +00:00
2013-05-15 19:55:51 +00:00
do ->
2014-09-19 21:34:17 +00:00
be.bastelstu.wcf.push.onConnect ->
2013-05-15 19:55:51 +00:00
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
2014-09-19 21:34:17 +00:00
be.bastelstu.wcf.push.onDisconnect ->
2013-05-15 19:55:51 +00:00
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
2014-09-19 21:34:17 +00:00
be.bastelstu.wcf.push.onMessage 'be.bastelstu.chat.newMessage', getMessages
be.bastelstu.wcf.push.onMessage 'be.bastelstu.wcf.push.tick60', getMessages
be.bastelstu.wcf.push.onMessage 'be.bastelstu.chat.roomChange', refreshRoomList
be.bastelstu.wcf.push.onMessage 'be.bastelstu.chat.join', refreshRoomList
be.bastelstu.wcf.push.onMessage 'be.bastelstu.chat.leave', refreshRoomList
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-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
2014-12-18 22:43:53 +00:00
Send messages
sendMessage = (text, options) ->
options = $.extend
showLoadingOverlay: false
suppressErrors: false
, options
new WCF.Action.Proxy
autoSend: true
data:
actionName: 'send'
className: 'chat\\data\\message\\MessageAction'
parameters:
text: text
enableSmilies: $('#timsChatSmilies').data 'status'
showLoadingOverlay: options.showLoadingOverlay
suppressErrors: options.suppressErrors
success: ->
do hideInputError
options.success?()
do getMessages
failure: (data) ->
return true unless (data?.returnValues?.errorType?) or (data?.message?)
options.failure? data
false
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
2014-12-16 22:45:28 +00:00
Sets user’ s status to away
autoAway = ->
do pe.autoAway?.stop
return if userList.current[WCF.User.userID].awayStatus?
2014-12-18 22:43:53 +00:00
sendMessage "/away #{WCF.Language.get 'chat.global.autoAway', { time: do (new Date).toTimeString }}",
suppressErrors: true
2014-12-16 22:45:28 +00:00
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
2014-08-09 22:23:29 +00:00
if loadingAwaiting
loadingAwaiting = false
do getMessages
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: ->
2014-08-09 22:23:29 +00:00
if loading
loadingAwaiting = true
return false
2013-05-15 19:55:51 +00:00
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-21 16:54:04 +00:00
2014-06-19 18:51:48 +00:00
if message.type is v.config.messageTypes.CLEAR
createNewMessage = yes
clearChannel 0
2014-06-21 16:54:04 +00:00
2014-12-17 20:04:55 +00:00
if message.isInPrivateChannel and message.sender is WCF.User.userID
container = $ "#timsChatMessageContainer#{message.receiver} > ul"
else if message.isInPrivateChannel
container = $ "#timsChatMessageContainer#{message.sender} > ul"
else
container = $ '#timsChatMessageContainer0 > ul'
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
2014-06-21 16:54:04 +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
2014-12-17 20:04:55 +00:00
li.appendTo container
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
2014-06-21 16:54:04 +00:00
2014-12-17 20:04:55 +00:00
textContainer = container.find '.timsChatMessage:last-child .timsChatTextContainer'
2014-06-21 16:54:04 +00:00
textContainer.append $(output).find('.timsChatTextContainer li:last-child')
# unmark messages
textContainer.parents('.timsChatMessage').removeClass 'checked'
textContainer.siblings('.timsChatMessageGroupMarker').prop 'checked', false
2014-12-17 20:04:55 +00:00
if v.config.messagesPerTab
timsChatText = container.find '.timsChatText'
if timsChatText.length > v.config.messagesPerTab
firstMessage = do timsChatText.first
timsChatMessage = firstMessage.parents '.timsChatMessage'
if timsChatMessage.find('.timsChatTextContainer').children().length > 1
time = firstMessage.siblings(':first').find '> time'
timsChatMessage.find('.timsChatInnerMessage > time').replaceWith time
do firstMessage.remove
else
do timsChatMessage.remove
2013-06-23 17:46:50 +00:00
lastMessage = message
2014-06-21 16:54:04 +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 = { }
2014-12-16 22:45:28 +00:00
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', ''
2014-12-16 22:45:28 +00:00
if user.userID is WCF.User.userID and user.awayStatus isnt awayStatus
if user.awayStatus?
do pe.autoAway?.stop # Away
else
do pe.autoAway?.resume # Back
awayStatus = user.awayStatus
2013-07-09 20:02:47 +00:00
if user.suspended
element.addClass 'suspended'
else
element.removeClass 'suspended'
2014-07-27 20:21:49 +00:00
if user.mod
element.addClass 'mod'
else
element.removeClass 'mod'
2013-07-09 20:02:47 +00:00
$('#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
2014-07-27 20:21:49 +00:00
li.addClass 'mod' if user.mod
2013-07-09 20:02:47 +00:00
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
2014-12-16 22:45:28 +00:00
caret: false
2013-05-15 19:55:51 +00:00
, 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'
2014-12-18 22:43:53 +00:00
return false if maxLength? and text.length > maxLength
2014-04-29 20:04:52 +00:00
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
2014-12-16 22:45:28 +00:00
$('#timsChatInput').setCaret options.caret if options.caret
2013-07-09 19:48:40 +00:00
do $('#timsChatInput').focus
2014-12-18 22:43:53 +00:00
true
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'
2014-07-30 19:43:00 +00:00
content = "#{message.username}#{message.separator} #{if message.message.length > 50 then message.message[0..50] + '\u2026' else message.message}"
2013-05-15 19:55:51 +00:00
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: {}
2015-01-09 23:49:04 +00:00
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
2015-01-09 23:49:04 +00:00
li = $ '<li />'
2014-06-20 15:26:51 +00:00
li.addClass 'timsChatRoom'
2013-05-15 19:55:51 +00:00
li.addClass 'active' if room.active
2014-06-20 15:26:51 +00:00
a = $("""<a href="#{room.link}">#{WCF.String.escapeHTML(room.title)}</a>""")
a.data 'roomID', room.roomID
a.appendTo li
2015-01-09 23:49:04 +00:00
span = $ '<span />'
span.addClass 'badge'
span.text WCF.String.formatNumeric room.userCount
span.append " / #{WCF.String.formatNumeric room.maxUsers}" if room.maxUsers > 0
span.appendTo li
2013-05-15 19:55:51 +00:00
$('#timsChatRoomList ul').append li
2015-01-09 23:49:04 +00:00
2013-05-15 19:55:51 +00:00
if window.history?.replaceState?
2014-06-20 15:26:51 +00:00
$('.timsChatRoom a').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'
2015-01-09 23:49:04 +00:00
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
2015-01-09 23:49:04 +00:00
suppressErrors: true
2013-05-26 15:19:04 +00:00
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-12-09 22:57:25 +00:00
document.title = v.titleTemplate.fetch getRoomList().active
2014-03-02 16:53:02 +00:00
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) ->
2015-01-09 23:49:04 +00:00
if data?.returnValues?.fieldName is 'room'
isJoining = no
loading = false
window.history.replaceState {}, '', roomList.active.link if window.history?.replaceState?
$('<div>').attr('id', 'timsChatJoinErrorDialog').append("<p>#{data.returnValues.errorType}</p>").wcfDialog
title: WCF.Language.get 'wcf.global.error.title'
do $('#timsChatInput').enable().focus
do refreshRoomList
else
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
2014-12-18 14:58:55 +00:00
avatar.append "<span>#{WCF.String.escapeHTML 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'
2014-06-21 19:24:57 +00:00
do $('#timsChatUpload').parent().hide
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'
2014-06-21 19:24:57 +00:00
do $('#timsChatUpload').parent().show
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
2014-12-09 22:57:25 +00:00
getRoomList = -> JSON.parse JSON.stringify roomList
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
2014-12-09 22:57:25 +00:00
getRoomList: getRoomList
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, @)