diff --git a/README.md b/README.md
index 7b4266c..c3be0e1 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ This is the official plugin repository for [Candy](http://candy-chat.github.com/
* __Inline Videos__ - If a user posts a URL to youtube video, it embeds the youtube video iframe into Candy.
* __join__ A plugin that allows to type `/join room [password]` to join a room.
* __jQuery-Ui__ - jQuery UI lightness theme
+* __Keyboard Shortcuts__ - Gives the user keyboard shortcuts to navigate Candy with.
* __Left Tabs__ - Moves the tabs to the left side and uses a bit of Bootstrap3-friendly theme elements.
* __Modify Role__ - Adds **add moderator** and **remove moderator** context menu links.
* __Namecomplete__ - Autocompletes names of users within room
diff --git a/keyboardshortcuts/LICENSE b/keyboardshortcuts/LICENSE
new file mode 100644
index 0000000..c8d3f9e
--- /dev/null
+++ b/keyboardshortcuts/LICENSE
@@ -0,0 +1,20 @@
+Copyright (C) 2014 Mojo Lingo LLC
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/keyboardshortcuts/README.md b/keyboardshortcuts/README.md
new file mode 100644
index 0000000..5a74745
--- /dev/null
+++ b/keyboardshortcuts/README.md
@@ -0,0 +1,32 @@
+# Keyboard Shortcuts
+
+A plugin for Candy Chat to allow keyboard shortcuts for interacting with Candy.
+
+## Usage
+
+Include the JavaScript file:
+
+```HTML
+
+```
+
+To enable this plugin, add its `init` method after you `init` Candy, but before `Candy.connect()`:
+
+```JavaScript
+CandyShop.KeyboardShortcuts.init();
+```
+
+You could also pass in your own keyboard shortcuts:
+
+```JavaScript
+var options = {
+ uniqueShortcutName: {
+ altKey: (boolean),
+ ctrlKey: (boolean),
+ shiftKey: (boolean),
+ keyCode: (integer)
+ }
+}
+```
+
+The plugin will then match the name of the `uniqueShortcutName` provided as the key to an identically named function inside itself, so you'll need to add a matching function inside the plugin itself.
diff --git a/keyboardshortcuts/keyboardshortcuts.js b/keyboardshortcuts/keyboardshortcuts.js
new file mode 100644
index 0000000..3783a88
--- /dev/null
+++ b/keyboardshortcuts/keyboardshortcuts.js
@@ -0,0 +1,178 @@
+/** File: keyboardshortcuts.js
+ * Candy Plugin Keyboard Shortcuts
+ * Author: Melissa Adamaitis
+ */
+
+var CandyShop = (function(self) { return self; }(CandyShop || {}));
+
+CandyShop.KeyboardShortcuts = (function(self, Candy, $) {
+ var _options = {
+ joinNewRoom: {
+ altKey: true,
+ ctrlKey: false,
+ shiftKey: false,
+ keyCode: 78 // 'n'
+ },
+ toggleSound: {
+ altKey: true,
+ ctrlKey: false,
+ shiftKey: false,
+ keyCode: 83 // 's'
+ },
+ changeTopic: {
+ altKey: true,
+ ctrlKey: false,
+ shiftKey: false,
+ keyCode: 84 // 't'
+ },
+ closeCurrentTab: {
+ altKey: true,
+ ctrlKey: false,
+ shiftKey: false,
+ keyCode: 87 // 'w'
+ },
+ nextTab: {
+ altKey: true,
+ ctrlKey: false,
+ shiftKey: false,
+ keyCode: 40 // down arrow
+ },
+ previousTab: {
+ altKey: true,
+ ctrlKey: false,
+ shiftKey: false,
+ keyCode: 38 // up arrow
+ },
+ helpScreen: {
+ altKey: true,
+ ctrlKey: false,
+ shiftKey: false,
+ keyCode: 191 // '/'
+ }
+ };
+
+ /** 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, _options, options);
+
+ $(window).keydown(function(ev) {
+ var keystrokes = {
+ altKey: ev.altKey,
+ ctrlKey: ev.ctrlKey,
+ shiftKey: ev.shiftKey,
+ keyCode: ev.keyCode
+ };
+ for (var option in _options) {
+ if (self.isEquivalent(_options[option], keystrokes)) {
+ window['CandyShop']['KeyboardShortcuts'][option]();
+ ev.preventDefault();
+ }
+ }
+ });
+ };
+
+ 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
Join new room:
alt/option + n " +
+ "Toggle sound:
alt/option + sChange topic:
alt/option + t " +
+ "Close current tab:
alt/option + wNext tab:
alt/option + down arrow " +
+ "Previous tab:
alt/option + up arrowHelp screen:
alt/option + ?
";
+ Candy.View.Pane.Chat.Modal.show(html, true, false);
+ };
+
+ // Used to find and show room pane relative to current.
+ self.showPane = function(number) {
+ var rooms = Candy.Core.getRooms(),
+ room_names = Object.keys(rooms);
+
+ // Push the lobby to the front of the room_names.
+ room_names.unshift(CandyShop.StaticLobby.getLobbyFakeJid());
+
+ 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(a, b) {
+ // Create arrays of property names
+ var aProps = Object.getOwnPropertyNames(a),
+ bProps = Object.getOwnPropertyNames(b);
+
+ // If number of properties is different, objects are not equivalent
+ if (aProps.length !== bProps.length) {
+ return false;
+ }
+
+ for (var i = 0; i < aProps.length; i++) {
+ var propName = aProps[i];
+
+ // If values of same property are not equal, objects are not equivalent
+ if (a[propName] !== b[propName]) {
+ return false;
+ }
+ }
+
+ // If we made it this far, objects are considered equivalent
+ return true;
+ };
+
+ return self;
+}(CandyShop.KeyboardShortcuts || {}, Candy, jQuery));