diff --git a/acptemplate/__messageLogTable.tpl b/acptemplate/__messageLogTable.tpl index f12c738..157f124 100644 --- a/acptemplate/__messageLogTable.tpl +++ b/acptemplate/__messageLogTable.tpl @@ -3,7 +3,7 @@ {lang}wcf.global.objectID{/lang} - {lang}chat.general.time{/lang} + {lang}chat.global.time{/lang} {lang}wcf.user.username{/lang} {lang}chat.acp.log.message{/lang} diff --git a/acptemplate/chatSuspensionList.tpl b/acptemplate/chatSuspensionList.tpl index c632484..e7330f3 100644 --- a/acptemplate/chatSuspensionList.tpl +++ b/acptemplate/chatSuspensionList.tpl @@ -66,7 +66,7 @@
-
+
{foreach from=$rooms item='roomBit'} @@ -38,7 +38,7 @@
-
+
{if $errorField == 'date'} diff --git a/file/acp/be.bastelstu.chat.update.php b/file/acp/be.bastelstu.chat.update.php index c7e59c8..df8725b 100644 --- a/file/acp/be.bastelstu.chat.update.php +++ b/file/acp/be.bastelstu.chat.update.php @@ -41,7 +41,7 @@ public function execute() { 'roomID' => $room->roomID, 'time' => TIME_NOW, 'type' => \chat\data\message\Message::TYPE_INFORMATION, - 'message' => \wcf\system\WCF::getLanguage()->get('chat.general.information.chatUpdate') + 'message' => \wcf\system\WCF::getLanguage()->get('chat.global.information.chatUpdate') ) )); $messageAction->executeAction(); diff --git a/file/js/be.bastelstu.Chat.litcoffee b/file/js/be.bastelstu.Chat.litcoffee index 9e894f8..94255b8 100644 --- a/file/js/be.bastelstu.Chat.litcoffee +++ b/file/js/be.bastelstu.Chat.litcoffee @@ -6,7 +6,7 @@ everything that happens in the GUI of **Tims Chat**. ### Copyright Information # @author Tim Düsterhus - # @copyright 2010-2013 Tim Düsterhus + # @copyright 2010-2014 Tim Düsterhus # @license Creative Commons Attribution-NonCommercial-ShareAlike # @package be.bastelstu.chat ### @@ -25,7 +25,7 @@ enabling EMCAScript 5 strict mode and overwriting console to prepend the name of window.console.warn "[be.bastelstu.Chat] #{message}" error: (message) -> window.console.error "[be.bastelstu.Chat] #{message}" - + Continue with defining the needed variables. All variables are local to our closure and will be exposed by a function if necessary. @@ -45,6 +45,9 @@ exposed by a function if necessary. lastMessage = null openChannel = 0 + messageContainerSize = 0 + userListSize = 0 + remainingFailures = 3 events = @@ -73,14 +76,18 @@ exposed by a function if necessary. Initialize **Tims Chat**. Bind needed DOM events and initialize data structures. initialized = false - init = (roomID, config, titleTemplate, messageTemplate, userTemplate) -> + init = (roomID, config, titleTemplate, messageTemplate, userTemplate, userMenuTemplate) -> return false if initialized initialized = true + messageContainerSize = $('.timsChatMessageContainer').height() + userListSize = $('#timsChatUserList').height() + v.config = config v.titleTemplate = titleTemplate v.messageTemplate = messageTemplate v.userTemplate = userTemplate + v.userMenuTemplate = userMenuTemplate console.log 'Initializing' @@ -110,14 +117,40 @@ Make the user leave the chat when **Tims Chat** is about to be unloaded. async: false suppressErrors: true undefined - + + $(window).resize -> + # TODO + return if WCF.System.Mobile.UX._enabled + + return unless $('html').hasClass 'fullscreen' + + do -> + verticalContentPadding = $('#content').innerHeight() - $('#content').height() + verticalSizeOfContentElements = do -> + height = 0 + $('#content > *:visible').each (k, v) -> height += $(v).outerHeight() + height + + freeSpace = $('body').height() - verticalContentPadding - verticalSizeOfContentElements + + $('.timsChatMessageContainer').height $('.timsChatMessageContainer').height() + freeSpace + do -> + verticalUserListContainerPadding = $('#timsChatUserListContainer').innerHeight() - $('#timsChatUserListContainer').height() + sidebarHeight = $('.sidebar > div').height() + + freeSpace = $('body').height() - verticalUserListContainerPadding - sidebarHeight + $('#timsChatUserList').height $('#timsChatUserList').height() + freeSpace + + + Insert the appropriate smiley code into the input when a smiley is clicked. $('#smilies').on 'click', 'img', -> insertText " #{$(@).attr('alt')} " Handle private channel menu - $('#privateChannelsMenu').on 'click', '.privateChannel', -> openPrivateChannel $(@).data 'privateChannelID' + $('#timsChatMessageTabMenu > .tabMenu').on 'click', '.timsChatMessageTabMenuAnchor', -> + openPrivateChannel $(@).data 'userID' 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. @@ -217,9 +250,24 @@ Reset autocompleter to default status, when the input is `click`ed, as the posit value: null caret: null +Bind user menu functions + + $('#dropdownMenuContainer').on 'click', '.jsTimsChatUserMenuWhisper', -> + command = "/whisper #{userList.current[$(@).parents('ul').data 'userID'].username}, " + return if $('#timsChatInput').val().match(new RegExp WCF.String.escapeRegExp("^#{command}"), 'i') + + insertText command, prepend: yes + + $('#dropdownMenuContainer').on 'click', '.jsTimsChatUserMenuQuery', -> openPrivateChannel $(@).parents('ul').data 'userID' + $('#dropdownMenuContainer').on 'click', '.jsTimsChatUserMenuBan', -> + command = "/ban #{userList.current[$(@).parents('ul').data 'userID'].username}, " + return if $('#timsChatInput').val().match(new RegExp WCF.String.escapeRegExp("^#{command}"), 'i') + + insertText command, prepend: yes + Refresh the room list when the associated button is `click`ed. - $('#timsChatRoomList button').click -> do refreshRoomList + $('#timsChatRoomListReloadButton').click -> do refreshRoomList Clear the chat by removing every single message once the clear button is `clicked`. @@ -247,9 +295,9 @@ Mark smilies as disabled when they are disabled. $('#timsChatSmilies').click (event) -> if $(@).data 'status' - $('#smilies').removeClass 'disabled' + $('#smilies').removeClass 'invisible' else - $('#smilies').addClass 'disabled' + $('#smilies').addClass 'invisible' Toggle fullscreen mode. @@ -259,7 +307,10 @@ Toggle fullscreen mode. if $(@).data 'status' $('html').addClass 'fullscreen' + do $(window).resize else + $('.timsChatMessageContainer').height messageContainerSize + $('#timsChatUserList').height userListSize $('html').removeClass 'fullscreen' Toggle checkboxes. @@ -272,12 +323,14 @@ Toggle checkboxes. Hide topic container. - $('.jsTopicCloser').on 'click', -> - if $('.timsChatMessageContainer.active').data('userID') is 0 - $('#timsChatTopic').addClass 'hidden' - else - closePrivateChannel $('.timsChatMessageContainer.active').data('userID') - + $('#timsChatTopicCloser').on 'click', -> + $('#timsChatTopic').addClass 'invisible' + do $(window).resize + +Close private channels + + $('#timsChatMessageTabMenu').on 'click', '.jsChannelCloser', -> closePrivateChannel $(@).parent().data 'userID' + Visibly mark the message once the associated checkbox is checked. $(document).on 'click', '.timsChatMessage :checkbox', (event) -> @@ -289,27 +342,20 @@ Visibly mark the message once the associated checkbox is checked. Scroll down when autoscroll is being activated. $('#timsChatAutoscroll').click (event) -> - if $('#timsChatAutoscroll').data 'status' + if $(@).data 'status' $('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop 'scrollHeight' + + 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 $('.timsChatMessageContainer.active').on 'scroll', (event) -> - event.stopPropagation(); - - element = $ @ - scrollTop = element.scrollTop() - scrollHeight = element.prop 'scrollHeight' - height = element.height() - - 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 - $(@).removeClass 'notification' - do $('#timsChatAutoscroll').click + do event.stopPropagation + handleScroll event Enable duplicate tab detection. @@ -387,35 +433,85 @@ Free the fish. freeTheFish = -> return if $.wcfIsset 'fish' console.warn 'Freeing the fish' - fish = $ """
#{WCF.String.escapeHTML('><((((\u00B0>')}
""" + fish = $ """
""" + fish.direction = 'right' fish.css position: 'fixed' top: '50%' left: '50%' - color: 'black' - textShadow: '1px 1px white' - zIndex: 9999 - + zIndex: 0x7FFFFFFF + textShadow: '1px 1px rgb(0, 0, 0)' + fish.appendTo $ 'body' - pe.fish = new WCF.PeriodicalExecuter -> - left = Math.random() * 100 - 50 - top = Math.random() * 100 - 50 - fish = $ '#fish' + + 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 - left *= -1 unless fish.width() < (fish.position().left + left) < ($(window).width() - fish.width()) - top *= -1 unless fish.height() < (fish.position().top + top) < ($(window).height() - fish.height()) - - if left > 0 - fish.text '><((((\u00B0>' if left > 0 - else if left < 0 - fish.text '<\u00B0))))><' + while index < value.length + html = $ '' + i = 0 + $(value.split '').each (key, value) -> + $("#{value}").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 fish.animate top: (fish.position().top + top) left: (fish.position().left + left) , 1e3 - , 1.5e3 + , 1.2e3 + + fish.peColor = new WCF.PeriodicalExecuter -> + do fish.updateRainbowText + , .125e3 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. @@ -449,15 +545,13 @@ Prevent loading messages in parallel. Insert the given messages into the chat stream. handleMessages = (messages) -> - $('.timsChatMessageContainer.active').trigger 'scroll' - for message in messages - message.isInPrivateChannel = (String(message.type) is v.config.messageTypes.WHISPER) and ($.wcfIsset("timsChatMessageContainer#{message.receiver}") or $.wcfIsset("timsChatMessageContainer#{message.sender}")) + message.isInPrivateChannel = (message.type is v.config.messageTypes.WHISPER) and ($.wcfIsset("timsChatMessageContainer#{message.receiver}") or $.wcfIsset("timsChatMessageContainer#{message.sender}")) events.newMessage.fire message createNewMessage = yes - if $('.timsChatMessage:last-child .text').is('ul') and lastMessage isnt null and lastMessage.type in [ 0, 7 ] + if $('.timsChatMessage:last-child .timsChatText').is('ul') and lastMessage isnt null and lastMessage.type in [ v.config.messageTypes.NORMAL, v.config.messageTypes.WHISPER ] if lastMessage.type is message.type and lastMessage.sender is message.sender and lastMessage.receiver is message.receiver and lastMessage.isInPrivateChannel is message.isInPrivateChannel createNewMessage = no @@ -493,11 +587,34 @@ Insert the given messages into the chat stream. else messageContainerID = 0 - $("#timsChatMessageContainer#{messageContainerID} .timsChatMessage:last-child .text").append $(output).find('.text li:last-child') + $("#timsChatMessageContainer#{messageContainerID} .timsChatMessage:last-child .timsChatText").append $(output).find('.timsChatText li:last-child') lastMessage = message + $('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop('scrollHeight') if $('#timsChatAutoscroll').data('status') is 1 +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 + Rebuild the userlist based on the given `users`. handleUsers = (users) -> @@ -548,14 +665,7 @@ Build HTML of the user and insert it into the list, if the users was not found i li.append v.userTemplate.fetch user - menu = $ '
    ' - unless user.userID is WCF.User.userID - menu.append $("
  • #{WCF.Language.get('chat.general.query')}
  • ").click -> openPrivateChannel user.userID - menu.append $ "
  • #{WCF.Language.get('chat.general.kick')}
  • " - menu.append $ "
  • #{WCF.Language.get('chat.general.ban')}
  • " - menu.append $ """
  • #{WCF.Language.get('chat.general.profile')}
  • """ - - events.userMenu.fire user, menu + menu = $(v.userMenuTemplate.fetch user) if menu.find('li').length li.append menu @@ -563,7 +673,6 @@ Build HTML of the user and insert it into the list, if the users was not found i li.addClass 'dropdown' li.appendTo $ '#timsChatUserList > ul' - foundUsers[id] = true Remove all users that left the chat. @@ -581,12 +690,17 @@ Insert the given `text` into the input. If `options.append` is true the given `t the existing text. If `options.submit` is true the message will be sent to the server afterwards. insertText = (text, options = { }) -> + options.append = false if options.prepend? and options.prepend and not options.append? + options = $.extend + prepend: false append: true submit: false , options + text = text + $('#timsChatInput').val() if options.prepend text = $('#timsChatInput').val() + text if options.append + $('#timsChatInput').val text do $('#timsChatInput').keyup @@ -598,26 +712,28 @@ the existing text. If `options.submit` is true the message will be sent to the s 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. notify = (message) -> - if scrollUpNotifications - $('.timsChatMessageContainer.active').addClass 'notification' + return if message.sender is WCF.User.userID - if message.isInPrivateChannel - if message.sender is WCF.User.userID - privateChannelID = message.receiver - else - privateChannelID = message.sender + if scrollUpNotifications + $("#timsChatMessageTabMenu > .tabMenu > ul > li.ui-state-active").addClass 'notify' + $(".timsChatMessageContainer.active").addClass 'notify' - if $('.timsChatMessageContainer.active').data('userID') isnt privateChannelID - $("#privateChannel#{privateChannelID}").addClass 'notify' + if message.isInPrivateChannel + id = if message.sender is WCF.User.userID then message.receiver else message.sender + + if $('.timsChatMessageContainer.active').data('userID') isnt id + $("#timsChatMessageTabMenuAnchor#{id}").parent().addClass 'notify' + $("#timsChatMessageContainer#{id}").addClass 'notify' else if $('.timsChatMessageContainer.active').data('userID') isnt 0 - $("#privateChannel0").addClass 'notify' + $("#timsChatMessageTabMenuAnchor0").parent().addClass 'notify' + $("#timsChatMessageContainer0").addClass 'notify' return if isActive or $('#timsChatNotify').data('status') is 0 document.title = v.titleTemplate.fetch $.extend {}, currentRoom, newMessageCount: ++newMessageCount - title = WCF.Language.get 'chat.general.notify.title' + title = WCF.Language.get 'chat.global.notify.title' content = "#{message.username}#{message.separator} #{message.message}" if window.Notification?.permission is 'granted' @@ -648,8 +764,9 @@ Fetch the roomlist from the server and update it in the GUI. for room in data.returnValues li = $ '
  • ' + li.addClass('timsChatRoom').data('roomID', room.roomID) li.addClass 'active' if room.active - $("""#{WCF.String.escapeHTML(room.title)} #{WCF.String.formatNumeric room.userCount}""").addClass('timsChatRoom').data('roomID', room.roomID).appendTo li + $("""#{WCF.String.escapeHTML(room.title)} #{WCF.String.formatNumeric room.userCount}""").appendTo li $('#timsChatRoomList ul').append li if window.history?.replaceState? @@ -705,7 +822,7 @@ Joins a room. success: (data) -> loading = false - $('#timsChatTopic').removeClass 'hidden' + $('#timsChatTopic').removeClass 'invisible' currentRoom = data.returnValues currentRoom.roomID = roomID @@ -737,66 +854,74 @@ Open private channel div = $ '
    ' div.attr 'id', "timsChatMessageContainer#{userID}" div.data 'userID', userID + div.addClass 'tabMenuContent' div.addClass 'timsChatMessageContainer' - div.addClass 'marginTop' div.addClass 'container' - div.wrapInner '