1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-02 16:08:31 +00:00

3.5.325. Tab complete commands. CSS tweaks. Fix chat lines not updating.

This commit is contained in:
SirStendec 2016-10-12 16:42:43 -04:00
parent 1a52caab6b
commit 6ab4bd64a8
12 changed files with 354 additions and 34 deletions

View file

@ -1,3 +1,15 @@
<div class="list-header">3.5.325 <time datetime="2016-10-12">(2016-10-12)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Tab-complete commands in chat.</li>
<li>Fixed: When the channel title is on top and the page is narrow, don't display the needless separator bar above the channel metadata.</li>
<li>Fixed: Issue causing chat lines to stop updating when users are timed out.</li>
</ul>
<div class="list-header">3.5.324 <time datetime="2016-10-12">(2016-10-12)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: The player wasn't being resized properly with BetterTTV when the chat width was changed.</li>
</ul>
<div class="list-header">3.5.323 <time datetime="2016-10-12">(2016-10-12)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Changed: Refactor how the Debug tab of the About menu works so that it's easier to add more content.</li>

View file

@ -55,9 +55,10 @@ FFZ.settings_info.command_aliases = {
for(var i=0; i < this.settings.command_aliases.length; i++) {
var pair = this.settings.command_aliases[i],
name = pair[0],
command = pair[1];
command = pair[1],
label = pair[2];
old_val.push(name + '=' + command);
old_val.push(name + (label ? ' ' + label : '') + '=' + command);
}
utils.prompt(
@ -67,8 +68,10 @@ FFZ.settings_info.command_aliases = {
"Variables, such as arguments you provide running the custom command, can be inserted into the output.<hr>" +
"All custom commands require names. Names go at the start of each line, and are separated from " +
" the actual command by an equals sign. Do not include the leading slash or dot. Those are automatically included.<br>" +
"<strong>Example:</strong> <code>boop=/timeout {0} 15 Boop!</code><hr>" +
"the actual command by an equals sign. Do not include the leading slash or dot. Those are automatically included. " +
"You can also include a description of the arguments after the name but before the equals-sign " +
"to include a helpful reminder when using tab-completion with the command.<br>" +
"<strong>Example:</strong> <code>boop &lt;user&gt;=/timeout {0} 15 Boop!</code><hr>" +
"<code>{0}</code>, <code>{1}</code>, <code>{2}</code>, etc. will be replaced with any arguments you've supplied. " +
"Follow an argument index with a <code>$</code> to also include all remaining arguments.<br>" +
@ -89,22 +92,35 @@ FFZ.settings_info.command_aliases = {
output = [];
for(var i=0; i < vals.length; i++) {
var cmd = vals[i],
name,
var cmd = vals[i];
if ( cmd.charAt(0) === '.' || cmd.charAt(0) === '/' )
cmd = cmd.substr(1);
var name,
label,
name_match = /^([^=]+)=/.exec(cmd);
if ( ! cmd || ! cmd.length )
continue;
if ( name_match ) {
name = name_match[1].toLowerCase();
var ind = name_match[1].indexOf(' ');
if ( ind === -1 ) {
name = name_match[1].toLowerCase();
label = null;
} else {
name = name_match[1].substr(0,ind).toLowerCase();
label = name_match[1].substr(ind).trim();
}
cmd = cmd.substr(name_match[0].length);
}
output.push([name, cmd]);
output.push([name, cmd, label]);
}
f.settings.set("command_aliases", output);
}, 600, input);
}
};
@ -117,14 +133,18 @@ FFZ.prototype.cache_command_aliases = function() {
for(var i=0; i < this.settings.command_aliases.length; i++) {
var pair = this.settings.command_aliases[i],
name = pair[0],
command = pair[1];
command = pair[1],
label = pair[2];
// Skip taken/invalid names.
if ( ! name || ! name.length || aliases[name] || KNOWN_COMMANDS.indexOf(name) !== -1 )
continue;
aliases[name] = command;
aliases[name] = [command, label];
}
if ( this._inputv )
Ember.propertyDidChange(this._inputv, 'ffz_commands');
}
@ -243,6 +263,9 @@ FFZ.chat_commands.card = function(room, args) {
});
}
FFZ.chat_commands.card.label = '/card &lt;user&gt;';
FFZ.chat_commands.card.info = 'Open Moderation Card';
FFZ.chat_commands.rules = function(room, args) {
var f = this,
@ -258,6 +281,8 @@ FFZ.chat_commands.rules = function(room, args) {
});
}
FFZ.chat_commands.rules.info = 'Show Chat Room Rules';
FFZ.chat_commands.open_link = function(room, args) {
if ( ! args || ! args.length )
@ -267,6 +292,9 @@ FFZ.chat_commands.open_link = function(room, args) {
wnd.opener = null;
}
FFZ.chat_commands.open_link.label = '/open_link &lt;url&gt;';
FFZ.chat_commands.open_link.info = 'Open URL in Tab';
FFZ.chat_commands.fetch_link = function(room, args) {
if ( ! args || ! args.length )
@ -318,6 +346,8 @@ FFZ.chat_commands.fetch_link = function(room, args) {
});
}
FFZ.chat_commands.fetch_link.label = '/fetch_link &lt;url&gt; <i>[template]</i>';
FFZ.chat_commands.fetch_link.info = 'Fetch URL and Display in Chat';
// -----------------

