1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 05:15:54 +00:00

I really need to remember to commit more frequently.

This commit is contained in:
SirStendec 2016-03-23 19:28:22 -04:00
parent 800553c602
commit d55af32b4e
45 changed files with 4777 additions and 2913 deletions

184
dark.css
View file

@ -89,6 +89,7 @@
/* main column */
body.ffz-dark,
.ffz-dark div#mantle_skin,
.ffz-dark div#main_col {
background:rgb(16,16,16);
@ -143,9 +144,14 @@
/* Popups */
.ffz-dark #commission_modal {
background-color: #101010 !important;
border-color: #32323e !important;
}
.ffz-dark .conversation-settings-menu,
.ffz-dark .ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
.ffz-dark .card,
.ffz-dark .card:not(#passport_modal),
.ffz-dark #flyout .content,
.ffz-dark .whatisthis,
.ffz-dark .ui-menu,
@ -164,6 +170,7 @@
background-color: rgb(16,16,16);
color: rgb(195,195,195); /*#acacbf;*/
border-color: #32323e;
box-shadow: rgba(255,255,255,0.2) 0 0 0 1px inset;
}
.ffz-dark .st-autocomplete-sidebar .label,
@ -207,6 +214,7 @@
.ffz-dark .change-banner .banner-preview,
.ffz-dark form.js-new_panel_form input,
.ffz-dark form.js-new_panel_form textarea,
.ffz-dark .conversation-input-bar textarea,
.ffz-dark .card input,
.ffz-dark .card textarea,
.ffz-dark .dropmenu input,
@ -216,6 +224,7 @@
.ffz-dark textarea,
.ffz-dark select,
.ffz-dark option,
.ffz-dark #mantle_skin .dropdown,
.ffz-dark .directory_header #custom_filter input {
background-color: rgba(255,255,255,0.05);
border-color: rgba(255,255,255,0.1);
@ -379,6 +388,17 @@
color: #fff;
}
.ffz-dark .tabs.tabs--fullwidth li:not(.selected) a:hover {
color: #fff !important;
background: #24242a;
border-color: #8c8c9c;
}
.ffz-dark .tabs li.selected a {
color: #fff !important;
border-color: #a68cd4 !important;
}
/* Subscriptions Page */
@ -715,6 +735,25 @@
/* Dashboard */
.ffz-dark .brick {
background-color: #121212;
}
.ffz-dark .brick--faint,
.ffz-dark .brick--block {
background-color: rgb(25,25,25);
}
.ffz-dark .brick--faint,
.ffz-dark .brick--block,
.ffz-dark #action_feed .action,
.ffz-dark .revHeader__item {
border-color: rgba(255,255,255,0.2);
}
.ffz-dark #mantle_skin .what { background-color: #6441a5; }
.ffz-dark #action_feed .action { padding-bottom: 20px }
.ffz-dark .js-stream-key-button-container .button {
margin: 15px auto;
}
@ -824,6 +863,13 @@
border-right-color: rgba(255,255,255,0.2);
}
.ffz-dark #dash_main .dash-player-contain.collapsed #player_overlay,
.ffz-dark .dash-hostmode-contain {
background: #202020;
}
.ffz-dark #dash_main #delay_controls,
.ffz-dark #dash_main #commercial_buttons,
.ffz-dark .dash-hostmode-list-contain {
border-top-color: rgba(255,255,255,0.2);
}
@ -909,15 +955,111 @@
}
/* Conversations */
/* VoDs */
.ffz-dark .ignore-cta {
background-color: #333;
box-shadow: 0 3px 0 #000;
.ffz-dark .app-main .chatReplay .notice-wrapper .svg-logo_glitch {
fill: #242424 !important;
}
.ffz-dark .ignore-cta .conversation-system-message {
color: #ccc;
.ffz-dark .app-main .chatReplay .notice-wrapper {
background-color: #191919;
}
.ffz-dark .chatReplay .loading-spinner-container {
background: rgba(25,25,25,0.65);
}
/* Conversations */
.ffz-dark .conversations-list-bottom-bar {
background-color: #19191f;
color: #8c8c9c;
border-color: rgba(255,255,255,0.1);
}
.ffz-dark .conversation-list-bottom-bar:hover {
color: #fff;
}
.ffz-dark .conversations-list {
background-color: #19191f;
border: 1px solid #32323e;
color: #fff;
}
.ffz-dark .conversations-list:before {
right: 9px;
border-color: rgba(50,50,62,0);
border-top-color: #32323e;
}
.ffz-dark .conversations-list:after {
border-color: rgba(25,25,31,0);
border-top-color: #19191f;
border-width: 10px;
margin-left: -10px;
}
.ffz-dark .conversations-list .conversations-list-header {
background-color: #121217;
border-bottom: 1px solid #32323e;
color: #fff;
}
.ffz-dark .conversations-list .conversation-preview-line {
color: #8c8c9c;
}
.ffz-dark .conversations-list .search-divider,
.ffz-dark .conversations-list .conversations-list-item {
border-bottom: 1px solid #32323e;
}
.ffz-dark .conversations-list .conversations-list-item:hover {
background-color: #121217;
}
.ffz-dark .conversation-window {
background-color: #19191f;
box-shadow: none;
color: #8c8c9c;
}
.ffz-dark .conversations-list .search-divider,
.ffz-dark .conversation-header {
background-color: #121217;
box-shadow: none;
}
.ffz-dark .conversation-input-actions .button,
.ffz-dark .conversation-input-actions .follow-button:not(.ember-follow) .follow,
.ffz-dark .follow-button:not(.ember-follow) .conversation-input-actions .follow {
background-color: #444;
}
.ffz-dark .conversation-window.has-focus .conversation-header {
background-color: #121217;
}
.ffz-dark .conversation-window.has-focus .conversation-header-name {
color: #fff;
}
.ffz-dark .conversation-window.has-focus .conversation-input-bar textarea:focus {
border-color: rgba(255,255,255,0.2);
}
.ffz-dark .conversation-window.has-focus .conversation-input-actions .button,
.ffz-dark .conversation-window.has-focus .conversation-input-actions .follow-button:not(.ember-follow) .follow,
.ffz-dark .follow-button:not(.ember-follow) .conversation-window.has-focus .conversation-input-actions .follow {
background-color: #6441a5;
}
.ffz-dark .conversation-system-message {
background-color: #19191f;
color: #8c8c9c;
border-bottom: 1px solid #32323e;
}
.ffz-dark .conversation-input-bar .emoticon-selector-toggle svg path {
@ -954,6 +1096,32 @@
border-bottom-color: rgb(16,16,16);
}
.ffz-dark .conversation-window {
border: 1px solid rgba(255,255,255,0.2);
}
.ffz-dark:not(.ffz-top-conversations) .conversation-window {
border-bottom: none;
}
.ffz-dark.ffz-top-conversations .conversation-window {
border-top: none;
}
.ffz-dark .conversation-window:not(.collapsed) .conversation-header {
border-bottom: 1px solid rgba(255,255,255,0.2);
}
/*.ffz-dark .ignore-cta {
background-color: #333;
box-shadow: 0 3px 0 #000;
}
.ffz-dark .ignore-cta .conversation-system-message {
color: #ccc;
}
.ffz-dark .conversations-list-icon {
background: #19191f;
color: #8c8c9c;
@ -1054,4 +1222,4 @@
.ffz-dark .conversation-window .new-message-divider span { background: transparent; }
.ffz-dark .conversation-window .timestamp-line:after,
.ffz-dark .conversation-window .new-message-divider:after { display: none; }
.ffz-dark .conversation-window .new-message-divider:after { display: none; }*/

View file

@ -24,7 +24,8 @@ var ftp = require('vinyl-ftp'),
// Server Dependencies
var http = require("http"),
//https = require("https"),
https = require("https"),
net = require('net'),
path = require("path"),
request = require("request"),
url = require("url");
@ -220,7 +221,21 @@ gulp.task('server', function() {
fs.exists(file, function(exists) {
if ( ! exists ) {
util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.bold.blue("CDN") + " GET " + util.colors.magenta(uri));
return request.get("http://cdn.frankerfacez.com/" + uri).pipe(res);
/*https.request({
hostname: 'cdn.frankerfacez.com',
port: 443,
path: uri,
method: 'GET'
}, function(cli_res) {
res.writeHead(cli_res.statusCode, cli_res.headers);
cli_res.on('data', function(chunk) { res.write(chunk); });
cli_res.on('end', function() { res.end() });
}).on('error', function(e) {
res.writeHead(502, {"Access-Control-Allow-Origin": "*"});
res.write('502 Bad Gateway');
res.end();
});*/
return request.get("http://cdn.frankerfacez.com/" + uri).on('error', function(err) { res.end() }).pipe(res);
}
var headers = {"Access-Control-Allow-Origin": "*"};
@ -242,8 +257,33 @@ gulp.task('server', function() {
};
http.createServer(handle_req).listen(8000, "localhost");
//https.createServer(handle_req).listen(8000, "localhost");
if ( fs.existsSync("dev_key.pem") ) {
var https_options = {
key: fs.readFileSync("dev_key.pem"),
cert: fs.readFileSync("dev_cert.pem")
};
util.log("[" + util.colors.cyan("HTTP") + "] Listening on Port: " + util.colors.magenta("8000"));
http.createServer(handle_req).listen(8001, "localhost");
https.createServer(https_options, handle_req).listen(8002, "localhost");
net.createServer(function(conn) {
conn.on('error', function(e) {
util.log("[" + util.colors.cyan("HTTP") + "] Connection Error: " + util.colors.magenta('' + e));
});
conn.once('data', function(buf) {
var address = (buf[0] === 22) ? 8002 : 8001;
var proxy = net.createConnection(address, function() {
proxy.write(buf);
conn.pipe(proxy).pipe(conn);
});
});
}).listen(8000);
util.log("[" + util.colors.cyan("HTTPS") + "] Listening on Port: " + util.colors.magenta("8000"));
} else {
http.createServer(handle_req).listen(8000, "localhost");
util.log("[" + util.colors.cyan("HTTP") + "] Listening on Port: " + util.colors.magenta("8000"));
}
});

View file

@ -2,11 +2,10 @@ var FFZ = window.FrankerFaceZ,
constants = require('./constants'),
utils = require('./utils'),
MOD_BADGES = [
SPECIAL_BADGES = [
['staff', 'staff', 'Staff'],
['admin', 'admin', 'Admin'],
['global_mod', 'global-moderator', 'Global Moderator'],
['mod', 'moderator', 'Moderator']
['global_mod', 'global-moderator', 'Global Moderator']
],
badge_css = function(badge) {
@ -31,6 +30,21 @@ FFZ.settings_info.show_badges = {
};
FFZ.settings_info.sub_notice_badges = {
type: "boolean",
value: false,
category: "Chat Appearance",
name: "Subscriber Notice Badges",
help: "Display a subscriber badge on chat messages about new subscribers.",
on_update: function(val) {
this.toggle_style('badges-sub-notice', ! val);
this.toggle_style('badges-sub-notice-on', val);
}
};
FFZ.settings_info.legacy_badges = {
type: "select",
options: {
@ -120,6 +134,9 @@ FFZ.prototype.setup_badges = function() {
this.toggle_style('badges-circular-small', val === 4);
this.toggle_style('badges-transparent', val === 5);
document.body.classList.toggle('ffz-transparent-badges', val === 5);
this.toggle_style('badges-sub-notice', ! this.settings.sub_notice_badges);
this.toggle_style('badges-sub-notice-on', this.settings.sub_notice_badges);
}
this.toggle_style('badges-legacy', this.settings.legacy_badges === 3);
@ -184,7 +201,7 @@ FFZ.prototype.get_badges = function(user, room_id, badges, msg) {
if ( full_badge.visible !== undefined ) {
var visible = full_badge.visible;
if ( typeof visible === "function" )
visible = visible.bind(this)(room_id, user, msg, badges);
visible = visible.call(this, room_id, user, msg, badges);
if ( ! visible )
continue;
@ -215,26 +232,34 @@ FFZ.prototype.get_badges = function(user, room_id, badges, msg) {
FFZ.prototype.get_line_badges = function(msg) {
var badges = {};
var badges = {},
room = msg.get && msg.get('room') || msg.room,
from = msg.get && msg.get('from') || msg.from,
tags = msg.get && msg.get('tags') || msg.tags || {},
labels = msg.labels || [];
if ( msg.room && msg.from === msg.room )
if ( room && from === room )
badges[0] = {klass: 'broadcaster', title: 'Broadcaster'};
else if ( msg.labels )
for(var i=0, l = MOD_BADGES.length; i < l; i++) {
var mb = MOD_BADGES[i];
if ( msg.labels.indexOf(mb[0]) !== -1 ) {
else {
for(var i=0, l = SPECIAL_BADGES.length; i < l; i++) {
var mb = SPECIAL_BADGES[i];
if ( tags['user-type'] === mb[0] || labels.indexOf(mb[0]) !== -1 ) {
badges[0] = {klass: mb[1], title: mb[2]}
break;
}
}
if ( msg.labels && msg.labels.indexOf('subscriber') !== -1 )
if ( tags.mod || labels.indexOf('mod') !== -1 )
badges[1] = {klass: 'moderator', title: 'Moderator'};
}
if ( tags.subscriber || labels.indexOf('subscriber') !== -1 )
badges[10] = {klass: 'subscriber', title: 'Subscriber'}
if ( msg.labels && msg.labels.indexOf('turbo') !== -1 )
if ( tags.turbo || labels.indexOf('turbo') !== -1 )
badges[15] = {klass: 'turbo', title: 'Turbo'};
// FFZ Badges
return this.get_badges(msg.from, msg.room, badges, msg);
return this.get_badges(from, room, badges, msg);
}
@ -244,8 +269,8 @@ FFZ.prototype.get_other_badges = function(user_id, room_id, user_type, has_sub,
if ( room_id && user_id === room_id )
badges[0] = {klass: 'broadcaster', title: 'Broadcaster'};
else
for(var i=0, l = MOD_BADGES.length; i < l; i++) {
var mb = MOD_BADGES[i];
for(var i=0, l = SPECIAL_BADGES.length; i < l; i++) {
var mb = SPECIAL_BADGES[i];
if ( user_type === mb[0] ) {
badges[0] = {klass: mb[1], title: mb[2]};
break;
@ -325,7 +350,7 @@ FFZ.prototype.bttv_badges = function(data) {
if ( full_badge.visible !== undefined ) {
var visible = full_badge.visible;
if ( typeof visible == "function" )
visible = visible.bind(this)(null, user_id);
visible = visible.call(this, null, user_id);
if ( ! visible )
continue;
@ -407,12 +432,12 @@ FFZ.prototype._legacy_add_donors = function() {
badges = user.badges = user.badges || {};
if ( ! badges[0] )
badges[0] = {id:2};
badges[1] = {id:2};
}
// Special Badges
this.users.sirstendec = {badges: {1: {id:0}}, sets: [4330]};
this.users.zenwan = {badges: {0: {id:2, image: "//cdn.frankerfacez.com/script/momiglee_badge.png", title: "WAN"}}};
this.users.sirstendec = {badges: {5: {id:0}}, sets: [4330]};
this.users.zenwan = {badges: {1: {id:2, image: "//cdn.frankerfacez.com/script/momiglee_badge.png", title: "WAN"}}};
this._legacy_load_bots();
this._legacy_load_donors();
@ -421,7 +446,7 @@ FFZ.prototype._legacy_add_donors = function() {
FFZ.prototype._legacy_load_bots = function(callback, tries) {
jQuery.ajax(constants.SERVER + "script/bots.txt", {context: this})
.done(function(data) {
this._legacy_parse_badges(callback, data, 0, 2, "Bot (By: {})");
this._legacy_parse_badges(callback, data, 1, 2, "Bot (By: {})");
}).fail(function(data) {
if ( data.status == 404 )
@ -436,7 +461,7 @@ FFZ.prototype._legacy_load_bots = function(callback, tries) {
FFZ.prototype._legacy_load_donors = function(callback, tries) {
jQuery.ajax(constants.SERVER + "script/donors.txt", {context: this})
.done(function(data) {
this._legacy_parse_badges(callback, data, 1, 1);
this._legacy_parse_badges(callback, data, 5, 1);
}).fail(function(data) {
if ( data.status == 404 )

View file

@ -1,4 +1,5 @@
var FFZ = window.FrankerFaceZ,
utils = require('./utils'),
hue2rgb = function(p, q, t) {
if ( t < 0 ) t += 1;
@ -29,6 +30,8 @@ FFZ.settings_info.fix_color = {
},
value: '1',
visible: function() { return localStorage.hasOwnProperty("ffz_setting_fix_color") },
category: "Chat Appearance",
no_bttv: true,
@ -64,21 +67,28 @@ FFZ.settings_info.luv_contrast = {
help: "Set the minimum contrast ratio used by Luv Adjustment to ensure colors are readable.",
method: function() {
var old_val = this.settings.luv_contrast,
new_val = prompt("Luv Adjustment Minimum Contrast Ratio\n\nPlease enter a new value for the minimum contrast ratio required between username colors and the background. The default is: 4.5", old_val);
var f = this,
old_val = this.settings.luv_contrast;
utils.prompt(
"Luv Adjustment Minimum Contrast Ratio",
"Please enter a new value for the minimum contrast ratio required between username colors and the background.</p><p><b>Default:</b> 4.5",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
var parsed = parseFloat(new_val);
if ( parsed === NaN || parsed < 1 )
if ( Number.isNaN(parsed) || ! Number.isFinite(parsed) )
parsed = 4.5;
this.settings.set("luv_contrast", parsed);
f.settings.set("luv_contrast", parsed);
});
},
on_update: function(val) {
this._rebuild_contrast();
this._rebuild_filter_styles();
if ( ! this.has_bttv && this.settings.fix_color == '1' )
this._rebuild_colors();
@ -116,7 +126,7 @@ FFZ.settings_info.color_blind = {
FFZ.prototype.setup_colors = function() {
this.toggle_style('chat-colors-gray', !this.has_bttv && this.settings.fix_color === '-1');
this._colors = {};
this._hex_colors = {};
this._rebuild_contrast();
this._update_colors();
@ -141,6 +151,9 @@ FFZ.prototype.setup_colors = function() {
FFZ.Color = {};
FFZ.Color._canvas = null;
FFZ.Color._context = null;
FFZ.Color.CVDMatrix = {
protanope: [ // reds are greatly reduced (1% men)
0.0, 2.02344, -2.52581,
@ -160,44 +173,66 @@ FFZ.Color.CVDMatrix = {
}
var RGBColor = FFZ.Color.RGB = function(r, g, b) {
this.r = r||0; this.g = g||0; this.b = b||0;
var RGBAColor = FFZ.Color.RGBA = function(r, g, b, a) {
this.r = r||0; this.g = g||0; this.b = b||0; this.a = a||0;
};
var HSVColor = FFZ.Color.HSV = function(h, s, v) {
this.h = h||0; this.s = s||0; this.v = v||0;
var HSVAColor = FFZ.Color.HSVA = function(h, s, v, a) {
this.h = h||0; this.s = s||0; this.v = v||0; this.a = a||0;
};
var HSLColor = FFZ.Color.HSL = function(h, s, l) {
this.h = h||0; this.s = s||0; this.l = l||0;
var HSLAColor = FFZ.Color.HSLA = function(h, s, l, a) {
this.h = h||0; this.s = s||0; this.l = l||0; this.a = a||0;
};
var XYZColor = FFZ.Color.XYZ = function(x, y, z) {
this.x = x||0; this.y = y||0; this.z = z||0;
var XYZAColor = FFZ.Color.XYZA = function(x, y, z, a) {
this.x = x||0; this.y = y||0; this.z = z||0; this.a = a||0;
};
var LUVColor = FFZ.Color.LUV = function(l, u, v) {
this.l = l||0; this.u = u||0; this.v = v||0;
var LUVAColor = FFZ.Color.LUVA = function(l, u, v, a) {
this.l = l||0; this.u = u||0; this.v = v||0; this.a = a||0;
};
// RGB Colors
// RGBA Colors
RGBColor.prototype.eq = function(rgb) {
return rgb.r === this.r && rgb.g === this.g && rgb.b === this.b;
RGBAColor.prototype.eq = function(rgb) {
return rgb.r === this.r && rgb.g === this.g && rgb.b === this.b && rgb.a === this.a;
}
RGBColor.fromCSS = function(rgb) {
RGBAColor.fromName = function(name) {
var context = FFZ.Color._context;
if ( ! context ) {
var canvas = FFZ.Color._canvas = document.createElement('canvas');
context = FFZ.Color._context = canvas.getContext("2d");
}
context.clearRect(0,0,1,1);
context.fillStyle = name;
context.fillRect(0,0,1,1);
var data = context.getImageData(0,0,1,1);
if ( ! data || ! data.data || data.data.length !== 4 )
return null;
return new RGBAColor(data.data[0], data.data[1], data.data[2], data.data[3] / 255);
}
RGBAColor.fromCSS = function(rgb) {
if ( ! rgb )
return null;
rgb = rgb.trim();
if ( rgb.charAt(0) === '#' )
return RGBColor.fromHex(rgb);
return RGBAColor.fromHex(rgb);
var match = /rgba?\( *(\d+%?) *, *(\d+%?) *, *(\d+%?) *(?:,[^\)]+)?\)/.exec(rgb);
var match = /rgba?\( *(\d+%?) *, *(\d+%?) *, *(\d+%?) *(?:, *([\d\.]+))?\)/i.exec(rgb);
if ( match ) {
var r = match[1],
g = match[2],
b = match[3];
b = match[3],
a = match[4];
if ( r.charAt(r.length-1) === '%' )
r = 255 * (parseInt(r) / 100);
@ -214,26 +249,36 @@ RGBColor.fromCSS = function(rgb) {
else
b = parseInt(b);
return new RGBColor(
if ( a )
if ( a.charAt(a.length-1) === '%' )
a = parseInt(a) / 100;
else
a = parseFloat(a);
else
a = 1;
return new RGBAColor(
Math.min(Math.max(0, r), 255),
Math.min(Math.max(0, g), 255),
Math.min(Math.max(0, b), 255)
Math.min(Math.max(0, b), 255),
Math.min(Math.max(0, a), 1)
);
}
return null;
return RGBAColor.fromName(rgb);
}
RGBColor.fromHex = function(code) {
RGBAColor.fromHex = function(code) {
var raw = parseInt(code.charAt(0) === '#' ? code.substr(1) : code, 16);
return new RGBColor(
return new RGBAColor(
(raw >> 16), // Red
(raw >> 8 & 0x00FF), // Green
(raw & 0x0000FF) // Blue
(raw & 0x0000FF), // Blue,
1 // Alpha
)
}
RGBColor.fromHSV = function(h, s, v) {
RGBAColor.fromHSVA = function(h, s, v, a) {
var r, g, b,
i = Math.floor(h * 6),
@ -251,55 +296,58 @@ RGBColor.fromHSV = function(h, s, v) {
case 5: r = v, g = p, b = q;
}
return new RGBColor(
return new RGBAColor(
Math.round(Math.min(Math.max(0, r*255), 255)),
Math.round(Math.min(Math.max(0, g*255), 255)),
Math.round(Math.min(Math.max(0, b*255), 255))
Math.round(Math.min(Math.max(0, b*255), 255)),
a === undefined ? 1 : a
);
}
RGBColor.fromXYZ = function(x, y, z) {
RGBAColor.fromXYZA = function(x, y, z, a) {
var R = 3.240479 * x - 1.537150 * y - 0.498535 * z,
G = -0.969256 * x + 1.875992 * y + 0.041556 * z,
B = 0.055648 * x - 0.204043 * y + 1.057311 * z;
// Make sure we end up in a real color space
return new RGBColor(
Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(R))),
Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(G))),
Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(B)))
return new RGBAColor(
Math.max(0, Math.min(255, 255 * XYZAColor.channelConverter(R))),
Math.max(0, Math.min(255, 255 * XYZAColor.channelConverter(G))),
Math.max(0, Math.min(255, 255 * XYZAColor.channelConverter(B))),
a === undefined ? 1 : a
);
}
RGBColor.fromHSL = function(h, s, l) {
RGBAColor.fromHSLA = function(h, s, l, a) {
if ( s === 0 ) {
var v = Math.round(Math.min(Math.max(0, 255*l), 255));
return new RGBColor(v, v, v);
return new RGBAColor(v, v, v, a === undefined ? 1 : a);
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s,
p = 2 * l - q;
return new RGBColor(
return new RGBAColor(
Math.round(Math.min(Math.max(0, 255 * hue2rgb(p, q, h + 1/3)), 255)),
Math.round(Math.min(Math.max(0, 255 * hue2rgb(p, q, h)), 255)),
Math.round(Math.min(Math.max(0, 255 * hue2rgb(p, q, h - 1/3)), 255))
Math.round(Math.min(Math.max(0, 255 * hue2rgb(p, q, h - 1/3)), 255)),
a === undefined ? 1 : a
);
}
RGBColor.prototype.toHSV = function() { return HSVColor.fromRGB(this.r, this.g, this.b); }
RGBColor.prototype.toHSL = function() { return HSLColor.fromRGB(this.r, this.g, this.b); }
RGBColor.prototype.toCSS = function() { return "rgb(" + Math.round(this.r) + "," + Math.round(this.g) + "," + Math.round(this.b) + ")"; }
RGBColor.prototype.toXYZ = function() { return XYZColor.fromRGB(this.r, this.g, this.b); }
RGBColor.prototype.toLUV = function() { return this.toXYZ().toLUV(); }
RGBAColor.prototype.toHSVA = function() { return HSVAColor.fromRGBA(this.r, this.g, this.b, this.a); }
RGBAColor.prototype.toHSLA = function() { return HSLAColor.fromRGBA(this.r, this.g, this.b, this.a); }
RGBAColor.prototype.toCSS = function() { return "rgb" + (this.a !== 1 ? "a" : "") + "(" + Math.round(this.r) + "," + Math.round(this.g) + "," + Math.round(this.b) + (this.a !== 1 ? "," + this.a : "") + ")"; }
RGBAColor.prototype.toXYZA = function() { return XYZAColor.fromRGBA(this.r, this.g, this.b, this.a); }
RGBAColor.prototype.toLUVA = function() { return this.toXYZA().toLUVA(); }
RGBColor.prototype.toHex = function() {
RGBAColor.prototype.toHex = function() {
var rgb = this.b | (this.g << 8) | (this.r << 16);
return '#' + (0x1000000 + rgb).toString(16).slice(1);
}
RGBColor.prototype.luminance = function() {
RGBAColor.prototype.luminance = function() {
var rgb = [this.r / 255, this.g / 255, this.b / 255];
for (var i =0, l = rgb.length; i < l; i++) {
if (rgb[i] <= 0.03928) {
@ -312,19 +360,20 @@ RGBColor.prototype.luminance = function() {
}
RGBColor.prototype.brighten = function(amount) {
RGBAColor.prototype.brighten = function(amount) {
amount = typeof amount === "number" ? amount : 1;
amount = Math.round(255 * (amount / 100));
return new RGBColor(
return new RGBAColor(
Math.max(0, Math.min(255, this.r + amount)),
Math.max(0, Math.min(255, this.g + amount)),
Math.max(0, Math.min(255, this.b + amount))
Math.max(0, Math.min(255, this.b + amount)),
this.a
);
}
RGBColor.prototype.daltonize = function(type, amount) {
RGBAColor.prototype.daltonize = function(type, amount) {
amount = typeof amount === "number" ? amount : 1.0;
var cvd;
if ( typeof type === "string" ) {
@ -366,21 +415,22 @@ RGBColor.prototype.daltonize = function(type, amount) {
G = Math.min(Math.max(0, GG + this.g), 255);
B = Math.min(Math.max(0, BB + this.b), 255);
return new RGBColor(R, G, B);
return new RGBAColor(R, G, B, this.a);
}
RGBColor.prototype._r = function(r) { return new RGBColor(r, this.g, this.b); }
RGBColor.prototype._g = function(g) { return new RGBColor(this.r, g, this.b); }
RGBColor.prototype._b = function(b) { return new RGBColor(this.r, this.g, b); }
RGBAColor.prototype._r = function(r) { return new RGBAColor(r, this.g, this.b, this.a); }
RGBAColor.prototype._g = function(g) { return new RGBAColor(this.r, g, this.b, this.a); }
RGBAColor.prototype._b = function(b) { return new RGBAColor(this.r, this.g, b, this.a); }
RGBAColor.prototype._a = function(a) { return new RGBAColor(this.r, this.g, this.b, a); }
// HSL Colors
HSLColor.prototype.eq = function(hsl) {
return hsl.h === this.h && hsl.s === this.s && hsl.l === this.l;
HSLAColor.prototype.eq = function(hsl) {
return hsl.h === this.h && hsl.s === this.s && hsl.l === this.l && hsl.a === this.a;
}
HSLColor.fromRGB = function(r, g, b) {
HSLAColor.fromRGBA = function(r, g, b, a) {
r /= 255; g /= 255; b /= 255;
var max = Math.max(r,g,b),
@ -406,27 +456,27 @@ HSLColor.fromRGB = function(r, g, b) {
h /= 6;
}
return new HSLColor(h, s, l);
return new HSLAColor(h, s, l, a === undefined ? 1 : a);
}
HSLColor.prototype.toRGB = function() { return RGBColor.fromHSL(this.h, this.s, this.l); }
HSLColor.prototype.toCSS = function() { return "hsl(" + Math.round(this.h*360) + "," + Math.round(this.s*100) + "%," + Math.round(this.l*100) + "%)"; }
HSLColor.prototype.toHex = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toHex(); }
HSLColor.prototype.toHSV = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toHSV(); }
HSLColor.prototype.toXYZ = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toXYZ(); }
HSLColor.prototype.toLUV = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toLUV(); }
HSLAColor.prototype.toRGBA = function() { return RGBAColor.fromHSLA(this.h, this.s, this.l, this.a); }
HSLAColor.prototype.toCSS = function() { return "hsl" + (this.a !== 1 ? "a" : "") + "(" + Math.round(this.h*360) + "," + Math.round(this.s*100) + "%," + Math.round(this.l*100) + "%" + (this.a !== 1 ? "," + this.a : "") + ")"; }
HSLAColor.prototype.toHSVA = function() { return this.toRGBA().toHSVA(); }
HSLAColor.prototype.toXYZA = function() { return this.toRGBA().toXYZA(); }
HSLAColor.prototype.toLUVA = function() { return this.toRGBA().toLUVA(); }
HSLColor.prototype._h = function(h) { return new HSLColor(h, this.s, this.l); }
HSLColor.prototype._s = function(s) { return new HSLColor(this.h, s, this.l); }
HSLColor.prototype._l = function(l) { return new HSLColor(this.h, this.s, l); }
HSLAColor.prototype._h = function(h) { return new HSLAColor(h, this.s, this.l, this.a); }
HSLAColor.prototype._s = function(s) { return new HSLAColor(this.h, s, this.l, this.a); }
HSLAColor.prototype._l = function(l) { return new HSLAColor(this.h, this.s, l, this.a); }
HSLAColor.prototype._a = function(a) { return new HSLAColor(this.h, this.s, this.l, a); }
// HSV Colors
HSVColor.prototype.eq = function(hsv) { return hsv.h === this.h && hsv.s === this.s && hsv.v === this.v; }
HSVAColor.prototype.eq = function(hsv) { return hsv.h === this.h && hsv.s === this.s && hsv.v === this.v && hsv.a === this.a; }
HSVColor.fromRGB = function(r, g, b) {
HSVAColor.fromRGBA = function(r, g, b, a) {
r /= 255; g /= 255; b /= 255;
var max = Math.max(r, g, b),
@ -453,24 +503,25 @@ HSVColor.fromRGB = function(r, g, b) {
h /= 6;
}
return new HSVColor(h, s, v);
return new HSVAColor(h, s, v, a === undefined ? 1 : a);
}
HSVColor.prototype.toRGB = function() { return RGBColor.fromHSV(this.h, this.s, this.v); }
HSVColor.prototype.toHSL = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toHSL(); }
HSVColor.prototype.toXYZ = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toXYZ(); }
HSVColor.prototype.toLUV = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toLUV(); }
HSVAColor.prototype.toRGBA = function() { return RGBAColor.fromHSVA(this.h, this.s, this.v, this.a); }
HSVAColor.prototype.toHSLA = function() { return this.toRGBA().toHSLA(); }
HSVAColor.prototype.toXYZA = function() { return this.toRGBA().toXYZA(); }
HSVAColor.prototype.toLUVA = function() { return this.toRGBA().toLUVA(); }
HSVColor.prototype._h = function(h) { return new HSVColor(h, this.s, this.v); }
HSVColor.prototype._s = function(s) { return new HSVColor(this.h, s, this.v); }
HSVColor.prototype._v = function(v) { return new HSVColor(this.h, this.s, v); }
HSVAColor.prototype._h = function(h) { return new HSVAColor(h, this.s, this.v, this.a); }
HSVAColor.prototype._s = function(s) { return new HSVAColor(this.h, s, this.v, this.a); }
HSVAColor.prototype._v = function(v) { return new HSVAColor(this.h, this.s, v, this.a); }
HSVAColor.prototype._a = function(a) { return new HSVAColor(this.h, this.s, this.v, a); }
// XYZ Colors
RGBColor.channelConverter = function (channel) {
RGBAColor.channelConverter = function (channel) {
// http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html
// This converts rgb 8bit to rgb linear, lazy because the other algorithm is really really dumb
return Math.pow(channel, 2.2);
@ -479,7 +530,7 @@ RGBColor.channelConverter = function (channel) {
return (channel <= 0.04045) ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
};
XYZColor.channelConverter = function (channel) {
XYZAColor.channelConverter = function (channel) {
// Using lazy conversion in the other direction as well
return Math.pow(channel, 1/2.2);
@ -488,27 +539,28 @@ XYZColor.channelConverter = function (channel) {
};
XYZColor.prototype.eq = function(xyz) { return xyz.x === this.x && xyz.y === this.y && xyz.z === this.z; }
XYZAColor.prototype.eq = function(xyz) { return xyz.x === this.x && xyz.y === this.y && xyz.z === this.z; }
XYZColor.fromRGB = function(r, g, b) {
var R = RGBColor.channelConverter(r / 255),
G = RGBColor.channelConverter(g / 255),
B = RGBColor.channelConverter(b / 255);
XYZAColor.fromRGBA = function(r, g, b, a) {
var R = RGBAColor.channelConverter(r / 255),
G = RGBAColor.channelConverter(g / 255),
B = RGBAColor.channelConverter(b / 255);
return new XYZColor(
return new XYZAColor(
0.412453 * R + 0.357580 * G + 0.180423 * B,
0.212671 * R + 0.715160 * G + 0.072169 * B,
0.019334 * R + 0.119193 * G + 0.950227 * B
0.019334 * R + 0.119193 * G + 0.950227 * B,
a === undefined ? 1 : a
);
}
XYZColor.fromLUV = function(l, u, v) {
var deltaGammaFactor = 1 / (XYZColor.WHITE.x + 15 * XYZColor.WHITE.y + 3 * XYZColor.WHITE.z);
var uDeltaGamma = 4 * XYZColor.WHITE.x * deltaGammaFactor;
var vDeltagamma = 9 * XYZColor.WHITE.y * deltaGammaFactor;
XYZAColor.fromLUVA = function(l, u, v, alpha) {
var deltaGammaFactor = 1 / (XYZAColor.WHITE.x + 15 * XYZAColor.WHITE.y + 3 * XYZAColor.WHITE.z);
var uDeltaGamma = 4 * XYZAColor.WHITE.x * deltaGammaFactor;
var vDeltagamma = 9 * XYZAColor.WHITE.y * deltaGammaFactor;
// XYZColor.EPSILON * XYZColor.KAPPA = 8
var Y = (l > 8) ? Math.pow((l + 16) / 116, 3) : l / XYZColor.KAPPA;
// XYZAColor.EPSILON * XYZAColor.KAPPA = 8
var Y = (l > 8) ? Math.pow((l + 16) / 116, 3) : l / XYZAColor.KAPPA;
var a = 1/3 * (((52 * l) / (u + 13 * l * uDeltaGamma)) - 1);
var b = -5 * Y;
@ -518,36 +570,37 @@ XYZColor.fromLUV = function(l, u, v) {
var X = (d - b) / (a - c);
var Z = X * a + b;
return new XYZColor(X, Y, Z);
return new XYZAColor(X, Y, Z, alpha === undefined ? 1 : alpha);
}
XYZColor.prototype.toRGB = function() { return RGBColor.fromXYZ(this.x, this.y, this.z); }
XYZColor.prototype.toLUV = function() { return LUVColor.fromXYZ(this.x, this.y, this.z); }
XYZColor.prototype.toHSL = function() { return RGBColor.fromXYZ(this.x, this.y, this.z).toHSL(); }
XYZColor.prototype.toHSV = function() { return RGBColor.fromXYZ(this.x, this.y, this.z).toHSV(); }
XYZAColor.prototype.toRGBA = function() { return RGBAColor.fromXYZA(this.x, this.y, this.z, this.a); }
XYZAColor.prototype.toLUVA = function() { return LUVAColor.fromXYZA(this.x, this.y, this.z, this.a); }
XYZAColor.prototype.toHSLA = function() { return this.toRGBA().toHSLA(); }
XYZAColor.prototype.toHSVA = function() { return this.toRGBA().toHSVA(); }
XYZColor.prototype._x = function(x) { return new XYZColor(x, this.y, this.z); }
XYZColor.prototype._y = function(y) { return new XYZColor(this.x, y, this.z); }
XYZColor.prototype._z = function(z) { return new XYZColor(this.x, this.y, z); }
XYZAColor.prototype._x = function(x) { return new XYZAColor(x, this.y, this.z, this.a); }
XYZAColor.prototype._y = function(y) { return new XYZAColor(this.x, y, this.z, this.a); }
XYZAColor.prototype._z = function(z) { return new XYZAColor(this.x, this.y, z, this.a); }
XYZAColor.prototype._a = function(a) { return new XYZAColor(this.x, this.y, this.z, a); }
// LUV Colors
XYZColor.EPSILON = Math.pow(6 / 29, 3);
XYZColor.KAPPA = Math.pow(29 / 3, 3);
XYZColor.WHITE = (new RGBColor(255, 255, 255)).toXYZ();
XYZAColor.EPSILON = Math.pow(6 / 29, 3);
XYZAColor.KAPPA = Math.pow(29 / 3, 3);
XYZAColor.WHITE = (new RGBAColor(255, 255, 255, 1)).toXYZA();
LUVColor.prototype.eq = function(luv) { return luv.l === this.l && luv.u === this.u && luv.v === this.v; }
LUVAColor.prototype.eq = function(luv) { return luv.l === this.l && luv.u === this.u && luv.v === this.v; }
LUVColor.fromXYZ = function(X, Y, Z) {
var deltaGammaFactor = 1 / (XYZColor.WHITE.x + 15 * XYZColor.WHITE.y + 3 * XYZColor.WHITE.z);
var uDeltaGamma = 4 * XYZColor.WHITE.x * deltaGammaFactor;
var vDeltagamma = 9 * XYZColor.WHITE.y * deltaGammaFactor;
LUVAColor.fromXYZA = function(X, Y, Z, a) {
var deltaGammaFactor = 1 / (XYZAColor.WHITE.x + 15 * XYZAColor.WHITE.y + 3 * XYZAColor.WHITE.z);
var uDeltaGamma = 4 * XYZAColor.WHITE.x * deltaGammaFactor;
var vDeltagamma = 9 * XYZAColor.WHITE.y * deltaGammaFactor;
var yGamma = Y / XYZColor.WHITE.y;
var yGamma = Y / XYZAColor.WHITE.y;
var deltaDivider = (X + 15 * Y + 3 * Z);
if (deltaDivider === 0) {
@ -559,23 +612,24 @@ LUVColor.fromXYZ = function(X, Y, Z) {
var uDelta = 4 * X * deltaFactor;
var vDelta = 9 * Y * deltaFactor;
var L = (yGamma > XYZColor.EPSILON) ? 116 * Math.pow(yGamma, 1/3) - 16 : XYZColor.KAPPA * yGamma;
var L = (yGamma > XYZAColor.EPSILON) ? 116 * Math.pow(yGamma, 1/3) - 16 : XYZAColor.KAPPA * yGamma;
var u = 13 * L * (uDelta - uDeltaGamma);
var v = 13 * L * (vDelta - vDeltagamma);
return new LUVColor(L, u, v);
return new LUVAColor(L, u, v, a === undefined ? 1 : a);
}
LUVColor.prototype.toXYZ = function() { return XYZColor.fromLUV(this.l, this.u, this.v); }
LUVColor.prototype.toRGB = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toRGB(); }
LUVColor.prototype.toHSL = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toHSL(); }
LUVColor.prototype.toHSV = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toHSV(); }
LUVAColor.prototype.toXYZA = function() { return XYZAColor.fromLUVA(this.l, this.u, this.v, this.a); }
LUVAColor.prototype.toRGBA = function() { return this.toXYZA().toRGBA(); }
LUVAColor.prototype.toHSLA = function() { return this.toXYZA().toHSLA(); }
LUVAColor.prototype.toHSVA = function() { return this.toXYZA().toHSVA(); }
LUVColor.prototype._l = function(l) { return new LUVColor(l, this.u, this.v); }
LUVColor.prototype._u = function(u) { return new LUVColor(this.l, u, this.v); }
LUVColor.prototype._v = function(v) { return new LUVColor(this.l, this.u, v); }
LUVAColor.prototype._l = function(l) { return new LUVAColor(l, this.u, this.v, this.a); }
LUVAColor.prototype._u = function(u) { return new LUVAColor(this.l, u, this.v, this.a); }
LUVAColor.prototype._v = function(v) { return new LUVAColor(this.l, this.u, v, this.a); }
LUVAColor.prototype._a = function(a) { return new LUVAColor(this.l, this.u, this.v, a); }
// --------------------
@ -583,8 +637,11 @@ LUVColor.prototype._v = function(v) { return new LUVColor(this.l, this.u, v); }
// --------------------
FFZ.prototype._rebuild_contrast = function() {
this._luv_required_bright = new XYZColor(0, (this.settings.luv_contrast * (new RGBColor(35,35,35).toXYZ().y + 0.05) - 0.05), 0).toLUV().l;
this._luv_required_dark = new XYZColor(0, ((new RGBColor(217,217,217).toXYZ().y + 0.05) / this.settings.luv_contrast - 0.05), 0).toLUV().l;
this._luv_required_bright = new XYZAColor(0, (this.settings.luv_contrast * (new RGBAColor(35,35,35,1).toXYZA().y + 0.05) - 0.05), 0, 1).toLUVA().l;
this._luv_required_dark = new XYZAColor(0, ((new RGBAColor(217,217,217,1).toXYZA().y + 0.05) / this.settings.luv_contrast - 0.05), 0, 1).toLUVA().l;
this._luv_background_bright = new XYZAColor(0, (this.settings.luv_contrast * (RGBAColor.fromCSS("#3c3a41").toXYZA().y + 0.05) - 0.05), 0, 1).toLUVA().l;
this._luv_background_dark = new XYZAColor(0, ((RGBAColor.fromCSS("#acacbf").toXYZA().y + 0.05) / this.settings.luv_contrast - 0.05), 0, 1).toLUVA().l;
}
FFZ.prototype._rebuild_colors = function() {
@ -592,7 +649,7 @@ FFZ.prototype._rebuild_colors = function() {
return;
// With update colors, we'll automatically process the colors we care about.
this._colors = {};
this._hex_colors = {};
this._update_colors();
}
@ -602,7 +659,8 @@ FFZ.prototype._update_colors = function(darkness_only) {
var Layout = window.App && App.__container__.lookup('controller:layout'),
Settings = window.App && App.__container__.lookup('controller:settings'),
is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('settings.darkMode'));
is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('settings.darkMode')),
cr_dark = this.settings.dark_twitch || (Layout && Layout.get('isTheatreMode'));
if ( darkness_only && this._color_old_darkness === is_dark )
return;
@ -618,29 +676,50 @@ FFZ.prototype._update_colors = function(darkness_only) {
if ( ! colors )
continue;
bit.style.color = is_dark ? colors[1] : colors[0];
bit.style.color = (bit.classList.contains('replay-color') ? cr_dark : is_dark) ? colors[1] : colors[0];
}
}
FFZ.prototype._handle_filter_color = function(color) {
if (!( color instanceof RGBAColor ))
color = RGBAColor.fromCSS(color);
var light_color = color,
dark_color = color,
luv = color.toLUVA();
if ( luv.l < this._luv_background_bright )
light_color = luv._l(this._luv_background_bright).toRGBA();
if ( luv.l > this._luv_background_dark )
dark_color = luv._l(this._luv_background_dark).toRGBA();
return [light_color, dark_color];
}
FFZ.prototype._handle_color = function(color) {
if ( color instanceof RGBColor )
color = color.toHex();
if ( color instanceof RGBAColor )
color = color.toCSS();
if ( ! color || this._colors.hasOwnProperty(color) )
return this._colors[color];
if ( ! color )
return null;
var rgb = RGBColor.fromHex(color),
if ( this._hex_colors.hasOwnProperty(color) )
return this._hex_colors[color];
light_color = color,
dark_color = color;
var rgb = RGBAColor.fromCSS(color),
light_color = rgb,
dark_color = rgb;
// Color Blindness Handling
if ( this.settings.color_blind !== '0' ) {
var new_color = rgb.daltonize(this.settings.color_blind);
if ( ! rgb.eq(new_color) ) {
rgb = new_color;
light_color = dark_color = rgb.toHex();
light_color = dark_color = rgb;
}
}
@ -657,7 +736,7 @@ FFZ.prototype._handle_color = function(color) {
break;
}
light_color = nc.toHex();
light_color = nc;
}
if ( lum < 0.15 ) {
@ -668,46 +747,46 @@ FFZ.prototype._handle_color = function(color) {
break;
}
dark_color = nc.toHex();
dark_color = nc;
}
}
// Color Processing - HSL
if ( this.settings.fix_color === '2' ) {
var hsl = rgb.toHSL();
var hsl = rgb.toHSLA();
light_color = hsl._l(Math.min(Math.max(0, 0.7 * hsl.l), 1)).toHex();
dark_color = hsl._l(Math.min(Math.max(0, 0.3 + (0.7 * hsl.l)), 1)).toHex();
light_color = hsl._l(Math.min(Math.max(0, 0.7 * hsl.l), 1)).toRGBA();
dark_color = hsl._l(Math.min(Math.max(0, 0.3 + (0.7 * hsl.l)), 1)).toRGBA();
}
// Color Processing - HSV
if ( this.settings.fix_color === '3' ) {
var hsv = rgb.toHSV();
var hsv = rgb.toHSVA();
if ( hsv.s === 0 ) {
// Black and White
light_color = hsv._v(Math.min(Math.max(0.5, 0.5 * hsv.v), 1)).toRGB().toHex();
dark_color = hsv._v(Math.min(Math.max(0.5, 0.5 + (0.5 * hsv.v)), 1)).toRGB().toHex();
light_color = hsv._v(Math.min(Math.max(0.5, 0.5 * hsv.v), 1)).toRGBA();
dark_color = hsv._v(Math.min(Math.max(0.5, 0.5 + (0.5 * hsv.v)), 1)).toRGBA();
} else {
light_color = RGBColor.fromHSV(hsv.h, Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.s)), 1), Math.min(0.7, hsv.v)).toHex();
dark_color = RGBColor.fromHSV(hsv.h, Math.min(0.7, hsv.s), Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.v)), 1)).toHex();
light_color = RGBAColor.fromHSVA(hsv.h, Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.s)), 1), Math.min(0.7, hsv.v), hsv.a);
dark_color = RGBAColor.fromHSVA(hsv.h, Math.min(0.7, hsv.s), Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.v)), 1), hsv.a);
}
}
// Color Processing - LUV
if ( this.settings.fix_color === '1' ) {
var luv = rgb.toLUV();
var luv = rgb.toLUVA();
if ( luv.l > this._luv_required_dark )
light_color = luv._l(this._luv_required_dark).toRGB().toHex();
light_color = luv._l(this._luv_required_dark).toRGBA();
if ( luv.l < this._luv_required_bright )
dark_color = luv._l(this._luv_required_bright).toRGB().toHex();
dark_color = luv._l(this._luv_required_bright).toRGBA();
}
var out = this._colors[color] = [light_color, dark_color];
var out = this._hex_colors[color] = [light_color.toHex(), dark_color.toHex()];
return out;
}

File diff suppressed because one or more lines are too long

View file

@ -100,30 +100,30 @@ FFZ.prototype.setup_channel = function() {
var t = this,
id = t.get('content.id');
id && Twitch.api && Twitch.api.get("streams/" + id, {}, {version:3})
id && utils.api.get("streams/" + id, {}, {version:3})
.done(function(data) {
if ( ! data || ! data.stream ) {
// If the stream is offline, clear its created_at time and set it to zero viewers.
t.set('stream.created_at', null);
t.set('stream.viewers', 0);
t.set('content.stream.created_at', null);
t.set('content.stream.viewers', 0);
return;
}
t.set('stream.created_at', data.stream.created_at || null);
t.set('stream.viewers', data.stream.viewers || 0);
t.set('content.stream.created_at', data.stream.created_at || null);
t.set('content.stream.viewers', data.stream.viewers || 0);
var game = data.stream.game || (data.stream.channel && data.stream.channel.game);
if ( game ) {
t.set('game', game);
t.set('rollbackData.game', game);
t.set('content.game', game);
t.set('content.rollbackData.game', game);
}
if ( data.stream.channel ) {
if ( data.stream.channel.status )
t.set('status', data.stream.channel.status);
t.set('content.status', data.stream.channel.status);
if ( data.stream.channel.views )
t.set('views', data.stream.channel.views);
t.set('content.views', data.stream.channel.views);
if ( data.stream.channel.followers && t.get('content.followers.isLoaded') )
t.set('content.followers.total', data.stream.channel.followers);
@ -206,7 +206,7 @@ FFZ.prototype._modify_cindex = function(view) {
},
ffzInit: function() {
var id = this.get('controller.id'),
var id = this.get('controller.content.id') || this.get('controller.id'),
el = this.get('element');
f._cindex = this;
@ -251,8 +251,8 @@ FFZ.prototype._modify_cindex = function(view) {
if ( f.has_bttv || ! f.settings.stream_title )
return;
var status = this.get("controller.status"),
channel = this.get("controller.id");
var status = this.get("controller.content.status") || this.get("controller.status"),
channel = this.get("controller.content.id") || this.get("controller.id");
status = f.render_tokens(f.tokenize_line(channel, channel, status, true));
@ -267,7 +267,7 @@ FFZ.prototype._modify_cindex = function(view) {
ffzUpdateHostButton: function() {
var channel_id = this.get('controller.id'),
var channel_id = this.get('controller.content.id') || this.get('controller.id'),
hosted_id = this.get('controller.hostModeTarget.id'),
user = f.get_user(),
@ -360,7 +360,7 @@ FFZ.prototype._modify_cindex = function(view) {
},
ffzClickHost: function(controller, is_host) {
var target = controller.get(is_host ? 'controller.hostModeTarget.id' : 'controller.id'),
var target = is_host ? controller.get('controller.hostModeTarget.id') : (controller.get('controller.content.id') || controller.get('controller.id')),
user = f.get_user(),
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
now_hosting = room && room.ffz_host_target;
@ -381,7 +381,7 @@ FFZ.prototype._modify_cindex = function(view) {
ffzUpdateChatters: function() {
// Get the counts.
var room_id = this.get('controller.id'),
var room_id = this.get('controller.content.id') || this.get('controller.id'),
room = f.rooms && f.rooms[room_id];
if ( ! room || ! f.settings.chatter_count ) {
@ -457,7 +457,7 @@ FFZ.prototype._modify_cindex = function(view) {
ffzUpdatePlayerStats: function() {
var channel_id = this.get('controller.id'),
var channel_id = this.get('controller.content.id') || this.get('controller.id'),
hosted_id = this.get('controller.hostModeTarget.id'),
el = this.get('element');
@ -471,13 +471,13 @@ FFZ.prototype._modify_cindex = function(view) {
player = undefined, stats = undefined;
try {
player = player_cont && player_cont.ffz_player;
player = player_cont && player_cont.get && player_cont.get('player');
stats = player && player.stats;
} catch(err) {
f.error("Channel ffzUpdatePlayerStats: player.stats: " + err);
}
if ( ! container || ! f.settings.player_stats || ! stats || stats.hlsLatencyBroadcaster === 'NaN' || stats.hlsLatencyBroadcaster === NaN ) {
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hlsLatencyBroadcaster || stats.hlsLatencyBroadcaster === 'NaN' || Number.isNaN(stats.hlsLatencyBroadcaster) ) {
if ( stat_el )
stat_el.parentElement.removeChild(stat_el);
} else {
@ -538,7 +538,7 @@ FFZ.prototype._modify_cindex = function(view) {
}
if ( ! container || ! f.settings.player_stats || ! stats || stats.hlsLatencyBroadcaster === 'NaN' || stats.hlsLatencyBroadcaster === NaN ) {
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hlsLatencyBroadcaster || stats.hlsLatencyBroadcaster === 'NaN' || Number.isNaN(stats.hlsLatencyBroadcaster) ) {
if ( stat_el )
stat_el.parentElement.removeChild(stat_el);
} else {
@ -648,7 +648,7 @@ FFZ.prototype._modify_cindex = function(view) {
},
ffzTeardown: function() {
var id = this.get('controller.id');
var id = this.get('controller.content.id') || this.get('controller.id');
if ( id )
f.ws_send("unsub", "channel." + id);

View file

@ -13,7 +13,12 @@ FFZ.basic_settings.delayed_chat = {
0: "No Delay",
300: "Minor (Bot Moderation; 0.3s)",
1200: "Normal (Human Moderation; 1.2s)",
5000: "Large (Spoiler Removal / Really Slow Mods; 5s)"
5000: "Large (Spoiler Removal / Really Slow Mods; 5s)",
10000: "Extra Large (10s)",
15000: "Extremely Large (15s)",
20000: "Mods Asleep; Delay Chat (20s)",
30000: "Half a Minute (30s)",
60000: "Why??? (1m)"
},
category: "Chat",
@ -134,7 +139,12 @@ FFZ.settings_info.chat_delay = {
0: "No Delay",
300: "Minor (Bot Moderation; 0.3s)",
1200: "Normal (Human Moderation; 1.2s)",
5000: "Large (Spoiler Removal / Really Slow Mods; 5s)"
5000: "Large (Spoiler Removal / Really Slow Mods; 5s)",
10000: "Extra Large (10s)",
15000: "Extremely Large (15s)",
20000: "Mods Asleep; Delay Chat (20s)",
30000: "Half a Minute (30s)",
60000: "Why??? (1m)"
},
value: 0,
@ -316,6 +326,25 @@ FFZ.settings_info.visible_rooms = {
// Initialization
// --------------------
FFZ.prototype.refresh_chat = function() {
var parents, lines = jQuery('ul.chat-lines');
if ( this.has_bttv || ! lines || ! lines.length )
return;
parents = lines.parents('.chatReplay');
if ( parents && parents.length )
return;
// There are chat-lines in the DOM and they aren't chat replay.
var controller = App.__container__.lookup('controller:chat');
if ( ! controller )
return;
var current_room = controller.get("currentRoom");
controller.blurRoom();
controller.focusRoom(current_room);
}
FFZ.prototype.setup_chatview = function() {
document.body.classList.toggle("ffz-minimal-chat-head", this.settings.minimal_chat === 1 || this.settings.minimal_chat === 3);
document.body.classList.toggle("ffz-minimal-chat-input", this.settings.minimal_chat === 2 || this.settings.minimal_chat === 3);
@ -423,7 +452,7 @@ FFZ.prototype.setup_chatview = function() {
} catch(err) { }
// Modify all existing Chat views.
var views = window.App && App.__container__.lookup('-view-registry:main') || Ember.View.views;
var views = window.App && App.__container__.lookup('-view-registry:main');
for(var key in views) {
if ( ! views.hasOwnProperty(key) )
continue;
@ -434,14 +463,18 @@ FFZ.prototype.setup_chatview = function() {
this.log("Manually updating existing Chat view.", view);
try {
if ( ! view.ffzInit )
this._modify_cview(view);
view.ffzInit();
} catch(err) {
this.error("setup: build_ui_link: " + err);
}
}
/*this.log("Hooking the Ember chat-right-column Component.");
var Column = App.__container__.resolve('component:')
this.log("Hooking the Ember 'Right Column' controller. Seriously...");
/*this.log("Hooking the Ember 'Right Column' controller. Seriously...");
var Column = App.__container__.lookup('controller:right-column');
if ( ! Column )
return;
@ -454,7 +487,7 @@ FFZ.prototype.setup_chatview = function() {
},0);
}
}.observes("firstTabSelected")
});
});*/
}
@ -487,10 +520,11 @@ FFZ.prototype._modify_cview = function(view) {
ffzInit: function() {
f._chatv = this;
this.ffz_unread = {};
this.$('.textarea-contain').append(f.build_ui_link(this));
this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
this.ffz_unread = {};
this.$('.chat-messages').find('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
if ( ! f.has_bttv ) {
if ( f.settings.group_tabs )
@ -556,13 +590,19 @@ FFZ.prototype._modify_cview = function(view) {
ffzChangeRoom: Ember.observer('controller.currentRoom', function() {
f.update_ui_link();
if ( ! this.ffz_unread )
f.log("!! Chat View Not Initialized !!", this);
// Temporary fix
this.ffz_unread = this.ffz_unread || {};
// Close mod cards when changing to a new room.
if ( f._mod_card )
f._mod_card.send('close');
var room = this.get('controller.currentRoom'),
room_id = room && room.get('id'),
was_unread = room_id && this.ffz_unread[room_id],
was_unread = room_id && this.ffz_unread && this.ffz_unread[room_id],
update_height = false;
if ( room ) {
@ -1209,7 +1249,7 @@ FFZ.prototype.connect_extra_chat = function() {
FFZ.prototype.disconnect_extra_chat = function() {
var Chat = App.__container__.lookup('controller:chat'),
var Chat = window.App && App.__container__.lookup('controller:chat'),
current_channel_id = Chat && Chat.get('currentChannelRoom.id'),
current_id = Chat && Chat.get('currentRoom.id'),
user = this.get_user();

View file

@ -13,7 +13,7 @@ FFZ.settings_info.conv_focus_on_click = {
no_mobile: true,
visible: false,
category: "Conversations",
category: "Whispers",
name: "Focus Input on Click",
help: "Focus on a conversation's input box when you click it."
};
@ -23,7 +23,7 @@ FFZ.settings_info.top_conversations = {
value: false,
no_mobile: true,
category: "Conversations",
category: "Whispers",
name: "Position on Top",
help: "Display the new conversation-style whisper UI at the top of the window instead of the bottom.",
on_update: function(val) {
@ -32,12 +32,27 @@ FFZ.settings_info.top_conversations = {
};
FFZ.settings_info.minimize_conversations = {
type: "boolean",
value: false,
no_mobile: true,
category: "Whispers",
name: "Minimize Whisper UI",
help: "Slide the Whisper UI mostly out of view when it's not being used and you have no unread messages.",
on_update: function(val) {
document.body.classList.toggle('ffz-minimize-conversations', val);
}
};
// ---------------
// Initialization
// ---------------
FFZ.prototype.setup_conversations = function() {
document.body.classList.toggle('ffz-top-conversations', this.settings.top_conversations);
document.body.classList.toggle('ffz-minimize-conversations', this.settings.minimize_conversations);
this.log("Hooking the Ember Conversation Window component.");
var ConvWindow = App.__container__.resolve('component:conversation-window');
@ -52,6 +67,7 @@ FFZ.prototype.setup_conversations = function() {
// TODO: Make this better later.
jQuery('.conversations-list').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery('.conversations-list').find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
}
@ -62,8 +78,7 @@ FFZ.prototype._modify_conversation_window = function(component) {
component.reopen({
headerBadges: Ember.computed("thread.participants", "currentUsername", function() {
var e = this.get("thread.participants").rejectBy("username", this.get("currentUsername")).objectAt(0),
var e = this.get("otherUser"),
badges = f.get_other_badges(e.get('username'), null, e.get('userType'), false, e.get('hasTurbo')),
out = [];
@ -97,6 +112,7 @@ FFZ.prototype._modify_conversation_window = function(component) {
jQuery('.badge', el).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
jQuery(el).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery(el).find('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
}
});
}
@ -120,7 +136,7 @@ FFZ.prototype._modify_conversation_line = function(component) {
click: function(e) {
if ( e.target && e.target.classList.contains('deleted-link') )
return f._deleted_link_click.bind(e.target)(e);
return f._deleted_link_click.call(e.target, e);
if ( f._click_emote(e.target, e) )
return;
@ -133,7 +149,9 @@ FFZ.prototype._modify_conversation_line = function(component) {
raw_color = this.get('message.from.color'),
colors = raw_color && f._handle_color(raw_color),
is_dark = (Layout && Layout.get('isTheatreMode')) || f.settings.dark_twitch;
is_dark = (Layout && Layout.get('isTheatreMode')) || f.settings.dark_twitch,
myself = f.get_user(),
from_me = myself && myself.login === user;
e.push('<div class="indicator"></div>');
@ -155,7 +173,7 @@ FFZ.prototype._modify_conversation_line = function(component) {
}
e.push('<span class="message' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">');
e.push(f.render_tokens(this.get('tokenizedMessage'), true));
e.push(f.render_tokens(this.get('tokenizedMessage'), true, f.settings.filter_whispered_links && !from_me));
e.push('</span>');
}
});

View file

@ -2,7 +2,7 @@ var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants'),
NO_LOGO = "http://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_150x150.png";
NO_LOGO = "//static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_150x150.png";
// --------------------
@ -38,6 +38,23 @@ FFZ.settings_info.sidebar_followed_games = {
}
}
FFZ.settings_info.sidebar_hide_recommended_channels = {
type: "boolean",
value: true,
category: "Appearance",
no_mobile: true,
name: "Sidebar Recommended Channels",
help: "Display the Recommended Channels section on the sidebar.",
on_update: function(val) {
document.body.classList.toggle('ffz-hide-recommended-channels', !val);
}
};
FFZ.settings_info.directory_creative_all_tags = {
type: "boolean",
value: false,
@ -145,9 +162,12 @@ FFZ.settings_info.directory_host_menus = {
// Initialization
// --------------------
FFZ._image_cache = {};
FFZ.prototype.setup_directory = function() {
document.body.classList.toggle('ffz-creative-tags', this.settings.directory_creative_all_tags);
document.body.classList.toggle('ffz-creative-showcase', this.settings.directory_creative_showcase);
document.body.classList.toggle('ffz-hide-recommended-channels', !this.settings.sidebar_hide_recommended_channels);
var GamesFollowing = App.__container__.lookup('controller:games-following'),
f = this;
@ -235,6 +255,8 @@ FFZ.prototype._modify_following = function() {
offset: this.get('content.length') + this.get('ffz_skipped')
};
// Don't use FFZ's Client ID because loading hosts is a normal part
// of the dashboard. We're just manipulating the logic a bit.
return Twitch.api.get("/api/users/:login/followed/hosting", t);
},
@ -304,8 +326,10 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
var el = this.get('element'),
meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'),
cap = thumb && thumb.querySelector('.cap');
cap = thumb && thumb.querySelector('.cap'),
channel_id = this.get('context.model.channel.name');
el.setAttribute('data-channel', channel_id);
// CSGO doesn't provide the actual uptime information...
if ( !is_csgo && f.settings.stream_uptime && f.settings.stream_uptime < 3 && cap ) {
@ -319,13 +343,15 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
this.ffzUpdateUptime();
}
this._ffz_image_timer = setInterval(this.ffzRotateImage.bind(this), 30000);
this.ffzRotateImage();
if ( f.settings.directory_logos ) {
el.classList.add('ffz-directory-logo');
var link = document.createElement('a'),
logo = document.createElement('img'),
t = this,
target = this.get('context.model.channel.name');
t = this;
logo.className = 'profile-photo';
logo.classList.toggle('is-csgo', is_csgo);
@ -333,14 +359,17 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
logo.src = this.get('context.model.channel.logo') || NO_LOGO;
logo.alt = this.get('context.model.channel.display_name');
link.href = '/' + target;
link.href = '/' + channel_id;
link.addEventListener('click', function(e) {
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
var Channel = App.__container__.resolve('model:channel');
if ( ! Channel )
return;
e.preventDefault();
t.get('controller').transitionTo('channel.index', Channel.find({id: target}).load());
t.get('controller').transitionTo('channel.index', Channel.find({id: channel_id}).load());
return false;
});
@ -358,9 +387,23 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
if ( this._ffz_uptime_timer )
clearInterval(this._ffz_uptime_timer);
if ( this._ffz_image_timer )
clearInterval(this._ffz_image_timer);
this._super();
},
ffzRotateImage: function() {
var url = this.get('context.model.preview.medium'),
now = Math.round((new Date).getTime() / 150000);
if ( FFZ._image_cache[url] && FFZ._image_cache[url] !== now )
url += '?_=' + now;
else
FFZ._image_cache[url] = now;
this.$('.thumb .cap img').attr('src', url);
},
ffzUpdateUptime: function() {
var raw_created = this.get('context.model.created_at'),
@ -411,7 +454,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
return;
if ( e ) {
if ( e.button !== 0 )
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
e.preventDefault();
@ -424,7 +467,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
},
ffzShowHostMenu: function(e) {
if ( e.button !== 0 )
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
e.preventDefault();
@ -485,10 +528,25 @@ FFZ.prototype._modify_directory_host = function(dir) {
f.show_popup(menu, [x, y], document.querySelector('#main_col > .tse-scroll-content > .tse-content'));
},
ffzRotateImage: function() {
var url = this.get('context.model.target.preview'),
now = Math.round((new Date).getTime() / 150000);
if ( FFZ._image_cache[url] && FFZ._image_cache[url] !== now )
url += '?_=' + now;
else
FFZ._image_cache[url] = now;
this.$('.thumb .cap img').attr('src', url);
},
ffzCleanup: function() {
var target = this.get('context.model.target.channel');
if ( f._popup && f._popup.classList.contains('ffz-channel-selector') && f._popup.getAttribute('data-channel') === target )
f.close_popup();
if ( this._ffz_image_timer )
clearInterval(this._ffz_image_timer);
},
ffzInit: function() {
@ -503,6 +561,10 @@ FFZ.prototype._modify_directory_host = function(dir) {
boxart = thumb && thumb.querySelector('.boxart');
el.setAttribute('data-channel', target.name);
this._ffz_image_timer = setInterval(this.ffzRotateImage.bind(this), 30000);
this.ffzRotateImage();
// Fix the game not showing
if ( ! boxart && thumb && this.get('context.model.game') ) {
@ -516,6 +578,9 @@ FFZ.prototype._modify_directory_host = function(dir) {
boxart.setAttribute('original-title', game);
boxart.addEventListener('click', function(e) {
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
e.preventDefault();
jQuery('.tipsy').remove();

View file

@ -11,7 +11,7 @@ FFZ.settings_info.enhance_profile_following = {
type: "boolean",
value: true,
category: "Appearance",
category: "Directory",
name: "Enhanced Following Control",
help: "Display additional controls on your own profile's Following tab to make management easier."
}
@ -33,9 +33,7 @@ FFZ.prototype.setup_profile_following = function() {
// First, we need to hook the model. This is what we'll use to grab the following notification state,
// rather than making potentially hundreds of API requests.
var Following = App.__container__.resolve('model:kraken-channel-following');
if ( ! Following )
return;
if ( Following )
this._hook_following(Following);
// Also try hooking that other model.
@ -71,7 +69,7 @@ FFZ.prototype.setup_profile_following = function() {
ffzInit: function() {
// Only process our own profile following page.
var user = f.get_user();
if ( ! f.settings.enhance_profile_following || ! user || user.login !== this.get('context.id') )
if ( ! f.settings.enhance_profile_following || ! user || user.login !== this.get('context.model.id') )
return;
var el = this.get('element'),
@ -99,6 +97,9 @@ FFZ.prototype.setup_profile_following = function() {
following.load();
}
// Make sure the Following is modified.
f._hook_following(following);
// We use this weird function to prevent trying to load twice mucking things up.
setTimeout(refresher);
}
@ -118,14 +119,15 @@ FFZ.prototype.setup_profile_following = function() {
continue;
// Is it an ember-view? Check its kids.
if ( added.classList.contains('ember-view') ) {
if ( added.classList.contains('user') )
t.ffzProcessUser(added);
else if ( added.classList.contains('ember-view') ) {
var users = added.querySelectorAll('.user.item');
if ( users )
for(var y=0; y < users.length; y++)
t.ffzProcessUser(users[y]);
} else if ( added.classList.contains('user') )
t.ffzProcessUser(added);
}
}
}
});
@ -215,8 +217,8 @@ FFZ.prototype.setup_profile_following = function() {
follow.textContent = 'Updating';
(was_following ?
Twitch.api.del("users/:login/follows/channels/" + user_id) :
Twitch.api.put("users/:login/follows/channels/" + user_id, {notifications: false}))
utils.api.del("users/:login/follows/channels/" + user_id) :
utils.api.put("users/:login/follows/channels/" + user_id, {notifications: false}))
.done(function() {
data = f._following_cache[user_id] = was_following ? null : [new Date(Date.now() - (f._ws_server_offset||0)), false];
})
@ -235,7 +237,7 @@ FFZ.prototype.setup_profile_following = function() {
notif.disabled = true;
notif.textContent = 'Updating';
Twitch.api.put("users/:login/follows/channels/" + user_id, {notifications: !was_following})
utils.api.put("users/:login/follows/channels/" + user_id, {notifications: !was_following})
.done(function() {
data[1] = ! was_following;
})
@ -254,25 +256,89 @@ FFZ.prototype.setup_profile_following = function() {
}
});
/*// Try adding a nice Manage button to the Following page of the directory
var DirectoryFollowing = App.__container__.resolve('view:directory');
if ( DirectoryFollowing ) {
DirectoryFollowing.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("view:following ffzInit: " + err);
}
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("view:following ffzTeardown: " + err);
}
this._super();
},
ffzInit: function() {
f.log("view:directory ffzInit called!", this);
var el = this.get('element'),
nav_bar = el && el.querySelector('.directory_header ul.nav'),
user = f.get_user();
if ( ! nav_bar || ! user || ! user.login || ! f.settings.enhance_profile_following )
return;
var li = document.createElement('li'),
btn = document.createElement('a'),
t = this;
li.className = 'ffz-manage-following';
btn.innerHTML = 'Manage Following';
btn.href = '/' + user.login + '/profile/following';
btn.addEventListener('click', function(e) {
var Channel = App.__container__.resolve('model:channel');
if ( ! Channel )
return;
e.preventDefault();
t.get('controller').transitionTo('profile.following', Channel.find({id: user.login}).load());
return false;
});
li.appendChild(btn);
nav_bar.appendChild(li);
},
ffzTeardown: function() {
this.$('.ffz-manage-following').remove();
}
});
try {
DirectoryFollowing.create().destroy();
} catch(err) { }
} else
f.log("Could not find Directory Following View.");*/
// Now, rebuild any views.
try {
ProfileView.create().destroy();
} catch(err) { }
var views = window.App && App.__container__.lookup('-view-registry:main') || Ember.View.views;
var views = window.App && App.__container__.lookup('-view-registry:main');
if ( views )
for(var key in views) {
var view = views[key];
if ( ! view || !(view instanceof ProfileView) )
continue;
this.log("Manually updating existing Following View.", view);
/*if ( DirectoryFollowing && view instanceof DirectoryFollowing ) {
try {
var following = view.get('context.following');
this._hook_following(following);
view.ffzInit();
} catch(err) {
this.error("setup: view:channel/following: model hook: " + err);
this.error("setup: view:following: ffzInit: " + err);
}
} else*/ if ( view instanceof ProfileView ) {
this.log("Manually updating existing Following View.", view);
try {
view.ffzInit();
} catch(err) {
@ -280,11 +346,16 @@ FFZ.prototype.setup_profile_following = function() {
}
}
}
}
FFZ.prototype._hook_following = function(Following) {
var f = this;
if ( Following.ffz_hooked )
return;
Following.reopen({
ffz_hooked: true,
apiLoad: function(e) {
var user = f.get_user(),
channel_id = this.get('id'),

View file

@ -1,4 +1,5 @@
var FFZ = window.FrankerFaceZ;
var FFZ = window.FrankerFaceZ,
utils = require('../utils');
// --------------------
@ -78,11 +79,11 @@ FFZ.settings_info.flip_dashboard = {
type: "boolean",
value: false,
category: "Appearance",
category: "Dashboard",
no_mobile: true,
no_bttv: true,
name: "Swap Dashboard Positions",
name: "Swap Column Positions",
help: "Swap the positions of the left and right columns of the dashboard.",
on_update: function(val) {
@ -106,17 +107,19 @@ FFZ.settings_info.right_column_width = {
help: "Set the width of the right sidebar for chat.",
method: function() {
var old_val = this.settings.right_column_width || 340,
new_val = prompt("Right Sidebar Width\n\nPlease enter a new width for the right sidebar, in pixels. Minimum: 250, Default: 340", old_val);
var f = this,
old_val = this.settings.right_column_width || 340;
utils.prompt("Right Sidebar Width", "Please enter a new width for the right sidebar, in pixels.</p><p><b>Minimum:</b> 250<br><b>Default:</b> 340", old_val, function(new_val) {
if ( new_val === null || new_val === undefined )
return;
var width = parseInt(new_val);
if ( ! width || width === NaN )
if ( ! width || Number.isNaN(width) || ! Number.isFinite(width) )
width = 340;
this.settings.set('right_column_width', Math.max(250, width));
f.settings.set('right_column_width', Math.max(250, width));
});
},
on_update: function(val) {
@ -220,7 +223,7 @@ FFZ.prototype.setup_layout = function() {
height = size[1],
host_height = size[2];
return "<style>.dynamic-player, .dynamic-player object, .dynamic-player video{width:" + width + "px !important;height:" + height + "px !important} .dynamic-target-player,.dynamic-target-player object, .dynamic-target-player video{width:" + width + "px !important;height:" + host_height + "px !important}</style><style>.dynamic-player .player object{width:100% !important; height:100% !important}</style>";
return "<style>.dynamic-player, .dynamic-player object, .dynamic-player video{width:" + width + "px !important;height:" + height + "px !important} .dynamic-target-player,.dynamic-target-player object, .dynamic-target-player video{width:" + width + "px !important;height:" + host_height + "px !important}</style><style>.dynamic-player .player object, .dynamic-player .player video{width:100% !important; height:100% !important}</style>";
}.property("playerSize"),
ffzPortraitWarning: function() {

View file

@ -1,15 +1,27 @@
var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
constants = require("../constants"),
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 + "*");
constants = require("../constants");
// ---------------------
// Settings
// ---------------------
FFZ.settings_info.alias_italics = {
type: "boolean",
value: true,
category: "Chat Appearance",
no_bttv: true,
name: "Display Aliases in Italics",
help: "Format the names of users that have aliases with italics to make it obvious at a glance that they have been renamed.",
on_update: function(val) {
document.body.classList.toggle('ffz-alias-italics', val);
}
};
FFZ.settings_info.room_status = {
type: "boolean",
value: true,
@ -32,7 +44,7 @@ FFZ.settings_info.replace_bad_emotes = {
value: true,
category: "Chat Appearance",
no_bttv: true,
warn_bttv: "Only affects Whispers when BetterTTV is enabled.",
name: "Fix Low Quality Twitch Global Emoticons",
help: "Replace emoticons such as DansGame and RedCoat with cleaned up versions that don't have pixels around the edges or white backgrounds for nicer display on dark chat."
@ -44,7 +56,8 @@ FFZ.settings_info.parse_emoji = {
options: {
0: "No Images / Font Only",
1: "Twitter Emoji Images",
2: "Google Noto Images"
2: "Google Noto Images",
3: "EmojiOne Images"
},
value: 1,
@ -66,23 +79,6 @@ FFZ.settings_info.parse_emoji = {
};
FFZ.settings_info.room_status = {
type: "boolean",
value: true,
category: "Chat Appearance",
no_bttv: true,
name: "Room Status Indicators",
help: "Display the current room state (slow mode, sub mode, and r9k mode) next to the Chat button.",
on_update: function() {
if ( this._roomv )
this._roomv.ffzUpdateStatus();
}
};
FFZ.settings_info.scrollback_length = {
type: "button",
value: 150,
@ -94,27 +90,32 @@ FFZ.settings_info.scrollback_length = {
help: "Set the maximum number of lines to keep in chat.",
method: function() {
var new_val = prompt("Scrollback Length\n\nPlease enter a new maximum length for the chat scrollback. Default: 150\n\nNote: Making this too large will cause your browser to lag.", this.settings.scrollback_length);
var f = this;
utils.prompt(
"Scrollback Length",
"Please enter a new maximum length for the chat scrollback. Please note that setting this too high may cause your computer to begin lagging as chat messages accumulate.</p><p><b>Default:</b> 150",
this.settings.scrollback_length,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
new_val = parseInt(new_val);
if ( new_val === NaN )
return;
if ( Number.isNaN(new_val) || ! Number.isFinite(new_val) )
new_val = 150;
if ( new_val < 10 )
new_val = 10;
new_val = Math.max(10, new_val);
this.settings.set("scrollback_length", new_val);
f.settings.set("scrollback_length", new_val);
// Update our everything.
var Chat = App.__container__.lookup('controller:chat'),
current_id = Chat && Chat.get('currentRoom.id');
for(var room_id in this.rooms) {
var room = this.rooms[room_id];
room.room && room.room.set('messageBufferSize', new_val + ((this._roomv && !this._roomv.get('stuckToBottom') && current_id === room_id) ? 150 : 0));
for(var room_id in f.rooms) {
var room = f.rooms[room_id];
room.room && room.room.set('messageBufferSize', new_val + ((f._roomv && !f._roomv.get('stuckToBottom') && current_id === room_id) ? 150 : 0));
}
});
}
};
@ -127,19 +128,19 @@ FFZ.settings_info.hosted_sub_notices = {
no_bttv: true,
name: "Show Hosted Channel Subscriber Notices",
help: "Display notices in chat when someone subscribes to the hosted channel."
help: "Display (or more specifically <i>hides</i> when disabled) notices in chat when someone subscribes to the hosted channel."
};
FFZ.settings_info.filter_bad_shorteners = {
FFZ.settings_info.filter_whispered_links = {
type: "boolean",
value: true,
category: "Chat Filtering",
no_bttv: true,
warn_bttv: "Only affects Whispers when BetterTTV is enabled.",
name: "Auto-Hide Potentially Dangerous Shortened Links",
help: "Replace potentially dangerous shortened links. Links are still accessible, but require an extra click to access."
name: "Auto-Hide Potentially Dangerous Whispered Links",
help: "Removes whispered links and displays a placeholder, with a warning that the link has not been approved by moderation or staff. Links remain accessible with an additional click."
};
@ -148,19 +149,25 @@ FFZ.settings_info.banned_words = {
value: [],
category: "Chat Filtering",
no_bttv: true,
warn_bttv: "Only affects Whispers when BetterTTV is enabled.",
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);
var f = this,
old_val = this.settings.banned_words.join(", ");
utils.prompt(
"Banned Words",
"Please enter a comma-separated list of words that you would like to have removed from chat messages.",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
new_val = new_val.trim().split(SPLITTER);
new_val = new_val.trim().split(constants.SPLITTER);
var vals = [];
for(var i=0; i < new_val.length; i++)
@ -169,7 +176,8 @@ FFZ.settings_info.banned_words = {
if ( vals.length == 1 && vals[0] == "disable" )
vals = [];
this.settings.set("banned_words", vals);
f.settings.set("banned_words", vals);
});
}
};
@ -179,20 +187,26 @@ FFZ.settings_info.keywords = {
value: [],
category: "Chat Filtering",
no_bttv: true,
warn_bttv: "Only affects Whispers when BetterTTV is enabled.",
name: "Highlight Keywords",
help: "Set additional keywords that will be highlighted in chat.",
method: function() {
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);
var f = this,
old_val = this.settings.keywords.join(", ");
utils.prompt(
"Highlight Keywords",
"Please enter a comma-separated list of words that you would like to be highlighted in chat.",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
// Split them up.
new_val = new_val.trim().split(SPLITTER);
new_val = new_val.trim().split(constants.SPLITTER);
var vals = [];
for(var i=0; i < new_val.length; i++)
@ -201,7 +215,8 @@ FFZ.settings_info.keywords = {
if ( vals.length == 1 && vals[0] == "disable" )
vals = [];
this.settings.set("keywords", vals);
f.settings.set("keywords", vals);
});
}
};
@ -211,7 +226,7 @@ FFZ.settings_info.clickable_emoticons = {
value: false,
category: "Chat Tooltips",
no_bttv: true,
warn_bttv: "Only affects Whispers when BetterTTV is enabled.",
no_mobile: true,
name: "Emoticon Information Pages",
@ -388,9 +403,14 @@ FFZ.settings_info.chat_font_family = {
help: "Change the font used for rendering chat messages.",
method: function() {
var old_val = this.settings.chat_font_family || "",
new_val = prompt("Chat Font Family\n\nPlease enter a font family to use rendering chat. Leave this blank to use the default.", old_val);
var f = this,
old_val = this.settings.chat_font_family || "";
utils.prompt(
"Chat Font Family",
"Please enter a font family to use rendering chat. Leave this blank to use the default.",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
@ -398,7 +418,8 @@ FFZ.settings_info.chat_font_family = {
if ( ! new_val )
new_val = null;
this.settings.set("chat_font_family", new_val);
f.settings.set("chat_font_family", new_val);
});
},
on_update: function(val) {
@ -434,17 +455,23 @@ FFZ.settings_info.chat_font_size = {
help: "Make the chat font bigger or smaller.",
method: function() {
var old_val = this.settings.chat_font_size,
new_val = prompt("Chat Font Size\n\nPlease enter a new size for the chat font. The default is 12.", old_val);
var f = this,
old_val = this.settings.chat_font_size;
utils.prompt(
"Chat Font Size",
"Please enter a new size for the chat font.</p><p><b>Default:</b> 12",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
var parsed = parseInt(new_val);
if ( ! parsed || parsed === NaN || parsed < 1 )
if ( ! parsed || Number.isNaN(parsed) || parsed < 1 )
parsed = 12;
this.settings.set("chat_font_size", parsed);
f.settings.set("chat_font_size", parsed);
});
},
on_update: function(val) {
@ -463,7 +490,7 @@ FFZ.settings_info.chat_font_size = {
}
utils.update_css(this._chat_style, "chat_font_size", css);
FFZ.settings_info.chat_ts_size.on_update.bind(this)(this.settings.chat_ts_size);
FFZ.settings_info.chat_ts_size.on_update.call(this, this.settings.chat_ts_size);
}
};
@ -479,21 +506,26 @@ FFZ.settings_info.chat_ts_size = {
help: "Make the chat timestamp font bigger or smaller.",
method: function() {
var old_val = this.settings.chat_ts_size;
var f = this,
old_val = this.settings.chat_ts_size;
if ( ! old_val )
old_val = this.settings.chat_font_size;
var new_val = prompt("Chat Timestamp Font Size\n\nPlease enter a new size for the chat timestamp font. The default is to match the regular chat font size.", old_val);
utils.prompt(
"Chat Timestamp Font Size",
"Please enter a new size for the chat timestamp font. The default is to match the regular chat font size.",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
var parsed = parseInt(new_val);
if ( ! parsed || parsed === NaN || parsed < 1 )
if ( parsed < 1 || Number.isNaN(parsed) || ! Number.isFinite(parsed) )
parsed = null;
this.settings.set("chat_ts_size", parsed);
f.settings.set("chat_ts_size", parsed);
});
},
on_update: function(val) {
@ -539,11 +571,13 @@ FFZ.prototype.setup_line = function() {
document.head.appendChild(s);
// Initial calculation.
FFZ.settings_info.chat_font_size.on_update.bind(this)(this.settings.chat_font_size);
FFZ.settings_info.chat_font_family.on_update.bind(this)(this.settings.chat_font_family);
FFZ.settings_info.chat_font_size.on_update.call(this, this.settings.chat_font_size);
FFZ.settings_info.chat_font_family.on_update.call(this, this.settings.chat_font_family);
// Chat Enhancements
document.body.classList.toggle('ffz-alias-italics', this.settings.alias_italics);
this.toggle_style('chat-setup', !this.has_bttv && (this.settings.chat_rows || this.settings.chat_separators));
this.toggle_style('chat-padding', !this.has_bttv && this.settings.chat_padding);
@ -560,17 +594,30 @@ FFZ.prototype.setup_line = function() {
this._last_row = {};
this.log("Hooking the Ember Whisper Line component.");
this.log("Hooking the Ember Chat Line component.");
var Line = App.__container__.resolve('component:chat-line');
if ( Line )
this._modify_chat_line(Line);
/*this.log("Hooking the Ember Whisper Line component.");
var Whisper = App.__container__.resolve('component:whisper-line');
if ( Whisper )
this._modify_line(Whisper);
this.log("Hooking the Ember Message Line component.");
var Line = App.__container__.resolve('component:message-line');
var Message = App.__container__.resolve('component:message-line');
if ( Line )
this._modify_line(Line);
if ( Message )
this._modify_line(Message);*/
this.log("Hooking the Ember VOD Chat Line component.");
var VOD = App.__container__.resolve('component:vod-chat-line');
if ( VOD )
this._modify_vod_line(VOD);
else
this.log("Couldn't find VOD Chat Line component.");
// Store the capitalization of our own name.
var user = this.get_user();
@ -585,13 +632,11 @@ FFZ.prototype.save_aliases = function() {
}
FFZ.prototype._modify_line = function(component) {
FFZ.prototype._modify_chat_line = function(component, is_vod) {
var f = this,
Layout = App.__container__.lookup('controller:layout'),
Settings = App.__container__.lookup('controller:settings');
component.reopen({
tokenizedMessage: function() {
try {
@ -600,13 +645,336 @@ FFZ.prototype._modify_line = function(component) {
f.error("chat-line tokenizedMessage: " + err);
return this._super();
}
}.property("msgObject.message", "isChannelLinksDisabled", "currentUserNick", "msgObject.from", "msgObject.tags.emotes"),
ffzUpdated: Ember.observer("msgObject.ffz_deleted", "msgObject.ffz_old_messages", function() {
this.rerender();
lineChanged: Ember.observer("msgObject.deleted", "isModeratorOrHigher", "msgObject.ffz_old_messages", function() {
this.$(".mod-icons").replaceWith(this.buildModIconsHTML());
if ( this.get("msgObject.deleted") ) {
this.$(".message").replaceWith(this.buildDeletedMessageHTML());
} else
this.$(".deleted,.message").replaceWith(this.buildMessageHTML());
}),
ffzUserLevel: function() {
if ( this.get('isStaff') )
return 5;
else if ( this.get('isAdmin') )
return 4;
else if ( this.get('isBroadcaster') )
return 3;
else if ( this.get('isGlobalMod') )
return 2;
else if ( this.get('isModerator') )
return 1;
return 0;
}.property('msgObject.labels.[]'),
buildModIconsHTML: function() {
var user = this.get('msgObject.from'),
room_id = this.get('msgObject.room'),
room = f.rooms && f.rooms[room_id],
deleted = this.get('msgObject.deleted'),
recipient = this.get('msgObject.to'),
is_whisper = recipient && recipient.length,
this_ul = this.get('ffzUserLevel'),
other_ul = room && room.room && room.room.get('ffzUserLevel') || 0,
output;
if ( is_whisper || this_ul >= other_ul || f.settings.mod_buttons.length === 0 )
return '';
output = '<span class="mod-icons float-left">';
for(var i=0, l = f.settings.mod_buttons.length; i < l; i++) {
var pair = f.settings.mod_buttons[i],
prefix = pair[0], btn = pair[1],
cmd, tip;
if ( btn === false ) {
if ( deleted )
output += '<a class="mod-icon float-left tooltip unban" title="Unban User" href="#">Unban</a>';
else
output += '<a class="mod-icon float-left tooltip ban" title="Ban User" href="#">Ban</a>';
} else if ( btn === 600 )
output += '<a class="mod-icon float-left tooltip timeout" title="Timeout User (10m)" href="#">Timeout</a>';
else {
if ( typeof btn === "string" ) {
cmd = btn.replace(/{user}/g, user).replace(/ *<LINE> */, "\n");
tip = "Custom Command" + (cmd.indexOf("\n") !== -1 ? 's' : '') + '\n' + cmd;
} else {
cmd = "/timeout " + user + " " + btn;
tip = "Timeout User (" + utils.duration_string(btn) + ")";
}
output += '<a class="mod-icon float-left tooltip' + (cmd.substr(0,9) === '/timeout' ? ' is-timeout' : '') + ' custom" data-cmd="' + utils.quote_attr(cmd) + '" title="' + utils.quote_attr(tip) + '" href="#">' + prefix + '</a>';
}
}
return output + '</span>';
},
buildSenderHTML: function() {
var user = this.get('msgObject.from'),
room_id = this.get('msgObject.room'),
room = f.rooms && f.rooms[room_id],
deleted = this.get('msgObject.deleted'),
r = this,
recipient = this.get('msgObject.to'),
is_whisper = recipient && recipient.length,
is_replay = this.get('ffz_is_replay'),
this_ul = this.get('ffzUserLevel'),
other_ul = room && room.room && room.room.get('ffzUserLevel') || 0,
raw_color = this.get('msgObject.color'),
colors = raw_color && f._handle_color(raw_color),
is_dark = (Layout && Layout.get('isTheatreMode')) || (is_replay ? f.settings.dark_twitch : (Settings && Settings.get('settings.darkMode'))),
output = '';
output = '<div class="indicator"></div><span class="timestamp float-left">' + this.get('timestamp') + '</span> ';
// Moderator Actions
output += this.buildModIconsHTML();
// Badges
output += '<span class="badges float-left">' + f.render_badges(f.get_line_badges(this.get('msgObject'), is_whisper)) + '</span>';
// Alias Support
var alias = f.aliases[user],
name = this.get('msgObject.tags.display-name') || (user && user.capitalize()) || "unknown user",
style = colors && 'color:' + (is_dark ? colors[1] : colors[0]) || '',
colored = style ? ' has-color' + (is_replay ? ' replay-color' : '') : '';
output += '<span class="from' + (alias ? ' ffz-alias tooltip' : '') + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '');
if ( alias )
output += '" title="' + utils.sanitize(name) + '">' + utils.sanitize(alias);
else
output += '">' + utils.sanitize(name);
// Whisper Legacy Sucks
if ( is_whisper ) {
var to_alias = f.aliases[recipient],
to_name = this.get('msgObject.tags.recipient-display-name') || (recipient && recipient.capitalize()) || "unknown user",
to_color = this.get('msgObject.toColor'),
to_colors = to_color && f._handle_color(to_color),
to_style = to_color ? 'color:' + (is_dark ? to_colors[1] : to_colors[0]) : '',
to_colored = to_style ? ' has-color' : '';
output += "</span><svg class='svg-whisper-arrow' height='10px' version='1.1' width='16px'><polyline points='6 2, 10 6, 6 10, 6 2' /></svg>";
output += '<span class="to' + (to_alias ? ' ffz-alias tooltip' : '') + to_colored + '" style="' + to_style + (to_colors ? '" data=color="' + to_color : '');
if ( to_alias )
output += '" title="' + utils.sanitize(to_name) + '">' + utils.sanitize(to_alias);
else
output += '">' + utils.sanitize(to_name);
}
return output + '</span><span class="colon">:</span> ';
},
buildDeletedMessageHTML: function() {
return '<span class="deleted"><a class="undelete" href=#">&lt;message deleted&gt;</a></span>';
},
buildMessageHTML: function() {
var output,
recipient = this.get('msgObject.to'),
is_whisper = recipient && recipient.length;
if ( this.get('msgObject.style') === 'action' ) {
var raw_color = this.get('msgObject.color'),
colors = raw_color && f._handle_color(raw_color),
is_replay = this.get('ffz_is_replay'),
is_dark = (Layout && Layout.get('isTheatreMode')) || (is_replay ? f.settings.dark_twitch : (Settings && Settings.get('settings.darkMode')));
if ( raw_color )
output = '<span class="message has-color' + (is_replay ? ' replay-color' : '') + '" style="color:' + (is_dark ? colors[1] : colors[0]) + '" data-color="' + raw_color + '">';
else
output = '<span class="message">';
} else
output = '<span class="message">';
output += f.render_tokens(this.get('tokenizedMessage'), true, is_whisper && f.settings.filter_whispered_links && this.get("ffzUserLevel") < 4);
var old_messages = this.get('msgObject.ffz_old_messages');
if ( old_messages && old_messages.length )
output += '<div class="button primary float-right ffz-old-messages">Show ' + utils.number_commas(old_messages.length) + ' Old</div>';
return output + '</span>';
},
tagName: "li",
classNameBindings: is_vod ? ["msgObject.ffz_has_mention:ffz-mentioned"] : [":message-line", ":chat-line", "msgObject.style", "msgObject.ffz_has_mention:ffz-mentioned", "ffzWasDeleted:ffz-deleted", "ffzHasOldMessages:clearfix", "ffzHasOldMessages:ffz-has-deleted"],
attributeBindings: ["msgObject.room:data-room", "msgObject.from:data-sender", "msgObject.deleted:data-deleted"],
render: function(e) {
e.push(this.buildSenderHTML());
if ( this.get("msgObject.deleted") )
e.push(this.buildDeletedMessageHTML())
else
e.push(this.buildMessageHTML());
},
ffzWasDeleted: function() {
return f.settings.prevent_clear && this.get("msgObject.ffz_deleted")
}.property("msgObject.ffz_deleted"),
ffzHasOldMessages: function() {
var old_messages = this.get("msgObject.ffz_old_messages");
return old_messages && old_messages.length;
}.property("msgObject.ffz_old_messages"),
click: function(e) {
if ( ! e.target )
return;
var cl = e.target.classList,
from = this.get("msgObject.from");
if ( cl.contains('ffz-old-messages') )
return f._show_deleted(this.get('msgObject.room'));
else if ( cl.contains('deleted-word') ) {
jQuery(e.target).trigger('mouseout');
e.target.outerHTML = e.target.getAttribute('data-text');
} else if ( cl.contains('deleted-link') )
return f._deleted_link_click.call(e.target, e);
else if ( cl.contains('mod-icon') ) {
jQuery(e.target).trigger('mouseout');
e.preventDefault();
if ( cl.contains('custom') ) {
var room_id = this.get('msgObject.room'),
room = room_id && f.rooms[room_id] && f.rooms[room_id].room,
cmd = e.target.getAttribute('data-cmd');
if ( room ) {
var lines = cmd.split("\n");
for(var i=0; i < lines.length; i++)
room.send(lines[i], true);
if ( cl.contains('is-timeout') )
room.clearMessages(from);
}
return;
} else if ( cl.contains('ban') )
this.sendAction("banUser", {user:from});
else if ( cl.contains('unban') )
this.sendAction("unbanUser", {user:from});
else if ( cl.contains('timeout') )
this.sendAction("timeoutUser", {user:from});
} else if ( cl.contains('badge') ) {
if ( cl.contains('turbo') )
window.open("/products/turbo?ref=chat_badge", "_blank");
else if ( cl.contains('subscriber') )
this.sendAction("clickSubscriber");
} else if ( f._click_emote(e.target, e) )
return;
else if ( e.target.classList.contains('from') ) {
var n = this.$();
this.sendAction("showModOverlay", {
left: n.offset().left,
top: n.offset().top + n.height(),
sender: from
});
} else if ( e.target.classList.contains('undelete') )
this.set("msgObject.deleted", false);
}
});
}
FFZ.prototype._modify_vod_line = function(component) {
var f = this;
// We need to override a few things.
this._modify_chat_line(component, true);
component.reopen({
ffz_is_replay: true,
/*lineChanged: Ember.observer("msgObject.deleted", "isModeratorOrHigher", function() {
this.$(".mod-icons").replaceWith(this.buildModIconsHTML());
if ( this.get("msgObject.deleted") )
this.$(".message").replaceWith(this.buildMessageHTML());
else
this.$(".deleted").replaceWith(this.buildMessageHTML());
}),*/
buildModIconsHTML: function() {
if ( ! this.get("isViewerModeratorOrHigher") || this.get("isModeratorOrHigher") )
return "";
return '<span class="mod-icons float-left">' +
(this.get('msgObject.deleted') ?
'<em class="mod-icon float-left unban"></em>' :
'<a class="mod-icon float-left tooltip delete" title="Delete Message" href="#">Delete</a>') + '</span>';
},
buildDeletedMesageHTML: function() {
return '<span clas="deleted">&lt;message deleted&gt;</span>';
},
render: function(e) {
if ( this.get('msgObject.isHorizontalLine') )
e.push(this.buildHorizontalLineHTML());
else {
e.push(this.buildSenderHTML());
if ( this.get("msgObject.deleted") )
e.push(this.buildDeletedMessageHTML())
else
e.push(this.buildMessageHTML());
}
},
click: function(e) {
if ( e.target.classList.contains('delete') ) {
e.preventDefault();
this.sendAction("timeoutUser", this.get("msgObject.id"));
}
},
didInsertElement: function() {
this._super();
if ( this.get("msgObject.ffz_has_mention") )
this.get("element").classList.add("ffz-mentioned");
}
});
}
/*FFZ.prototype._modify_line = function(component) {
var f = this,
Layout = App.__container__.lookup('controller:layout'),
Settings = App.__container__.lookup('controller:settings');
component.reopen({
click: function(e) {
if ( e.target && e.target.classList.contains('ffz-old-messages') )
return f._show_deleted(this.get('msgObject.room'));
@ -640,146 +1008,11 @@ FFZ.prototype._modify_line = function(component) {
return this._super(e);
},
ffzUserLevel: function() {
if ( this.get('isStaff') )
return 5;
else if ( this.get('isAdmin') )
return 4;
else if ( this.get('isBroadcaster') )
return 3;
else if ( this.get('isGlobalMod') )
return 2;
else if ( this.get('isModerator') )
return 1;
return 0;
}.property('msgObject.labels.[]'),
render: function(e) {
var deleted = this.get('msgObject.deleted'),
r = this,
user = this.get('msgObject.from'),
room_id = this.get('msgObject.room'),
room = f.rooms && f.rooms[room_id],
recipient = this.get('msgObject.to'),
is_whisper = recipient && recipient.length,
this_ul = this.get('ffzUserLevel'),
other_ul = room && room.room && room.room.get('ffzUserLevel') || 0,
raw_color = this.get('msgObject.color'),
colors = raw_color && f._handle_color(raw_color),
is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('settings.darkMode'));
e.push('<div class="indicator"></div>');
e.push('<span class="timestamp float-left">' + this.get("timestamp") + '</span> ');
// Moderation actions
if ( ! is_whisper && this_ul < other_ul ) {
e.push('<span class="mod-icons float-left">');
for(var i=0, l = f.settings.mod_buttons.length; i < l; i++) {
var pair = f.settings.mod_buttons[i],
prefix = pair[0], btn = pair[1],
cmd, tip;
if ( btn === false ) {
if ( deleted )
e.push('<a class="mod-icon float-left tooltip unban" title="Unban User" href="#">Unban</a>');
else
e.push('<a class="mod-icon float-left tooltip ban" title="Ban User" href="#">Ban</a>');
} else if ( btn === 600 )
e.push('<a class="mod-icon float-left tooltip timeout" title="Timeout User (10m)" href="#">Timeout</a>');
else {
if ( typeof btn === "string" ) {
cmd = btn.replace(/{user}/g, user).replace(/ *<LINE> */, "\n");
tip = 'Custom Command' + (cmd.indexOf('\n') !== -1 ? 's' : '') + '\n' + cmd;
} else {
cmd = "/timeout " + user + " " + btn;
tip = "Timeout User (" + utils.duration_string(btn) + ")";
}
e.push('<a class="mod-icon float-left tooltip' + (cmd.substr(0, 9) === '/timeout' ? ' is-timeout' : '') + ' custom" data-cmd="' + utils.quote_attr(cmd) + '" title="' + utils.quote_attr(tip) + '" href="#">' + prefix + '</a>');
}
}
e.push('</span>');
}
// Badges
e.push('<span class="badges float-left">');
e.push(f.render_badges(f.get_line_badges(this.get('msgObject'), is_whisper)));
e.push('</span>');
// Handle aliases
var alias = f.aliases[user],
name = this.get('msgObject.tags.display-name') || (user && user.capitalize()) || "unknown user",
style = colors && 'color:' + (is_dark ? colors[1] : colors[0]),
colored = style ? ' has-color' : '';
if ( alias )
e.push('<span class="from ffz-alias tooltip' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '" title="' + utils.sanitize(name) + '">' + utils.sanitize(alias) + '</span>');
else
e.push('<span class="from' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">' + utils.sanitize(name) + '</span>');
// If it's a whisper, we need to get that user's color, alias, and draw the whisper arrow thing.
if ( is_whisper ) {
var to_alias = f.aliases[recipient],
to_name = this.get('msgObject.tags.recipient-display-name') || (recipient && recipient.capitalize()) || "unknown user",
to_color = this.get('msgObject.toColor'),
to_colors = to_color && f._handle_color(to_color),
to_style = to_color && 'color:' + (is_dark ? to_colors[1] : to_colors[0]),
to_colored = to_style ? ' has-color' : '';
this._renderWhisperArrow(e);
if ( to_alias )
e.push('<span class="to ffz-alias tooltip' + to_colored + '" style="' + to_style + (to_color ? '" data-color="' + to_color : '') + '" title="' + utils.sanitize(to_name) + '">' + utils.sanitize(to_alias) + '</span>');
else
e.push('<span class="to' + to_colored + '" style="' + to_style + (to_colors ? '" data-color="' + to_color : '') + '">' + utils.sanitize(to_name) + '</span>');
}
// Finally, onto the message proper.
e.push('<span class="colon">:</span> ');
if ( this.get('msgObject.style') !== 'action' ) {
style = '';
colored = '';
}
if ( deleted )
e.push('<span class="deleted"><a class="undelete" href="#">&lt;message deleted&gt;</a></span>');
else {
e.push('<span class="message' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">');
e.push(f.render_tokens(this.get('tokenizedMessage'), true));
var old_messages = this.get('msgObject.ffz_old_messages');
if ( old_messages && old_messages.length )
e.push('<div class="button primary float-right ffz-old-messages">Show ' + utils.number_commas(old_messages.length) + ' Old</div>');
e.push('</span>');
}
e.push(this.buildSenderHTML());
e.push(this.buildMessageHTML())
},
classNameBindings: [
'msgObject.ffz_has_mention:ffz-mentioned',
'ffzWasDeleted:ffz-deleted',
'ffzHasOldMessages:clearfix',
'ffzHasOldMessages:ffz-has-deleted'
],
ffzWasDeleted: function() {
return f.settings.prevent_clear && this.get('msgObject.ffz_deleted');
}.property('msgObject.ffz_deleted'),
@ -789,6 +1022,12 @@ FFZ.prototype._modify_line = function(component) {
return old_messages && old_messages.length;
}.property('msgObject.ffz_old_messages'),
classNameBindings: [
'msgObject.ffz_has_mention:ffz-mentioned',
'ffzWasDeleted:ffz-deleted',
'ffzHasOldMessages:clearfix',
'ffzHasOldMessages:ffz-has-deleted'
],
didInsertElement: function() {
this._super();
@ -800,7 +1039,7 @@ FFZ.prototype._modify_line = function(component) {
el.setAttribute('data-deleted', this.get('msgObject.deleted') || false);
}
});
}
}*/
// ---------------------
@ -843,47 +1082,65 @@ FFZ.get_capitalization = function(name, callback) {
// ---------------------
FFZ.prototype._remove_banned = function(tokens) {
var banned_words = this.settings.banned_words,
banned_links = this.settings.filter_bad_shorteners ? ['apo.af', 'goo.gl', 'j.mp', 'bit.ly'] : null,
has_banned_words = banned_words && banned_words.length;
if ( !has_banned_words && (! banned_links || ! banned_links.length) )
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),
link_regex = this.settings.filter_bad_shorteners && FFZ._words_to_regex(banned_links),
new_tokens = [];
for(var i=0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if ( ! _.isString(token ) ) {
if ( token.emoticonSrc && has_banned_words && regex.test(token.altText) )
new_tokens.push(token.altText.replace(regex, "$1***"));
else if ( token.isLink && has_banned_words && regex.test(token.href) )
new_tokens.push({
isLink: true,
href: token.href,
isDeleted: true,
isLong: false,
censoredHref: token.href.replace(regex, "$1***")
});
else if ( token.isLink && this.settings.filter_bad_shorteners && link_regex.test(token.href) )
new_tokens.push({
isLink: true,
href: token.href,
isDeleted: true,
isLong: false,
isShortened: true
});
else
new_tokens.push(token);
if ( typeof token === "string" )
token = {type: "text", text: token};
if ( token.type === "text" && regex.test(token.text) ) {
token = token.text.replace(regex, function(all, prefix, match) {
if ( prefix.length )
new_tokens.push({type: "text", text: prefix});
new_tokens.push({
type: "deleted",
length: match.length,
text: match
});
return "";
});
if ( token )
new_tokens.push({type: "text", text: token});
} else if ( token.type === "emoticon" && regex.test(token.altText) ) {
token = token.altText.replace(regex, function(all, prefix, match) {
if ( prefix.length )
new_tokens.push({type: "text", text: prefix});
new_tokens.push({
type: "deleted",
length: match.length,
text: match
});
return "";
});
if ( token )
new_tokens.push({type: "text", text: token});
} else if ( token.type === "link" && regex.test(token.text) )
new_tokens.push({
type: "link",
isDeleted: true,
isMailTo: token.isMailTo,
isLong: false,
length: token.text.length,
censoredLink: token.text.replace(regex, "$1***"),
link: token.link,
text: token.text
});
} else if ( has_banned_words )
new_tokens.push(token.replace(regex, "$1***"));
else
new_tokens.push(token);
}

View file

@ -158,7 +158,9 @@ FFZ.settings_info.mod_buttons = {
help: "Change out the different in-line moderation icons to use any command quickly.",
method: function() {
var old_val = "";
var f = this,
old_val = "";
for(var i=0; i < this.settings.mod_buttons.length; i++) {
var pair = this.settings.mod_buttons[i],
prefix = pair[0], cmd = pair[1], had_prefix = pair[2];
@ -182,8 +184,18 @@ FFZ.settings_info.mod_buttons = {
old_val += ' ' + prefix + cmd;
}
var new_val = prompt("Custom In-Line Moderation Icons\n\nPlease enter a list of commands to be made available as mod icons within chat lines. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"\n\nTo send multiple commands, separate them with \"<LINE>\".\n\nNumeric values will become timeout buttons for that number of seconds. The text \"<BAN>\" is a special value that will act like the normal Ban button in chat.\n\nTo assign a specific letter for use as the icon, specify it at the start of the command followed by an equals sign.\n\nExample: A=\"!reg add\"\n\nDefault: <BAN> 600", old_val);
utils.prompt(
"Custom In-Line Moderation Icons",
"Please enter a list of commands to be made available as mod icons within chat lines. Commands are separated by spaces. " +
"To include spaces in a command, surround the command with double quotes (\"). Use <code>{user}</code> to insert the user's name " +
"into the command, otherwise it will be appended to the end.</p><p><b>Example:</b> <code>!permit \"!reg add {user}\"</code></p><p>To " +
"send multiple commands, separate them with <code>&lt;LINE&gt;</code>.</p><p>Numeric values will become timeout buttons for " +
"that number of seconds. The text <code>&lt;BAN&gt;</code> is a special value that will act like the normal Ban button in chat.</p><p>" +
"To assign a specific letter for use as the icon, specify it at the start of the command followed by an equals sign.</p><p>" +
"<b>Example:</b> <code>A=\"!reg add\"</code></p><p><b>Default:</b> <code>&lt;BAN&gt; 600</code>",
old_val.substr(1),
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
@ -239,7 +251,7 @@ FFZ.settings_info.mod_buttons = {
val = false;
var num = parseInt(val);
if ( num > 0 && num !== NaN )
if ( num > 0 && ! Number.isNaN(num) )
val = num;
if ( ! prefix ) {
@ -266,7 +278,8 @@ FFZ.settings_info.mod_buttons = {
final.push([prefix, val, had_prefix]);
}
this.settings.set('mod_buttons', final);
f.settings.set('mod_buttons', final);
}, 600);
}
};
@ -282,7 +295,8 @@ FFZ.settings_info.mod_card_buttons = {
help: "Add additional buttons to moderation cards for running chat commands on those users.",
method: function() {
var old_val = "";
var f = this,
old_val = "";
for(var i=0; i < this.settings.mod_card_buttons.length; i++) {
var cmd = this.settings.mod_card_buttons[i];
if ( cmd.indexOf(' ') !== -1 )
@ -291,8 +305,13 @@ FFZ.settings_info.mod_card_buttons = {
old_val += ' ' + cmd;
}
var new_val = prompt("Moderation Card Additional Buttons\n\nPlease enter a list of additional commands to display buttons for on moderation cards. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"", old_val);
utils.prompt(
"Moderation Card Additional Buttons",
"Please enter a list of additional commands to display buttons for on moderation cards. Commands are separated by spaces. " +
"To include spaces in a command, surround the command with double quotes (\"). Use <code>{user}</code> to insert the " +
"user's name into the command, otherwise it will be appended to the end.</p><p><b>Example:</b> !permit \"!reg add {user}\"",
old_val.substr(1),
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
@ -329,7 +348,8 @@ FFZ.settings_info.mod_card_buttons = {
}
}
this.settings.set("mod_card_buttons", vals);
f.settings.set("mod_card_buttons", vals);
}, 600);
}
};
@ -345,9 +365,15 @@ FFZ.settings_info.mod_card_durations = {
help: "Add additional timeout buttons to moderation cards with specific durations.",
method: function() {
var old_val = this.settings.mod_card_durations.join(", "),
new_val = prompt("Moderation Card Timeout Buttons\n\nPlease enter a comma-separated list of durations that you would like to have timeout buttons for. Durations must be expressed in seconds.\n\nEnter \"reset\" without quotes to return to the default value.", old_val);
var f = this,
old_val = this.settings.mod_card_durations.join(", ");
utils.prompt(
"Moderation Card Timeout Buttons",
"Please enter a comma-separated list of durations that you would like to have timeout buttons for. " +
"Durations must be expressed in seconds.</p><p><b>Default:</b> 300, 600, 3600, 43200, 86400, 604800",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
@ -363,11 +389,12 @@ FFZ.settings_info.mod_card_durations = {
if ( val === 0 )
val = 1;
if ( val !== NaN && val > 0 )
if ( ! Number.isNaN(val) && val > 0 )
vals.push(val);
}
this.settings.set("mod_card_durations", vals);
f.settings.set("mod_card_durations", vals);
}, 600);
}
};
@ -420,7 +447,7 @@ FFZ.prototype.setup_mod_card = function() {
} else if ( followers === undefined ) {
var t = this;
this.set('cardInfo.user.ffz_followers', false);
Twitch.api.get("channels/" + this.get('cardInfo.user.id') + '/follows', {limit:1}).done(function(data) {
utils.api.get("channels/" + this.get('cardInfo.user.id') + '/follows', {limit:1}).done(function(data) {
t.set('cardInfo.user.ffz_followers', data._total);
t.ffzRebuildInfo();
}).fail(function(data) {
@ -682,7 +709,7 @@ FFZ.prototype.setup_mod_card = function() {
real_msg.title = "Message User";
real_msg.addEventListener('click', function() {
window.open('http://www.twitch.tv/message/compose?to=' + controller.get('cardInfo.user.id'));
window.open('//www.twitch.tv/message/compose?to=' + controller.get('cardInfo.user.id'));
})
msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling);
@ -699,7 +726,11 @@ FFZ.prototype.setup_mod_card = function() {
var user = controller.get('cardInfo.user.id'),
alias = f.aliases[user];
var new_val = prompt("Alias for User: " + user + "\n\nPlease enter an alias for the user. Leave it blank to remove the alias.", alias);
utils.prompt(
"Alias for <b>" + utils.sanitize(controller.get('cardInfo.user.display_name') || user) + "</b>",
"Please enter an alias for the user. Leave it blank to remove the alias.",
alias,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
@ -713,14 +744,11 @@ FFZ.prototype.setup_mod_card = function() {
// Update UI
f._update_alias(user);
Ember.propertyDidChange(controller, 'userName');
var name = el.querySelector('h3.name'),
link = name && name.querySelector('a');
if ( link )
name = link;
Ember.propertyDidChange(controller, 'cardInfo.user.display_name');
var name = el.querySelector('h3.name');
if ( name )
name.classList.toggle('ffz-alias', new_val);
});
});
if ( msg_btn )
@ -1040,9 +1068,11 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
// Interactivity
jQuery('a.undelete', l_el).click(function(e) { this.parentElement.outerHTML = this.getAttribute('data-message'); });
jQuery('.deleted-word', l_el).click(function(e) { jQuery(this).trigger('mouseout'); this.outerHTML = this.getAttribute('data-text'); });
jQuery('a.deleted-link', l_el).click(f._deleted_link_click);
jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) });
jQuery('.html-tooltip', l_el).tipsy({html:true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
jQuery('.ffz-tooltip', l_el).tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
if ( modcard ) {
modcard.get('cardInfo.user.id') !== msg.from && jQuery('span.from', l_el).click(function(e) {
@ -1082,6 +1112,9 @@ FFZ.prototype._update_alias = function(user) {
var line = lines[i],
el_from = line.querySelector('.from');
if ( ! el_from )
continue;
el_from.classList.toggle('ffz-alias', alias);
el_from.textContent = display_name;
el_from.title = alias ? cap_name : '';
@ -1110,7 +1143,7 @@ FFZ.chat_commands.purge = function(room, args) {
}
FFZ.chat_commands.p = function(room, args) {
return FFZ.chat_commands.purge.bind(this)(room, args);
return FFZ.chat_commands.purge.call(this, room, args);
}
FFZ.chat_commands.p.enabled = function() { return this.settings.short_commands; }

View file

@ -107,7 +107,6 @@ FFZ.prototype._modify_player = function(player) {
f._cindex && f._cindex.ffzUpdatePlayerStats();
};
player.reopen({
didInsertElement: function() {
this._super();
@ -139,6 +138,10 @@ FFZ.prototype._modify_player = function(player) {
ffzInit: function() {
var id = this.get('channel.id');
f.players[id] = this;
var player = this.get('player');
if ( player )
this.ffzPostPlayer();
},
ffzTeardown: function() {
@ -153,22 +156,12 @@ FFZ.prototype._modify_player = function(player) {
},
ffzPostPlayer: function() {
var player = this.get('ffz_player') || this.get('player');
if ( ! player ) {
var tp2 = window.require("web-client/components/twitch-player2");
if ( ! tp2 || ! tp2.getPlayer )
var player = this.get('player');
if ( ! player )
return;
player = tp2.getPlayer();
if ( ! player || ! player.getVideo )
// We can't get a valid player. :-(
return;
}
this.set('ffz_player', player);
// Only set up the stats hooks if we need stats.
var has_video;
var has_video = false;
try {
has_video = player.getVideo();
@ -184,24 +177,25 @@ FFZ.prototype._modify_player = function(player) {
if ( this.get('ffzStatsInitialized') )
return;
var player = this.get('ffz_player');
var t = this,
player = this.get('player');
if ( ! player )
return;
this.set('ffzStatsInitialized', true);
// Make it so stats can no longer be disabled if we want them.
if ( player.setStatsEnabled ) {
player.ffzSetStatsEnabled = player.setStatsEnabled;
try {
player.ffz_stats = player.getStatsEnabled();
} catch(err) {
// Assume stats are off.
f.error("Player2 ffzInitStats: getStatsEnabled still doesn't work: " + err);
f.log("Player2 ffzInitStats: getStatsEnabled still doesn't work.");
player.ffz_stats = false;
}
var t = this;
player.setStatsEnabled = function(e, s) {
if ( s !== false )
player.ffz_stats = e;
@ -222,34 +216,13 @@ FFZ.prototype._modify_player = function(player) {
player.ffzSetStatsEnabled(true);
}
}, 5000);
}
if ( f.settings.player_stats && ! player.ffz_stats ) {
if ( f.settings.player_stats && ( ! player.setStatsEnabled || ! player.ffz_stats ) ) {
this._ffz_player_stats_initialized = true;
player.addEventListener('statschange', update_stats);
player.ffzSetStatsEnabled(true);
}
},
ffzSetQuality: function(q) {
var player = this.get('ffz_player');
if ( ! player )
return;
this.$(".js-quality-display-contain").attr("data-q", "loading");
player.setQuality(q);
var t = this.$(".js-player-alert");
t.find(".js-player-alert__message").text();
t.attr("data-active", !0);
},
ffzGetQualities: function() {
var player = this.get('ffz_player');
if ( ! player )
return [];
return player.getQualities();
},
}
});
}

View file

@ -11,7 +11,7 @@ var FFZ = window.FrankerFaceZ,
if ( ! room.moderator_badge )
return "";
return '.chat-line[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement) { background-image:url("' + room.moderator_badge + '") !important; }';
return '.chat-line[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement) { background-repeat: no-repeat; background-size: initial; background-position: center; background-image:url("' + room.moderator_badge + '") !important; }';
};
@ -37,22 +37,18 @@ FFZ.prototype.setup_room = function() {
// Responsive ban button.
var f = this,
RC = App.__container__.lookup('controller:room');
if ( RC ) {
var orig_ban = RC._actions.banUser,
orig_to = RC._actions.timeoutUser;
RC._actions.banUser = function(e) {
orig_ban.bind(this)(e);
orig_ban.call(this, e);
this.get("model").clearMessages(e.user);
}
RC._actions.timeoutUser = function(e) {
orig_to.bind(this)(e);
this.get("model").clearMessages(e.user);
}
RC._actions.purgeUser = function(e) {
this.get("model.tmiRoom").sendMessage("/timeout " + e.user + " 1");
orig_to.call(this, e);
this.get("model").clearMessages(e.user);
}
@ -79,7 +75,7 @@ FFZ.prototype.setup_room = function() {
renderBottom: e.bottom,
renderRight: e.right,
isIgnored: this.get("tmiSession").isIgnored(e.sender),
isChannelOwner: this.get("controllers.login.userData.login") === e.sender,
isChannelOwner: this.get("login.userData.login") === e.sender,
profileHref: Twitch.uri.profile(e.sender),
isModeratorOrHigher: this.get("model.isModeratorOrHigher")
});
@ -161,15 +157,6 @@ FFZ.prototype._modify_rview = function(view) {
this._super();
},
ffzAlternate: function() {
/*if ( ! this._ffz_chat_display ) {
var el = this.get('element');
this._ffz_chat_display = el && el.querySelector('ul.chat-lines');
}
this._ffz_chat_display && this._ffz_chat_display.classList.toggle('ffz-should-alternate');*/
},
ffzInit: function() {
f._roomv = this;
@ -232,6 +219,7 @@ FFZ.prototype._modify_rview = function(view) {
var r9k_badge = cont.querySelector('#ffz-stat-r9k'),
sub_badge = cont.querySelector('#ffz-stat-sub'),
emote_badge = cont.querySelector('#ffz-stat-emote'),
slow_badge = cont.querySelector('#ffz-stat-slow'),
banned_badge = cont.querySelector('#ffz-stat-banned'),
delay_badge = cont.querySelector('#ffz-stat-delay'),
@ -243,6 +231,8 @@ FFZ.prototype._modify_rview = function(view) {
r9k_badge.parentElement.removeChild(r9k_badge);
if ( sub_badge )
sub_badge.parentElement.removeChild(sub_badge);
if ( emote_badge )
emote_badge.parentElement.removeChild(emote_badge);
if ( slow_badge )
slow_badge.parentElement.removeChild(slow_badge);
if ( delay_badge )
@ -275,6 +265,16 @@ FFZ.prototype._modify_rview = function(view) {
jQuery(sub_badge).tipsy({gravity:"s", offset:15});
}
if ( ! emote_badge ) {
emote_badge = document.createElement('span');
emote_badge.className = 'ffz room-state stat float-right';
emote_badge.id = 'ffz-stat-emote';
emote_badge.innerHTML = 'E<span>MOTE</span>';
emote_badge.title = "This room is in Twitch emote-only mode. Emotes added by extensions are not permitted in this mode.";
cont.appendChild(emote_badge);
jQuery(emote_badge).tipsy({gravity: "s", offset: 15});
}
if ( ! slow_badge ) {
slow_badge = document.createElement('span');
slow_badge.className = 'ffz room-state stat float-right';
@ -315,6 +315,7 @@ FFZ.prototype._modify_rview = function(view) {
var vis_count = 0,
r9k_vis = room && room.get('r9k'),
sub_vis = room && room.get('subsOnly'),
emote_vis = room && room.get('emoteOnly') && room.get('emoteOnly') !== '0',
ban_vis = room && room.get('ffz_banned'),
slow_vis = room && room.get('slowMode'),
delay_vis = f.settings.chat_delay !== 0,
@ -322,6 +323,7 @@ FFZ.prototype._modify_rview = function(view) {
if ( r9k_vis ) vis_count += 1;
if ( sub_vis ) vis_count += 1;
if ( emote_vis ) vis_count += 1;
if ( ban_vis ) vis_count += 1;
if ( slow_vis ) vis_count += 1;
if ( delay_vis ) vis_count += 1;
@ -329,6 +331,7 @@ FFZ.prototype._modify_rview = function(view) {
r9k_badge.classList.toggle('truncated', vis_count > 3);
sub_badge.classList.toggle('truncated', vis_count > 3);
emote_badge.classList.toggle('truncated', vis_count > 3);
banned_badge.classList.toggle('truncated', vis_count > 3);
slow_badge.classList.toggle('truncated', vis_count > 3);
delay_badge.classList.toggle('truncated', vis_count > 3);
@ -336,6 +339,7 @@ FFZ.prototype._modify_rview = function(view) {
r9k_badge.classList.toggle('hidden', ! r9k_vis);
sub_badge.classList.toggle('hidden', ! sub_vis);
emote_badge.classList.toggle('hidden', ! emote_vis);
banned_badge.classList.toggle('hidden', ! ban_vis);
slow_badge.classList.toggle('hidden', ! slow_vis);
@ -370,7 +374,6 @@ FFZ.prototype._modify_rview = function(view) {
messages.addEventListener('mousemove', this._ffz_mouse_move);
messages.addEventListener('touchmove', this._ffz_mouse_move);
messages.addEventListener('mouseout', this._ffz_mouse_out);
document.addEventListener('mouseout', this._ffz_mouse_out);
},
ffzDisableFreeze: function() {
@ -389,6 +392,7 @@ FFZ.prototype._modify_rview = function(view) {
if ( this._ffz_mouse_move ) {
messages.removeEventListener('mousemove', this._ffz_mouse_move);
messages.removeEventListener('touchmove', this._ffz_mouse_move);
this._ffz_mouse_move = undefined;
}
@ -553,7 +557,7 @@ FFZ.prototype.run_command = function(text, room_id) {
var val = command.enabled;
if ( typeof val == "function" ) {
try {
val = command.enabled.bind(this)(room, args);
val = command.enabled.call(this, room, args);
} catch(err) {
this.error('command "' + cmd + '" enabled: ' + err);
val = false;
@ -567,7 +571,7 @@ FFZ.prototype.run_command = function(text, room_id) {
this.log("Received Command: " + cmd, args, true);
try {
output = command.bind(this)(room, args);
output = command.call(this, room, args);
} catch(err) {
this.error('command "' + cmd + '" runner: ' + err);
output = "There was an error running the command.";
@ -602,7 +606,7 @@ FFZ.prototype.run_ffz_command = function(text, room_id) {
var command = FFZ.ffz_commands[cmd], output;
if ( command ) {
try {
output = command.bind(this)(room, args);
output = command.call(this, room, args);
} catch(err) {
this.log("Error Running Command - " + cmd + ": " + err, room);
output = "There was an error running the command.";
@ -849,9 +853,6 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
}
}
}
if ( (removed % 2) && this._roomv && this._roomv.get('context.model.id') === room_id )
this._roomv.ffzAlternate();
}
@ -987,7 +988,7 @@ FFZ.prototype._modify_room = function(room) {
ffzUpdateStatus: function() {
if ( f._roomv )
f._roomv.ffzUpdateStatus();
}.observes('r9k', 'subsOnly', 'slow', 'ffz_banned'),
}.observes('r9k', 'subsOnly', 'emoteOnly', 'slow', 'ffz_banned'),
// User Level
ffzUserLevel: function() {
@ -1057,9 +1058,6 @@ FFZ.prototype._modify_room = function(room) {
}
}
if ( (removed % 2) && f._roomv && f._roomv.get('context.model.id') === this.get('id') )
f._roomv.ffzAlternate();
// Delete pending messages
if (t.ffzPending) {
msgs = t.ffzPending;
@ -1111,11 +1109,8 @@ FFZ.prototype._modify_room = function(room) {
len = messages.get("length"),
limit = this.get("messageBufferSize");
if ( len > limit ) {
if ( len > limit )
messages.removeAt(0, len - limit);
if ( ((len - limit) % 2) && f._roomv && f._roomv.get('context.model.id') === this.get('id') )
f._roomv.ffzAlternate();
}
},
// Artificial chat delay
@ -1232,6 +1227,15 @@ FFZ.prototype._modify_room = function(room) {
// Tokenization
f.tokenize_chat_line(msg, false, this.get('roomProperties.hide_chat_links'));
// If it's from Twitch notify, and it's directly related to
if ( msg.from === 'twitchnotify' && msg.message.indexOf('subscribed to') === -1 && msg.message.indexOf('subscribed') !== -1 ) {
if ( ! msg.tags )
msg.tags = {};
msg.tags.subscriber = true;
if ( msg.labels && msg.labels.indexOf("subscriber") === -1 )
msg.labels.push("subscriber");
}
// Keep the history.
if ( ! is_whisper && msg.from && msg.from !== 'jtv' && msg.from !== 'twitchnotify' && f.settings.mod_card_history ) {
var room = f.rooms && f.rooms[msg.room];
@ -1293,6 +1297,10 @@ FFZ.prototype._modify_room = function(room) {
if ( msg.color )
f._handle_color(msg.color);
// Report this message to the dashboard.
if ( window !== window.parent && parent.postMessage && msg.from && msg.from !== "jtv" && msg.from !== "twitchnotify" )
parent.postMessage({from_ffz: true, command: 'chat_message', data: {from: msg.from, room: msg.room}}, location.protocol + "//www.twitch.tv/");
// Add the message.
return this._super(msg);
},
@ -1403,10 +1411,8 @@ FFZ.prototype._modify_room = function(room) {
if ( f._cindex )
f._cindex.ffzUpdateChatters();
try {
if ( window.parent && window.parent.postMessage )
window.parent.postMessage({from_ffz: true, command: 'chatter_count', message: Object.keys(this.get('ffz_chatters') || {}).length}, "http://www.twitch.tv/");
} catch(err) { /* Ignore errors because of security */ }
if ( window !== window.parent && parent.postMessage )
parent.postMessage({from_ffz: true, command: 'chatter_count', data: Object.keys(this.get('ffz_chatters') || {}).length}, location.protocol + "//www.twitch.tv/");
},

326
src/ember/vod-chat.js Normal file
View file

@ -0,0 +1,326 @@
var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
constants = require("../constants");
// ---------------------
// Settings
// ---------------------
// ---------------------
// Initialization
// ---------------------
FFZ.prototype.setup_vod_chat = function() {
var f = this,
VRC = App.__container__.resolve('component:vod-right-column');
if ( VRC )
this._modify_vod_right_column(VRC);
else
f.error("Unable to locate VOD Right Column component.");
// Get the VOD Chat Service
var VODService = App.__container__.lookup('service:vod-chat-service');
if ( VODService )
VODService.reopen({
messageBufferSize: f.settings.scrollback_length,
pushMessage: function(msg) {
if ( msg.get("color") === null ) {
var colors = this.get("colorSettings"),
from = msg.get("from");
if ( ! colors.get(from) )
colors.set(from, constants.CHAT_COLORS[Math.floor(Math.random() * constants.CHAT_COLORS.length)]);
msg.set("color", colors.get(from));
}
this.get("messages").pushObject(msg);
var messages = this.get("messages"),
len = this.get("messages.length"),
limit = this.get("messageBufferSize");
if ( len > limit )
messages.removeAt(0, len - limit);
}
});
else
f.error("Unable to locate VOD Chat Service.");
// Get the VOD Chat Display
var VODChat = App.__container__.resolve('component:vod-chat-display');
if ( VODChat )
this._modify_vod_chat_display(VODChat);
else
f.error("Unable to locate VOD Chat Display component.");
// Modify all existing VOD Chat views.
var views = window.App && App.__container__.lookup('-view-registry:main') || Ember.View.views;
for(var key in views) {
var view = views[key];
if ( VRC && view instanceof VRC ) {
this.log("Manually updating existing VOD Right Column.");
try {
this._modify_vod_right_column(view);
view.ffzInit();
//Ember.propertyDidChange(view, 'canSeeDarkLaunch');
} catch(err) {
this.error("setup: setup_vod_chat: " + err);
}
} else if ( VODChat && view instanceof VODChat ) {
this.log("Manually updating existing VOD Chat view.", view);
try {
this._modify_vod_chat_display(view);
view.ffzInit();
} catch(err) {
this.error("setup: setup_vod_chat: " + err);
}
}
}
}
FFZ.prototype._modify_vod_right_column = function(component) {
var f = this;
component.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("VODRightColumn didInsertElement: " + err);
}
},
ffzInit: function() {
if ( f.settings.dark_twitch ) {
var el = this.get('element'),
cont = el && el.querySelector('.chat-container');
if ( cont )
cont.classList.add('dark');
}
}
});
}
FFZ.prototype._modify_vod_chat_display = function(component) {
var f = this,
VODService = App.__container__.lookup('service:vod-chat-service');
component.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("VODChat didInsertElement: " + err);
}
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("VODChat willClearRender: " + err);
}
this._super();
},
_prepareToolTips: function() {
this.$(".tooltip").tipsy({
live: true,
gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 's')
})
},
ffzInit: function() {
f._vodc = this;
// Load the room, if necessary
var room_id = this.get('video.channel.name');
if ( room_id && ! f.rooms[room_id] )
f.load_room(room_id); // TODO: Function to reprocess existing messages.
this.ffz_frozen = false;
if ( f.settings.chat_hover_pause )
this.ffzEnableFreeze();
this.$('.chat-messages').find('.html-tooltip').tipsy({
live: true, html: true,
gravity: utils.tooltip_placement(2 * constants.TOOLTIP_DISTANCE, function() {
return this.classList.contains('right') ? 'e' : 'n'
})});
this.$('.chat-messages').find('.ffz-tooltip').tipsy({
live: true, html: true,
title: f.render_tooltip(),
gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, function() {
return this.classList.contains('right') ? 'e' : 'n'
})});
},
ffzTeardown: function() {
if ( f._vodc === this )
f._vodc = undefined;
// TODO: Function to unload the old room?
this.ffzDisableFreeze();
},
ffzEnableFreeze: function() {
var scroller = this.get('chatMessagesScroller');
if ( ! scroller )
return;
this._ffz_interval = setInterval(this.ffzPulse.bind(this), 200);
this._ffz_mouse_move = this.ffzMouseMove.bind(this);
this._ffz_mouse_out = this.ffzMouseOut.bind(this);
scroller.on('mousemove', this._ffz_mouse_move);
scroller.on('touchmove', this._ffz_mouse_move);
scroller.on('mouseout', this._ffz_mouse_out);
},
ffzDisableFreeze: function() {
if ( this._ffz_interval ) {
clearInterval(this._ffz_interval);
this._ffz_interval = undefined;
}
this.ffzUnfreeze();
var scroller = this.get('chatMessagesScroller');
if ( ! scroller )
return;
if ( this._ffz_mouse_move ) {
scroller.off('mousemove', this._ffz_mouse_move);
scroller.off('touchmove', this._ffz_mouse_move);
this._ffz_mouse_move = undefined;
}
if ( this._ffz_mouse_out ) {
scroller.off('mouseout', this._ffz_mouse_out);
this._ffz_mouse_out = undefined;
}
},
ffzUnfreeze: function(from_stuck) {
this.ffz_frozen = false;
this._ffz_last_move = 0;
this.ffzUnwarnPaused();
if ( ! from_stuck && this.get('stuckToBottom') )
this._scrollToBottom();
},
ffzPulse: function() {
if ( this.ffz_frozen ) {
var elapsed = Date.now() - this._ffz_last_move;
if ( elapsed > 750 )
this.ffzUnfreeze();
}
},
/*ffzMouseDown: function(event) {
var scroller = this.get('chatMessagesScroller');
if ( scroller && scroller[0] && ((!this.ffz_frozen && "mousedown" === event.type) || "mousewheel" === event.type || (is_android && "scroll" === event.type) ) ) {
var r = scroller[0].scrollHeight - scroller[0].scrollTop - scroller[0].offsetHeight;
this._setStuckToBottom(10 >= r);
}
},*/
ffzMouseOut: function(event) {
this._ffz_outside = true;
var e = this;
setTimeout(function() {
if ( e._ffz_outside )
e.ffzUnfreeze();
}, 25);
},
ffzMouseMove: function(event) {
this._ffz_last_move = Date.now();
this._ffz_outside = false;
if ( event.screenX === this._ffz_last_screenx && event.screenY === this._ffz_last_screeny )
return;
this._ffz_last_screenx = event.screenX;
this._ffz_last_screeny = event.screenY;
if ( this.ffz_frozen )
return;
this.ffz_frozen = true;
if ( this.get('stuckToBottom') ) {
VODService && VODService.set("messageBufferSize", f.settings.scrollback_length + 150);
this.ffzWarnPaused();
}
},
_scrollToBottom: _.throttle(function() {
var e = this,
scroller = e.get('chatMessagesScroller');
if ( ! scroller || ! scroller.length )
return;
Ember.run.next(function() {
setTimeout(function() {
if ( e.ffz_frozen )
return;
scroller[0].scrollTop = scroller[0].scrollHeight;
e._setStuckToBottom(true);
})
})
}, 300),
_setStuckToBottom: function(val) {
this.set("stuckToBottom", val);
VODService && VODService.set("messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150));
if ( ! val )
this.ffUnfreeze(true);
},
ffzWarnPaused: function() {
var el = this.get('element'),
warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
if ( ! el )
return;
if ( ! warning ) {
warning = document.createElement('div');
warning.className = 'more-messages-indicator ffz-freeze-indicator';
warning.innerHTML = '(Chat Paused Due to Mouse Movement)';
var cont = el.querySelector('.chat-interface');
if ( ! cont )
return;
cont.insertBefore(warning, cont.childNodes[0])
}
warning.classList.remove('hidden');
},
ffzUnwarnPaused: function() {
var el = this.get('element'),
warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
if ( warning )
warning.classList.add('hidden');
}
});
}

View file

@ -9,19 +9,6 @@ var FFZ = window.FrankerFaceZ,
return "";
return 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n";
},
from_code_point = function(cp) {
var code = typeof cp === "string" ? parseInt(cp, 16) : cp;
if ( code < 0x10000)
return String.fromCharCode(code);
code -= 0x10000;
return String.fromCharCode(
0xD800 + (code >> 10),
0xDC00 + (code & 0x3FF)
);
};
@ -218,97 +205,6 @@ FFZ.ws_commands.load_set = function(set_id) {
}
// ---------------------
// Tooltip Powah!
// ---------------------
FFZ.prototype._emote_tooltip = function(emote) {
if ( ! emote )
return null;
if ( emote._tooltip )
return emote._tooltip;
var set = this.emote_sets[emote.set_id],
owner = emote.owner,
title = set && set.title || "Global",
source = set && set.source || "FFZ",
preview_url = this.settings.emote_image_hover ? (emote.urls[4] || emote.urls[2]) : null,
image = preview_url ? '<img class="emoticon ffz-image-hover" src="' + preview_url + '?_=preview">' : '';
emote._tooltip = image + "Emoticon: " + (emote.hidden ? "???" : emote.name) + "<br>" + source + " " + title + (owner ? "<br>By: " + owner.display_name : "");
return emote._tooltip;
}
FFZ.prototype._reset_tooltips = function(twitch_only) {
for(var emote_id in this._twitch_emotes) {
var data = this._twitch_emotes[emote_id];
if ( data && data.tooltip )
data.tooltip = null;
}
if ( ! twitch_only ) {
for(var set_id in this.emote_sets) {
var emote_set = this.emote_sets[set_id];
for(var emote_id in emote_set.emoticons) {
var emote = emote_set.emoticons[emote_id];
if ( emote._tooltip )
emote._tooltip = null;
}
}
}
var emotes = document.querySelectorAll('img.emoticon');
for(var i=0; i < emotes.length; i++) {
var emote = emotes[i];
if ( emote.classList.contains('ffz-image-hover') )
continue;
var set_id,
emote_id = emote.getAttribute('data-emote');
if ( emote_id ) {
// Twitch Emotes
if ( this.has_bttv )
continue;
emote.setAttribute('original-title', utils.build_tooltip.bind(this)(emote_id, false, emote.alt));
continue;
}
if ( twitch_only )
continue;
// FFZ Emoji
emote_id = emote.getAttribute('data-ffz-emoji');
if ( emote_id ) {
var emoji = this.emoji_data && this.emoji_data[emote_id],
setting = this.settings.parse_emoji,
src = emoji ? (setting === 2 ? emoji.noto_src : emoji.tw_src) : null,
image = '';
if ( src && this.settings.emote_image_hover )
image = '<img class="emoticon ffz-image-hover emoji" src="' + src + '">';
emote.setAttribute('original-title', emoji ? (image + 'Emoji: ' + emote.alt + '<br>Name: ' + emoji.name + (emoji.short_name ? "<br>Short Name: :" + emoji.short_name + ":" : "")) : emote.alt);
continue;
}
// FFZ Emotes
emote_id = emote.getAttribute('data-ffz-emote');
set_id = emote.getAttribute('data-ffz-set');
var emote_set = this.emote_sets[set_id];
if ( ! emote_set || ! emote_set.emoticons || ! emote_set.emoticons[emote_id] )
continue;
emote.setAttribute('original-title', this._emote_tooltip(emote_set.emoticons[emote_id]));
}
}
// ---------------------
// Emoji Loading
// ---------------------
@ -325,21 +221,29 @@ FFZ.prototype.load_emoji_data = function(callback, tries) {
emoji.code = eid;
new_data[eid] = emoji;
if ( emoji.short_name )
by_name[emoji.short_name] = eid;
if ( emoji.names && emoji.names.length )
for(var x=0,y=emoji.names.length; x < y; x++)
by_name[emoji.names[x]] = eid;
emoji.raw = _.map(emoji.code.split("-"), from_code_point).join("");
emoji.raw = _.map(emoji.code.split("-"), utils.codepoint_to_emoji).join("");
emoji.tw_src = constants.SERVER + 'emoji/tw-' + eid + '.svg';
emoji.tw_src = constants.SERVER + 'emoji/tw/' + eid + '.svg';
emoji.noto_src = constants.SERVER + 'emoji/noto-' + eid + '.svg';
emoji.one_src = constants.SERVER + 'emoji/one/' + eid + '.svg';
emoji.token = {
emoticonSrc: true,
type: "emoticon",
imgSrc: true,
tw_src: emoji.tw_src,
noto_src: emoji.noto_src,
one_src: emoji.one_src,
tw: emoji.tw,
noto: emoji.noto,
one: emoji.one,
ffzEmoji: eid,
altText: emoji.raw
@ -481,6 +385,15 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) {
else
emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")\\b", "g");
emote.token = {
type: "emoticon",
srcSet: emote.srcSet,
imgSrc: emote.urls[1],
ffzEmote: emote.id,
ffzEmoteSet: set_id,
altText: emote.hidden ? '???' : emote.name
};
output_css += build_css(emote);
data.count++;
data.emoticons[emote.id] = emote;

View file

@ -14,7 +14,7 @@ var FFZ = window.FrankerFaceZ,
// API Constructor
// ---------------------
var API = FFZ.API = function(instance, name, icon) {
var API = FFZ.API = function(instance, name, icon, version) {
this.ffz = instance || FFZ.get();
// Check for a known API!
@ -54,12 +54,13 @@ var API = FFZ.API = function(instance, name, icon) {
this.name = name || ("Extension#" + this.id);
this.icon = icon || null;
this.version = version || null;
this.ffz.log('Registered New Extension #' + this.id + ': ' + this.name);
};
FFZ.prototype.api = function(name, icon) {
FFZ.prototype.api = function(name, icon, version) {
// Load the known APIs list.
if ( ! this._known_apis ) {
this._known_apis = {};
@ -71,7 +72,7 @@ FFZ.prototype.api = function(name, icon) {
}
}
return new API(this, name, icon);
return new API(this, name, icon, version);
}
@ -163,6 +164,15 @@ API.prototype._load_set = function(real_id, set_id, data) {
else
new_emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")(?=\\W|$)", "g");
new_emote.token = {
type: "emoticon",
srcSet: new_emote.srcSet,
imgSrc: new_emote.urls[1],
ffzEmote: id,
ffzEmoteSet: real_id,
altText: new_emote.hidden ? '???' : new_emote.name
};
output_css += build_css(new_emote);
emote_set.count++;
emoticons[id] = new_emote;

View file

@ -42,6 +42,14 @@ FFZ.prototype.setup_bttv = function(delay) {
utils.update_css(this._chat_style, 'chat_ts_font_size', '');
}
// Remove Sub Count and the Chart
if ( this.is_dashboard ) {
this._update_subscribers();
this._remove_dash_chart();
}
document.body.classList.add('ffz-bttv');
// Disable Chat Tabs
if ( this._chatv ) {
if ( this.settings.group_tabs )
@ -79,6 +87,8 @@ FFZ.prototype.setup_bttv = function(delay) {
this.toggle_style('chat-colors-gray');
this.toggle_style('badges-transparent');
this.toggle_style('badges-sub-notice');
this.toggle_style('badges-sub-notice-on');
// Disable other features too.
document.body.classList.remove('ffz-transparent-badges');
@ -93,12 +103,6 @@ FFZ.prototype.setup_bttv = function(delay) {
this._draw_following_channels();
}
// Remove Sub Count
if ( this.is_dashboard )
this._update_subscribers();
document.body.classList.add('ffz-bttv');
// Send Message Behavior
var original_send = BetterTTV.chat.helpers.sendMessage, f = this;
BetterTTV.chat.helpers.sendMessage = function(message) {
@ -197,6 +201,41 @@ FFZ.prototype.setup_bttv = function(delay) {
}
};
// Emoji!
var parse_emoji = function(token) {
var setting = f.settings.parse_emoji,
output = [],
segments = token.split(constants.EMOJI_REGEX),
text = null;
if ( setting === 0 )
return [token];
while(segments.length) {
text = (text || '') + segments.shift();
if ( segments.length ) {
var match = segments.shift(),
eid = utils.emoji_to_codepoint(match),
data = f.emoji_data[eid],
src = data && (setting === 3 ? data.one_src : (setting === 2 ? data.noto_src : data.tw_src));
if ( src ) {
if ( text && text.length )
output.push(text);
var code = utils.quote_attr(data.raw);
output.push(['<img class="emoticon emoji ffz-tooltip" height="18px" data-ffz-emoji="' + eid + '" src="' + utils.quote_attr(src) + '" data-regex="' + code + '" alt="' + code + '">']);
text = null;
} else
text = (text || '') + match;
}
}
if ( text && text.length )
output.push(text);
return output;
}
// Emoticonize
var original_emoticonize = BetterTTV.chat.templates.emoticonize;
BetterTTV.chat.templates.emoticonize = function(message, emotes) {
@ -206,106 +245,64 @@ FFZ.prototype.setup_bttv = function(delay) {
l_room = room && room.toLowerCase(),
l_sender = received_sender && received_sender.toLowerCase(),
sets = f.getEmotes(l_sender, l_room),
emotes = [],
emotes = {}, emote,
user = f.get_user(),
new_tokens = [],
mine = user && user.login === l_sender;
// Build a list of emotes that match.
_.each(sets, function(set_id) {
var set = f.emote_sets[set_id];
if ( ! set )
return;
// Build an object with all of our emotes.
for(var i=0; i < sets.length; i++) {
var emote_set = f.emote_sets[sets[i]];
if ( emote_set && emote_set.emoticons )
for(var emote_id in emote_set.emoticons) {
emote = emote_set.emoticons[emote_id];
if ( ! emotes[emote.name] )
emotes[emote.name] = emote;
}
}
_.each(set.emoticons, function(emote) {
_.any(tokens, function(token) {
return _.isString(token) && token.match(emote.regex);
}) && emotes.push(emote);
});
});
// Don't bother proceeding if we have no emotes.
if ( emotes.length ) {
// Why is emote parsing so bad? ;_;
_.each(emotes, function(emote) {
var tooltip = f._emote_tooltip(emote),
eo = ['<img class="emoticon html-tooltip" data-ffz-emote="' + emote.id + '" srcset="' + utils.quote_attr(emote.srcSet || "") + '" src="' + utils.quote_attr(emote.urls[1]) + '" data-regex="' + utils.quote_attr(emote.name) + '" title="' + utils.quote_attr(tooltip) + '">'],
old_tokens = tokens;
tokens = [];
for(var i=0; i < old_tokens.length; i++) {
var token = old_tokens[i];
for(var i=0, l=tokens.length; i < l; i++) {
var token = tokens[i];
if ( typeof token !== "string" ) {
tokens.push(token);
new_tokens.push(token);
continue;
}
var tbits = token.split(emote.regex);
while(tbits.length) {
var bit = tbits.shift();
if ( tbits.length ) {
bit += tbits.shift();
if ( bit )
tokens.push(bit);
// Split the token!
var segments = token.split(' '),
text = [], segment;
tbits.shift();
tokens.push(eo);
for(var x=0,y=segments.length; x < y; x++) {
segment = segments[x];
emote = emotes[segment];
if ( emote ) {
if ( text.length ) {
var toks = parse_emoji(text.join(' ') + ' ');
for(var q=0; q < toks.length; q++)
new_tokens.push(toks[q]);
text = [];
}
new_tokens.push(['<img class="emoticon ffz-tooltip" data-ffz-set="' + emote.set_id + '" data-ffz-emote="' + emote.id + '" srcset="' + utils.quote_attr(emote.srcSet || "") + '" src="' + utils.quote_attr(emote.urls[1]) + '" data-regex="' + utils.quote_attr(emote.name) + '">']);
if ( mine && l_room )
f.add_usage(l_room, emote);
text.push('');
} else
tokens.push(bit);
}
}
});
text.push(segment);
}
// Sneak in Emojicon Processing
if ( f.settings.parse_emoji && f.emoji_data ) {
var old_tokens = tokens,
setting = f.settings.parse_emoji;
tokens = [];
for(var i=0; i < old_tokens.length; i++) {
var token = old_tokens[i];
if ( typeof token !== "string" ) {
tokens.push(token);
continue;
}
var tbits = token.split(constants.EMOJI_REGEX);
while(tbits.length) {
var bit = tbits.shift();
bit && tokens.push(bit);
if ( tbits.length ) {
var match = tbits.shift(),
variant = tbits.shift();
if ( variant === '\uFE0E' )
tokens.push(match);
else {
var eid = utils.emoji_to_codepoint(match, variant),
data = f.emoji_data[eid],
src = data && (setting === 2 ? data.noto_src : data.tw_src);
if ( data && src ) {
var image = src && f.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + src + '">' : '',
tooltip = image + "Emoji: " + data.raw + "<br>Name: " + data.name + (data.short_name ? "<br>Short Name: :" + data.short_name + ":" : ""),
code = utils.quote_attr(data.raw);
tokens.push(['<img class="emoticon emoji html-tooltip" height="18px" data-ffz-emoji="' + eid + '" src="' + utils.quote_attr(src) + '" data-regex="' + code + '" alt="' + code + '" title="' + utils.quote_attr(tooltip) + '">']);
} else
tokens.push(match + (variant || ""));
}
}
}
if ( text.length > 1 || (text.length === 1 && text[0] !== '') ) {
var toks = parse_emoji(text.join(' ') + ' ');
for(var q=0; q < toks.length; q++)
new_tokens.push(toks[q]);
}
}
return tokens;
return new_tokens;
}
this.update_ui_link();

View file

@ -99,6 +99,7 @@ FFZ.prototype.find_rechat = function() {
// Tooltips
jQuery(container).find('.tooltip').tipsy({live: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
jQuery(container).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery(container).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
// Load the room data.
var room_id = el.getAttribute('data-room');
@ -234,20 +235,26 @@ FFZ.prototype.process_rechat_line = function(line, reprocess) {
else if ( node.nodeType === node.ELEMENT_NODE ) {
if ( node.tagName === 'IMG' )
tokens.push({
type: "emoticon",
altText: node.alt,
emoticonSrc: node.src
imgSrc: node.src
});
else if ( node.tagName === 'A' )
tokens.push({
isLink: true,
href: node.textContent
type: "link",
isDeleted: false,
isLong: false,
length: node.textContent.length,
link: node.href,
text: node.textContent
});
else if ( node.tagName === 'SPAN' )
tokens.push({
mentionedUser: node.textContent,
own: node.classList.contains('mentioning')
type: "mention",
user: node.textContent,
isOwnMessage: node.classList.contains('mentioning')
});
else {

View file

@ -69,7 +69,7 @@ FFZ.prototype._feature_friday_ui = function(room_id, parent, view) {
btn.classList.toggle('live', ff.live);
btn.classList.toggle('blue', this.has_bttv && BetterTTV.settings.get('showBlueButtons'));
btn.href = "http://www.twitch.tv/" + ff.channel;
btn.href = "//www.twitch.tv/" + ff.channel;
btn.title = message;
btn.target = "_new";
btn.innerHTML = "<span>" + message + "</span>";
@ -122,7 +122,7 @@ FFZ.prototype._update_ff_live = function() {
return;
var f = this;
Twitch.api.get("streams/" + this.feature_friday.channel)
utils.api.get("streams/" + this.feature_friday.channel)
.done(function(data) {
f.feature_friday.live = data.stream != null;
f.update_ui_link();

View file

@ -12,6 +12,16 @@ var FFZ = window.FrankerFaceZ = function() {
this._log_data = [];
this._apis = {};
// Error Logging
var t = this;
window.addEventListener('error', function(event) {
if ( ! event.error )
return;
var has_stack = event.error && event.error.stack;
t.log("JavaScript Error: " + event.message + " [" + event.filename + ":" + event.lineno + ":" + event.colno + "]", has_stack ? event.error.stack : undefined, false, has_stack);
});
// Get things started.
this.initialize();
}
@ -19,10 +29,13 @@ var FFZ = window.FrankerFaceZ = function() {
FFZ.get = function() { return FFZ.instance; }
// TODO: This should be in a module.
FFZ.msg_commands = {};
// Version
var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 100,
major: 3, minor: 5, revision: 133,
toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
}
@ -32,36 +45,38 @@ var VER = FFZ.version_info = {
// Logging
FFZ.prototype.log = function(msg, data, to_json, log_json) {
msg = "FFZ: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
if ( to_json )
msg = msg + ' -- ' + JSON.stringify(data);
this._log_data.push(msg + ((!to_json && log_json) ? " -- " + JSON.stringify(data) : ""));
if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg);
console.groupCollapsed("FFZ: " + msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
console.log(data);
else
console.dir(data);
console.groupEnd(msg);
console.groupEnd("FFZ: " + msg);
} else
console.log(msg);
console.log("FFZ: " + msg);
}
FFZ.prototype.error = function(msg, data, to_json) {
msg = "FFZ Error: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
this._log_data.push(msg);
FFZ.prototype.error = function(msg, data, to_json, log_json) {
msg = "Error: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
this._log_data.push(msg + ((!to_json && log_json) ? " -- " + JSON.stringify(data) : ""));
if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg);
console.groupCollapsed("FFZ " + msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
console.log(data);
else
console.dir(data);
console.groupEnd(msg);
console.groupEnd("FFZ " + msg);
} else
console.assert(false, msg);
console.assert(false, "FFZ " + msg);
}
@ -78,9 +93,9 @@ FFZ.prototype.paste_logs = function() {
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");
callback.call(this, e.trim() + ".log");
}).fail(function(e) {
callback.bind(this)(null);
callback.call(this, null);
});
}
@ -130,6 +145,7 @@ require('./ember/router');
require('./ember/channel');
require('./ember/player');
require('./ember/room');
require('./ember/vod-chat');
require('./ember/layout');
require('./ember/line');
require('./ember/chatview');
@ -143,7 +159,7 @@ require('./ember/following');
require('./debug');
require('./ext/rechat');
//require('./ext/rechat');
require('./ext/betterttv');
require('./ext/emote_menu');
@ -156,6 +172,7 @@ require('./ui/tooltips');
require('./ui/notifications');
require('./ui/viewer_count');
require('./ui/sub_count');
require('./ui/dash_stats');
require('./ui/menu_button');
require('./ui/following');
@ -182,6 +199,14 @@ FFZ.prototype.initialize = function(increment, delay) {
return;
}
// Check for the transfer page.
if ( location.pathname === "/crossdomain/transfer" ) {
if ( location.hash.indexOf("ffz-settings-transfer") !== -1 )
this.init_settings_transfer();
return;
}
// Check for special non-ember pages.
if ( /^\/(?:$|search$|user\/|p\/|settings|m\/|messages?\/)/.test(location.pathname) ) {
this.init_normal(delay);
@ -218,9 +243,20 @@ FFZ.prototype.initialize = function(increment, delay) {
}
FFZ.prototype.init_settings_transfer = function() {
this.log("This is the HTTP Transfer URL. Building a settings backup and posting it to our parent.");
this.load_settings();
try { this.setup_line(); } catch(err) { }
var msg = {from_ffz: true, command: "http_settings", data: this._get_settings_object()};
window.opener.postMessage(msg, "https://www.twitch.tv");
window.close();
}
FFZ.prototype.init_player = function(delay) {
var start = (window.performance && performance.now) ? performance.now() : Date.now();
this.log("Found Twitch Player after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info);
this.log("Found Twitch Player after " + (delay||0) + " ms at: " + location);
this.log("Initializing FrankerFaceZ version " + FFZ.version_info);
this.users = {};
this.is_dashboard = false;
@ -243,7 +279,8 @@ FFZ.prototype.init_player = function(delay) {
FFZ.prototype.init_normal = function(delay, no_socket) {
var start = (window.performance && performance.now) ? performance.now() : Date.now();
this.log("Found non-Ember Twitch after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info);
this.log("Found non-Ember Twitch after " + (delay||0) + " ms at: " + location);
this.log("Initializing FrankerFaceZ version " + FFZ.version_info);
this.users = {};
this.is_dashboard = false;
@ -272,6 +309,7 @@ FFZ.prototype.init_normal = function(delay, no_socket) {
this.setup_following_count(false);
this.setup_menu();
this.setup_message_event();
this.fix_tooltips();
this.find_bttv(10);
@ -286,7 +324,11 @@ FFZ.prototype.is_dashboard = false;
FFZ.prototype.init_dashboard = function(delay) {
var start = (window.performance && performance.now) ? performance.now() : Date.now();
this.log("Found Twitch Dashboard after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info);
this.log("Found Twitch Dashboard after " + (delay||0) + " ms at: " + location);
this.log("Initializing FrankerFaceZ version " + FFZ.version_info);
var match = location.pathname.match(/\/([^\/]+)/);
this.dashboard_channel = match && match[1] || undefined;
this.users = {};
this.is_dashboard = true;
@ -311,6 +353,7 @@ FFZ.prototype.init_dashboard = function(delay) {
this.setup_notifications();
this.setup_following_count(false);
this.setup_menu();
this.setup_dash_stats();
this._update_subscribers();
@ -329,7 +372,8 @@ FFZ.prototype.init_dashboard = function(delay) {
FFZ.prototype.init_ember = function(delay) {
var start = (window.performance && performance.now) ? performance.now() : Date.now();
this.log("Found Twitch application after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info);
this.log("Found Twitch application after " + (delay||0) + " ms at: " + location);
this.log("Initializing FrankerFaceZ version " + FFZ.version_info);
this.users = {};
this.is_dashboard = false;
@ -367,6 +411,7 @@ FFZ.prototype.init_ember = function(delay) {
this.setup_player();
this.setup_channel();
this.setup_room();
this.setup_vod_chat();
this.setup_line();
this.setup_layout();
this.setup_chatview();
@ -389,12 +434,14 @@ FFZ.prototype.init_ember = function(delay) {
this.fix_tooltips();
this.connect_extra_chat();
this.setup_rechat();
//this.setup_rechat();
this.setup_message_event();
this.find_bttv(10);
this.find_emote_menu(10);
//this.check_news();
this.check_ff();
this.refresh_chat();
var end = (window.performance && performance.now) ? performance.now() : Date.now(),
duration = end - start;
@ -414,8 +461,16 @@ FFZ.prototype.setup_message_event = function() {
FFZ.prototype._on_window_message = function(e) {
if ( ! e.data || ! e.data.from_ffz )
var msg = e.data;
if ( typeof msg === "string" )
msg = JSON.parse(msg);
if ( ! msg || ! msg.from_ffz )
return;
var msg = e.data;
var handler = FFZ.msg_commands[msg.command];
if ( handler )
handler.call(this, msg.data);
else
this.log("Invalid Message: " + msg.command, msg.data, false, true);
}

View file

@ -1,39 +1,30 @@
var FFZ = window.FrankerFaceZ,
constants = require("./constants"),
FileSaver = require("./FileSaver");
utils = require("./utils"),
FileSaver = require("./FileSaver"),
createElement = document.createElement.bind(document),
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);
},
option_setting = function(select, key) {
this.settings.set(key, JSON.parse(select.options[select.selectedIndex].value));
},
toggle_basic_setting = function(swit, key) {
var getter = FFZ.basic_settings[key].get,
val = !(typeof getter === 'function' ? getter.bind(this)() : this.settings.get(getter)),
setter = FFZ.basic_settings[key].set;
if ( typeof setter === 'function' )
setter.bind(this)(val);
toggle_setting = function(swit, key, info) {
var val = !(info.get ? (typeof info.get === 'function' ? info.get.call(this) : this.settings.get(info.get)) : this.settings.get(key));
if ( typeof info.set === "function" )
info.set.call(this, val);
else
this.settings.set(setter, val);
this.settings.set(info.set || key, val);
swit.classList.toggle('active', val);
},
option_basic_setting = function(select, key) {
FFZ.basic_settings[key].set.bind(this)(JSON.parse(select.options[select.selectedIndex].value));
option_setting = function(select, key, info) {
var val = JSON.parse(select.options[select.selectedIndex].value);
if ( typeof info.set === "function" )
info.set.call(this, val);
else
this.settings.set(info.set || key, val);
};
@ -59,11 +50,17 @@ FFZ.prototype.load_settings = function() {
this.settings.del = this._setting_del.bind(this);
this.settings.load = this._setting_load.bind(this);
var found_settings = false;
for(var key in FFZ.settings_info) {
if ( ! FFZ.settings_info.hasOwnProperty(key) )
continue;
this._setting_load(key);
var info = FFZ.settings_info[key],
ls_key = info && info.storage_key || make_ls(key);
found_settings = found_settings || localStorage.hasOwnProperty(key);
this._setting_load(key) || found_settings;
}
// Listen for Changes
@ -75,11 +72,19 @@ FFZ.prototype.load_settings = function() {
// Backup and Restore
// --------------------
FFZ.prototype._settings_open_http_window = function() {
window.open("http://www.twitch.tv/crossdomain/transfer#ffz-settings-transfer", "_ffz_settings");
}
FFZ.msg_commands.http_settings = function(data) {
this._load_settings_file(data);
}
FFZ.prototype.reset_settings = function() {
if ( ! confirm(this.tr('Are you sure you wish to reset FrankerFaceZ?\n\nThis will force the tab to refresh.')) )
return;
// Clear Settings
for(var key in FFZ.settings_info) {
if ( ! FFZ.settings_info.hasOwnProperty(key) )
@ -94,13 +99,12 @@ FFZ.prototype.reset_settings = function() {
// TODO: Filters
// Refresh
window.location.reload();
}
FFZ.prototype.save_settings_file = function() {
FFZ.prototype._get_settings_object = function() {
var data = {
version: 1,
script_version: FFZ.version_info + '',
@ -120,7 +124,15 @@ FFZ.prototype.save_settings_file = function() {
data.settings[key] = this.settings[key];
}
var blob = new Blob([JSON.stringify(data, null, 4)], {type: "application/json;charset=utf-8"});
return data;
}
FFZ.prototype.save_settings_file = function() {
var data = this._get_settings_object(),
blob = new Blob(
[JSON.stringify(data, null, 4)], {type: "application/json;charset=utf-8"});
FileSaver.saveAs(blob, "ffz-settings.json");
}
@ -137,12 +149,15 @@ FFZ.prototype.load_settings_file = function(file) {
}
}
FFZ.prototype._load_settings_file = function(data) {
FFZ.prototype._load_settings_file = function(data, hide_alert) {
if ( typeof data === "string" )
try {
data = JSON.parse(data);
} catch(err) {
this.error("Error Loading Settings: " + err);
return alert("There was an error attempting to read the provided settings data.");
if ( ! hide_alert )
alert("There was an error attempting to read the provided settings data.");
return [-1,-1,-1];
}
this.log("Loading Settings Data", data);
@ -161,7 +176,7 @@ FFZ.prototype._load_settings_file = function(data) {
val = data.settings[key];
if ( info.process_value )
val = info.process_value.bind(this)(val);
val = info.process_value.call(this, val);
if ( val !== this.settings.get(key) )
this.settings.set(key, val);
@ -188,9 +203,12 @@ FFZ.prototype._load_settings_file = function(data) {
}
// Do this in a timeout so that any styles have a moment to update.
if ( ! hide_alert )
setTimeout(function(){
alert('Successfully loaded ' + applied.length + ' settings and skipped ' + skipped.length + ' settings. Added ' + aliases + ' user nicknames.');
});
return [applied.length, skipped.length, aliases];
}
@ -198,112 +216,274 @@ FFZ.prototype._load_settings_file = function(data) {
// Menu Page
// --------------------
FFZ.menu_pages.settings = {
render: function(view, container) {
// Bottom Bar
var menu = document.createElement('ul'),
page = document.createElement('div'),
var is_android = navigator.userAgent.indexOf('Android') !== -1,
settings_renderer = function(settings_data, collapsable, collapsed_key) {
return function(view, container) {
var f = this,
settings = {},
categories = [];
tab_basic = document.createElement('li'),
link_basic = document.createElement('a'),
for(var key in settings_data) {
var info = settings_data[key],
cat = info.category || "Miscellaneous",
cat_store = settings[cat];
tab_adv = document.createElement('li'),
link_adv = document.createElement('a'),
if ( info.hasOwnProperty('visible') ) {
var visible = info.visible;
if ( typeof visible === "function" )
visible = visible.call(this);
tab_save = document.createElement('li'),
link_save = document.createElement('a'),
height = parseInt(container.style.maxHeight || '0');
// Height Calculation
if ( ! height )
height = Math.max(200, view.$().height() - 172);
if ( height && height !== NaN ) {
height -= 37;
page.style.maxHeight = height + 'px';
if ( ! visible )
continue;
}
// Menu Building
page.className = 'ffz-ui-sub-menu-page';
menu.className = 'menu sub-menu clearfix';
if ( is_android && info.no_mobile )
continue;
tab_basic.className = 'item';
tab_basic.id = 'ffz-settings-page-basic';
link_basic.innerHTML = 'Basic';
tab_basic.appendChild(link_basic);
if ( ! cat_store ) {
categories.push(cat);
cat_store = settings[cat] = [];
}
tab_adv.className = 'item';
tab_adv.id = 'ffz-settings-page-advanced';
link_adv.innerHTML = 'Advanced';
tab_adv.appendChild(link_adv);
cat_store.push([key, info]);
}
tab_save.className = 'item';
tab_save.id = 'ffz-settings-page-save';
link_save.textContent = 'Backup & Restore';
tab_save.appendChild(link_save);
categories.sort(function(a,b) {
var a = a.toLowerCase(),
b = b.toLowerCase();
menu.appendChild(tab_basic);
menu.appendChild(tab_adv);
menu.appendChild(tab_save);
if ( a === "debugging" )
a = "zzz" + a;
var cp = FFZ.menu_pages.settings.change_page;
if ( b === "debugging" )
b = "zzz" + b;
link_basic.addEventListener('click', cp.bind(this, view, container, menu, page, 'basic'));
link_adv.addEventListener('click', cp.bind(this, view, container, menu, page, 'advanced'));
link_save.addEventListener('click', cp.bind(this, view, container, menu, page, 'save'));
if ( a < b ) return -1;
else if ( a > b ) return 1;
return 0;
});
if ( this.settings.advanced_settings )
link_adv.click();
else
link_basic.click();
var current_category = (collapsed_key ? this[collapsed_key] : null) || categories[0];
for(var ci=0; ci < categories.length; ci++) {
var category = categories[ci],
cset = settings[category],
bttv_skipped = [],
added = 0,
menu = createElement('div'),
heading = createElement('div');
heading.className = 'heading';
menu.className = 'chat-menu-content';
menu.setAttribute('data-category', category);
if ( collapsable ) {
menu.classList.add('collapsable');
menu.classList.toggle('collapsed', current_category !== category);
menu.addEventListener('click', function() {
var t = this;
if ( ! t.classList.contains('collapsed') )
return;
jQuery(".chat-menu-content:not(.collapsed)", container).addClass("collapsed");
t.classList.remove('collapsed');
if ( collapsed_key )
f[collapsed_key] = t.getAttribute('data-category');
setTimeout(function(){t.scrollIntoViewIfNeeded()});
});
}
heading.innerHTML = category;
menu.appendChild(heading);
cset.sort(function(a,b) {
var a = a[1],
b = b[1],
at = 2, //a.type === "boolean" ? 1 : 2,
bt = 2, //b.type === "boolean" ? 1 : 2,
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 = createElement('p'),
val = info.get ? (typeof info.get === 'function' ? info.get.call(this) : this.settings.get(info.get)) : this.settings.get(key);
el.className = 'clearfix';
if ( this.has_bttv && info.no_bttv ) {
bttv_skipped.push([info.name, info.help]);
continue;
} else {
if ( info.type === "boolean" ) {
var swit = createElement('a'),
label = createElement('span');
swit.className = 'switch';
swit.classList.toggle('active', val);
swit.appendChild(createElement('span'))
label.className = 'switch-label';
label.innerHTML = info.name;
el.appendChild(swit);
el.appendChild(label);
swit.addEventListener('click', toggle_setting.bind(this, swit, key, info))
} else if ( info.type === "select" ) {
var select = createElement('select'),
label = createElement('span');
label.className = 'option-label';
label.innerHTML = info.name;
for(var ok in info.options) {
var op = createElement('option');
op.value = JSON.stringify(ok);
if ( val == ok )
op.setAttribute('selected', true);
op.innerHTML = info.options[ok];
select.appendChild(op);
}
select.addEventListener('change', option_setting.bind(this, select, key, info));
el.appendChild(label);
el.appendChild(select);
} else if ( typeof info.method === "function" ) {
el.classList.add("option");
var link = createElement('a');
link.innerHTML = info.name;
link.href = '#';
el.appendChild(link);
link.addEventListener('click', info.method.bind(this));
} else
continue;
if ( info.help || (this.has_bttv && info.warn_bttv) ) {
var help = document.createElement('span');
help.className = 'help';
help.innerHTML = (this.has_bttv && info.warn_bttv ? '<i>' + info.warn_bttv + (info.help ? '</i><br>' : '</i>') : '') + (info.help || "");
el.appendChild(help);
}
}
added++;
menu.appendChild(el);
}
if ( ! added )
continue;
if ( bttv_skipped.length ) {
var el = createElement('p'),
label = createElement('span'),
help = createElement('span');
el.className = 'bttv-incompatibility clearfix disabled';
label.className = 'switch-label';
label.innerHTML = "Features Incompatible with BetterTTV";
help.className = 'help';
for(var i=0; i < bttv_skipped.length; i++) {
var skipped = bttv_skipped[i];
help.innerHTML += (i > 0 ? ', ' : '') + '<b' + (skipped[1] ? ' class="html-tooltip" title="' + utils.quote_attr(skipped[1]) + '"' : '') + '>' + skipped[0] + '</b>';
}
el.appendChild(label);
el.appendChild(help);
menu.appendChild(el);
jQuery('.html-tooltip', el).tipsy({html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery('.ffz-tooltip', el).tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
}
container.appendChild(page);
container.appendChild(menu);
}
}
},
change_page: function(view, container, menu, page, key) {
page.innerHTML = '';
page.setAttribute('data-page', key);
render_basic = settings_renderer(FFZ.basic_settings, false, '_ffz_basic_settings_page'),
render_advanced = settings_renderer(FFZ.settings_info, true, '_ffz_settings_page');
var els = menu.querySelectorAll('li.active');
for(var i=0, l = els.length; i < l; i++)
els[i].classList.remove('active');
var el = menu.querySelector('#ffz-settings-page-' + key);
if ( el )
el.classList.add('active');
FFZ.menu_pages.settings = {
name: "Settings",
icon: constants.GEAR,
sort_order: 99999,
wide: true,
FFZ.menu_pages.settings['render_' + key].bind(this)(view, page);
default_page: function() { return this.settings.advanced_settings ? 'advanced' : 'basic' },
if ( key === 'advanced' )
this.settings.set('advanced_settings', true);
else if ( key === 'basic' )
this.settings.set('advanced_settings', false);
pages: {
basic: {
name: "Basic",
sort_order: 1,
render: function(view, container) {
this.settings.set("advanced_settings", false);
return render_basic.call(this, view, container);
}
},
render_save: function(view, container) {
var backup_head = document.createElement('div'),
restore_head = document.createElement('div'),
reset_head = document.createElement('div'),
advanced: {
name: "Advanced",
sort_order: 2,
backup_cont = document.createElement('div'),
restore_cont = document.createElement('div'),
reset_cont = document.createElement('div'),
render: function(view, container) {
this.settings.set("advanced_settings", true);
return render_advanced.call(this, view, container);
}
},
backup_para = document.createElement('p'),
backup_link = document.createElement('a'),
backup_help = document.createElement('span'),
backup: {
name: "Backup & Restore",
sort_order: 3,
restore_para = document.createElement('p'),
restore_input = document.createElement('input'),
restore_link = document.createElement('a'),
restore_help = document.createElement('span'),
render: function(view, container) {
var backup_head = createElement('div'),
restore_head = createElement('div'),
reset_head = createElement('div'),
reset_para = document.createElement('p'),
reset_link = document.createElement('a'),
reset_help = document.createElement('span'),
backup_cont = createElement('div'),
restore_cont = createElement('div'),
reset_cont = createElement('div'),
backup_para = createElement('p'),
backup_link = createElement('a'),
backup_help = createElement('span'),
http_para = createElement('p'),
http_link = createElement('a'),
http_help = createElement('span'),
restore_para = createElement('p'),
restore_input = createElement('input'),
restore_link = createElement('a'),
restore_help = createElement('span'),
reset_para = createElement('p'),
reset_link = createElement('a'),
reset_help = createElement('span'),
f = this;
@ -346,6 +526,20 @@ FFZ.menu_pages.settings = {
restore_para.appendChild(restore_help);
restore_cont.appendChild(restore_para);
http_para.className = 'clearfix option';
http_link.href = '#';
http_link.innerHTML = 'Import from HTTP';
http_link.addEventListener('click', this._settings_open_http_window.bind(this));
http_help.className = 'help';
http_help.innerHTML = 'Load your settings from HTTP into HTTPS. (This briefly opens a new window.)';
http_para.appendChild(http_link);
http_para.appendChild(http_help);
if ( location.protocol === "https:" )
restore_cont.appendChild(http_para);
reset_cont.className = 'chat-menu-content';
reset_head.className = 'heading';
reset_head.innerHTML = this.tr('Reset Settings');
@ -367,383 +561,9 @@ FFZ.menu_pages.settings = {
container.appendChild(backup_cont);
container.appendChild(restore_cont);
container.appendChild(reset_cont);
},
render_basic: function(view, container) {
var settings = {},
categories = [],
is_android = navigator.userAgent.indexOf('Android') !== -1;
for(var key in FFZ.basic_settings) {
if ( ! FFZ.basic_settings.hasOwnProperty(key) )
continue;
var info = FFZ.basic_settings[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 ( is_android && info.no_mobile )
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;
});
var f = this,
current_page = this._ffz_basic_settings_page || categories[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'; // collapsable';
menu.setAttribute('data-category', category);
//menu.classList.toggle('collapsed', current_page !== category);
heading.innerHTML = category;
menu.appendChild(heading);
/*menu.addEventListener('click', function() {
if ( ! this.classList.contains('collapsed') )
return;
var t = this,
old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)');
for(var i=0; i < old_selection.length; i++)
old_selection[i].classList.add('collapsed');
f._ffz_basic_settings_page = t.getAttribute('data-category');
t.classList.remove('collapsed');
setTimeout(function(){t.scrollIntoViewIfNeeded()});
});*/
cset.sort(function(a,b) {
var a = a[1],
b = b[1],
at = a.type === "boolean" ? 1 : 2,
bt = b.type === "boolean" ? 1 : 2,
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 = info.type !== "button" && typeof info.get === 'function' ? info.get.bind(this)() : this.settings.get(info.get);
el.className = 'clearfix';
if ( this.has_bttv && info.no_bttv ) {
var label = document.createElement('span'),
help = document.createElement('span');
label.className = 'switch-label';
label.innerHTML = info.name;
help = document.createElement('span');
help.className = 'help';
help.innerHTML = 'Disabled due to incompatibility with BetterTTV.';
el.classList.add('disabled');
el.appendChild(label);
el.appendChild(help);
} else {
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_basic_setting.bind(this, swit, key));
} else if ( info.type === "select" ) {
var select = document.createElement('select'),
label = document.createElement('span');
label.className = 'option-label';
label.innerHTML = info.name;
for(var ok in info.options) {
var op = document.createElement('option');
op.value = JSON.stringify(ok);
if ( val == ok )
op.setAttribute('selected', true);
op.innerHTML = info.options[ok];
select.appendChild(op);
}
select.addEventListener('change', option_basic_setting.bind(this, select, key));
el.appendChild(label);
el.appendChild(select);
} 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);
}
},
render_advanced: function(view, container) {
var settings = {},
categories = [],
is_android = navigator.userAgent.indexOf('Android') !== -1;
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 ( is_android && info.no_mobile )
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;
});
var f = this,
current_page = this._ffz_settings_page || categories[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 collapsable';
menu.setAttribute('data-category', category);
menu.classList.toggle('collapsed', current_page !== category);
heading.innerHTML = category;
menu.appendChild(heading);
menu.addEventListener('click', function() {
if ( ! this.classList.contains('collapsed') )
return;
var t = this,
old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)');
for(var i=0; i < old_selection.length; i++)
old_selection[i].classList.add('collapsed');
f._ffz_settings_page = t.getAttribute('data-category');
t.classList.remove('collapsed');
setTimeout(function(){t.scrollIntoViewIfNeeded()});
});
cset.sort(function(a,b) {
var a = a[1],
b = b[1],
at = a.type === "boolean" ? 1 : 2,
bt = b.type === "boolean" ? 1 : 2,
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 ( this.has_bttv && info.no_bttv ) {
var label = document.createElement('span'),
help = document.createElement('span');
label.className = 'switch-label';
label.innerHTML = info.name;
help = document.createElement('span');
help.className = 'help';
help.innerHTML = 'Disabled due to incompatibility with BetterTTV.';
el.classList.add('disabled');
el.appendChild(label);
el.appendChild(help);
} else {
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 if ( info.type === "select" ) {
var select = document.createElement('select'),
label = document.createElement('span');
label.className = 'option-label';
label.innerHTML = info.name;
for(var ok in info.options) {
var op = document.createElement('option');
op.value = JSON.stringify(ok);
if ( val == ok )
op.setAttribute('selected', true);
op.innerHTML = info.options[ok];
select.appendChild(op);
}
select.addEventListener('change', option_setting.bind(this, select, key));
el.appendChild(label);
el.appendChild(select);
} 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,
wide: true,
sub_menu: true
};
@ -790,7 +610,7 @@ FFZ.prototype._setting_update = function(e) {
if ( info.process_value )
try {
val = info.process_value.bind(this)(val);
val = info.process_value.call(this, val);
} catch(err) {
this.log('Error processing value for setting "' + key + '": ' + err);
return;
@ -799,7 +619,7 @@ FFZ.prototype._setting_update = function(e) {
this.settings[key] = val;
if ( info.on_update )
try {
info.on_update.bind(this)(val, false);
info.on_update.call(this, val, false);
} catch(err) {
this.log('Error running updater for setting "' + key + '": ' + err);
}
@ -825,7 +645,7 @@ FFZ.prototype._setting_load = function(key, default_value) {
}
if ( info && info.process_value )
val = info.process_value.bind(this)(val);
val = info.process_value.call(this, val);
this.settings[key] = val;
return val;
@ -840,13 +660,13 @@ FFZ.prototype._setting_get = function(key) {
}
FFZ.prototype._setting_set = function(key, val) {
FFZ.prototype._setting_set = function(key, val, suppress_log) {
var info = FFZ.settings_info[key],
ls_key = info.storage_key || make_ls(key);
if ( info.process_value )
try {
val = info.process_value.bind(this)(val)
val = info.process_value.call(this, val)
} catch(err) {
this.log('Error processing value for setting "' + key + '": ' + err);
return false;
@ -857,11 +677,12 @@ FFZ.prototype._setting_set = function(key, val) {
var jval = JSON.stringify(val);
localStorage.setItem(ls_key, jval);
if ( ! suppress_log )
this.log('Changed Setting "' + key + '" to: ' + jval);
if ( info.on_update )
try {
info.on_update.bind(this)(val, true);
info.on_update.call(this, val, true);
} catch(err) {
this.log('Error running updater for setting "' + key + '": ' + err);
}
@ -883,7 +704,7 @@ FFZ.prototype._setting_del = function(key) {
if ( info.on_update )
try {
info.on_update.bind(this)(val, true);
info.on_update.call(this, val, true);
} catch(err) {
this.log('Error running updater for setting "' + key + '": ' + err);
}

View file

@ -22,6 +22,7 @@ FFZ.prototype._ws_open = false;
FFZ.prototype._ws_delay = 0;
FFZ.prototype._ws_host_idx = -1;
FFZ.prototype._ws_current_pool = -1;
FFZ.prototype._ws_last_ping = null;
FFZ.prototype._ws_server_offset = null;
@ -166,7 +167,7 @@ FFZ.prototype.ws_create = function() {
// Send the channel(s).
if ( f._cindex ) {
var channel_id = f._cindex.get('controller.id'),
var channel_id = f._cindex.get('controller.model.id'),
hosted_id = f._cindex.get('controller.hostModeTarget.id');
if ( channel_id )
@ -343,7 +344,7 @@ FFZ.prototype.setup_time = function() {
difference = (new_time - last_time) - 5000;
last_time = new_time;
if ( Math.abs(difference) > 250 ) {
if ( Math.abs(difference) > 1000 ) {
f.log("WARNING! Time drift of " + difference + "ms across 5 seconds. Did the local time change?");
f._ws_server_offset = null;
f.ws_ping();
@ -373,7 +374,7 @@ FFZ.prototype._ws_on_pong = function(success, server_time) {
if ( this._ws_ping_time ) {
var rtt = now - this._ws_ping_time,
ping = rtt / 2;
ping = this._ws_last_ping = rtt / 2;
this._ws_ping_time = null;
this._ws_server_offset = (d_now - (server_time + ping));
@ -404,7 +405,7 @@ FFZ.ws_commands.reconnect = function() {
// Socket Close Callbacks
for(var i=0; i < FFZ.ws_on_close.length; i++) {
try {
FFZ.ws_on_close[i].bind(this)();
FFZ.ws_on_close[i].call(this);
} catch(err) {
this.log("Error on Socket Close Callback: " + err);
}

View file

@ -0,0 +1 @@
.chat-line.notification .indicator { display: none !important }

View file

@ -0,0 +1 @@
.chat-line.notification .badge.subscriber { display: none }

View file

@ -5,6 +5,8 @@
/* Invert Some Badges */
body:not(.ffz-dark) .app-main:not(.theatre) .conversation-window .badges .badge:not(.subscriber):not(.ffz-badge-0),
body:not(.ffz-dark) > .chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-0),
body:not(.ffz-dark) > .ember-chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-0),
.app-main:not(.theatre) .chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-0),
.app-main:not(.theatre) .ember-chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-0) {
filter: invert(100%);

View file

@ -1,10 +1,6 @@
/* Regular Alternating Background */
.conversation-chat-lines > div:nth-child(2n+0):before,
.chat-history .chat-line:nth-child(2n+0):before,
.ember-chat .chat-lines > div:nth-child(2n+0) .chat-line:before,
/* ReChat */
.ember-chat.chat-messages > .rechat-chat-line:nth-child(2n+0):before {
.chat-line:nth-child(2n+0):before {
background-color: rgba(0,0,0, 0.1);
}
@ -14,40 +10,32 @@
.ffz-dark .chat-history .chat-line:nth-child(2n+0):before,
.theatre .conversation-chat-lines > div:nth-child(2n+0):before,
.theatre .chat-history .chat-line:nth-child(2n+0):before,
.theatre .ember-chat .chat-lines > div:nth-child(2n+0) .chat-line:before,
.theatre .chat-line:nth-child(2n+0):before,
.dark .chat-history .chat-line:nth-child(2n+0):before,
.force-dark .chat-history .chat-line:nth-child(2n+0):before,
.dark .chat-lines > div:nth-child(2n+0) .chat-line:before,
.force-dark .chat-lines > div:nth-child(2n+0) .chat-line:before,
/* ReChat */
.theatre .chat-lines > .rechat-chat-line:nth-child(2n+0):before,
.dark .chat-lines > .rechat-chat-line:nth-child(2n+0):before,
.force-dark .chat-lines > .rechat-chat-line:nth-child(2n+0):before {
.dark .chat-line:nth-child(2n+0):before,
.force-dark .chat-line:nth-child(2n+0):before {
background-color: rgba(255,255,255, 0.05);
}
/* DEPRECIATED: Whisper Backgrounds */
.ember-chat .chat-line.whisper-line:before {
.chat-line.whisper:before {
background-color: rgba(185, 163, 227, 0.2);
}
.ember-chat .chat-lines > div:nth-child(2n+0) .chat-line.whisper-line:before {
.chat-line.whisper:nth-child(2n+0):before {
background-color: rgba(185, 163, 227, 0.4);
}
.theatre .chat-line.whisper-line:before,
.dark .chat-line.whisper-line:before,
.force-dark .chat-line.whisper-line:before {
.theatre .chat-line.whisper:before,
.dark .chat-line.whisper:before,
.force-dark .chat-line.whisper:before {
background-color: rgba(100, 65, 165, 0.2);
}
.theatre .chat-lines > div:nth-child(2n+0) .chat-line.whisper-line:before,
.dark .chat-lines > div:nth-child(2n+0) .chat-line.whisper-line:before,
.force-dark .chat-lines > div:nth-child(2n+0) .chat-line.whisper-line:before {
.theatre .chat-line.whisper:nth-child(2n+0):before,
.dark .chat-line.whisper:nth-child(2n+0):before,
.force-dark .chat-line.whisper:nth-child(2n+0):before {
background-color: rgba(100, 65, 165, 0.4);
}
@ -111,13 +99,11 @@
/* DEPRECIATED: Mention Backgrounds */
.chat-history .chat-line.ffz-mentioned:before,
.ember-chat .chat-line.ffz-mentioned:before {
.chat-line.ffz-mentioned:before {
background-color: rgba(255,127,127,0.2);
}
.chat-history .chat-line.ffz-mentioned:nth-child(2n+0):before,
.ember-chat .chat-lines > div:nth-child(2n+0) .chat-line.ffz-mentioned:before {
.chat-line.ffz-mentioned:nth-child(2n+0):before {
background-color: rgba(255,127,127, 0.4);
}
@ -131,14 +117,10 @@
background-color: rgba(255,0,0, 0.2) !important;
}
.theatre .chat-lines > div:nth-child(2n+0) .chat-line.ffz-mentioned:before,
.dark .chat-history .chat-line.ffz-mentioned:nth-child(2n+0):before,
.force-dark .chat-history .chat-line.ffz-mentioned:nth-child(2n+0):before,
.dark .chat-lines > div:nth-child(2n+0) .chat-line.ffz-mentioned:before,
.force-dark .chat-lines > div:nth-child(2n+0) .chat-line.ffz-mentioned:before {
.ffz-dark .chat-history .chat-line.ffz-mentioned:nth-child(2n+0):before,
.theatre .chat-line.ffz-mentioned:nth-child(2n+0):before,
.dark .chat-line.ffz-mentioned:nth-child(2n+0):before,
.force-dark .chat-line.ffz-mentioned:nth-child(2n+0):before {
background-color: rgba(255,0,0, 0.3) !important;
}

View file

@ -15,15 +15,14 @@
}
/* Hide First Line */
.conversation-chat-lines > div:first-child:before,
.chat-lines > div:first-child .chat-line:before {
border-top-color: transparent;
.chat-line:first-of-type:before {
border-top-color: transparent !important;
}
/* Hide Last Line */
.conversation-chat-lines > div:last-child:nth-child(odd):before,
.chat-lines > div:last-child:nth-child(odd) .chat-line:before {
border-bottom-color: transparent;
.chat-line:last-of-type:nth-child(odd):before {
border-bottom-color: transparent !important;
}

View file

@ -4,18 +4,13 @@ var FFZ = window.FrankerFaceZ,
helpers,
conv_helpers,
EXPLANATION_TRAIL = '<hr>FFZ is hiding this link because this url shortener is known to be used by Twitch spam bots posting malicious links. Please use caution when visiting shortened links.',
EXPLANATION_WARN = '<hr>This link has been sent to you via a whisper rather than standard chat, and has not been checked or approved of by any moderators or staff members. Please treat this link with caution and do not visit it if you do not trust the sender.',
reg_escape = function(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
},
LINK = /(?:https?:\/\/)?(?:[-a-zA-Z0-9@:%_\+~#=]+\.)+[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#!?&//=]*)/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 + "*"),
LINK = /(?:https?:\/\/)?(?:[-a-zA-Z0-9@:%_\+~#=]+\.)+[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/g,
LINK_SPLIT = /^(?:(https?):\/\/)?(?:(.*?)@)?([^\/:]+)(?::(\d+))?(.*?)(?:\?(.*?))?(?:\#(.*?))?$/,
YOUTUBE_CHECK = /^(?:https?:\/\/)?(?:m\.|www\.)?youtu(?:be\.com|\.be)\/(?:v\/|watch\/|.*?(?:embed|watch).*?v=)?([a-zA-Z0-9\-_]+)$/,
@ -46,91 +41,6 @@ var FFZ = window.FrankerFaceZ,
return '<iframe class="ffz-image-hover' + (extra_class ? ' ' + extra_class : '') + '" allowtransparency="true" src="' + constants.SERVER + 'script/img-proxy.html#' + utils.quote_attr(href) + '"></iframe>';
},
build_link_tooltip = function(href) {
var link_data = this._link_data[href],
tooltip;
if ( link_data && link_data.tooltip )
return link_data.tooltip;
if ( ! link_data )
return "";
if ( link_data.type == "youtube" ) {
tooltip = this.settings.link_image_hover ? image_iframe(link_data.full || href, 'ffz-yt-thumb') : '';
tooltip += "<b>YouTube: " + utils.sanitize(link_data.title) + "</b><hr>";
tooltip += "Channel: " + utils.sanitize(link_data.channel) + " | " + utils.time_to_string(link_data.duration) + "<br>";
tooltip += utils.number_commas(link_data.views||0) + " Views | &#128077; " + utils.number_commas(link_data.likes||0) + " &#128078; " + utils.number_commas(link_data.dislikes||0);
} else if ( link_data.type == "strawpoll" ) {
tooltip = "<b>Strawpoll: " + utils.sanitize(link_data.title) + "</b><hr><table><tbody>";
for(var key in link_data.items) {
var votes = link_data.items[key],
percentage = Math.floor((votes / link_data.total) * 100);
tooltip += '<tr><td style="text-align:left">' + utils.sanitize(key) + '</td><td style="text-align:right">' + utils.number_commas(votes) + "</td></tr>";
}
tooltip += "</tbody></table><hr>Total: " + utils.number_commas(link_data.total);
var fetched = utils.parse_date(link_data.fetched);
if ( fetched ) {
var age = Math.floor((fetched.getTime() - Date.now()) / 1000);
if ( age > 60 )
tooltip += "<br><small>Data was cached " + utils.time_to_string(age) + " ago.</small>";
}
} else if ( link_data.type == "twitch" ) {
tooltip = "<b>Twitch: " + utils.sanitize(link_data.display_name) + "</b><hr>";
var since = utils.parse_date(link_data.since);
if ( since )
tooltip += "Member Since: " + utils.date_string(since) + "<br>";
tooltip += "<nobr>Views: " + utils.number_commas(link_data.views) + "</nobr> | <nobr>Followers: " + utils.number_commas(link_data.followers) + "</nobr>";
} else if ( link_data.type == "twitch_vod" ) {
tooltip = "<b>Twitch " + (link_data.broadcast_type == "highlight" ? "Highlight" : "Broadcast") + ": " + utils.sanitize(link_data.title) + "</b><hr>";
tooltip += "By: " + utils.sanitize(link_data.display_name) + (link_data.game ? " | Playing: " + utils.sanitize(link_data.game) : " | Not Playing") + "<br>";
tooltip += "Views: " + utils.number_commas(link_data.views) + " | " + utils.time_to_string(link_data.length);
} else if ( link_data.type == "twitter" ) {
tooltip = "<b>Tweet By: " + utils.sanitize(link_data.user) + "</b><hr>";
tooltip += utils.sanitize(link_data.tweet);
} else if ( link_data.type == "reputation" ) {
tooltip = (this.settings.link_image_hover && is_image(link_data.full || href, this.settings.image_hover_all_domains)) ? image_iframe(link_data.full || href) : '';
tooltip += '<span style="word-wrap: break-word">' + utils.sanitize(link_data.full.toLowerCase()) + '</span>';
if ( link_data.trust < 50 || link_data.safety < 50 || (link_data.tags && link_data.tags.length > 0) ) {
tooltip += "<hr>";
var had_extra = false;
if ( link_data.trust < 50 || link_data.safety < 50 ) {
link_data.unsafe = true;
tooltip += "<b>Potentially Unsafe Link</b><br>";
tooltip += "Trust: " + link_data.trust + "% | Child Safety: " + link_data.safety + "%";
had_extra = true;
}
if ( link_data.tags && link_data.tags.length > 0 )
tooltip += (had_extra ? "<br>" : "") + "Tags: " + link_data.tags.join(", ");
tooltip += "<br>Data Source: WOT";
}
} else if ( link_data.full ) {
tooltip = (this.settings.link_image_hover && is_image(link_data.full || href, this.settings.image_hover_all_domains)) ? image_iframe(link_data.full || href) : '';
tooltip += '<span style="word-wrap: break-word">' + utils.sanitize(link_data.full.toLowerCase()) + '</span>';
}
if ( ! tooltip )
tooltip = '<span style="word-wrap: break-word">' + utils.sanitize(href.toLowerCase()) + '</span>';
link_data.tooltip = tooltip;
return tooltip;
},
load_link_data = function(href, success, data) {
if ( ! success )
return;
@ -138,24 +48,12 @@ var FFZ = window.FrankerFaceZ,
this._link_data[href] = data;
//data.unsafe = false;
var tooltip = build_link_tooltip.bind(this)(href), links,
no_trail = href.charAt(href.length-1) == "/" ? href.substr(0, href.length-1) : null;
if ( no_trail )
links = document.querySelectorAll('span.message a[href="' + href + '"], span.message a[href="' + no_trail + '"], span.message a[data-url="' + href + '"], span.message a[data-url="' + no_trail + '"]');
else
links = document.querySelectorAll('span.message a[href="' + href + '"], span.message a[data-url="' + href + '"]');
if ( ! this.settings.link_info )
return;
for(var x=0; x < links.length; x++) {
// If this link is unsafe, add the unsafe-link class to all instances of the link.
if ( data.unsafe )
links[x].classList.add('unsafe-link');
if ( ! links[x].classList.contains('deleted-link') )
links[x].title = tooltip;
}
jQuery('a.chat-link[data-url="' + href + '"]').addClass('unsafe-link');
};
@ -167,7 +65,7 @@ FFZ.src_to_id = function(src) {
var match = /\/emoticons\/v1\/(\d+)\/1\.0/.exec(src),
id = match ? parseInt(match[1]) : null;
if ( id === NaN )
if ( Number.isNaN(id) )
id = null;
FFZ.SRC_IDS[src] = id;
@ -181,9 +79,10 @@ FFZ._emote_mirror_swap = function(img) {
return;
img.setAttribute('data-alt-attempts', attempts + 1);
var id = img.getAttribute('data-emote');
var id = img.getAttribute('data-emote'),
src = '//' + img.src.split('//')[1];
if ( img.src.substr(0, constants.TWITCH_BASE.length) === constants.TWITCH_BASE ) {
if ( src.substr(0, constants.TWITCH_BASE.length) === constants.TWITCH_BASE ) {
img.src = constants.EMOTE_MIRROR_BASE + id + ".png";
img.srcset = "";
} else {
@ -283,6 +182,9 @@ FFZ.prototype.setup_tokenization = function() {
var show_deleted = f.settings.show_deleted_links;
return _.chain(tokens).map(function(token) {
if ( token.type === "text" )
token = token.text;
if ( ! _.isString(token) )
return token;
@ -293,10 +195,21 @@ FFZ.prototype.setup_tokenization = function() {
return _.zip(
token.split(LINK),
_.map(matches, function(e) {
var long = e.length > 255;
if ( ! show_deleted && (delete_links || long) )
return {isLink: true, isDeleted: true, isLong: long, href: e};
return {isLink: true, href: e};
var long = e.length > 255,
out = {
type: "link",
length: e.length,
isDeleted: ! show_deleted && (delete_links || long),
isLong: long,
isMailTo: e.indexOf("@") > -1 && (-1 === e.indexOf("/") || e.indexOf("@") < e.indexOf("/")),
text: e,
link: e
};
if ( ! out.isMailTo && ! e.match(/^(?:https?:\/\/)/) )
out.link = "http://" + e;
return out;
})
);
}).flatten().compact().value();
@ -325,8 +238,6 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
this._twitch_set_to_channel[33] = "--turbo-faces--";
this._twitch_set_to_channel[42] = "--turbo-faces--";
this._reset_tooltips(true);
}).fail(function(data) {
if ( data.status === 404 )
return;
@ -338,6 +249,127 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
}
// ---------------------
// Tooltip Rendering
// ---------------------
FFZ.prototype.render_tooltip = function(el) {
var f = this,
func = function() {
if ( this.classList.contains('emoticon') ) {
var preview_url, width=0, height=0, image, set_id, emote, emote_set,
emote_id = this.getAttribute('data-ffz-emote');
if ( emote_id ) {
set_id = this.getAttribute('data-ffz-set');
emote_set = f.emote_sets[set_id];
emote = emote_set && emote_set.emoticons[emote_id];
if ( emote ) {
var owner = emote.owner,
title = emote_set.title || "Global",
source = emote_set.source || "FFZ";
if ( f.settings.emote_image_hover ) {
if ( emote.urls[4] ) {
height = emote.height * 4;
width = emote.width * 4;
preview_url = emote.urls[4];
} else if ( emote.urls[2] ) {
height = emote.height * 2;
width = emote.width * 2;
}
if ( width > 186 )
height *= 186 / width;
height = Math.min(186, height);
} else
preview_url = null;
//image = preview_url ? `<img style="height:${height}px" class="emoticon ffz-image-hover" src="${preview_url}?_=preview">` : '';
image = preview_url ? '<img style="height:' + height + 'px" class="emoticon ffz-image-hover" src="' + preview_url + '"?_=preview">' : '';
return image + 'Emoticon: ' + (emote.hidden ? '???' : emote.name) + '<br>' + source + ' ' + title + (owner ? '<br>By: ' + owner.display_name : '');
//return `${image}Emoticon: ${emote.hidden ? '???' : emote.name}<br>${source} ${title}${owner ? '<br>By: ' + owner.display_name : ""}`;
}
}
emote_id = this.getAttribute('data-emote');
if ( emote_id ) {
set_id = f._twitch_emote_to_set[emote_id];
emote_set = set_id && f._twitch_set_to_channel[set_id];
var set_type = "Channel";
preview_url = f.settings.emote_image_hover && (constants.TWITCH_BASE + emote_id + '/3.0');
//image = preview_url ? `<img style="height:112px" class="emoticon ffz-image-hover" src="${preview_url}?_=preview">` : '';
image = preview_url ? '<img style="height:112px" class="emoticon ffz-image-hover" src="' + preview_url + '"?_=preview">' : '';
if ( emote_set === "--global--" ) {
emote_set = "Twitch Global";
set_type = null;
} else if ( emote_set === "--twitch-turbo--" || emote_set === "turbo" || emote_set === "--turbo-faces--" ) {
emote_set = "Twitch Turbo";
set_type = null;
}
if ( this.classList.contains('ffz-tooltip-no-credit') )
return image + this.alt;
else
return image + 'Emoticon: ' + this.alt + '<br>' + (set_type ? set_type + ': ' : '') + emote_set;
//return `${image}Emoticon: ${this.alt}<br>${set_type ? set_type + ": " : ""}${emote_set}`;
}
emote_id = this.getAttribute('data-ffz-emoji');
if ( emote_id ) {
emote = f.emoji_data[emote_id];
var src = emote && (f.settings.parse_emoji === 3 ? emote.one_src : (f.settings.parse_emoji === 2 ? emote.noto_src : emote.tw_src));
preview_url = f.settings.emote_image_hover && src;
//image = preview_url ? `<img style="height:72px" class="emoticon ffz-image-hover" src="${preview_url}">` : '';
image = preview_url ? '<img style="height:72px" class="emoticon ffz-image-hover" src="' + preview_url + '"?_=preview">' : '';
return image + "Emoji: " + this.alt + '<br>Name: ' + emote.name + (emote.short_name ? '<br>Short Name :' + emote.short_name + ':' : '') + (emote.cat ? '<br>Category: ' + utils.sanitize(constants.EMOJI_CATEGORIES[emote.cat] || emote.cat) : '');
//return `${image}Emoji: ${this.alt}<br>Name: ${emote.name}${emote.short_name ? '<br>Short Name: :' + emote.short_name + ':' : ''}`;
}
} else if ( this.classList.contains('email-link') ) {
var url = this.getAttribute("data-url");
return url ? "E-Mail " + url.substr(7) : '';
} else if ( this.classList.contains('chat-link') ) {
// TODO: A lot of shit. Lookup data.
var url = this.getAttribute("data-url"),
text = '';
if ( ! url )
return;
if ( f.settings.link_image_hover && is_image(url, f.settings.image_hover_all_domains) )
preview_url = url;
else
preview_url = null;
image = preview_url ? image_iframe(url) : '';
// If it's not a deleted link, don't waste time showing the URL in the tooltip.
if ( this.classList.contains('deleted-link') )
text = url;
if ( this.classList.contains('warn-link') )
text += EXPLANATION_WARN;
return image + text; //`${image}${text}`;
}
f.log("Unable to Build Tooltip For: " + this.className, this);
return "";
};
return el ? func(el) : func;
};
// ---------------------
// Tokenization
// ---------------------
@ -364,9 +396,6 @@ FFZ.prototype.tokenize_conversation_line = function(message, prevent_notificatio
if ( helpers && helpers.emoticonizeMessage && emotes )
tokens = helpers.emoticonizeMessage(tokens, emotes);
if ( this.settings.replace_bad_emotes )
tokens = this.tokenize_replace_emotes(tokens);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
tokens = this.tokenize_emotes(from_user, undefined, tokens, from_me);
@ -392,13 +421,12 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
if ( msgObject.cachedTokens )
return msgObject.cachedTokens;
var msg = msgObject.message || msgObject.get('body'),
var msg = msgObject.get && (msgObject.get('message') || msgObject.get('body')) || msgObject.message,
room_id = msgObject.get && msgObject.get('room') || msgObject.room,
from_user = msgObject.get && msgObject.get('from') || msgObject.from,
user = this.get_user(),
room_id = msgObject.room,
from_user = msgObject.from,
from_me = user && from_user === user.login,
emotes = msgObject.tags && msgObject.tags.emotes,
emotes = msgObject.get && msgObject.get('tags.emotes') || msgObject.tags && msgObject.tags.emotes,
tokens = [msg];
// Standard tokenization
@ -421,9 +449,6 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
if ( helpers && helpers.emoticonizeMessage )
tokens = helpers.emoticonizeMessage(tokens, emotes);
if ( this.settings.replace_bad_emotes )
tokens = this.tokenize_replace_emotes(tokens);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
tokens = this.tokenize_emotes(from_user, room_id, tokens, from_me);
@ -442,8 +467,10 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
tokens = this.tokenize_mentions(tokens);
for(var i=0; i < tokens.length; i++) {
var token = tokens[i];
if ( _.isString(token) || ! token.mentionedUser || token.own )
var token = tokens[i],
is_mention = token.type === "mention";
if ( ! is_mention || token.isOwnMessage )
continue;
// We have a mention!
@ -535,11 +562,8 @@ FFZ.prototype.tokenize_line = function(user, room, message, no_emotes, no_emoji)
message = helpers.mentionizeMessage(message, u.login, user === u.login);
}
if ( ! no_emotes ) {
if ( ! no_emotes )
message = this.tokenize_emotes(user, room, message);
if ( this.settings.replace_bad_emotes )
message = this.tokenize_replace_emotes(message);
}
if ( this.settings.parse_emoji && ! no_emoji )
message = this.tokenize_emoji(message);
@ -548,153 +572,113 @@ FFZ.prototype.tokenize_line = function(user, room, message, no_emotes, no_emoji)
}
FFZ.prototype.render_tokens = function(tokens, render_links) {
FFZ.prototype.render_tokens = function(tokens, render_links, warn_links) {
var f = this;
return _.map(tokens, function(token) {
if ( ! token )
return "";
if ( token.hidden )
return "";
else if ( token.isRaw )
else if ( token.type === "raw" )
return token.html;
else if ( token.emoticonSrc ) {
var tooltip, src = token.emoticonSrc, srcset, cls, extra;
else if ( token.type === "emoticon" ) {
var src = token.imgSrc, srcset, cls, extra;
if ( token.ffzEmote ) {
var emote_set = f.emote_sets && f.emote_sets[token.ffzEmoteSet],
emote = emote_set && emote_set.emoticons && emote_set.emoticons[token.ffzEmote];
tooltip = emote ? f._emote_tooltip(emote) : token.altText;
srcset = emote ? emote.srcSet : token.srcSet;
extra = (emote ? ' data-ffz-emote="' + emote.id + '"' : '') + (emote_set ? ' data-ffz-set="' + emote_set.id + '"' : '');
//extra = (emote ? ` data-ffz-emote="${emote.id}"` : '') + (emote_set ? ` data-ffz-set="${emote_set.id}"` : '');
extra = (emote ? ' data-ffz-emote="' + emote.id + '"' : '') + (emote_set ? ' data-ffz-set="' + emote_set.id + '"' : '')
} else if ( token.ffzEmoji ) {
var eid = token.ffzEmoji,
emoji = f.emoji_data && f.emoji_data[eid],
setting = f.settings.parse_emoji,
image = '';
if ( setting === 0 || (setting === 1 && ! emoji.tw) || (setting === 2 && ! emoji.noto) )
var setting = f.settings.parse_emoji;
if ( setting === 0 || (setting === 1 && ! token.tw) || (setting === 2 && ! token.noto) || (setting === 3 && ! token.one) )
return token.altText;
src = setting === 2 ? token.noto_src : token.tw_src;
if ( emoji && f.settings.emote_image_hover )
image = '<img class="emoticon ffz-image-hover" src="' + src + '">';
tooltip = emoji ? image + "Emoji: " + token.altText + "<br>Name: " + emoji.name + (emoji.short_name ? "<br>Short Name: :" + emoji.short_name + ":" : "") : token.altText;
extra = ' data-ffz-emoji="' + eid + '" height="18px"';
src = setting === 3 ? token.one_src : (setting === 2 ? token.noto_src : token.tw_src);
//extra = ` data-ffz-emoji="${token.ffzEmoji}" height="18px"`;
extra = ' data-ffz-emoji="' + token.ffzEmoji + '" height="18px"';
cls = ' emoji';
} else {
var id = token.replacedId || FFZ.src_to_id(token.emoticonSrc),
data = id && f._twitch_emotes && f._twitch_emotes[id];
var id = FFZ.src_to_id(src),
replacement = f.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[id];
if ( data )
tooltip = data.tooltip ? data.tooltip : utils.build_tooltip.bind(f)(id, false, token.altText);
else {
try {
var set_id = f._twitch_emote_to_set[id];
if ( set_id ) {
tooltip = utils.load_emote_data.bind(f)(id, token.altText, true, {
code: token.altText,
id: id,
set: f._twitch_set_to_channel[set_id],
set_id: set_id
});
} else {
tooltip = f._twitch_emotes[id] = token.altText;
f.ws_send("twitch_emote", id, utils.load_emote_data.bind(f, id, token.altText));
}
} catch(err) {
f.error("Error Generating Emote Tooltip: " + err);
}
}
//extra = ` data-emote="${id}" onerror="FrankerFaceZ._emote_mirror_swap(this)"`;
extra = ' data-emote="' + id + '" onerror="FrankerFaceZ._emote_mirror_swap(this)"';
//var mirror_url = utils.quote_attr(constants.EMOTE_MIRROR_BASE + id + '.png');
extra = ' data-emote="' + id + '" onerror="FrankerFaceZ._emote_mirror_swap(this)"'; // Disable error checking for now.
if ( ! constants.EMOTE_REPLACEMENTS[id] )
if ( replacement ) {
src = constants.EMOTE_REPLACEMENT_BASE + replacement;
srcset = '';
} else
srcset = utils.build_srcset(id);
}
return '<img class="emoticon html-tooltip' + (cls||"") + '"' + (extra||"") + ' src="' + utils.quote_attr(src) + '" ' + (srcset ? 'srcset="' + utils.quote_attr(srcset) + '" ' : '') + 'alt="' + utils.quote_attr(token.altText) + '" title="' + utils.quote_attr(tooltip) + '">';
//return `<img class="emoticon ffz-tooltip${cls||''}"${extra||''} src="${utils.quote_attr(src)}"${srcset ? ' srcset="' + utils.quote_attr(srcset) + '"' : ''} alt="${utils.quote_attr(token.altText)}">`;
return '<img class="emoticon ffz-tooltip' + (cls||'') + '"' + (extra||'') + ' src="' + utils.quote_attr(src) + '"' + (srcset ? ' srcset="' + utils.quote_attr(srcset) + '"' : '') + ' alt="' + utils.quote_attr(token.altText) + '">';
}
else if ( token.isLink ) {
var text = token.title || (token.isLong && '<long link>') || (token.isShortened && '<shortened link>') || (token.isDeleted && '<deleted link>') || token.href;
else if ( token.type === "link" ) {
var text = token.title || (token.isLong && '<long link>') || (token.isDeleted && '<deleted link>') || (warn_links && '<whispered link>') || token.text;
if ( ! render_links && render_links !== undefined )
return utils.sanitize(text);
var href = token.href,
tooltip, cls = '',
var href = token.link || token.text,
cls = '';
ind_at = href.indexOf("@"),
ind_sl = href.indexOf("/");
if ( ind_at !== -1 && (ind_sl === -1 || ind_at < ind_sl) ) {
if ( token.isMailTo ) {
// E-Mail Link
cls = 'email-link';
if ( f.settings.link_info ) {
cls += ' tooltip';
tooltip = 'E-Mail ' + href;
}
href = 'mailto:' + href;
} else {
// Web Link
if ( ! href.match(/^https?:\/\//) )
href = 'http://' + href;
cls = 'chat-link';
if ( f.settings.link_info ) {
cls = 'html-tooltip';
var data = f._link_data && f._link_data[href];
if ( data ) {
tooltip = data.tooltip;
if ( data.unsafe )
cls += ' unsafe-link';
} else {
if (!( f._link_data && f._link_data[href] )) {
f._link_data = f._link_data || {};
f._link_data[href] = true;
f.ws_send("get_link", href, load_link_data.bind(f, href));
if ( f.settings.link_image_hover && is_image(href, f.settings.image_hover_all_domains) )
tooltip = image_iframe(href);
}
} else if ( f.settings.link_image_hover ) {
cls = 'html-tooltip';
if ( is_image(href, f.settings.image_hover_all_domains) )
tooltip = image_iframe(href);
}
}
}
// Deleted Links
var actual_href = href;
if ( token.isShortened ) {
cls = 'shortened-link deleted-link ' + cls;
tooltip = utils.sanitize(token.href) + EXPLANATION_TRAIL;
href = '#';
} else if ( token.isDeleted ) {
if ( token.isDeleted ) {
cls = 'deleted-link ' + cls;
tooltip = utils.sanitize(token.censoredHref || token.href);
href = '#';
} else if ( warn_links ) {
cls = 'warn-link deleted-link ' + cls;
href = '#';
}
return '<a class="' + cls + '" data-original-url="' + utils.quote_attr(token.href) + '" data-url="' + utils.quote_attr(actual_href) + '" href="' + utils.quote_attr(href || '#') + '" title="' + utils.quote_attr(tooltip || '') + '" target="_blank">' + utils.sanitize(text) + '</a>';
//return `<a class="ffz-tooltip ${cls}" data-text="${utils.quote_attr(token.text)}" data-url="${utils.quote_attr(actual_href)}" href="${utils.quote_attr(href||'#')}" target="_blank">${utils.sanitize(text)}</a>`;
return '<a class="ffz-tooltip' + (cls ? ' ' + cls : '') + '" data-text="' + utils.quote_attr(token.text) + '" data-url="' + utils.quote_attr(actual_href) + '" href="' + utils.quote_attr(href||'#') + '" target="_blank">' + utils.sanitize(text) + '</a>';
}
else if ( token.mentionedUser )
return '<span class="' + (token.own ? "mentioning" : "mentioned") + '">' + utils.sanitize(token.mentionedUser) + "</span>";
else if ( token.type === "deleted" )
return '<span class="deleted-word tooltip" title="' + utils.quote_attr(token.text) + '" data-text="' + utils.sanitize(token.text) + '">&times;&times;&times;</span>';
//return `<span class="deleted-word tooltip" title="${utils.quote_attr(token.text)}" data-text="${utils.sanitize(token.text)}">&times;&times;&times;</span>`;
else if ( token.deletedLink )
else if ( token.type === "mention" )
return '<span class="' + (token.isOwnMessage ? 'mentioning' : 'mentioned') + '">' + utils.sanitize(token.user) + '</span>';
//return `<span class="${token.isOwnMessage ? 'mentioning' : 'mentioned'}">${utils.sanitize(token.user)}</span>`;
else if ( token.deletedLink || token.text )
return utils.sanitize(token.text);
else if ( typeof token !== "string" )
return '<b class="html-tooltip" title="<div style=&quot;text-align:left&quot;>' + utils.quote_attr(JSON.stringify(token,null,2)) + '</div>">[invalid token]</b>';
//return `<b class="html-tooltip" title="<div style=&quot;text-align:left&quot;>${utils.quote_attr(JSON.stringify(token,null,2))}</div>">[invalid token]</b>`;
return utils.sanitize(token);
}).join("");
}
@ -704,170 +688,80 @@ FFZ.prototype.render_tokens = function(tokens, render_links) {
// Emoticon Processing
// ---------------------
FFZ.prototype.tokenize_replace_emotes = function(tokens) {
// Replace bad Twitch emoticons with custom emoticons.
var f = this;
FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
"use strict";
if ( _.isString(tokens) )
var sets = this.getEmotes(user, room),
emotes = {},
emote,
new_tokens = [];
if ( ! tokens || ! tokens.length || ! sets || ! sets.length )
return tokens;
// Build an object with all of our emotes.
for(var i=0; i < sets.length; i++) {
var emote_set = this.emote_sets[sets[i]];
if ( emote_set && emote_set.emoticons )
for(var emote_id in emote_set.emoticons) {
emote = emote_set.emoticons[emote_id];
if ( ! emotes[emote.name] )
emotes[emote.name] = emote;
}
}
if ( typeof tokens === "string" )
tokens = [tokens];
for(var i=0; i < tokens.length; i++) {
for(var i=0, l=tokens.length; i < l; i++) {
var token = tokens[i];
if ( ! token || ! token.emoticonSrc || token.ffzEmote )
if ( ! token )
continue;
// Check for a few specific emoticon IDs.
var emote_id = FFZ.src_to_id(token.emoticonSrc);
if ( constants.EMOTE_REPLACEMENTS.hasOwnProperty(emote_id) ) {
token.replacedId = emote_id;
token.emoticonSrc = constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote_id];
}
if ( typeof token !== "string" )
if ( token.type === "text" )
token = token.text;
else {
new_tokens.push(token);
continue;
}
return tokens;
// Split the token!
var segments = token.split(' '),
text = [], segment;
for(var x=0,y=segments.length; x < y; x++) {
segment = segments[x];
emote = emotes[segment];
if ( emote ) {
if ( text.length ) {
// We have pending text. Join it together, with an extra space
// on the end for good measure.
new_tokens.push({type: "text", text: text.join(' ') + ' '});
text = []
}
FFZ.prototype.tokenize_title_emotes = function(tokens) {
var f = this,
Channel = App.__container__.lookup('controller:channel'),
possible = Channel && Channel.get('product.emoticons'),
emotes = [];
if ( _.isString(tokens) )
tokens = [tokens];
// Build a list of emotes that match.
_.each(_.union(f.__twitch_global_emotes||[], possible), function(emote) {
if ( ! emote || emote.state === "inactive" )
return;
var r = new RegExp("\\b" + emote.regex + "\\b");
_.any(tokens, function(token) {
return _.isString(token) && token.match(r);
}) && emotes.push(emote);
});
// Include Global Emotes~!
if ( f.__twitch_global_emotes === undefined || f.__twitch_global_emotes === null ) {
f.__twitch_global_emotes = false;
Twitch.api.get("chat/emoticon_images", {emotesets:"0,42"}).done(function(data) {
if ( ! data || ! data.emoticon_sets || ! data.emoticon_sets[0] ) {
f.__twitch_global_emotes = [];
return;
}
var emotes = f.__twitch_global_emotes = [];
data = data.emoticon_sets[0];
for(var i=0; i < data.length; i++) {
var em = data[i];
emotes.push({regex: em.code, url: utils.TWITCH_BASE + em.id + "/1.0"});
}
if ( f._cindex )
f._cindex.ffzFixTitle();
}).fail(function() {
setTimeout(function(){f.__twitch_global_emotes = null;},5000);
});;
}
if ( ! emotes.length )
return tokens;
if ( typeof tokens === "string" )
tokens = [tokens];
_.each(emotes, function(emote) {
var eo = {isEmoticon:true, srcSet: emote.url + ' 1x', emoticonSrc: emote.url, altText: emote.regex};
var r = new RegExp("\\b" + emote.regex + "\\b");
tokens = _.compact(_.flatten(_.map(tokens, function(token) {
if ( _.isObject(token) )
return token;
var tbits = token.split(r), bits = [];
tbits.forEach(function(val, ind) {
bits.push(val);
if ( ind !== tbits.length - 1 )
bits.push(eo);
});
return bits;
})));
});
return tokens;
}
FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
var f = this;
// Get our sets.
var sets = this.getEmotes(user, room),
emotes = [];
// Build a list of emotes that match.
_.each(sets, function(set_id) {
var set = f.emote_sets[set_id];
if ( ! set )
return;
_.each(set.emoticons, function(emote) {
_.any(tokens, function(token) {
return _.isString(token) && token.match(emote.regex);
}) && emotes.push(emote);
});
});
// Don't bother proceeding if we have no emotes.
if ( ! emotes.length )
return tokens;
// Now that we have all the matching tokens, do crazy stuff.
if ( typeof tokens === "string" )
tokens = [tokens];
// This is weird stuff I basically copied from the old Twitch code.
// Here, for each emote, we split apart every text token and we
// put it back together with the matching bits of text replaced
// with an object telling Twitch's line template how to render the
// emoticon.
_.each(emotes, function(emote) {
var eo = {
srcSet: emote.srcSet,
emoticonSrc: emote.urls[1],
ffzEmote: emote.id,
ffzEmoteSet: emote.set_id,
altText: (emote.hidden ? "???" : emote.name)
};
tokens = _.compact(_.flatten(_.map(tokens, function(token) {
if ( _.isObject(token) )
return token;
var tbits = token.split(emote.regex), bits = [];
while(tbits.length) {
var bit = tbits.shift();
if ( tbits.length ) {
bit += tbits.shift();
if ( bit )
bits.push(bit);
tbits.shift();
bits.push(eo);
// Push this emote to the tokens.
new_tokens.push(emote.token);
if ( do_report && room )
f.add_usage(room, emote);
this.add_usage(room, emote);
// Finally, push an empty string to text so that this emote gets spaced.
text.push('');
} else
bits.push(bit);
text.push(segment);
}
return bits;
})));
});
return tokens;
// Add any left over text from this segment.
if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
new_tokens.push({type: "text", text: text.join(' ')});
}
return new_tokens;
}
@ -876,48 +770,54 @@ FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
// ---------------------
FFZ.prototype.tokenize_emoji = function(tokens) {
"use strict";
if ( ! tokens || ! tokens.length || ! this.emoji_data )
return tokens;
if ( typeof tokens === "string" )
tokens = [tokens];
if ( ! this.emoji_data )
return tokens;
var new_tokens = [];
var f = this;
for(var i=0, l=tokens.length; i < l; i++) {
var token = tokens[i];
if ( ! token )
continue;
return _.compact(_.flatten(_.map(tokens, function(token) {
if ( _.isObject(token) )
return token;
var tbits = token.split(constants.EMOJI_REGEX), bits = [];
while(tbits.length) {
// Deal with the unmatched string first.
var bit = tbits.shift();
bit && bits.push(bit);
if ( tbits.length ) {
// We have an emoji too, so let's handle that.
var match = tbits.shift(),
variant = tbits.shift();
if ( variant === '\uFE0E' ) {
// Text Variant
bits.push(match);
} else {
// Find the right image~!
var eid = utils.emoji_to_codepoint(match, variant),
data = f.emoji_data[eid];
if ( data )
bits.push(data.token);
else
bits.push(match + (variant || ""));
}
}
if ( typeof token !== "string" )
if ( token.type === "text" )
token = token.text;
else {
new_tokens.push(token);
continue;
}
return bits;
})));
var segments = token.split(constants.EMOJI_REGEX),
text = null;
while(segments.length) {
text = (text || '') + segments.shift();
if ( segments.length ) {
var match = segments.shift(),
eid = utils.emoji_to_codepoint(match),
data = this.emoji_data[eid];
if ( data ) {
if ( text && text.length )
new_tokens.push({type: "text", text: text});
new_tokens.push(data.token);
text = null;
} else
text = (text || '') + match;
}
}
if ( text && text.length )
new_tokens.push({type: "text", text: text});
}
return new_tokens;
}
@ -942,7 +842,7 @@ FFZ._words_to_regex = function(list) {
reg += (reg ? "|" : "") + reg_escape(list[i]);
}
regex = FFZ._regex_cache[list] = new RegExp("(^|.*?" + SEPARATORS + ")(" + reg + ")(?=$|" + SEPARATORS + ")", "ig");
regex = FFZ._regex_cache[list] = new RegExp("(^|.*?" + constants.SEPARATORS + ")(" + reg + ")(?=$|" + constants.SEPARATORS + ")", "ig");
}
return regex;
@ -962,12 +862,10 @@ FFZ.prototype.tokenize_mentions = function(tokens) {
for(var i=0; i < tokens.length; i++) {
var token = tokens[i];
if ( ! _.isString(token) ) {
new_tokens.push(token);
continue;
}
if ( token.type === "text" )
token = token.text;
if ( ! token.match(regex) ) {
if ( ! _.isString(token) || ! token.match(regex) ) {
new_tokens.push(token);
continue;
}
@ -975,8 +873,10 @@ FFZ.prototype.tokenize_mentions = function(tokens) {
token = token.replace(regex, function(all, prefix, match) {
new_tokens.push(prefix);
new_tokens.push({
mentionedUser: match,
own: false
type: "mention",
length: match.length,
user: match,
isOwnMessage: false,
});
return "";
@ -998,38 +898,25 @@ FFZ.prototype._deleted_link_click = function(e) {
if ( ! this.classList.contains("deleted-link") )
return true;
// Stop from Navigating
e.preventDefault();
// Get the URL
var href = this.getAttribute('data-url'),
link = this.getAttribute('data-original-url') || href,
var link = this.getAttribute('data-url'),
text = this.getAttribute('data-text') || link,
f = FrankerFaceZ.get();
// 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;
this.classList.remove('warn-link');
// Set up the Link
this.href = href;
this.target = "_new";
this.textContent = link;
this.href = link;
this.target = "_blank";
this.textContent = text;
// Now, check for a tooltip.
var link_data = f._link_data[href];
if ( link_data && typeof link_data != "boolean" ) {
this.title = link_data.tooltip;
if ( link_data.unsafe )
this.classList.add('unsafe-link');
}
// Stop from Navigating
e.preventDefault();
// Refresh tipsy.
jQuery(this).trigger('mouseout').trigger('mouseover');
}

View file

@ -1,18 +1,26 @@
var FFZ = window.FrankerFaceZ,
constants = require("../constants");
constants = require("../constants"),
utils = require("../utils"),
createElement = document.createElement.bind(document),
NICE_DESCRIPTION = {
"cluster": null,
"manifest_cluster": null,
"user_ip": null
};
// -------------------
// Initialization
// -------------------
FFZ.prototype._has_news = false;
/*FFZ.prototype._has_news = false;
FFZ.prototype._news_id = 0;
FFZ.prototype.check_news = function(tries) {
jQuery.ajax(constants.SERVER + "script/news.json", {cache: false, dataType: "json", context: this})
.done(function(data) {
FFZ.ws_commands.update_news.bind(this)(data.id);
FFZ.ws_commands.update_news.call(this, data.id);
}).fail(function(data) {
if ( data.status === 404 )
return;
@ -26,7 +34,7 @@ FFZ.prototype.check_news = function(tries) {
FFZ.ws_commands.update_news = function(version) {
var old_version = parseInt(localStorage.ffzLastNewsId || "0") || 0;
if ( ! old_version || old_version === NaN || old_version < 0 )
if ( ! old_version || Number.isNaN(old_version) || old_version < 0 )
old_version = 0;
if ( version <= old_version ) {
@ -37,73 +45,75 @@ FFZ.ws_commands.update_news = function(version) {
this._has_news = true;
this._news_id = version;
this.update_ui_link();
}
}*/
// -------------------
// About Page
// -------------------
FFZ.menu_pages.about_changelog = {
name: "Changelog",
visible: false,
wide: true,
render: function(view, container) {
var heading = document.createElement('div');
var include_html = function(heading_text, filename) {
return function(view, container) {
var heading = createElement('div');
heading.className = 'chat-menu-content center';
heading.innerHTML = '<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">change log</div>';
heading.innerHTML = '<h1>FrankerFaceZ</h1>' + (heading_text ? '<div class="ffz-about-subheading">' + heading_text + '</div>' : '');
jQuery.ajax(constants.SERVER + "script/changelog.html", {cache: false, context: this})
jQuery.ajax(filename, {cache: false, context: this})
.done(function(data) {
container.appendChild(heading);
container.innerHTML += data;
jQuery('#ffz-old-news-button', container).on('click', function() {
jQuery(this).remove();
jQuery('#ffz-old-news', container).css('display', 'block');
});
}).fail(function(data) {
var content = document.createElement('div');
var content = createElement('div');
content.className = 'chat-menu-content menu-side-padding';
content.textContent = 'There was an error loading the change log from the server.';
content.textContent = 'There was an error loading this page from the server.';
container.appendChild(heading);
container.appendChild(content);
});
}
};
},
render_news = include_html("news", constants.SERVER + "script/news.html");
FFZ.menu_pages.about_news = {
name: "News",
visible: false,
wide: true,
var update_player_stats = function(player, container) {
if ( ! document.querySelector('.ffz-ui-sub-menu-page[data-page="debugging"]') || ! player.getVideoInfo )
return;
render: function(view, container) {
// Handle the news state.
if ( this._has_news ) {
this._has_news = false;
localStorage.ffzLastNewsId = this._news_id;
this.update_ui_link();
setTimeout(update_player_stats.bind(this, player, container), 1000);
var player_data;
try {
player_data = player.getVideoInfo();
} catch(err) { }
if ( ! player_data )
return;
var sorted_keys = Object.keys(player_data).sort();
for(var i=0; i < sorted_keys.length; i++) {
var key = sorted_keys[i],
data = player_data[key],
line = container.querySelector('li[data-property="' + key + '"]');
if ( ! line ) {
var desc = NICE_DESCRIPTION.hasOwnProperty(key) ? NICE_DESCRIPTION[key] : key;
if ( ! desc )
continue;
line = createElement('li');
line.setAttribute('data-property', key);
line.innerHTML = desc + '<span></span>';
container.appendChild(line);
}
var heading = document.createElement('div');
heading.className = 'chat-menu-content center';
heading.innerHTML = '<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">announcements and news</div>';
jQuery.ajax(constants.SERVER + "script/news.html", {cache: false, context: this})
.done(function(data) {
container.appendChild(heading);
container.innerHTML += data;
}).fail(function(data) {
var content = document.createElement('div');
content.className = 'chat-menu-content menu-side-padding';
content.textContent = 'There was an error loading the announcements from the server.';
container.appendChild(heading);
container.appendChild(content);
});
line.querySelector('span').textContent = data;
}
};
@ -113,11 +123,13 @@ FFZ.menu_pages.about = {
icon: constants.HEART,
sort_order: 100000,
pages: {
about: {
name: "About",
render: function(view, container, inner, menu) {
var room = this.rooms[view.get("context.currentRoom.id")],
has_emotes = false, f = this;
// Check for emoticons.
if ( room && room.set ) {
var set = this.emote_sets[room.set];
if ( set && set.count > 0 )
@ -125,7 +137,7 @@ FFZ.menu_pages.about = {
}
// Heading
var heading = document.createElement('div'),
var heading = createElement('div'),
content = '';
content += "<h1>FrankerFaceZ</h1>";
@ -149,24 +161,11 @@ FFZ.menu_pages.about = {
// Button Stuff
var btn_container = document.createElement('div'),
ad_button = document.createElement('a'),
news_button = document.createElement('a'),
donate_button = document.createElement('a'),
message = "To use custom emoticons in " + (has_emotes ? "this channel" : "tons of channels") + ", get FrankerFaceZ from http://www.frankerfacez.com";
// News
/*news_button.className = 'button ffz-news';
news_button.innerHTML = 'Announcements and News';
news_button.addEventListener('click', function() {
f._ui_change_page(view, inner, menu, container, 'about_news');
});
btn_container.appendChild(news_button);
btn_container.className = 'chat-menu-content center';
container.appendChild(btn_container);
btn_container = document.createElement('div');*/
var btn_container = createElement('div'),
ad_button = createElement('a'),
news_button = createElement('a'),
donate_button = createElement('a'),
message = "To use custom emoticons in " + (has_emotes ? "this channel" : "tons of channels") + ", get FrankerFaceZ from https://www.frankerfacez.com";
// Advertising
@ -190,22 +189,21 @@ FFZ.menu_pages.about = {
// Credits
var credits = document.createElement('div');
var credits = 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">&nbsp;</a></td><td><a class="twitter" href="https://twitter.com/dansalvato1" title="Twitter" target="_new">&nbsp;</a></td><td><a class="youtube" href="https://www.youtube.com/user/dansalvato1" title="YouTube" target="_new">&nbsp;</a></td></tr>';
content += '<tr><td>Stendec</td><td><a class="twitch" href="http://www.twitch.tv/sirstendec" title="Twitch" target="_new">&nbsp;</a></td><td><a class="twitter" href="https://twitter.com/SirStendec" title="Twitter" target="_new">&nbsp;</a></td><td><a class="youtube" href="https://www.youtube.com/channel/UCnxuvmK1DCPCXSJ-mXIh4KQ" title="YouTube" target="_new">&nbsp;</a></td></tr>';
content += '<tr><td>Dan Salvato</td><td><a class="twitch" href="//www.twitch.tv/dansalvato" title="Twitch" target="_new">&nbsp;</a></td><td><a class="twitter" href="https://twitter.com/dansalvato1" title="Twitter" target="_new">&nbsp;</a></td><td><a class="youtube" href="https://www.youtube.com/user/dansalvato1" title="YouTube" target="_new">&nbsp;</a></td></tr>';
content += '<tr><td>Stendec</td><td><a class="twitch" href="//www.twitch.tv/sirstendec" title="Twitch" target="_new">&nbsp;</a></td><td><a class="twitter" href="https://twitter.com/SirStendec" title="Twitter" target="_new">&nbsp;</a></td><td><a class="youtube" href="https://www.youtube.com/channel/UCnxuvmK1DCPCXSJ-mXIh4KQ" title="YouTube" target="_new">&nbsp;</a></td></tr>';
content += '<tr class="debug"><td><a href="#" id="ffz-changelog">Version ' + FFZ.version_info + '</a></td><td colspan="3"><a href="#" id="ffz-debug-logs">Logs</a></td></tr>';
credits.className = 'chat-menu-content center';
credits.innerHTML = content;
// Functional Changelog
credits.querySelector('#ffz-changelog').addEventListener('click', function() {
f._ui_change_page(view, inner, menu, container, 'about_changelog');
});
// Make the Version clickable.
credits.querySelector('#ffz-changelog').addEventListener('click',
f._ui_change_subpage.bind(f, view, inner, menu, container, 'changelog'));
// Make the Logs button functional.
var getting_logs = false;
@ -225,4 +223,185 @@ FFZ.menu_pages.about = {
container.appendChild(credits);
}
},
changelog: {
name: "Changelog",
wide: true,
render: include_html("change log", constants.SERVER + "script/changelog.html")
},
/*news: {
name: "News",
wide: true,
render: function(view, container) {
if ( this._has_news ) {
this._has_news = false;
localStorage.ffzLastNewsId = this._news_id;
this.update_ui_link();
}
return render_news.call(this, view, container);
}
},*/
credits: {
name: "Credits",
wide: true,
render: include_html("credits", constants.SERVER + "script/credits.html")
},
debugging: {
name: "Debug",
wide: true,
render: function(view, container) {
// Heading
var heading = createElement('div'),
info_head = createElement('div'),
info = createElement('ul'),
info_list = [
['Client ID', localStorage.ffzClientId || '<i>not set</i>'],
['Socket Server', this._ws_sock && this._ws_sock.url || '<i>disconnected</i>' ],
['Server Ping', this._ws_last_ping || '<i>unknown</i>'],
['Time Offset', this._ws_sock && this._ws_server_offset && (this._ws_server_offset < 0 ? "-" : "") + utils.time_to_string(Math.abs(this._ws_server_offset) / 1000) || '<i>unknown</i>']
],
twitch_head = createElement('div'),
twitch = createElement('ul'),
twitch_list = [
['Deploy Flavor', SiteOptions.deploy_flavor]
],
player_head = createElement('div'),
player_list = createElement('ul'),
pkeys = Object.keys(this.players),
player = pkeys.length && this.players[pkeys[0]] && this.players[pkeys[0]].ffz_player,
player_data,
ver_head = createElement('div'),
vers = createElement('ul'),
version_list = [
['Ember', Ember.VERSION],
['GIT Version', EmberENV.GIT_VERSION],
null,
['FrankerFaceZ', FFZ.version_info.toString()]
],
log_head = createElement('div'),
logs = createElement('pre');
if ( player ) {
try {
player_data = player.getVideoInfo();
} catch(err) { }
}
heading.className = 'chat-menu-content center';
heading.innerHTML = '<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">woofs for nerds</div>';
info_head.className = twitch_head.className = player_head.className = ver_head.className = log_head.className = 'list-header';
info.className = twitch.className = player_list.className = vers.className = 'chat-menu-content menu-side-padding version-list';
info_head.innerHTML = 'Client Status';
for(var i=0; i < info_list.length; i++) {
var data = info_list[i],
line = createElement('li');
line.innerHTML = data === null ? '<br>' : data[0] + '<span>' + data[1] + '</span>';
info.appendChild(line);
}
twitch_head.innerHTML = 'Twitch Configuration';
// Check for Twitch geo-location
var user = this.get_user();
if ( user && user.login ) {
twitch_list.push(["Current User", user.login + " [" + user.id + "]"]);
var us = [];
user.is_staff && us.push("staff");
user.is_admin && us.push("admin");
user.is_partner && us.push("partner");
user.is_broadcaster && us.push("broadcaster");
user.has_turbo && us.push("turbo");
twitch_list.push(["User State", us.join(", ") || "<i>none</i>"]);
} else
twitch_list.push(["Current User", "<i>not logged in</i>"]);
if ( window.Twitch && Twitch.geo && Twitch.geo._result ) {
var data = Twitch.geo._result;
if ( data.geo )
twitch_list.push(["Region", data.geo + (data.eu ? " [EU]" : "")]);
if ( data.received_language )
twitch_list.push(["Received Language", data.received_language])
}
for(var i=0; i < twitch_list.length; i++) {
var data = twitch_list[i],
line = createElement('li');
line.innerHTML = data === null ? '<br>' : data[0] + '<span>' + data[1] + '</span>';
twitch.appendChild(line);
}
if ( player_data ) {
player_head.innerHTML = "Player Statistics";
update_player_stats(player, player_list);
}
ver_head.innerHTML = 'Versions';
if ( this.has_bttv )
version_list.push(["BetterTTV", BetterTTV.info.version + 'r' + BetterTTV.info.release]);
if ( Object.keys(this._apis).length ) {
version_list.push(null);
for(var key in this._apis) {
var api = this._apis[key];
version_list.push(['<b>Ext #' + api.id + '.</b> ' + api.name, api.version || '<i>unknown</i>']);
}
}
for(var i=0; i < version_list.length; i++) {
var data = version_list[i],
line = createElement('li');
line.innerHTML = data === null ? '<br>' : data[0] + '<span>' + data[1] + '</span>';
vers.appendChild(line);
}
log_head.className = 'list-header';
log_head.innerHTML = 'Logs';
logs.className = 'chat-menu-content menu-side-padding';
logs.textContent = this._log_data.join("\n");
container.appendChild(heading);
container.appendChild(ver_head);
container.appendChild(vers);
container.appendChild(info_head);
container.appendChild(info);
container.appendChild(twitch_head);
container.appendChild(twitch);
if ( player_data ) {
container.appendChild(player_head);
container.appendChild(player_list);
}
container.appendChild(log_head);
container.appendChild(logs);
}
}
}
}

2
src/ui/channel_stats.js Normal file
View file

@ -0,0 +1,2 @@
var FFZ = window.FrankerFaceZ;

View file

@ -88,7 +88,7 @@ FFZ.basic_settings.keywords = {
help: "Set additional keywords that will be highlighted in chat.",
method: function() {
FFZ.settings_info.keywords.method.bind(this)();
FFZ.settings_info.keywords.method.call(this);
}
};
@ -102,7 +102,7 @@ FFZ.basic_settings.banned_words = {
help: "Set a list of words that will be removed from chat messages, locally.",
method: function() {
FFZ.settings_info.banned_words.method.bind(this)();
FFZ.settings_info.banned_words.method.call(this);
}
};
@ -137,7 +137,7 @@ FFZ.settings_info.dark_twitch = {
document.body.classList.toggle("ffz-dark", val);
var Settings = window.App && App.__container__.lookup('controller:settings'),
settings = Settings.get('settings');
settings = Settings && Settings.get('settings');
if ( val ) {
this._load_dark_css();
@ -146,8 +146,9 @@ FFZ.settings_info.dark_twitch = {
} else
settings && settings.set('darkMode', this.settings.twitch_chat_dark);
// Try coloring ReChat
jQuery('.rechat-chat-line').parents('.chat-container').toggleClass('dark', val || this.settings.twitch_chat_dark);
// Try coloring chat replay
jQuery('.chatReplay').toggleClass('dark', val || false);
//jQuery('.rechat-chat-line').parents('.chat-container').toggleClass('dark', val || this.settings.twitch_chat_dark);
}
};

271
src/ui/dash_stats.js Normal file
View file

@ -0,0 +1,271 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils');
// -------------------
// Settings
// -------------------
FFZ.settings_info.dashboard_graph = {
type: "boolean",
value: true,
no_mobile: true,
no_bttv: true,
category: "Dashboard",
name: "Statistics Graph <small>(Requires Refresh)</small>",
help: "Display a graph of your viewers, followers, and chat activity over time."
}
// -------------------
// Collecting Chat
// -------------------
FFZ.msg_commands.chat_message = function(data) {
if ( ! this.dashboard_channel || data.room !== this.dashboard_channel )
return;
this._stat_chat_lines++;
if ( this._stat_chatters.indexOf(data.from) === -1 )
this._stat_chatters.push(data.from);
}
// -------------------
// Initialization
// -------------------
FFZ.prototype.setup_dash_stats = function() {
this._stat_chat_lines = 0;
this._stat_chatters = [];
this._stat_last_game = null;
this._stat_last_status = null;
this._update_dash_stats_timer = setTimeout(this.update_dash_stats.bind(this), 0);
var f = this,
stats = document.querySelector('#stats');
if ( this.has_bttv || ! stats || ! window.Highcharts || ! this.settings.dashboard_graph )
return;
f.log("Adding dashboard statistics chart.");
// Build a chart, under stats.
var container = document.createElement('div');
container.id = "chart_container";
container.className = 'ffz-stat-chart';
stats.parentElement.insertBefore(container, stats.nextSibling);
Highcharts.setOptions({global: {useUTC: false}});
// Load chart visibility
var vis = {};
if ( localStorage.ffz_dash_chart_visibility )
vis = JSON.parse(localStorage.ffz_dash_chart_visibility);
var date_format = this.settings.twenty_four_timestamps ? "%H:%M" : "%l:%M",
chart = this._dash_chart = new Highcharts.Chart({
chart: {
type: 'line',
zoomType: "x",
animation: false,
renderTo: container,
height: 200
},
title: { text: null },
credits: { enabled: false },
exporting: { enabled: false },
legend: {
backgroundColor: "#fff"
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150,
dateTimeLabelFormats: {
millisecond: date_format,
second: date_format,
minute: date_format
}
},
tooltip: {
formatter: function() {
if ( this.point )
this.points = [this.point];
var s = [],
key = this.points[0].key || this.points[0].x;
if ( key ) {
if ( typeof key === "number" )
key = Highcharts.dateFormat((f.settings.twenty_four_timestamps ? "%H:%M" : "%l:%M %P"), key);
s.push('<span style="font-size:10px">' + key + '</span>');
}
for(var i=0; i < this.points.length; i++) {
var point = this.points[i],
series = point.series,
to = series.tooltipOptions,
y = point.text || point.y;
if ( ! to || ! to.enabled || y === undefined || y === null )
continue;
if ( typeof y === "number" )
y = utils.number_commas(y);
s.push('<span style="color:' + series.color + '">' + series.name + '</span>: <b>' + y + '</b>');
}
return s.join("<br>");
},
crosshairs: true,
shared: true
},
yAxis: [
{title: { text: null }, min: 0},
{title: { text: null }, min: 0},
{title: { text: null }, min: 0, opposite: true},
{title: { text: null }, min: 0, opposite: true}
],
series: [
{
type: 'flags',
name: 'Status',
showInLegend: false,
shape: 'squarepin',
data: [],
zIndex: 5
},
{name: "Viewers", data: [], zIndex: 4, visible: vis.hasOwnProperty('viewers')?vis.viewers:true},
{name: "Followers", data: [], yAxis: 1, zIndex: 3, visible: vis.hasOwnProperty('followers')?vis.followers:true},
{name: "Subscribers", data: [], zIndex: 3, showInLegend: false, visible: vis.hasOwnProperty('subscribers')?vis.subscribers:true},
{name: "Chat Lines", type: 'area', data: [], yAxis: 2, visible: vis.hasOwnProperty('chat_lines')?vis.chat_lines:false, zIndex: 1},
{name: "Chatters", data: [], yAxis: 3, visible: vis.hasOwnProperty('chatters')?vis.chatters:false, zIndex: 2}
]
});
}
FFZ.prototype._dash_chart_chatters = function(force) {
var now = utils.last_minute(),
series = this._dash_chart.series[4],
len = series.data.length,
last_point = len > 0 && series.data[len-1],
rendered = false;
if ( ! force && ! this._stat_chat_lines && len > 0 && last_point.y === 0 ) {
series.addPoint({x: now, y: null}, false);
this._dash_chart.series[5].addPoint({x: now, y: null}, false);
rendered = true;
} else if ( force || this._stat_chat_lines || len > 0 && last_point && last_point.y !== null ) {
if ( this._stat_chat_lines !== 0 && last_point && last_point.y === null ) {
series.addPoint({x:now-60000, y: 0}, false);
this._dash_chart.series[5].addPoint({x:now-60000, y: 0}, false);
}
series.addPoint({x: now, y: this._stat_chat_lines || 0}, false);
this._dash_chart.series[5].addPoint({x: now, y: this._stat_chatters.length || 0}, false);
rendered = true;
}
this._stat_chat_lines = 0;
this._stat_chatters = [];
return rendered;
}
FFZ.prototype._remove_dash_chart = function() {
if ( ! this._dash_chart )
return;
this.log("Removing dashboard statistics chart.");
this._dash_chart.destroy();
this._dash_chart = null;
jQuery("#chart_container.ffz-stat-chart").remove();
}
FFZ.prototype.update_dash_stats = function() {
var f = this,
id = this.dashboard_channel;
if ( this.has_bttv || ! id )
return this._remove_dash_chart();
this._update_dash_stats_timer = setTimeout(this.update_dash_stats.bind(this), 60000);
if ( f._dash_chart ) {
var series = f._dash_chart.series;
localStorage.ffz_dash_chart_visibility = JSON.stringify({
viewers: series[1].visble,
followers: series[2].visible,
subscribers: series[3].visible,
chat_lines: series[4].visible,
chatters: series[5].visible
});
}
utils.api.get("streams/" + id , {}, {version: 3})
.done(function(data) {
var viewers = null,
followers = null,
game = null,
status = null;
if ( ! data || ! data.stream )
!f.has_bttv && jQuery("#channel_viewer_count").text("Offline");
else {
!f.has_bttv && jQuery("#channel_viewer_count").text(utils.number_commas(data.stream.viewers));
viewers = data.stream.viewers;
var chan = data.stream.channel;
if ( chan ) {
followers = chan.hasOwnProperty('followers') ? chan.followers || 0 : null;
game = chan.game || "Not Playing";
status = chan.status || "Untitled Broadcast";
if ( chan.views )
jQuery("#views_count span").text(utils.number_commas(chan.views));
if ( followers )
jQuery("#followers_count span").text(utils.number_commas(followers));
}
}
if ( f._dash_chart ) {
var now = utils.last_minute(),
force_draw;
// If the game or status changed, we need to insert a pin.
if ( f._stat_last_game !== game || f._stat_last_status !== status ) {
f._dash_chart.series[0].addPoint({x: now, title: game, text: status}, false);
f._stat_last_game = game;
f._stat_last_status = status;
}
force_draw = utils.maybe_chart(f._dash_chart.series[1], {x: now, y: viewers}, false);
force_draw = f._dash_chart_chatters(force_draw) || force_draw;
utils.maybe_chart(f._dash_chart.series[2], {x: now, y: followers}, false, force_draw);
f._dash_chart.redraw();
}
}).fail(function() {
if ( f._dash_chart ) {
f._dash_chart_chatters();
f._dash_chart.redraw();
}
});
}

View file

@ -70,6 +70,12 @@ FFZ.prototype.setup_following_count = function(has_ember) {
Live.load();
/*var Host = window.App && App.__container__.resolve('model:host'),
HostLive = Host && Host.find("following");
if ( HostLive )
HostLive.load();*/
var total = Live.get('total'),
streams = Live.get('content');
if ( typeof total === "number" ) {
@ -87,7 +93,7 @@ FFZ.prototype._following_get_me = function(tries) {
return setTimeout(this._following_get_me.bind(this, tries), Math.floor(2000*Math.random()) + 500);
var f = this;
Twitch.api.get("/api/me").done(function(data) {
utils.api.get("/api/me").done(function(data) {
f.log("Fetched User Data -- " + (data.name || data.login));
f.__user = data;
f._update_following_count();
@ -128,17 +134,21 @@ FFZ.prototype._update_following_count = function() {
var Stream = window.App && App.__container__.resolve('model:stream'),
Live = Stream && Stream.find("live"),
Host = window.App && App.__container__.resolve('model:host'),
HostLive = Host && Host.find("following"),
f = this;
if ( HostLive && document.body.getAttribute('data-current-path').indexOf('directory.following') !== -1 )
HostLive.load();
if ( Live )
Live.load();
else {
var a = {},
u = this.get_user();
var u = this.get_user();
a.Authorization = "OAuth " + u.chat_oauth_token;
Twitch.api && Twitch.api.get("streams/followed", {limit:20, offset:0}, {version:3, headers: a})
utils.api.get("streams/followed", {limit:20, offset:0}, {version:3}, u && u.chat_oauth_token)
.done(function(data) {
f._draw_following_count(data._total);
f._draw_following_channels(data.streams, data._total);
@ -159,15 +169,17 @@ FFZ.prototype._build_following_tooltip = function(el) {
var tooltip = (this.has_bttv ? '<span class="stat playing">FrankerFaceZ</span>' : '') + 'Following',
bb = el.getBoundingClientRect(),
height = document.body.clientHeight - (bb.bottom + 54),
max_lines = Math.max(Math.floor(height / 36) - 1, 2),
height = document.body.clientHeight - (bb.bottom + 50),
max_lines = Math.max(Math.floor(height / 40) - 1, 2),
/*Host = window.App && App.__container__.resolve('model:host'),
HostLive = Host && Host.find("following"),*/
streams = this._tooltip_streams,
total = this._tooltip_total || (streams && streams.length) || 0;
total = this._tooltip_total || (streams && streams.length) || 0,
c = 0;
if ( streams && streams.length ) {
var c = 0;
for(var i=0, l = streams.length; i < l; i++) {
var stream = streams[i];
if ( ! stream || ! stream.channel )
@ -189,11 +201,43 @@ FFZ.prototype._build_following_tooltip = function(el) {
(uptime > 0 ? '<span class="stat">' + constants.CLOCK + ' ' + (hours > 0 ? hours + 'h' : '') + minutes + 'm</span>' : '') +
'<span class="stat">' + constants.LIVE + ' ' + utils.number_commas(stream.viewers) + '</span>' +
'<b>' + utils.sanitize(stream.channel.display_name || stream.channel.name) + '</b><br>' +
'<span class="playing">' + (stream.channel.game ? 'Playing ' + utils.sanitize(stream.channel.game) : 'Not Playing') + '</span>';
'<span class="playing">' + (stream.channel.game === 'Creative' ? 'Being Creative' : (stream.channel.game ? 'Playing ' + utils.sanitize(stream.channel.game) : 'Not Playing')) + '</span>';
}
} else
} else {
c++; // is a terrible programming language
tooltip += "<hr>No one you're following is online.";
}
// If we have hosts, and room, try displaying some hosts.
/*if ( HostLive && (c + 1) < max_lines && HostLive.get('content.length') > 0 ) {
var t = HostLive.get('content.length');
c++;
tooltip += '<hr>Live Hosts';
for(var i=0; i < t; i++) {
var host = HostLive.get('content.' + i),
stream = host && host.target;
if ( ! stream )
continue;
c += 1;
if ( c > max_lines ) {
var sc = 1 + (streams && streams.length || 0);
tooltip += '<hr><span>And ' + utils.number_commas(t - (max_lines - sc)) + ' more...</span>';
break;
}
var hosting;
if ( ! host.ffz_hosts || host.ffz_hosts.length === 1 )
hosting = host.display_name;
else
hosting = utils.number_commas(host.ffz_hosts.length);
tooltip += (i === 0 ? '<hr>' : '') +
'<span class="stat">' + constants.LIVE + ' ' + utils.number_commas(stream.viewers) + '</span>' +
hosting + ' hosting <b>' + utils.sanitize(stream.channel.display_name || stream.channel.name) + '</b><br>' +
'<span class="playing">' + (stream.meta_game ? 'Playing ' + utils.sanitize(stream.meta_game) : 'Not Playing') + '</span>';
}
}*/
// Reposition the tooltip.
setTimeout(function() {

View file

@ -113,7 +113,7 @@ FFZ.ws_on_close.push(function() {
FFZ.ws_commands.follow_buttons = function(data) {
var controller = window.App && App.__container__.lookup('controller:channel'),
current_id = controller && controller.get('id'),
current_id = controller && controller.get('content.id'),
current_host = controller && controller.get('hostModeTarget.id'),
need_update = false;
@ -132,7 +132,7 @@ FFZ.ws_commands.follow_buttons = function(data) {
FFZ.ws_commands.follow_sets = function(data) {
var controller = App.__container__.lookup('controller:channel'),
current_id = controller && controller.get('id'),
current_id = controller && controller.get('content.id'),
current_host = controller && controller.get('hostModeTarget.id'),
need_update = false,
f = this;
@ -189,7 +189,7 @@ FFZ.ws_commands.follow_sets = function(data) {
FFZ.prototype.rebuild_following_ui = function() {
var controller = App.__container__.lookup('controller:channel'),
channel_id = controller && controller.get('id'),
channel_id = controller && controller.get('content.id'),
hosted_id = controller && controller.get('hostModeTarget.id');
if ( ! this._cindex )
@ -307,7 +307,7 @@ FFZ.prototype._build_following_button = function(container, channel_id) {
return update();
}
Twitch.api.get("users/" + user.login + "/follows/channels/" + channel_id)
utils.api.get("users/" + user.login + "/follows/channels/" + channel_id)
.done(function(data) {
following = true;
notifications = data.notifications;
@ -330,7 +330,7 @@ FFZ.prototype._build_following_button = function(container, channel_id) {
return null;
notifications = notice;
return Twitch.api.put("users/:login/follows/channels/" + channel_id, {notifications: notifications})
return utils.api.put("users/:login/follows/channels/" + channel_id, {notifications: notifications})
.fail(check_following);
},
@ -367,7 +367,7 @@ FFZ.prototype._build_following_button = function(container, channel_id) {
if ( following )
do_follow()
else
Twitch.api.del("users/:login/follows/channels/" + channel_id)
utils.api.del("users/:login/follows/channels/" + channel_id)
.done(check_following);
return false;

View file

@ -5,9 +5,9 @@ var FFZ = window.FrankerFaceZ,
fix_menu_position = function(container) {
var swapped = document.body.classList.contains('ffz-sidebar-swap') && ! document.body.classList.contains('ffz-portrait');
var bounds = container.getBoundingClientRect(),
var bounds = container.children[0].getBoundingClientRect(),
left = parseInt(container.style.left || '0'),
right = bounds.left + container.scrollWidth,
right = bounds.left + bounds.width,
moved = !!container.style.left;
if ( swapped ) {
@ -217,7 +217,7 @@ FFZ.prototype.build_ui_popup = function(view) {
// Stuff
jQuery(inner).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
jQuery(inner).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
// Menu Container
var sub_container = document.createElement('div');
@ -270,7 +270,7 @@ FFZ.prototype.build_ui_popup = function(view) {
var page = FFZ.menu_pages[key];
try {
if ( !page || (page.hasOwnProperty("visible") && (!page.visible || (typeof page.visible == "function" && !page.visible.bind(this)(view)))) )
if ( !page || (page.hasOwnProperty("visible") && (!page.visible || (typeof page.visible == "function" && !page.visible.call(this, view)))) )
continue;
} catch(err) {
this.error("menu_pages " + key + " visible: " + err);
@ -298,7 +298,7 @@ FFZ.prototype.build_ui_popup = function(view) {
el = document.createElement('li'),
link = document.createElement('a');
el.className = 'item' + (page.sub_menu ? ' has-sub-menu' : '');
el.className = 'item' + (page.sub_menu || page.pages ? ' has-sub-menu' : '');
el.id = "ffz-menu-page-" + key;
link.title = page.name;
link.innerHTML = page.icon;
@ -315,12 +315,12 @@ FFZ.prototype.build_ui_popup = function(view) {
var page = (this._last_page || "channel").split("_", 1)[0];
// Do we have news?
if ( this._has_news ) {
/*if ( this._has_news ) {
// Render news, then set the page back so our default doesn't change.
this._ui_change_page(view, inner, menu, sub_container, 'about_news');
this._last_page = page;
} else
} else*/
// Render Current Page
this._ui_change_page(view, inner, menu, sub_container, page);
@ -335,26 +335,144 @@ FFZ.prototype.build_ui_popup = function(view) {
}
FFZ.prototype._ui_change_subpage = function(view, inner, menu, container, subpage) {
var page = this._last_page,
last_subpages = this._last_subpage = this._last_subpage || {};
last_subpages[page] = subpage;
container.innerHTML = "";
container.setAttribute('data-page', subpage);
// Get the data structure for this page.
var page_data = FFZ.menu_pages[page],
data = page_data.pages[subpage];
// Render the page first. If there's an error, it won't update the other UI stuff.
data.render.call(this, view, container, inner, menu);
// Make sure the correct menu tab is selected
jQuery('li.active', menu).removeClass('active');
jQuery('#ffz-menu-page-' + page + '-subpage-' + subpage, menu).addClass('active');
// Apply wideness - TODO: Revamp wide menus entirely for thin containers
var is_wide = false,
app = document.querySelector(".app-main") || document.querySelector(".ember-chat-container");
if ( data.hasOwnProperty('wide') )
is_wide = data.wide || (typeof data.wide === "function" && data.wide.call(this));
else if ( page_data.hasOwnProperty('wide') )
is_wide = page_data.wide || (typeof page_data.wide === "function" && page_data.wide.call(this));
inner.style.maxWidth = is_wide ? (app.offsetWidth < 640 ? (app.offsetWidth-40) : 600) + "px" : "";
// Re-position if necessary.
var f = this;
setTimeout(function(){f._fix_menu_position();});
}
FFZ.prototype._ui_change_page = function(view, inner, menu, container, page) {
this._last_page = page;
container.innerHTML = "";
container.setAttribute('data-page', page);
// Allow settings to be wide. We need to know if chat is stand-alone.
var app = document.querySelector(".app-main") || document.querySelector(".ember-chat-container");
inner.style.maxWidth = (!FFZ.menu_pages[page].wide || (typeof FFZ.menu_pages[page].wide == "function" && !FFZ.menu_pages[page].wide.bind(this)())) ? "" : (app.offsetWidth < 640 ? (app.offsetWidth-40) : 600) + "px";
// Get the data structure for the new page.
var data = FFZ.menu_pages[page];
var els = menu.querySelectorAll('li.active');
for(var i=0; i < els.length; i++)
els[i].classList.remove('active');
// See if we're dealing with a sub-menu situation.
if ( data.pages ) {
// We need to render the sub-menu, and then call the _ui_change_sub_page method
// to render the sub-page.
var submenu = document.createElement('ul'),
subcontainer = document.createElement('div'),
var el = menu.querySelector('#ffz-menu-page-' + page);
if ( el )
el.classList.add('active');
else
this.log("No matching page: " + page);
height = parseInt(container.style.maxHeight || '0');
FFZ.menu_pages[page].render.bind(this)(view, container, inner, menu);
if ( ! height )
height = Math.max(200, view.$().height() - 172);
if ( height && ! Number.isNaN(height) ) {
height -= 37;
subcontainer.style.maxHeight = height + 'px';
}
submenu.className = 'menu sub-menu clearfix';
subcontainer.className = 'ffz-ui-sub-menu-page';
// Building Tabs
var subpages = [];
for(var key in data.pages) {
var subpage = data.pages[key];
try {
if ( ! subpage || (subpage.hasOwnProperty("visible") && (!subpage.visible || (typeof subpage.visible === "function" && !subpage.visible.call(this, view)))) )
continue;
} catch(err) {
this.error("menu_pages " + page + " subpage " + key + " visible: " + err);
continue;
}
subpages.push([subpage.sort_order || 0, key, subpage]);
}
subpages.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 < subpages.length; i++) {
var key = subpages[i][1],
subpage = subpages[i][2],
tab = document.createElement('li'),
link = document.createElement('a');
tab.className = 'item';
tab.id = 'ffz-menu-page-' + page + '-subpage-' + key;
link.innerHTML = subpage.name;
link.addEventListener('click', this._ui_change_subpage.bind(this, view, inner, submenu, subcontainer, key));
tab.appendChild(link);
submenu.appendChild(tab);
}
// Activate a Tab
var last_subpages = this._last_subpage = this._last_subpage || {},
last_subpage = last_subpages[page] = last_subpages[page] || data.default_page || subpages[0][1];
if ( typeof last_subpage === "function" )
last_subpage = last_subpage.call(this);
this._ui_change_subpage(view, inner, submenu, subcontainer, last_subpage);
// Make sure the correct menu tab is selected
jQuery('li.active', menu).removeClass('active');
jQuery('#ffz-menu-page-' + page, menu).addClass('active');
// Add this to the container.
container.appendChild(subcontainer);
container.appendChild(submenu);
return;
}
// Render the page first. If there's an error, it won't update the other UI stuff.
data.render.call(this, view, container, inner, menu);
// Make sure the correct menu tab is selected
jQuery('li.active', menu).removeClass('active');
jQuery('#ffz-menu-page-' + page, menu).addClass('active');
// Apply wideness - TODO: Revamp wide menus entirely for thin containers
var is_wide = data.wide || (typeof data.wide === "function" && data.wide.call(this)),
app = document.querySelector(".app-main") || document.querySelector(".ember-chat-container");
inner.style.maxWidth = is_wide ? (app.offsetWidth < 640 ? (app.offsetWidth-40) : 600) + "px" : "";
// Re-position if necessary.
var f = this;
@ -430,7 +548,16 @@ FFZ.menu_pages.channel = {
can_use = is_subscribed || !emote.subscriber_only,
img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x), url("' + constants.TWITCH_BASE + emote.id + '/3.0") 4x)';
s.className = 'emoticon html-tooltip' + (!can_use ? " locked" : "");
s.className = 'emoticon ffz-tooltip ffz-tooltip-no-credit' + (!can_use ? " locked" : "");
if ( emote.emoticon_set ) {
var favs = this.settings.favorite_emotes["twitch-" + emote.emoticon_set];
s.classList.add('ffz-can-favorite');
s.classList.toggle('ffz-favorite', favs && favs.indexOf(emote.id) !== -1);
}
s.setAttribute('data-emote', emote.id);
s.alt = emote.regex;
s.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")';
s.style.backgroundImage = '-webkit-' + img_set;
@ -438,19 +565,18 @@ FFZ.menu_pages.channel = {
s.style.backgroundImage = '-ms-' + img_set;
s.style.backgroundImage = img_set;
s.style.width = emote.width + "px";
s.style.height = emote.height + "px";
s.title = (this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + emote.id + '/3.0?_=preview">' : '') + emote.regex;
s.style.width = (10+emote.width) + "px";
s.style.height = (10+emote.height) + "px";
s.addEventListener('click', function(can_use, id, code, e) {
s.addEventListener('click', function(can_use, id, code, emote_set, e) {
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )
window.open("https://twitchemotes.com/emote/" + id);
else if ( can_use )
this._add_emote(view, code);
this._add_emote(view, code, "twitch-" + emote_set, id, e);
else
return;
e.preventDefault();
}.bind(this, can_use, emote.id, emote.regex));
}.bind(this, can_use, emote.id, emote.regex, emote.emoticon_set));
grid.appendChild(s);
c++;
@ -507,7 +633,7 @@ FFZ.menu_pages.channel = {
var extra_sets = _.union(room && room.extra_sets || [], room && room.ext_sets || [], []);
// Basic Emote Sets
this._emotes_for_sets(inner, view, room && room.set && [room.set] || [], (this.feature_friday || has_product || extra_sets.length ) ? "Channel Emoticons" : null, "http://cdn.frankerfacez.com/script/devicon.png", "FrankerFaceZ");
this._emotes_for_sets(inner, view, room && room.set && [room.set] || [], (this.feature_friday || has_product || extra_sets.length ) ? "Channel Emoticons" : null, (room && room.moderator_badge) || "//cdn.frankerfacez.com/script/devicon.png", "FrankerFaceZ");
for(var i=0; i < extra_sets.length; i++) {
// Look up the set name.
@ -593,7 +719,11 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub
c++;
var s = document.createElement('span');
s.className = 'emoticon html-tooltip';
s.className = 'emoticon ffz-tooltip';
s.setAttribute('data-ffz-emote', emote.id);
s.setAttribute('data-ffz-set', set.id);
s.style.backgroundImage = 'url("' + emote.urls[1] + '")';
if ( srcset ) {
@ -604,9 +734,8 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub
s.style.backgroundImage = img_set;
}
s.style.width = emote.width + "px";
s.style.height = emote.height + "px";
s.title = this._emote_tooltip(emote);
s.style.width = (10+emote.width) + "px";
s.style.height = (10+emote.height) + "px";
s.addEventListener('click', function(id, code, e) {
e.preventDefault();
@ -636,7 +765,29 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub
}
FFZ.prototype._add_emote = function(view, emote) {
FFZ.prototype._add_emote = function(view, emote, favorites_set, favorites_key, event) {
if ( event && event.ctrlKey ) {
var el = event.target;
if ( ! el.classList.contains('locked') && el.classList.contains('ffz-can-favorite') && favorites_set && favorites_key ) {
var favs = this.settings.favorite_emotes[favorites_set] = this.settings.favorite_emotes[favorites_set] || [],
is_favorited = favs.indexOf(favorites_key) !== -1;
if ( is_favorited )
favs.removeObject(favorites_key);
else
favs.push(favorites_key);
this.settings.set("favorite_emotes", this.settings.favorite_emotes, true);
if ( el.classList.contains('ffz-is-favorite') && is_favorited ) {
jQuery(el).trigger('mouseout');
el.parentElement.removeChild(el);
} else
el.classList.toggle('ffz-favorite', ! is_favorited);
}
return;
}
var input_el, text, room;
if ( this.has_bttv ) {

View file

@ -43,5 +43,5 @@ FFZ.prototype.update_ui_link = function(link) {
link.classList.toggle('live', live);
link.classList.toggle('dark', dark);
link.classList.toggle('blue', blue);
link.classList.toggle('news', this._has_news);
//link.classList.toggle('news', this._has_news);
}

View file

@ -56,7 +56,7 @@ FFZ.settings_info.global_emotes_in_menu = {
FFZ.settings_info.emoji_in_menu = {
type: "boolean",
value: false,
value: true,
category: "Chat Input",
@ -66,11 +66,18 @@ FFZ.settings_info.emoji_in_menu = {
FFZ.settings_info.emote_menu_collapsed = {
storage_key: "ffz_setting_my_emoticons_collapsed_sections",
value: [],
visible: false
}
FFZ.settings_info.favorite_emotes = {
value: {},
visible: false
}
FFZ.prototype.setup_my_emotes = function() {
this._twitch_badges = {};
this._twitch_badges["--global--"] = "//cdn.frankerfacez.com/script/twitch_logo.png";
@ -100,47 +107,208 @@ FFZ.menu_pages.myemotes = {
return ffz_sets.length || (sk && sk.length) || this.settings.emoji_in_menu;
},
render: function(view, container) {
var tmi = view.get('controller.currentRoom.tmiSession'),
twitch_sets = (tmi && tmi.getEmotes() || {'emoticon_sets': {}})['emoticon_sets'];
default_page: function() {
for(var key in this.settings.favorite_emotes)
if ( this.settings.favorite_emotes[key] && this.settings.favorite_emotes[key].length )
return 'favorites';
// We don't have to do async stuff anymore cause we pre-load data~!
return FFZ.menu_pages.myemotes.draw_menu.bind(this)(view, container, twitch_sets);
return 'all';
},
toggle_section: function(heading) {
pages: {
favorites: {
name: "Favorites",
sort_order: 1,
render: function(view, container) {
FFZ.menu_pages.myemotes.render_lists.call(this, view, container, true);
var el = document.createElement("div");
el.className = "emoticon-grid ffz-no-emotes center";
el.innerHTML = "You have no favorite emoticons.<br><img src=\"//cdn.frankerfacez.com/emoticon/26608/2\"><br>To make an emote a favorite, find it on the <nobr>All Emoticons</nobr> tab and <nobr>Ctrl-Click</nobr> it.";
container.appendChild(el);
}
},
all: {
name: "All Emoticons",
sort_order: 2,
render: function(view, container) {
FFZ.menu_pages.myemotes.render_lists.call(this, view, container, false);
}
},
emoji: {
name: "Emoji",
sort_order: 3,
visible: function() { return this.settings.emoji_in_menu },
render: function(view, container) {
var sets = [];
for(var cat in constants.EMOJI_CATEGORIES) {
var menu = FFZ.menu_pages.myemotes.draw_emoji.call(this, view, cat, false);
if ( menu )
sets.push([cat, menu]);
}
sets.sort(function(a,b) {
var an = a[0], bn = b[0];
if ( an < bn ) return -1;
if ( an > bn ) return 1;
return 0;
});
for(var i=0; i < sets.length; i++)
container.appendChild(sets[i][1]);
}
}
},
render_lists: function(view, container, favorites_only) {
var tmi = view.get('controller.currentRoom.tmiSession'),
twitch_sets = (tmi && tmi.getEmotes() || {'emoticon_sets': {}})['emoticon_sets'],
user = this.get_user(),
ffz_sets = this.getEmotes(user && user.login, null),
sets = [];
// Start with Twitch Sets
for(var set_id in twitch_sets) {
if ( ! twitch_sets.hasOwnProperty(set_id) || ( ! this.settings.global_emotes_in_menu && set_id === '0' ) )
continue;
var favorites_list = this.settings.favorite_emotes["twitch-" + set_id];
if ( favorites_only && (! favorites_list || ! favorites_list.length) )
continue;
var set = twitch_sets[set_id];
if ( ! set.length )
continue;
var menu = FFZ.menu_pages.myemotes.draw_twitch_set.call(this, view, set_id, set, favorites_only);
if ( menu )
sets.push([this._twitch_set_to_channel[set_id], menu]);
}
// Emoji~!
if ( favorites_only && this.settings.emoji_in_menu ) {
var favorites_list = this.settings.favorite_emotes["emoji"];
if ( favorites_list && favorites_list.length ) {
var menu = FFZ.menu_pages.myemotes.draw_emoji.call(this, view, null, favorites_only);
if ( menu )
sets.push(["emoji", menu]);
}
}
// Now, FFZ!
for(var i=0; i < ffz_sets.length; i++) {
var set_id = ffz_sets[i],
set = this.emote_sets[set_id],
menu_id = set.hasOwnProperty('source_ext') ? 'ffz-ext-' + set.source_ext + '-' + set.source_id : 'ffz-' + set.id,
favorites_list = this.settings.favorite_emotes[menu_id];
if ( favorites_only && (! favorites_list || ! favorites_list.length) )
continue;
if ( ! set || ! set.count || ( ! this.settings.global_emotes_in_menu && this.default_sets.indexOf(set_id) !== -1 ) )
continue;
var menu = FFZ.menu_pages.myemotes.draw_ffz_set.call(this, view, set, favorites_only);
if ( menu )
sets.push([set.title.toLowerCase(), menu]);
}
if ( ! sets.length )
return false;
// Finally, sort and add them all.
sets.sort(function(a,b) {
var an = a[0], bn = b[0];
if ( an === "turbo" || an === "--turbo-faces--" )
an = "zza|" + an;
else if ( an === "global" || (an && an.substr(0,16) === "global emoticons") || an === "--global--" )
an = "zzy|" + an;
else if ( an.substr(0,5) === "emoji" )
an = "zzz|" + an;
if ( bn === "turbo" || bn === "--turbo-faces--" )
bn = "zza|" + bn;
else if ( bn === "global" || (bn && bn.substr(0,16) === "global emoticons") || bn === "--global--" )
bn = "zzy|" + bn;
else if ( bn.substr(0,5) === "emoji" )
bn = "zzz|" + bn;
if ( an < bn ) return -1;
if ( an > bn ) return 1;
return 0;
});
if ( favorites_only ) {
var grid = document.createElement('div');
grid.className = 'emoticon-grid favorites-grid';
for(var i=0; i < sets.length; i++)
grid.appendChild(sets[i][1]);
container.appendChild(grid);
} else
for(var i=0; i < sets.length; i++)
container.appendChild(sets[i][1]);
return true;
},
toggle_section: function(heading, container) {
var menu = heading.parentElement,
set_id = menu.getAttribute('data-set'),
collapsed_list = this.settings.emote_menu_collapsed,
is_collapsed = collapsed_list.indexOf(set_id) !== -1;
is_collapsed = collapsed_list.indexOf(set_id) === -1;
if ( is_collapsed )
if ( ! is_collapsed )
collapsed_list.removeObject(set_id);
else
collapsed_list.push(set_id);
this.settings.set('emote_menu_collapsed', collapsed_list);
this.settings.set('emote_menu_collapsed', collapsed_list, true);
menu.classList.toggle('collapsed', !is_collapsed);
if ( is_collapsed )
menu.appendChild(container);
else
menu.removeChild(container);
},
draw_emoji: function(view) {
draw_emoji: function(view, cat, favorites_only) {
var heading = document.createElement('div'),
menu = document.createElement('div'),
menu_id = 'emoji' + (cat ? '-' + cat : ''),
emotes = favorites_only ? document.createDocumentFragment() : document.createElement('div'),
collapsed = ! favorites_only && this.settings.emote_menu_collapsed.indexOf(menu_id) === -1,
f = this,
settings = this.settings.parse_emoji || 1;
settings = this.settings.parse_emoji || 1,
favorites = this.settings.favorite_emotes["emoji"] || [],
c = 0;
menu.className = 'emoticon-grid';
menu.setAttribute('data-set', menu_id);
if ( ! favorites_only ) {
heading.className = 'heading';
heading.innerHTML = '<span class="right">Unicode</span>Emoji';
heading.style.backgroundImage = 'url("' + constants.SERVER + 'emoji/' + (settings === 2 ? 'noto-' : 'tw-') + '1f4af.svg")';
heading.innerHTML = '<span class="right">Unicode</span>' + (cat ? utils.sanitize(constants.EMOJI_CATEGORIES[cat]) : 'Emoji');
heading.style.backgroundImage = 'url("' + constants.SERVER + 'emoji/' + (settings === 3 ? 'one/' : (settings === 2 ? 'noto-' : 'tw/')) + (constants.EMOJI_LOGOS[cat] || '1f4af') + '.svg")';
heading.style.backgroundSize = "18px";
menu.className = 'emoticon-grid collapsable';
menu.classList.add('collapsable');
menu.appendChild(heading);
menu.setAttribute('data-set', 'emoji');
menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('emoji') !== -1);
heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); });
menu.classList.toggle('collapsed', collapsed);
heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this, emotes); });
}
var set = [];
@ -162,32 +330,59 @@ FFZ.menu_pages.myemotes = {
var emoji = set[i],
em = document.createElement('span');
if ( (settings === 1 && ! emoji.tw) || (settings === 2 && ! emoji.noto) )
if ( (cat && cat !== emoji.cat) || (settings === 1 && ! emoji.tw) || (settings === 2 && ! emoji.noto) || (settings === 3 && ! emoji.one) )
continue;
var src = settings === 2 ? emoji.noto_src : emoji.tw_src,
var is_favorite = favorites.indexOf(emoji.raw) !== -1,
src = settings === 3 ? emoji.one_src : (settings === 2 ? emoji.noto_src : emoji.tw_src),
image = this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + src + '">' : '';
em.className = 'emoticon emoji html-tooltip';
em.title = image + 'Emoji: ' + emoji.raw + '<br>Name: ' + emoji.name + (emoji.short_name ? '<br>Short Name: :' + emoji.short_name + ':' : '');
em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw));
if ( favorites_only && ! is_favorite )
continue;
em.className = 'emoticon emoji ffz-tooltip ffz-can-favorite';
em.classList.toggle('ffz-favorite', is_favorite);
em.classList.toggle('ffz-is-favorite', favorites_only);
em.setAttribute('data-ffz-emoji', emoji.code);
em.alt = emoji.raw;
em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw, "emoji", emoji.raw));
em.style.backgroundImage = 'url("' + src + '")';
em.style.backgroundSize = "18px";
menu.appendChild(em);
c++;
emotes.appendChild(em);
}
if ( ! c )
return;
if ( favorites_only )
return emotes;
if ( ! collapsed )
menu.appendChild(emotes);
return menu;
},
draw_twitch_set: function(view, set_id, set) {
draw_twitch_set: function(view, set_id, set, favorites_only) {
var heading = document.createElement('div'),
menu = document.createElement('div'),
emotes = favorites_only ? document.createDocumentFragment() : document.createElement('div'),
collapsed = ! favorites_only && this.settings.emote_menu_collapsed.indexOf('twitch-' + set_id) === -1,
f = this,
channel_id = this._twitch_set_to_channel[set_id], title;
channel_id = this._twitch_set_to_channel[set_id], title,
favorites = this.settings.favorite_emotes["twitch-" + set_id] || [],
c = 0;
menu.className = 'emoticon-grid';
menu.setAttribute('data-set', 'twitch-' + set_id);
if ( ! favorites_only ) {
if ( channel_id === "twitch_unknown" )
title = "Unknown Channel";
else if ( channel_id === "--global--" )
@ -206,7 +401,7 @@ FFZ.menu_pages.myemotes = {
heading.style.backgroundImage = 'url("' + this._twitch_badges[channel_id] + '")';
else {
var f = this;
Twitch.api.get("chat/" + channel_id + "/badges", null, {version: 3})
utils.api.get("chat/" + channel_id + "/badges", null, {version: 3})
.done(function(data) {
if ( data.subscriber && data.subscriber.image ) {
f._twitch_badges[channel_id] = data.subscriber.image;
@ -216,12 +411,11 @@ FFZ.menu_pages.myemotes = {
});
}
menu.className = 'emoticon-grid collapsable';
menu.classList.add('collapsable');
menu.appendChild(heading);
menu.setAttribute('data-set', 'twitch-' + set_id);
menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('twitch-' + set_id) !== -1);
heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); });
menu.classList.toggle('collapsed', collapsed);
heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this, emotes); });
}
set.sort(function(a,b) {
var an = a.code.toLowerCase(),
@ -237,11 +431,21 @@ FFZ.menu_pages.myemotes = {
for(var i=0; i < set.length; i++) {
var emote = set[i],
code = constants.KNOWN_CODES[emote.code] || emote.code,
is_favorite = favorites.indexOf(emote.id) !== -1;
em = document.createElement('span'),
img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x, url("' + constants.TWITCH_BASE + emote.id + '/3.0") 4x)';
if ( favorites_only && ! is_favorite )
continue;
em.className = 'emoticon html-tooltip';
var em = document.createElement('span'),
img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x)';
em.className = 'emoticon ffz-tooltip ffz-can-favorite';
em.setAttribute('data-emote', emote.id);
em.alt = code;
em.classList.toggle('ffz-favorite', is_favorite);
em.classList.toggle('ffz-is-favorite', favorites_only);
em.classList.toggle('ffz-tooltip-no-credit', ! favorites_only);
if ( this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[emote.id] ) {
em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")';
@ -250,31 +454,51 @@ FFZ.menu_pages.myemotes = {
em.style.backgroundImage = '-webkit-' + img_set;
em.style.backgroundImage = '-moz-' + img_set;
em.style.backgroundImage = '-ms-' + img_set;
em.style.backgroudnImage = img_set;
em.style.backgroundImage = img_set;
}
em.title = (this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + emote.id + '/3.0?_=preview">' : '') + code;
em.addEventListener("click", function(id, c, e) {
e.preventDefault();
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )
window.open("https://twitchemotes.com/emote/" + id);
else
this._add_emote(view, c);
this._add_emote(view, c, "twitch-" + set_id, id, e);
}.bind(this, emote.id, code));
menu.appendChild(em);
c++;
emotes.appendChild(em);
}
if ( ! c )
return;
if ( favorites_only )
return emotes;
if ( ! collapsed )
menu.appendChild(emotes);
return menu;
},
draw_ffz_set: function(view, set) {
draw_ffz_set: function(view, set, favorites_only) {
var heading = document.createElement('div'),
menu = document.createElement('div'),
emote_container = favorites_only ? document.createDocumentFragment() : document.createElement('div'),
f = this,
emotes = [],
menu_id = set.hasOwnProperty('source_ext') ? 'ffz-ext-' + set.source_ext + '-' + set.source_id : 'ffz-' + set.id,
icon = set.icon || (set.hasOwnProperty('source_ext') && '//cdn.frankerfacez.com/emoji/tw-1f4ac.svg') || '//cdn.frankerfacez.com/script/devicon.png';
favorites = this.settings.favorite_emotes[menu_id] || [],
c = 0,
icon = set.icon || (set.hasOwnProperty('source_ext') && '//cdn.frankerfacez.com/emoji/tw-1f4ac.svg') || '//cdn.frankerfacez.com/script/devicon.png',
collapsed = ! favorites_only && this.settings.emote_menu_collapsed.indexOf(menu_id) === -1;
menu.className = 'emoticon-grid';
menu.setAttribute('data-set', menu_id);
if ( ! favorites_only ) {
menu.classList.add('collapsable');
heading.className = 'heading';
heading.innerHTML = '<span class="right">' + (utils.sanitize(set.source) || 'FrankerFaceZ') + '</span>' + set.title;
@ -283,12 +507,10 @@ FFZ.menu_pages.myemotes = {
if ( icon.indexOf('.svg') !== -1 )
heading.style.backgroundSize = "18px";
menu.className = 'emoticon-grid collapsable';
menu.appendChild(heading);
menu.setAttribute('data-set', menu_id);
menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf(menu_id) !== -1);
heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); });
menu.classList.toggle('collapsed', collapsed);
heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this, emote_container); });
}
for(var emote_id in set.emoticons)
set.emoticons.hasOwnProperty(emote_id) && ! set.emoticons[emote_id].hidden && emotes.push(set.emoticons[emote_id]);
@ -306,8 +528,12 @@ FFZ.menu_pages.myemotes = {
for(var i=0; i < emotes.length; i++) {
var emote = emotes[i],
is_favorite = favorites.indexOf(emote.id) !== -1;
em = document.createElement('span'),
if ( favorites_only && ! is_favorite )
continue;
var em = document.createElement('span'),
img_set = 'image-set(url("' + emote.urls[1] + '") 1x';
if ( emote.urls[2] )
@ -318,19 +544,24 @@ FFZ.menu_pages.myemotes = {
img_set += ')';
em.className = 'emoticon html-tooltip';
em.className = 'emoticon ffz-tooltip ffz-can-favorite';
em.classList.toggle('ffz-favorite', is_favorite);
em.classList.toggle('ffz-is-favorite', favorites_only);
em.setAttribute('data-ffz-emote', emote.id);
em.setAttribute('data-ffz-set', set.id);
em.style.backgroundImage = 'url("' + emote.urls[1] + '")';
em.style.backgroundImage = '-webkit-' + img_set;
em.style.backgroundImage = '-moz-' + img_set;
em.style.backgroundImage = '-ms-' + img_set;
em.style.backgroudnImage = img_set;
em.style.backgroundImage = img_set;
if ( emote.height )
em.style.height = emote.height + "px";
em.style.height = (10+emote.height) + "px";
if ( emote.width )
em.style.width = emote.width + "px";
em.style.width = (10+emote.width) + "px";
em.title = this._emote_tooltip(emote);
em.addEventListener("click", function(id, code, e) {
e.preventDefault();
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) {
@ -345,97 +576,22 @@ FFZ.menu_pages.myemotes = {
if ( url )
window.open(url);
} else
this._add_emote(view, code);
this._add_emote(view, code, menu_id, id, e);
}.bind(this, emote.id, emote.name));
menu.appendChild(em);
c++;
emote_container.appendChild(em);
}
return menu;
},
draw_menu: function(view, container, twitch_sets) {
// Make sure we're still on the My Emoticons page. Since this is
// asynchronous, the user could've tabbed away.
if ( container.getAttribute('data-page') !== 'myemotes' )
if ( ! c )
return;
container.innerHTML = "";
try {
var user = this.get_user(),
ffz_sets = this.getEmotes(user && user.login, null),
sets = [];
if ( favorites_only )
return emote_container;
// Start with Twitch Sets
for(var set_id in twitch_sets) {
if ( ! twitch_sets.hasOwnProperty(set_id) || ( ! this.settings.global_emotes_in_menu && set_id === '0' ) )
continue;
if ( ! collapsed )
menu.appendChild(emote_container);
var set = twitch_sets[set_id];
if ( ! set.length )
continue;
sets.push([this._twitch_set_to_channel[set_id], FFZ.menu_pages.myemotes.draw_twitch_set.bind(this)(view, set_id, set)]);
}
// Emoji~!
if ( this.settings.emoji_in_menu )
sets.push(["emoji", FFZ.menu_pages.myemotes.draw_emoji.bind(this)(view)]);
// Now, FFZ!
for(var i=0; i < ffz_sets.length; i++) {
var set_id = ffz_sets[i],
set = this.emote_sets[set_id];
if ( ! set || ! set.count || ( ! this.settings.global_emotes_in_menu && this.default_sets.indexOf(set_id) !== -1 ) )
continue;
sets.push([set.title.toLowerCase(), FFZ.menu_pages.myemotes.draw_ffz_set.bind(this)(view, set)]);
}
// Finally, sort and add them all.
sets.sort(function(a,b) {
var an = a[0], bn = b[0];
if ( an === "turbo" || an === "--turbo-faces--" )
an = "zza|" + an;
else if ( an === "global" || (an && an.substr(0,16) === "global emoticons") || an === "--global--" )
an = "zzy|" + an;
else if ( an === "emoji" )
an = "zzz|" + an;
if ( bn === "turbo" || bn === "--turbo-faces--" )
bn = "zza|" + bn;
else if ( bn === "global" || (bn && bn.substr(0,16) === "global emoticons") || bn === "--global--" )
bn = "zzy|" + bn;
else if ( bn === "emoji" )
bn = "zzz|" + bn;
if ( an < bn ) return -1;
if ( an > bn ) return 1;
return 0;
});
for(var i=0; i < sets.length; i++)
container.appendChild(sets[i][1]);
} catch(err) {
this.error("myemotes draw_menu: " + 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);
}
return menu;
}
};

View file

@ -1,4 +1,5 @@
var FFZ = window.FrankerFaceZ;
var FFZ = window.FrankerFaceZ,
utils = require("../utils");
// ---------------------
@ -74,17 +75,21 @@ FFZ.settings_info.notification_timeout = {
help: "Specify how long notifications should be displayed before automatically closing.",
method: function() {
var old_val = this.settings.notification_timeout,
new_val = prompt("Notification Timeout\n\nPlease enter the time you'd like notifications to be displayed before automatically closing, in seconds.\n\nDefault is: 60", old_val);
var f = this;
utils.prompt(
"Notification Timeout",
"Please enter the time you'd like notifications to be displayed before automatically closing, in seconds.</p><p><b>Default:</b> 60",
this.settings.notification_timeout,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
var parsed = parseInt(new_val);
if ( parsed === NaN || parsed < 1 )
parsed = 60;
new_val = parseInt(new_val);
if ( Number.isNaN(new_val) || ! Number.isFinite(new_val) || new_val < 1 )
new_val = 60;
this.settings.set("notification_timeout", parsed);
f.settings.set("notification_timeout", new_val);
});
}
};
@ -136,7 +141,7 @@ FFZ.prototype.show_notification = function(message, title, tag, timeout, on_clic
dir: "ltr",
body: message,
tag: tag || "FrankerFaceZ",
icon: "http://cdn.frankerfacez.com/icon32.png"
icon: "//cdn.frankerfacez.com/icon32.png"
};
var f = this,

View file

@ -1,4 +1,5 @@
var FFZ = window.FrankerFaceZ;
var FFZ = window.FrankerFaceZ,
constants = require('../constants');
// ---------------
@ -13,6 +14,11 @@ FFZ.prototype.setup_popups = function() {
if ( e.button && e.button !== 0 )
return;
// Check for modal clicks
var modal = document.getElementById('ffz-modal-container');
if ( modal && (modal === e.target || modal.contains(e.target)) )
return;
var popup = f._popup,
parent = f._popup_parent;

View file

@ -304,7 +304,7 @@ FFZ.prototype._update_race = function(container, not_timer) {
for(var i=0; i < entrants.length; i++) {
var ent = entrants[i],
name = '<a target="_new" href="http://www.speedrunslive.com/profiles/#!/' + utils.sanitize(ent.name) + '">' + ent.display_name + '</a>',
twitch_link = ent.channel ? '<a target="_new" class="twitch" href="http://www.twitch.tv/' + utils.sanitize(ent.channel) + '"></a>' : '',
twitch_link = ent.channel ? '<a target="_new" class="twitch" href="//www.twitch.tv/' + utils.sanitize(ent.channel) + '"></a>' : '',
hitbox_link = ent.hitbox ? '<a target="_new" class="hitbox" href="http://www.hitbox.tv/' + utils.sanitize(ent.hitbox) + '"></a>' : '',
time = elapsed ? utils.time_to_string(ent.time||elapsed) : "",
place = utils.place_string(ent.place),

View file

@ -31,22 +31,19 @@ FFZ.prototype._update_subscribers = function() {
// context of the web user.
// Get the count!
jQuery.ajax({url: "/broadcast/dashboard/partnership"}).done(function(data) {
try {
var html = document.createElement('span'), dash;
jQuery.getJSON("/" + id + "/dashboard/revenue/summary_data").done(function(data) {
var el, sub_count = data && data.data && data.data.total_subscriptions;
if ( typeof sub_count === "string" )
sub_count = parseInt(sub_count.replace(/[,\.]/g, ""));
html.innerHTML = data;
dash = html.querySelector("#dash_main");
var match = dash && dash.textContent.match(/([\d,\.]+) total active subscribers/),
sub_count = match && match[1];
if ( ! sub_count ) {
var el = document.querySelector("#ffz-sub-display");
if ( typeof sub_count !== "number" || sub_count === 0 || sub_count === NaN || sub_count === Infinity ) {
el = document.querySelector("#ffz-sub-display");
if ( el )
el.parentElement.removeChild(el);
if ( f._update_subscribers_timer ) {
var failed = f._failed_sub_checks = (f._failed_sub_checks || 0) + 1;
if ( f._update_subscribers_timer && failed >= 5 ) {
f.log("Subscriber count failed 5 times. Giving up.");
clearTimeout(f._update_subscribers_timer);
delete f._update_subscribers_timer;
}
@ -54,7 +51,17 @@ FFZ.prototype._update_subscribers = function() {
return;
}
var el = document.querySelector('#ffz-sub-display span');
// Graph this glorious data point
if ( f._dash_chart ) {
if ( ! f._dash_chart.series[3].options.showInLegend ) {
f._dash_chart.series[3].options.showInLegend = true;
f._dash_chart.legend.renderLegend();
}
f._dash_chart.series[3].addPoint({x: utils.last_minute(), y: sub_count});
}
el = document.querySelector('#ffz-sub-display span');
if ( ! el ) {
var cont = f.is_dashboard ? document.querySelector("#stats") : document.querySelector("#channel .stats-and-actions .channel-stats");
if ( ! cont )
@ -63,14 +70,14 @@ FFZ.prototype._update_subscribers = function() {
var stat = document.createElement('span');
stat.className = 'ffz stat';
stat.id = 'ffz-sub-display';
stat.title = 'Active Channel Subscribers';
stat.title = 'Subscribers';
stat.innerHTML = constants.STAR + ' ';
el = document.createElement('span');
stat.appendChild(el);
Twitch.api.get("chat/" + id + "/badges", null, {version: 3})
utils.api.get("chat/" + id + "/badges", null, {version: 3})
.done(function(data) {
if ( data.subscriber && data.subscriber.image ) {
stat.innerHTML = '';
@ -87,15 +94,12 @@ FFZ.prototype._update_subscribers = function() {
jQuery(stat).tipsy({gravity: f.is_dashboard ? "s" : utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
el.innerHTML = sub_count;
el.innerHTML = utils.number_commas(sub_count);
} catch(err) {
f.error("_update_subscribers: " + err);
}
}).fail(function(){
var el = document.querySelector("#ffz-sub-display");
if ( el )
el.parentElement.removeChild(el);
return;
});;
});
}

View file

@ -73,100 +73,6 @@ var sanitize_el = document.createElement('span'),
},
// IRC Messages
splitIRCMessage = function(msgString) {
msgString = $.trim(msgString);
var split = {raw: msgString};
var tagsEnd = -1;
if ( msgString.charAt(0) === '@' ) {
tagsEnd = msgString.indexOf(' ');
split.tags = msgString.substr(1, tagsEnd - 1);
}
var prefixStart = tagsEnd + 1,
prefixEnd = -1;
if ( msgString.charAt(prefixStart) === ':' ) {
prefixEnd = msgString.indexOf(' ', prefixStart);
split.prefix = msgString.substr(prefixStart + 1, prefixEnd - (prefixStart + 1));
}
var trailingStart = msgString.indexOf(' :', prefixStart);
if ( trailingStart >= 0 ) {
split.trailing = msgString.substr(trailingStart + 2);
} else {
trailingStart = msgString.length;
}
var commandAndParams = msgString.substr(prefixEnd + 1, trailingStart - prefixEnd - 1).split(' ');
split.command = commandAndParams[0];
if ( commandAndParams.length > 1 )
split.params = commandAndParams.slice(1);
return split;
},
ESCAPE_CHARS = {
':': ';',
s: ' ',
r: '\r',
n: '\n',
'\\': '\\'
},
unescapeTag = function(tag) {
var result = '';
for(var i=0; i < tag.length; i++) {
var c = tag.charAt(i);
if ( c === '\\' ) {
if ( i === tag.length - 1 )
throw 'Improperly escaped tag';
c = ESCAPE_CHARS[tag.charAt(i+1)];
if ( c === undefined )
throw 'Improperly escaped tag';
i++;
}
result += c;
}
return result;
},
parseTag = function(tag, value) {
switch(tag) {
case 'slow':
try {
return parseInt(value);
} catch(err) { return 0; }
case 'subs-only':
case 'r9k':
case 'subscriber':
case 'turbo':
return value === '1';
default:
try {
return unescapeTag(value);
} catch(err) { return ''; }
}
},
parseIRCTags = function(tagsString) {
var tags = {},
keyValues = tagsString.split(';');
for(var i=0; i < keyValues.length; ++i) {
var kv = keyValues[i].split('=');
if ( kv.length === 2 )
tags[kv[0]] = parseTag(kv[0], kv[1]);
}
return tags;
},
uncompressEmotes = function(value) {
var output = {},
emotes = value.split("/"),
@ -199,121 +105,211 @@ var sanitize_el = document.createElement('span'),
},
// This code borrowed from the twemoji project, with tweaks.
UFE0Fg = /\uFE0F/g,
U200D = String.fromCharCode(0x200D),
EMOJI_CODEPOINTS = {},
emoji_to_codepoint = function(icon, variant) {
if ( EMOJI_CODEPOINTS[icon] && EMOJI_CODEPOINTS[icon][variant] )
return EMOJI_CODEPOINTS[icon][variant];
emoji_to_codepoint = function(surrogates, sep) {
if ( EMOJI_CODEPOINTS[surrogates] && EMOJI_CODEPOINTS[surrogates][sep] )
return EMOJI_CODEPOINTS[surrogates][sep];
var ico = variant === '\uFE0F' ? icon.slice(0, -1) : (icon.length === 3 && icon.charAt(1) === '\uFE0F' ? icon.charAt(0) + icon.charAt(2) : icon),
r = [], c = 0, p = 0, i = 0;
var input = surrogates.indexOf(U200D) === -1 ? surrogates.replace(UFE0Fg, '') : surrogates,
out = [],
c = 0, p = 0, i = 0;
while ( i < ico.length ) {
c = ico.charCodeAt(i++);
while (i < input.length) {
c = input.charCodeAt(i++);
if ( p ) {
r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
out.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
p = 0;
} else if ( 0xD800 <= c && c <= 0xDBFF) {
} else if ( 0xD800 <= c && c <= 0xDBFF )
p = c;
} else {
r.push(c.toString(16));
}
else
out.push(c.toString(16));
}
var es = EMOJI_CODEPOINTS[icon] = EMOJI_CODEPOINTS[icon] || {},
out = es[variant] = r.join("-");
return out;
var retval = EMOJI_CODEPOINTS[surrogates] = out.join('-');
return retval;
},
// Twitch Emote Tooltips
codepoint_to_emoji = function(codepoint) {
var code = typeof codepoint === 'string' ? parseInt(codepoint, 16) : codepoint;
if ( code < 0x10000 )
return String.fromCharCode(code);
code -= 0x10000;
return String.fromCharCode(
0xD800 + (code >> 10),
0xDC00 + (code & 0x3FF)
)
},
// Twitch Emote Helpers
SRCSETS = {},
build_srcset = function(id) {
if ( SRCSETS[id] )
return SRCSETS[id];
var out = SRCSETS[id] = constants.TWITCH_BASE + id + "/1.0 1x, " + constants.TWITCH_BASE + id + "/2.0 2x, " + constants.TWITCH_BASE + id + "/3.0 4x";
var out = SRCSETS[id] = constants.TWITCH_BASE + id + "/1.0 1x, " + constants.TWITCH_BASE + id + "/2.0 2x";
return out;
},
data_to_tooltip = function(data) {
var emote_set = data.set,
set_type = data.set_type,
// Twitch API
f = FFZ.get(),
image = '';
if ( data.id && f.settings.emote_image_hover )
image = '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + data.id + '/3.0?_=preview">';
if ( set_type === undefined )
set_type = "Channel";
if ( ! emote_set )
return image + data.code;
else if ( emote_set === "--global--" ) {
emote_set = "Twitch Global";
set_type = null;
} else if ( emote_set == "--twitch-turbo--" || emote_set == "turbo" || emote_set == "--turbo-faces--" ) {
emote_set = "Twitch Turbo";
set_type = null;
}
return image + "Emoticon: " + data.code + "<br>" + (set_type ? set_type + ": " : "") + emote_set;
api_call = function(method, url, data, options, token) {
options = options || {};
var headers = options.headers = options.headers || {};
headers['Client-ID'] = constants.CLIENT_ID;
if ( token )
headers.Authorization = 'OAuth ' + token;
return Twitch.api[method].call(this, url, data, options);
},
build_tooltip = function(id, force_update, code) {
var emote_data = this._twitch_emotes[id];
if ( ! emote_data && code ) {
var set_id = this._twitch_emote_to_set[id];
if ( set_id ) {
emote_data = this._twitch_emotes[id] = {
code: code,
id: id,
set: this._twitch_set_to_channel[set_id],
set_id: set_id
}
}
}
// Dialogs
show_modal = function(contents, on_close, width) {
var container = document.createElement('div'),
subwindow = document.createElement('div'),
card = document.createElement('div'),
close_button = document.createElement('div'),
if ( ! emote_data )
return "???";
closer = function() { container.parentElement.removeChild(container) };
if ( typeof emote_data == "string" )
return emote_data;
container.className = 'twitch_subwindow_container';
container.id = 'ffz-modal-container';
if ( ! force_update && emote_data.tooltip )
return emote_data.tooltip;
subwindow.className = 'twitch_subwindow ffz-subwindow';
subwindow.style.width = '100%';
subwindow.style.maxWidth = (width||420) + 'px';
return emote_data.tooltip = data_to_tooltip(emote_data);
card.className = 'card';
close_button.className = 'modal-close-button';
close_button.innerHTML = constants.CLOSE;
close_button.addEventListener('click', function() {
closer();
if ( on_close )
on_close(false);
});
container.appendChild(subwindow);
subwindow.appendChild(card);
subwindow.appendChild(close_button);
card.appendChild(contents);
var el = document.querySelector('app-main');
if ( el )
el.parentElement.insertBefore(container, el.nextSibling);
else
document.body.appendChild(container);
return closer;
},
load_emote_data = function(id, code, success, data) {
if ( ! success )
return code;
if ( code )
data.code = code;
ember_lookup = function(thing) {
if ( ! window.App )
return;
this._twitch_emotes[id] = data;
var tooltip = build_tooltip.bind(this)(id);
var images = document.querySelectorAll('img[data-emote="' + id + '"]');
for(var x=0; x < images.length; x++)
images[x].title = tooltip;
return tooltip;
if ( App.__deprecatedInstance__ && App.__deprecatedInstance__.registry && App.__deprecatedInstance_.registry.lookup )
return App.__deprecatedInstance__.registry.lookup(thing);
if ( App.__container__ && App.__container__.lookup )
return App.__container__.lookup(thing);
};
module.exports = {
build_srcset: build_srcset,
build_tooltip: build_tooltip,
load_emote_data: load_emote_data,
module.exports = FFZ.utils = {
ember_views: function() {
return ember_lookup('-view-registry:main') || {};
},
ember_lookup: ember_lookup,
ember_resolve: function(thing) {
if ( ! window.App )
return;
if ( App.__deprecatedInstance__ && App.__deprecatedInstance__.registry && App.__deprecatedInstance_.registry.resolve )
return App.__deprecatedInstance__.registry.resolve(thing);
if ( App.__container__ && App.__container__.resolve )
return App.__container__.resolve(thing);
},
build_srcset: build_srcset,
/*build_tooltip: build_tooltip,
load_emote_data: load_emote_data,*/
api: {
del: function(u,d,o,t) { return api_call('del', u,d,o,t); },
get: function(u,d,o,t) { return api_call('get', u,d,o,t); },
post: function(u,d,o,t) { return api_call('post', u,d,o,t); },
put: function(u,d,o,t) { return api_call('put', u,d,o,t); }
},
show_modal: show_modal,
prompt: function(title, description, old_value, callback, width) {
var contents = document.createElement('div'),
heading = document.createElement('div'),
form = document.createElement('form'),
input, close_btn, okay_btn;
contents.className = 'text-content';
heading.className = 'content-header';
heading.innerHTML = '<h4>' + title + '</h4>';
form.innerHTML = '<div class="item">' + (description ? '<p>' + description + '</p>' : '') + '<input type="text"></div><div class="buttons"><a class="js-subwindow-close button"><span>Cancel</span></a><button class="button primary" type="submit"><span>OK</span></button></div>';
contents.appendChild(heading);
contents.appendChild(form);
input = form.querySelector('input');
close_btn = form.querySelector('.js-subwindow-close');
okay_btn = form.querySelector('.button.primary');
if ( old_value !== undefined )
input.value = old_value;
var closer,
cb = function(success) {
closer();
if ( ! callback )
return;
callback(success ? input.value : null);
};
closer = show_modal(contents, cb, width);
form.addEventListener('submit', function(e) { e.preventDefault(); cb(true); return false });
okay_btn.addEventListener('click', function(e) { e.preventDefault(); cb(true); return false });
close_btn.addEventListener('click', function(e) { e.preventDefault(); cb(false); return false });
},
last_minute: function() {
var now = new Date();
if ( now.getSeconds() >= 30 )
now.setMinutes(now.getMinutes()+1);
now.setSeconds(0);
now.setMilliseconds(0);
return now.getTime();
},
maybe_chart: function(series, point, render, force) {
var len = series.data.length;
if ( force || point.y !== null || (len > 0 && series.data[len-1].y !== null) ) {
series.addPoint(point, render);
return true;
}
return false;
},
update_css: function(element, id, css) {
var all = element.innerHTML,
@ -338,7 +334,11 @@ module.exports = {
tooltip_placement: function(margin, prefer) {
return function() {
var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
var pref = prefer;
if ( typeof pref === "function" )
pref = pref.call(this);
var dir = {ns: pref[0], ew: (pref.length > 1 ? pref[1] : false)},
$this = $(this),
half_width = $this.width() / 2,
half_height = $this.height() / 2,
@ -354,12 +354,10 @@ module.exports = {
}
},
splitIRCMessage: splitIRCMessage,
parseIRCTags: parseIRCTags,
uncompressEmotes: uncompressEmotes,
emoji_to_codepoint: emoji_to_codepoint,
codepoint_to_emoji: codepoint_to_emoji,
parse_date: parse_date,
@ -418,7 +416,7 @@ module.exports = {
var seconds = elapsed % 60,
minutes = Math.floor(elapsed / 60),
hours = Math.floor(minutes / 60),
days = "";
days = null;
minutes = minutes % 60;
@ -431,7 +429,7 @@ module.exports = {
days = ( days > 0 ) ? days + " days, " : "";
}
return days + ((!no_hours || days || hours) ? ((days && hours < 10 ? "0" : "") + hours + ':') : '') + (minutes < 10 ? "0" : "") + minutes + (no_seconds ? "" : (":" + (seconds < 10 ? "0" : "") + seconds));
return (days||'') + ((!no_hours || days || hours) ? ((days && hours < 10 ? "0" : "") + hours + ':') : '') + (minutes < 10 ? "0" : "") + minutes + (no_seconds ? "" : (":" + (seconds < 10 ? "0" : "") + seconds));
},
duration_string: function(val) {

388
style.css
View file

@ -19,6 +19,7 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
cursor: pointer;
}
.ffz-hide-recommended-channels .js-recommended-channels,
.ffz-hide-recent-past-broadcast .recent-past-broadcast,
.ffz-hide-view-count .stat.twitch-channel-views,
.ffz-minimal-chat-input .chat-interface .emoticon-selector-toggle,
@ -125,7 +126,7 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
}
.ember-chat .chat-menu.ffz-ui-popup { padding: 0; }
.ember-chat .chat-menu.ffz-ui-popup { padding: 0 }
.ffz-button {
float: right;
@ -216,8 +217,8 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
color: #aaa;
position: absolute;
top: -25px;
left: 120px;
top: -20px;
left: 10px;
right: 10px;
z-index: 7;
opacity: 0.95;
@ -525,6 +526,31 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
margin: 0;
}
.emoticon-selector .emoticon-grid.ffz-no-emotes img {
padding: 5px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.emoticon-selector .emoticon-grid.ffz-no-emotes {
padding: 10px 20px;
}
.emoticon.ffz-favorite { position: relative; }
.favorites-grid:not(:empty) + .ffz-no-emotes,
.favorites-grid .emoticon.ffz-favorite:before { display: none }
.emoticon.ffz-favorite:before {
content: "";
display: block;
height: 14px; width: 14px;
position: absolute;
right: 0; bottom: 0;
background: url("//cdn.frankerfacez.com/script/emoticon_favorite.png") no-repeat;
}
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content.menu-side-padding { padding-left: 20px; padding-right: 20px; }
.emoticon-grid.collapsed span,
@ -584,9 +610,31 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
.ffz-ui-sub-menu-page,
.ffz-ui-menu-page { overflow-y: auto; }
.ffz-ui-menu-page[data-page="about"],
.ffz-ui-menu-page[data-page="about"] .ffz-ui-sub-menu-page .chat-menu-content:first-child {
padding: 20px 0 10px;
}
.ffz-ui-sub-menu-page[data-page="about"],
.ffz-ui-menu-page .chat-menu-content p { padding: 0 20px; }
.ffz-ui-menu-page .version-list span { float: right }
.ffz-ui-menu-page .version-list li:not(:last-of-type) {
border-bottom: 1px dotted rgba(127,127,127,0.5);
}
.ffz-ui-menu-page pre {
box-shadow: none !important;
white-space: pre-wrap;
}
#ffz-old-news-button {
text-align: center;
text-transform: none;
padding-bottom: 15px;
}
#ffz-old-news { display: none }
.chat-container.dark .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
.app-main.theatre .chat-container .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
@ -618,7 +666,7 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid:first-of-type .heading {
border-top: none;
padding-top: 0;
background-position-y: 0;
background-position: 20px 0;
}
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content {
@ -661,6 +709,7 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
list-style-type: none;
border-top: 1px solid rgba(0,0,0,0.2);
background-color: #eee;
margin: 0 1px 1px;
}
.ffz-ui-popup ul.menu:not(.sub-menu) {
@ -695,7 +744,10 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
float: left;
}
.ffz-ui-popup ul.sub-menu { background-color: #dfdfdf; }
.ffz-ui-popup ul.sub-menu {
background-color: #dfdfdf;
margin: 0 1px;
}
.app-main.theatre .ffz-ui-popup ul.sub-menu,
.chat-container.dark .ffz-ui-popup ul.sub-menu,
@ -814,6 +866,10 @@ span.ffz-handle:after { left: 8px }
border-right: 1px solid rgba(0,0,0,0.2);
}
.ffz-ui-popup.dark ul.menu { border-top-color: rgba(255,255,255,0.1) }
.ffz-ui-popup.dark ul.menu a { border-left-color: rgba(255,255,255,0.1) }
.ffz-ui-popup.dark ul.sub-menu a { border-right-color: rgba(255,255,255,0.1) }
.ffz-ui-popup ul.menu li.active {
background-color: #fff;
}
@ -898,12 +954,43 @@ span.ffz-handle:after { left: 8px }
/* BTTV Menu Fixes */
.bttv-incompatibility {
padding-top: 8px !important;
}
.ffz-ui-popup.dark .emoticon-grid .heading,
.ffz-ui-popup.dark li.title { color: #fff; }
.ffz-ui-popup.dark .ffz-ui-menu-page { background-color: #1e1e1e; }
.ffz-ui-popup.dark .ffz-ui-menu-page { background-color: #101010; }
.ffz-bttv .ffz-ui-popup .chat-menu-content p a {
padding: 0;
}
.ffz-bttv .ffz-ui-popup.dark ul.menu,
.ffz-bttv .ffz-ui-popup.dark .ffz-ui-menu-page { margin: 0 }
.ffz-ui-popup.dark .ffz-ui-menu-page .chat-menu-content .heading,
.ffz-ui-popup.dark .ffz-ui-menu-page .emoticon-grid .heading {
border-color: rgba(255,255,255,0.1);
}
.ffz-ui-popup.dark ul.menu:not(.sub-menu),
.ffz-ui-popup.dark .ffz-ui-menu-page { border: 1px solid rgba(255,255,255,0.1) }
.ffz-ui-popup.dark .ffz-ui-menu-page { border-bottom: none }
.ffz-bttv .emoticon.emoji { padding-top: 0 }
/* Host Menu */
.ffz-subwindow input {
padding: 5px;
}
.ffz-subwindow p {
margin-bottom: 0;
padding: 0 0 10px;
}
.ffz-subwindow .card,
.ffz-channel-selector {
background-image: url("//cdn.frankerfacez.com/script/zreknarf-bg.png");
background-repeat: no-repeat;
@ -946,6 +1033,29 @@ span.ffz-handle:after { left: 8px }
}
/* Oh my god this is awful why does Chat Replay move chat lines? */
.app-main.theatre .chatReplay .chat-messages .chat-line .mod-icons,
.ffz-dark .chatReplay .chat-messages .chat-line .mod-icons {
background-color: rgba(0,0,0,0.8);
}
.chatReplay .chat-messages .chat-line .mod-icons {
position: absolute;
z-index: 10;
left: 0; top: 2px; bottom: 2px;
padding: 4px 5px 4px 4px;
background: rgba(255,255,255,0.8);
transition: margin-left .08s linear;
}
.chatReplay .chat-messages .chat-line:hover .mod-icons {
margin-left: 0;
}
.chatReplay .chat-messages .horizontal-line { margin: 4px 0 }
/* Positioning Fixes */
body:not(.ffz-bttv) .ember-chat .chat-settings { bottom: 40px; }
@ -953,8 +1063,36 @@ body:not(.ffz-bttv) .notification-controls .notify-menu { bottom: 25px; }
body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
/* Dashboard Changes */
#dash_main #stream-config-status, #chart_container.ffz-stat-chart { margin: 20px auto 0 }
/*#dash_main #video_player { height: 303.75px }
#stream-config-status { display: none }
#delay_controls { float: left }
#delay_controls + #commercial_buttons { float: right }
#delay_controls + #commercial_buttons + div { clear: both }
#delay_controls, #delay_controls + #commercial_buttons {
width: 50%;
margin-bottom: 20px;
}
#dash_main #delay_controls, #dash_main #commercial_buttons {
margin-top: 0;
padding-top: 0;
border-top: none;
}
#dash_main #delay_controls .section_header, #commercial_buttons .section_header { padding-top: 0 }
#dash_main #delay_controls .ui-slider { margin-top: 18px }*/
/* Menu Scrollbar */
.conversations-list .scroll-container::-webkit-scrollbar,
.chatters-container::-webkit-scrollbar,
.ffz-scrollbar::-webkit-scrollbar,
.ember-chat .chat-settings::-webkit-scrollbar,
@ -965,9 +1103,11 @@ body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
.emoticon-selector-box .all-emotes::-webkit-scrollbar,
.ffz-ui-sub-menu-page::-webkit-scrollbar,
.ffz-ui-menu-page::-webkit-scrollbar {
height: 6px;
width: 6px;
}
.conversations-list .scroll-container::-webkit-scrollbar-thumb,
.chatters-container::-webkit-scrollbar-thumb,
.ffz-scrollbar::-webkit-scrollbar-thumb,
.ember-chat .chat-settings::-webkit-scrollbar-thumb,
@ -983,12 +1123,14 @@ body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
box-shadow: 0 0 1px 1px rgba(255,255,255,0.25);
}
.ffz-dark .conversations-list .scroll-container::-webkit-scrollbar-thumb,
.ffz-dark .ffz-scrollbar::-webkit-scrollbar-thumb,
.ffz-dark .table::-webkit-scrollbar-thumb,
.ffz-dark .conversation-window .conversation-content::-webkit-scrollbar-thumb,
.ffz-dark .conversations-list .conversations-list-inner::-webkit-scrollbar-thumb,
.ffz-dark .conversation-input-bar .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
.theatre .conversations-list .scroll-container::-webkit-scrollbar-thumb,
.theatre .chatters-container::-webkit-scrollbar-thumb,
.theatre .ffz-scrollbar::-webkit-scrollbar-thumb,
.theatre .ember-chat .chat-settings::-webkit-scrollbar-thumb,
@ -1027,6 +1169,7 @@ body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
/* Fix Moderation Cards */
img.channel_background[src=""],
img.channel_background[src="null"] { display: none; }
.ffz-moderation-card {
@ -1034,7 +1177,11 @@ img.channel_background[src="null"] { display: none; }
max-width: 340px;
}
.ffz-moderation-card .extra-interface {
.ember-chat .ffz-moderation-card .close-button {
right: 7px;
}
.ember-chat .ffz-moderation-card .extra-interface {
padding-top: 0;
}
@ -1043,7 +1190,9 @@ img.channel_background[src="null"] { display: none; }
}
.ffz-moderation-card.ffz-has-info h3.name {
margin-top: 0;
margin-top: -2px;
margin-bottom: 0;
padding-top: 0;
white-space: nowrap;
}
@ -1054,6 +1203,11 @@ img.channel_background[src="null"] { display: none; }
margin-left: 50px;
height: 18px;
line-height: 18px;
pointer-events: none;
}
.ffz-moderation-card .info .stat {
pointer-events: auto;
}
.ffz-moderation-card .info.channel-stats .stat {
@ -1108,7 +1262,7 @@ img.channel_background[src="null"] { display: none; }
text-shadow: black 0 0 5px;
}
.ffz-moderation-card .channel_background {
.ember-chat .ffz-moderation-card .channel_background {
width: 100%;
top: 0;
}
@ -1156,13 +1310,14 @@ img.channel_background[src="null"] { display: none; }
text-decoration: none;
font-size: 18px;
font-weight: bold;
margin-top: -1px !important;
color: #888 !important;
}
/* Chat Rows */
.ffz-alias { font-style: italic; }
.ffz-alias-italics .ffz-alias { font-style: italic; }
.ember-chat .chat-messages .chat-line.ffz-has-deleted {
line-height: 30px;
@ -1184,7 +1339,8 @@ img.channel_background[src="null"] { display: none; }
text-decoration: none;
}
body:not(.ffz-bttv) .more-messages-indicator {
body:not(.ffz-bttv) .ember-chat-container:not(.chatReplay) .more-messages-indicator,
body:not(.ffz-bttv) .chat-container:not(.chatReplay) .more-messages-indicator {
/* This looks better when it's full width. */
margin: 0 -20px;
}
@ -1199,6 +1355,10 @@ body:not(.ffz-bttv) .more-messages-indicator {
/* Emoticon Tooltips */
.ffz-wide-tip hr {
margin: 5px 0;
}
.ffz-wide-tip .tipsy-inner {
min-width: 300px;
max-width: 600px;
@ -1310,6 +1470,8 @@ body:not(.ffz-bttv) .more-messages-indicator {
/* Dumb Fixes */
.channel-stats .stat { vertical-align: top; }
.ffz-bttv .no-bttv { display: none; }
.chat-container.dark, .app-main.theatre .chat-container,
@ -1320,6 +1482,15 @@ body:not(.ffz-bttv) .more-messages-indicator {
}
/* Banned Words */
.deleted-word {
font-weight: bold;
opacity: 0.35;
cursor: pointer;
}
/* Unsafe Links */
a.unsafe-link {
@ -1641,6 +1812,10 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
.ember-chat .chat-interface .more-messages-indicator.ffz-freeze-indicator {
opacity: 1;
cursor: default;
pointer-events: none;
}
.chat-container:not(.chatReplay) .chat-interface .more-messages-indicator.ffz-freeze-indicator {
top: 0;
}
@ -1669,6 +1844,9 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
margin-right: 0px;
}
.chat-history .chat-line.admin .from,
.chat-history .chat-line.admin .colon { display: none }
.chat-history .chat-line.admin .message { color: #666; }
.chat-history .timestamp {
@ -1878,13 +2056,21 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
/* No Blue */
.ffz-no-blue .theatre .conversations-list-icon,
.ffz-no-blue.ffz-dark .conversations-list-icon,
.ffz-no-blue #carousel .nav {
background-color: rgba(25,25,25,.5);
}
.ffz-no-blue .theatre .conversations-list-bottom-bar,
.ffz-no-blue.ffz-dark .conversations-list-bottom-bar,
.ffz-no-blue .theatre .conversations-list,
.ffz-no-blue.ffz-dark .conversations-list,
.ffz-no-blue .theatre .conversations-list-header,
.ffz-no-blue .theatre .conversation-window,
.ffz-no-blue.ffz-dark .conversation-window,
.ffz-no-blue .theatre .conversation-system-message,
.ffz-no-blue.ffz-dark .conversation-system-message,
.ffz-no-blue .warp,
.ffz-no-blue #large_nav .content,
.ffz-no-blue #small_nav .content,
.ffz-no-blue .chat-container.dark,
@ -1940,10 +2126,18 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
.ffz-no-blue .takeover #carousel,
.ffz-no-blue #carousel_and_background,
.ffz-no-blue #carousel .items .pic img,
.ffz-no-blue .app-main.theatre .archives-contain,
.ffz-no-blue.ffz-dark .archives-contain,
.ffz-no-blue #content .turbo_landing {
background-color: #191919;
}
.ffz-no-blue .warp__anchor,
.ffz-no-blue .warp__item--anchor,
.ffz-no-blue .warp__drawer,
.ffz-no-blue .leaf,
.ffz-no-blue .app-main.theatre .archives-contain .list-video:hover,
.ffz-no-blue.ffz-dark .archives-contain .list-video:hover,
.ffz-no-blue .theatre .moderation-card .back-button,
.ffz-no-blue .dark .moderation-card .back-button,
.ffz-no-blue .force-dark .moderation-card .back-button
@ -1956,8 +2150,8 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
background-color: #232323;
}
.ffz-no-blue .theatre .conversations-list-icon,
.ffz-no-blue.ffz-dark .conversations-list-icon,
.ffz-no-blue .theatre .conversations-list-bottom-bar,
.ffz-no-blue.ffz-dark .conversations-list-bottom-bar,
.ffz-no-blue .theatre .conversations-list .conversation-preview-line,
.ffz-no-blue.ffz-dark .conversations-list .conversation-preview-line,
.ffz-no-blue .theatre .conversation-window,
@ -1991,7 +2185,8 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
.ffz-no-blue .theatre .conversation-header,
.ffz-no-blue.ffz-dark .conversation-header,
.ffz-no-blue .theatre .conversations-list .conversations-list-header,
.ffz-no-blue .theatre .conversations-list .search-divider,
.ffz-no-blue.ffz-dark .conversations-list .search-divider,
.ffz-no-blue.ffz-dark .conversations-list .conversations-list-header,
.ffz-no-blue .theatre .conversation-window.has-focus .conversation-header,
.ffz-no-blue.ffz-dark .conversation-window.has-focus .conversation-header,
@ -2036,14 +2231,13 @@ li[data-name="following"] a {
padding: 2px 5px;
}
#large_nav .game_filter.selected .ffz-follow-count { right: 13px; }
#small_nav .ffz-follow-count {
position: absolute;
bottom: 2px;
right: 2px;
padding: 0 2px;
right: 5px;
padding: 2px;
font-size: 10px;
line-height: 10px;
background-color: #191919;
color: rgba(255,255,255,0.5);
}
@ -2053,8 +2247,6 @@ li[data-name="following"] a {
background-color: #101014;
}
#small_nav .game_filter.selected .ffz-follow-count { right: 5px; }
/* Image Tooltips */
.ffz-image-hover {
@ -2109,7 +2301,9 @@ li[data-name="following"] a {
}
.ffz-classic-player:not(.ffz-top-conversations) .app-main.theatre .player .player-controls-bottom,
.ffz-classic-player .app-main.theatre .player[data-fullscreen="true"] .player-controls-bottom {
.ffz-classic-player:not(.ffz-top-conversations) .app-main.theatre .player[data-controls=true] .player-controls-bottom,
.ffz-classic-player .app-main.theatre .player[data-fullscreen="true"] .player-controls-bottom,
.ffz-classic-player .app-main.theatre .player[data-fullscreen="true"][data-controls=true] .player-controls-bottom {
padding-bottom: 0;
}
@ -2174,6 +2368,13 @@ li[data-name="following"] a {
/* Directory Logos */
.item .meta .title a a,
.item .meta .title a img { pointer-events: none }
.ffz-directory-logo .meta p.title {
padding-top: 4px;
}
.ffz-directory-logo .meta p { width: auto; }
.ffz-directory-logo .profile-photo {
@ -2240,11 +2441,16 @@ body:not(.ffz-bttv) .conversation-window .new-message-divider + .timestamp-line
margin-top: -3px;
}
/* Fix the ignore-cta covering up messages with no way to dismiss it. */
body:not(.ffz-bttv) .conversation-window .ignore-cta + .conversation-content {
/* ignore-cta is a bit silly right now */
body:not(.ffz-bttv) .conversation-window .ignore-cta:not(.hidden) + .conversation-content {
padding-top: 76px;
}
.conversation-window .conversation-system-message.ignore-cta-container {
padding-right: 26px;
}
/* Hide that which should be hidden. */
.conversation-window.collapsed .ignore-cta,
.conversation-chat-line.action .colon,
@ -2255,25 +2461,11 @@ body:not(.ffz-conv-title-clickable) .conversation-header a.conversation-header-n
.conversation-window.has-unread .conversation-header .conversation-header-name { color: #fff !important; }
.ffz-top-conversations:not(.ffz-bttv) .conversation-window.collapsed .conversation-header {
box-shadow: none;
}
.conversations-list:not(.ffz-bttv) .conversations-list-item:last-of-type {
border-bottom: none !important;
}
/* Top Conversations */
.ffz-top-conversations:not(.ffz-bttv) .conversations-content .conversations-list-icon,
.ffz-top-conversations:not(.ffz-bttv) .conversation-window {
border: 1px solid #dedede;
border-top: 0;
}
.ffz-top-conversations.ffz-dark:not(.ffz-bttv) .conversations-content .conversations-list-icon,
.ffz-top-conversations.ffz-dark:not(.ffz-bttv) .conversation-window,
.ffz-top-conversations:not(.ffz-bttv) .theatre .conversations-content .conversations-list-icon,
.ffz-top-conversations:not(.ffz-bttv) .theatre .conversation-window {
border-color: rgba(255,255,255,0.2);
body:not(.ffz-top-conversations) .conversations-list-bottom-bar {
border-bottom: none;
}
.ffz-top-conversations .conversations-content {
@ -2287,47 +2479,25 @@ body:not(.ffz-conv-title-clickable) .conversation-header a.conversation-header-n
top: 0px;
}
.ffz-top-conversations .conversations-list {
bottom: inherit;
top: 54px;
.ffz-top-conversations .conversation-window.collapsed .conversation-header {
box-shadow: none;
}
.ffz-top-conversations.ffz-bttv .conversations-list { top: 46px; }
.ffz-top-conversations.ffz-bttv .conversations-list:before,
.ffz-top-conversations.ffz-bttv .conversations-list:after { display: none; }
.ffz-top-conversations:not(.ffz-bttv) .conversations-list:before,
.ffz-top-conversations:not(.ffz-bttv) .conversations-list:after {
bottom: 100%;
top: auto;
.ffz-top-conversations .conversations-list-container.list-displayed {
top: 243px;
}
.ffz-top-conversations:not(.ffz-bttv) .conversations-list:before {
border-top-color: transparent;
border-bottom-color: #dedede;
.ffz-top-conversations .conversations-list-bottom-bar {
border-top: none;
}
.ffz-top-conversations:not(.ffz-bttv) .conversations-list:after {
border-top-color: transparent;
border-bottom-color: #f2f2f2;
.ffz-top-conversations .conversations-content .total-unread {
top: inherit;
bottom: -10px;
}
.ffz-dark.ffz-top-conversations:not(.ffz-bttv) .conversations-list:before,
.ffz-top-conversations:not(.ffz-bttv) .theatre .conversations-list:before {
border-top-color: transparent;
border-bottom-color: #32323e;
}
.ffz-dark.ffz-top-conversations:not(.ffz-bttv) .conversations-list:after,
.ffz-top-conversations:not(.ffz-bttv) .theatre .conversations-list:after {
border-top-color: transparent;
border-bottom-color: #121212;
}
.ffz-top-conversations .theatre .player-controls-bottom {
.ffz-top-conversations .theatre .player-controls-bottom,
.ffz-top-conversations .theatre .player[data-controls=true] .player-controls-bottom {
padding-bottom: 0;
}
@ -2337,8 +2507,52 @@ body:not(.ffz-conv-title-clickable) .conversation-header a.conversation-header-n
.ffz-top-conversations #directory-list { padding-top: 50px; }
/* Minimize Conversations */
/*.ffz-minimize-conversations .conversation-window,*/
.ffz-minimize-conversations .conversations-list-container {
transition: bottom ease-in-out 75ms, top ease-in-out 75ms, padding-bottom ease-in-out 75ms, padding-top ease-in-out 75ms;
}
.ffz-minimize-conversations .conversations-list-container,
.ffz-minimize-conversations .conversations-content:hover {
pointer-events: all;
}
.ffz-minimize-conversations:not(.ffz-top-conversations) .conversations-content:hover .conversation-window.collapsed:not(.has-unread),
.ffz-minimize-conversations:not(.ffz-top-conversations) .conversations-content:hover .conversations-list-container:not(.list-displayed) {
bottom: 0;
padding-top: 0;
}
.ffz-minimize-conversations:not(.ffz-top-conversations) .conversation-window.collapsed:not(.has-unread),
.ffz-minimize-conversations:not(.ffz-top-conversations) .conversations-list-container:not(.list-displayed) {
bottom: -30px;
padding-top: 30px;
}
.ffz-minimize-conversations.ffz-top-conversations .conversations-content:hover .conversation-window.collapsed:not(.has-unread),
.ffz-minimize-conversations.ffz-top-conversations .conversations-content:hover .conversations-list-container:not(.list-displayed) {
top: 0;
padding-bottom: 0;
}
.ffz-minimize-conversations.ffz-top-conversations .conversation-window.collapsed:not(.has-unread),
.ffz-minimize-conversations.ffz-top-conversations .conversations-list-container:not(.list-displayed) {
top: -30px;
padding-bottom: 30px;
}
/* Following Page */
.directory_header .nav li.ffz-manage-following {
float: right;
margin-right: 0;
}
.user.item { position: relative; }
.user.item .overlay_info.length {
@ -2452,3 +2666,25 @@ body:not(.ffz-creative-showcase) .creative-hero,
color: #fff !important;
background-color: #6441a5 !important;
}
/* Content-Box~! Down with Twitch randomly changing the box-sizing for everything! */
#ffz-channel-table *,
#ffz-group-table *,
.ffz-ui-popup * {
box-sizing: content-box;
}
/* Dank * /
.streams .ember-view[data-channel="memeathon"] .stream .thumb:after {
content: "";
position: absolute;
top: 0; bottom: 0;
left: 0; right: 0;
background: url("//cdn.frankerfacez.com/script/wow.gif") no-repeat;
background-size: contain;
background-position: bottom right;
pointer-events: none;
}