diff --git a/package.json b/package.json index 91544367..017f6b4a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.27.1", + "version": "3.28.0", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/templates/channel.pug b/templates/channel.pug index e56f5b63..82fd3070 100644 --- a/templates/channel.pug +++ b/templates/channel.pug @@ -240,6 +240,7 @@ html(lang="en") script(id="socketio-js", src=sioSource) script(src="/js/data.js") script(src="/js/util.js") + script(src="/js/tabcomplete.js") script(src="/js/player.js") script(src="/js/paginator.js") script(src="/js/ui.js") diff --git a/templates/useroptions.pug b/templates/useroptions.pug index cf60900f..fe4d4b57 100644 --- a/templates/useroptions.pug +++ b/templates/useroptions.pug @@ -114,6 +114,12 @@ mixin us-chat +rcheckbox("us-sendbtn", "Add a send button to chat") +rcheckbox("us-no-emotes", "Disable chat emotes") +rcheckbox("us-strip-image", "Remove images from chat") + .form-group + label.control-label.col-sm-4(for="#us-chat-tab-method") Tab completion method + .col-sm-8 + select#us-chat-tab-method.form-control + option(value="Cycle options") Cycle options + option(value="Longest unique match") Longest unique match mixin us-mod #us-mod.tab-pane diff --git a/test/www/tabcomplete.js b/test/www/tabcomplete.js index 6985bc78..7b460214 100644 --- a/test/www/tabcomplete.js +++ b/test/www/tabcomplete.js @@ -4,7 +4,7 @@ require('../../www/js/tabcomplete'); describe('CyTube.tabCompletionMethods', () => { - describe('"Longest unique prefix"', () => { + describe('"Longest unique match"', () => { const testcases = [ { input: 'and his name is j', @@ -26,6 +26,16 @@ describe('CyTube.tabCompletionMethods', () => { }, description: 'completes a unique match' }, + { + input: 'johnc', + position: 5, + options: ['johncena', 'johnstamos', 'johto'], + output: { + text: 'johncena ', + newPosition: 9 + }, + description: 'completes a unique match at the beginning of the string' + }, { input: 'and his name is johnc', position: 21, @@ -70,7 +80,7 @@ describe('CyTube.tabCompletionMethods', () => { testcases.forEach(test => { it(test.description, () => { assert.deepEqual( - CyTube.tabCompleteMethods['Longest unique prefix']( + CyTube.tabCompleteMethods['Longest unique match']( test.input, test.position, test.options, @@ -108,6 +118,30 @@ describe('CyTube.tabCompletionMethods', () => { ], description: 'cycles through options correctly' }, + { + input: 'c', + position: 1, + options: ['COBOL', 'Carlos', 'carl', 'john', 'joseph', ''], + outputs: [ + { + text: 'carl ', + newPosition: 5 + }, + { + text: 'Carlos ', + newPosition: 7 + }, + { + text: 'COBOL ', + newPosition: 6 + }, + { + text: 'carl ', + newPosition: 5 + } + ], + description: 'cycles through options correctly at the beginning of the string' + }, { input: 'hey ', position: 5, diff --git a/www/js/data.js b/www/js/data.js index 73e614d0..9317cc65 100644 --- a/www/js/data.js +++ b/www/js/data.js @@ -123,7 +123,8 @@ var USEROPTS = { show_shadowchat : getOrDefault("show_shadowchat", false), emotelist_sort : getOrDefault("emotelist_sort", true), no_emotes : getOrDefault("no_emotes", false), - strip_image : getOrDefault("strip_image", false) + strip_image : getOrDefault("strip_image", false), + chat_tab_method : getOrDefault("chat_tab_method", "Cycle options") }; /* Backwards compatibility check */ diff --git a/www/js/tabcomplete.js b/www/js/tabcomplete.js index 95323d47..2b2f834b 100644 --- a/www/js/tabcomplete.js +++ b/www/js/tabcomplete.js @@ -2,7 +2,7 @@ CyTube.tabCompleteMethods = {}; // Bash-style completion // Only completes as far as it is possible to maintain uniqueness of the completion. -CyTube.tabCompleteMethods['Longest unique prefix'] = function (input, position, options, context) { +CyTube.tabCompleteMethods['Longest unique match'] = function (input, position, options, context) { var lower = input.toLowerCase(); // First, backtrack to the nearest whitespace to find the // incomplete string that should be completed. @@ -10,12 +10,12 @@ CyTube.tabCompleteMethods['Longest unique prefix'] = function (input, position, var incomplete = ''; for (start = position - 1; start >= 0; start--) { if (/\s/.test(lower[start])) { - start++; break; } incomplete = lower[start] + incomplete; } + start++; // Nothing to complete if (!incomplete.length) { @@ -101,12 +101,12 @@ CyTube.tabCompleteMethods['Cycle options'] = function (input, position, options, var incomplete = ''; for (start = position - 1; start >= 0; start--) { if (/\s/.test(lower[start])) { - start++; break; } incomplete = lower[start] + incomplete; } + start++; // Nothing to complete if (!incomplete.length) { diff --git a/www/js/ui.js b/www/js/ui.js index 8e16af0b..4e55804c 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -111,7 +111,11 @@ $("#guestname").keydown(function (ev) { } }); -function chatTabComplete() { + +/** + * TODO: Remove this post-deployment + */ +function oldChatTabComplete() { var words = $("#chatline").val().split(" "); var current = words[words.length - 1].toLowerCase(); if (!current.match(/^[\w-]{1,20}$/)) { @@ -186,6 +190,54 @@ function chatTabComplete() { $("#chatline").val(words.join(" ")); } +CyTube.chatTabCompleteData = { + context: {} +}; + +function chatTabComplete() { + if (!CyTube.tabCompleteMethods) { + oldChatTabComplete(); + return; + } + var chatline = document.getElementById("chatline"); + var currentText = chatline.value; + var currentPosition = chatline.selectionEnd; + if (typeof currentPosition !== 'number' || !chatline.setSelectionRange) { + // Bail, we're on IE8 or something similarly dysfunctional + return; + } + var firstWord = !/\s/.test(currentText.trim()); + var options = []; + var userlistElems = document.getElementById("userlist").children; + for (var i = 0; i < userlistElems.length; i++) { + var username = userlistElems[i].children[1].textContent; + if (firstWord) { + username += ':'; + } + options.push(username); + } + + CHANNEL.emotes.forEach(function (emote) { + options.push(emote.name); + }); + + var method = USEROPTS.chat_tab_method; + if (!CyTube.tabCompleteMethods[method]) { + console.error("Unknown chat tab completion method '" + method + "', using default"); + method = "Cycle options"; + } + + var result = CyTube.tabCompleteMethods[method]( + currentText, + currentPosition, + options, + CyTube.chatTabCompleteData.context + ); + + chatline.value = result.text; + chatline.setSelectionRange(result.newPosition, result.newPosition); +} + $("#chatline").keydown(function(ev) { // Enter/return if(ev.keyCode == 13) { @@ -218,7 +270,11 @@ $("#chatline").keydown(function(ev) { return; } else if(ev.keyCode == 9) { // Tab completion - chatTabComplete(); + try { + chatTabComplete(); + } catch (error) { + console.error(error); + } ev.preventDefault(); return false; } diff --git a/www/js/util.js b/www/js/util.js index 198a4bae..f9c3429a 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -643,6 +643,7 @@ function showUserOptions() { $("#us-sendbtn").prop("checked", USEROPTS.chatbtn); $("#us-no-emotes").prop("checked", USEROPTS.no_emotes); $("#us-strip-image").prop("checked", USEROPTS.strip_image); + $("#us-chat-tab-method").val(USEROPTS.chat_tab_method); $("#us-modflair").prop("checked", USEROPTS.modhat); $("#us-shadowchat").prop("checked", USEROPTS.show_shadowchat); @@ -677,6 +678,7 @@ function saveUserOptions() { USEROPTS.chatbtn = $("#us-sendbtn").prop("checked"); USEROPTS.no_emotes = $("#us-no-emotes").prop("checked"); USEROPTS.strip_image = $("#us-strip-image").prop("checked"); + USEROPTS.chat_tab_method = $("#us-chat-tab-method").val(); if (CLIENT.rank >= 2) { USEROPTS.modhat = $("#us-modflair").prop("checked");