diff --git a/Gruntfile.js b/Gruntfile.js index 828da36..9560f52 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,4 +1,5 @@ module.exports = function(grunt) { + var components = ""; var files = [ "./lib/**/*.js", "./client/js/shout.js" @@ -14,7 +15,10 @@ module.exports = function(grunt) { uglify: { js: { files: { - "client/js/components.min.js": ["client/components/*.js"] + "client/js/components.min.js": [ + "client/components/*.js", + "client/components/jquery/*.js" + ] } } } diff --git a/client/components/jquery/tabcomplete.js b/client/components/jquery/tabcomplete.js new file mode 100644 index 0000000..af03b9d --- /dev/null +++ b/client/components/jquery/tabcomplete.js @@ -0,0 +1,256 @@ +/*! + * tabcomplete + * http://github.com/erming/tabcomplete + * v1.3.1 + */ +(function($) { + var keys = { + backspace: 8, + tab: 9, + up: 38, + down: 40 + }; + + $.tabcomplete = {}; + $.tabcomplete.defaultOptions = { + after: "", + arrowKeys: false, + caseSensitive: false, + hint: "placeholder", + minLength: 1 + }; + + $.fn.tab = // Alias + $.fn.tabcomplete = function(args, options) { + if (this.length > 1) { + return this.each(function() { + $(this).tabcomplete(args, options); + }); + } + + // Only enable the plugin on and elements. + var tag = this.prop("tagName"); + if (tag != "INPUT" && tag != "TEXTAREA") { + return; + } + + // Set default options. + options = $.extend( + $.tabcomplete.defaultOptions, + options + ); + + // Remove any leftovers. + // This allows us to override the plugin if necessary. + this.unbind(".tabcomplete"); + this.prev(".hint").remove(); + + var self = this; + var backspace = false; + var i = -1; + var words = []; + var last = ""; + + var hint = $.noop; + + // Determine what type of hinting to use. + switch (options.hint) { + case "placeholder": + hint = placeholder; + break; + + case "select": + hint = select; + break; + } + + this.on("input.tabcomplete", function() { + var input = self.val(); + var word = input.split(/ |\n/).pop(); + + // Reset iteration. + i = -1; + last = ""; + words = []; + + // Check for matches if the current word is the last word. + if (self[0].selectionStart == input.length + && word.length) { + if (typeof args === "function") { + // If the user supplies a function, invoke it + // and keep the result. + words = args(word); + } else { + // Otherwise, call the .match() function. + words = match(word, args, options.caseSensitive); + } + + // Append 'after' to each word. + if (options.after) { + words = $.map(words, function(w) { return w + options.after; }); + } + } + + // Emit the number of matching words with the 'match' event. + self.trigger("match", words.length); + + if (options.hint) { + if (!(options.hint == "select" && backspace) && word.length >= options.minLength) { + // Show hint. + hint.call(self, words[0]); + } else { + // Clear hinting. + // This call is needed when using backspace. + hint.call(self, ""); + } + } + + if (backspace) { + backspace = false; + } + }); + + this.on("keydown.tabcomplete", function(e) { + var key = e.which; + if (key == keys.tab + || (options.arrowKeys && (key == keys.up || key == keys.down))) { + + // Don't lose focus on tab click. + e.preventDefault(); + + // Iterate the matches with tab and the up and down keys by incrementing + // or decrementing the 'i' variable. + if (key != keys.up) { + i++; + } else { + if (i == -1) return; + if (i == 0) { + // Jump to the last word. + i = words.length - 1; + } else { + i--; + } + } + + // Get next match. + var word = words[i % words.length]; + if (!word) { + return; + } + + var value = self.val(); + last = last || value.split(/ |\n/).pop(); + + // Return if the 'minLength' requirement isn't met. + if (last.length < options.minLength) { + return; + } + + // Update element with the completed text. + var text = value.substr(0, self[0].selectionStart - last.length) + word; + self.val(text); + + // Put the cursor at the end after completion. + // This isn't strictly necessary, but solves an issue with + // Internet Explorer. + if (options.hint == "select") { + self[0].selectionStart = text.length; + } + + // Remember the word until next time. + last = word; + + // Emit event. + self.trigger("tabcomplete", last); + + if (options.hint) { + // Turn off any additional hinting. + hint.call(self, ""); + } + } else if (e.which == keys.backspace) { + // Remember that backspace was pressed. This is used + // by the 'input' event. + backspace = true; + + // Reset iteration. + i = -1; + last = ""; + } + }); + + if (options.hint) { + // If enabled, turn on hinting. + hint.call(this, ""); + } + + return this; + } + + // Simple matching. + // Filter the array and return the items that begins with 'word'. + function match(word, array, caseSensitive) { + return $.grep( + array, + function(w) { + if (caseSensitive) { + return !w.indexOf(word); + } else { + return !w.toLowerCase().indexOf(word.toLowerCase()); + } + } + ); + } + + // Show placeholder text. + // This works by creating a copy of the input and placing it behind + // the real input. + function placeholder(word) { + var input = this; + var clone = input.prev(".hint"); + + input.css({ + backgroundColor: "transparent", + position: "relative", + }); + + // Lets create a clone of the input if it does + // not already exist. + if (!clone.length) { + input.wrap( + $("").css({position: "relative", height: input.css("height")}) + ); + clone = input + .clone() + .attr("tabindex", -1) + .removeAttr("id name placeholder") + .addClass("hint") + .insertBefore(input); + clone.css({ + position: "absolute", + }); + } + + var hint = ""; + if (typeof word !== "undefined") { + var value = input.val(); + hint = value + word.substr(value.split(/ |\n/).pop().length); + } + + clone.val(hint); + } + + // Hint by selecting part of the suggested word. + function select(word) { + var input = this; + var value = input.val(); + if (word) { + input.val( + value + + word.substr(value.split(/ |\n/).pop().length) + ); + + // Select hint. + input[0].selectionStart = value.length; + } + } +})(jQuery); diff --git a/client/css/style.css b/client/css/style.css index 19f483f..c57572a 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -61,14 +61,14 @@ button { background: #323841; color: #fff; } -#channels { +#networks { min-height: 100%; padding: 30px 40px 80px; } -#channels .network + .network { +#networks .network + .network { margin-top: 30px; } -#channels .chan { +#networks .chan { display: block; margin: 1px -10px; padding: 6px 10px 8px; @@ -77,12 +77,12 @@ button { transition: all .2s; width: 160px; } -#channels .chan:first-child { +#networks .chan:first-child { color: #84d1ff; font-size: 15px; font-weight: bold; } -#channels .badge { +#networks .badge { background: rgba(255, 255, 255, .06); border-radius: 3px; color: #afb6c0; @@ -93,6 +93,9 @@ button { right: 10px; transition: all .1s; } +#networks .badge:empty { + display: none; +} #footer { height: 80px; line-height: 80px; @@ -163,17 +166,10 @@ button { position: relative; width: 100%; } -#chat form { - bottom: 0; - height: 40px; - left: 0; - position: absolute; - right: 180px; -} #chat button:hover { opacity: .6; } -#chat .chat { +#chat .window { bottom: 40px; left: 0; position: absolute; @@ -220,17 +216,30 @@ button { } #messages .from { background: #f9f9f9; - color: #33b0f7; + color: #ddd; padding-right: 10px; text-align: right; width: 134px; } +#messages .from button { + color: #33b0f7; +} #messages .text { padding-left: 10px; padding-right: 6px; } #messages .type { color: #ccc; + display: none; +} +#messages .join .type, +#messages .part .type, +#messages .mode .type, +#messages .nick .type, +#messages .kick .type, +#messages .quit .type, +#messages .quit .type { + display: inline; } #meta { border-bottom: 1px solid #e9ecef; @@ -244,6 +253,9 @@ button { #meta .count { color: #ccc; } +#meta .type { + text-transform: capitalize; +} #users { bottom: 0; overflow: auto; @@ -256,7 +268,14 @@ button { display: block; line-height: 1.6em; } -#input { +#form { + bottom: 0; + height: 40px; + left: 0; + position: absolute; + right: 180px; +} +#form input { border: 0; border-top: 1px solid #e9ecef; height: 100%; diff --git a/client/index.html b/client/index.html index 14bf3b0..442a2fc 100644 --- a/client/index.html +++ b/client/index.html @@ -18,20 +18,7 @@ + + - +