mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
Way too much stuff for one commit. I'm a terrible person.
This commit is contained in:
parent
0f5e3915ad
commit
fc21671b2c
18 changed files with 2204 additions and 476 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
build
|
||||
Extension Building
|
||||
Extension Building
|
||||
.idea
|
5
script.min.js
vendored
5
script.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -10,5 +10,6 @@ module.exports = {
|
|||
CHAT_BUTTON: '<svg class="svg-emoticons ffz-svg" height="18px" width="24px" viewBox="0 0 249 195" version="1.1">' + SVGPATH + '</svg>',
|
||||
|
||||
GEAR: '<svg class="svg-gear" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M15,7v2h-2.115c-0.125,0.615-0.354,1.215-0.713,1.758l1.484,1.484l-1.414,1.414l-1.484-1.484C10.215,12.531,9.615,12.76,9,12.885V15H7v-2.12c-0.614-0.126-1.21-0.356-1.751-0.714l-1.491,1.49l-1.414-1.414l1.491-1.49C3.477,10.211,3.247,9.613,3.12,9H1V7h2.116C3.24,6.384,3.469,5.785,3.829,5.242L2.343,3.757l1.414-1.414l1.485,1.485C5.785,3.469,6.384,3.24,7,3.115V1h2v2.12c0.613,0.126,1.211,0.356,1.752,0.714l1.49-1.491l1.414,1.414l-1.49,1.492C12.523,5.79,12.754,6.387,12.88,7H15z M8,6C6.896,6,6,6.896,6,8s0.896,2,2,2s2-0.896,2-2S9.104,6,8,6z" fill-rule="evenodd"></path></svg>',
|
||||
HEART: '<svg class="svg-heart" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M8,13.5L1.5,7V4l2-2h3L8,3.5L9.5,2h3l2,2v3L8,13.5z" fill-rule="evenodd"></path></svg>'
|
||||
HEART: '<svg class="svg-heart" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M8,13.5L1.5,7V4l2-2h3L8,3.5L9.5,2h3l2,2v3L8,13.5z" fill-rule="evenodd"></path></svg>',
|
||||
EMOTE: '<svg class="svg-emote" height="16px" version="1.1" viewBox="0 0 18 18" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M9,18c-4.971,0-9-4.029-9-9s4.029-9,9-9s9,4.029,9,9S13.971,18,9,18z M14,4.111V4h-0.111C12.627,2.766,10.904,2,9,2C7.095,2,5.373,2.766,4.111,4H4v0.111C2.766,5.373,2,7.096,2,9s0.766,3.627,2,4.889V14l0.05-0.051C5.317,15.217,7.067,16,9,16c1.934,0,3.684-0.783,4.949-2.051L14,14v-0.111c1.234-1.262,2-2.984,2-4.889S15.234,5.373,14,4.111zM11,6h2v4h-2V6z M12.535,12.535C11.631,13.44,10.381,14,9,14s-2.631-0.56-3.536-1.465l0.707-0.707C6.896,12.553,7.896,13,9,13s2.104-0.447,2.828-1.172L12.535,12.535z M5,6h2v4H5V6z" fill-rule="evenodd"></path></svg>'
|
||||
}
|
22
src/debug.js
22
src/debug.js
|
@ -2,9 +2,25 @@ var FFZ = window.FrankerFaceZ;
|
|||
|
||||
|
||||
// -----------------------
|
||||
// Developer Mode Command
|
||||
// Developer Mode
|
||||
// -----------------------
|
||||
|
||||
FFZ.settings_info.developer_mode = {
|
||||
type: "boolean",
|
||||
value: false,
|
||||
storage_key: "ffzDebugMode",
|
||||
|
||||
visible: function() { return this.settings.developer_mode || (Date.now() - parseInt(localStorage.ffzLastDevMode || "0")) < 604800000; },
|
||||
category: "Debugging",
|
||||
name: "Developer Mode",
|
||||
help: "Load FrankerFaceZ from the local development server instead of the CDN. Please refresh after changing this setting.",
|
||||
|
||||
on_update: function() {
|
||||
localStorage.ffzLastDevMode = Date.now();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
FFZ.ffz_commands.developer_mode = function(room, args) {
|
||||
var enabled, args = args && args.length ? args[0].toLowerCase() : null;
|
||||
if ( args == "y" || args == "yes" || args == "true" || args == "on" )
|
||||
|
@ -13,9 +29,9 @@ FFZ.ffz_commands.developer_mode = function(room, args) {
|
|||
enabled = false;
|
||||
|
||||
if ( enabled === undefined )
|
||||
return "Developer Mode is currently " + (localStorage.ffzDebugMode == "true" ? "enabled." : "disabled.");
|
||||
return "Developer Mode is currently " + (this.settings.developer_mode ? "enabled." : "disabled.");
|
||||
|
||||
localStorage.ffzDebugMode = enabled;
|
||||
this.settings.set("developer_mode", enabled);
|
||||
return "Developer Mode is now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser.";
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,10 @@ FFZ.prototype.setup_chatview = function() {
|
|||
|
||||
// For some reason, this doesn't work unless we create an instance of the
|
||||
// chat view and then destroy it immediately.
|
||||
Chat.create().destroy();
|
||||
try {
|
||||
Chat.create().destroy();
|
||||
} catch(err) { }
|
||||
|
||||
|
||||
// Modify all existing Chat views.
|
||||
for(var key in Ember.View.views) {
|
||||
|
|
|
@ -3,6 +3,80 @@ var FFZ = window.FrankerFaceZ,
|
|||
|
||||
reg_escape = function(str) {
|
||||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
||||
},
|
||||
|
||||
SEPARATORS = "[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]",
|
||||
SPLITTER = new RegExp(SEPARATORS + "*," + SEPARATORS + "*"),
|
||||
|
||||
quote_attr = function(attr) {
|
||||
return (attr + '')
|
||||
.replace(/&/g, "&")
|
||||
.replace(/'/g, "'")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
},
|
||||
|
||||
|
||||
TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/",
|
||||
build_srcset = function(id) {
|
||||
return TWITCH_BASE + id + "/1.0 1x, " + TWITCH_BASE + id + "/2.0 2x, " + TWITCH_BASE + id + "/3.0 4x";
|
||||
},
|
||||
|
||||
|
||||
data_to_tooltip = function(data) {
|
||||
var output = "<tr><td>Emoticon</td><td>" + data.code + "</td></tr>",
|
||||
set = data.set,
|
||||
set_type = data.set_type;
|
||||
|
||||
if ( set_type === undefined )
|
||||
set_type = "Channel";
|
||||
|
||||
if ( ! set )
|
||||
return data.code;
|
||||
|
||||
else if ( set == "00000turbo" || set == "turbo" ) {
|
||||
set = "Twitch Turbo";
|
||||
set_type = null;
|
||||
}
|
||||
|
||||
if ( ! set_type )
|
||||
output += '<tr><td class="center" colspan="2">' + set + '</td></tr>';
|
||||
else
|
||||
output += "<tr><td>" + set_type + "</td><td>" + set + "</td></tr>";
|
||||
|
||||
return '<table class="emote-data">' + output + '</table>';
|
||||
},
|
||||
|
||||
build_tooltip = function(id) {
|
||||
var emote_data = this._twitch_emotes[id],
|
||||
set = emote_data ? emote_data.set : null;
|
||||
|
||||
if ( ! emote_data )
|
||||
return "???";
|
||||
|
||||
if ( typeof emote_data == "string" )
|
||||
return emote_data;
|
||||
|
||||
if ( emote_data.tooltip )
|
||||
return emote_data.tooltip;
|
||||
|
||||
return emote_data.tooltip = data_to_tooltip(emote_data);
|
||||
},
|
||||
|
||||
load_emote_data = function(id, code, success, data) {
|
||||
if ( ! success )
|
||||
return;
|
||||
|
||||
if ( code )
|
||||
data.code = code;
|
||||
|
||||
this._twitch_emotes[id] = data;
|
||||
var tooltip = build_tooltip.bind(this)(id);
|
||||
|
||||
var images = document.querySelectorAll('img[emote-id="' + id + '"]');
|
||||
for(var x=0; x < images.length; x++)
|
||||
images[x].title = tooltip;
|
||||
};
|
||||
|
||||
|
||||
|
@ -22,6 +96,37 @@ FFZ.settings_info.capitalize = {
|
|||
};
|
||||
|
||||
|
||||
FFZ.settings_info.banned_words = {
|
||||
type: "button",
|
||||
value: [],
|
||||
|
||||
category: "Chat",
|
||||
visible: function() { return ! this.has_bttv },
|
||||
|
||||
name: "Banned Words",
|
||||
help: "Set a list of words that will be locally removed from chat messages.",
|
||||
|
||||
method: function() {
|
||||
var old_val = this.settings.banned_words.join(", "),
|
||||
new_val = prompt("Banned Words\n\nPlease enter a comma-separated list of words that you would like to be removed from chat messages.", old_val);
|
||||
|
||||
if ( new_val === null || new_val === undefined )
|
||||
return;
|
||||
|
||||
new_val = new_val.trim().split(SPLITTER);
|
||||
var vals = [];
|
||||
|
||||
for(var i=0; i < new_val.length; i++)
|
||||
new_val[i] && vals.push(new_val[i]);
|
||||
|
||||
if ( vals.length == 1 && vals[0] == "disable" )
|
||||
vals = [];
|
||||
|
||||
this.settings.set("banned_words", vals);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
FFZ.settings_info.keywords = {
|
||||
type: "button",
|
||||
value: [],
|
||||
|
@ -40,12 +145,16 @@ FFZ.settings_info.keywords = {
|
|||
return;
|
||||
|
||||
// Split them up.
|
||||
new_val = new_val.trim().split(/\W*,\W*/);
|
||||
new_val = new_val.trim().split(SPLITTER);
|
||||
var vals = [];
|
||||
|
||||
if ( new_val.length == 1 && (new_val[0] == "" || new_val[0] == "disable") )
|
||||
new_val = [];
|
||||
for(var i=0; i < new_val.length; i++)
|
||||
new_val[i] && vals.push(new_val[i]);
|
||||
|
||||
this.settings.set("keywords", new_val);
|
||||
if ( vals.length == 1 && vals[0] == "disable" )
|
||||
vals = [];
|
||||
|
||||
this.settings.set("keywords", vals);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -106,6 +215,10 @@ FFZ.prototype.setup_line = function() {
|
|||
document.head.appendChild(s);
|
||||
|
||||
|
||||
// Emoticon Data
|
||||
this._twitch_emotes = {};
|
||||
|
||||
|
||||
this.log("Hooking the Ember Line controller.");
|
||||
|
||||
var Line = App.__container__.resolve('controller:line'),
|
||||
|
@ -117,6 +230,7 @@ FFZ.prototype.setup_line = function() {
|
|||
var tokens = this._super();
|
||||
|
||||
try {
|
||||
tokens = f._remove_banned(tokens);
|
||||
tokens = f._emoticonize(this, tokens);
|
||||
var user = f.get_user();
|
||||
|
||||
|
@ -143,11 +257,12 @@ FFZ.prototype.setup_line = function() {
|
|||
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'),
|
||||
controller = this.get('context'),
|
||||
user = controller.get('model.from'),
|
||||
room = controller.get('parentController.content.id'),
|
||||
color = controller.get('model.color'),
|
||||
|
||||
row_type = this.get('context.model.ffz_alternate');
|
||||
row_type = controller.get('model.ffz_alternate');
|
||||
|
||||
|
||||
// Color Processing
|
||||
|
@ -210,6 +325,107 @@ FFZ.prototype.setup_line = function() {
|
|||
// Mark that we've checked this message for mentions.
|
||||
this.set('context.model.ffz_notified', true);
|
||||
|
||||
|
||||
// Banned Links
|
||||
var bad_links = el.querySelectorAll('a.deleted-link');
|
||||
for(var i=0; i < bad_links.length; i++) {
|
||||
var link = bad_links[i];
|
||||
|
||||
link.addEventListener("click", function(e) {
|
||||
if ( ! this.classList.contains("deleted-link") )
|
||||
return true;
|
||||
|
||||
// Get the URL
|
||||
var href = this.getAttribute('data-url'),
|
||||
link = href;
|
||||
|
||||
// Delete Old Stuff
|
||||
this.classList.remove('deleted-link');
|
||||
this.removeAttribute("data-url");
|
||||
this.removeAttribute("title");
|
||||
this.removeAttribute("original-title");
|
||||
|
||||
// Process URL
|
||||
if ( href.indexOf("@") > -1 && (-1 === href.indexOf("/") || href.indexOf("@") < href.indexOf("/")) )
|
||||
href = "mailto:" + href;
|
||||
else if ( ! href.match(/^https?:\/\//) )
|
||||
href = "http://" + href;
|
||||
|
||||
// Set up the Link
|
||||
this.href = href;
|
||||
this.target = "_new";
|
||||
this.textContent = link;
|
||||
|
||||
// Stop from Navigating
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// Also add a nice tooltip.
|
||||
jQuery(link).tipsy();
|
||||
}
|
||||
|
||||
|
||||
// Enhanced Emotes
|
||||
var images = el.querySelectorAll('img');
|
||||
for(var i=0; i < images.length; i++) {
|
||||
var img = images[i],
|
||||
name = img.alt,
|
||||
match = /\/emoticons\/v1\/(\d+)\/1\.0/.exec(img.src),
|
||||
id = match ? parseInt(match[1]) : null;
|
||||
|
||||
if ( id !== null ) {
|
||||
// High-DPI Images
|
||||
img.setAttribute('srcset', build_srcset(id));
|
||||
img.setAttribute('emote-id', id);
|
||||
|
||||
// Source Lookup
|
||||
var emote_data = f._twitch_emotes[id];
|
||||
if ( emote_data ) {
|
||||
if ( typeof emote_data != "string" )
|
||||
img.title = emote_data.tooltip;
|
||||
|
||||
} else {
|
||||
f._twitch_emotes[id] = img.alt;
|
||||
f.ws_send("twitch_emote", id, load_emote_data.bind(f, id, img.alt));
|
||||
}
|
||||
|
||||
jQuery(img).tipsy({html:true});
|
||||
|
||||
} else if ( img.getAttribute('data-ffz-emote') ) {
|
||||
var data = JSON.parse(decodeURIComponent(img.getAttribute('data-ffz-emote'))),
|
||||
id = data && data[0] || null,
|
||||
set_id = data && data[1] || null,
|
||||
|
||||
set = f.emote_sets[set_id],
|
||||
emote = set ? set.emotes[id] : null,
|
||||
|
||||
set_name = set.id,
|
||||
set_type = "FFZ Channel";
|
||||
|
||||
if ( set.id == "global" ) {
|
||||
set_name = "FrankerFaceZ Global";
|
||||
set_type = null;
|
||||
|
||||
} else if ( set.id == "globalevent" ) {
|
||||
set_name = "FrankerFaceZ Event";
|
||||
set_type = null;
|
||||
|
||||
} else if ( f.feature_friday && set.id == f.feature_friday.set )
|
||||
set_name = "Feature Friday - " + f.feature_friday.channel;
|
||||
|
||||
img.title = data_to_tooltip({
|
||||
code: emote.hidden ? "???" : emote.name,
|
||||
set: set_name,
|
||||
set_type: set_type
|
||||
});
|
||||
|
||||
jQuery(img).tipsy({html:true});
|
||||
|
||||
} else
|
||||
jQuery(img).tipsy();
|
||||
}
|
||||
|
||||
|
||||
} catch(err) {
|
||||
try {
|
||||
f.error("LineView didInsertElement: " + err);
|
||||
|
@ -288,7 +504,6 @@ FFZ.prototype._handle_color = function(color) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// ---------------------
|
||||
// Capitalization
|
||||
// ---------------------
|
||||
|
@ -298,7 +513,7 @@ FFZ._cap_fetching = 0;
|
|||
|
||||
FFZ.get_capitalization = function(name, callback) {
|
||||
// Use the BTTV code if it's present.
|
||||
if ( window.BetterTTV )
|
||||
if ( window.BetterTTV && BetterTTV.chat && BetterTTV.chat.helpers.lookupDisplayName )
|
||||
return BetterTTV.chat.helpers.lookupDisplayName(name);
|
||||
|
||||
if ( ! name )
|
||||
|
@ -314,15 +529,14 @@ FFZ.get_capitalization = function(name, callback) {
|
|||
return old_data[0];
|
||||
}
|
||||
|
||||
if ( FFZ._cap_fetching < 5 ) {
|
||||
if ( FFZ._cap_fetching < 25 ) {
|
||||
FFZ._cap_fetching++;
|
||||
Twitch.api.get("users/" + name)
|
||||
.always(function(data) {
|
||||
var cap_name = data.display_name || name;
|
||||
FFZ.capitalization[name] = [cap_name, Date.now()];
|
||||
FFZ._cap_fetching--;
|
||||
typeof callback === "function" && callback(cap_name);
|
||||
});
|
||||
FFZ.get().ws_send("get_display_name", name, function(success, data) {
|
||||
var cap_name = success ? data : name;
|
||||
FFZ.capitalization[name] = [cap_name, Date.now()];
|
||||
FFZ._cap_fetching--;
|
||||
typeof callback === "function" && callback(cap_name);
|
||||
});
|
||||
}
|
||||
|
||||
return old_data ? old_data[0] : name;
|
||||
|
@ -331,7 +545,7 @@ FFZ.get_capitalization = function(name, callback) {
|
|||
|
||||
FFZ.prototype.capitalize = function(view, user) {
|
||||
var name = FFZ.get_capitalization(user, this.capitalize.bind(this, view));
|
||||
if ( name )
|
||||
if ( name && view )
|
||||
view.$('.from').text(name);
|
||||
}
|
||||
|
||||
|
@ -346,8 +560,21 @@ FFZ._get_regex = function(word) {
|
|||
return FFZ._regex_cache[word] = FFZ._regex_cache[word] || RegExp("\\b" + reg_escape(word) + "\\b", "ig");
|
||||
}
|
||||
|
||||
FFZ._mentions_to_regex = function(list) {
|
||||
return FFZ._regex_cache[list] = FFZ._regex_cache[list] || RegExp("\\b(?:" + _.chain(list).map(reg_escape).value().join("|") + ")\\b", "ig");
|
||||
FFZ._words_to_regex = function(list) {
|
||||
var regex = FFZ._regex_cache[list];
|
||||
if ( ! regex ) {
|
||||
var reg = "";
|
||||
for(var i=0; i < list.length; i++) {
|
||||
if ( ! list[i] )
|
||||
continue;
|
||||
|
||||
reg += (reg ? "|" : "") + reg_escape(list[i]);
|
||||
}
|
||||
|
||||
regex = FFZ._regex_cache[list] = new RegExp("(^|.*?" + SEPARATORS + ")(" + reg + ")(?=$|" + SEPARATORS + ")", "ig");
|
||||
}
|
||||
|
||||
return regex;
|
||||
}
|
||||
|
||||
|
||||
|
@ -359,24 +586,72 @@ FFZ.prototype._mentionize = function(controller, tokens) {
|
|||
if ( typeof tokens == "string" )
|
||||
tokens = [tokens];
|
||||
|
||||
var regex = FFZ._mentions_to_regex(mention_words);
|
||||
var regex = FFZ._words_to_regex(mention_words),
|
||||
new_tokens = [];
|
||||
|
||||
return _.chain(tokens).map(function(token) {
|
||||
if ( !_.isString(token) )
|
||||
return token;
|
||||
else if ( !token.match(regex) )
|
||||
return [token];
|
||||
for(var i=0; i < tokens.length; i++) {
|
||||
var token = tokens[i];
|
||||
if ( ! _.isString(token) ) {
|
||||
new_tokens.push(token);
|
||||
continue;
|
||||
}
|
||||
|
||||
return _.zip(
|
||||
_.map(token.split(regex), _.identity),
|
||||
_.map(token.match(regex), function(e) {
|
||||
return {
|
||||
mentionedUser: e,
|
||||
own: false
|
||||
};
|
||||
})
|
||||
);
|
||||
}).flatten().compact().value();
|
||||
if ( ! token.match(regex) ) {
|
||||
new_tokens.push(token);
|
||||
continue;
|
||||
}
|
||||
|
||||
token = token.replace(regex, function(all, prefix, match) {
|
||||
new_tokens.push(prefix);
|
||||
new_tokens.push({
|
||||
mentionedUser: match,
|
||||
own: false
|
||||
});
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
if ( token )
|
||||
new_tokens.push(token);
|
||||
}
|
||||
|
||||
return new_tokens;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------
|
||||
// Banned Words
|
||||
// ---------------------
|
||||
|
||||
FFZ.prototype._remove_banned = function(tokens) {
|
||||
var banned_words = this.settings.banned_words;
|
||||
if ( ! banned_words || ! banned_words.length )
|
||||
return tokens;
|
||||
|
||||
if ( typeof tokens == "string" )
|
||||
tokens = [tokens];
|
||||
|
||||
var regex = FFZ._words_to_regex(banned_words),
|
||||
new_tokens = [];
|
||||
|
||||
for(var i=0; i < tokens.length; i++) {
|
||||
var token = tokens[i];
|
||||
if ( ! _.isString(token ) ) {
|
||||
if ( token.emoticonSrc && regex.test(token.altText) )
|
||||
new_tokens.push(token.altText.replace(regex, "$1***"));
|
||||
else if ( token.isLink && regex.test(token.href) )
|
||||
new_tokens.push({
|
||||
mentionedUser: '</span><a class="deleted-link" title="' + quote_attr(token.href.replace(regex, "$1***")) + '" data-url="' + quote_attr(token.href) + '" href="#"><banned link></a><span class="mentioning">',
|
||||
own: true
|
||||
});
|
||||
else
|
||||
new_tokens.push(token);
|
||||
|
||||
} else
|
||||
new_tokens.push(token.replace(regex, "$1***"));
|
||||
}
|
||||
|
||||
return new_tokens;
|
||||
}
|
||||
|
||||
|
||||
|
@ -421,7 +696,7 @@ FFZ.prototype._emoticonize = function(controller, tokens) {
|
|||
// emoticon.
|
||||
_.each(emotes, function(emote) {
|
||||
//var eo = {isEmoticon:true, cls: emote.klass};
|
||||
var eo = {isEmoticon:true, cls: emote.klass, emoticonSrc: emote.url, altText: (emote.hidden ? "???" : emote.name)};
|
||||
var eo = {isEmoticon:true, cls: emote.klass, emoticonSrc: emote.url + '" data-ffz-emote="' + encodeURIComponent(JSON.stringify([emote.id, emote.set_id])), altText: (emote.hidden ? "???" : emote.name)};
|
||||
|
||||
tokens = _.compact(_.flatten(_.map(tokens, function(token) {
|
||||
if ( _.isObject(token) )
|
||||
|
|
|
@ -31,7 +31,7 @@ FFZ.settings_info.enhanced_moderation = {
|
|||
category: "Chat",
|
||||
|
||||
name: "Enhanced Moderation",
|
||||
help: "Use /p, /t, /u and /b in chat to moderator, or use hotkeys with moderation cards."
|
||||
help: "Use /p, /t, /u and /b in chat to moderate chat, or use hotkeys with moderation cards."
|
||||
};
|
||||
|
||||
|
||||
|
@ -236,7 +236,7 @@ FFZ.chat_commands.b.enabled = function() { return this.settings.enhanced_moderat
|
|||
|
||||
FFZ.chat_commands.u = function(room, args) {
|
||||
if ( ! args || ! args.length )
|
||||
return "Unban Usage: /b username [more usernames separated by spaces]";
|
||||
return "Unban Usage: /u username [more usernames separated by spaces]";
|
||||
|
||||
if ( args.length > 10 )
|
||||
return "Please only unban up to 10 users at once.";
|
||||
|
|
|
@ -179,7 +179,7 @@ FFZ.prototype._legacy_load_css = function(set_id, callback, data) {
|
|||
margins = check_margins(margins, height);
|
||||
var hidden = path.substr(path.lastIndexOf("/") + 1, 1) === ".",
|
||||
id = ++f._last_emote_id,
|
||||
emote = {id: id, hidden: hidden, name: name, height: height, width: width, url: path, margins: margins, extra_css: extra};
|
||||
emote = {id: id, set_id: set_id, hidden: hidden, name: name, height: height, width: width, url: path, margins: margins, extra_css: extra};
|
||||
|
||||
emotes[id] = emote;
|
||||
return "";
|
||||
|
|
11
src/main.js
11
src/main.js
|
@ -22,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; }
|
|||
|
||||
// Version
|
||||
var VER = FFZ.version_info = {
|
||||
major: 3, minor: 1, revision: 0,
|
||||
major: 3, minor: 2, revision: 1,
|
||||
toString: function() {
|
||||
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
|
||||
}
|
||||
|
@ -105,9 +105,12 @@ FFZ.prototype.get_user = function() {
|
|||
|
||||
//require('./templates');
|
||||
|
||||
// Import these first to set up data structures
|
||||
require('./ui/menu');
|
||||
require('./settings');
|
||||
|
||||
require('./socket');
|
||||
|
||||
|
||||
require('./emoticons');
|
||||
require('./badges');
|
||||
|
||||
|
@ -134,8 +137,9 @@ require('./ui/notifications');
|
|||
require('./ui/viewer_count');
|
||||
|
||||
require('./ui/menu_button');
|
||||
require('./ui/menu');
|
||||
require('./ui/races');
|
||||
require('./ui/my_emotes');
|
||||
require('./ui/about_page');
|
||||
|
||||
require('./commands');
|
||||
|
||||
|
@ -198,6 +202,7 @@ FFZ.prototype.setup_ember = function(delay) {
|
|||
this.setup_notifications();
|
||||
this.setup_css();
|
||||
this.setup_menu();
|
||||
this.setup_my_emotes();
|
||||
this.setup_races();
|
||||
|
||||
this.find_bttv(10);
|
||||
|
|
173
src/settings.js
173
src/settings.js
|
@ -1,8 +1,15 @@
|
|||
var FFZ = window.FrankerFaceZ,
|
||||
constants = require("./constants");
|
||||
|
||||
|
||||
make_ls = function(key) {
|
||||
return "ffz_setting_" + key;
|
||||
},
|
||||
|
||||
toggle_setting = function(swit, key) {
|
||||
var val = ! this.settings.get(key);
|
||||
this.settings.set(key, val);
|
||||
swit.classList.toggle('active', val);
|
||||
};
|
||||
|
||||
|
||||
|
@ -19,8 +26,11 @@ FFZ.prototype.load_settings = function() {
|
|||
this.settings = {};
|
||||
|
||||
for(var key in FFZ.settings_info) {
|
||||
var ls_key = make_ls(key),
|
||||
info = FFZ.settings_info[key],
|
||||
if ( ! FFZ.settings_info.hasOwnProperty(key) )
|
||||
continue;
|
||||
|
||||
var info = FFZ.settings_info[key],
|
||||
ls_key = info.storage_key || make_ls(key),
|
||||
val = info.hasOwnProperty("value") ? info.value : undefined;
|
||||
|
||||
if ( localStorage.hasOwnProperty(ls_key) ) {
|
||||
|
@ -40,10 +50,143 @@ FFZ.prototype.load_settings = function() {
|
|||
this.settings.del = this._setting_del.bind(this);
|
||||
|
||||
// Listen for Changes
|
||||
window.addEventListener("storage", this._setting_update.bind(this));
|
||||
window.addEventListener("storage", this._setting_update.bind(this), false);
|
||||
}
|
||||
|
||||
|
||||
// --------------------
|
||||
// Menu Page
|
||||
// --------------------
|
||||
|
||||
FFZ.menu_pages.settings = {
|
||||
render: function(view, container) {
|
||||
var settings = {},
|
||||
categories = [];
|
||||
for(var key in FFZ.settings_info) {
|
||||
if ( ! FFZ.settings_info.hasOwnProperty(key) )
|
||||
continue;
|
||||
|
||||
var info = FFZ.settings_info[key],
|
||||
cat = info.category || "Miscellaneous",
|
||||
cs = settings[cat];
|
||||
|
||||
if ( info.visible !== undefined && info.visible !== null ) {
|
||||
var visible = info.visible;
|
||||
if ( typeof info.visible == "function" )
|
||||
visible = info.visible.bind(this)();
|
||||
|
||||
if ( ! visible )
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! cs ) {
|
||||
categories.push(cat);
|
||||
cs = settings[cat] = [];
|
||||
}
|
||||
|
||||
cs.push([key, info]);
|
||||
}
|
||||
|
||||
categories.sort(function(a,b) {
|
||||
var a = a.toLowerCase(),
|
||||
b = b.toLowerCase();
|
||||
|
||||
if ( a === "Debugging" )
|
||||
a = "zzz" + a;
|
||||
|
||||
if ( b === "Debugging" )
|
||||
b = "zzz" + b;
|
||||
|
||||
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 a = a[1],
|
||||
b = b[1],
|
||||
|
||||
at = a.type,
|
||||
bt = b.type,
|
||||
|
||||
an = a.name.toLowerCase(),
|
||||
bn = b.name.toLowerCase();
|
||||
|
||||
if ( at < bt ) return -1;
|
||||
else if ( at > bt ) return 1;
|
||||
|
||||
else 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", 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",
|
||||
icon: constants.GEAR,
|
||||
sort_order: 99999
|
||||
};
|
||||
|
||||
|
||||
// --------------------
|
||||
// Tracking Updates
|
||||
// --------------------
|
||||
|
@ -62,6 +205,22 @@ FFZ.prototype._setting_update = function(e) {
|
|||
val = undefined,
|
||||
info = FFZ.settings_info[key];
|
||||
|
||||
if ( ! info ) {
|
||||
// Try iterating to find the key.
|
||||
for(key in FFZ.settings_info) {
|
||||
if ( ! FFZ.settings_info.hasOwnProperty(key) )
|
||||
continue;
|
||||
|
||||
info = FFZ.settings_info[key];
|
||||
if ( info.storage_key == ls_key )
|
||||
break;
|
||||
}
|
||||
|
||||
// Not us.
|
||||
if ( info.storage_key != ls_key )
|
||||
return;
|
||||
}
|
||||
|
||||
this.log("Updated Setting: " + key);
|
||||
|
||||
try {
|
||||
|
@ -92,8 +251,8 @@ FFZ.prototype._setting_get = function(key) {
|
|||
|
||||
|
||||
FFZ.prototype._setting_set = function(key, val) {
|
||||
var ls_key = make_ls(key),
|
||||
info = FFZ.settings_info[key],
|
||||
var info = FFZ.settings_info[key],
|
||||
ls_key = info.storage_key || make_ls(key),
|
||||
jval = JSON.stringify(val);
|
||||
|
||||
this.settings[key] = val;
|
||||
|
@ -111,8 +270,8 @@ FFZ.prototype._setting_set = function(key, val) {
|
|||
|
||||
|
||||
FFZ.prototype._setting_del = function(key) {
|
||||
var ls_key = make_ls(key),
|
||||
info = FFZ.settings_info[key],
|
||||
var info = FFZ.settings_info[key],
|
||||
ls_key = info.storage_key || make_ls(key),
|
||||
val = undefined;
|
||||
|
||||
if ( localStorage.hasOwnProperty(ls_key) )
|
||||
|
|
|
@ -38,7 +38,7 @@ FFZ.prototype.ws_create = function() {
|
|||
|
||||
// Send the current rooms.
|
||||
for(var room_id in f.rooms)
|
||||
f.ws_send("sub", room_id);
|
||||
f.rooms.hasOwnProperty(room_id) && f.ws_send("sub", room_id);
|
||||
|
||||
// Send any pending commands.
|
||||
var pending = f._ws_pending;
|
||||
|
@ -64,8 +64,11 @@ FFZ.prototype.ws_create = function() {
|
|||
}
|
||||
|
||||
// We never ever want to not have a socket.
|
||||
if ( f._ws_delay < 30000 )
|
||||
if ( f._ws_delay < 60000 )
|
||||
f._ws_delay += 5000;
|
||||
else
|
||||
// Randomize delay.
|
||||
f._ws_delay = (Math.floor(Math.random()*60)+30)*1000;
|
||||
|
||||
setTimeout(f.ws_create.bind(f), f._ws_delay);
|
||||
}
|
||||
|
@ -124,4 +127,32 @@ FFZ.prototype.ws_send = function(func, data, callback, can_wait) {
|
|||
|
||||
this._ws_sock.send(request + " " + func + data);
|
||||
return request;
|
||||
}
|
||||
|
||||
// ----------------
|
||||
// Authorization
|
||||
// ----------------
|
||||
|
||||
FFZ.ws_commands.do_authorize = function(data) {
|
||||
// Try finding a channel we can send on.
|
||||
var conn;
|
||||
for(var room_id in this.rooms) {
|
||||
if ( ! this.rooms.hasOwnProperty(room_id) )
|
||||
continue;
|
||||
|
||||
var r = this.rooms[room_id];
|
||||
if ( r && r.room && !r.room.get('roomProperties.eventchat') && !r.room.get('isGroupRoom') && r.room.tmiRoom ) {
|
||||
var c = r.room.tmiRoom._getConnection();
|
||||
if ( c.isConnected ) {
|
||||
conn = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( conn )
|
||||
conn._send("PRIVMSG #frankerfacezauthorizer :AUTH " + data);
|
||||
else
|
||||
// Try again shortly.
|
||||
setTimeout(FFZ.ws_commands.do_authorize.bind(this, data), 5000);
|
||||
}
|
95
src/ui/about_page.js
Normal file
95
src/ui/about_page.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
var FFZ = window.FrankerFaceZ,
|
||||
constants = require("../constants");
|
||||
|
||||
|
||||
// -------------------
|
||||
// About Page
|
||||
// -------------------
|
||||
|
||||
FFZ.menu_pages.about = {
|
||||
name: "About FrankerFaceZ",
|
||||
icon: constants.HEART,
|
||||
sort_order: 998,
|
||||
|
||||
render: function(view, container) {
|
||||
var room = this.rooms[view.get("context.currentRoom.id")],
|
||||
has_emotes = false, f = this;
|
||||
|
||||
// Check for emoticons.
|
||||
if ( room && room.sets.length ) {
|
||||
for(var i=0; i < room.sets.length; i++) {
|
||||
var set = this.emote_sets[room.sets[i]];
|
||||
if ( set && set.count > 0 ) {
|
||||
has_emotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Heading
|
||||
var heading = document.createElement('div'),
|
||||
content = '';
|
||||
|
||||
content += "<h1>FrankerFaceZ</h1>";
|
||||
content += '<div class="ffz-about-subheading">new ways to woof</div>';
|
||||
|
||||
heading.className = 'chat-menu-content center';
|
||||
heading.innerHTML = content;
|
||||
container.appendChild(heading);
|
||||
|
||||
|
||||
// Advertising
|
||||
var btn_container = document.createElement('div'),
|
||||
ad_button = document.createElement('a'),
|
||||
message = "To use custom emoticons in " + (has_emotes ? "this channel" : "tons of channels") + ", get FrankerFaceZ from http://www.frankerfacez.com";
|
||||
|
||||
ad_button.className = 'button primary';
|
||||
ad_button.innerHTML = "Advertise in Chat";
|
||||
ad_button.addEventListener('click', this._add_emote.bind(this, view, message));
|
||||
|
||||
btn_container.appendChild(ad_button);
|
||||
|
||||
// Donate
|
||||
var donate_button = document.createElement('a');
|
||||
|
||||
donate_button.className = 'button ffz-donate';
|
||||
donate_button.href = "http://www.frankerfacez.com/donate.html";
|
||||
donate_button.target = "_new";
|
||||
donate_button.innerHTML = "Donate";
|
||||
|
||||
btn_container.appendChild(donate_button);
|
||||
btn_container.className = 'chat-menu-content center';
|
||||
container.appendChild(btn_container);
|
||||
|
||||
// Credits
|
||||
var credits = document.createElement('div');
|
||||
|
||||
content = '<table class="ffz-about-table">';
|
||||
content += '<tr><th colspan="4">Developers</th></tr>';
|
||||
content += '<tr><td>Dan Salvato</td><td><a class="twitch" href="http://www.twitch.tv/dansalvato" title="Twitch" target="_new"> </a></td><td><a class="twitter" href="https://twitter.com/dansalvato1" title="Twitter" target="_new"> </a></td><td><a class="youtube" href="https://www.youtube.com/user/dansalvato1" title="YouTube" target="_new"> </a></td></tr>';
|
||||
content += '<tr><td>Stendec</td><td><a class="twitch" href="http://www.twitch.tv/sirstendec" title="Twitch" target="_new"> </a></td><td><a class="twitter" href="https://twitter.com/SirStendec" title="Twitter" target="_new"> </a></td><td><a class="youtube" href="https://www.youtube.com/channel/UCnxuvmK1DCPCXSJ-mXIh4KQ" title="YouTube" target="_new"> </a></td></tr>';
|
||||
|
||||
content += '<tr class="debug"><td>Version ' + FFZ.version_info + '</td><td colspan="3"><a href="#" id="ffz-debug-logs">Logs</a></td></tr>';
|
||||
|
||||
credits.className = 'chat-menu-content center';
|
||||
credits.innerHTML = content;
|
||||
|
||||
// Make the Logs button functional.
|
||||
var getting_logs = false;
|
||||
credits.querySelector('#ffz-debug-logs').addEventListener('click', function() {
|
||||
if ( getting_logs )
|
||||
return;
|
||||
|
||||
getting_logs = true;
|
||||
f._pastebin(f._log_data.join("\n"), function(url) {
|
||||
getting_logs = false;
|
||||
if ( ! url )
|
||||
alert("There was an error uploading the FrankerFaceZ logs.");
|
||||
else
|
||||
prompt("Your FrankerFaceZ logs have been uploaded to the URL:", url);
|
||||
});
|
||||
});
|
||||
|
||||
container.appendChild(credits);
|
||||
}
|
||||
}
|
252
src/ui/menu.js
252
src/ui/menu.js
|
@ -60,23 +60,43 @@ FFZ.prototype.build_ui_popup = function(view) {
|
|||
menu.className = 'menu clearfix';
|
||||
inner.appendChild(menu);
|
||||
|
||||
var el = document.createElement('li');
|
||||
el.className = 'title';
|
||||
el.innerHTML = "<span>FrankerFaceZ</span>";
|
||||
menu.appendChild(el);
|
||||
|
||||
el.addEventListener("click", this._add_emote.bind(this, view, "To use custom emoticons in tons of channels, get FrankerFaceZ from http://www.frankerfacez.com"));
|
||||
var heading = document.createElement('li');
|
||||
heading.className = 'title';
|
||||
heading.innerHTML = "<span>" + (constants.DEBUG ? "[DEV] " : "") + "FrankerFaceZ</span>";
|
||||
menu.appendChild(heading);
|
||||
|
||||
var sub_container = document.createElement('div');
|
||||
sub_container.className = 'ffz-ui-menu-page';
|
||||
inner.appendChild(sub_container);
|
||||
|
||||
var menu_pages = [];
|
||||
for(var key in FFZ.menu_pages) {
|
||||
if ( ! FFZ.menu_pages.hasOwnProperty(key) )
|
||||
continue;
|
||||
|
||||
var page = FFZ.menu_pages[key];
|
||||
if ( !page || (page.hasOwnProperty("visible") && (!page.visible || (typeof page.visible == "function" && !page.visible.bind(this)()))) )
|
||||
continue;
|
||||
|
||||
var el = document.createElement('li'),
|
||||
menu_pages.push([page.sort_order || 0, key, page]);
|
||||
}
|
||||
|
||||
menu_pages.sort(function(a,b) {
|
||||
if ( a[0] < b[0] ) return 1;
|
||||
else if ( a[0] > b[0] ) return -1;
|
||||
|
||||
var al = a[1].toLowerCase(),
|
||||
bl = b[1].toLowerCase();
|
||||
|
||||
if ( al < bl ) return 1;
|
||||
if ( al > bl ) return -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
for(var i=0; i < menu_pages.length; i++) {
|
||||
var key = menu_pages[i][1],
|
||||
page = menu_pages[i][2],
|
||||
el = document.createElement('li'),
|
||||
link = document.createElement('a');
|
||||
|
||||
el.className = 'item';
|
||||
|
@ -84,6 +104,8 @@ FFZ.prototype.build_ui_popup = function(view) {
|
|||
link.title = page.name;
|
||||
link.innerHTML = page.icon;
|
||||
|
||||
jQuery(link).tipsy();
|
||||
|
||||
link.addEventListener("click", this._ui_change_page.bind(this, view, menu, sub_container, key));
|
||||
|
||||
el.appendChild(link);
|
||||
|
@ -103,6 +125,7 @@ FFZ.prototype.build_ui_popup = function(view) {
|
|||
FFZ.prototype._ui_change_page = function(view, menu, container, page) {
|
||||
this._last_page = page;
|
||||
container.innerHTML = "";
|
||||
container.setAttribute('data-page', page);
|
||||
|
||||
var els = menu.querySelectorAll('li.active');
|
||||
for(var i=0; i < els.length; i++)
|
||||
|
@ -118,137 +141,48 @@ FFZ.prototype._ui_change_page = function(view, menu, container, page) {
|
|||
}
|
||||
|
||||
|
||||
// --------------------
|
||||
// Settings Page
|
||||
// --------------------
|
||||
|
||||
FFZ.menu_pages.settings = {
|
||||
render: function(view, container) {
|
||||
var settings = {},
|
||||
categories = [];
|
||||
for(var key in FFZ.settings_info) {
|
||||
var info = FFZ.settings_info[key],
|
||||
cat = info.category || "Miscellaneous",
|
||||
cs = settings[cat];
|
||||
|
||||
if ( info.visible !== undefined && info.visible !== null ) {
|
||||
var visible = info.visible;
|
||||
if ( typeof info.visible == "function" )
|
||||
visible = info.visible.bind(this)();
|
||||
|
||||
if ( ! visible )
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! cs ) {
|
||||
categories.push(cat);
|
||||
cs = settings[cat] = [];
|
||||
}
|
||||
|
||||
cs.push([key, info]);
|
||||
}
|
||||
|
||||
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",
|
||||
icon: constants.GEAR
|
||||
};
|
||||
|
||||
|
||||
FFZ.prototype._ui_toggle_setting = function(swit, key) {
|
||||
var val = ! this.settings.get(key);
|
||||
this.settings.set(key, val);
|
||||
swit.classList.toggle('active', val);
|
||||
}
|
||||
|
||||
|
||||
// --------------------
|
||||
// Favorites Page
|
||||
// --------------------
|
||||
|
||||
FFZ.prototype._tokenize_message = function(message, room_id) {
|
||||
var lc = App.__container__.lookup('controller:line'),
|
||||
rc = App.__container__.lookup('controller:room'),
|
||||
room = this.rooms[room_id],
|
||||
user = this.get_user();
|
||||
|
||||
if ( ! lc || ! rc || ! room )
|
||||
return [message];
|
||||
|
||||
rc.set('model', room.room);
|
||||
lc.set('parentController', rc);
|
||||
|
||||
var model = {
|
||||
from: user && user.login || "FrankerFaceZ",
|
||||
message: message,
|
||||
tags: {
|
||||
emotes: room.room.tmiSession._emotesParser.parseEmotesTag(message)
|
||||
}
|
||||
};
|
||||
|
||||
lc.set('model', model);
|
||||
|
||||
var tokens = lc.get('tokenizedMessage');
|
||||
|
||||
lc.set('model', null);
|
||||
rc.set('model', null);
|
||||
lc.set('parentController', null);
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
|
||||
/*FFZ.menu_pages.favorites = {
|
||||
render: function(view, container) {
|
||||
|
||||
// Get the current room.
|
||||
var room_id = view.get('controller.currentRoom.id');
|
||||
|
||||
|
||||
},
|
||||
|
||||
name: "Favorites",
|
||||
|
@ -266,35 +200,8 @@ FFZ.menu_pages.channel = {
|
|||
var room_id = view.get('controller.currentRoom.id'),
|
||||
room = this.rooms[room_id];
|
||||
|
||||
//this.track('trackEvent', 'Menu', 'Open', room_id);
|
||||
|
||||
// Add the header and ad button.
|
||||
/*var btn = document.createElement('a');
|
||||
btn.className = 'button glyph-only ffz-button';
|
||||
btn.title = 'Advertise for FrankerFaceZ in chat!';
|
||||
btn.href = '#';
|
||||
btn.innerHTML = '<svg class="svg-followers" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M8,13.5L1.5,7V4l2-2h3L8,3.5L9.5,2h3l2,2v3L8,13.5z" fill-rule="evenodd"></path></svg>';
|
||||
|
||||
var hdr = document.createElement('div');
|
||||
hdr.className = 'list-header first';
|
||||
hdr.appendChild(btn);
|
||||
hdr.appendChild(document.createTextNode('FrankerFaceZ'));
|
||||
inner.appendChild(hdr);*/
|
||||
|
||||
var c = this._emotes_for_sets(inner, view, room && room.menu_sets || []);
|
||||
|
||||
/*if ( ! this._ws_exists ) {
|
||||
btn.className = "button ffz-button primary";
|
||||
btn.innerHTML = "Server Error";
|
||||
btn.title = "FFZ Server Error";
|
||||
btn.addEventListener('click', alert.bind(window, "The FrankerFaceZ client was unable to create a WebSocket to communicate with the FrankerFaceZ server.\n\nThis is most likely due to your browser's configuration either disabling WebSockets entirely or limiting the number of simultaneous connections. Please ensure that WebSockets have not been disabled."));
|
||||
|
||||
} else {
|
||||
if ( c === 0 )
|
||||
btn.addEventListener('click', this._add_emote.bind(this, view, "To use custom emoticons in tons of channels, get FrankerFaceZ from http://www.frankerfacez.com"));
|
||||
else
|
||||
btn.addEventListener('click', this._add_emote.bind(this, view, "To view this channel's emoticons, get FrankerFaceZ from http://www.frankerfacez.com"));
|
||||
}*/
|
||||
// Basic Emote Sets
|
||||
this._emotes_for_sets(inner, view, room && room.menu_sets || []);
|
||||
|
||||
// Feature Friday!
|
||||
this._feature_friday_ui(room_id, inner, view);
|
||||
|
@ -330,6 +237,9 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) {
|
|||
continue;
|
||||
|
||||
for(var eid in set.emotes) {
|
||||
if ( ! set.emotes.hasOwnProperty(eid) )
|
||||
continue;
|
||||
|
||||
var emote = set.emotes[eid];
|
||||
if ( !set.emotes.hasOwnProperty(eid) || emote.hidden )
|
||||
continue;
|
||||
|
@ -356,11 +266,21 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) {
|
|||
|
||||
|
||||
FFZ.prototype._add_emote = function(view, emote) {
|
||||
var room = view.get('controller.currentRoom'),
|
||||
current_text = room.get('messageToSend') || '';
|
||||
var input_el, text, room;
|
||||
|
||||
if ( current_text && current_text.substr(-1) !== " " )
|
||||
current_text += ' ';
|
||||
if ( this.has_bttv ) {
|
||||
input_el = view.get('element').querySelector('textarea');
|
||||
text = input_el.value;
|
||||
|
||||
room.set('messageToSend', current_text + (emote.name || emote));
|
||||
} else {
|
||||
room = view.get('controller.currentRoom');
|
||||
text = room.get('messageToSend') || '';
|
||||
}
|
||||
|
||||
text += (text && text.substr(-1) !== " " ? " " : "") + (emote.name || emote);
|
||||
|
||||
if ( input_el )
|
||||
input_el.value = text;
|
||||
else
|
||||
room.set('messageToSend', text);
|
||||
}
|
298
src/ui/my_emotes.js
Normal file
298
src/ui/my_emotes.js
Normal file
|
@ -0,0 +1,298 @@
|
|||
var FFZ = window.FrankerFaceZ,
|
||||
constants = require("../constants"),
|
||||
|
||||
TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/",
|
||||
BANNED_SETS = {"00000turbo":true},
|
||||
|
||||
|
||||
get_emotes = function(ffz) {
|
||||
var Chat = App.__container__.lookup('controller:chat'),
|
||||
room_id = Chat.get('currentRoom.id'),
|
||||
room = ffz.rooms[room_id],
|
||||
tmiSession = room ? room.room.tmiSession : null,
|
||||
|
||||
set_ids = tmiSession && tmiSession._emotesParser && tmiSession._emotesParser.emoticonSetIds || "0",
|
||||
user = ffz.get_user(),
|
||||
user_sets = user && ffz.users[user.login] && ffz.users[user.login].sets || [];
|
||||
|
||||
// Remove the 'default' set.
|
||||
set_ids = set_ids.split(",").removeObject("0")
|
||||
|
||||
return [set_ids, user_sets];
|
||||
};
|
||||
|
||||
|
||||
// -------------------
|
||||
// Initialization
|
||||
// -------------------
|
||||
|
||||
FFZ.prototype.setup_my_emotes = function() {
|
||||
this._twitch_emote_sets = {};
|
||||
this._twitch_set_to_channel = {};
|
||||
|
||||
if ( localStorage.ffzTwitchSets ) {
|
||||
try {
|
||||
this._twitch_set_to_channel = JSON.parse(localStorage.ffzTwitchSets);
|
||||
} catch(err) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------
|
||||
// Menu Page
|
||||
// -------------------
|
||||
|
||||
FFZ.menu_pages.my_emotes = {
|
||||
name: "My Emoticons",
|
||||
icon: constants.EMOTE,
|
||||
|
||||
visible: function() {
|
||||
var emotes = get_emotes(this);
|
||||
return emotes[0].length > 0 || emotes[1].length > 0;
|
||||
},
|
||||
|
||||
render: function(view, container) {
|
||||
var emotes = get_emotes(this), f = this;
|
||||
|
||||
new RSVP.Promise(function(done) {
|
||||
var needed_sets = [];
|
||||
for(var i=0; i < emotes[0].length; i++) {
|
||||
var set_id = emotes[0][i];
|
||||
if ( ! f._twitch_emote_sets[set_id] )
|
||||
needed_sets.push(set_id);
|
||||
}
|
||||
|
||||
RSVP.all([
|
||||
new RSVP.Promise(function(d) {
|
||||
if ( ! needed_sets.length )
|
||||
return d();
|
||||
|
||||
Twitch.api.get("chat/emoticon_images", {emotesets: needed_sets.join(",")}, {version: 3})
|
||||
.done(function(data) {
|
||||
if ( data.emoticon_sets ) {
|
||||
for(var set_id in data.emoticon_sets) {
|
||||
if ( ! data.emoticon_sets.hasOwnProperty(set_id) )
|
||||
continue;
|
||||
|
||||
var set = f._twitch_emote_sets[set_id] = f._twitch_emote_sets[set_id] || {};
|
||||
set.emotes = data.emoticon_sets[set_id];
|
||||
set.source = "Twitch";
|
||||
}
|
||||
}
|
||||
d();
|
||||
}).fail(function() {
|
||||
d();
|
||||
});
|
||||
}),
|
||||
new RSVP.Promise(function(d) {
|
||||
if ( ! needed_sets.length )
|
||||
return d();
|
||||
|
||||
var promises = [],
|
||||
old_needed = needed_sets,
|
||||
handle_set = function(id, name) {
|
||||
var set = f._twitch_emote_sets[id] = f._twitch_emote_sets[id] || {};
|
||||
|
||||
if ( !name || BANNED_SETS[name] )
|
||||
return;
|
||||
|
||||
if ( name == "turbo" ) {
|
||||
set.channel = "Twitch Turbo";
|
||||
set.badge = "//cdn.frankerfacez.com/script/turbo_badge.png";
|
||||
return;
|
||||
}
|
||||
|
||||
// Badge Lookup
|
||||
promises.push(new RSVP.Promise(function(set, name, dn) {
|
||||
Twitch.api.get("chat/" + name + "/badges", null, {version: 3})
|
||||
.done(function(data) {
|
||||
if ( data.subscriber && data.subscriber.image )
|
||||
set.badge = data.subscriber.image;
|
||||
dn();
|
||||
}).fail(dn)}.bind(this,set,name)));
|
||||
|
||||
// Mess Up Capitalization
|
||||
var lname = name.toLowerCase(),
|
||||
old_data = FFZ.capitalization[lname];
|
||||
if ( old_data && Date.now() - old_data[1] < 3600000 ) {
|
||||
set.channel = old_data[0];
|
||||
return;
|
||||
}
|
||||
|
||||
promises.push(new RSVP.Promise(function(set, lname, name, dn) {
|
||||
if ( ! f.ws_send("get_display_name", lname, function(success, data) {
|
||||
var cap_name = success ? data : name;
|
||||
FFZ.capitalization[lname] = [cap_name, Date.now()];
|
||||
set.channel = cap_name;
|
||||
dn();
|
||||
}) ) {
|
||||
// Can't use socket.
|
||||
set.channel = name;
|
||||
dn();
|
||||
}
|
||||
|
||||
// Timeout
|
||||
setTimeout(function(set,name,dn) {
|
||||
if ( ! set.channel )
|
||||
set.channel = name;
|
||||
dn();
|
||||
}.bind(this,set,name,dn), 5000);
|
||||
}.bind(this, set, lname, name)));
|
||||
},
|
||||
handle_promises = function() {
|
||||
if ( promises.length )
|
||||
RSVP.all(promises).then(d,d);
|
||||
else
|
||||
d();
|
||||
};
|
||||
|
||||
// Process all the sets we already have.
|
||||
needed_sets = [];
|
||||
for(var i=0;i<old_needed.length;i++) {
|
||||
var set_id = old_needed[i];
|
||||
if ( f._twitch_set_to_channel[set_id] )
|
||||
handle_set(set_id, f._twitch_set_to_channel[set_id]);
|
||||
else
|
||||
needed_sets.push(set_id);
|
||||
}
|
||||
|
||||
if ( needed_sets.length > 0 ) {
|
||||
f.ws_send("twitch_sets", needed_sets, function(success, data) {
|
||||
needed_sets = [];
|
||||
if ( success ) {
|
||||
for(var set_id in data) {
|
||||
if ( ! data.hasOwnProperty(set_id) )
|
||||
continue;
|
||||
|
||||
f._twitch_set_to_channel[set_id] = data[set_id];
|
||||
handle_set(set_id, data[set_id]);
|
||||
}
|
||||
|
||||
localStorage.ffzTwitchSets = JSON.stringify(f._twitch_set_to_channel);
|
||||
}
|
||||
|
||||
handle_promises();
|
||||
});
|
||||
|
||||
// Timeout!
|
||||
setTimeout(function() {
|
||||
if ( needed_sets.length )
|
||||
handle_promises();
|
||||
}, 5000);
|
||||
|
||||
} else
|
||||
handle_promises();
|
||||
})
|
||||
]).then(function() {
|
||||
var sets = {};
|
||||
for(var i=0; i < emotes[0].length; i++) {
|
||||
var set_id = emotes[0][i];
|
||||
if ( f._twitch_emote_sets[set_id] )
|
||||
sets[set_id] = f._twitch_emote_sets[set_id];
|
||||
}
|
||||
done(sets);
|
||||
}, function() { done({}); })
|
||||
}).then(function(twitch_sets) {
|
||||
try {
|
||||
|
||||
// Don't override a different page. We can wait.
|
||||
if ( container.getAttribute('data-page') != "my_emotes" )
|
||||
return;
|
||||
|
||||
container.innerHTML = "";
|
||||
|
||||
var ffz_sets = {},
|
||||
sets = [];
|
||||
|
||||
for(var set_id in twitch_sets) {
|
||||
if ( ! twitch_sets.hasOwnProperty(set_id) )
|
||||
continue;
|
||||
|
||||
var set = twitch_sets[set_id];
|
||||
if ( set.channel && set.emotes && set.emotes.length )
|
||||
sets.push([1, set.channel, set]);
|
||||
}
|
||||
|
||||
sets.sort(function(a,b) {
|
||||
if ( a[0] < b[0] ) return -1;
|
||||
else if ( a[0] > b[0] ) return 1;
|
||||
|
||||
var an = a[1].toLowerCase(),
|
||||
bn = b[1].toLowerCase();
|
||||
|
||||
if ( an === "twitch turbo" )
|
||||
an = "zzz" + an;
|
||||
|
||||
if ( bn === "twitch turbo" )
|
||||
bn = "zzz" + bn;
|
||||
|
||||
if ( an < bn ) return -1;
|
||||
else if ( an > bn ) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
for(var i=0; i < sets.length; i++) {
|
||||
var set = sets[i][2],
|
||||
heading = document.createElement('div'),
|
||||
menu = document.createElement('div');
|
||||
|
||||
heading.className = 'heading';
|
||||
heading.innerHTML = '<span class="right">' + set.source + '</span>' + FFZ.get_capitalization(set.channel);
|
||||
if ( set.badge )
|
||||
heading.style.backgroundImage = 'url("' + set.badge + '")';
|
||||
|
||||
menu.className = 'emoticon-grid';
|
||||
menu.appendChild(heading);
|
||||
|
||||
for(var x=0; x < set.emotes.length; x++) {
|
||||
var emote = set.emotes[x];
|
||||
|
||||
var s = document.createElement('span');
|
||||
s.className = 'emoticon tooltip';
|
||||
s.style.backgroundImage = 'url("' + TWITCH_BASE + emote.id + '/1.0")';
|
||||
|
||||
var img_set = 'image-set(url("' + TWITCH_BASE + emote.id + '/1.0") 1x, url("' + TWITCH_BASE + emote.id + '/2.0") 2x, url("' + TWITCH_BASE + emote.id + '/3.0") 4x)';
|
||||
s.style.backgroundImage = '-webkit-' + img_set;
|
||||
s.style.backgroundImage = '-moz-' + img_set;
|
||||
s.style.backgroundImage = '-ms-' + img_set;
|
||||
s.style.backgroundImage = img_set;
|
||||
|
||||
s.title = emote.code;
|
||||
s.addEventListener('click', f._add_emote.bind(f, view, emote.code));
|
||||
menu.appendChild(s);
|
||||
}
|
||||
|
||||
container.appendChild(menu);
|
||||
}
|
||||
|
||||
if ( ! sets.length ) {
|
||||
var menu = document.createElement('div');
|
||||
|
||||
menu.className = 'chat-menu-content center';
|
||||
menu.innerHTML = "Error Loading Subscriptions";
|
||||
|
||||
container.appendChild(menu);
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
f.log("My Emotes Menu Error", err);
|
||||
container.innerHTML = "";
|
||||
|
||||
var menu = document.createElement('div'),
|
||||
heading = document.createElement('div'),
|
||||
p = document.createElement('p');
|
||||
|
||||
heading.className = 'heading';
|
||||
heading.innerHTML = 'Error Loading Menu';
|
||||
menu.appendChild(heading);
|
||||
|
||||
p.className = 'clearfix';
|
||||
p.textContent = err;
|
||||
menu.appendChild(p);
|
||||
|
||||
menu.className = 'chat-menu-content';
|
||||
container.appendChild(menu);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -8,7 +8,7 @@ FFZ.prototype.setup_css = function() {
|
|||
|
||||
s.id = "ffz-ui-css";
|
||||
s.setAttribute('rel', 'stylesheet');
|
||||
s.setAttribute('href', constants.SERVER + "script/style.css");
|
||||
s.setAttribute('href', constants.SERVER + "script/style.css?_=" + Date.now());
|
||||
document.head.appendChild(s);
|
||||
|
||||
jQuery.noty.themes.ffzTheme = {
|
||||
|
|
|
@ -42,8 +42,7 @@ var sanitize_cache = {},
|
|||
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;
|
||||
return (0.2126 * rgb[0]) + (0.7152 * rgb[1]) + (0.0722 * rgb[2]);
|
||||
};
|
||||
|
||||
|
||||
|
|
131
style.css
131
style.css
|
@ -292,15 +292,29 @@
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
.ffz-about-table a.twitch,
|
||||
.ffz-about-table a.youtube,
|
||||
.ffz-about-table a.twitter,
|
||||
#ffz-race-popup a.twitch,
|
||||
#ffz-race-popup a.hitbox {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin: 0 5px;
|
||||
margin-left: 5px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#ffz-race-popup a.twitch {
|
||||
.ffz-about-table a.youtube {
|
||||
width: 23px;
|
||||
background-image: url("//cdn.frankerfacez.com/channel/global/youtube_logo.png");
|
||||
}
|
||||
|
||||
.ffz-about-table a.twitter {
|
||||
width: 20px;
|
||||
background-image: url("//cdn.frankerfacez.com/channel/global/twitter_logo.png");
|
||||
}
|
||||
|
||||
#ffz-race-popup a.twitch,
|
||||
.ffz-about-table a.twitch {
|
||||
width: 15px;
|
||||
background-image: url("//cdn.frankerfacez.com/channel/global/twitch_logo.png");
|
||||
}
|
||||
|
@ -335,9 +349,16 @@
|
|||
margin: 0 -20px;
|
||||
}
|
||||
|
||||
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading {
|
||||
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
|
||||
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading {
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid rgba(0,0,0, 0.2);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading {
|
||||
padding-left: 23px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content {
|
||||
|
@ -345,6 +366,10 @@
|
|||
background-color: transparent;
|
||||
}
|
||||
|
||||
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content + .chat-menu-content {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.ffz-ui-menu-page span.help {
|
||||
display: block;
|
||||
opacity: 0.75;
|
||||
|
@ -366,6 +391,7 @@
|
|||
background-color: #282828;
|
||||
}
|
||||
|
||||
.ffz-ui-menu-page .heading .right,
|
||||
.ffz-ui-popup ul.menu li.item {
|
||||
float: right;
|
||||
}
|
||||
|
@ -374,11 +400,10 @@
|
|||
float: left;
|
||||
}
|
||||
|
||||
.ffz-ui-popup ul.menu li.title span {
|
||||
.ffz-ui-popup ul.menu li.title > span {
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
line-height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ffz-ui-popup ul.menu a {
|
||||
|
@ -414,6 +439,12 @@
|
|||
|
||||
|
||||
/* Chat Mentions */
|
||||
.ember-chat .mentioned:empty,
|
||||
.ember-chat .mentioning:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ffz-chat-background .ember-chat .mentioning,
|
||||
.ffz-chat-background .ember-chat .mentioned {
|
||||
border-radius: 10px;
|
||||
padding: 3px 7px;
|
||||
|
@ -424,7 +455,10 @@
|
|||
|
||||
.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 {
|
||||
.ffz-chat-background .chat-container.dark .chat-line .mentioned,
|
||||
.ffz-chat-background .app-main.theatre .chat-container .chat-line .mentioning,
|
||||
.ffz-chat-background .ember-chat-container.dark .chat-line .mentioning,
|
||||
.ffz-chat-background .chat-container.dark .chat-line .mentioning {
|
||||
color: #8c8c9c;
|
||||
background-color: rgba(16,16,20, 0.75);
|
||||
}
|
||||
|
@ -541,4 +575,89 @@
|
|||
.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);
|
||||
}
|
||||
|
||||
/* Emoticon Tooltips */
|
||||
|
||||
.tipsy table.emote-data td { padding: 0 2px; }
|
||||
|
||||
.tipsy table.emote-data td:first-of-type {
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.tipsy table.emote-data td:last-of-type {
|
||||
text-align: left;
|
||||
padding-right: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tipsy table.emote-data td.center { text-align: center; }
|
||||
|
||||
/* Menu Page Loader */
|
||||
|
||||
.ffz-ui-menu-page:empty {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ffz-ui-menu-page:empty::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 80px;
|
||||
height: 63px;
|
||||
background-image: url("//cdn.frankerfacez.com/script/spinner-dark.png");
|
||||
|
||||
margin: 50px auto;
|
||||
-webkit-animation: ffz-rotateplane 1.2s infinite linear;
|
||||
animation: ffz-rotateplane 1.2s infinite linear;
|
||||
}
|
||||
|
||||
@-webkit-keyframes ffz-rotateplane {
|
||||
0% { -webkit-transform: perspective(120px) rotateY(90deg) }
|
||||
25% { -webkit-transform: perspective(120px) rotateY(180deg) }
|
||||
75% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) }
|
||||
100% { -webkit-transform: perspective(120px) rotateY(90deg) rotateX(180deg) }
|
||||
}
|
||||
|
||||
@keyframes ffz-rotateplane {
|
||||
0% { transform: perspective(120px) rotateY(90deg) }
|
||||
25% { transform: perspective(120px) rotateY(180deg) }
|
||||
75% { transform: perspective(120px) rotateY(180deg) rotateX(180deg) }
|
||||
100% { transform: perspective(120px) rotateY(90deg) rotateX(180deg) }
|
||||
}
|
||||
|
||||
/* Menu About Page */
|
||||
|
||||
.ffz-about-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ffz-about-table td:first-child {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ffz-about-table .debug td {
|
||||
padding-top: 10px;
|
||||
opacity: 0.8;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.ffz-about-subheading {
|
||||
/*text-transform: uppercase;*/
|
||||
letter-spacing: 2px;
|
||||
margin: -5px 0 5px;
|
||||
}
|
||||
|
||||
.button.ffz-donate {
|
||||
margin-left: 10px;
|
||||
background: #00b132;
|
||||
color: #fff !important;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.button.ffz-donate:not(.disabled):hover {
|
||||
background: #08c43d;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue