1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-29 15:55:33 +00:00

Added "/ffz log" command to upload logs to pastebin. Added extra error handling in Ember-facing code. Added settings menu categories. Added user name color adjustment. Added support for better moderation cards. Added support for extra moderation commands. Fixed menu size limit. Fixed typo in show_message RPC. Fixed CSS for UI button.

This commit is contained in:
SirStendec 2015-02-10 01:34:23 -05:00
parent fe55f43b8e
commit b90a25d794
19 changed files with 1788 additions and 676 deletions

1232
script.js

File diff suppressed because it is too large Load diff

4
script.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,25 @@
var FFZ = window.FrankerFaceZ; var FFZ = window.FrankerFaceZ;
// -----------------
// Log Export
// -----------------
FFZ.ffz_commands.log = function(room, args) {
this._pastebin(this._log_data.join("\n"), function(url) {
if ( ! url )
return this.room_message(room, "There was an error uploading the FrankerFaceZ log.");
this.room_message(room, "Your FrankerFaceZ log has been pasted to: " + url);
});
};
// ----------------- // -----------------
// Mass Moderation // Mass Moderation
// ----------------- // -----------------
FFZ.chat_commands.massunmod = function(room, args) { FFZ.ffz_commands.massunmod = function(room, args) {
args = args.join(" ").trim(); args = args.join(" ").trim();
if ( ! args.length ) if ( ! args.length )
@ -29,10 +43,10 @@ FFZ.chat_commands.massunmod = function(room, args) {
return "Sent unmod command for " + count + " users."; return "Sent unmod command for " + count + " users.";
} }
FFZ.chat_commands.massunmod.help = "Usage: /ffz massunmod <list, of, users>\nBroadcaster only. Unmod all the users in the provided list."; FFZ.ffz_commands.massunmod.help = "Usage: /ffz massunmod <list, of, users>\nBroadcaster only. Unmod all the users in the provided list.";
FFZ.chat_commands.massmod = function(room, args) { FFZ.ffz_commands.massmod = function(room, args) {
args = args.join(" ").trim(); args = args.join(" ").trim();
if ( ! args.length ) if ( ! args.length )
@ -47,7 +61,6 @@ FFZ.chat_commands.massmod = function(room, args) {
if ( args.length > 50 ) if ( args.length > 50 )
return "Each user you mod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses."; return "Each user you mod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses.";
var count = args.length; var count = args.length;
while(args.length) { while(args.length) {
var name = args.shift(); var name = args.shift();
@ -57,4 +70,4 @@ FFZ.chat_commands.massmod = function(room, args) {
return "Sent mod command for " + count + " users."; return "Sent mod command for " + count + " users.";
} }
FFZ.chat_commands.massmod.help = "Usage: /ffz massmod <list, of, users>\nBroadcaster only. Mod all the users in the provided list."; FFZ.ffz_commands.massmod.help = "Usage: /ffz massmod <list, of, users>\nBroadcaster only. Mod all the users in the provided list.";

View file

@ -5,7 +5,7 @@ var FFZ = window.FrankerFaceZ;
// Developer Mode Command // Developer Mode Command
// ----------------------- // -----------------------
FFZ.chat_commands.developer_mode = function(room, args) { FFZ.ffz_commands.developer_mode = function(room, args) {
var enabled, args = args && args.length ? args[0].toLowerCase() : null; var enabled, args = args && args.length ? args[0].toLowerCase() : null;
if ( args == "y" || args == "yes" || args == "true" || args == "on" ) if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true; enabled = true;
@ -19,4 +19,4 @@ FFZ.chat_commands.developer_mode = function(room, args) {
return "Developer Mode is now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser."; return "Developer Mode is now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser.";
} }
FFZ.chat_commands.developer_mode.help = "Usage: /ffz developer_mode <on|off>\nEnable or disable Developer Mode. When Developer Mode is enabled, the script will be reloaded from //localhost:8000/script.js instead of from the CDN."; FFZ.ffz_commands.developer_mode.help = "Usage: /ffz developer_mode <on|off>\nEnable or disable Developer Mode. When Developer Mode is enabled, the script will be reloaded from //localhost:8000/script.js instead of from the CDN.";

View file

@ -25,7 +25,11 @@ FFZ.prototype.setup_chatview = function() {
continue; continue;
this.log("Adding UI link manually to Chat view.", view); this.log("Adding UI link manually to Chat view.", view);
view.$('.textarea-contain').append(this.build_ui_link(view)); try {
view.$('.textarea-contain').append(this.build_ui_link(view));
} catch(err) {
this.error("setup: build_ui_link: " + err);
}
} }
} }
@ -40,16 +44,28 @@ FFZ.prototype._modify_cview = function(view) {
view.reopen({ view.reopen({
didInsertElement: function() { didInsertElement: function() {
this._super(); this._super();
this.$() && this.$('.textarea-contain').append(f.build_ui_link(this)); try {
this.$() && this.$('.textarea-contain').append(f.build_ui_link(this));
} catch(err) {
f.error("didInsertElement: build_ui_link: " + err);
}
}, },
willClearRender: function() { willClearRender: function() {
this._super(); this._super();
this.$(".ffz-ui-toggle").remove(); try {
this.$(".ffz-ui-toggle").remove();
} catch(err) {
f.error("willClearRender: remove ui link: " + err);
}
}, },
ffzUpdateLink: Ember.observer('controller.currentRoom', function() { ffzUpdateLink: Ember.observer('controller.currentRoom', function() {
f.update_ui_link(); try {
f.update_ui_link();
} catch(err) {
f.error("ffzUpdateLink: update_ui_link: " + err);
}
}) })
}); });
} }

View file

@ -1,4 +1,5 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
reg_escape = function(str) { reg_escape = function(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
@ -13,6 +14,7 @@ FFZ.settings_info.capitalize = {
type: "boolean", type: "boolean",
value: true, value: true,
category: "Chat",
visible: function() { return ! this.has_bttv }, visible: function() { return ! this.has_bttv },
name: "Username Capitalization", name: "Username Capitalization",
@ -24,6 +26,7 @@ FFZ.settings_info.keywords = {
type: "button", type: "button",
value: [], value: [],
category: "Chat",
visible: function() { return ! this.has_bttv }, visible: function() { return ! this.has_bttv },
name: "Highlight Keywords", name: "Highlight Keywords",
@ -33,7 +36,7 @@ FFZ.settings_info.keywords = {
var old_val = this.settings.keywords.join(", "), var old_val = this.settings.keywords.join(", "),
new_val = prompt("Highlight Keywords\n\nPlease enter a comma-separated list of words that you would like to be highlighted in chat.", old_val); new_val = prompt("Highlight Keywords\n\nPlease enter a comma-separated list of words that you would like to be highlighted in chat.", old_val);
if ( ! new_val ) if ( new_val === null || new_val === undefined )
return; return;
// Split them up. // Split them up.
@ -47,17 +50,40 @@ FFZ.settings_info.keywords = {
}; };
FFZ.settings_info.fix_color = {
type: "boolean",
value: false,
category: "Chat",
visible: function() { return ! this.has_bttv },
name: "Adjust Username Colors",
help: "Ensure that username colors contrast with the background enough to be readable.",
on_update: function(val) {
if ( this.has_bttv )
return;
document.body.classList.toggle("ffz-chat-colors", val);
}
};
FFZ.settings_info.chat_rows = { FFZ.settings_info.chat_rows = {
type: "boolean", type: "boolean",
value: false, value: false,
category: "Chat",
visible: function() { return ! this.has_bttv }, visible: function() { return ! this.has_bttv },
name: "Chat Line Backgrounds", name: "Chat Line Backgrounds",
help: "Display alternating background colors for lines in chat.", help: "Display alternating background colors for lines in chat.",
on_update: function(val) { on_update: function(val) {
document.querySelector(".app-main").classList.toggle("ffz-chat-background", val); if ( this.has_bttv )
return;
document.body.classList.toggle("ffz-chat-background", val);
} }
}; };
@ -67,10 +93,19 @@ FFZ.settings_info.chat_rows = {
// --------------------- // ---------------------
FFZ.prototype.setup_line = function() { FFZ.prototype.setup_line = function() {
// Alternating Background // Chat Enhancements
document.querySelector('.app-main').classList.toggle('ffz-chat-background', this.settings.chat_rows); document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && this.settings.fix_color);
document.body.classList.toggle('ffz-chat-background', !this.has_bttv && this.settings.chat_rows);
this._colors = {};
this._last_row = {}; this._last_row = {};
var s = this._fix_color_style = document.createElement('style');
s.id = "ffz-style-username-colors";
s.type = 'text/css';
document.head.appendChild(s);
this.log("Hooking the Ember Line controller."); this.log("Hooking the Ember Line controller.");
var Line = App.__container__.resolve('controller:line'), var Line = App.__container__.resolve('controller:line'),
@ -79,11 +114,20 @@ FFZ.prototype.setup_line = function() {
Line.reopen({ Line.reopen({
tokenizedMessage: function() { tokenizedMessage: function() {
// Add our own step to the tokenization procedure. // Add our own step to the tokenization procedure.
var tokens = f._emoticonize(this, this._super()), var tokens = this._super();
user = f.get_user();
if ( ! user || this.get("model.from") != user.login ) try {
tokens = f._mentionize(this, tokens); tokens = f._emoticonize(this, tokens);
var user = f.get_user();
if ( ! user || this.get("model.from") != user.login )
tokens = f._mentionize(this, tokens);
} catch(err) {
try {
f.error("LineController tokenizedMessage: " + err);
} catch(err) { }
}
return tokens; return tokens;
@ -97,60 +141,84 @@ FFZ.prototype.setup_line = function() {
Line.reopen({ Line.reopen({
didInsertElement: function() { didInsertElement: function() {
this._super(); this._super();
try {
var el = this.get('element'),
user = this.get('context.model.from'),
room = this.get('context.parentController.content.id'),
color = this.get('context.model.color'),
var el = this.get('element'), row_type = this.get('context.model.ffz_alternate');
user = this.get('context.model.from'),
room = this.get('context.parentController.content.id'),
row_type = this.get('context.model.ffzAlternate');
if ( row_type === undefined ) {
row_type = f._last_row[room] = f._last_row.hasOwnProperty(room) ? !f._last_row[room] : false;
this.set("context.model.ffzAlternate", row_type);
}
el.classList.toggle('ffz-alternate', row_type); // Color Processing
el.setAttribute('data-room', room); if ( color )
el.setAttribute('data-sender', user); f._handle_color(color);
f.render_badge(this);
if ( f.settings.capitalize ) // Row Alternation
f.capitalize(this, user); if ( row_type === undefined ) {
row_type = f._last_row[room] = f._last_row.hasOwnProperty(room) ? !f._last_row[room] : false;
// Check for any mentions. this.set("context.model.ffz_alternate", row_type);
var mentioned = el.querySelector('span.mentioned');
if ( mentioned ) {
el.classList.add("ffz-mentioned");
if ( ! document.hasFocus() && ! this.get('context.model.ffzNotified') && f.settings.highlight_notifications ) {
var cap_room = FFZ.get_capitalization(room),
cap_user = FFZ.get_capitalization(user),
room_name = cap_room,
msg = this.get("context.model.message");
if ( this.get("context.parentController.content.isGroupRoom") )
room_name = this.get("context.parentController.content.tmiRoom.displayName");
if ( this.get("context.model.style") == "action" )
msg = "* " + cap_user + " " + msg;
else
msg = cap_user + ": " + msg;
f.show_notification(
msg,
"Twitch Chat Mention in " + room_name,
cap_room,
60000,
window.focus.bind(window)
);
} }
}
// Mark that we've checked this message for mentions. el.classList.toggle('ffz-alternate', row_type);
this.set('context.model.ffzNotified', true);
// Basic Data
el.setAttribute('data-room', room);
el.setAttribute('data-sender', user);
// Badge
f.render_badge(this);
// Capitalization
if ( f.settings.capitalize )
f.capitalize(this, user);
// Mention Highlighting
var mentioned = el.querySelector('span.mentioned');
if ( mentioned ) {
el.classList.add("ffz-mentioned");
if ( ! document.hasFocus() && ! this.get('context.model.ffz_notified') && f.settings.highlight_notifications ) {
var cap_room = FFZ.get_capitalization(room),
cap_user = FFZ.get_capitalization(user),
room_name = cap_room,
msg = this.get("context.model.message");
if ( this.get("context.parentController.content.isGroupRoom") )
room_name = this.get("context.parentController.content.tmiRoom.displayName");
if ( this.get("context.model.style") == "action" )
msg = "* " + cap_user + " " + msg;
else
msg = cap_user + ": " + msg;
f.show_notification(
msg,
"Twitch Chat Mention in " + room_name,
cap_room,
60000,
window.focus.bind(window)
);
}
}
// Mark that we've checked this message for mentions.
this.set('context.model.ffz_notified', true);
} catch(err) {
try {
f.error("LineView didInsertElement: " + err);
} catch(err) { }
}
} }
}); });
// Store the capitalization of our own name. // Store the capitalization of our own name.
var user = this.get_user(); var user = this.get_user();
if ( user && user.name ) if ( user && user.name )
@ -158,6 +226,69 @@ FFZ.prototype.setup_line = function() {
} }
// ---------------------
// Fix Name Colors
// ---------------------
FFZ.prototype._handle_color = function(color) {
if ( ! color || this._colors[color] )
return;
this._colors[color] = true;
// Parse the color.
var raw = parseInt(color.substr(1), 16),
rgb = [
(raw >> 16),
(raw >> 8 & 0x00FF),
(raw & 0x0000FF)
],
lum = utils.get_luminance(rgb),
output = "",
rule = 'span[style="color:' + color + '"]',
matched = false;
if ( lum > 0.3 ) {
// Color Too Bright. We need a lum of 0.3 or less.
matched = true;
var s = 255,
nc = rgb;
while(s--) {
nc = utils.darken(nc);
if ( utils.get_luminance(nc) <= 0.3 )
break;
}
output += '.ffz-chat-colors .ember-chat-container:not(.dark) .chat-line ' + rule + ', .ffz-chat-colors .chat-container:not(.dark) .chat-line ' + rule + ' { color: ' + utils.rgb_to_css(nc) + ' !important; }\n';
} else
output += '.ffz-chat-colors .ember-chat-container:not(.dark) .chat-line ' + rule + ', .ffz-chat-colors .chat-container:not(.dark) .chat-line ' + rule + ' { color: ' + color + ' !important; }\n';
if ( lum < 0.1 ) {
// Color Too Dark. We need a lum of 0.1 or more.
matched = true;
var s = 255,
nc = rgb;
while(s--) {
nc = utils.brighten(nc);
if ( utils.get_luminance(nc) >= 0.1 )
break;
}
output += '.ffz-chat-colors .theatre .chat-container .chat-line ' + rule + ', .ffz-chat-colors .chat-container.dark .chat-line ' + rule + ', .ffz-chat-colors .ember-chat-container.dark .chat-line ' + rule + ' { color: ' + utils.rgb_to_css(nc) + ' !important; }\n';
} else
output += '.ffz-chat-colors .theatre .chat-container .chat-line ' + rule + ', .ffz-chat-colors .chat-container.dark .chat-line ' + rule + ', .ffz-chat-colors .ember-chat-container.dark .chat-line ' + rule + ' { color: ' + color + ' !important; }\n';
if ( matched )
this._fix_color_style.innerHTML += output;
}
// --------------------- // ---------------------
// Capitalization // Capitalization
// --------------------- // ---------------------
@ -205,30 +336,13 @@ FFZ.prototype.capitalize = function(view, user) {
} }
FFZ.chat_commands.capitalization = function(room, args) {
var enabled, args = args && args.length ? args[0].toLowerCase() : null;
if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true;
else if ( args == "n" || args == "no" || args == "false" || args == "off" )
enabled = false;
if ( enabled === undefined )
return "Chat Name Capitalization is currently " + (this.settings.capitalize ? "enabled." : "disabled.");
this.settings.set("capitalize", enabled);
return "Chat Name Capitalization is now " + (enabled ? "enabled." : "disabled.");
}
FFZ.chat_commands.capitalization.help = "Usage: /ffz capitalization <on|off>\nEnable or disable Chat Name Capitalization. This setting does not work with BetterTTV.";
// --------------------- // ---------------------
// Extra Mentions // Extra Mentions
// --------------------- // ---------------------
FFZ._regex_cache = {}; FFZ._regex_cache = {};
FFZ._get_rex = function(word) { FFZ._get_regex = function(word) {
return FFZ._regex_cache[word] = FFZ._regex_cache[word] || RegExp("\\b" + reg_escape(word) + "\\b", "ig"); return FFZ._regex_cache[word] = FFZ._regex_cache[word] || RegExp("\\b" + reg_escape(word) + "\\b", "ig");
} }
@ -266,25 +380,6 @@ FFZ.prototype._mentionize = function(controller, tokens) {
} }
FFZ.chat_commands.mentionize = function(room, args) {
if ( args && args.length ) {
var mention_words = args.join(" ").trim().split(/\W*,\W*/);
if ( mention_words.length == 1 && mention_words[0] == "disable" )
mention_words = [];
this.settings.set("keywords", mention_words);
}
var mention_words = this.settings.keywords;
if ( mention_words.length )
return "The following words will be highlighted: " + mention_words.join(", ");
else
return "There are no words set that will be highlighted.";
}
FFZ.chat_commands.mentionize.help = "Usage: /ffz mentionize <comma, separated, word, list|disable>\nSet a list of words that will also be highlighted in chat.";
// --------------------- // ---------------------
// Emoticon Replacement // Emoticon Replacement
// --------------------- // ---------------------

View file

@ -0,0 +1,247 @@
var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
keycodes = {
ESC: 27,
P: 80,
B: 66,
T: 84
},
btns = [
['5m', 300],
['10m', 600],
['1hr', 3600],
['12hr', 43200],
['24hr', 86400]],
MESSAGE = '<svg class="svg-messages" height="16px" version="1.1" viewBox="0 0 18 18" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M1,15V3h16v12H1z M15.354,5.354l-0.707-0.707L9,10.293L3.354,4.646L2.646,5.354L6.293,9l-3.646,3.646l0.707,0.707L7,9.707l1.646,1.646h0.707L11,9.707l3.646,3.646l0.707-0.707L11.707,9L15.354,5.354z" fill-rule="evenodd"></path></svg>';
// ----------------
// Settings
// ----------------
FFZ.settings_info.enhanced_moderation = {
type: "boolean",
value: false,
visible: function() { return ! this.has_bttv },
category: "Chat",
name: "Enhanced Moderation",
help: "Use /p, /t, /u and /b in chat to moderator, or use hotkeys with moderation cards."
};
// ----------------
// Initialization
// ----------------
FFZ.prototype.setup_mod_card = function() {
this.log("Hooking the Ember Moderation Card view.");
var Card = App.__container__.resolve('view:moderation-card'),
f = this;
Card.reopen({
didInsertElement: function() {
this._super();
try {
if ( ! f.settings.enhanced_moderation )
return;
var el = this.get('element'),
controller = this.get('context');
// Only do the big stuff if we're mod.
if ( controller.get('parentController.model.isModeratorOrHigher') ) {
el.classList.add('ffz-moderation-card');
el.setAttribute('tabindex', 1);
// Key Handling
el.addEventListener('keyup', function(e) {
var key = e.keyCode || e.which,
user_id = controller.get('model.user.id'),
room = controller.get('parentController.model');
if ( key == keycodes.P )
room.send("/timeout " + user_id + " 1");
else if ( key == keycodes.B )
room.send("/ban " + user_id);
else if ( key == keycodes.T )
room.send("/timeout " + user_id + " 600");
else if ( key != keycodes.ESC )
return;
controller.send('hideModOverlay');
});
// Extra Moderation
var line = document.createElement('div');
line.className = 'interface clearfix';
var btn_click = function(timeout) {
var user_id = controller.get('model.user.id'),
room = controller.get('parentController.model');
if ( timeout === -1 )
room.send("/unban " + user_id);
else
room.send("/timeout " + user_id + " " + timeout);
},
btn_make = function(text, timeout) {
var btn = document.createElement('button');
btn.className = 'button';
btn.innerHTML = text;
btn.title = "Timeout User for " + utils.number_commas(timeout) + " Second" + (timeout != 1 ? "s" : "");
if ( timeout === 600 )
btn.title = "(T)" + btn.title.substr(1);
else if ( timeout === 1 )
btn.title = "(P)urge - " + btn.title;
jQuery(btn).tipsy();
btn.addEventListener('click', btn_click.bind(this, timeout));
return btn;
};
line.appendChild(btn_make('Purge', 1));
var s = document.createElement('span');
s.className = 'right';
line.appendChild(s);
for(var i=0; i < btns.length; i++)
s.appendChild(btn_make(btns[i][0], btns[i][1]));
el.appendChild(line);
// Unban Button
var unban_btn = document.createElement('button');
unban_btn.className = 'unban button glyph-only light';
unban_btn.innerHTML = "&#x2713;";
unban_btn.title = "(U)nban User";
jQuery(unban_btn).tipsy();
unban_btn.addEventListener("click", btn_click.bind(this, -1));
var ban_btn = el.querySelector('button.ban');
ban_btn.setAttribute('title', '(B)an User');
jQuery(ban_btn).after(unban_btn);
// Fix Other Buttons
this.$("button.timeout").remove();
}
// More Fixing Other Buttons
var op_btn = el.querySelector('button.mod');
if ( op_btn ) {
var model = controller.get('parentController.model'),
can_op = model.get('isBroadcaster') || model.get('isStaff') || model.get('isAdmin');
if ( ! can_op )
op_btn.parentElement.removeChild(op_btn);
}
var msg_btn = el.querySelector(".interface > button");
if ( msg_btn && msg_btn.className == "button" ) {
msg_btn.innerHTML = MESSAGE;
msg_btn.classList.add('glyph-only');
msg_btn.classList.add('message');
msg_btn.title = "Message User";
jQuery(msg_btn).tipsy();
}
// Focus the Element
this.$().draggable({
start: function() {
el.focus();
}});
el.focus();
} catch(err) {
try {
f.error("ModerationCardView didInsertElement: " + err);
} catch(err) { }
}
}});
}
// ----------------
// Chat Commands
// ----------------
FFZ.chat_commands.purge = FFZ.chat_commands.p = function(room, args) {
if ( ! args || ! args.length )
return "Purge Usage: /p username [more usernames separated by spaces]";
if ( args.length > 10 )
return "Please only purge up to 10 users at once.";
for(var i=0; i < args.length; i++) {
var name = args[i];
if ( name )
room.room.send("/timeout " + name + " 1");
}
}
FFZ.chat_commands.p.enabled = function() { return this.settings.enhanced_moderation; }
FFZ.chat_commands.t = function(room, args) {
if ( ! args || ! args.length )
return "Timeout Usage: /t username [duration]";
room.room.send("/timeout " + args.join(" "));
}
FFZ.chat_commands.t.enabled = function() { return this.settings.enhanced_moderation; }
FFZ.chat_commands.b = function(room, args) {
if ( ! args || ! args.length )
return "Ban Usage: /b username [more usernames separated by spaces]";
if ( args.length > 10 )
return "Please only ban up to 10 users at once.";
for(var i=0; i < args.length; i++) {
var name = args[i];
if ( name )
room.room.send("/ban " + name);
}
}
FFZ.chat_commands.b.enabled = function() { return this.settings.enhanced_moderation; }
FFZ.chat_commands.u = function(room, args) {
if ( ! args || ! args.length )
return "Unban Usage: /b username [more usernames separated by spaces]";
if ( args.length > 10 )
return "Please only unban up to 10 users at once.";
for(var i=0; i < args.length; i++) {
var name = args[i];
if ( name )
room.room.send("/unban " + name);
}
}
FFZ.chat_commands.u.enabled = function() { return this.settings.enhanced_moderation; }

View file

@ -50,6 +50,7 @@ FFZ.prototype.setup_room = function() {
// -------------------- // --------------------
FFZ.chat_commands = {}; FFZ.chat_commands = {};
FFZ.ffz_commands = {};
FFZ.prototype.room_message = function(room, text) { FFZ.prototype.room_message = function(room, text) {
@ -66,6 +67,54 @@ FFZ.prototype.room_message = function(room, text) {
FFZ.prototype.run_command = function(text, room_id) { FFZ.prototype.run_command = function(text, room_id) {
var room = this.rooms[room_id];
if ( ! room || ! room.room )
return false;
if ( ! text )
return;
var args = text.split(" "),
cmd = args.shift().substr(1).toLowerCase(),
command = FFZ.chat_commands[cmd],
output;
if ( ! command )
return false;
if ( command.hasOwnProperty('enabled') ) {
var val = command.enabled;
if ( typeof val == "function" ) {
try {
val = command.enabled.bind(this)(room, args);
} catch(err) {
this.error('command "' + cmd + '" enabled: ' + err);
val = false;
}
}
if ( ! val )
return false;
}
this.log("Received Command: " + cmd, args, true);
try {
output = command.bind(this)(room, args);
} catch(err) {
this.error('command "' + cmd + '" runner: ' + err);
output = "There was an error running the command.";
}
if ( output )
this.room_message(room, output);
return true;
}
FFZ.prototype.run_ffz_command = function(text, room_id) {
var room = this.rooms[room_id]; var room = this.rooms[room_id];
if ( ! room || !room.room ) if ( ! room || !room.room )
return; return;
@ -84,7 +133,7 @@ FFZ.prototype.run_command = function(text, room_id) {
this.log("Received Command: " + cmd, args, true); this.log("Received Command: " + cmd, args, true);
var command = FFZ.chat_commands[cmd], output; var command = FFZ.ffz_commands[cmd], output;
if ( command ) { if ( command ) {
try { try {
output = command.bind(this)(room, args); output = command.bind(this)(room, args);
@ -100,9 +149,9 @@ FFZ.prototype.run_command = function(text, room_id) {
} }
FFZ.chat_commands.help = function(room, args) { FFZ.ffz_commands.help = function(room, args) {
if ( args && args.length ) { if ( args && args.length ) {
var command = FFZ.chat_commands[args[0].toLowerCase()]; var command = FFZ.ffz_commands[args[0].toLowerCase()];
if ( ! command ) if ( ! command )
return 'There is no "' + args[0] + '" command.'; return 'There is no "' + args[0] + '" command.';
@ -114,13 +163,13 @@ FFZ.chat_commands.help = function(room, args) {
} }
var cmds = []; var cmds = [];
for(var c in FFZ.chat_commands) for(var c in FFZ.ffz_commands)
FFZ.chat_commands.hasOwnProperty(c) && cmds.push(c); FFZ.ffz_commands.hasOwnProperty(c) && cmds.push(c);
return "The available commands are: " + cmds.join(", "); return "The available commands are: " + cmds.join(", ");
} }
FFZ.chat_commands.help.help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command."; FFZ.ffz_commands.help.help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command.";
// -------------------- // --------------------
@ -214,12 +263,20 @@ FFZ.prototype._modify_room = function(room) {
// Track which rooms the user is currently in. // Track which rooms the user is currently in.
init: function() { init: function() {
this._super(); this._super();
f.add_room(this.id, this); try {
f.add_room(this.id, this);
} catch(err) {
f.error("add_room: " + err);
}
}, },
willDestroy: function() { willDestroy: function() {
this._super(); this._super();
f.remove_room(this.id); try {
f.remove_room(this.id);
} catch(err) {
f.error("remove_room: " + err);
}
}, },
getSuggestions: function() { getSuggestions: function() {
@ -228,19 +285,35 @@ FFZ.prototype._modify_room = function(room) {
// filteredSuggestions property of the chat-input component would // filteredSuggestions property of the chat-input component would
// be even better, but I was already hooking the room model. // be even better, but I was already hooking the room model.
var suggestions = this._super(); var suggestions = this._super();
if ( this.settings.capitalize )
suggestions = _.map(suggestions, FFZ.get_capitalization); try {
if ( f.settings.capitalize )
suggestions = _.map(suggestions, FFZ.get_capitalization);
} catch(err) {
f.error("get_suggestions: " + err);
}
return suggestions; return suggestions;
}, },
send: function(text) { send: function(text) {
var cmd = text.split(' ', 1)[0].toLowerCase(); try {
if ( cmd === "/ffz" ) { var cmd = text.split(' ', 1)[0].toLowerCase();
this.set("messageToSend", ""); if ( cmd === "/ffz" ) {
f.run_command(text.substr(5), this.get('id')); this.set("messageToSend", "");
} else f.run_ffz_command(text.substr(5), this.get('id'));
return this._super(text); return;
} else if ( cmd.charAt(0) === "/" && f.run_command(text, this.get('id')) ) {
this.set("messageToSend", "");
return;
}
} catch(err) {
f.error("send: " + err);
}
return this._super(text);
} }
}); });
} }

View file

@ -11,7 +11,11 @@ FFZ.prototype.setup_router = function() {
var f = this; var f = this;
App.__container__.lookup('router:main').reopen({ App.__container__.lookup('router:main').reopen({
ffzTransition: function() { ffzTransition: function() {
f.track_page(); try {
f.track_page();
} catch(err) {
f.error("ffzTransition: " + err);
}
}.on('didTransition') }.on('didTransition')
}); });
} }

View file

@ -18,72 +18,77 @@ FFZ.prototype._modify_viewers = function(controller) {
controller.reopen({ controller.reopen({
lines: function() { lines: function() {
var viewers = this._super(), var viewers = this._super();
categories = [], try {
data = {}, var categories = [],
last_category = null; data = {},
last_category = null;
// Get the broadcaster name. // Get the broadcaster name.
var Channel = App.__container__.lookup('controller:channel'), var Channel = App.__container__.lookup('controller:channel'),
room_id = this.get('parentController.model.id'), room_id = this.get('parentController.model.id'),
broadcaster = Channel && Channel.get('id'); broadcaster = Channel && Channel.get('id');
// We can get capitalization for the broadcaster from the channel. // We can get capitalization for the broadcaster from the channel.
if ( broadcaster ) { if ( broadcaster ) {
var display_name = Channel.get('display_name'); var display_name = Channel.get('display_name');
if ( display_name ) if ( display_name )
FFZ.capitalization[broadcaster] = [display_name, Date.now()]; FFZ.capitalization[broadcaster] = [display_name, Date.now()];
} }
// If the current room isn't the channel's chat, then we shouldn't // If the current room isn't the channel's chat, then we shouldn't
// display them as the broadcaster. // display them as the broadcaster.
if ( room_id != broadcaster ) if ( room_id != broadcaster )
broadcaster = null; broadcaster = null;
// Now, break the viewer array down into something we can use. // Now, break the viewer array down into something we can use.
for(var i=0; i < viewers.length; i++) { for(var i=0; i < viewers.length; i++) {
var entry = viewers[i]; var entry = viewers[i];
if ( entry.category ) { if ( entry.category ) {
last_category = entry.category; last_category = entry.category;
categories.push(last_category); categories.push(last_category);
data[last_category] = []; data[last_category] = [];
} else { } else {
var viewer = entry.chatter.toLowerCase(); var viewer = entry.chatter.toLowerCase();
if ( ! viewer ) if ( ! viewer )
continue;
// If the viewer is the broadcaster, give them their own
// group. Don't put them with normal mods!
if ( viewer == broadcaster ) {
categories.unshift("Broadcaster");
data["Broadcaster"] = [viewer];
} else if ( data.hasOwnProperty(last_category) )
data[last_category].push(viewer);
}
}
// Now, rebuild the viewer list. However, we're going to actually
// sort it this time.
viewers = [];
for(var i=0; i < categories.length; i++) {
var category = categories[i],
chatters = data[category];
if ( ! chatters || ! chatters.length )
continue; continue;
// If the viewer is the broadcaster, give them their own viewers.push({category: category});
// group. Don't put them with normal mods! viewers.push({chatter: ""});
if ( viewer == broadcaster ) {
categories.unshift("Broadcaster");
data["Broadcaster"] = [viewer];
} else if ( data.hasOwnProperty(last_category) ) // Push the chatters, capitalizing them as we go.
data[last_category].push(viewer); chatters.sort();
while(chatters.length) {
var viewer = chatters.shift();
viewer = FFZ.get_capitalization(viewer);
viewers.push({chatter: viewer});
}
} }
}
// Now, rebuild the viewer list. However, we're going to actually } catch(err) {
// sort it this time. f.error("ViewersController lines: " + err);
viewers = [];
for(var i=0; i < categories.length; i++) {
var category = categories[i],
chatters = data[category];
if ( ! chatters || ! chatters.length )
continue;
viewers.push({category: category});
viewers.push({chatter: ""});
// Push the chatters, capitalizing them as we go.
chatters.sort();
while(chatters.length) {
var viewer = chatters.shift();
viewer = FFZ.get_capitalization(viewer);
viewers.push({chatter: viewer});
}
} }
return viewers; return viewers;

View file

@ -23,22 +23,27 @@ FFZ.prototype.setup_bttv = function(delay) {
this.log("BetterTTV was detected after " + delay + "ms. Hooking."); this.log("BetterTTV was detected after " + delay + "ms. Hooking.");
this.has_bttv = true; this.has_bttv = true;
this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString()); // this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString());
// Disable Dark if it's enabled. // Disable Dark if it's enabled.
document.querySelector(".app-main").classList.remove("ffz-dark"); document.body.classList.remove("ffz-dark");
if ( this._dark_style ) { if ( this._dark_style ) {
this._dark_style.parentElement.removeChild(this._dark_style); this._dark_style.parentElement.removeChild(this._dark_style);
delete this._dark_style; delete this._dark_style;
} }
// Disable other features too.
document.body.classList.remove("ffz-chat-colors");
document.body.classList.remove("ffz-chat-background");
// Send Message Behavior // Send Message Behavior
var original_send = BetterTTV.chat.helpers.sendMessage, f = this; var original_send = BetterTTV.chat.helpers.sendMessage, f = this;
BetterTTV.chat.helpers.sendMessage = function(message) { BetterTTV.chat.helpers.sendMessage = function(message) {
var cmd = message.split(' ', 1)[0].toLowerCase(); var cmd = message.split(' ', 1)[0].toLowerCase();
if ( cmd === "/ffz" ) if ( cmd === "/ffz" )
f.run_command(message.substr(5), BetterTTV.chat.store.currentRoom); f.run_ffz_command(message.substr(5), BetterTTV.chat.store.currentRoom);
else else
return original_send(message); return original_send(message);
} }

View file

@ -75,7 +75,7 @@ FFZ.prototype._feature_friday_ui = function(room_id, parent, view) {
btn.innerHTML = "<span>" + message + "</span>"; btn.innerHTML = "<span>" + message + "</span>";
// Track the number of users to click this button. // Track the number of users to click this button.
btn.addEventListener('click', function() { f.track('trackLink', this.href, 'link'); }); // btn.addEventListener('click', function() { f.track('trackLink', this.href, 'link'); });
btnc.appendChild(btn); btnc.appendChild(btn);
parent.appendChild(btnc); parent.appendChild(btnc);

View file

@ -9,6 +9,9 @@ require('./shims');
var FFZ = window.FrankerFaceZ = function() { var FFZ = window.FrankerFaceZ = function() {
FFZ.instance = this; FFZ.instance = this;
// Logging
this._log_data = [];
// Get things started. // Get things started.
this.initialize(); this.initialize();
} }
@ -19,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; }
// Version // Version
var VER = FFZ.version_info = { var VER = FFZ.version_info = {
major: 3, minor: 0, revision: 0, major: 3, minor: 1, revision: 0,
toString: function() { toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
} }
@ -30,6 +33,8 @@ var VER = FFZ.version_info = {
FFZ.prototype.log = function(msg, data, to_json) { FFZ.prototype.log = function(msg, data, to_json) {
msg = "FFZ: " + msg + (to_json ? " -- " + JSON.stringify(data) : ""); msg = "FFZ: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
this._log_data.push(msg);
if ( data !== undefined && console.groupCollapsed && console.dir ) { if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg); console.groupCollapsed(msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 ) if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
@ -43,6 +48,43 @@ FFZ.prototype.log = function(msg, data, to_json) {
} }
FFZ.prototype.error = function(msg, data, to_json) {
msg = "FFZ Error: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
this._log_data.push(msg);
if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
console.log(data);
else
console.dir(data);
console.groupEnd(msg);
} else
console.assert(false, msg);
}
FFZ.prototype.paste_logs = function() {
this._pastebin(this._log_data.join("\n"), function(url) {
if ( ! url )
return console.log("FFZ Error: Unable to upload log to pastebin.");
console.log("FFZ: Your FrankerFaceZ log has been pasted to: " + url);
});
}
FFZ.prototype._pastebin = function(data, callback) {
jQuery.ajax({url: "http://putco.de/", type: "PUT", data: data, context: this})
.success(function(e) {
callback.bind(this)(e.trim() + ".log");
}).fail(function(e) {
callback.bind(this)(null);
});
}
// ------------------- // -------------------
// User Data // User Data
// ------------------- // -------------------
@ -69,14 +111,15 @@ require('./socket');
require('./emoticons'); require('./emoticons');
require('./badges'); require('./badges');
require('./ember/router'); // Analytics: require('./ember/router');
require('./ember/room'); require('./ember/room');
require('./ember/line'); require('./ember/line');
require('./ember/chatview'); require('./ember/chatview');
require('./ember/viewers'); require('./ember/viewers');
require('./ember/moderation-card');
//require('./ember/teams'); //require('./ember/teams');
require('./tracking'); // Analytics: require('./tracking');
require('./debug'); require('./debug');
@ -141,13 +184,14 @@ FFZ.prototype.setup_ember = function(delay) {
this.setup_emoticons(); this.setup_emoticons();
this.setup_badges(); this.setup_badges();
this.setup_piwik(); //this.setup_piwik();
this.setup_router(); //this.setup_router();
this.setup_room(); this.setup_room();
this.setup_line(); this.setup_line();
this.setup_chatview(); this.setup_chatview();
this.setup_viewers(); this.setup_viewers();
this.setup_mod_card();
//this.setup_teams(); //this.setup_teams();

View file

@ -19,7 +19,7 @@ FFZ.settings_info.dark_twitch = {
if ( this.has_bttv ) if ( this.has_bttv )
return; return;
document.querySelector(".app-main").classList.toggle("ffz-dark", val); document.body.classList.toggle("ffz-dark", val);
if ( val ) if ( val )
this._load_dark_css(); this._load_dark_css();
} }
@ -34,7 +34,7 @@ FFZ.prototype.setup_dark = function() {
if ( this.has_bttv ) if ( this.has_bttv )
return; return;
document.querySelector(".app-main").classList.toggle("ffz-dark", this.settings.dark_twitch); document.body.classList.toggle("ffz-dark", this.settings.dark_twitch);
if ( this.settings.dark_twitch ) if ( this.settings.dark_twitch )
this._load_dark_css(); this._load_dark_css();
} }

View file

@ -95,7 +95,7 @@ FFZ.prototype.build_ui_popup = function(view) {
// Add the menu to the DOM. // Add the menu to the DOM.
this._popup = container; this._popup = container;
sub_container.style.maxHeight = Math.max(300, view.$().height() - 212) + "px"; sub_container.style.maxHeight = Math.max(100, view.$().height() - 162) + "px";
view.$('.chat-interface').append(container); view.$('.chat-interface').append(container);
} }
@ -124,31 +124,12 @@ FFZ.prototype._ui_change_page = function(view, menu, container, page) {
FFZ.menu_pages.settings = { FFZ.menu_pages.settings = {
render: function(view, container) { render: function(view, container) {
var menu = document.createElement('div'); var settings = {},
menu.className = 'chat-menu-content'; categories = [];
for(var key in FFZ.settings_info) {
var settings = []; var info = FFZ.settings_info[key],
for(var key in FFZ.settings_info) cat = info.category || "Miscellaneous",
settings.push([key, FFZ.settings_info[key]]); cs = settings[cat];
settings.sort(function(a,b) {
var ai = a[1],
bi = b[1],
an = ai.name.toLowerCase(),
bn = bi.name.toLowerCase();
if ( an < bn ) return -1;
else if ( an > bn ) return 1;
return 0;
});
for(var i=0; i < settings.length; i++) {
var key = settings[i][0],
info = settings[i][1],
el = document.createElement('p'),
val = this.settings.get(key);
if ( info.visible !== undefined && info.visible !== null ) { if ( info.visible !== undefined && info.visible !== null ) {
var visible = info.visible; var visible = info.visible;
@ -159,45 +140,94 @@ FFZ.menu_pages.settings = {
continue; continue;
} }
el.className = 'clearfix'; if ( ! cs ) {
categories.push(cat);
if ( info.type == "boolean" ) { cs = settings[cat] = [];
var swit = document.createElement('a'),
label = document.createElement('span');
swit.className = 'switch';
swit.classList.toggle('active', val);
swit.innerHTML = "<span></span>";
label.className = 'switch-label';
label.innerHTML = info.name;
el.appendChild(swit);
el.appendChild(label);
swit.addEventListener("click", this._ui_toggle_setting.bind(this, swit, key));
} else {
el.classList.add("option");
var link = document.createElement('a');
link.innerHTML = info.name;
link.href = "#";
el.appendChild(link);
link.addEventListener("click", info.method.bind(this));
} }
if ( info.help ) { cs.push([key, info]);
var help = document.createElement('span');
help.className = 'help';
help.innerHTML = info.help;
el.appendChild(help);
}
menu.appendChild(el);
} }
container.appendChild(menu); categories.sort(function(a,b) {
var a = a.toLowerCase(),
b = b.toLowerCase();
if ( a < b ) return -1;
else if ( a > b ) return 1;
return 0;
});
for(var ci=0; ci < categories.length; ci++) {
var category = categories[ci],
cset = settings[category],
menu = document.createElement('div'),
heading = document.createElement('div');
heading.className = 'heading';
menu.className = 'chat-menu-content';
heading.innerHTML = category;
menu.appendChild(heading);
cset.sort(function(a,b) {
var ai = a[1],
bi = b[1],
an = ai.name.toLowerCase(),
bn = bi.name.toLowerCase();
if ( an < bn ) return -1;
else if ( an > bn ) return 1;
return 0;
});
for(var i=0; i < cset.length; i++) {
var key = cset[i][0],
info = cset[i][1],
el = document.createElement('p'),
val = this.settings.get(key);
el.className = 'clearfix';
if ( info.type == "boolean" ) {
var swit = document.createElement('a'),
label = document.createElement('span');
swit.className = 'switch';
swit.classList.toggle('active', val);
swit.innerHTML = "<span></span>";
label.className = 'switch-label';
label.innerHTML = info.name;
el.appendChild(swit);
el.appendChild(label);
swit.addEventListener("click", this._ui_toggle_setting.bind(this, swit, key));
} else {
el.classList.add("option");
var link = document.createElement('a');
link.innerHTML = info.name;
link.href = "#";
el.appendChild(link);
link.addEventListener("click", info.method.bind(this));
}
if ( info.help ) {
var help = document.createElement('span');
help.className = 'help';
help.innerHTML = info.help;
el.appendChild(help);
}
menu.appendChild(el);
}
container.appendChild(menu);
}
}, },
name: "Settings", name: "Settings",
@ -236,8 +266,7 @@ FFZ.menu_pages.channel = {
var room_id = view.get('controller.currentRoom.id'), var room_id = view.get('controller.currentRoom.id'),
room = this.rooms[room_id]; room = this.rooms[room_id];
this.log("Menu for Room: " + room_id, room); //this.track('trackEvent', 'Menu', 'Open', room_id);
this.track('trackEvent', 'Menu', 'Open', room_id);
// Add the header and ad button. // Add the header and ad button.
/*var btn = document.createElement('a'); /*var btn = document.createElement('a');

View file

@ -19,6 +19,7 @@ FFZ.settings_info.highlight_notifications = {
type: "boolean", type: "boolean",
value: false, value: false,
category: "Chat",
visible: function() { return ! this.has_bttv }, visible: function() { return ! this.has_bttv },
name: "Highlight Notifications", name: "Highlight Notifications",
@ -54,7 +55,7 @@ FFZ.settings_info.highlight_notifications = {
// --------------------- // ---------------------
FFZ.ws_commands.message = function(message) { FFZ.ws_commands.message = function(message) {
this.show_mesage(message); this.show_message(message);
} }

View file

@ -19,6 +19,8 @@ FFZ.prototype.setup_races = function() {
FFZ.settings_info.srl_races = { FFZ.settings_info.srl_races = {
type: "boolean", type: "boolean",
value: true, value: true,
category: "Channel Metadata",
name: "SRL Race Information", name: "SRL Race Information",
help: 'Display information about <a href="http://www.speedrunslive.com/" target="_new">SpeedRunsLive</a> races under channels.', help: 'Display information about <a href="http://www.speedrunslive.com/" target="_new">SpeedRunsLive</a> races under channels.',
on_update: function(val) { on_update: function(val) {

View file

@ -11,8 +11,42 @@ var sanitize_cache = {},
else if ( num == 3 ) return '3rd'; else if ( num == 3 ) return '3rd';
else if ( num == null ) return '---'; else if ( num == null ) return '---';
return num + "th"; return num + "th";
},
brighten = function(rgb, amount) {
amount = (amount === 0) ? 0 : (amount || 1);
amount = Math.round(255 * -(amount / 100));
var r = Math.max(0, Math.min(255, rgb[0] - amount)),
g = Math.max(0, Math.min(255, rgb[1] - amount)),
b = Math.max(0, Math.min(255, rgb[2] - amount));
return [r,g,b];
},
rgb_to_css = function(rgb) {
return "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
},
darken = function(rgb, amount) {
amount = (amount === 0) ? 0 : (amount || 1);
return brighten(rgb, -amount);
},
get_luminance = function(rgb) {
rgb = [rgb[0]/255, rgb[1]/255, rgb[2]/255];
for (var i =0; i<rgb.length; i++) {
if (rgb[i] <= 0.03928) {
rgb[i] = rgb[i] / 12.92;
} else {
rgb[i] = Math.pow( ((rgb[i]+0.055)/1.055), 2.4 );
}
}
var l = (0.2126 * rgb[0]) + (0.7152 * rgb[1]) + (0.0722 * rgb[2]);
return l;
}; };
module.exports = { module.exports = {
update_css: function(element, id, css) { update_css: function(element, id, css) {
var all = element.innerHTML, var all = element.innerHTML,
@ -34,6 +68,11 @@ module.exports = {
element.innerHTML = all; element.innerHTML = all;
}, },
get_luminance: get_luminance,
brighten: brighten,
darken: darken,
rgb_to_css: rgb_to_css,
number_commas: function(x) { number_commas: function(x) {
var parts = x.toString().split("."); var parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");

155
style.css
View file

@ -16,8 +16,17 @@
.ffz-ui-toggle svg.svg-emoticons path { fill: rgba(0,0,0,0.2); } .ffz-ui-toggle svg.svg-emoticons path { fill: rgba(0,0,0,0.2); }
.ffz-ui-toggle:hover svg.svg-emoticons path { fill: rgba(0,0,0,0.5); } .ffz-ui-toggle:hover svg.svg-emoticons path { fill: rgba(0,0,0,0.5); }
.chat-container.dark .ffz-ui-toggle svg.svg-emoticons path,.app-main.theatre .ffz-ui-toggle svg.svg-emoticons path,.ffz-ui-toggle.dark svg.svg-emoticons path { fill: #888; }
.chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path,.app-main.theatre .ffz-ui-toggle:hover svg.svg-emoticons path,.ffz-ui-toggle.dark:hover svg.svg-emoticons path { fill: #777; } .ember-chat-container.dark .ffz-ui-toggle svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle svg.svg-emoticons path,
.ffz-ui-toggle.dark svg.svg-emoticons path { fill: #888; }
.ember-chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle:hover svg.svg-emoticons path,
.ffz-ui-toggle.dark:hover svg.svg-emoticons path { fill: #777; }
.ffz-ui-toggle.no-emotes svg.svg-emoticons path { fill: rgba(80,0,0,0.2); } .ffz-ui-toggle.no-emotes svg.svg-emoticons path { fill: rgba(80,0,0,0.2); }
.ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path { fill: rgba(80,0,0,0.5); } .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path { fill: rgba(80,0,0,0.5); }
@ -28,14 +37,37 @@
.ffz-ui-toggle.blue.live svg.svg-emoticons path { fill: rgba(47,88,185,0.5); } .ffz-ui-toggle.blue.live svg.svg-emoticons path { fill: rgba(47,88,185,0.5); }
.ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: rgba(47,88,185,1); } .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: rgba(47,88,185,1); }
.app-main.theatre .ffz-ui-toggle.no-emotes svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.no-emotes svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.no-emotes svg.svg-emoticons path { fill: #543f3f; }
.app-main.theatre .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.no-emotes:hover svg.svg-emoticons path { fill: #453434; }
.app-main.theatre .ffz-ui-toggle.live svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.live svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.live svg.svg-emoticons path { fill: #5b4487; } .ember-chat-container.dark .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.live:hover svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.live:hover svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.live:hover svg.svg-emoticons path { fill: #513c78; } .app-main.theatre .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.no-emotes svg.svg-emoticons path { fill: #453434; }
.ember-chat-container.dark .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.no-emotes:hover svg.svg-emoticons path { fill: #543f3f; }
.ember-chat-container.dark .ffz-ui-toggle.live svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.live svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.live svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.live svg.svg-emoticons path { fill: #513c78; }
.ember-chat-container.dark .ffz-ui-toggle.live:hover svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.live:hover svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.live:hover svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.live:hover svg.svg-emoticons path { fill: #5b4487; }
.ember-chat-container.dark .ffz-ui-toggle.blue.live svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.blue.live svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.blue.live svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.blue.live svg.svg-emoticons path { fill: #3c4e78; }
.ember-chat-container.dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.blue.live:hover svg.svg-emoticons path { fill: #445887; }
.app-main.theatre .ffz-ui-toggle.blue.live svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.blue.live svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.blue.live svg.svg-emoticons path { fill: #445887; }
.app-main.theatre .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.blue.live:hover svg.svg-emoticons path { fill: #3c4e78; }
.ffz-ui-toggle.live { .ffz-ui-toggle.live {
animation: ffzfade 8s linear infinite; animation: ffzfade 8s linear infinite;
@ -158,7 +190,9 @@
fill: rgba(255,255,255,0.35) !important; fill: rgba(255,255,255,0.35) !important;
} }
.app-main.theatre .follow-button .notify:before, .app-main.theatre .button.drop:after, .app-main.theatre .follow-button .drop.follow:after { .app-main.theatre .follow-button .notify:before,
.app-main.theatre .button.drop:after,
.app-main.theatre .follow-button .drop.follow:after {
border: 5px solid rgba(255,255,255,0.35); border: 5px solid rgba(255,255,255,0.35);
border-left-color: transparent; border-left-color: transparent;
border-right-color: transparent; border-right-color: transparent;
@ -258,7 +292,8 @@
color: inherit; color: inherit;
} }
#ffz-race-popup a.twitch, #ffz-race-popup a.hitbox { #ffz-race-popup a.twitch,
#ffz-race-popup a.hitbox {
display: inline-block; display: inline-block;
height: 16px; height: 16px;
margin: 0 5px; margin: 0 5px;
@ -287,12 +322,24 @@
/* Menu Options */ /* Menu Options */
@media screen and (max-width: 369px) {
.ember-chat-container .ember-chat .chat-interface .emoticon-selector {
right: -10px;
}
}
.ffz-ui-menu-page { .ffz-ui-menu-page {
overflow-y: auto; overflow-y: auto;
padding: 0 20px; padding: 0 20px;
margin: 0 -20px; margin: 0 -20px;
} }
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading {
margin-bottom: 5px;
border-bottom: 1px solid rgba(0,0,0, 0.2);
}
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content { .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content {
padding: 10px 0; padding: 10px 0;
background-color: transparent; background-color: transparent;
@ -341,7 +388,7 @@
margin-bottom: -1px; margin-bottom: -1px;
cursor: pointer; cursor: pointer;
border-left: 1px solid rgba(0,0,0,0.2); border-left: 1px solid rgba(0,0,0,0.2);
border-bottom: 1px solid rgba(0,0,0,0.2); border-bottom: 1px solid transparent;
} }
.ffz-ui-popup ul.menu li.active { .ffz-ui-popup ul.menu li.active {
@ -375,13 +422,79 @@
background-color: rgba(255,255,255, 0.75); background-color: rgba(255,255,255, 0.75);
} }
.ffz-chat-background.theatre .chat-container .chat-line .mentioned, .ffz-chat-background .chat-container.dark .chat-line .mentioned { .ffz-chat-background .app-main.theatre .chat-container .chat-line .mentioned,
.ffz-chat-background .ember-chat-container.dark .chat-line .mentioned,
.ffz-chat-background .chat-container.dark .chat-line .mentioned {
color: #8c8c9c; color: #8c8c9c;
background-color: rgba(16,16,20, 0.75); background-color: rgba(16,16,20, 0.75);
} }
/* Fix Moderation Cards */
.ember-chat .moderation-card {
box-shadow: #808080 0 0 5px;
}
.ember-chat .moderation-card button {
margin: 0;
padding: 0 5px;
}
.ember-chat .moderation-card button:not(.glyph-only):hover,
.ember-chat .moderation-card button:not(.glyph-only):focus {
color: #fff;
background-color: rgba(117,80,186, 1);
}
.ember-chat .moderation-card button.message {
height: 30px; width: 28px;
}
.ember-chat .ffz-moderation-card .interface .mod-controls:last-of-type,
.ember-chat .moderation-card .interface span.right {
float: right;
}
.ember-chat .moderation-card:focus {
outline: none;
box-shadow: #000 0 0 5px;
}
.ember-chat-container.dark .ember-chat .moderation-card:focus,
.chat-container.dark .ember-chat .moderation-card:focus,
.app-main.theatre .ember-chat .moderation-card:focus {
box-shadow: #fff 0 0 5px;
}
.ember-chat-container.dark .ember-chat .moderation-card .interface,
.chat-container.dark .ember-chat .moderation-card .interface,
.app-main.theatre .ember-chat .moderation-card .interface {
background-color: #232329;
}
.ember-chat .ffz-moderation-card .interface:not(:last-of-type) {
border-bottom: none;
padding-bottom: 0;
}
.ember-chat .moderation-card .interface {
border-top: none;
}
.ember-chat .moderation-card h3.name {
display: inline-block;
text-shadow: black 0 0 5px;
}
.ember-chat .moderation-card .channel_background {
width: 100%;
top: 0;
}
/* Chat Rows */ /* Chat Rows */
.ffz-chat-background .more-messages-indicator { .ffz-chat-background .more-messages-indicator {
/* This looks better when it's full width. */ /* This looks better when it's full width. */
margin: 0 -20px; margin: 0 -20px;
@ -404,10 +517,6 @@
background-color: rgba(0,0,0, 0.1); background-color: rgba(0,0,0, 0.1);
} }
.theatre.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-alternate, .ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate {
background-color: rgba(255,255,255, 0.05);
}
.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned { .ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned {
background-color: rgba(255,127,127, 0.2); background-color: rgba(255,127,127, 0.2);
} }
@ -416,10 +525,20 @@
background-color: rgba(255,127,127, 0.4); background-color: rgba(255,127,127, 0.4);
} }
.theatre.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned, .ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned { .ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-alternate,
.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate,
.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate {
background-color: rgba(255,255,255, 0.05);
}
.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-mentioned,
.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned,
.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned {
background-color: rgba(255,0,0, 0.2); background-color: rgba(255,0,0, 0.2);
} }
.theatre.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate, .ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate { .ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate,
.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate,
.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate {
background-color: rgba(255,0,0, 0.3); background-color: rgba(255,0,0, 0.3);
} }