diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 41d1881..0000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: php -php: - - 5.3 -script: php build.php \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..555681d --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +Tims Chat 3.0 +============= + +Tims Chat is a chat-plugin for WoltLab Community Framework. + + +Version notes +------------- + +The currently available source code represents an early alpha-version of Tims Chat, even though it may be installable, we cannot guarantee a working installation at any time. You MUST NOT install and/or use Tims Chat 3.0 in a production environment. + +Contribution +------------ + +Developers are always welcome to fork Tims Chat and provide features or bug fixes using pull requests. If you make changes or add classes it is mandatory to follow the requirements below: + +* Testing is key, you MUST try out your changes before submitting pull requests +* You MUST save your files with Unix-style line endings (\n) +* You MUST NOT include the closing tag of a PHP block at the end of file, provide an empty newline instead +* You MUST use tabs for indentation + * Tab size of 8 is required + * Empty lines MUST be indented equal to previous line +* All comments within source code MUST be written in English language +* Use a sensible number of commits. In most cases one commit should be enough. + * Split huge changes into several logical groups + * Rebase small changes that consist of several commits into one commit + +Follow the above conventions if you want your pull requests accepted. + +License +------- + +For licensing information refer to the LICENSE file in this folder. \ No newline at end of file diff --git a/build.php b/build.php old mode 100644 new mode 100755 index 5439cc6..f10da56 --- a/build.php +++ b/build.php @@ -1,16 +1,74 @@ #!/usr/bin/env php + * @package timwolla.wcf.chat + */ +echo << TimWolla.WCF.Chat = titleTemplate: null + title: document.title messageTemplate: null + newMessageCount: null + events: + newMessage: $.Callbacks() + userMenu: $.Callbacks() init: () -> + console.log('[TimWolla.WCF.Chat] Initializing'); @bindEvents() @refreshRoomList() new WCF.PeriodicalExecuter $.proxy(@refreshRoomList, this), 60e3 new WCF.PeriodicalExecuter $.proxy(@getMessages, this), @config.reloadTime * 1000 + @getMessages() $('#chatInput').focus() + console.log '[TimWolla.WCF.Chat] Finished initializing' ### # Binds all the events needed for Tims Chat. ### bindEvents: () -> + @isActive = true + $(window).focus $.proxy () -> + document.title = @title + @newMessageCount = 0 + clearTimeout @timeout + @isActive = true + , this + + $(window).blur $.proxy () -> + @title = document.title + @isActive = false + , this + $('.smiley').click $.proxy (event) -> @insertText ' ' + $(event.target).attr('alt') + ' ' , this @@ -34,11 +55,6 @@ TimWolla.WCF ?= {} @toggleSidebarContents $ event.target , this - $('.chatUser .chatUserLink').click $.proxy (event) -> - event.preventDefault() - @toggleUserMenu $ event.target - , this - $('#chatForm').submit $.proxy (event) -> event.preventDefault() @submit $ event.target @@ -108,6 +124,7 @@ TimWolla.WCF ?= {} ### freeTheFish: () -> return if $.wcfIsset('fish') + console.warn '[TimWolla.WCF.Chat] Freeing the fish' fish = $ '
' + WCF.String.escapeHTML('><((((°>') + '
' fish.css position: 'absolute' @@ -120,15 +137,20 @@ TimWolla.WCF ?= {} fish.appendTo $ 'body' new WCF.PeriodicalExecuter(() -> left = (Math.random() * 100 - 50) + top = (Math.random() * 100 - 50) + fish = $('#fish') - $('#fish').text('><((((°>') if (left > 0) - $('#fish').text('<°))))><') if (left < 0) + left *= -1 if((fish.position().left + left) < (0 + fish.width()) or (fish.position().left + left) > ($(document).width() - fish.width())) + top *= -1 if((fish.position().top + top) < (0 + fish.height()) or (fish.position().top + top) > ($(document).height() - fish.height())) - $('#fish').animate - top: '+=' + (Math.random() * 100 - 50) + fish.text('><((((°>') if (left > 0) + fish.text('<°))))><') if (left < 0) + + fish.animate + top: '+=' + top left: '+=' + left , 1000 - , 3e3); + , 1.5e3); ### # Loads new messages. ### @@ -137,7 +159,17 @@ TimWolla.WCF ?= {} dataType: 'json' type: 'POST' success: $.proxy((data, textStatus, jqXHR) -> - @handleMessages(data) + if (!@isActive && $('#chatNotify').data('status') is 1) + @newMessageCount += data.messages.length + if (@newMessageCount > 0) + @timeout = setTimeout $.proxy(() -> + document.title = @newMessageCount + WCF.Language.get('wcf.chat.newMessages') + setTimeout $.proxy(() -> + document.title = @title + , this), 3000 + , this), 1000 + @handleMessages(data.messages) + @handleUsers(data.users) , this) ### # Inserts the new messages. @@ -146,6 +178,8 @@ TimWolla.WCF ?= {} ### handleMessages: (messages) -> for message in messages + @events.newMessage.fire message + output = @messageTemplate.fetch message li = $ '
  • ' li.addClass 'chatMessage chatMessage'+message.type @@ -156,6 +190,43 @@ TimWolla.WCF ?= {} $('.chatMessageContainer').animate scrollTop: $('.chatMessageContainer ul').height() , 1000 + handleUsers: (users) -> + foundUsers = {} + for user in users + id = 'chatUser-'+user.userID + element = $('#'+id) + if element[0] + console.log '[TimWolla.WCF.Chat] Shifting user ' + user.userID + element = element.detach() + $('#chatUserList').append element + else + console.log '[TimWolla.WCF.Chat] Inserting user ' + user.userID + li = $ '
  • ' + li.attr 'id', id + li.addClass 'chatUser' + a = $ ''+user.username+'' + a.click $.proxy (event) -> + event.preventDefault() + @toggleUserMenu $ event.target + , this + li.append a + menu = $ '' + menu.addClass 'chatUserMenu' + menu.append $ '
  • {lang}wcf.chat.query{/lang}
  • ' + menu.append $ '
  • {lang}wcf.chat.kick{/lang}
  • ' + menu.append $ '
  • {lang}wcf.chat.ban{/lang}
  • ' + menu.append $ '
  • {lang}wcf.chat.profile{/lang}
  • ' + @events.userMenu.fire user, menu + li.append menu + li.appendTo $ '#chatUserList' + + foundUsers[id] = true + + $('.chatUser').each () -> + if typeof foundUsers[$(this).attr('id')] is 'undefined' + $(this).remove() + + $('#toggleUsers .badge').text(users.length); ### # Inserts text into our input. # @@ -180,25 +251,30 @@ TimWolla.WCF ?= {} # Refreshes the room-list. ### refreshRoomList: () -> + console.log '[TimWolla.WCF.Chat] Refreshing the room-list' $('#toggleRooms a').addClass 'ajaxLoad' $.ajax $('#toggleRooms a').data('refreshUrl'), dataType: 'json' type: 'POST' success: $.proxy((data, textStatus, jqXHR) -> - $('.chatRoom').unbind 'click' $('#chatRoomList li').remove() $('#toggleRooms a').removeClass 'ajaxLoad' + $('#toggleRooms .badge').text(data.length); + for room in data li = $ '
  • ' li.addClass 'activeMenuItem' if room.active $('' + room.title + '').addClass('chatRoom').appendTo li $('#chatRoomList ul').append li + $('.chatRoom').click $.proxy (event) -> return if typeof window.history.replaceState is 'undefined' event.preventDefault() @changeRoom $ event.target , this + + console.log '[TimWolla.WCF.Chat] Found ' + data.length + ' rooms' , this) ### # Handles submitting of messages. @@ -251,12 +327,12 @@ TimWolla.WCF ?= {} # @param jQuery-object target ### toggleUserMenu: (target) -> - liUserID = '#' + target.parent().parent().attr 'id' + li = target.parent() - if $(liUserID).hasClass 'activeMenuItem' - $(liUserID + ' .chatUserMenu').wcfBlindOut 'vertical', () -> - $(liUserID).removeClass 'activeMenuItem' + if li.hasClass 'activeMenuItem' + li.find('.chatUserMenu').wcfBlindOut 'vertical', () -> + li.removeClass 'activeMenuItem' else - $(liUserID).addClass 'activeMenuItem' - $(liUserID + ' .chatUserMenu').wcfBlindIn() + li.addClass 'activeMenuItem' + li.find('.chatUserMenu').wcfBlindIn 'vertical' )(jQuery) diff --git a/file/lib/data/chat/message/ChatMessage.class.php b/file/lib/data/chat/message/ChatMessage.class.php index dfd625d..9de2527 100755 --- a/file/lib/data/chat/message/ChatMessage.class.php +++ b/file/lib/data/chat/message/ChatMessage.class.php @@ -36,9 +36,7 @@ class ChatMessage extends \wcf\data\DatabaseObject { const TYPE_GLOBALMESSAGE = 11; /** - * Returns the message. - * - * @return string + * @see \wcf\data\chat\message\ChatMessage::getFormattedMessage() */ public function __toString() { return $this->getFormattedMessage(); @@ -91,10 +89,11 @@ public function getUsername() { /** * Converts this message into json-form. * + * @param boolean $raw * @return string */ - public function jsonify() { - return \wcf\util\JSON::encode(array( + public function jsonify($raw = false) { + $array = array( 'formattedUsername' => $this->getFormattedUsername(), 'formattedMessage' => (string) $this, 'formattedTime' => \wcf\util\DateUtil::format(\wcf\util\DateUtil::getDateTimeByTimestamp($this->time), 'H:i:s'), @@ -104,6 +103,9 @@ public function jsonify() { 'receiver' => $this->receiver, 'type' => $this->type, 'roomID' => $this->roomID - )); + ); + + if ($raw) return $array; + return \wcf\util\JSON::encode($array); } } diff --git a/file/lib/page/ChatMessagePage.class.php b/file/lib/page/ChatMessagePage.class.php index b3b674d..d7f2fbf 100644 --- a/file/lib/page/ChatMessagePage.class.php +++ b/file/lib/page/ChatMessagePage.class.php @@ -18,24 +18,68 @@ class ChatMessagePage extends AbstractPage { //public $neededPermissions = array('user.chat.canEnter'); public $room = null; public $roomID = 0; + public $users = array(); public $useTemplate = false; /** - * Reads room data. + * @see \wcf\page\Page::readData() */ public function readData() { parent::readData(); + + $this->readRoom(); + $this->readMessages(); + $this->readUsers(); + } + + public function readMessages() { + $this->messages = chat\message\ChatMessageList::getMessagesSince($this->room, \wcf\util\ChatUtil::readUserData('lastSeen')); + + // update last seen message + $sql = "SELECT + max(messageID) as messageID + FROM + wcf".WCF_N."_chat_message"; + $stmt = WCF::getDB()->prepareStatement($sql); + $stmt->execute(); + $row = $stmt->fetchArray(); + \wcf\util\ChatUtil::writeUserData(array('lastSeen' => $row['messageID'])); + } + + public function readRoom() { $this->roomID = \wcf\util\ChatUtil::readUserData('roomID'); $this->room = chat\room\ChatRoom::getCache()->search($this->roomID); if (!$this->room) throw new \wcf\system\exception\IllegalLinkException(); if (!$this->room->canEnter()) throw new \wcf\system\exception\PermissionDeniedException(); + } + + public function readUsers() { + $packageID = \wcf\system\package\PackageDependencyHandler::getPackageID('timwolla.wcf.chat'); - $this->messages = chat\message\ChatMessageList::getMessagesSince($this->room, \wcf\util\ChatUtil::readUserData('lastSeen')); - $stmt = WCF::getDB()->prepareStatement("SELECT max(messageID) as messageID FROM wcf".WCF_N."_chat_message"); + $sql = "SELECT + userID + FROM + wcf".WCF_N."_user_storage + WHERE + field = 'roomID' + AND packageID = ".intval($packageID)." + AND fieldValue = ".intval($this->roomID); + $stmt = WCF::getDB()->prepareStatement($sql); $stmt->execute(); - $row = $stmt->fetchArray(); - \wcf\util\ChatUtil::writeUserData(array('lastSeen' => $row['messageID'])); + while ($row = $stmt->fetchArray()) $userIDs[] = $row['userID']; + + $sql = "SELECT + * + FROM + wcf".WCF_N."_user + WHERE + userID IN (".rtrim(str_repeat('?,', count($userIDs)), ',').") + ORDER BY + username ASC"; + $stmt = WCF::getDB()->prepareStatement($sql); + $stmt->execute($userIDs); + $this->users = $stmt->fetchObjects('\wcf\data\user\User'); } /** @@ -50,11 +94,19 @@ public function show() { parent::show(); @header('Content-type: application/json'); - $result = '['; + $json = array('users' => array(), 'messages' => array()); + foreach ($this->messages as $message) { - $result .= $message->jsonify().','; + $json['messages'][] = $message->jsonify(true); } - echo rtrim($result, ',').']'; + foreach ($this->users as $user) { + $json['users'][] = array( + 'userID' => $user->userID, + 'username' => $user->username + ); + } + + echo \wcf\util\JSON::encode($json); exit; } } diff --git a/file/lib/page/ChatPage.class.php b/file/lib/page/ChatPage.class.php index 53d2a5e..a9eb6ed 100644 --- a/file/lib/page/ChatPage.class.php +++ b/file/lib/page/ChatPage.class.php @@ -66,6 +66,8 @@ public function readData() { $this->readRoom(); $this->userData['color'] = \wcf\util\ChatUtil::readUserData('color'); \wcf\util\ChatUtil::writeUserData(array('roomID' => $this->room->roomID)); + $this->newestMessages = chat\message\ChatMessageList::getNewestMessages($this->room, CHAT_LASTMESSAGES); + \wcf\util\ChatUtil::writeUserData(array('lastSeen' => end($this->newestMessages)->messageID)); if (CHAT_DISPLAY_JOIN_LEAVE) { $messageAction = new chat\message\ChatMessageAction(array(), 'create', array( @@ -82,14 +84,10 @@ public function readData() { )); $messageAction->executeAction(); $return = $messageAction->getReturnValues(); - - \wcf\util\ChatUtil::writeUserData(array('lastSeen' => $return['returnValues'] -> messageID)); } $this->readDefaultSmileys(); $this->readChatVersion(); - - $this->newestMessages = chat\message\ChatMessageList::getNewestMessages($this->room, CHAT_LASTMESSAGES); } /** diff --git a/file/lib/system/menu/page/ChatPageMenuItemProvider.class.php b/file/lib/system/menu/page/ChatPageMenuItemProvider.class.php index 533f707..29e201f 100644 --- a/file/lib/system/menu/page/ChatPageMenuItemProvider.class.php +++ b/file/lib/system/menu/page/ChatPageMenuItemProvider.class.php @@ -29,7 +29,7 @@ public function isVisible() { do { $cache->seek($i++); - $this->room = $cache->search($cache->key()); + $this->room = $cache->current(); } while (!$this->room->canEnter()); diff --git a/file/style/timwolla.wcf.chat.scss b/file/style/timwolla.wcf.chat.scss index a760cb1..d6e457d 100644 --- a/file/style/timwolla.wcf.chat.scss +++ b/file/style/timwolla.wcf.chat.scss @@ -1,20 +1,18 @@ -#chatBox { - padding: 0; - - div { - text-align: center; - } -} +/** + * Chat-Styles + * + * @author Tim Düsterhus, Maximilian Mader + * @copyright 2010-2011 Tim Düsterhus + * @license Creative Commons Attribution-NonCommercial-ShareAlike + * @package timwolla.wcf.chat + */ -#chatBox aside, #chatRoomContent { +#chatRoomContent { text-align: left; } .sidebar { margin-bottom: -20px !important; -} - -aside { overflow: auto; padding: 0; @@ -32,18 +30,8 @@ aside { } } } - - & aside { - padding-right: 1px; - } - - & aside { - padding-left: 1px; - } } - - #topic, #smileyList, #chatOptions { padding: 5px; } @@ -51,6 +39,7 @@ #topic, #smileyList, #chatOptions { .chatMessageContainer { height: 200px; overflow-y: scroll; + overflow-x: hidden; padding-left: 7px !important; } @@ -196,6 +185,18 @@ .chatSidebarTabs { margin-right: -1px; } } + + &.active .badge { + font-size: 65% !important; + color: #fff; + background-color: #369; + + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 1); + -moz-box-shadow: 0 0 1px rgba(255, 255, 255, 1); + -ms-box-shadow: 0 0 1px rgba(255, 255, 255, 1); + -o-box-shadow: 0 0 1px rgba(255, 255, 255, 1); + box-shadow: 0 0 1px rgba(255, 255, 255, 1); + } } } } diff --git a/install.sql b/install.sql index 7086b5d..21aa183 100644 --- a/install.sql +++ b/install.sql @@ -50,4 +50,9 @@ ALTER TABLE wcf1_chat_message ADD FOREIGN KEY (sender) REFERENCES wcf1_user (use ALTER TABLE wcf1_chat_room ADD FOREIGN KEY (owner) REFERENCES wcf1_user (userID) ON DELETE SET NULL; ALTER TABLE wcf1_chat_room_suspension ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE; -ALTER TABLE wcf1_chat_room_suspension ADD FOREIGN KEY (roomID) REFERENCES wcf1_chat_room (roomID) ON DELETE CASCADE; \ No newline at end of file +ALTER TABLE wcf1_chat_room_suspension ADD FOREIGN KEY (roomID) REFERENCES wcf1_chat_room (roomID) ON DELETE CASCADE; + +INSERT INTO wcf1_chat_room (title, topic, position) VALUES ('Testroom 1', 'Topic of Testroom 1', 1); +INSERT INTO wcf1_chat_room (title, topic, position) VALUES ('Testroom 2', 'Topic of Testroom 2', 2); +INSERT INTO wcf1_chat_room (title, topic, position) VALUES ('Testroom with a very long', 'The topic of this room is rather loing as well!', 3); +INSERT INTO wcf1_chat_room (title, topic, position) VALUES ('Room w/o topic', '', 4); diff --git a/package.xml b/package.xml index 0ee7745..6a8f69f 100644 --- a/package.xml +++ b/package.xml @@ -28,9 +28,9 @@ install.sql objectType.xml option.xml - pagemenu.xml + pagemenu.xml eventListener.xml - templatelistener.xml + templatelistener.xml acloptions.xml @@ -40,9 +40,9 @@ template.tar objectType.xml option.xml - pagemenu.xml + pagemenu.xml eventListener.xml - templatelistener.xml + templatelistener.xml acloptions.xml \ No newline at end of file diff --git a/pagemenu.xml b/pagemenu.xml old mode 100644 new mode 100755 index 3ec684d..6f55380 --- a/pagemenu.xml +++ b/pagemenu.xml @@ -1,5 +1,5 @@ - + index.php/Chat diff --git a/template/chat.tpl b/template/chat.tpl index 11f563c..d2d2651 100644 --- a/template/chat.tpl +++ b/template/chat.tpl @@ -76,19 +76,19 @@ {capture assign='sidebar'} -
    +
    {/capture} -{include file='header' sandbox=false sidebarDirection='right'} +{include file='header' sandbox=false sidebarOrientation='right'}
    topic|language === ''} style="display: none;"{/if}>{$room->topic|language}
    @@ -167,7 +167,7 @@ {lang}wcf.chat.mark{/lang} - +
    @@ -184,7 +184,9 @@ animations: {CHAT_ANIMATIONS}, maxTextLength: {CHAT_LENGTH} } + {event name='shouldInit'} TimWolla.WCF.Chat.init(); + {event name='didInit'} TimWolla.WCF.Chat.handleMessages([ {implode from=$newestMessages item='message'} {@$message->jsonify()} diff --git a/template/chatMessage.tpl b/template/chatMessage.tpl index 9abbf4d..466534a 100644 --- a/template/chatMessage.tpl +++ b/template/chatMessage.tpl @@ -1 +1 @@ -{literal} {@$formattedUsername} {@$formattedMessage}{/literal} +{literal}
    {@$formattedUsername}
    {@$formattedMessage}
    {/literal}