From 8d4013cc559c0a98327bdc39f5c11b684ea5ce01 Mon Sep 17 00:00:00 2001 From: Jill Tankersley Date: Sun, 30 Oct 2016 13:16:02 -0400 Subject: [PATCH] Add functionality from a private repo --- favicon/candy.js | 85 ++++++++++ grouped-messages/candy.js | 52 ++++++ keyboardshortcuts/candy.js | 238 +++++++++++++++++++++++++++ keyboardshortcutslefttabs/candy.js | 64 ++++++++ message_decorator/candy.js | 93 +++++++++++ mobiledisplay/candy.js | 253 +++++++++++++++++++++++++++++ muc_room_bar/candy.js | 180 ++++++++++++++++++++ navigable_list/candy.js | 74 +++++++++ roster_cacher/candy.js | 37 +++++ searchroster/candy.js | 92 +++++++++++ welcome/candy.js | 51 ++++++ 11 files changed, 1219 insertions(+) create mode 100644 favicon/candy.js create mode 100644 grouped-messages/candy.js create mode 100644 keyboardshortcuts/candy.js create mode 100644 keyboardshortcutslefttabs/candy.js create mode 100644 message_decorator/candy.js create mode 100644 mobiledisplay/candy.js create mode 100644 muc_room_bar/candy.js create mode 100644 navigable_list/candy.js create mode 100644 roster_cacher/candy.js create mode 100644 searchroster/candy.js create mode 100644 welcome/candy.js diff --git a/favicon/candy.js b/favicon/candy.js new file mode 100644 index 0000000..91aa860 --- /dev/null +++ b/favicon/candy.js @@ -0,0 +1,85 @@ +/** File: favicon.js + * Candy Plugin Favicon Notifier + * Author: John Rose + * + * Show a different favicon when there are unread notifications + */ + + var CandyShop = (function(self) { return self; }(CandyShop || {})); + + CandyShop.Favicon = (function(self, Candy, $) { + + self.about = { + name: 'Candy Favicon Plugin', + version: '1.0' + }; + + self.init = function(){ + self.$favicon = $('link[rel="shortcut icon"]'); + self.standardFaviconUrl = self.$favicon.attr("href"); + self.notificationsFaviconUrl = "/ui/favicons/favicon-notification-pending.ico"; + self.faviconInterval = null; + self.maxBlinks = 5; + self.currentBlinks = 0; + self.faviconBlinkMs = 500; + + // Override the renderUnreadMessages function in candy.bundle.js + Candy.View.Pane.Window.renderUnreadMessages = function(count) { + window.top.document.title = Candy.View.Template.Window.unreadmessages.replace("{{count}}", count).replace("{{title}}", Candy.View.Pane.Window._plainTitle); + if (count === 0) { + CandyShop.Favicon.clearUnreadMessages(); + } else { + CandyShop.Favicon.showNotificationsFavicon(); + CandyShop.Favicon.startFaviconBlink(); + } + }; + + // Override the clearUnreadMessages function in candy.bundle.js + Candy.View.Pane.Window.clearUnreadMessages = function(count) { + Candy.View.Pane.Window._unreadMessagesCount = 0; + window.top.document.title = Candy.View.Pane.Window._plainTitle; + CandyShop.Favicon.clearUnreadMessages(); + }; + }; + + self.clearUnreadMessages = function() { + CandyShop.Favicon.stopFaviconBlink(); + CandyShop.Favicon.showStandardFavicon(); + }; + + self.startFaviconBlink = function() { + var csf = CandyShop.Favicon; + csf.stopFaviconBlink(); // just in case? + csf.currentBlinks = 0; + csf.faviconInterval = window.setInterval(function() { CandyShop.Favicon.faviconBlink(); }, csf.faviconBlinkMs ); + }; + + self.stopFaviconBlink = function() { + window.clearInterval(CandyShop.Favicon.faviconInterval); + }; + + self.showNotificationsFavicon = function() { + CandyShop.Favicon.$favicon.attr("href", CandyShop.Favicon.notificationsFaviconUrl); + }; + + self.showStandardFavicon = function() { + CandyShop.Favicon.$favicon.attr("href", CandyShop.Favicon.standardFaviconUrl); + }; + + self.faviconBlink = function() { + var csf = CandyShop.Favicon; + if (csf.currentBlinks >= csf.maxBlinks) { + csf.showNotificationsFavicon(); + csf.stopFaviconBlink(); + return; + } + if (csf.$favicon.attr("href") === csf.standardFaviconUrl) { + csf.showNotificationsFavicon(); + csf.currentBlinks += 1; + } else { + csf.showStandardFavicon(); + } + }; + + return self; +}(CandyShop.Favicon || {}, Candy, jQuery)); \ No newline at end of file diff --git a/grouped-messages/candy.js b/grouped-messages/candy.js new file mode 100644 index 0000000..a1123bc --- /dev/null +++ b/grouped-messages/candy.js @@ -0,0 +1,52 @@ +/* + * grouped-messages + * @version 1.0 + * @author Ben Klang + * + * Visually group subsequent messages from the same sender + */ + +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +CandyShop.GroupedMessages = (function(self, Candy, $) { + + /** + * groupDelay: Amount of time (ms) after which subsequent messages should be in a different group. Default: 5 minutes (300,000) + */ + var _options = { + groupDelay: 300000, + }; + + /** Function: init + * Initializes the grouped-messages plugin with the default settings. + */ + self.init = function(options) { + // Apply the supplied options to the defaults specified + $.extend(true, _options, options); + + $(Candy).on('candy:view.message.after-show', handleOnShow); + }; + + /** Function: handleOnShow + * Each time a message gets displayed, this method checks for possible + * image loaders (created by buildImageLoaderSource). + * If there is one, the image "behind" the loader gets loaded in the + * background. As soon as the image is loaded, the image loader gets + * replaced by proper scaled image. + * + * Parameters: + * (Array) args + */ + var handleOnShow = function(e, args) { + var sender = args.name; + var prev_message = args.element.prev(); + var prev_sender = prev_message.attr('data-sender-name'); + var prev_timestamp = new Date(prev_message.attr('data-timestamp')); + if (prev_sender === sender && Date.now() - prev_timestamp < _options.groupDelay) { + $(args.element).addClass('grouped'); + $(args.element).prev().addClass('grouped-parent'); + } + }; + + return self; +}(CandyShop.GroupedMessages || {}, Candy, jQuery)); diff --git a/keyboardshortcuts/candy.js b/keyboardshortcuts/candy.js new file mode 100644 index 0000000..f8e7806 --- /dev/null +++ b/keyboardshortcuts/candy.js @@ -0,0 +1,238 @@ +/** File: autojoininvites.js + * Candy Plugin Keyboard Shortcuts + * Author: Melissa Adamaitis + * + * Optionally uses CandyShop.RoomBar for user invite modal. + */ + +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +CandyShop.KeyboardShortcuts = (function(self, Candy, $) { + /** Object: self._options + * Options for this plugin's operation + * + * Options: + * (Boolean) notifyNormalMessage - Notification on normalmessage. Defaults to false + * (Boolean) notifyPersonalMessage - Notification for private messages. Defaults to true + * (Boolean) notifyMention - Notification for mentions. Defaults to true + * (Integer) closeTime - Time until closing the Notification. (0 = Don't close) Defaults to 3000 + * (String) title - Title to be used in notification popup. Set to null to use the contact's name. + * (String) icon - Path to use for image/icon for notification popup. + */ + self._options = { + inviteUserToRoom: { + name: 'Invite a user to the current room', + altKey: true, + ctrlKey: false, + shiftKey: false, + keyCode: 73 // 'i' + }, + joinNewRoom: { + name: 'Join a new room', + altKey: true, + ctrlKey: false, + shiftKey: false, + keyCode: 78 // 'n' + }, + toggleSound: { + name: 'Toggle sound', + altKey: true, + ctrlKey: false, + shiftKey: false, + keyCode: 83 // 's' + }, + changeTopic: { + name: 'Change room topic', + altKey: true, + ctrlKey: false, + shiftKey: false, + keyCode: 84 // 't' + }, + closeCurrentTab: { + name: 'Close current tab', + altKey: true, + ctrlKey: false, + shiftKey: false, + keyCode: 87 // 'w' + }, + nextTab: { + name: 'Next tab', + altKey: true, + ctrlKey: false, + shiftKey: false, + keyCode: 40, // down arrow + specialKeyDisplay: 'Down Arrow' + }, + previousTab: { + name: 'Previous tab', + altKey: true, + ctrlKey: false, + shiftKey: false, + keyCode: 38, // up arrow + specialKeyDisplay: 'Up Arrow' + }, + helpScreen: { + name: 'Show the help screen', + altKey: true, + ctrlKey: false, + shiftKey: false, + keyCode: 191, // '/' + specialKeyDisplay: '/' + } + }; + + /** Object: about + * + * Contains: + * (String) name - Candy Plugin Keyboard Shortcuts + * (Float) version - Candy Plugin Keyboard Shortcuts + */ + self.about = { + name: 'Candy Plugin Keyboard Shortcuts', + version: '1.0' + }; + + /** + * Initializes the KeyboardShortcuts plugin with the default settings. + */ + self.init = function(options){ + // apply the supplied options to the defaults specified + $.extend(true, self._options, options); + + $(window).keydown(function(ev) { + var keystrokes = { + altKey: ev.altKey, + ctrlKey: ev.ctrlKey, + shiftKey: ev.shiftKey, + keyCode: ev.keyCode + }; + for (var option in self._options) { + if (self.isEquivalent(self._options[option], keystrokes)) { + window['CandyShop']['KeyboardShortcuts'][option](); + ev.preventDefault(); + } + }; + }); + }; + + self.addShortcut = function(name, shortcut) { + self._options[name] = shortcut; + } + + self.closeCurrentTab = function() { + Candy.View.Pane.Room.close(Candy.View.getCurrent().roomJid); + }; + + self.joinNewRoom = function() { + CandyShop.CreateRoom.showModal(); + }; + + self.toggleSound = function() { + Candy.View.Pane.Chat.Toolbar.onSoundControlClick(); + }; + + self.changeTopic = function() { + var currentJid = Candy.View.getCurrent().roomJid, + element = Candy.View.Pane.Room.getPane(Candy.View.getCurrent().roomJid), + currentTopic = element.find('.roombar .topic').html(); + CandyShop.RoomBar.updateRoomTopic(currentJid, $(element).attr('id'), currentTopic); + }; + + self.nextTab = function() { + this.showPane('+1'); + }; + + self.previousTab = function() { + this.showPane('-1'); + }; + + self.helpScreen = function() { + var html = '

Keyboard Shortcuts

    '; + + for (var shortcut in self._options) { + shortcut = self._options[shortcut]; + var shortcutString = ''; + + if (shortcut.ctrlKey) { + shortcutString += 'Ctrl + '; + } + + if (shortcut.altKey) { + shortcutString += 'Alt/Option + '; + } + + if (shortcut.shiftKey) { + shortcutString += 'Shift + '; + } + + shortcutString += shortcut.specialKeyDisplay || String.fromCharCode(shortcut.keyCode); + + html += Mustache.to_html(self.Template.listItem, { + name: shortcut.name, + shortcut: shortcutString + }); + + } + + html += '
'; + + Candy.View.Pane.Chat.Modal.show(html, true, false); + }; + + self.inviteUserToRoom = function() { + // Only show the invite modal if we have access to it via CandyShop.RoomBar and if we are in a groupchat. + if (CandyShop.RoomBar && Candy.Core.getRoom(Candy.View.getCurrent().roomJid)) { + CandyShop.RoomBar.showInviteUsersModal(Candy.View.getCurrent().roomJid); + } + }; + + // Used to find and show room pane relative to current. + self.showPane = function(number) { + var rooms = Candy.Core.getRooms(), + room_names = Object.keys(rooms); + + var currentIndex = room_names.indexOf(Candy.View.getCurrent().roomJid), + newIndex = currentIndex; + + if (number === '+1') { + if ((currentIndex + 1) < room_names.length) { + newIndex = currentIndex + 1; + } else { + newIndex = 0; + } + } else if (number === '-1') { + if ((currentIndex - 1) >= 0) { + newIndex = currentIndex - 1; + } else { + newIndex = room_names.length - 1; + } + } else { + newIndex = number; + } + + Candy.View.Pane.Room.show(room_names[newIndex]); + }; + + // Used to help JavaScript determine if two objects are identical. + self.isEquivalent = function(options, incoming) { + // Create arrays of property names + var optionsProps = Object.getOwnPropertyNames(options); + + for (var i = 0; i < optionsProps.length; i++) { + var propName = optionsProps[i]; + + // If values of same property are not equal, objects are not equivalent + if (propName !== 'name' && propName !== 'specialKeyDisplay' && options[propName] !== incoming[propName]) { + return false; + } + } + // If we made it this far, objects are considered equivalent + return true; + }; + + self.Template = { + listItem: '
  • {{name}}
    {{shortcut}}
  • ' + }; + + return self; +}(CandyShop.KeyboardShortcuts || {}, Candy, jQuery)); diff --git a/keyboardshortcutslefttabs/candy.js b/keyboardshortcutslefttabs/candy.js new file mode 100644 index 0000000..0fb76c7 --- /dev/null +++ b/keyboardshortcutslefttabs/candy.js @@ -0,0 +1,64 @@ +/** File: keyboardshortcutslefttabs.js + * Candy Plugin Keyboard Shortcuts Left Tabs Compatibility + * Author: Melissa Adamaitis + */ + +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +CandyShop.KeyboardShortcutsLeftTabs = (function(self, Candy, $) { + /** Object: about + * + * Contains: + * (String) name - Candy Plugin Keyboard Shortcuts Left Tabs Compatibility + * (Float) version - Candy Plugin Keyboard Shortcuts Left Tabs Compatibility + */ + self.about = { + name: 'Candy Plugin Keyboard Shortcuts Left Tabs Compatibility', + version: '1.0' + }; + + /** + * Initializes the KeyboardShortcutsLeftTabs plugin with the default settings. + */ + self.init = function(){ + // Override the default behaviour in CandyShop.KeyboardShortcuts + CandyShop.KeyboardShortcuts.showPane = self.showPane; + }; + + // Used to find and show room pane relative to current. + // Because Candy.Core.getRooms() is a little broken, we need to use the DOM. + self.showPane = function(number) { + // The order of the selector is important here, because groupchats are visually above one on one chats. + var rooms = $('#chat-tabs .roomtype-groupchat, #chat-tabs .roomtype-chat'); + + var currentIndex = 0; + + for (var i = 0; i < rooms.length; i++) { + if ($(rooms[i]).attr('data-roomjid') === Candy.View.getCurrent().roomJid) { + currentIndex = i; + } + } + + var newIndex = currentIndex; + + if (number === '+1') { + if ((currentIndex + 1) < rooms.length) { + newIndex = currentIndex + 1; + } else { + newIndex = 0; + } + } else if (number === '-1') { + if ((currentIndex - 1) >= 0) { + newIndex = currentIndex - 1; + } else { + newIndex = rooms.length - 1; + } + } else { + newIndex = number; + } + + Candy.View.Pane.Room.show($(rooms[newIndex]).attr('data-roomjid')); + }; + + return self; +}(CandyShop.KeyboardShortcutsLeftTabs || {}, Candy, jQuery)); diff --git a/message_decorator/candy.js b/message_decorator/candy.js new file mode 100644 index 0000000..6c9c5ad --- /dev/null +++ b/message_decorator/candy.js @@ -0,0 +1,93 @@ +/** File: messagedecorator.js + * Candy - Chats are not dead yet. + * + * Authors: + * - Ben Klang + * + * Copyright: + * (c) 2015 Power Home Remodeling Group + */ +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +/** Class: CandyShop.MessageDecorator + * Converts certain text markup characters to HTML, such as *bold* and _italic_ + */ +CandyShop.MessageDecorator = (function(self, Candy, $) { + + self.init = function() { + $(Candy).on('candy:view.message.before-render', function(e, args) { + try { + args.templateData.message = self.markup(args.templateData.message); + } catch(ex) { + Candy.Core.error('[CandyShop MessageDecorator]', ex); + } + }); + }; + + self.markup = function(message) { + // Syntax highlighting: + // `foo` => foo + // *foo* => foo + // _foo_ => foo + // Rules: + // 1. It must work with more than one match is found on a line + // 2. It must ignore bold/italic within a code segment: `*bar*` would become *bar* + // 3. It is acceptable to not work if the markup character is not after a whitespace: foo`bar` would not be marked up + // Test cases: + // *bold* + // _italic_ + // `code` + // *bold* *bold* _italic_ _italic_ `code` `code` + // *bold* _italic_ `code` *bold* _italic_ `code` + // *bold* _italic_ `code` _italic_ *bold* + // + // The following should not apply markup within the code segment: + // *bold* `code *not bolded* _not italicized_ code` _italic_ + // + // The following should not be changed + // * Foo + // # Foo + // + // The following should not be marked up at all: + // * not bold * and more * not bolded * words + + // Keep track of code segments with placeholders so we can avoid undesired + // bold/italics within them + var codeSegments = []; + message = message.replace(/(\s|^)`([^`\s][^`]+)`/g, function(match, leadingSpace, code, offset) { + codeSegments.push(code); + return leadingSpace + "\0"; + }); + + message = applyBold(message); + message = applyItalic(message); + + // With the code sections safely extracted to codeSegments, + // apply bold/italic markup on remaining content + message = applyBold(message); + message = applyItalic(message); + + // Now put the code segments back in their placeholders + message = message.replace(/\0/g, function() { + return '' + codeSegments.shift() + ''; + }); + return message; + + }; + + function applyBold(message) { + return applyStyle(message, '*', 'strong') + } + + function applyItalic(message) { + return applyStyle(message, '_', 'em') + } + + function applyStyle(message, delimiter, tag) { + delimiter = RegExp.quote(delimiter); + regex = new RegExp('(\\s|^)' + delimiter + '([^' + delimiter + '\\s][^' + delimiter + ']+)' + delimiter, 'g'); + return message.replace(regex, '$1<' + tag + '>$2'); + } + + return self; +}(CandyShop.MessageDecorator || {}, Candy, jQuery)); diff --git a/mobiledisplay/candy.js b/mobiledisplay/candy.js new file mode 100644 index 0000000..172b7fe --- /dev/null +++ b/mobiledisplay/candy.js @@ -0,0 +1,253 @@ +/** File: mobiledisplay.js + * Candy Plugin Mobile Display + * Author: Melissa Adamaitis + */ + +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +CandyShop.MobileDisplay = (function(self, Candy, $) { + + self._options = { + smallnessThreshold: 620 + }; + + /** Object: about + * + * Contains: + * (String) name - Candy Plugin Auto-Join Incoming MUC Invites + * (Float) version - Candy Plugin Auto-Join Incoming MUC Invites + */ + self.about = { + name: 'Candy Plugin Auto-Join Incoming MUC Invites', + version: '1.0' + }; + + /** + * Initializes the MobileDisplay plugin with the default settings. + */ + self.init = function(options) { + // Apply the supplied options to the defaults specified + $.extend(true, self._options, options); + + self.setup(); + self.resizeHandler(); + + $(Candy).on('candy:view.connection.status-' + Strophe.Status.CONNECTED, self.setup); + $(Candy).on('candy:view.connection.status-' + Strophe.Status.ATTACHED, self.setup); + + $(Candy).on('candy:view.message.after-show', self.handleNotifications); + + // Listen to the size of the window. + $(window).resize($.debounce(250, self.resizeHandler)); + + // Listen for the name of the room to keep it current. + $(Candy).on('candy:view.room.after-show', self.resizeHandler); + $(Candy).on('candy:view.private-room.after-open', self.resizeHandler); + + // Clear mention notifications on window focus. + $(window).focus(self.clearNotifications); + }; + + self.setup = function() { + // Add bar to dom. + if ($('#mobile-top-bar').length === 0) { + $('#header.connect').append(self.Template.chatbar); + } + self.initPikabu(); + }; + + self.initPikabu = function() { + if (typeof(self.pikabu) === 'undefined') { + self.pikabu = new Pikabu({ + viewportSelector: '.body-connect', + widths: { + left: '90%', + right: '90%' + }, + selectors: { + element: '#candy' + }, + onOpened: function() { + $('#mobile-top-bar').css({zIndex: 0}); + }, + onClosed: function() { + $('#mobile-top-bar').css({zIndex: 1}); + } + }); + } + } + + self.resizeHandler = function() { + self.clearNotifications(); + if ($(window).width() < self._options.smallnessThreshold) { + Candy.Core.log('[CandyShop MobileDisplay] switching to mobile view.'); + self.setupDrawers(); + self.setRightDrawer(); + + $('#candy').addClass('mobile-respond-small'); + + $('.room-pane > div:first-of-type').addClass('full-width'); + $('#chat-rooms-wrapper').addClass('full-width'); + + // Show the mobile top bar. + $('#mobile-top-bar').show(); + + // On screen small, attach input bar to bottom of screen. + $('.message-form-wrapper').css({ + position: 'fixed', + bottom: '0' + }); + + // On screen small, hide the toolbar and side panels. + $('#chat-toolbar').hide(); + $('#chat-modal-overlay').hide(); + $('.roombar,.individual_roombar').hide(); + + // Set sidebar content. + $('#left-menu-wrapper').addClass('m-pikabu-sidebar m-pikabu-left'); + $('.m-pikabu-left').css('top', $('#header.connect').height()); + + // Add close pane buttons to sidebar content. + if ($('#left-menu-wrapper .menu-main-head a.m-pikabu-nav-toggle').length === 0) { + $('#left-menu-wrapper .menu-main-head').prepend(Mustache.to_html(self.Template.close, { position: 'left' })); + } + + // Add click listeners to sidebar content to close drawer. + $('#left-menu-wrapper .menu-main-head a.m-pikabu-nav-toggle').click(function() { + self.pikabu.closeSidebars(); + $('#mobile-top-bar').css({zIndex: 1}); + }); + } else { + Candy.Core.log('[CandyShop MobileDisplay] switching to desktop view.'); + $('#candy').removeClass('mobile-respond-small'); + $('.room-pane > div:first-of-type').removeClass('full-width'); + $('#chat-rooms-wrapper').removeClass('full-width'); + + // Hide the mobile top bar. + $('#mobile-top-bar').hide(); + + // On screen big, let the input bar do whatever it wants. + $('.message-form-wrapper').css({ + position: 'initial', + bottom: 'initial' + }); + + // Unset sidebar content. + $('.m-pikabu-left').css('top', 0); + $('#left-menu-wrapper').removeClass('m-pikabu-sidebar m-pikabu-left'); + $('.roster-wrapper').removeClass('m-pikabu-sidebar m-pikabu-right'); + + // On screen big, show the toolbar and side panels. + $('#chat-toolbar').show(); + $('.roster-wrapper').show(); + $('#left-menu-wrapper').show(); + $('.room-pane:visible .roombar,.room-pane:visible .individual_roombar').show(); + + // Remove close pane buttons from sidebar content. + $('#left-menu-wrapper .menu-main-head a.m-pikabu-nav-toggle').remove(); + $('.roster-wrapper .pane-heading a.m-pikabu-nav-toggle').remove(); + } + CandyShop.LeftTabs.adjustTextareaWidth(); + CandyShop.LeftTabs.resetHeight(); + CandyShop.LeftTabs.scheduleResetHeight(400); + }; + + self.setRightDrawer = function() { + $('.roster-wrapper').filter(':hidden').removeClass('m-pikabu-sidebar m-pikabu-right'); + $('.roster-wrapper').filter(':visible').addClass('m-pikabu-sidebar m-pikabu-right'); + $('.roster-wrapper a.m-pikabu-nav-toggle').remove(); + if ($('.roster-wrapper:visible .pane-heading a.m-pikabu-nav-toggle').length === 0) { + $('.roster-wrapper:visible .pane-heading').append(Mustache.to_html(self.Template.close, { position: 'right' })); + $('.roster-wrapper:visible .pane-heading a.m-pikabu-nav-toggle').click(function() { + self.pikabu.closeSidebars(); + }); + } + }; + + self.setupDrawers = function() { + self.pikabu.closeSidebars(); + if (Candy.View.getCurrent().roomJid) { + var roomName; + // If it's a one-on-one handle the name differently. + if ($('.room-pane.roomtype-chat:visible').length === 0) { + roomName = CandyShop.LeftPaneHead.getRoomName(Candy.View.getCurrent().roomJid); + } else { + if (Candy.Core.getRoster().get(Candy.View.getCurrent().roomJid)) { + roomName = Candy.Core.getRoster().get(Candy.View.getCurrent().roomJid).getNick(); + } else { + roomName = Candy.View.Pane.Chat.getTab(Candy.View.getCurrent().roomJid).find('.room-label').html(); + } + } + $('#mobile-top-bar .mobile-top-bar-roomname').html(roomName); + // FIXME: this CSS needs to be fixed so that we don't need to add an invisible space just to line up nicely. + // see also: mucroombar.js + var roomTopic = $('.room-pane:visible .roombar .topic').html() || " "; + $('#mobile-top-bar .mobile-top-bar-roomtopic').html(roomTopic); + } + }; + + self.handleNotifications = function(e, args) { + if ($('small.unread:visible').length > 0 && args.message) { + var $button = $('#mobile-top-bar a.toggle-mobile-chat-drawer[data-role="left"] button'); + + if (self.mentionsMe(args.message) || $button.hasClass('direct-mentions')) { + $button.addClass('direct-mentions'); + } else { + $button.addClass('mentions'); + } + + // Pulse the color. + if ($button.hasClass('pulse-notification') || $button.hasClass('pulse-notification-dm')) { + $button.removeClass('pulse-notification pulse-notification-dm'); + // Use a settimeout to allow "ample" time to go on before reapplying the pulse class so that it reanimates. + setTimeout(function() { + if (self.mentionsMe(args.message)) { + $button.addClass('pulse-notification-dm'); + } else { + $button.addClass('pulse-notification'); + } + }, 50); + } else { + if (self.mentionsMe(args.message)) { + $button.addClass('pulse-notification-dm'); + } else { + $button.addClass('pulse-notification'); + } + } + } + }; + + self.clearNotifications = function() { + if ($('small.unread:visible').length === 0) { + $('a.toggle-mobile-chat-drawer[data-role="left"] button').removeClass('pulse-notification mentions direct-mentions'); + } + }; + + self.mentionsMe = function( message ) { + message = message.toLowerCase(); + var nick = Candy.Core.getUser().getNick().toLowerCase(); + var cid = Strophe.getNodeFromJid(Candy.Core.getUser().getJid()).toLowerCase(); + var jid = Candy.Core.getUser().getJid().toLowerCase(); + + if (message.indexOf(nick) === -1 && + message.indexOf(cid) === -1 && + message.indexOf(jid) === -1) { + return false; + } + + return true; + }; + + self.Template = { + chatbar: "", + close: "" + }; + + return self; +}(CandyShop.MobileDisplay || {}, Candy, jQuery)); diff --git a/muc_room_bar/candy.js b/muc_room_bar/candy.js new file mode 100644 index 0000000..51b1a0f --- /dev/null +++ b/muc_room_bar/candy.js @@ -0,0 +1,180 @@ +/** File: mucroombar.js + * Candy Plugin Add MUC Management Bar + * Author: Melissa Adamaitis + * + * Compatible with CandyShop.MobileDisplay + */ + +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +CandyShop.RoomBar = (function(self, Candy, $) { + /** Object: about + * + * Contains: + * (String) name - Candy Plugin Add MUC Management Bar + * (Float) version - Candy Plugin Add MUC Management Bar + */ + self.about = { + name: 'Candy Plugin Add MUC Management Bar', + version: '1.0' + }; + + self._options = { + clickToSetTopicMessage: 'Click here to set the topic.', + noTopicSetMessage: 'No topic set.' + }; + + /** + * Initializes the RoomBar plugin with the default settings. + */ + self.init = function(options){ + // apply the supplied options to the defaults specified + $.extend(true, self._options, options); + + // Add a room bar when the room is first created. + $(Candy).on('candy:view.room.after-add', function(ev, obj) { + CandyShop.RoomBar.addRoomBar(obj); + CandyShop.RoomBar.appendInviteUsersButton(obj.roomJid); + return undefined; + }); + + // Change the topic in the roombar when it is changed. + $(Candy).on('candy:view.room.after-subject-change', function(ev, obj) { + Candy.Core.log('[CandyShop RoomBar] recieved subject change for ' + obj.roomJid + ' to "' + obj.subject + '".'); + CandyShop.RoomBar.showTopic(obj.roomJid, obj.subject, obj.element); + }); + + // Remove the now-useless "Change Subject" menu item + $(Candy).on('candy:view.roster.context-menu', function (ev, obj) { + delete obj.menulinks.subject; + }); + }; + + self.addRoomBar = function(obj){ + if (obj.element.hasClass('roomtype-groupchat') || obj.element.hasClass('roomtype-chat')) { + var roombarHtml; + if (obj.element.hasClass('roomtype-chat')) { + // one-on-one chat + roombarHtml = self.Template.individual_roombar; + } else { + // group chat + roombarHtml = self.Template.roombar; + } + obj.element.find('.message-pane-wrapper').prepend(roombarHtml + self.Template.historyloaded); + if (typeof CandyShop.LeftPaneHead === 'object') { + obj.element.find('.message-pane-wrapper .roombar .roomname').html(CandyShop.LeftPaneHead.getRoomName(obj.roomJid)) + } else { + var roomName = $('#tab-'+obj.element[0].id+' .room-label').html(); + obj.element.find('.message-pane-wrapper .roombar .roomname').html(roomName); + } + } + + var element = '#' + obj.element.attr('id'); + self.showTopic(obj.roomJid, undefined, element); + + $(element + ' .message-pane-wrapper .roombar .topic').click(function() { + var _this = $(this); + if (_this.has('input').length === 0) { + self.updateRoomTopic(obj.roomJid, obj.element.attr('id'), _this.html()); + } + }); + }; + + self.showTopic = function(roomJid, topic, element) { + var isModerator = Candy.Core.getRoom(roomJid) !== null && Candy.Core.getRoom(roomJid).user !== null && Candy.Core.getRoom(roomJid).user.getRole() === 'moderator' ? true : false; + + if (!topic || topic.trim() === '') { + topic = isModerator ? self._options.clickToSetTopicMessage : self._options.noTopicSetMessage; + $(element).find('.message-pane-wrapper .roombar .topic').addClass('no-topic-set'); + } + + if (isModerator) { + $(element).find('.message-pane-wrapper .roombar .topic').addClass('is-moderator'); + } + + $(element).find('.message-pane-wrapper .roombar .topic').html(topic); + + // FIXME: this CSS needs to be fixed so that we don't need to add an invisible space just to line up nicely. + // see also: mobiledisplay.js + if (CandyShop.MobileDisplay) { + topic = topic || ' '; + $('#mobile-top-bar .mobile-top-bar-roomtopic').html(topic); + } + }; + + self.updateRoomTopic = function(roomJid, elementId, currentTopic) { + // If we're a room moderator, be able to edit the room topic. + if (Candy.Core.getRoom(roomJid) !== null && Candy.Core.getRoom(roomJid).user !== null && Candy.Core.getRoom(roomJid).user.getRole() === 'moderator') { + // If there isn't an active input for room topic already, create input interface. + if ($('#' + elementId + '.message-pane-wrapper .roombar .topic input').length === 0) { + // Replace topic with an input field + if (currentTopic.trim() === '' || currentTopic === self._options.clickToSetTopicMessage) { currentTopic = ''; } + var field_html = ''; + $('#' + elementId + ' .message-pane-wrapper .roombar .topic').html(field_html); + // Add focus to the new element. + var input = $('#' + elementId + ' .message-pane-wrapper .roombar .topic input'); + input.focus(); + input.select(); + // Set listener for on return press or lose focus. + input.blur(function() { + if(currentTopic !== $(this).val()) { + CandyShop.RoomBar.sendNewTopic(roomJid, $(this).val()); + } else { + $('#' + elementId + ' .message-pane-wrapper .roombar .topic').html(currentTopic); + } + }); + input.keydown(function(ev) { + var keycode = (ev.keyCode ? ev.keyCode : ev.which); + switch(keycode) { + case 13: //enter key + if(currentTopic !== $(this).val()) { + CandyShop.RoomBar.sendNewTopic(roomJid, $(this).val()); + } else { + $('#' + elementId + ' .message-pane-wrapper .roombar .topic').html(currentTopic); + } + break; + case 27: // escape key + $('#' + elementId + ' .message-pane-wrapper .roombar .topic').html(currentTopic); + break; + } + }); + } + } + }; + + self.appendInviteUsersButton = function(roomJid) { + var pane_heading = $('#chat-rooms > div.roomtype-groupchat[data-roomjid="' + roomJid + '"] .roster-wrapper .pane-heading'); + if ($(pane_heading).find('.invite-users').length === 0) { + var html = self.Template.invite_button; + $(pane_heading).append(html); + $(pane_heading).find('.invite-users').click(function() { + CandyShop.UserSearch.showUserSearchModal(roomJid); + }); + } + }; + + // Display the set topic modal and add submit handler. + self.sendNewTopic = function(roomJid, topic) { + if (topic === '') { + topic = ' '; + } + + Candy.Core.log('[CandyShop RoomBar] sending new topic of "' + topic + '" for ' + roomJid); + // Even though it does the exact same thing, Candy.View.Pane.Room.setSubject(roomJid, topic) was not sending the stanza out. + Candy.Core.getConnection().muc.setTopic(Candy.Util.escapeJid(roomJid), topic); + }; + + self.Template = { + roombar: '
    ' + + '
    ' + + '
    ' + + '
    ', + individual_roombar: '
    ' + + '
    ' + + '
    ', + historyloaded: '
    Your history was successfully loaded
    ', + invite_button: '' + }; + + return self; +}(CandyShop.RoomBar || {}, Candy, jQuery)); diff --git a/navigable_list/candy.js b/navigable_list/candy.js new file mode 100644 index 0000000..b3ec893 --- /dev/null +++ b/navigable_list/candy.js @@ -0,0 +1,74 @@ +/** navigable_list.js + * Plugin to nagivate through items on list with arrow keys + * Author: Rafael Macedo + */ + +var NavigableList = (function() { + function NavigableList(container, list) { + this.container = container; + this.list = list; + this.index = -1; + }; + + NavigableList.init = function(container, list) { + var instance = new this(container, list); + instance.init(); + return instance; + }; + + NavigableList.prototype.init = function() { + this.addEventListeners(); + }; + + NavigableList.prototype.reset = function(list) { + this.list.removeClass('current'); + this.list = list; + this.index = -1; + }; + + NavigableList.prototype.addEventListeners = function() { + this.container.on("keyup", $.proxy(function(e) { + var key = e.keyCode || e.which; + if (key === 40) { + this.next(function(navigableList) { + navigableList.highlightCurrent(); + }); + } else if (key === 38) { + this.prev(function(navigableList) { + navigableList.highlightCurrent() + }); + } + }, this)); + }; + + NavigableList.prototype.current = function() { + return $(this.list[this.index]); + }; + + NavigableList.prototype.highlightCurrent = function() { + this.list.removeClass('current'); + this.current().addClass('current').focus(); + }; + + NavigableList.prototype.next = function(callback) { + if (this.index >= this.list.length - 1) { + this.index = 0; + } else { + this.index++; + } + + callback(this); + }; + + NavigableList.prototype.prev = function(callback) { + if (this.index === 0) { + this.index = this.list.length - 1; + } else { + this.index--; + } + + callback(this); + }; + + return NavigableList; +})(); diff --git a/roster_cacher/candy.js b/roster_cacher/candy.js new file mode 100644 index 0000000..e3589f8 --- /dev/null +++ b/roster_cacher/candy.js @@ -0,0 +1,37 @@ +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +CandyShop.RosterCacher = (function(self, Candy, $) { + self.init = function(){ + var debouncedRosterCacher = $.debounce(1000, self.cacheRoster); + + $(Candy).on('candy:core:roster:fetched', debouncedRosterCacher) + .on('candy:core:roster:added', debouncedRosterCacher) + .on('candy:core:roster:updated', debouncedRosterCacher) + .on('candy:core:roster:removed', debouncedRosterCacher); + }; + + self.getCachedRoster = function() { + if (localStorage.roster_items) { + try { + Candy.Core.log('[CandyShop RosterCacher] Returning JSON parse of local storage roster items.'); + return JSON.parse(localStorage.roster_items); + } catch (ex) { + Candy.Core.warn('[CandyShop RosterCacher] Unable to load the stored roster; fetching from the server instead.'); + return []; + } + } else { + return []; + } + }; + + self.cacheRoster = function() { + // Store current roster version in localStorage + var roster = Candy.Core.getConnection().roster; + localStorage.roster_items = JSON.stringify(roster.items); + if (roster.ver) { + localStorage.roster_version = roster.ver; + } + }; + + return self; +}(CandyShop.RosterCacher || {}, Candy, jQuery)); diff --git a/searchroster/candy.js b/searchroster/candy.js new file mode 100644 index 0000000..6c3f217 --- /dev/null +++ b/searchroster/candy.js @@ -0,0 +1,92 @@ +/** File: searchroster.js + * Search Roster - Search people and rooms. + * + * Authors: + * - Jason Deegan + * + * Copyright: + * (c) 2014 Power Home Remodeling Group. All rights reserved. + */ +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +/** Class: CandyShop.SearchRoster + * Allows for completion of a name in the roster + */ +CandyShop.SearchRoster = (function(self, Candy, $) { + /** String: _selector + * The selector for the visible message box + */ + var _selector = '#search-roster-value'; + + /** String: _clearbtn + * The button for clearing the search input box + */ + var _clearbtn= '#search-roster-value-btn'; + + /** Boolan: isSearching + * Keeping track of whether user is in the middle of searching. Relevant for hide/show of full roster. + */ + var isSearching = false; + + /** Booleans: _searchtemp_displayOfflineUsers, _searchtemp_displayOfflineUsersOverride + * Keeping track of whether user is in the middle of searching and whether the displayOfflineUsers option has been selected. + */ + var _searchtemp_displayOfflineUsers = true; + var _searchtemp_displayOfflineUsersOverride = false; + + /** Function: init + * Initialize the SearchRoster plugin + * + * Parameters: + * None + */ + self.init = function() { + // initialize tooltip help + $(_selector).tooltip(); + + // listen for keyup when someone wants to search + $(document).on('keyup', _selector, function() { + self.searchRoster(); + }); + + // on search 'Clear' button press do: Show all users, clear search field and give search field focus + $(document).on('click', _clearbtn, function() { + $(this).closest('div').find( 'input' ).focus(); + self.stopSearching(); + }); + }; + + self.searchRoster = function(){ + var searchCriteria = $(_selector).val().toLowerCase(); + + if (searchCriteria === '') { + self.stopSearching(); + return; + } else { + CandyShop.SearchRoster.isSearching = true; + if (CandyShop.SearchRoster._searchtemp_displayOfflineUsersOverride !== true) { + CandyShop.SearchRoster._searchtemp_displayOfflineUsers = true; + } + } + + $('#chat-modal .user').hide(); + + $('#chat-modal .user').filter(function(i,el) { + if (CandyShop.SearchRoster._searchtemp_displayOfflineUsers === true) { + return $(el).data('nick').toLowerCase().indexOf(searchCriteria) >= 0 || $(el).data('jid').toLowerCase().indexOf(searchCriteria) >= 0; + } else { + return ($(el).data('nick').toLowerCase().indexOf(searchCriteria) >= 0 || $(el).data('jid').toLowerCase().indexOf(searchCriteria) >= 0) && $(el).data('status') === 'available'; + } + }).show(); + }; + + self.stopSearching = function(){ + // Clear search input and reset Defaults + $('#search-roster-value').val(''); + CandyShop.SearchRoster.isSearching = false; + CandyShop.SearchRoster._searchtemp_displayOfflineUsers = true; + CandyShop.SearchRoster._searchtemp_displayOfflineUsersOverride = false; + }; + + return self; +}(CandyShop.SearchRoster || {}, Candy, jQuery)); diff --git a/welcome/candy.js b/welcome/candy.js new file mode 100644 index 0000000..c0c0ad4 --- /dev/null +++ b/welcome/candy.js @@ -0,0 +1,51 @@ +/** File: welcome.js + * Candy Plugin Welcome Screen + * Author: Melissa Adamaitis + */ + +var CandyShop = (function(self) { return self; }(CandyShop || {})); + +CandyShop.Welcome = (function(self, Candy, $) { + /** Object: about + * + * Contains: + * (String) name - Candy Plugin Welcome Screen + * (Float) version - Candy Plugin Welcome Screen + */ + self.about = { + name: 'Candy Plugin Welcome Screen', + version: '1.0' + }; + + /** + * Initializes the Welcome plugin with the default settings. + */ + self.init = function(){ + $(Candy).on('candy:view.room.after-show', self.setTitle); + + self.showWelcome(); + + $(Candy).on('candy:view.room.after-close', self.showWelcome); + $(Candy).on('candy:view.room.after-show', self.showWelcome); + }; + + self.showWelcome = function(){ + // Show the welcome info again. + if ($('#chat-rooms .room-pane:visible').length === 0) { + if ($('#chat-rooms #welcome-pane').length === 0) { + $('#chat-rooms').prepend(self.Template.welcome); + } + } else { + $('#welcome-pane').remove(); + } + return true; + }; + + self.Template = { + welcome: '

    Welcome!

    Click on More on the left to add a room or start a chat with another user.

    ' + }; + + return self; +}(CandyShop.Welcome || {}, Candy, jQuery)); + +