View file

@ -4,9 +4,90 @@ var FFZ = window.FrankerFaceZ,
is_android = navigator.userAgent.indexOf('Android') !== -1,
MOD_COMMANDS = {
ban: {
label: '/ban &lt;user&gt; <i>[reason]</i>',
info: 'Permanently Ban User'
},
unban: {
label: '/unban &lt;user&gt;',
info: 'Unban User'
},
timeout: {
label: '/timeout &lt;user&gt; <i>[duration=600] [reason]</i>',
info: 'Temporarily Ban User'
},
clear: {info: 'Clear Chat for All Users'},
slow: {
label: '/slow <i>[duration=120]</i>',
info: 'Enable Slow Mode'
},
slowoff: {info: 'Disable Slow Mode'},
r9kbeta: {info: 'Enable R9k Mode'},
r9kbetaoff: {info: 'Disable R9k Mode'},
subscribers: {info: 'Enable Subscribers-Only Mode'},
subscribersoff: {info: 'Disable Subscribers-Only Mode'},
emotesonly: {info: 'Enable Emotes-Only Mode'},
emotesonlyoff: {info: 'Disable Emotes-Only Mode'},
},
BROADCASTER_COMMANDS = {
mod: {
label: '/mod &lt;user&gt;',
info: 'Make User a Moderator'
},
unmod: {
label: '/unmod &lt;user&gt;',
info: 'Remove User\'s Moderator Status'
},
host: {
label: '/host &lt;channel&gt;',
info: 'Enter Host Mode with Channel'
},
unhost: {info: 'Exit Host Mode'},
commercial: {
label: '/commercial <i>[length]</i>',
info: 'Trigger Commercial(s) for Length'
}
},
DEFAULT_COMMANDS = {
color: {
label: '/color &lt;color&gt;',
info: 'Set Username Color'
},
help: {
label: '/help <i>[command]</i>',
info: 'List Available Chat Commands'
},
me: {
label: '/me &lt;message&gt;',
info: 'Send a Colored Chat "Action"'
},
w: {
label: '/w &lt;user&gt; &lt;message&gt;',
info: 'Whisper to User'
},
disconnect: {info: 'Disconnect from Chat'},
mods: {info: 'List Chat Moderators'},
},
CHARCODES = {
AT_SIGN: 64,
COLON: 58
COLON: 58,
PERIOD: 46,
SLASH: 47
},
KEYCODES = {
@ -117,6 +198,23 @@ FFZ.settings_info.input_complete_emotes = {
}
FFZ.settings_info.input_complete_commands = {
type: "boolean",
value: true,
category: "Chat Input",
no_bttv: true,
name: "Tab-Complete Commands",
help: "Use tab completion to complete commands in chat.",
on_update: function(val) {
if ( this._inputv )
Ember.propertyDidChange(this._inputv, 'ffz_commands');
}
}
FFZ.settings_info.input_complete_aliases = {
type: "select",
options: {
@ -548,10 +646,94 @@ FFZ.prototype.modify_chat_input = function(component) {
var area = t.get('chatTextArea');
move_selection(area, prefix.length);
area.focus();
var text = t.get('textareaValue'),
ind = text.indexOf(' '),
start = ind !== -1 && text.substr(0, ind);
if ( (prefix.length-1) === ind && f.settings.input_complete_commands && (start.charAt(0) === '/' || start.charAt(0) === '.') ) {
var commands = t.get('ffz_commands'),
cmd = commands[start.substr(1)];
if ( cmd && cmd.label && cmd.label.split(' ',2)[1] === '&lt;user&gt;' ) {
t.ffzFetchNameSuggestions();
t.set("ffz_suggestions_visible", true);
t.ffzSetPartialWord();
}
}
});
},
ffz_commands: function() {
var commands = _.extend({}, DEFAULT_COMMANDS),
in_conversation = ConvoInput && this.parentView instanceof ConvoInput,
room = this.get('parentView.context.model'),
is_moderator = room && room.get('isModeratorOrHigher'),
user = f.get_user(),
is_broadcaster = room && user && user.login === room.get('name');
if ( in_conversation )
return {};
if ( is_moderator )
commands = _.extend(commands, MOD_COMMANDS);
if ( is_broadcaster)
commands = _.extend(commands, BROADCASTER_COMMANDS);
// FFZ Commands
for(var cmd in FFZ.chat_commands) {
var data = FFZ.chat_commands[cmd];
if ( commands[cmd] )
continue;
var enabled = data.hasOwnProperty('enabled') ? data.enabled : true;
if ( typeof enabled === "function" )
try {
enabled = data.enabled.call(f, room, [])
} catch(err) {
f.error('command "' + cmd + '" enabled', err);
enabled = false;
}
if ( ! enabled )
continue;
commands[cmd] = {
label: data.label,
info: data.info,
alias: false,
short: data.short
}
}
// Aliases
for(var cmd in f._command_aliases) {
var data = f._command_aliases[cmd],
replacement = data[0],
label = data[1];
if ( ! label ) {
var vars = utils.extract_cmd_variables(replacement, true);
label = vars.join(' ');
} else
label = utils.sanitize(label);
commands[cmd] = {
label: '/' + cmd + (label ? ' ' + label : ''),
info: replacement,
alias: true
}
}
return commands;
}.property('parentView.context.model.isModeratorOrHigher', 'parentView.context.model.name'),
ffz_emoticons: function() {
var emotes = {},
@ -653,8 +835,26 @@ FFZ.prototype.modify_chat_input = function(component) {
ffz_suggestions: function() {
var output = [],
emotes = this.get('ffz_emoticons'),
commands = this.get('ffz_commands'),
suggestions = this.get('ffz_name_suggestions'); //.mapBy('id').uniq();
if ( f.settings.input_complete_commands ) {
// Include Commands
for(var command_name in commands) {
var cmd = '/' + command_name,
data = commands[command_name];
output.push({
type: "command",
match: cmd,
alternate_match: command_name.toLowerCase(),
content: data.content || cmd,
label: data.label || cmd,
info: data.alias ? 'Alias: ' + data.info : (data.short ? 'Short ' : '') + 'Command' + (data.info ? ': ' + data.info : '')
});
}
}
if ( f.settings.input_complete_emotes ) {
// Include Emoticons
for(var emote_name in emotes) {
@ -794,12 +994,14 @@ FFZ.prototype.modify_chat_input = function(component) {
}.property('ffz_emoticons', 'ffz_name_suggestions'),
ffz_filtered_suggestions: Ember.computed("ffz_suggestions", "ffz_partial_word", function() {
ffz_filtered_suggestions: Ember.computed("ffz_suggestions", "ffz_partial_word", "ffz_partial_word_start", function() {
var suggestions = this.get('ffz_suggestions'),
partial = this.get('ffz_partial_word'),
word_start = this.get('ffz_partial_word_start'),
part2 = partial.substr(1),
char = partial.charAt(0),
is_at = char === '@';
is_at = char === '@',
is_command = char === '.' || char === '/';
return suggestions.filter(function(item) {
var name = item.match || item.content || item.label,
@ -808,6 +1010,16 @@ FFZ.prototype.modify_chat_input = function(component) {
if ( ! name )
return false;
if ( type === 'command' ) {
if ( word_start > 0 )
return false;
if ( is_command )
return item.alternate_match.indexOf(part2.toLowerCase()) === 0;
return name.toLowerCase().indexOf(partial.toLowerCase()) === 0;
}
if ( type === 'user' ) {
// Names are case insensitive, and we have to ignore the leading @ of our
// partial word when matching.
@ -946,6 +1158,19 @@ FFZ.prototype.modify_chat_input = function(component) {
break;
case CHARCODES.PERIOD:
case CHARCODES.SLASH:
// If we get a command, show the menu, but only if we're at the start of a line.
if ( ! this.get('ffz_suggestions_visible') && f.settings.input_complete_commands && !(ConvoInput && this.parentView instanceof ConvoInput) ) {
var ind = selection_start(this.get('chatTextArea')) - 1;
Ember.run.next(function() {
if ( ind === -1 )
t.ffzShowSuggestions();
});
}
break;
case CHARCODES.COLON:
if ( f.settings.input_emoji ) {
var textarea = this.get('chatTextArea'),
@ -1139,10 +1364,15 @@ FFZ.prototype.modify_chat_input = function(component) {
});
}
} else if ( start === '/w' || start === '/ignore' || start === '/unignore' || start === '/mod' || start === '/unmod' || start === '/ban' || start === '/unban' || start === '/timeout' || start === '/purge' ) {
t.ffzFetchNameSuggestions();
t.set("ffz_suggestions_visible", true);
t.ffzSetPartialWord();
} else if ( f.settings.input_complete_commands && (start.charAt(0) === '/' || start.charAt(0) === '.') ) {
var commands = t.get('ffz_commands'),
cmd = commands[start.substr(1)];
if ( cmd && cmd.label && cmd.label.split(' ',2)[1] === '&lt;user&gt;' ) {
t.ffzFetchNameSuggestions();
t.set("ffz_suggestions_visible", true);
t.ffzSetPartialWord();
}
}
});
}

View file

@ -1401,6 +1401,8 @@ FFZ.chat_commands.join = function(room, args) {
}
FFZ.chat_commands.join.no_bttv = true;
FFZ.chat_commands.join.label = '/join &lt;channel&gt;';
FFZ.chat_commands.join.info = 'Add Channel to Pinned Rooms';
FFZ.chat_commands.part = function(room, args) {
@ -1422,4 +1424,6 @@ FFZ.chat_commands.part = function(room, args) {
return "You are not in " + room_id + ".";
}
FFZ.chat_commands.part.no_bttv = true;
FFZ.chat_commands.part.no_bttv = true;
FFZ.chat_commands.part.label = '/part &lt;channel&gt;';
FFZ.chat_commands.part.info = 'Remove Channel from Pinned Rooms';

View file

@ -222,7 +222,7 @@ FFZ.prototype.setup_layout = function() {
height = size[1],
host_height = size[2];
return "<style>body[data-current-path^=\"user.channel.\"] .app-main:not(.theatre) #player,.dynamic-player, .dynamic-player object, .dynamic-player video{width:" + width + "px !important;height:" + height + "px !important} .cn-hosted .dynamic-target-player,.cn-hosted .dynamic-target-player object,body[data-current-path^=\"user.channel.\"] .app-main:not(.theatre) .cn-hosted #player, .cn-hosted .dynamic-target-player video{width:" + width + "px !important;height:" + host_height + "px !important}</style><style>.dynamic-player .player object, .dynamic-player .player video{width:100% !important; height:100% !important}</style>";
return "<style>.ffz-small-player:not(.ffz-bttv)[data-current-path^=\"user.channel.\"] .app-main:not(.theatre) #player,.dynamic-player, .dynamic-player object, .dynamic-player video{width:" + width + "px !important;height:" + height + "px !important} .cn-hosted .dynamic-target-player,.cn-hosted .dynamic-target-player object,.ffz-small-player:not(.ffz-bttv)[data-current-path^=\"user.channel.\"] .app-main:not(.theatre) .cn-hosted #player, .cn-hosted .dynamic-target-player video{width:" + width + "px !important;height:" + host_height + "px !important}</style><style>.dynamic-player .player object, .dynamic-player .player video{width:100% !important; height:100% !important}</style>";
}.property("playerSize"),
ffzPortraitWarning: function() {

View file

@ -712,6 +712,8 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
return [{type: 'text', text: 'hi'}];
}.property('msgObject.message'),*/
ffz_modified: true,
ffzTokenizedMessage: function() {
try {
return f.tokenize_chat_line(this.get('msgObject'));

View file

@ -1567,7 +1567,7 @@ FFZ.prototype._update_alias = function(user) {
FFZ.chat_commands.purge = function(room, args) {
if ( ! args || ! args.length )
return "Purge Usage: /p username [ban reason]";
return "Usage: /purge <user> [ban reason]";
var name = args.shift(),
reason = args.length ? args.join(" ") : "";
@ -1575,25 +1575,34 @@ FFZ.chat_commands.purge = function(room, args) {
room.room.send("/timeout " + name + " 1 " + reason, true);
}
FFZ.chat_commands.purge.label = '/purge &lt;user&gt; <i>[reason]</i>';
FFZ.chat_commands.purge.info = 'Ban User for 1 Second';
FFZ.chat_commands.p = function(room, args) {
return FFZ.chat_commands.purge.call(this, room, args);
}
FFZ.chat_commands.p.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.p.short = true;
FFZ.chat_commands.p.label = '/p &lt;user&gt; <i>[reason]</i>';
FFZ.chat_commands.p.info = 'Ban User for 1 Second';
FFZ.chat_commands.t = function(room, args) {
if ( ! args || ! args.length )
return "Timeout Usage: /t username [duration] [ban reason]";
return "Usage: /t <user> [duration=600] [reason]";
room.room.send("/timeout " + args.join(" "), true);
}
FFZ.chat_commands.t.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.t.short = true;
FFZ.chat_commands.t.label = '/t &lt;user&gt; <i>[duration=600] [reason]</i>';
FFZ.chat_commands.t.info = 'Temporarily Ban User';
FFZ.chat_commands.b = function(room, args) {
if ( ! args || ! args.length )
return "Ban Usage: /b username [ban reason]";
return "Usage: /b <user> [reason]";
var name = args.shift(),
reason = args.length ? args.join(" ") : "";
@ -1602,11 +1611,14 @@ FFZ.chat_commands.b = function(room, args) {
}
FFZ.chat_commands.b.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.b.short = true;
FFZ.chat_commands.b.label = '/b &lt;user&gt; <i>[reason]</i>';
FFZ.chat_commands.b.info = 'Permanently Ban User';
FFZ.chat_commands.u = function(room, args) {
if ( ! args || ! args.length )
return "Unban Usage: /u username [more usernames separated by spaces]";
return "Usage: /u <user> [more usernames separated by spaces]";
if ( args.length > 10 )
return "Please only unban up to 10 users at once.";
@ -1618,4 +1630,7 @@ FFZ.chat_commands.u = function(room, args) {
}
}
FFZ.chat_commands.u.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.u.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.u.short = true;
FFZ.chat_commands.u.label = '/u &lt;user&gt; <i>[&lt;user&gt; ...]';
FFZ.chat_commands.u.info = 'Unban User(s)';

View file

@ -727,7 +727,7 @@ FFZ.prototype.run_command = function(text, room_id) {
try {
val = command.enabled.call(this, room, args);
} catch(err) {
this.error('command "' + cmd + '" enabled: ' + err);
this.error('command "' + cmd + '" enabled', err);
val = false;
}
}
@ -2147,7 +2147,7 @@ FFZ.prototype._modify_room = function(room) {
was_handled = true;
} else {
var alias = f._command_aliases[cmd],
var alias = f._command_aliases[cmd][0],
args = text.substr(1 + cmd.length).trimLeft().split(/\s+/g),
output = utils.replace_cmd_variables(alias, null, this, null, args);

View file

@ -12,7 +12,7 @@ FFZ.prototype.setup_ember_wrapper = function() {
}
FFZ.prototype.update_views = function(klass, modifier, if_not_exists, immediate) {
FFZ.prototype.update_views = function(klass, modifier, if_not_exists, immediate, no_modify_existing) {
var original_klass;
if ( typeof klass === 'string' ) {
original_klass = klass;
@ -33,10 +33,10 @@ FFZ.prototype.update_views = function(klass, modifier, if_not_exists, immediate)
} else
original_klass = klass.toString();
if ( this._ember_finalized || immediate )
this._update_views([[original_klass, klass, modifier]]);
if ( this._ember_finalized || immediate || ! this._views_to_update )
this._update_views([[original_klass, klass, modifier, no_modify_existing || false]]);
else
this._views_to_update.push([original_klass, klass, modifier]);
this._views_to_update.push([original_klass, klass, modifier, no_modify_existing || false]);
return true;
}
@ -79,10 +79,12 @@ FFZ.prototype._update_views = function(klasses) {
updated_instances++;
try {
if ( ! view.ffz_modified )
if ( ! view.ffz_modified && ! klasses[i][3] )
klasses[i][2].call(this, view);
(view.ffz_update || view.ffz_init).call(view);
var func = view.ffz_update || view.ffz_init;
if ( func )
func.call(view);
} catch(err) {
this.error("An error occured when updating an existing Ember instance of: " + klasses[i][0], err);

View file

@ -34,7 +34,7 @@ FFZ.msg_commands = {};
// Version
var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 323,
major: 3, minor: 5, revision: 325,
toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
}
@ -335,6 +335,7 @@ FFZ.prototype.init_normal = function(delay, no_socket) {
// Initialize all the modules.
this.load_settings();
this.setup_ember_wrapper();
// Start this early, for quick loading.
this.setup_dark();
@ -357,6 +358,7 @@ FFZ.prototype.init_normal = function(delay, no_socket) {
this.setup_following_count(false);
this.setup_menu();
this.finalize_ember_wrapper();
this.setup_message_event();
this.fix_tooltips();
this.find_bttv(10);
@ -384,6 +386,7 @@ FFZ.prototype.init_dashboard = function(delay) {
// Initialize all the modules.
this.load_settings();
this.setup_ember_wrapper();
// Start this early, for quick loading.
this.setup_dark();
@ -406,6 +409,8 @@ FFZ.prototype.init_dashboard = function(delay) {
this.setup_dash_stats();
this.setup_dash_feed();
this.finalize_ember_wrapper();
this._update_subscribers();
// Set up the FFZ message passer.

View file

@ -554,6 +554,19 @@ module.exports = FFZ.utils = {
CMD_VAR_REGEX: CMD_VAR_REGEX,
extract_cmd_variables: function(command, args_only) {
var matches = [];
CMD_VAR_REGEX.lastIndex = 0;
command.replace(CMD_VAR_REGEX, function(match, variable) {
if ( args_only && ! /\d+(?:$\d*)?/.test(variable) )
return;
matches.push('{' + variable + '}');
});
return _.unique(matches);
},
replace_cmd_variables: function(command, user, room, message, args) {
user = user || {};
room = room || {};

View file

@ -311,7 +311,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
width: auto;
z-index: 7;
padding: 10px;
padding: 10px !important;
}
.ffz-theater-stats .app-main.theatre .cn-metabar__more {
@ -3534,6 +3534,13 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
padding-top: 80px;
}
.ffz-channel-title-top .cn-metabar__more,
.app-main.theatre .cn-metabar__more {
margin-top: 0 !important;
padding-top: 0 !important;
border-top: none !important;
}
.ffz-hide-channel-banner .cn-cover { height: 0 !important }
.ffz-minimal-channel-title.ffz-channel-title-top #channel { padding-top: 50px }