1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +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 */ /* main column */
body.ffz-dark,
.ffz-dark div#mantle_skin, .ffz-dark div#mantle_skin,
.ffz-dark div#main_col { .ffz-dark div#main_col {
background:rgb(16,16,16); background:rgb(16,16,16);
@ -143,9 +144,14 @@
/* Popups */ /* Popups */
.ffz-dark #commission_modal {
background-color: #101010 !important;
border-color: #32323e !important;
}
.ffz-dark .conversation-settings-menu, .ffz-dark .conversation-settings-menu,
.ffz-dark .ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box, .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 #flyout .content,
.ffz-dark .whatisthis, .ffz-dark .whatisthis,
.ffz-dark .ui-menu, .ffz-dark .ui-menu,
@ -164,6 +170,7 @@
background-color: rgb(16,16,16); background-color: rgb(16,16,16);
color: rgb(195,195,195); /*#acacbf;*/ color: rgb(195,195,195); /*#acacbf;*/
border-color: #32323e; border-color: #32323e;
box-shadow: rgba(255,255,255,0.2) 0 0 0 1px inset;
} }
.ffz-dark .st-autocomplete-sidebar .label, .ffz-dark .st-autocomplete-sidebar .label,
@ -207,6 +214,7 @@
.ffz-dark .change-banner .banner-preview, .ffz-dark .change-banner .banner-preview,
.ffz-dark form.js-new_panel_form input, .ffz-dark form.js-new_panel_form input,
.ffz-dark form.js-new_panel_form textarea, .ffz-dark form.js-new_panel_form textarea,
.ffz-dark .conversation-input-bar textarea,
.ffz-dark .card input, .ffz-dark .card input,
.ffz-dark .card textarea, .ffz-dark .card textarea,
.ffz-dark .dropmenu input, .ffz-dark .dropmenu input,
@ -216,6 +224,7 @@
.ffz-dark textarea, .ffz-dark textarea,
.ffz-dark select, .ffz-dark select,
.ffz-dark option, .ffz-dark option,
.ffz-dark #mantle_skin .dropdown,
.ffz-dark .directory_header #custom_filter input { .ffz-dark .directory_header #custom_filter input {
background-color: rgba(255,255,255,0.05); background-color: rgba(255,255,255,0.05);
border-color: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.1);
@ -379,6 +388,17 @@
color: #fff; 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 */ /* Subscriptions Page */
@ -715,6 +735,25 @@
/* Dashboard */ /* 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 { .ffz-dark .js-stream-key-button-container .button {
margin: 15px auto; margin: 15px auto;
} }
@ -824,6 +863,13 @@
border-right-color: rgba(255,255,255,0.2); 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 { .ffz-dark .dash-hostmode-list-contain {
border-top-color: rgba(255,255,255,0.2); border-top-color: rgba(255,255,255,0.2);
} }
@ -909,15 +955,111 @@
} }
/* Conversations */ /* VoDs */
.ffz-dark .ignore-cta { .ffz-dark .app-main .chatReplay .notice-wrapper .svg-logo_glitch {
background-color: #333; fill: #242424 !important;
box-shadow: 0 3px 0 #000;
} }
.ffz-dark .ignore-cta .conversation-system-message { .ffz-dark .app-main .chatReplay .notice-wrapper {
color: #ccc; 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 { .ffz-dark .conversation-input-bar .emoticon-selector-toggle svg path {
@ -954,6 +1096,32 @@
border-bottom-color: rgb(16,16,16); 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 { .ffz-dark .conversations-list-icon {
background: #19191f; background: #19191f;
color: #8c8c9c; color: #8c8c9c;
@ -1054,4 +1222,4 @@
.ffz-dark .conversation-window .new-message-divider span { background: transparent; } .ffz-dark .conversation-window .new-message-divider span { background: transparent; }
.ffz-dark .conversation-window .timestamp-line:after, .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 // Server Dependencies
var http = require("http"), var http = require("http"),
//https = require("https"), https = require("https"),
net = require('net'),
path = require("path"), path = require("path"),
request = require("request"), request = require("request"),
url = require("url"); url = require("url");
@ -220,7 +221,21 @@ gulp.task('server', function() {
fs.exists(file, function(exists) { fs.exists(file, function(exists) {
if ( ! exists ) { if ( ! exists ) {
util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.bold.blue("CDN") + " GET " + util.colors.magenta(uri)); 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": "*"}; var headers = {"Access-Control-Allow-Origin": "*"};
@ -242,8 +257,33 @@ gulp.task('server', function() {
}; };
http.createServer(handle_req).listen(8000, "localhost"); if ( fs.existsSync("dev_key.pem") ) {
//https.createServer(handle_req).listen(8000, "localhost"); 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'), constants = require('./constants'),
utils = require('./utils'), utils = require('./utils'),
MOD_BADGES = [ SPECIAL_BADGES = [
['staff', 'staff', 'Staff'], ['staff', 'staff', 'Staff'],
['admin', 'admin', 'Admin'], ['admin', 'admin', 'Admin'],
['global_mod', 'global-moderator', 'Global Moderator'], ['global_mod', 'global-moderator', 'Global Moderator']
['mod', 'moderator', 'Moderator']
], ],
badge_css = function(badge) { 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 = { FFZ.settings_info.legacy_badges = {
type: "select", type: "select",
options: { options: {
@ -120,6 +134,9 @@ FFZ.prototype.setup_badges = function() {
this.toggle_style('badges-circular-small', val === 4); this.toggle_style('badges-circular-small', val === 4);
this.toggle_style('badges-transparent', val === 5); this.toggle_style('badges-transparent', val === 5);
document.body.classList.toggle('ffz-transparent-badges', 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); 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 ) { if ( full_badge.visible !== undefined ) {
var visible = full_badge.visible; var visible = full_badge.visible;
if ( typeof visible === "function" ) if ( typeof visible === "function" )
visible = visible.bind(this)(room_id, user, msg, badges); visible = visible.call(this, room_id, user, msg, badges);
if ( ! visible ) if ( ! visible )
continue; continue;
@ -215,26 +232,34 @@ FFZ.prototype.get_badges = function(user, room_id, badges, msg) {
FFZ.prototype.get_line_badges = function(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'}; badges[0] = {klass: 'broadcaster', title: 'Broadcaster'};
else if ( msg.labels ) else {
for(var i=0, l = MOD_BADGES.length; i < l; i++) { for(var i=0, l = SPECIAL_BADGES.length; i < l; i++) {
var mb = MOD_BADGES[i]; var mb = SPECIAL_BADGES[i];
if ( msg.labels.indexOf(mb[0]) !== -1 ) { if ( tags['user-type'] === mb[0] || labels.indexOf(mb[0]) !== -1 ) {
badges[0] = {klass: mb[1], title: mb[2]} badges[0] = {klass: mb[1], title: mb[2]}
break; 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'} 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'}; badges[15] = {klass: 'turbo', title: 'Turbo'};
// FFZ Badges // 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 ) if ( room_id && user_id === room_id )
badges[0] = {klass: 'broadcaster', title: 'Broadcaster'}; badges[0] = {klass: 'broadcaster', title: 'Broadcaster'};
else else
for(var i=0, l = MOD_BADGES.length; i < l; i++) { for(var i=0, l = SPECIAL_BADGES.length; i < l; i++) {
var mb = MOD_BADGES[i]; var mb = SPECIAL_BADGES[i];
if ( user_type === mb[0] ) { if ( user_type === mb[0] ) {
badges[0] = {klass: mb[1], title: mb[2]}; badges[0] = {klass: mb[1], title: mb[2]};
break; break;
@ -325,7 +350,7 @@ FFZ.prototype.bttv_badges = function(data) {
if ( full_badge.visible !== undefined ) { if ( full_badge.visible !== undefined ) {
var visible = full_badge.visible; var visible = full_badge.visible;
if ( typeof visible == "function" ) if ( typeof visible == "function" )
visible = visible.bind(this)(null, user_id); visible = visible.call(this, null, user_id);
if ( ! visible ) if ( ! visible )
continue; continue;
@ -407,12 +432,12 @@ FFZ.prototype._legacy_add_donors = function() {
badges = user.badges = user.badges || {}; badges = user.badges = user.badges || {};
if ( ! badges[0] ) if ( ! badges[0] )
badges[0] = {id:2}; badges[1] = {id:2};
} }
// Special Badges // Special Badges
this.users.sirstendec = {badges: {1: {id:0}}, sets: [4330]}; this.users.sirstendec = {badges: {5: {id:0}}, sets: [4330]};
this.users.zenwan = {badges: {0: {id:2, image: "//cdn.frankerfacez.com/script/momiglee_badge.png", title: "WAN"}}}; this.users.zenwan = {badges: {1: {id:2, image: "//cdn.frankerfacez.com/script/momiglee_badge.png", title: "WAN"}}};
this._legacy_load_bots(); this._legacy_load_bots();
this._legacy_load_donors(); this._legacy_load_donors();
@ -421,7 +446,7 @@ FFZ.prototype._legacy_add_donors = function() {
FFZ.prototype._legacy_load_bots = function(callback, tries) { FFZ.prototype._legacy_load_bots = function(callback, tries) {
jQuery.ajax(constants.SERVER + "script/bots.txt", {context: this}) jQuery.ajax(constants.SERVER + "script/bots.txt", {context: this})
.done(function(data) { .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) { }).fail(function(data) {
if ( data.status == 404 ) if ( data.status == 404 )
@ -436,7 +461,7 @@ FFZ.prototype._legacy_load_bots = function(callback, tries) {
FFZ.prototype._legacy_load_donors = function(callback, tries) { FFZ.prototype._legacy_load_donors = function(callback, tries) {
jQuery.ajax(constants.SERVER + "script/donors.txt", {context: this}) jQuery.ajax(constants.SERVER + "script/donors.txt", {context: this})
.done(function(data) { .done(function(data) {
this._legacy_parse_badges(callback, data, 1, 1); this._legacy_parse_badges(callback, data, 5, 1);
}).fail(function(data) { }).fail(function(data) {
if ( data.status == 404 ) if ( data.status == 404 )

View file

@ -1,4 +1,5 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
utils = require('./utils'),
hue2rgb = function(p, q, t) { hue2rgb = function(p, q, t) {
if ( t < 0 ) t += 1; if ( t < 0 ) t += 1;
@ -29,6 +30,8 @@ FFZ.settings_info.fix_color = {
}, },
value: '1', value: '1',
visible: function() { return localStorage.hasOwnProperty("ffz_setting_fix_color") },
category: "Chat Appearance", category: "Chat Appearance",
no_bttv: true, no_bttv: true,
@ -64,24 +67,31 @@ FFZ.settings_info.luv_contrast = {
help: "Set the minimum contrast ratio used by Luv Adjustment to ensure colors are readable.", help: "Set the minimum contrast ratio used by Luv Adjustment to ensure colors are readable.",
method: function() { method: function() {
var old_val = this.settings.luv_contrast, var f = this,
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); old_val = this.settings.luv_contrast;
if ( new_val === null || new_val === undefined ) utils.prompt(
return; "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); var parsed = parseFloat(new_val);
if ( parsed === NaN || parsed < 1 ) if ( Number.isNaN(parsed) || ! Number.isFinite(parsed) )
parsed = 4.5; parsed = 4.5;
this.settings.set("luv_contrast", parsed); f.settings.set("luv_contrast", parsed);
});
}, },
on_update: function(val) { on_update: function(val) {
this._rebuild_contrast(); this._rebuild_contrast();
this._rebuild_filter_styles();
if ( ! this.has_bttv && this.settings.fix_color == '1' ) if ( ! this.has_bttv && this.settings.fix_color == '1' )
this._rebuild_colors(); this._rebuild_colors();
} }
}; };
@ -116,7 +126,7 @@ FFZ.settings_info.color_blind = {
FFZ.prototype.setup_colors = function() { FFZ.prototype.setup_colors = function() {
this.toggle_style('chat-colors-gray', !this.has_bttv && this.settings.fix_color === '-1'); this.toggle_style('chat-colors-gray', !this.has_bttv && this.settings.fix_color === '-1');
this._colors = {}; this._hex_colors = {};
this._rebuild_contrast(); this._rebuild_contrast();
this._update_colors(); this._update_colors();
@ -141,6 +151,9 @@ FFZ.prototype.setup_colors = function() {
FFZ.Color = {}; FFZ.Color = {};
FFZ.Color._canvas = null;
FFZ.Color._context = null;
FFZ.Color.CVDMatrix = { FFZ.Color.CVDMatrix = {
protanope: [ // reds are greatly reduced (1% men) protanope: [ // reds are greatly reduced (1% men)
0.0, 2.02344, -2.52581, 0.0, 2.02344, -2.52581,
@ -160,44 +173,66 @@ FFZ.Color.CVDMatrix = {
} }
var RGBColor = FFZ.Color.RGB = function(r, g, b) { var RGBAColor = FFZ.Color.RGBA = function(r, g, b, a) {
this.r = r||0; this.g = g||0; this.b = b||0; 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) { var HSVAColor = FFZ.Color.HSVA = function(h, s, v, a) {
this.h = h||0; this.s = s||0; this.v = v||0; 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) { var HSLAColor = FFZ.Color.HSLA = function(h, s, l, a) {
this.h = h||0; this.s = s||0; this.l = l||0; 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) { var XYZAColor = FFZ.Color.XYZA = function(x, y, z, a) {
this.x = x||0; this.y = y||0; this.z = z||0; 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) { var LUVAColor = FFZ.Color.LUVA = function(l, u, v, a) {
this.l = l||0; this.u = u||0; this.v = v||0; 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) { RGBAColor.prototype.eq = function(rgb) {
return rgb.r === this.r && rgb.g === this.g && rgb.b === this.b; 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(); rgb = rgb.trim();
if ( rgb.charAt(0) === '#' ) 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 ) { if ( match ) {
var r = match[1], var r = match[1],
g = match[2], g = match[2],
b = match[3]; b = match[3],
a = match[4];
if ( r.charAt(r.length-1) === '%' ) if ( r.charAt(r.length-1) === '%' )
r = 255 * (parseInt(r) / 100); r = 255 * (parseInt(r) / 100);
@ -214,26 +249,36 @@ RGBColor.fromCSS = function(rgb) {
else else
b = parseInt(b); 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, r), 255),
Math.min(Math.max(0, g), 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); var raw = parseInt(code.charAt(0) === '#' ? code.substr(1) : code, 16);
return new RGBColor( return new RGBAColor(
(raw >> 16), // Red (raw >> 16), // Red
(raw >> 8 & 0x00FF), // Green (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, var r, g, b,
i = Math.floor(h * 6), i = Math.floor(h * 6),
@ -251,55 +296,58 @@ RGBColor.fromHSV = function(h, s, v) {
case 5: r = v, g = p, b = q; 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, r*255), 255)),
Math.round(Math.min(Math.max(0, g*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, var R = 3.240479 * x - 1.537150 * y - 0.498535 * z,
G = -0.969256 * x + 1.875992 * y + 0.041556 * z, G = -0.969256 * x + 1.875992 * y + 0.041556 * z,
B = 0.055648 * x - 0.204043 * y + 1.057311 * z; B = 0.055648 * x - 0.204043 * y + 1.057311 * z;
// Make sure we end up in a real color space // Make sure we end up in a real color space
return new RGBColor( return new RGBAColor(
Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(R))), Math.max(0, Math.min(255, 255 * XYZAColor.channelConverter(R))),
Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(G))), Math.max(0, Math.min(255, 255 * XYZAColor.channelConverter(G))),
Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(B))) 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 ) { if ( s === 0 ) {
var v = Math.round(Math.min(Math.max(0, 255*l), 255)); 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, var q = l < 0.5 ? l * (1 + s) : l + s - l * s,
p = 2 * l - q; 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 + 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)), 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); } RGBAColor.prototype.toHSVA = function() { return HSVAColor.fromRGBA(this.r, this.g, this.b, this.a); }
RGBColor.prototype.toHSL = function() { return HSLColor.fromRGB(this.r, this.g, this.b); } RGBAColor.prototype.toHSLA = function() { return HSLAColor.fromRGBA(this.r, this.g, this.b, this.a); }
RGBColor.prototype.toCSS = function() { return "rgb(" + Math.round(this.r) + "," + Math.round(this.g) + "," + Math.round(this.b) + ")"; } 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 : "") + ")"; }
RGBColor.prototype.toXYZ = function() { return XYZColor.fromRGB(this.r, this.g, this.b); } RGBAColor.prototype.toXYZA = function() { return XYZAColor.fromRGBA(this.r, this.g, this.b, this.a); }
RGBColor.prototype.toLUV = function() { return this.toXYZ().toLUV(); } 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); var rgb = this.b | (this.g << 8) | (this.r << 16);
return '#' + (0x1000000 + rgb).toString(16).slice(1); 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]; var rgb = [this.r / 255, this.g / 255, this.b / 255];
for (var i =0, l = rgb.length; i < l; i++) { for (var i =0, l = rgb.length; i < l; i++) {
if (rgb[i] <= 0.03928) { 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 = typeof amount === "number" ? amount : 1;
amount = Math.round(255 * (amount / 100)); 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.r + amount)),
Math.max(0, Math.min(255, this.g + 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; amount = typeof amount === "number" ? amount : 1.0;
var cvd; var cvd;
if ( typeof type === "string" ) { if ( typeof type === "string" ) {
@ -366,21 +415,22 @@ RGBColor.prototype.daltonize = function(type, amount) {
G = Math.min(Math.max(0, GG + this.g), 255); G = Math.min(Math.max(0, GG + this.g), 255);
B = Math.min(Math.max(0, BB + this.b), 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); } RGBAColor.prototype._r = function(r) { return new RGBAColor(r, this.g, this.b, this.a); }
RGBColor.prototype._g = function(g) { return new RGBColor(this.r, g, this.b); } RGBAColor.prototype._g = function(g) { return new RGBAColor(this.r, g, this.b, this.a); }
RGBColor.prototype._b = function(b) { return new RGBColor(this.r, this.g, b); } 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 // HSL Colors
HSLColor.prototype.eq = function(hsl) { HSLAColor.prototype.eq = function(hsl) {
return hsl.h === this.h && hsl.s === this.s && hsl.l === this.l; 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; r /= 255; g /= 255; b /= 255;
var max = Math.max(r,g,b), var max = Math.max(r,g,b),
@ -406,27 +456,27 @@ HSLColor.fromRGB = function(r, g, b) {
h /= 6; 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); } HSLAColor.prototype.toRGBA = function() { return RGBAColor.fromHSLA(this.h, this.s, this.l, this.a); }
HSLColor.prototype.toCSS = function() { return "hsl(" + Math.round(this.h*360) + "," + Math.round(this.s*100) + "%," + Math.round(this.l*100) + "%)"; } 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 : "") + ")"; }
HSLColor.prototype.toHex = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toHex(); } HSLAColor.prototype.toHSVA = function() { return this.toRGBA().toHSVA(); }
HSLColor.prototype.toHSV = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toHSV(); } HSLAColor.prototype.toXYZA = function() { return this.toRGBA().toXYZA(); }
HSLColor.prototype.toXYZ = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toXYZ(); } HSLAColor.prototype.toLUVA = function() { return this.toRGBA().toLUVA(); }
HSLColor.prototype.toLUV = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toLUV(); }
HSLColor.prototype._h = function(h) { return new HSLColor(h, this.s, this.l); } HSLAColor.prototype._h = function(h) { return new HSLAColor(h, this.s, this.l, this.a); }
HSLColor.prototype._s = function(s) { return new HSLColor(this.h, s, this.l); } HSLAColor.prototype._s = function(s) { return new HSLAColor(this.h, s, this.l, this.a); }
HSLColor.prototype._l = function(l) { return new HSLColor(this.h, this.s, l); } 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 // 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; r /= 255; g /= 255; b /= 255;
var max = Math.max(r, g, b), var max = Math.max(r, g, b),
@ -453,24 +503,25 @@ HSVColor.fromRGB = function(r, g, b) {
h /= 6; 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); } HSVAColor.prototype.toRGBA = function() { return RGBAColor.fromHSVA(this.h, this.s, this.v, this.a); }
HSVColor.prototype.toHSL = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toHSL(); } HSVAColor.prototype.toHSLA = function() { return this.toRGBA().toHSLA(); }
HSVColor.prototype.toXYZ = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toXYZ(); } HSVAColor.prototype.toXYZA = function() { return this.toRGBA().toXYZA(); }
HSVColor.prototype.toLUV = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toLUV(); } HSVAColor.prototype.toLUVA = function() { return this.toRGBA().toLUVA(); }
HSVColor.prototype._h = function(h) { return new HSVColor(h, this.s, this.v); } HSVAColor.prototype._h = function(h) { return new HSVAColor(h, this.s, this.v, this.a); }
HSVColor.prototype._s = function(s) { return new HSVColor(this.h, s, this.v); } HSVAColor.prototype._s = function(s) { return new HSVAColor(this.h, s, this.v, this.a); }
HSVColor.prototype._v = function(v) { return new HSVColor(this.h, this.s, v); } 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 // XYZ Colors
RGBColor.channelConverter = function (channel) { RGBAColor.channelConverter = function (channel) {
// http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html // 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 // This converts rgb 8bit to rgb linear, lazy because the other algorithm is really really dumb
return Math.pow(channel, 2.2); 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); 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 // Using lazy conversion in the other direction as well
return Math.pow(channel, 1/2.2); 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) { XYZAColor.fromRGBA = function(r, g, b, a) {
var R = RGBColor.channelConverter(r / 255), var R = RGBAColor.channelConverter(r / 255),
G = RGBColor.channelConverter(g / 255), G = RGBAColor.channelConverter(g / 255),
B = RGBColor.channelConverter(b / 255); B = RGBAColor.channelConverter(b / 255);
return new XYZColor( return new XYZAColor(
0.412453 * R + 0.357580 * G + 0.180423 * B, 0.412453 * R + 0.357580 * G + 0.180423 * B,
0.212671 * R + 0.715160 * G + 0.072169 * 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) { XYZAColor.fromLUVA = function(l, u, v, alpha) {
var deltaGammaFactor = 1 / (XYZColor.WHITE.x + 15 * XYZColor.WHITE.y + 3 * XYZColor.WHITE.z); var deltaGammaFactor = 1 / (XYZAColor.WHITE.x + 15 * XYZAColor.WHITE.y + 3 * XYZAColor.WHITE.z);
var uDeltaGamma = 4 * XYZColor.WHITE.x * deltaGammaFactor; var uDeltaGamma = 4 * XYZAColor.WHITE.x * deltaGammaFactor;
var vDeltagamma = 9 * XYZColor.WHITE.y * deltaGammaFactor; var vDeltagamma = 9 * XYZAColor.WHITE.y * deltaGammaFactor;
// XYZColor.EPSILON * XYZColor.KAPPA = 8 // XYZAColor.EPSILON * XYZAColor.KAPPA = 8
var Y = (l > 8) ? Math.pow((l + 16) / 116, 3) : l / XYZColor.KAPPA; 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 a = 1/3 * (((52 * l) / (u + 13 * l * uDeltaGamma)) - 1);
var b = -5 * Y; var b = -5 * Y;
@ -518,36 +570,37 @@ XYZColor.fromLUV = function(l, u, v) {
var X = (d - b) / (a - c); var X = (d - b) / (a - c);
var Z = X * a + b; 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); } XYZAColor.prototype.toRGBA = function() { return RGBAColor.fromXYZA(this.x, this.y, this.z, this.a); }
XYZColor.prototype.toLUV = function() { return LUVColor.fromXYZ(this.x, this.y, this.z); } XYZAColor.prototype.toLUVA = function() { return LUVAColor.fromXYZA(this.x, this.y, this.z, this.a); }
XYZColor.prototype.toHSL = function() { return RGBColor.fromXYZ(this.x, this.y, this.z).toHSL(); } XYZAColor.prototype.toHSLA = function() { return this.toRGBA().toHSLA(); }
XYZColor.prototype.toHSV = function() { return RGBColor.fromXYZ(this.x, this.y, this.z).toHSV(); } XYZAColor.prototype.toHSVA = function() { return this.toRGBA().toHSVA(); }
XYZColor.prototype._x = function(x) { return new XYZColor(x, this.y, this.z); } XYZAColor.prototype._x = function(x) { return new XYZAColor(x, this.y, this.z, this.a); }
XYZColor.prototype._y = function(y) { return new XYZColor(this.x, y, this.z); } XYZAColor.prototype._y = function(y) { return new XYZAColor(this.x, y, this.z, this.a); }
XYZColor.prototype._z = function(z) { return new XYZColor(this.x, this.y, z); } 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 // LUV Colors
XYZColor.EPSILON = Math.pow(6 / 29, 3); XYZAColor.EPSILON = Math.pow(6 / 29, 3);
XYZColor.KAPPA = Math.pow(29 / 3, 3); XYZAColor.KAPPA = Math.pow(29 / 3, 3);
XYZColor.WHITE = (new RGBColor(255, 255, 255)).toXYZ(); 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) { LUVAColor.fromXYZA = function(X, Y, Z, a) {
var deltaGammaFactor = 1 / (XYZColor.WHITE.x + 15 * XYZColor.WHITE.y + 3 * XYZColor.WHITE.z); var deltaGammaFactor = 1 / (XYZAColor.WHITE.x + 15 * XYZAColor.WHITE.y + 3 * XYZAColor.WHITE.z);
var uDeltaGamma = 4 * XYZColor.WHITE.x * deltaGammaFactor; var uDeltaGamma = 4 * XYZAColor.WHITE.x * deltaGammaFactor;
var vDeltagamma = 9 * XYZColor.WHITE.y * 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); var deltaDivider = (X + 15 * Y + 3 * Z);
if (deltaDivider === 0) { if (deltaDivider === 0) {
@ -559,23 +612,24 @@ LUVColor.fromXYZ = function(X, Y, Z) {
var uDelta = 4 * X * deltaFactor; var uDelta = 4 * X * deltaFactor;
var vDelta = 9 * Y * 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 u = 13 * L * (uDelta - uDeltaGamma);
var v = 13 * L * (vDelta - vDeltagamma); 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); } LUVAColor.prototype.toXYZA = function() { return XYZAColor.fromLUVA(this.l, this.u, this.v, this.a); }
LUVColor.prototype.toRGB = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toRGB(); } LUVAColor.prototype.toRGBA = function() { return this.toXYZA().toRGBA(); }
LUVColor.prototype.toHSL = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toHSL(); } LUVAColor.prototype.toHSLA = function() { return this.toXYZA().toHSLA(); }
LUVColor.prototype.toHSV = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toHSV(); } LUVAColor.prototype.toHSVA = function() { return this.toXYZA().toHSVA(); }
LUVColor.prototype._l = function(l) { return new LUVColor(l, this.u, this.v); } LUVAColor.prototype._l = function(l) { return new LUVAColor(l, this.u, this.v, this.a); }
LUVColor.prototype._u = function(u) { return new LUVColor(this.l, u, this.v); } LUVAColor.prototype._u = function(u) { return new LUVAColor(this.l, u, this.v, this.a); }
LUVColor.prototype._v = function(v) { return new LUVColor(this.l, this.u, v); } 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() { 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_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 XYZColor(0, ((new RGBColor(217,217,217).toXYZ().y + 0.05) / this.settings.luv_contrast - 0.05), 0).toLUV().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() { FFZ.prototype._rebuild_colors = function() {
@ -592,7 +649,7 @@ FFZ.prototype._rebuild_colors = function() {
return; return;
// With update colors, we'll automatically process the colors we care about. // With update colors, we'll automatically process the colors we care about.
this._colors = {}; this._hex_colors = {};
this._update_colors(); this._update_colors();
} }
@ -602,7 +659,8 @@ FFZ.prototype._update_colors = function(darkness_only) {
var Layout = window.App && App.__container__.lookup('controller:layout'), var Layout = window.App && App.__container__.lookup('controller:layout'),
Settings = window.App && App.__container__.lookup('controller:settings'), 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 ) if ( darkness_only && this._color_old_darkness === is_dark )
return; return;
@ -618,96 +676,117 @@ FFZ.prototype._update_colors = function(darkness_only) {
if ( ! colors ) if ( ! colors )
continue; 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) { FFZ.prototype._handle_color = function(color) {
if ( color instanceof RGBColor ) if ( color instanceof RGBAColor )
color = color.toHex(); color = color.toCSS();
if ( ! color || this._colors.hasOwnProperty(color) ) if ( ! color )
return this._colors[color]; return null;
var rgb = RGBColor.fromHex(color), if ( this._hex_colors.hasOwnProperty(color) )
return this._hex_colors[color];
light_color = color, var rgb = RGBAColor.fromCSS(color),
dark_color = color;
// Color Blindness Handling light_color = rgb,
if ( this.settings.color_blind !== '0' ) { dark_color = rgb;
var new_color = rgb.daltonize(this.settings.color_blind);
if ( ! rgb.eq(new_color) ) { // Color Blindness Handling
rgb = new_color; if ( this.settings.color_blind !== '0' ) {
light_color = dark_color = rgb.toHex(); var new_color = rgb.daltonize(this.settings.color_blind);
} if ( ! rgb.eq(new_color) ) {
} rgb = new_color;
light_color = dark_color = rgb;
}
}
// Color Processing - RGB // Color Processing - RGB
if ( this.settings.fix_color === '4' ) { if ( this.settings.fix_color === '4' ) {
var lum = rgb.luminance(); var lum = rgb.luminance();
if ( lum > 0.3 ) { if ( lum > 0.3 ) {
var s = 127, nc = rgb; var s = 127, nc = rgb;
while(s--) { while(s--) {
nc = nc.brighten(-1); nc = nc.brighten(-1);
if ( nc.luminance() <= 0.3 ) if ( nc.luminance() <= 0.3 )
break; break;
} }
light_color = nc.toHex(); light_color = nc;
} }
if ( lum < 0.15 ) { if ( lum < 0.15 ) {
var s = 127, nc = rgb; var s = 127, nc = rgb;
while(s--) { while(s--) {
nc = nc.brighten(); nc = nc.brighten();
if ( nc.luminance() >= 0.15 ) if ( nc.luminance() >= 0.15 )
break; break;
} }
dark_color = nc.toHex(); dark_color = nc;
} }
} }
// Color Processing - HSL // Color Processing - HSL
if ( this.settings.fix_color === '2' ) { 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(); 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)).toHex(); dark_color = hsl._l(Math.min(Math.max(0, 0.3 + (0.7 * hsl.l)), 1)).toRGBA();
} }
// Color Processing - HSV // Color Processing - HSV
if ( this.settings.fix_color === '3' ) { if ( this.settings.fix_color === '3' ) {
var hsv = rgb.toHSV(); var hsv = rgb.toHSVA();
if ( hsv.s === 0 ) { if ( hsv.s === 0 ) {
// Black and White // Black and White
light_color = hsv._v(Math.min(Math.max(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)).toRGB().toHex(); dark_color = hsv._v(Math.min(Math.max(0.5, 0.5 + (0.5 * hsv.v)), 1)).toRGBA();
} else { } 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(); 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 = RGBColor.fromHSV(hsv.h, Math.min(0.7, hsv.s), Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.v)), 1)).toHex(); 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 // Color Processing - LUV
if ( this.settings.fix_color === '1' ) { if ( this.settings.fix_color === '1' ) {
var luv = rgb.toLUV(); var luv = rgb.toLUVA();
if ( luv.l > this._luv_required_dark ) 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 ) 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; 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, var t = this,
id = t.get('content.id'); 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) { .done(function(data) {
if ( ! data || ! data.stream ) { if ( ! data || ! data.stream ) {
// If the stream is offline, clear its created_at time and set it to zero viewers. // If the stream is offline, clear its created_at time and set it to zero viewers.
t.set('stream.created_at', null); t.set('content.stream.created_at', null);
t.set('stream.viewers', 0); t.set('content.stream.viewers', 0);
return; return;
} }
t.set('stream.created_at', data.stream.created_at || null); t.set('content.stream.created_at', data.stream.created_at || null);
t.set('stream.viewers', data.stream.viewers || 0); t.set('content.stream.viewers', data.stream.viewers || 0);
var game = data.stream.game || (data.stream.channel && data.stream.channel.game); var game = data.stream.game || (data.stream.channel && data.stream.channel.game);
if ( game ) { if ( game ) {
t.set('game', game); t.set('content.game', game);
t.set('rollbackData.game', game); t.set('content.rollbackData.game', game);
} }
if ( data.stream.channel ) { if ( data.stream.channel ) {
if ( data.stream.channel.status ) 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 ) 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') ) if ( data.stream.channel.followers && t.get('content.followers.isLoaded') )
t.set('content.followers.total', data.stream.channel.followers); t.set('content.followers.total', data.stream.channel.followers);
@ -206,7 +206,7 @@ FFZ.prototype._modify_cindex = function(view) {
}, },
ffzInit: function() { ffzInit: function() {
var id = this.get('controller.id'), var id = this.get('controller.content.id') || this.get('controller.id'),
el = this.get('element'); el = this.get('element');
f._cindex = this; f._cindex = this;
@ -251,8 +251,8 @@ FFZ.prototype._modify_cindex = function(view) {
if ( f.has_bttv || ! f.settings.stream_title ) if ( f.has_bttv || ! f.settings.stream_title )
return; return;
var status = this.get("controller.status"), var status = this.get("controller.content.status") || this.get("controller.status"),
channel = this.get("controller.id"); channel = this.get("controller.content.id") || this.get("controller.id");
status = f.render_tokens(f.tokenize_line(channel, channel, status, true)); status = f.render_tokens(f.tokenize_line(channel, channel, status, true));
@ -267,7 +267,7 @@ FFZ.prototype._modify_cindex = function(view) {
ffzUpdateHostButton: function() { 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'), hosted_id = this.get('controller.hostModeTarget.id'),
user = f.get_user(), user = f.get_user(),
@ -360,7 +360,7 @@ FFZ.prototype._modify_cindex = function(view) {
}, },
ffzClickHost: function(controller, is_host) { 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(), user = f.get_user(),
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room, room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
now_hosting = room && room.ffz_host_target; now_hosting = room && room.ffz_host_target;
@ -381,7 +381,7 @@ FFZ.prototype._modify_cindex = function(view) {
ffzUpdateChatters: function() { ffzUpdateChatters: function() {
// Get the counts. // 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]; room = f.rooms && f.rooms[room_id];
if ( ! room || ! f.settings.chatter_count ) { if ( ! room || ! f.settings.chatter_count ) {
@ -457,7 +457,7 @@ FFZ.prototype._modify_cindex = function(view) {
ffzUpdatePlayerStats: function() { 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'), hosted_id = this.get('controller.hostModeTarget.id'),
el = this.get('element'); el = this.get('element');
@ -471,13 +471,13 @@ FFZ.prototype._modify_cindex = function(view) {
player = undefined, stats = undefined; player = undefined, stats = undefined;
try { try {
player = player_cont && player_cont.ffz_player; player = player_cont && player_cont.get && player_cont.get('player');
stats = player && player.stats; stats = player && player.stats;
} catch(err) { } catch(err) {
f.error("Channel ffzUpdatePlayerStats: player.stats: " + 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 ) if ( stat_el )
stat_el.parentElement.removeChild(stat_el); stat_el.parentElement.removeChild(stat_el);
} else { } 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 ) if ( stat_el )
stat_el.parentElement.removeChild(stat_el); stat_el.parentElement.removeChild(stat_el);
} else { } else {
@ -648,7 +648,7 @@ FFZ.prototype._modify_cindex = function(view) {
}, },
ffzTeardown: function() { ffzTeardown: function() {
var id = this.get('controller.id'); var id = this.get('controller.content.id') || this.get('controller.id');
if ( id ) if ( id )
f.ws_send("unsub", "channel." + id); f.ws_send("unsub", "channel." + id);

View file

@ -13,7 +13,12 @@ FFZ.basic_settings.delayed_chat = {
0: "No Delay", 0: "No Delay",
300: "Minor (Bot Moderation; 0.3s)", 300: "Minor (Bot Moderation; 0.3s)",
1200: "Normal (Human Moderation; 1.2s)", 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", category: "Chat",
@ -134,7 +139,12 @@ FFZ.settings_info.chat_delay = {
0: "No Delay", 0: "No Delay",
300: "Minor (Bot Moderation; 0.3s)", 300: "Minor (Bot Moderation; 0.3s)",
1200: "Normal (Human Moderation; 1.2s)", 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, value: 0,
@ -316,6 +326,25 @@ FFZ.settings_info.visible_rooms = {
// Initialization // 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() { 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-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); 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) { } } catch(err) { }
// Modify all existing Chat views. // 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) { for(var key in views) {
if ( ! views.hasOwnProperty(key) ) if ( ! views.hasOwnProperty(key) )
continue; continue;
@ -434,14 +463,18 @@ FFZ.prototype.setup_chatview = function() {
this.log("Manually updating existing Chat view.", view); this.log("Manually updating existing Chat view.", view);
try { try {
if ( ! view.ffzInit )
this._modify_cview(view);
view.ffzInit(); view.ffzInit();
} catch(err) { } catch(err) {
this.error("setup: build_ui_link: " + 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'); var Column = App.__container__.lookup('controller:right-column');
if ( ! Column ) if ( ! Column )
return; return;
@ -454,7 +487,7 @@ FFZ.prototype.setup_chatview = function() {
},0); },0);
} }
}.observes("firstTabSelected") }.observes("firstTabSelected")
}); });*/
} }
@ -467,7 +500,7 @@ FFZ.prototype._modify_cview = function(view) {
view.reopen({ view.reopen({
didInsertElement: function() { didInsertElement: function() {
this._super(); this._super();
try { try {
this.ffzInit(); this.ffzInit();
@ -487,10 +520,11 @@ FFZ.prototype._modify_cview = function(view) {
ffzInit: function() { ffzInit: function() {
f._chatv = this; f._chatv = this;
this.ffz_unread = {};
this.$('.textarea-contain').append(f.build_ui_link(this)); 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.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
this.$('.chat-messages').find('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
this.ffz_unread = {};
if ( ! f.has_bttv ) { if ( ! f.has_bttv ) {
if ( f.settings.group_tabs ) if ( f.settings.group_tabs )
@ -556,13 +590,19 @@ FFZ.prototype._modify_cview = function(view) {
ffzChangeRoom: Ember.observer('controller.currentRoom', function() { ffzChangeRoom: Ember.observer('controller.currentRoom', function() {
f.update_ui_link(); 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. // Close mod cards when changing to a new room.
if ( f._mod_card ) if ( f._mod_card )
f._mod_card.send('close'); f._mod_card.send('close');
var room = this.get('controller.currentRoom'), var room = this.get('controller.currentRoom'),
room_id = room && room.get('id'), 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; update_height = false;
if ( room ) { if ( room ) {
@ -1209,7 +1249,7 @@ FFZ.prototype.connect_extra_chat = function() {
FFZ.prototype.disconnect_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_channel_id = Chat && Chat.get('currentChannelRoom.id'),
current_id = Chat && Chat.get('currentRoom.id'), current_id = Chat && Chat.get('currentRoom.id'),
user = this.get_user(); user = this.get_user();

View file

@ -13,7 +13,7 @@ FFZ.settings_info.conv_focus_on_click = {
no_mobile: true, no_mobile: true,
visible: false, visible: false,
category: "Conversations", category: "Whispers",
name: "Focus Input on Click", name: "Focus Input on Click",
help: "Focus on a conversation's input box when you click it." help: "Focus on a conversation's input box when you click it."
}; };
@ -23,7 +23,7 @@ FFZ.settings_info.top_conversations = {
value: false, value: false,
no_mobile: true, no_mobile: true,
category: "Conversations", category: "Whispers",
name: "Position on Top", name: "Position on Top",
help: "Display the new conversation-style whisper UI at the top of the window instead of the bottom.", help: "Display the new conversation-style whisper UI at the top of the window instead of the bottom.",
on_update: function(val) { 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 // Initialization
// --------------- // ---------------
FFZ.prototype.setup_conversations = function() { FFZ.prototype.setup_conversations = function() {
document.body.classList.toggle('ffz-top-conversations', this.settings.top_conversations); 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."); this.log("Hooking the Ember Conversation Window component.");
var ConvWindow = App.__container__.resolve('component:conversation-window'); var ConvWindow = App.__container__.resolve('component:conversation-window');
@ -52,6 +67,7 @@ FFZ.prototype.setup_conversations = function() {
// TODO: Make this better later. // 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('.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({ component.reopen({
headerBadges: Ember.computed("thread.participants", "currentUsername", function() { 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')), badges = f.get_other_badges(e.get('username'), null, e.get('userType'), false, e.get('hasTurbo')),
out = []; 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('.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('.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) { click: function(e) {
if ( e.target && e.target.classList.contains('deleted-link') ) 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) ) if ( f._click_emote(e.target, e) )
return; return;
@ -133,7 +149,9 @@ FFZ.prototype._modify_conversation_line = function(component) {
raw_color = this.get('message.from.color'), raw_color = this.get('message.from.color'),
colors = raw_color && f._handle_color(raw_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>'); 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('<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>'); e.push('</span>');
} }
}); });

View file

@ -2,7 +2,7 @@ var FFZ = window.FrankerFaceZ,
utils = require('../utils'), utils = require('../utils'),
constants = require('../constants'), 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 = { FFZ.settings_info.directory_creative_all_tags = {
type: "boolean", type: "boolean",
value: false, value: false,
@ -145,9 +162,12 @@ FFZ.settings_info.directory_host_menus = {
// Initialization // Initialization
// -------------------- // --------------------
FFZ._image_cache = {};
FFZ.prototype.setup_directory = function() { FFZ.prototype.setup_directory = function() {
document.body.classList.toggle('ffz-creative-tags', this.settings.directory_creative_all_tags); 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-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'), var GamesFollowing = App.__container__.lookup('controller:games-following'),
f = this; f = this;
@ -235,6 +255,8 @@ FFZ.prototype._modify_following = function() {
offset: this.get('content.length') + this.get('ffz_skipped') 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); 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'), var el = this.get('element'),
meta = el && el.querySelector('.meta'), meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'), 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... // CSGO doesn't provide the actual uptime information...
if ( !is_csgo && f.settings.stream_uptime && f.settings.stream_uptime < 3 && cap ) { 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.ffzUpdateUptime();
} }
this._ffz_image_timer = setInterval(this.ffzRotateImage.bind(this), 30000);
this.ffzRotateImage();
if ( f.settings.directory_logos ) { if ( f.settings.directory_logos ) {
el.classList.add('ffz-directory-logo'); el.classList.add('ffz-directory-logo');
var link = document.createElement('a'), var link = document.createElement('a'),
logo = document.createElement('img'), logo = document.createElement('img'),
t = this, t = this;
target = this.get('context.model.channel.name');
logo.className = 'profile-photo'; logo.className = 'profile-photo';
logo.classList.toggle('is-csgo', is_csgo); 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.src = this.get('context.model.channel.logo') || NO_LOGO;
logo.alt = this.get('context.model.channel.display_name'); logo.alt = this.get('context.model.channel.display_name');
link.href = '/' + target; link.href = '/' + channel_id;
link.addEventListener('click', function(e) { 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'); var Channel = App.__container__.resolve('model:channel');
if ( ! Channel ) if ( ! Channel )
return; return;
e.preventDefault(); 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; return false;
}); });
@ -358,9 +387,23 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
if ( this._ffz_uptime_timer ) if ( this._ffz_uptime_timer )
clearInterval(this._ffz_uptime_timer); clearInterval(this._ffz_uptime_timer);
if ( this._ffz_image_timer )
clearInterval(this._ffz_image_timer);
this._super(); 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() { ffzUpdateUptime: function() {
var raw_created = this.get('context.model.created_at'), var raw_created = this.get('context.model.created_at'),
@ -411,7 +454,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
return; return;
if ( e ) { if ( e ) {
if ( e.button !== 0 ) if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return; return;
e.preventDefault(); e.preventDefault();
@ -424,7 +467,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
}, },
ffzShowHostMenu: function(e) { ffzShowHostMenu: function(e) {
if ( e.button !== 0 ) if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return; return;
e.preventDefault(); 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')); 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() { ffzCleanup: function() {
var target = this.get('context.model.target.channel'); var target = this.get('context.model.target.channel');
if ( f._popup && f._popup.classList.contains('ffz-channel-selector') && f._popup.getAttribute('data-channel') === target ) if ( f._popup && f._popup.classList.contains('ffz-channel-selector') && f._popup.getAttribute('data-channel') === target )
f.close_popup(); f.close_popup();
if ( this._ffz_image_timer )
clearInterval(this._ffz_image_timer);
}, },
ffzInit: function() { ffzInit: function() {
@ -503,6 +561,10 @@ FFZ.prototype._modify_directory_host = function(dir) {
boxart = thumb && thumb.querySelector('.boxart'); 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 // Fix the game not showing
if ( ! boxart && thumb && this.get('context.model.game') ) { 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.setAttribute('original-title', game);
boxart.addEventListener('click', function(e) { boxart.addEventListener('click', function(e) {
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
e.preventDefault(); e.preventDefault();
jQuery('.tipsy').remove(); jQuery('.tipsy').remove();

View file

@ -11,7 +11,7 @@ FFZ.settings_info.enhance_profile_following = {
type: "boolean", type: "boolean",
value: true, value: true,
category: "Appearance", category: "Directory",
name: "Enhanced Following Control", name: "Enhanced Following Control",
help: "Display additional controls on your own profile's Following tab to make management easier." help: "Display additional controls on your own profile's Following tab to make management easier."
} }
@ -33,10 +33,8 @@ 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, // 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. // rather than making potentially hundreds of API requests.
var Following = App.__container__.resolve('model:kraken-channel-following'); var Following = App.__container__.resolve('model:kraken-channel-following');
if ( ! Following ) if ( Following )
return; this._hook_following(Following);
this._hook_following(Following);
// Also try hooking that other model. // Also try hooking that other model.
var Notification = App.__container__.resolve('model:notification'); var Notification = App.__container__.resolve('model:notification');
@ -71,7 +69,7 @@ FFZ.prototype.setup_profile_following = function() {
ffzInit: function() { ffzInit: function() {
// Only process our own profile following page. // Only process our own profile following page.
var user = f.get_user(); 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; return;
var el = this.get('element'), var el = this.get('element'),
@ -99,6 +97,9 @@ FFZ.prototype.setup_profile_following = function() {
following.load(); 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. // We use this weird function to prevent trying to load twice mucking things up.
setTimeout(refresher); setTimeout(refresher);
} }
@ -118,14 +119,15 @@ FFZ.prototype.setup_profile_following = function() {
continue; continue;
// Is it an ember-view? Check its kids. // 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'); var users = added.querySelectorAll('.user.item');
if ( users ) if ( users )
for(var y=0; y < users.length; y++) for(var y=0; y < users.length; y++)
t.ffzProcessUser(users[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'; follow.textContent = 'Updating';
(was_following ? (was_following ?
Twitch.api.del("users/:login/follows/channels/" + user_id) : utils.api.del("users/:login/follows/channels/" + user_id) :
Twitch.api.put("users/:login/follows/channels/" + user_id, {notifications: false})) utils.api.put("users/:login/follows/channels/" + user_id, {notifications: false}))
.done(function() { .done(function() {
data = f._following_cache[user_id] = was_following ? null : [new Date(Date.now() - (f._ws_server_offset||0)), false]; 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.disabled = true;
notif.textContent = 'Updating'; 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() { .done(function() {
data[1] = ! was_following; data[1] = ! was_following;
}) })
@ -254,37 +256,106 @@ 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. // Now, rebuild any views.
try { try {
ProfileView.create().destroy(); ProfileView.create().destroy();
} catch(err) { } } 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');
for(var key in views) { if ( views )
var view = views[key]; for(var key in views) {
if ( ! view || !(view instanceof ProfileView) ) var view = views[key];
continue; /*if ( DirectoryFollowing && view instanceof DirectoryFollowing ) {
try {
view.ffzInit();
} catch(err) {
this.error("setup: view:following: ffzInit: " + err);
}
this.log("Manually updating existing Following View.", view); } else*/ if ( view instanceof ProfileView ) {
try { this.log("Manually updating existing Following View.", view);
var following = view.get('context.following'); try {
this._hook_following(following); view.ffzInit();
} catch(err) { } catch(err) {
this.error("setup: view:channel/following: model hook: " + err); this.error("setup: view:channel/following: " + err);
} }
}
try { }
view.ffzInit();
} catch(err) {
this.error("setup: view:channel/following: " + err);
}
}
} }
FFZ.prototype._hook_following = function(Following) { FFZ.prototype._hook_following = function(Following) {
var f = this; var f = this;
if ( Following.ffz_hooked )
return;
Following.reopen({ Following.reopen({
ffz_hooked: true,
apiLoad: function(e) { apiLoad: function(e) {
var user = f.get_user(), var user = f.get_user(),
channel_id = this.get('id'), 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", type: "boolean",
value: false, value: false,
category: "Appearance", category: "Dashboard",
no_mobile: true, no_mobile: true,
no_bttv: 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.", help: "Swap the positions of the left and right columns of the dashboard.",
on_update: function(val) { on_update: function(val) {
@ -106,17 +107,19 @@ FFZ.settings_info.right_column_width = {
help: "Set the width of the right sidebar for chat.", help: "Set the width of the right sidebar for chat.",
method: function() { method: function() {
var old_val = this.settings.right_column_width || 340, var f = this,
new_val = prompt("Right Sidebar Width\n\nPlease enter a new width for the right sidebar, in pixels. Minimum: 250, Default: 340", old_val); old_val = this.settings.right_column_width || 340;
if ( new_val === null || new_val === undefined ) 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) {
return; if ( new_val === null || new_val === undefined )
return;
var width = parseInt(new_val); var width = parseInt(new_val);
if ( ! width || width === NaN ) if ( ! width || Number.isNaN(width) || ! Number.isFinite(width) )
width = 340; 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) { on_update: function(val) {
@ -220,7 +223,7 @@ FFZ.prototype.setup_layout = function() {
height = size[1], height = size[1],
host_height = size[2]; 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"), }.property("playerSize"),
ffzPortraitWarning: function() { ffzPortraitWarning: function() {

File diff suppressed because it is too large Load diff

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.", help: "Change out the different in-line moderation icons to use any command quickly.",
method: function() { method: function() {
var old_val = ""; var f = this,
old_val = "";
for(var i=0; i < this.settings.mod_buttons.length; i++) { for(var i=0; i < this.settings.mod_buttons.length; i++) {
var pair = this.settings.mod_buttons[i], var pair = this.settings.mod_buttons[i],
prefix = pair[0], cmd = pair[1], had_prefix = pair[2]; prefix = pair[0], cmd = pair[1], had_prefix = pair[2];
@ -182,91 +184,102 @@ FFZ.settings_info.mod_buttons = {
old_val += ' ' + prefix + cmd; 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);
if ( new_val === null || new_val === undefined ) utils.prompt(
return; "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;
var vals = [], prefix = ''; var vals = [], prefix = '';
new_val = new_val.trim(); new_val = new_val.trim();
while(new_val) { while(new_val) {
if ( new_val.charAt(1) === '=' ) { if ( new_val.charAt(1) === '=' ) {
prefix = new_val.charAt(0); prefix = new_val.charAt(0);
new_val = new_val.substr(2); new_val = new_val.substr(2);
continue; continue;
} }
if ( new_val.charAt(0) === '"' ) { if ( new_val.charAt(0) === '"' ) {
var end = new_val.indexOf('"', 1); var end = new_val.indexOf('"', 1);
if ( end === -1 ) if ( end === -1 )
end = new_val.length; end = new_val.length;
var segment = new_val.substr(1, end - 1); var segment = new_val.substr(1, end - 1);
if ( segment ) { if ( segment ) {
vals.push([prefix, segment]); vals.push([prefix, segment]);
prefix = ''; prefix = '';
} }
new_val = new_val.substr(end + 1); new_val = new_val.substr(end + 1);
} else { } else {
var ind = new_val.indexOf(' '); var ind = new_val.indexOf(' ');
if ( ind === -1 ) { if ( ind === -1 ) {
if ( new_val ) { if ( new_val ) {
vals.push([prefix, new_val]); vals.push([prefix, new_val]);
prefix = ''; prefix = '';
} }
new_val = ''; new_val = '';
} else { } else {
var segment = new_val.substr(0, ind); var segment = new_val.substr(0, ind);
if ( segment ) { if ( segment ) {
vals.push([prefix, segment]); vals.push([prefix, segment]);
prefix = ''; prefix = '';
} }
new_val = new_val.substr(ind + 1); new_val = new_val.substr(ind + 1);
} }
} }
} }
var final = []; var final = [];
for(var i=0; i < vals.length; i++) { for(var i=0; i < vals.length; i++) {
var had_prefix = false, prefix = vals[i][0], val = vals[i][1]; var had_prefix = false, prefix = vals[i][0], val = vals[i][1];
if ( val === "<BAN>" ) if ( val === "<BAN>" )
val = false; val = false;
var num = parseInt(val); var num = parseInt(val);
if ( num > 0 && num !== NaN ) if ( num > 0 && ! Number.isNaN(num) )
val = num; val = num;
if ( ! prefix ) { if ( ! prefix ) {
var tmp; var tmp;
if ( typeof val === "string" ) if ( typeof val === "string" )
tmp = /\w/.exec(val); tmp = /\w/.exec(val);
else else
tmp = utils.duration_string(val); tmp = utils.duration_string(val);
prefix = tmp && tmp.length ? tmp[0].toUpperCase() : "C"; prefix = tmp && tmp.length ? tmp[0].toUpperCase() : "C";
} else } else
had_prefix = true; had_prefix = true;
if ( typeof val === "string" ) { if ( typeof val === "string" ) {
// Split it up for this step. // Split it up for this step.
var lines = val.split(/ *<LINE> */); var lines = val.split(/ *<LINE> */);
for(var x=0; x < lines.length; x++) { for(var x=0; x < lines.length; x++) {
if ( lines[x].indexOf('{user}') === -1 ) if ( lines[x].indexOf('{user}') === -1 )
lines[x] += ' {user}'; lines[x] += ' {user}';
} }
val = lines.join("<LINE>"); val = lines.join("<LINE>");
} }
final.push([prefix, val, had_prefix]); 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.", help: "Add additional buttons to moderation cards for running chat commands on those users.",
method: function() { method: function() {
var old_val = ""; var f = this,
old_val = "";
for(var i=0; i < this.settings.mod_card_buttons.length; i++) { for(var i=0; i < this.settings.mod_card_buttons.length; i++) {
var cmd = this.settings.mod_card_buttons[i]; var cmd = this.settings.mod_card_buttons[i];
if ( cmd.indexOf(' ') !== -1 ) if ( cmd.indexOf(' ') !== -1 )
@ -291,45 +305,51 @@ FFZ.settings_info.mod_card_buttons = {
old_val += ' ' + cmd; 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;
if ( new_val === null || new_val === undefined ) var vals = [];
return; new_val = new_val.trim();
var vals = []; while(new_val) {
new_val = new_val.trim(); if ( new_val.charAt(0) === '"' ) {
var end = new_val.indexOf('"', 1);
if ( end === -1 )
end = new_val.length;
while(new_val) { var segment = new_val.substr(1, end - 1);
if ( new_val.charAt(0) === '"' ) { if ( segment )
var end = new_val.indexOf('"', 1); vals.push(segment);
if ( end === -1 )
end = new_val.length;
var segment = new_val.substr(1, end - 1); new_val = new_val.substr(end + 1);
if ( segment )
vals.push(segment);
new_val = new_val.substr(end + 1); } else {
var ind = new_val.indexOf(' ');
if ( ind === -1 ) {
if ( new_val )
vals.push(new_val);
} else { new_val = '';
var ind = new_val.indexOf(' ');
if ( ind === -1 ) {
if ( new_val )
vals.push(new_val);
new_val = ''; } else {
var segment = new_val.substr(0, ind);
if ( segment )
vals.push(segment);
} else { new_val = new_val.substr(ind + 1);
var segment = new_val.substr(0, ind); }
if ( segment ) }
vals.push(segment); }
new_val = new_val.substr(ind + 1); f.settings.set("mod_card_buttons", vals);
} }, 600);
}
}
this.settings.set("mod_card_buttons", vals);
} }
}; };
@ -345,29 +365,36 @@ FFZ.settings_info.mod_card_durations = {
help: "Add additional timeout buttons to moderation cards with specific durations.", help: "Add additional timeout buttons to moderation cards with specific durations.",
method: function() { method: function() {
var old_val = this.settings.mod_card_durations.join(", "), var f = this,
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); old_val = this.settings.mod_card_durations.join(", ");
if ( new_val === null || new_val === undefined ) utils.prompt(
return; "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;
if ( new_val === "reset" ) if ( new_val === "reset" )
new_val = FFZ.settings_info.mod_card_durations.value.join(", "); new_val = FFZ.settings_info.mod_card_durations.value.join(", ");
// Split them up. // Split them up.
new_val = new_val.trim().split(/[ ,]+/); new_val = new_val.trim().split(/[ ,]+/);
var vals = []; var vals = [];
for(var i=0; i < new_val.length; i++) { for(var i=0; i < new_val.length; i++) {
var val = parseInt(new_val[i]); var val = parseInt(new_val[i]);
if ( val === 0 ) if ( val === 0 )
val = 1; val = 1;
if ( val !== NaN && val > 0 ) if ( ! Number.isNaN(val) && val > 0 )
vals.push(val); 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 ) { } else if ( followers === undefined ) {
var t = this; var t = this;
this.set('cardInfo.user.ffz_followers', false); 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.set('cardInfo.user.ffz_followers', data._total);
t.ffzRebuildInfo(); t.ffzRebuildInfo();
}).fail(function(data) { }).fail(function(data) {
@ -682,7 +709,7 @@ FFZ.prototype.setup_mod_card = function() {
real_msg.title = "Message User"; real_msg.title = "Message User";
real_msg.addEventListener('click', function() { 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); msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling);
@ -699,28 +726,29 @@ FFZ.prototype.setup_mod_card = function() {
var user = controller.get('cardInfo.user.id'), var user = controller.get('cardInfo.user.id'),
alias = f.aliases[user]; 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(
if ( new_val === null || new_val === undefined ) "Alias for <b>" + utils.sanitize(controller.get('cardInfo.user.display_name') || user) + "</b>",
return; "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;
new_val = new_val.trim(); new_val = new_val.trim();
if ( ! new_val ) if ( ! new_val )
new_val = undefined; new_val = undefined;
f.aliases[user] = new_val; f.aliases[user] = new_val;
f.save_aliases(); f.save_aliases();
// Update UI // Update UI
f._update_alias(user); f._update_alias(user);
Ember.propertyDidChange(controller, 'userName'); Ember.propertyDidChange(controller, 'cardInfo.user.display_name');
var name = el.querySelector('h3.name'), var name = el.querySelector('h3.name');
link = name && name.querySelector('a'); if ( name )
name.classList.toggle('ffz-alias', new_val);
if ( link ) });
name = link;
if ( name )
name.classList.toggle('ffz-alias', new_val);
}); });
if ( msg_btn ) if ( msg_btn )
@ -1040,9 +1068,11 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
// Interactivity // Interactivity
jQuery('a.undelete', l_el).click(function(e) { this.parentElement.outerHTML = this.getAttribute('data-message'); }); 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('a.deleted-link', l_el).click(f._deleted_link_click);
jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) }); 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('.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 ) { if ( modcard ) {
modcard.get('cardInfo.user.id') !== msg.from && jQuery('span.from', l_el).click(function(e) { 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], var line = lines[i],
el_from = line.querySelector('.from'); el_from = line.querySelector('.from');
if ( ! el_from )
continue;
el_from.classList.toggle('ffz-alias', alias); el_from.classList.toggle('ffz-alias', alias);
el_from.textContent = display_name; el_from.textContent = display_name;
el_from.title = alias ? cap_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) { 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; } 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(); f._cindex && f._cindex.ffzUpdatePlayerStats();
}; };
player.reopen({ player.reopen({
didInsertElement: function() { didInsertElement: function() {
this._super(); this._super();
@ -139,6 +138,10 @@ FFZ.prototype._modify_player = function(player) {
ffzInit: function() { ffzInit: function() {
var id = this.get('channel.id'); var id = this.get('channel.id');
f.players[id] = this; f.players[id] = this;
var player = this.get('player');
if ( player )
this.ffzPostPlayer();
}, },
ffzTeardown: function() { ffzTeardown: function() {
@ -153,22 +156,12 @@ FFZ.prototype._modify_player = function(player) {
}, },
ffzPostPlayer: function() { ffzPostPlayer: function() {
var player = this.get('ffz_player') || this.get('player'); var player = this.get('player');
if ( ! player ) { if ( ! player )
var tp2 = window.require("web-client/components/twitch-player2"); return;
if ( ! tp2 || ! tp2.getPlayer )
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. // Only set up the stats hooks if we need stats.
var has_video; var has_video = false;
try { try {
has_video = player.getVideo(); has_video = player.getVideo();
@ -184,72 +177,52 @@ FFZ.prototype._modify_player = function(player) {
if ( this.get('ffzStatsInitialized') ) if ( this.get('ffzStatsInitialized') )
return; return;
var player = this.get('ffz_player'); var t = this,
player = this.get('player');
if ( ! player ) if ( ! player )
return; return;
this.set('ffzStatsInitialized', true); this.set('ffzStatsInitialized', true);
// Make it so stats can no longer be disabled if we want them. // Make it so stats can no longer be disabled if we want them.
player.ffzSetStatsEnabled = player.setStatsEnabled; if ( player.setStatsEnabled ) {
try { player.ffzSetStatsEnabled = player.setStatsEnabled;
player.ffz_stats = player.getStatsEnabled(); try {
} catch(err) { player.ffz_stats = player.getStatsEnabled();
// Assume stats are off. } catch(err) {
f.error("Player2 ffzInitStats: getStatsEnabled still doesn't work: " + err); // Assume stats are off.
player.ffz_stats = false; 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;
player.setStatsEnabled = function(e, s) { var out = player.ffzSetStatsEnabled(e || f.settings.player_stats);
if ( s !== false )
player.ffz_stats = e;
var out = player.ffzSetStatsEnabled(e || f.settings.player_stats); if ( ! t._ffz_player_stats_initialized ) {
t._ffz_player_stats_initialized = true;
player.addEventListener('statschange', update_stats);
}
if ( ! t._ffz_player_stats_initialized ) { return out;
t._ffz_player_stats_initialized = true; }
player.addEventListener('statschange', update_stats);
}
return out; this._ffz_stat_interval = setInterval(function() {
} if ( f.settings.player_stats || player.ffz_stats ) {
player.ffzSetStatsEnabled(false);
player.ffzSetStatsEnabled(true);
}
}, 5000);
}
this._ffz_stat_interval = setInterval(function() { if ( f.settings.player_stats && ( ! player.setStatsEnabled || ! player.ffz_stats ) ) {
if ( f.settings.player_stats || player.ffz_stats ) {
player.ffzSetStatsEnabled(false);
player.ffzSetStatsEnabled(true);
}
}, 5000);
if ( f.settings.player_stats && ! player.ffz_stats ) {
this._ffz_player_stats_initialized = true; this._ffz_player_stats_initialized = true;
player.addEventListener('statschange', update_stats); player.addEventListener('statschange', update_stats);
player.ffzSetStatsEnabled(true); 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 ) if ( ! room.moderator_badge )
return ""; 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. // Responsive ban button.
var f = this, var f = this,
RC = App.__container__.lookup('controller:room'); RC = App.__container__.lookup('controller:room');
if ( RC ) { if ( RC ) {
var orig_ban = RC._actions.banUser, var orig_ban = RC._actions.banUser,
orig_to = RC._actions.timeoutUser; orig_to = RC._actions.timeoutUser;
RC._actions.banUser = function(e) { RC._actions.banUser = function(e) {
orig_ban.bind(this)(e); orig_ban.call(this, e);
this.get("model").clearMessages(e.user); this.get("model").clearMessages(e.user);
} }
RC._actions.timeoutUser = function(e) { RC._actions.timeoutUser = function(e) {
orig_to.bind(this)(e); orig_to.call(this, e);
this.get("model").clearMessages(e.user);
}
RC._actions.purgeUser = function(e) {
this.get("model.tmiRoom").sendMessage("/timeout " + e.user + " 1");
this.get("model").clearMessages(e.user); this.get("model").clearMessages(e.user);
} }
@ -79,7 +75,7 @@ FFZ.prototype.setup_room = function() {
renderBottom: e.bottom, renderBottom: e.bottom,
renderRight: e.right, renderRight: e.right,
isIgnored: this.get("tmiSession").isIgnored(e.sender), 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), profileHref: Twitch.uri.profile(e.sender),
isModeratorOrHigher: this.get("model.isModeratorOrHigher") isModeratorOrHigher: this.get("model.isModeratorOrHigher")
}); });
@ -161,15 +157,6 @@ FFZ.prototype._modify_rview = function(view) {
this._super(); 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() { ffzInit: function() {
f._roomv = this; f._roomv = this;
@ -232,6 +219,7 @@ FFZ.prototype._modify_rview = function(view) {
var r9k_badge = cont.querySelector('#ffz-stat-r9k'), var r9k_badge = cont.querySelector('#ffz-stat-r9k'),
sub_badge = cont.querySelector('#ffz-stat-sub'), sub_badge = cont.querySelector('#ffz-stat-sub'),
emote_badge = cont.querySelector('#ffz-stat-emote'),
slow_badge = cont.querySelector('#ffz-stat-slow'), slow_badge = cont.querySelector('#ffz-stat-slow'),
banned_badge = cont.querySelector('#ffz-stat-banned'), banned_badge = cont.querySelector('#ffz-stat-banned'),
delay_badge = cont.querySelector('#ffz-stat-delay'), delay_badge = cont.querySelector('#ffz-stat-delay'),
@ -243,6 +231,8 @@ FFZ.prototype._modify_rview = function(view) {
r9k_badge.parentElement.removeChild(r9k_badge); r9k_badge.parentElement.removeChild(r9k_badge);
if ( sub_badge ) if ( sub_badge )
sub_badge.parentElement.removeChild(sub_badge); sub_badge.parentElement.removeChild(sub_badge);
if ( emote_badge )
emote_badge.parentElement.removeChild(emote_badge);
if ( slow_badge ) if ( slow_badge )
slow_badge.parentElement.removeChild(slow_badge); slow_badge.parentElement.removeChild(slow_badge);
if ( delay_badge ) if ( delay_badge )
@ -275,6 +265,16 @@ FFZ.prototype._modify_rview = function(view) {
jQuery(sub_badge).tipsy({gravity:"s", offset:15}); 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 ) { if ( ! slow_badge ) {
slow_badge = document.createElement('span'); slow_badge = document.createElement('span');
slow_badge.className = 'ffz room-state stat float-right'; slow_badge.className = 'ffz room-state stat float-right';
@ -315,6 +315,7 @@ FFZ.prototype._modify_rview = function(view) {
var vis_count = 0, var vis_count = 0,
r9k_vis = room && room.get('r9k'), r9k_vis = room && room.get('r9k'),
sub_vis = room && room.get('subsOnly'), sub_vis = room && room.get('subsOnly'),
emote_vis = room && room.get('emoteOnly') && room.get('emoteOnly') !== '0',
ban_vis = room && room.get('ffz_banned'), ban_vis = room && room.get('ffz_banned'),
slow_vis = room && room.get('slowMode'), slow_vis = room && room.get('slowMode'),
delay_vis = f.settings.chat_delay !== 0, delay_vis = f.settings.chat_delay !== 0,
@ -322,13 +323,15 @@ FFZ.prototype._modify_rview = function(view) {
if ( r9k_vis ) vis_count += 1; if ( r9k_vis ) vis_count += 1;
if ( sub_vis ) vis_count += 1; if ( sub_vis ) vis_count += 1;
if ( ban_vis ) vis_count += 1; if ( emote_vis ) vis_count += 1;
if ( ban_vis ) vis_count += 1;
if ( slow_vis ) vis_count += 1; if ( slow_vis ) vis_count += 1;
if ( delay_vis ) vis_count += 1; if ( delay_vis ) vis_count += 1;
if ( batch_vis ) vis_count += 1; if ( batch_vis ) vis_count += 1;
r9k_badge.classList.toggle('truncated', vis_count > 3); r9k_badge.classList.toggle('truncated', vis_count > 3);
sub_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); banned_badge.classList.toggle('truncated', vis_count > 3);
slow_badge.classList.toggle('truncated', vis_count > 3); slow_badge.classList.toggle('truncated', vis_count > 3);
delay_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); r9k_badge.classList.toggle('hidden', ! r9k_vis);
sub_badge.classList.toggle('hidden', ! sub_vis); sub_badge.classList.toggle('hidden', ! sub_vis);
emote_badge.classList.toggle('hidden', ! emote_vis);
banned_badge.classList.toggle('hidden', ! ban_vis); banned_badge.classList.toggle('hidden', ! ban_vis);
slow_badge.classList.toggle('hidden', ! slow_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('mousemove', this._ffz_mouse_move);
messages.addEventListener('touchmove', this._ffz_mouse_move); messages.addEventListener('touchmove', this._ffz_mouse_move);
messages.addEventListener('mouseout', this._ffz_mouse_out); messages.addEventListener('mouseout', this._ffz_mouse_out);
document.addEventListener('mouseout', this._ffz_mouse_out);
}, },
ffzDisableFreeze: function() { ffzDisableFreeze: function() {
@ -389,6 +392,7 @@ FFZ.prototype._modify_rview = function(view) {
if ( this._ffz_mouse_move ) { if ( this._ffz_mouse_move ) {
messages.removeEventListener('mousemove', this._ffz_mouse_move); messages.removeEventListener('mousemove', this._ffz_mouse_move);
messages.removeEventListener('touchmove', this._ffz_mouse_move);
this._ffz_mouse_move = undefined; this._ffz_mouse_move = undefined;
} }
@ -553,7 +557,7 @@ FFZ.prototype.run_command = function(text, room_id) {
var val = command.enabled; var val = command.enabled;
if ( typeof val == "function" ) { if ( typeof val == "function" ) {
try { try {
val = command.enabled.bind(this)(room, args); val = command.enabled.call(this, room, args);
} catch(err) { } catch(err) {
this.error('command "' + cmd + '" enabled: ' + err); this.error('command "' + cmd + '" enabled: ' + err);
val = false; val = false;
@ -567,7 +571,7 @@ FFZ.prototype.run_command = function(text, room_id) {
this.log("Received Command: " + cmd, args, true); this.log("Received Command: " + cmd, args, true);
try { try {
output = command.bind(this)(room, args); output = command.call(this, room, args);
} catch(err) { } catch(err) {
this.error('command "' + cmd + '" runner: ' + err); this.error('command "' + cmd + '" runner: ' + err);
output = "There was an error running the command."; 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; var command = FFZ.ffz_commands[cmd], output;
if ( command ) { if ( command ) {
try { try {
output = command.bind(this)(room, args); output = command.call(this, room, args);
} catch(err) { } catch(err) {
this.log("Error Running Command - " + cmd + ": " + err, room); this.log("Error Running Command - " + cmd + ": " + err, room);
output = "There was an error running the command."; 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() { ffzUpdateStatus: function() {
if ( f._roomv ) if ( f._roomv )
f._roomv.ffzUpdateStatus(); f._roomv.ffzUpdateStatus();
}.observes('r9k', 'subsOnly', 'slow', 'ffz_banned'), }.observes('r9k', 'subsOnly', 'emoteOnly', 'slow', 'ffz_banned'),
// User Level // User Level
ffzUserLevel: function() { 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 // Delete pending messages
if (t.ffzPending) { if (t.ffzPending) {
msgs = t.ffzPending; msgs = t.ffzPending;
@ -1111,11 +1109,8 @@ FFZ.prototype._modify_room = function(room) {
len = messages.get("length"), len = messages.get("length"),
limit = this.get("messageBufferSize"); limit = this.get("messageBufferSize");
if ( len > limit ) { if ( len > limit )
messages.removeAt(0, 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 // Artificial chat delay
@ -1232,6 +1227,15 @@ FFZ.prototype._modify_room = function(room) {
// Tokenization // Tokenization
f.tokenize_chat_line(msg, false, this.get('roomProperties.hide_chat_links')); 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. // Keep the history.
if ( ! is_whisper && msg.from && msg.from !== 'jtv' && msg.from !== 'twitchnotify' && f.settings.mod_card_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]; var room = f.rooms && f.rooms[msg.room];
@ -1293,6 +1297,10 @@ FFZ.prototype._modify_room = function(room) {
if ( msg.color ) if ( msg.color )
f._handle_color(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. // Add the message.
return this._super(msg); return this._super(msg);
}, },
@ -1403,10 +1411,8 @@ FFZ.prototype._modify_room = function(room) {
if ( f._cindex ) if ( f._cindex )
f._cindex.ffzUpdateChatters(); f._cindex.ffzUpdateChatters();
try { if ( window !== window.parent && parent.postMessage )
if ( window.parent && window.parent.postMessage ) parent.postMessage({from_ffz: true, command: 'chatter_count', data: Object.keys(this.get('ffz_chatters') || {}).length}, location.protocol + "//www.twitch.tv/");
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 */ }
}, },

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 "";
return 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n"; 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 // Emoji Loading
// --------------------- // ---------------------
@ -325,21 +221,29 @@ FFZ.prototype.load_emoji_data = function(callback, tries) {
emoji.code = eid; emoji.code = eid;
new_data[eid] = emoji; new_data[eid] = emoji;
by_name[emoji.short_name] = eid; 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.noto_src = constants.SERVER + 'emoji/noto-' + eid + '.svg';
emoji.one_src = constants.SERVER + 'emoji/one/' + eid + '.svg';
emoji.token = { emoji.token = {
emoticonSrc: true, type: "emoticon",
imgSrc: true,
tw_src: emoji.tw_src, tw_src: emoji.tw_src,
noto_src: emoji.noto_src, noto_src: emoji.noto_src,
one_src: emoji.one_src,
tw: emoji.tw, tw: emoji.tw,
noto: emoji.noto, noto: emoji.noto,
one: emoji.one,
ffzEmoji: eid, ffzEmoji: eid,
altText: emoji.raw altText: emoji.raw
@ -481,6 +385,15 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) {
else else
emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")\\b", "g"); 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); output_css += build_css(emote);
data.count++; data.count++;
data.emoticons[emote.id] = emote; data.emoticons[emote.id] = emote;

View file

@ -14,7 +14,7 @@ var FFZ = window.FrankerFaceZ,
// API Constructor // API Constructor
// --------------------- // ---------------------
var API = FFZ.API = function(instance, name, icon) { var API = FFZ.API = function(instance, name, icon, version) {
this.ffz = instance || FFZ.get(); this.ffz = instance || FFZ.get();
// Check for a known API! // Check for a known API!
@ -54,12 +54,13 @@ var API = FFZ.API = function(instance, name, icon) {
this.name = name || ("Extension#" + this.id); this.name = name || ("Extension#" + this.id);
this.icon = icon || null; this.icon = icon || null;
this.version = version || null;
this.ffz.log('Registered New Extension #' + this.id + ': ' + this.name); 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. // Load the known APIs list.
if ( ! this._known_apis ) { if ( ! this._known_apis ) {
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 else
new_emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")(?=\\W|$)", "g"); 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); output_css += build_css(new_emote);
emote_set.count++; emote_set.count++;
emoticons[id] = new_emote; 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', ''); 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 // Disable Chat Tabs
if ( this._chatv ) { if ( this._chatv ) {
if ( this.settings.group_tabs ) if ( this.settings.group_tabs )
@ -79,6 +87,8 @@ FFZ.prototype.setup_bttv = function(delay) {
this.toggle_style('chat-colors-gray'); this.toggle_style('chat-colors-gray');
this.toggle_style('badges-transparent'); this.toggle_style('badges-transparent');
this.toggle_style('badges-sub-notice');
this.toggle_style('badges-sub-notice-on');
// Disable other features too. // Disable other features too.
document.body.classList.remove('ffz-transparent-badges'); document.body.classList.remove('ffz-transparent-badges');
@ -93,12 +103,6 @@ FFZ.prototype.setup_bttv = function(delay) {
this._draw_following_channels(); this._draw_following_channels();
} }
// Remove Sub Count
if ( this.is_dashboard )
this._update_subscribers();
document.body.classList.add('ffz-bttv');
// Send Message Behavior // Send Message Behavior
var original_send = BetterTTV.chat.helpers.sendMessage, f = this; var original_send = BetterTTV.chat.helpers.sendMessage, f = this;
BetterTTV.chat.helpers.sendMessage = function(message) { BetterTTV.chat.helpers.sendMessage = function(message) {
@ -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 // Emoticonize
var original_emoticonize = BetterTTV.chat.templates.emoticonize; var original_emoticonize = BetterTTV.chat.templates.emoticonize;
BetterTTV.chat.templates.emoticonize = function(message, emotes) { BetterTTV.chat.templates.emoticonize = function(message, emotes) {
@ -206,107 +245,65 @@ FFZ.prototype.setup_bttv = function(delay) {
l_room = room && room.toLowerCase(), l_room = room && room.toLowerCase(),
l_sender = received_sender && received_sender.toLowerCase(), l_sender = received_sender && received_sender.toLowerCase(),
sets = f.getEmotes(l_sender, l_room), sets = f.getEmotes(l_sender, l_room),
emotes = [], emotes = {}, emote,
user = f.get_user(), user = f.get_user(),
new_tokens = [],
mine = user && user.login === l_sender; mine = user && user.login === l_sender;
// Build a list of emotes that match. // Build an object with all of our emotes.
_.each(sets, function(set_id) { for(var i=0; i < sets.length; i++) {
var set = f.emote_sets[set_id]; var emote_set = f.emote_sets[sets[i]];
if ( ! set ) if ( emote_set && emote_set.emoticons )
return; 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) { for(var i=0, l=tokens.length; i < l; i++) {
_.any(tokens, function(token) { var token = tokens[i];
return _.isString(token) && token.match(emote.regex); if ( typeof token !== "string" ) {
}) && emotes.push(emote); new_tokens.push(token);
}); continue;
}); }
// Don't bother proceeding if we have no emotes. // Split the token!
if ( emotes.length ) { var segments = token.split(' '),
// Why is emote parsing so bad? ;_; text = [], segment;
_.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 x=0,y=segments.length; x < y; x++) {
segment = segments[x];
emote = emotes[segment];
for(var i=0; i < old_tokens.length; i++) { if ( emote ) {
var token = old_tokens[i]; if ( text.length ) {
if ( typeof token !== "string" ) { var toks = parse_emoji(text.join(' ') + ' ');
tokens.push(token); for(var q=0; q < toks.length; q++)
continue; new_tokens.push(toks[q]);
}
var tbits = token.split(emote.regex); text = [];
while(tbits.length) { }
var bit = tbits.shift();
if ( tbits.length ) {
bit += tbits.shift();
if ( bit )
tokens.push(bit);
tbits.shift(); 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) + '">']);
tokens.push(eo);
if ( mine && l_room ) if ( mine && l_room )
f.add_usage(l_room, emote); f.add_usage(l_room, emote);
} else text.push('');
tokens.push(bit); } else
} text.push(segment);
} }
});
}
// Sneak in Emojicon Processing if ( text.length > 1 || (text.length === 1 && text[0] !== '') ) {
if ( f.settings.parse_emoji && f.emoji_data ) { var toks = parse_emoji(text.join(' ') + ' ');
var old_tokens = tokens, for(var q=0; q < toks.length; q++)
setting = f.settings.parse_emoji; new_tokens.push(toks[q]);
}
}
tokens = []; return new_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 || ""));
}
}
}
}
}
return tokens;
}
this.update_ui_link(); this.update_ui_link();
} }

View file

@ -99,6 +99,7 @@ FFZ.prototype.find_rechat = function() {
// Tooltips // Tooltips
jQuery(container).find('.tooltip').tipsy({live: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); 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('.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. // Load the room data.
var room_id = el.getAttribute('data-room'); 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 ) { else if ( node.nodeType === node.ELEMENT_NODE ) {
if ( node.tagName === 'IMG' ) if ( node.tagName === 'IMG' )
tokens.push({ tokens.push({
type: "emoticon",
altText: node.alt, altText: node.alt,
emoticonSrc: node.src imgSrc: node.src
}); });
else if ( node.tagName === 'A' ) else if ( node.tagName === 'A' )
tokens.push({ tokens.push({
isLink: true, type: "link",
href: node.textContent isDeleted: false,
isLong: false,
length: node.textContent.length,
link: node.href,
text: node.textContent
}); });
else if ( node.tagName === 'SPAN' ) else if ( node.tagName === 'SPAN' )
tokens.push({ tokens.push({
mentionedUser: node.textContent, type: "mention",
own: node.classList.contains('mentioning') user: node.textContent,
isOwnMessage: node.classList.contains('mentioning')
}); });
else { 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('live', ff.live);
btn.classList.toggle('blue', this.has_bttv && BetterTTV.settings.get('showBlueButtons')); 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.title = message;
btn.target = "_new"; btn.target = "_new";
btn.innerHTML = "<span>" + message + "</span>"; btn.innerHTML = "<span>" + message + "</span>";
@ -122,7 +122,7 @@ FFZ.prototype._update_ff_live = function() {
return; return;
var f = this; var f = this;
Twitch.api.get("streams/" + this.feature_friday.channel) utils.api.get("streams/" + this.feature_friday.channel)
.done(function(data) { .done(function(data) {
f.feature_friday.live = data.stream != null; f.feature_friday.live = data.stream != null;
f.update_ui_link(); f.update_ui_link();

View file

@ -12,6 +12,16 @@ var FFZ = window.FrankerFaceZ = function() {
this._log_data = []; this._log_data = [];
this._apis = {}; 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. // Get things started.
this.initialize(); this.initialize();
} }
@ -19,10 +29,13 @@ var FFZ = window.FrankerFaceZ = function() {
FFZ.get = function() { return FFZ.instance; } FFZ.get = function() { return FFZ.instance; }
// TODO: This should be in a module.
FFZ.msg_commands = {};
// Version // Version
var VER = FFZ.version_info = { var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 100, major: 3, minor: 5, revision: 133,
toString: function() { toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
} }
@ -32,36 +45,38 @@ var VER = FFZ.version_info = {
// Logging // Logging
FFZ.prototype.log = function(msg, data, to_json, log_json) { 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) : "")); this._log_data.push(msg + ((!to_json && log_json) ? " -- " + JSON.stringify(data) : ""));
if ( data !== undefined && console.groupCollapsed && console.dir ) { if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg); console.groupCollapsed("FFZ: " + msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 ) if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
console.log(data); console.log(data);
else else
console.dir(data); console.dir(data);
console.groupEnd(msg); console.groupEnd("FFZ: " + msg);
} else } else
console.log(msg); console.log("FFZ: " + msg);
} }
FFZ.prototype.error = function(msg, data, to_json) { FFZ.prototype.error = function(msg, data, to_json, log_json) {
msg = "FFZ Error: " + msg + (to_json ? " -- " + JSON.stringify(data) : ""); msg = "Error: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
this._log_data.push(msg); this._log_data.push(msg + ((!to_json && log_json) ? " -- " + JSON.stringify(data) : ""));
if ( data !== undefined && console.groupCollapsed && console.dir ) { if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg); console.groupCollapsed("FFZ " + msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 ) if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
console.log(data); console.log(data);
else else
console.dir(data); console.dir(data);
console.groupEnd(msg); console.groupEnd("FFZ " + msg);
} else } 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) { FFZ.prototype._pastebin = function(data, callback) {
jQuery.ajax({url: "http://putco.de/", type: "PUT", data: data, context: this}) jQuery.ajax({url: "http://putco.de/", type: "PUT", data: data, context: this})
.success(function(e) { .success(function(e) {
callback.bind(this)(e.trim() + ".log"); callback.call(this, e.trim() + ".log");
}).fail(function(e) { }).fail(function(e) {
callback.bind(this)(null); callback.call(this, null);
}); });
} }
@ -130,6 +145,7 @@ require('./ember/router');
require('./ember/channel'); require('./ember/channel');
require('./ember/player'); require('./ember/player');
require('./ember/room'); require('./ember/room');
require('./ember/vod-chat');
require('./ember/layout'); require('./ember/layout');
require('./ember/line'); require('./ember/line');
require('./ember/chatview'); require('./ember/chatview');
@ -143,7 +159,7 @@ require('./ember/following');
require('./debug'); require('./debug');
require('./ext/rechat'); //require('./ext/rechat');
require('./ext/betterttv'); require('./ext/betterttv');
require('./ext/emote_menu'); require('./ext/emote_menu');
@ -156,6 +172,7 @@ require('./ui/tooltips');
require('./ui/notifications'); require('./ui/notifications');
require('./ui/viewer_count'); require('./ui/viewer_count');
require('./ui/sub_count'); require('./ui/sub_count');
require('./ui/dash_stats');
require('./ui/menu_button'); require('./ui/menu_button');
require('./ui/following'); require('./ui/following');
@ -182,6 +199,14 @@ FFZ.prototype.initialize = function(increment, delay) {
return; 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. // Check for special non-ember pages.
if ( /^\/(?:$|search$|user\/|p\/|settings|m\/|messages?\/)/.test(location.pathname) ) { if ( /^\/(?:$|search$|user\/|p\/|settings|m\/|messages?\/)/.test(location.pathname) ) {
this.init_normal(delay); 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) { FFZ.prototype.init_player = function(delay) {
var start = (window.performance && performance.now) ? performance.now() : Date.now(); 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.users = {};
this.is_dashboard = false; this.is_dashboard = false;
@ -243,7 +279,8 @@ FFZ.prototype.init_player = function(delay) {
FFZ.prototype.init_normal = function(delay, no_socket) { FFZ.prototype.init_normal = function(delay, no_socket) {
var start = (window.performance && performance.now) ? performance.now() : Date.now(); 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.users = {};
this.is_dashboard = false; this.is_dashboard = false;
@ -272,6 +309,7 @@ FFZ.prototype.init_normal = function(delay, no_socket) {
this.setup_following_count(false); this.setup_following_count(false);
this.setup_menu(); this.setup_menu();
this.setup_message_event();
this.fix_tooltips(); this.fix_tooltips();
this.find_bttv(10); this.find_bttv(10);
@ -286,7 +324,11 @@ FFZ.prototype.is_dashboard = false;
FFZ.prototype.init_dashboard = function(delay) { FFZ.prototype.init_dashboard = function(delay) {
var start = (window.performance && performance.now) ? performance.now() : Date.now(); 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.users = {};
this.is_dashboard = true; this.is_dashboard = true;
@ -311,6 +353,7 @@ FFZ.prototype.init_dashboard = function(delay) {
this.setup_notifications(); this.setup_notifications();
this.setup_following_count(false); this.setup_following_count(false);
this.setup_menu(); this.setup_menu();
this.setup_dash_stats();
this._update_subscribers(); this._update_subscribers();
@ -329,7 +372,8 @@ FFZ.prototype.init_dashboard = function(delay) {
FFZ.prototype.init_ember = function(delay) { FFZ.prototype.init_ember = function(delay) {
var start = (window.performance && performance.now) ? performance.now() : Date.now(); 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.users = {};
this.is_dashboard = false; this.is_dashboard = false;
@ -367,6 +411,7 @@ FFZ.prototype.init_ember = function(delay) {
this.setup_player(); this.setup_player();
this.setup_channel(); this.setup_channel();
this.setup_room(); this.setup_room();
this.setup_vod_chat();
this.setup_line(); this.setup_line();
this.setup_layout(); this.setup_layout();
this.setup_chatview(); this.setup_chatview();
@ -389,12 +434,14 @@ FFZ.prototype.init_ember = function(delay) {
this.fix_tooltips(); this.fix_tooltips();
this.connect_extra_chat(); this.connect_extra_chat();
this.setup_rechat(); //this.setup_rechat();
this.setup_message_event();
this.find_bttv(10); this.find_bttv(10);
this.find_emote_menu(10); this.find_emote_menu(10);
//this.check_news(); //this.check_news();
this.check_ff(); this.check_ff();
this.refresh_chat();
var end = (window.performance && performance.now) ? performance.now() : Date.now(), var end = (window.performance && performance.now) ? performance.now() : Date.now(),
duration = end - start; duration = end - start;
@ -414,8 +461,16 @@ FFZ.prototype.setup_message_event = function() {
FFZ.prototype._on_window_message = function(e) { 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; 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);
} }

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,7 @@ FFZ.prototype._ws_open = false;
FFZ.prototype._ws_delay = 0; FFZ.prototype._ws_delay = 0;
FFZ.prototype._ws_host_idx = -1; FFZ.prototype._ws_host_idx = -1;
FFZ.prototype._ws_current_pool = -1; FFZ.prototype._ws_current_pool = -1;
FFZ.prototype._ws_last_ping = null;
FFZ.prototype._ws_server_offset = null; FFZ.prototype._ws_server_offset = null;
@ -166,7 +167,7 @@ FFZ.prototype.ws_create = function() {
// Send the channel(s). // Send the channel(s).
if ( f._cindex ) { 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'); hosted_id = f._cindex.get('controller.hostModeTarget.id');
if ( channel_id ) if ( channel_id )
@ -343,7 +344,7 @@ FFZ.prototype.setup_time = function() {
difference = (new_time - last_time) - 5000; difference = (new_time - last_time) - 5000;
last_time = new_time; 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.log("WARNING! Time drift of " + difference + "ms across 5 seconds. Did the local time change?");
f._ws_server_offset = null; f._ws_server_offset = null;
f.ws_ping(); f.ws_ping();
@ -373,7 +374,7 @@ FFZ.prototype._ws_on_pong = function(success, server_time) {
if ( this._ws_ping_time ) { if ( this._ws_ping_time ) {
var rtt = now - 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_ping_time = null;
this._ws_server_offset = (d_now - (server_time + ping)); this._ws_server_offset = (d_now - (server_time + ping));
@ -404,7 +405,7 @@ FFZ.ws_commands.reconnect = function() {
// Socket Close Callbacks // Socket Close Callbacks
for(var i=0; i < FFZ.ws_on_close.length; i++) { for(var i=0; i < FFZ.ws_on_close.length; i++) {
try { try {
FFZ.ws_on_close[i].bind(this)(); FFZ.ws_on_close[i].call(this);
} catch(err) { } catch(err) {
this.log("Error on Socket Close Callback: " + 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 */ /* 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) .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) .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) { .app-main:not(.theatre) .ember-chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-0) {
filter: invert(100%); filter: invert(100%);

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,26 @@
var FFZ = window.FrankerFaceZ, 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 // Initialization
// ------------------- // -------------------
FFZ.prototype._has_news = false; /*FFZ.prototype._has_news = false;
FFZ.prototype._news_id = 0; FFZ.prototype._news_id = 0;
FFZ.prototype.check_news = function(tries) { FFZ.prototype.check_news = function(tries) {
jQuery.ajax(constants.SERVER + "script/news.json", {cache: false, dataType: "json", context: this}) jQuery.ajax(constants.SERVER + "script/news.json", {cache: false, dataType: "json", context: this})
.done(function(data) { .done(function(data) {
FFZ.ws_commands.update_news.bind(this)(data.id); FFZ.ws_commands.update_news.call(this, data.id);
}).fail(function(data) { }).fail(function(data) {
if ( data.status === 404 ) if ( data.status === 404 )
return; return;
@ -26,7 +34,7 @@ FFZ.prototype.check_news = function(tries) {
FFZ.ws_commands.update_news = function(version) { FFZ.ws_commands.update_news = function(version) {
var old_version = parseInt(localStorage.ffzLastNewsId || "0") || 0; 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; old_version = 0;
if ( version <= old_version ) { if ( version <= old_version ) {
@ -37,192 +45,363 @@ FFZ.ws_commands.update_news = function(version) {
this._has_news = true; this._has_news = true;
this._news_id = version; this._news_id = version;
this.update_ui_link(); this.update_ui_link();
} }*/
// ------------------- // -------------------
// About Page // About Page
// ------------------- // -------------------
FFZ.menu_pages.about_changelog = { var include_html = function(heading_text, filename) {
name: "Changelog", return function(view, container) {
visible: false, var heading = createElement('div');
wide: true, heading.className = 'chat-menu-content center';
heading.innerHTML = '<h1>FrankerFaceZ</h1>' + (heading_text ? '<div class="ffz-about-subheading">' + heading_text + '</div>' : '');
render: function(view, container) { jQuery.ajax(filename, {cache: false, context: this})
var heading = document.createElement('div'); .done(function(data) {
container.appendChild(heading);
container.innerHTML += data;
heading.className = 'chat-menu-content center'; jQuery('#ffz-old-news-button', container).on('click', function() {
heading.innerHTML = '<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">change log</div>'; jQuery(this).remove();
jQuery('#ffz-old-news', container).css('display', 'block');
});
jQuery.ajax(constants.SERVER + "script/changelog.html", {cache: false, context: this}) }).fail(function(data) {
.done(function(data) { var content = createElement('div');
container.appendChild(heading); content.className = 'chat-menu-content menu-side-padding';
container.innerHTML += data; content.textContent = 'There was an error loading this page from the server.';
}).fail(function(data) { container.appendChild(heading);
var content = document.createElement('div'); container.appendChild(content);
content.className = 'chat-menu-content menu-side-padding'; });
content.textContent = 'There was an error loading the change log from the server.'; }
},
container.appendChild(heading); render_news = include_html("news", constants.SERVER + "script/news.html");
container.appendChild(content);
});
}
};
FFZ.menu_pages.about_news = { var update_player_stats = function(player, container) {
name: "News", if ( ! document.querySelector('.ffz-ui-sub-menu-page[data-page="debugging"]') || ! player.getVideoInfo )
visible: false, return;
wide: true,
render: function(view, container) { setTimeout(update_player_stats.bind(this, player, container), 1000);
// Handle the news state.
if ( this._has_news ) {
this._has_news = false;
localStorage.ffzLastNewsId = this._news_id;
this.update_ui_link();
}
var player_data;
var heading = document.createElement('div'); try {
player_data = player.getVideoInfo();
} catch(err) { }
heading.className = 'chat-menu-content center'; if ( ! player_data )
heading.innerHTML = '<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">announcements and news</div>'; return;
jQuery.ajax(constants.SERVER + "script/news.html", {cache: false, context: this}) var sorted_keys = Object.keys(player_data).sort();
.done(function(data) { for(var i=0; i < sorted_keys.length; i++) {
container.appendChild(heading); var key = sorted_keys[i],
container.innerHTML += data; data = player_data[key],
line = container.querySelector('li[data-property="' + key + '"]');
}).fail(function(data) { if ( ! line ) {
var content = document.createElement('div'); var desc = NICE_DESCRIPTION.hasOwnProperty(key) ? NICE_DESCRIPTION[key] : key;
content.className = 'chat-menu-content menu-side-padding'; if ( ! desc )
content.textContent = 'There was an error loading the announcements from the server.'; continue;
container.appendChild(heading); line = createElement('li');
container.appendChild(content); line.setAttribute('data-property', key);
}); line.innerHTML = desc + '<span></span>';
} container.appendChild(line);
}
line.querySelector('span').textContent = data;
}
}; };
FFZ.menu_pages.about = { FFZ.menu_pages.about = {
name: "About", name: "About",
icon: constants.HEART, icon: constants.HEART,
sort_order: 100000, sort_order: 100000,
render: function(view, container, inner, menu) { pages: {
var room = this.rooms[view.get("context.currentRoom.id")], about: {
has_emotes = false, f = this; 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 ) {
if ( room && room.set ) { var set = this.emote_sets[room.set];
var set = this.emote_sets[room.set]; if ( set && set.count > 0 )
if ( set && set.count > 0 ) has_emotes = true;
has_emotes = true; }
}
// Heading // Heading
var heading = document.createElement('div'), var heading = createElement('div'),
content = ''; content = '';
content += "<h1>FrankerFaceZ</h1>"; content += "<h1>FrankerFaceZ</h1>";
content += '<div class="ffz-about-subheading">new ways to woof</div>'; content += '<div class="ffz-about-subheading">new ways to woof</div>';
heading.className = 'chat-menu-content center'; heading.className = 'chat-menu-content center';
heading.innerHTML = content; heading.innerHTML = content;
container.appendChild(heading); container.appendChild(heading);
var clicks = 0, head = heading.querySelector("h1"); var clicks = 0, head = heading.querySelector("h1");
head && head.addEventListener("click", function() { head && head.addEventListener("click", function() {
head.style.cursor = "pointer"; head.style.cursor = "pointer";
clicks++; clicks++;
if ( clicks >= 3 ) { if ( clicks >= 3 ) {
clicks = 0; clicks = 0;
var el = document.querySelector(".app-main") || document.querySelector(".ember-chat-container"); var el = document.querySelector(".app-main") || document.querySelector(".ember-chat-container");
el && el.classList.toggle('ffz-flip'); el && el.classList.toggle('ffz-flip');
} }
setTimeout(function(){clicks=0;head.style.cursor=""},2000); setTimeout(function(){clicks=0;head.style.cursor=""},2000);
}); });
// Button Stuff // Button Stuff
var btn_container = document.createElement('div'), var btn_container = createElement('div'),
ad_button = document.createElement('a'), ad_button = createElement('a'),
news_button = document.createElement('a'), news_button = createElement('a'),
donate_button = document.createElement('a'), donate_button = createElement('a'),
message = "To use custom emoticons in " + (has_emotes ? "this channel" : "tons of channels") + ", get FrankerFaceZ from http://www.frankerfacez.com"; message = "To use custom emoticons in " + (has_emotes ? "this channel" : "tons of channels") + ", get FrankerFaceZ from https://www.frankerfacez.com";
// News // Advertising
/*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); ad_button.className = 'button primary';
btn_container.className = 'chat-menu-content center'; ad_button.innerHTML = "Advertise in Chat";
container.appendChild(btn_container); ad_button.addEventListener('click', this._add_emote.bind(this, view, message));
btn_container = document.createElement('div');*/
btn_container.appendChild(ad_button);
// Donate
donate_button.className = 'button ffz-donate';
donate_button.href = "https://www.frankerfacez.com/donate";
donate_button.target = "_new";
donate_button.innerHTML = "Donate";
btn_container.appendChild(donate_button);
btn_container.className = 'chat-menu-content center';
container.appendChild(btn_container);
// Advertising // Credits
var credits = createElement('div');
ad_button.className = 'button primary'; content = '<table class="ffz-about-table">';
ad_button.innerHTML = "Advertise in Chat"; content += '<tr><th colspan="4">Developers</th></tr>';
ad_button.addEventListener('click', this._add_emote.bind(this, view, message)); 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>';
btn_container.appendChild(ad_button); 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>';
// Donate credits.className = 'chat-menu-content center';
credits.innerHTML = content;
donate_button.className = 'button ffz-donate'; // Make the Version clickable.
donate_button.href = "https://www.frankerfacez.com/donate"; credits.querySelector('#ffz-changelog').addEventListener('click',
donate_button.target = "_new"; f._ui_change_subpage.bind(f, view, inner, menu, container, 'changelog'));
donate_button.innerHTML = "Donate";
btn_container.appendChild(donate_button); // Make the Logs button functional.
btn_container.className = 'chat-menu-content center'; var getting_logs = false;
container.appendChild(btn_container); credits.querySelector('#ffz-debug-logs').addEventListener('click', function() {
if ( getting_logs )
return;
getting_logs = true;
f._pastebin(f._log_data.join("\n"), function(url) {
getting_logs = false;
if ( ! url )
alert("There was an error uploading the FrankerFaceZ logs.");
else
prompt("Your FrankerFaceZ logs have been uploaded to the URL:", url);
});
});
container.appendChild(credits);
}
},
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';
// Credits info_head.innerHTML = 'Client Status';
var credits = document.createElement('div');
content = '<table class="ffz-about-table">'; for(var i=0; i < info_list.length; i++) {
content += '<tr><th colspan="4">Developers</th></tr>'; var data = info_list[i],
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>'; line = createElement('li');
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>'; line.innerHTML = data === null ? '<br>' : data[0] + '<span>' + data[1] + '</span>';
info.appendChild(line);
}
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'; twitch_head.innerHTML = 'Twitch Configuration';
credits.innerHTML = content;
// Functional Changelog // Check for Twitch geo-location
credits.querySelector('#ffz-changelog').addEventListener('click', function() { var user = this.get_user();
f._ui_change_page(view, inner, menu, container, 'about_changelog'); if ( user && user.login ) {
}); twitch_list.push(["Current User", user.login + " [" + user.id + "]"]);
var us = [];
// Make the Logs button functional. user.is_staff && us.push("staff");
var getting_logs = false; user.is_admin && us.push("admin");
credits.querySelector('#ffz-debug-logs').addEventListener('click', function() { user.is_partner && us.push("partner");
if ( getting_logs ) user.is_broadcaster && us.push("broadcaster");
return; user.has_turbo && us.push("turbo");
getting_logs = true; twitch_list.push(["User State", us.join(", ") || "<i>none</i>"]);
f._pastebin(f._log_data.join("\n"), function(url) {
getting_logs = false;
if ( ! url )
alert("There was an error uploading the FrankerFaceZ logs.");
else
prompt("Your FrankerFaceZ logs have been uploaded to the URL:", url);
});
});
container.appendChild(credits); } 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.", help: "Set additional keywords that will be highlighted in chat.",
method: function() { 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.", help: "Set a list of words that will be removed from chat messages, locally.",
method: function() { 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); document.body.classList.toggle("ffz-dark", val);
var Settings = window.App && App.__container__.lookup('controller:settings'), var Settings = window.App && App.__container__.lookup('controller:settings'),
settings = Settings.get('settings'); settings = Settings && Settings.get('settings');
if ( val ) { if ( val ) {
this._load_dark_css(); this._load_dark_css();
@ -146,8 +146,9 @@ FFZ.settings_info.dark_twitch = {
} else } else
settings && settings.set('darkMode', this.settings.twitch_chat_dark); settings && settings.set('darkMode', this.settings.twitch_chat_dark);
// Try coloring ReChat // Try coloring chat replay
jQuery('.rechat-chat-line').parents('.chat-container').toggleClass('dark', val || this.settings.twitch_chat_dark); 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(); 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'), var total = Live.get('total'),
streams = Live.get('content'); streams = Live.get('content');
if ( typeof total === "number" ) { 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); return setTimeout(this._following_get_me.bind(this, tries), Math.floor(2000*Math.random()) + 500);
var f = this; 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.log("Fetched User Data -- " + (data.name || data.login));
f.__user = data; f.__user = data;
f._update_following_count(); f._update_following_count();
@ -128,17 +134,21 @@ FFZ.prototype._update_following_count = function() {
var Stream = window.App && App.__container__.resolve('model:stream'), var Stream = window.App && App.__container__.resolve('model:stream'),
Live = Stream && Stream.find("live"), Live = Stream && Stream.find("live"),
Host = window.App && App.__container__.resolve('model:host'),
HostLive = Host && Host.find("following"),
f = this; f = this;
if ( HostLive && document.body.getAttribute('data-current-path').indexOf('directory.following') !== -1 )
HostLive.load();
if ( Live ) if ( Live )
Live.load(); Live.load();
else { else {
var a = {}, var u = this.get_user();
u = this.get_user();
a.Authorization = "OAuth " + u.chat_oauth_token; utils.api.get("streams/followed", {limit:20, offset:0}, {version:3}, u && u.chat_oauth_token)
Twitch.api && Twitch.api.get("streams/followed", {limit:20, offset:0}, {version:3, headers: a})
.done(function(data) { .done(function(data) {
f._draw_following_count(data._total); f._draw_following_count(data._total);
f._draw_following_channels(data.streams, 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', var tooltip = (this.has_bttv ? '<span class="stat playing">FrankerFaceZ</span>' : '') + 'Following',
bb = el.getBoundingClientRect(), bb = el.getBoundingClientRect(),
height = document.body.clientHeight - (bb.bottom + 54), height = document.body.clientHeight - (bb.bottom + 50),
max_lines = Math.max(Math.floor(height / 36) - 1, 2), 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, 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 ) { if ( streams && streams.length ) {
var c = 0;
for(var i=0, l = streams.length; i < l; i++) { for(var i=0, l = streams.length; i < l; i++) {
var stream = streams[i]; var stream = streams[i];
if ( ! stream || ! stream.channel ) 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>' : '') + (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>' + '<span class="stat">' + constants.LIVE + ' ' + utils.number_commas(stream.viewers) + '</span>' +
'<b>' + utils.sanitize(stream.channel.display_name || stream.channel.name) + '</b><br>' + '<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."; 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. // Reposition the tooltip.
setTimeout(function() { setTimeout(function() {

View file

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

View file

@ -5,9 +5,9 @@ var FFZ = window.FrankerFaceZ,
fix_menu_position = function(container) { fix_menu_position = function(container) {
var swapped = document.body.classList.contains('ffz-sidebar-swap') && ! document.body.classList.contains('ffz-portrait'); 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'), left = parseInt(container.style.left || '0'),
right = bounds.left + container.scrollWidth, right = bounds.left + bounds.width,
moved = !!container.style.left; moved = !!container.style.left;
if ( swapped ) { if ( swapped ) {
@ -217,7 +217,7 @@ FFZ.prototype.build_ui_popup = function(view) {
// Stuff // Stuff
jQuery(inner).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); 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 // Menu Container
var sub_container = document.createElement('div'); var sub_container = document.createElement('div');
@ -270,7 +270,7 @@ FFZ.prototype.build_ui_popup = function(view) {
var page = FFZ.menu_pages[key]; var page = FFZ.menu_pages[key];
try { 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; continue;
} catch(err) { } catch(err) {
this.error("menu_pages " + key + " visible: " + err); this.error("menu_pages " + key + " visible: " + err);
@ -298,7 +298,7 @@ FFZ.prototype.build_ui_popup = function(view) {
el = document.createElement('li'), el = document.createElement('li'),
link = document.createElement('a'); 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; el.id = "ffz-menu-page-" + key;
link.title = page.name; link.title = page.name;
link.innerHTML = page.icon; link.innerHTML = page.icon;
@ -315,12 +315,12 @@ FFZ.prototype.build_ui_popup = function(view) {
var page = (this._last_page || "channel").split("_", 1)[0]; var page = (this._last_page || "channel").split("_", 1)[0];
// Do we have news? // 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. // 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._ui_change_page(view, inner, menu, sub_container, 'about_news');
this._last_page = page; this._last_page = page;
} else } else*/
// Render Current Page // Render Current Page
this._ui_change_page(view, inner, menu, sub_container, 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) { FFZ.prototype._ui_change_page = function(view, inner, menu, container, page) {
this._last_page = page; this._last_page = page;
container.innerHTML = ""; container.innerHTML = "";
container.setAttribute('data-page', page); container.setAttribute('data-page', page);
// Allow settings to be wide. We need to know if chat is stand-alone. // Get the data structure for the new page.
var app = document.querySelector(".app-main") || document.querySelector(".ember-chat-container"); var data = FFZ.menu_pages[page];
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";
var els = menu.querySelectorAll('li.active'); // See if we're dealing with a sub-menu situation.
for(var i=0; i < els.length; i++) if ( data.pages ) {
els[i].classList.remove('active'); // 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); height = parseInt(container.style.maxHeight || '0');
if ( el )
el.classList.add('active');
else
this.log("No matching page: " + page);
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. // Re-position if necessary.
var f = this; var f = this;
@ -430,7 +548,16 @@ FFZ.menu_pages.channel = {
can_use = is_subscribed || !emote.subscriber_only, 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)'; 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 = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")';
s.style.backgroundImage = '-webkit-' + img_set; s.style.backgroundImage = '-webkit-' + img_set;
@ -438,19 +565,18 @@ FFZ.menu_pages.channel = {
s.style.backgroundImage = '-ms-' + img_set; s.style.backgroundImage = '-ms-' + img_set;
s.style.backgroundImage = img_set; s.style.backgroundImage = img_set;
s.style.width = emote.width + "px"; s.style.width = (10+emote.width) + "px";
s.style.height = emote.height + "px"; s.style.height = (10+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.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 ) if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )
window.open("https://twitchemotes.com/emote/" + id); window.open("https://twitchemotes.com/emote/" + id);
else if ( can_use ) else if ( can_use )
this._add_emote(view, code); this._add_emote(view, code, "twitch-" + emote_set, id, e);
else else
return; return;
e.preventDefault(); e.preventDefault();
}.bind(this, can_use, emote.id, emote.regex)); }.bind(this, can_use, emote.id, emote.regex, emote.emoticon_set));
grid.appendChild(s); grid.appendChild(s);
c++; c++;
@ -507,7 +633,7 @@ FFZ.menu_pages.channel = {
var extra_sets = _.union(room && room.extra_sets || [], room && room.ext_sets || [], []); var extra_sets = _.union(room && room.extra_sets || [], room && room.ext_sets || [], []);
// Basic Emote 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++) { for(var i=0; i < extra_sets.length; i++) {
// Look up the set name. // Look up the set name.
@ -593,7 +719,11 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub
c++; c++;
var s = document.createElement('span'); 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] + '")'; s.style.backgroundImage = 'url("' + emote.urls[1] + '")';
if ( srcset ) { 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.backgroundImage = img_set;
} }
s.style.width = emote.width + "px"; s.style.width = (10+emote.width) + "px";
s.style.height = emote.height + "px"; s.style.height = (10+emote.height) + "px";
s.title = this._emote_tooltip(emote);
s.addEventListener('click', function(id, code, e) { s.addEventListener('click', function(id, code, e) {
e.preventDefault(); 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; var input_el, text, room;
if ( this.has_bttv ) { 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('live', live);
link.classList.toggle('dark', dark); link.classList.toggle('dark', dark);
link.classList.toggle('blue', blue); 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 = { FFZ.settings_info.emoji_in_menu = {
type: "boolean", type: "boolean",
value: false, value: true,
category: "Chat Input", category: "Chat Input",
@ -66,11 +66,18 @@ FFZ.settings_info.emoji_in_menu = {
FFZ.settings_info.emote_menu_collapsed = { FFZ.settings_info.emote_menu_collapsed = {
storage_key: "ffz_setting_my_emoticons_collapsed_sections",
value: [], value: [],
visible: false visible: false
} }
FFZ.settings_info.favorite_emotes = {
value: {},
visible: false
}
FFZ.prototype.setup_my_emotes = function() { FFZ.prototype.setup_my_emotes = function() {
this._twitch_badges = {}; this._twitch_badges = {};
this._twitch_badges["--global--"] = "//cdn.frankerfacez.com/script/twitch_logo.png"; 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; return ffz_sets.length || (sk && sk.length) || this.settings.emoji_in_menu;
}, },
render: function(view, container) { default_page: function() {
var tmi = view.get('controller.currentRoom.tmiSession'), for(var key in this.settings.favorite_emotes)
twitch_sets = (tmi && tmi.getEmotes() || {'emoticon_sets': {}})['emoticon_sets']; 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 'all';
return FFZ.menu_pages.myemotes.draw_menu.bind(this)(view, container, twitch_sets); },
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) { toggle_section: function(heading, container) {
var menu = heading.parentElement, var menu = heading.parentElement,
set_id = menu.getAttribute('data-set'), set_id = menu.getAttribute('data-set'),
collapsed_list = this.settings.emote_menu_collapsed, 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); collapsed_list.removeObject(set_id);
else else
collapsed_list.push(set_id); 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); 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'), var heading = document.createElement('div'),
menu = 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, f = this,
settings = this.settings.parse_emoji || 1; settings = this.settings.parse_emoji || 1,
favorites = this.settings.favorite_emotes["emoji"] || [],
c = 0;
heading.className = 'heading'; menu.className = 'emoticon-grid';
heading.innerHTML = '<span class="right">Unicode</span>Emoji'; menu.setAttribute('data-set', menu_id);
heading.style.backgroundImage = 'url("' + constants.SERVER + 'emoji/' + (settings === 2 ? 'noto-' : 'tw-') + '1f4af.svg")';
heading.style.backgroundSize = "18px";
menu.className = 'emoticon-grid collapsable'; if ( ! favorites_only ) {
menu.appendChild(heading); heading.className = 'heading';
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.setAttribute('data-set', 'emoji'); menu.classList.add('collapsable');
menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('emoji') !== -1); menu.appendChild(heading);
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 = []; var set = [];
@ -162,66 +330,92 @@ FFZ.menu_pages.myemotes = {
var emoji = set[i], var emoji = set[i],
em = document.createElement('span'); 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; 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 + '">' : ''; image = this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + src + '">' : '';
em.className = 'emoticon emoji html-tooltip'; if ( favorites_only && ! is_favorite )
em.title = image + 'Emoji: ' + emoji.raw + '<br>Name: ' + emoji.name + (emoji.short_name ? '<br>Short Name: :' + emoji.short_name + ':' : ''); continue;
em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw));
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.backgroundImage = 'url("' + src + '")';
em.style.backgroundSize = "18px"; 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; 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'), var heading = document.createElement('div'),
menu = 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, 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] || [],
if ( channel_id === "twitch_unknown" ) c = 0;
title = "Unknown Channel";
else if ( channel_id === "--global--" )
title = "Global Emoticons";
else if ( channel_id === "turbo" || channel_id === "--turbo-faces--" )
title = "Twitch Turbo";
else
title = FFZ.get_capitalization(channel_id, function(name) {
heading.innerHTML = '<span class="right">Twitch</span>' + utils.sanitize(name);
});
heading.className = 'heading';
heading.innerHTML = '<span class="right">Twitch</span>' + utils.sanitize(title);
if ( this._twitch_badges[channel_id] )
heading.style.backgroundImage = 'url("' + this._twitch_badges[channel_id] + '")';
else {
var f = this;
Twitch.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;
localStorage.ffzTwitchBadges = JSON.stringify(f._twitch_badges);
heading.style.backgroundImage = 'url("' + data.subscriber.image + '")';
}
});
}
menu.className = 'emoticon-grid collapsable';
menu.appendChild(heading);
menu.className = 'emoticon-grid';
menu.setAttribute('data-set', 'twitch-' + set_id); 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); }); if ( ! favorites_only ) {
if ( channel_id === "twitch_unknown" )
title = "Unknown Channel";
else if ( channel_id === "--global--" )
title = "Global Emoticons";
else if ( channel_id === "turbo" || channel_id === "--turbo-faces--" )
title = "Twitch Turbo";
else
title = FFZ.get_capitalization(channel_id, function(name) {
heading.innerHTML = '<span class="right">Twitch</span>' + utils.sanitize(name);
});
heading.className = 'heading';
heading.innerHTML = '<span class="right">Twitch</span>' + utils.sanitize(title);
if ( this._twitch_badges[channel_id] )
heading.style.backgroundImage = 'url("' + this._twitch_badges[channel_id] + '")';
else {
var f = this;
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;
localStorage.ffzTwitchBadges = JSON.stringify(f._twitch_badges);
heading.style.backgroundImage = 'url("' + data.subscriber.image + '")';
}
});
}
menu.classList.add('collapsable');
menu.appendChild(heading);
menu.classList.toggle('collapsed', collapsed);
heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this, emotes); });
}
set.sort(function(a,b) { set.sort(function(a,b) {
var an = a.code.toLowerCase(), var an = a.code.toLowerCase(),
@ -237,11 +431,21 @@ FFZ.menu_pages.myemotes = {
for(var i=0; i < set.length; i++) { for(var i=0; i < set.length; i++) {
var emote = set[i], var emote = set[i],
code = constants.KNOWN_CODES[emote.code] || emote.code, code = constants.KNOWN_CODES[emote.code] || emote.code,
is_favorite = favorites.indexOf(emote.id) !== -1;
em = document.createElement('span'), if ( favorites_only && ! is_favorite )
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)'; 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] ) { if ( this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[emote.id] ) {
em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")'; em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")';
@ -250,45 +454,63 @@ FFZ.menu_pages.myemotes = {
em.style.backgroundImage = '-webkit-' + img_set; em.style.backgroundImage = '-webkit-' + img_set;
em.style.backgroundImage = '-moz-' + img_set; em.style.backgroundImage = '-moz-' + img_set;
em.style.backgroundImage = '-ms-' + 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) { em.addEventListener("click", function(id, c, e) {
e.preventDefault(); e.preventDefault();
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )
window.open("https://twitchemotes.com/emote/" + id); window.open("https://twitchemotes.com/emote/" + id);
else else
this._add_emote(view, c); this._add_emote(view, c, "twitch-" + set_id, id, e);
}.bind(this, emote.id, code)); }.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; return menu;
}, },
draw_ffz_set: function(view, set) { draw_ffz_set: function(view, set, favorites_only) {
var heading = document.createElement('div'), var heading = document.createElement('div'),
menu = document.createElement('div'), menu = document.createElement('div'),
emote_container = favorites_only ? document.createDocumentFragment() : document.createElement('div'),
f = this, f = this,
emotes = [], emotes = [],
menu_id = set.hasOwnProperty('source_ext') ? 'ffz-ext-' + set.source_ext + '-' + set.source_id : 'ffz-' + set.id, 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;
heading.className = 'heading'; menu.className = 'emoticon-grid';
heading.innerHTML = '<span class="right">' + (utils.sanitize(set.source) || 'FrankerFaceZ') + '</span>' + set.title; menu.setAttribute('data-set', menu_id);
heading.style.backgroundImage = 'url("' + icon + '")'; if ( ! favorites_only ) {
if ( icon.indexOf('.svg') !== -1 ) menu.classList.add('collapsable');
heading.style.backgroundSize = "18px";
menu.className = 'emoticon-grid collapsable'; heading.className = 'heading';
menu.appendChild(heading); heading.innerHTML = '<span class="right">' + (utils.sanitize(set.source) || 'FrankerFaceZ') + '</span>' + set.title;
menu.setAttribute('data-set', menu_id); heading.style.backgroundImage = 'url("' + icon + '")';
menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf(menu_id) !== -1); if ( icon.indexOf('.svg') !== -1 )
heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); }); heading.style.backgroundSize = "18px";
menu.appendChild(heading);
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) for(var emote_id in set.emoticons)
set.emoticons.hasOwnProperty(emote_id) && ! set.emoticons[emote_id].hidden && emotes.push(set.emoticons[emote_id]); 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++) { for(var i=0; i < emotes.length; i++) {
var emote = emotes[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'; img_set = 'image-set(url("' + emote.urls[1] + '") 1x';
if ( emote.urls[2] ) if ( emote.urls[2] )
@ -318,19 +544,24 @@ FFZ.menu_pages.myemotes = {
img_set += ')'; 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 = 'url("' + emote.urls[1] + '")';
em.style.backgroundImage = '-webkit-' + img_set; em.style.backgroundImage = '-webkit-' + img_set;
em.style.backgroundImage = '-moz-' + img_set; em.style.backgroundImage = '-moz-' + img_set;
em.style.backgroundImage = '-ms-' + img_set; em.style.backgroundImage = '-ms-' + img_set;
em.style.backgroudnImage = img_set; em.style.backgroundImage = img_set;
if ( emote.height ) if ( emote.height )
em.style.height = emote.height + "px"; em.style.height = (10+emote.height) + "px";
if ( emote.width ) 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) { em.addEventListener("click", function(id, code, e) {
e.preventDefault(); e.preventDefault();
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) { if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) {
@ -345,97 +576,22 @@ FFZ.menu_pages.myemotes = {
if ( url ) if ( url )
window.open(url); window.open(url);
} else } else
this._add_emote(view, code); this._add_emote(view, code, menu_id, id, e);
}.bind(this, emote.id, emote.name)); }.bind(this, emote.id, emote.name));
menu.appendChild(em);
c++;
emote_container.appendChild(em);
} }
if ( ! c )
return;
if ( favorites_only )
return emote_container;
if ( ! collapsed )
menu.appendChild(emote_container);
return menu; 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' )
return;
container.innerHTML = "";
try {
var 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 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);
}
} }
}; };

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.", help: "Specify how long notifications should be displayed before automatically closing.",
method: function() { method: function() {
var old_val = this.settings.notification_timeout, var f = this;
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); 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;
if ( new_val === null || new_val === undefined ) new_val = parseInt(new_val);
return; if ( Number.isNaN(new_val) || ! Number.isFinite(new_val) || new_val < 1 )
new_val = 60;
var parsed = parseInt(new_val); f.settings.set("notification_timeout", new_val);
if ( parsed === NaN || parsed < 1 ) });
parsed = 60;
this.settings.set("notification_timeout", parsed);
} }
}; };
@ -136,7 +141,7 @@ FFZ.prototype.show_notification = function(message, title, tag, timeout, on_clic
dir: "ltr", dir: "ltr",
body: message, body: message,
tag: tag || "FrankerFaceZ", tag: tag || "FrankerFaceZ",
icon: "http://cdn.frankerfacez.com/icon32.png" icon: "//cdn.frankerfacez.com/icon32.png"
}; };
var f = this, 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 ) if ( e.button && e.button !== 0 )
return; 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, var popup = f._popup,
parent = f._popup_parent; 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++) { for(var i=0; i < entrants.length; i++) {
var ent = entrants[i], var ent = entrants[i],
name = '<a target="_new" href="http://www.speedrunslive.com/profiles/#!/' + utils.sanitize(ent.name) + '">' + ent.display_name + '</a>', 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>' : '', 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) : "", time = elapsed ? utils.time_to_string(ent.time||elapsed) : "",
place = utils.place_string(ent.place), place = utils.place_string(ent.place),

View file

@ -31,71 +31,75 @@ FFZ.prototype._update_subscribers = function() {
// context of the web user. // context of the web user.
// Get the count! // Get the count!
jQuery.ajax({url: "/broadcast/dashboard/partnership"}).done(function(data) { jQuery.getJSON("/" + id + "/dashboard/revenue/summary_data").done(function(data) {
try { var el, sub_count = data && data.data && data.data.total_subscriptions;
var html = document.createElement('span'), dash; if ( typeof sub_count === "string" )
sub_count = parseInt(sub_count.replace(/[,\.]/g, ""));
html.innerHTML = data; if ( typeof sub_count !== "number" || sub_count === 0 || sub_count === NaN || sub_count === Infinity ) {
dash = html.querySelector("#dash_main"); el = document.querySelector("#ffz-sub-display");
if ( el )
el.parentElement.removeChild(el);
var match = dash && dash.textContent.match(/([\d,\.]+) total active subscribers/), var failed = f._failed_sub_checks = (f._failed_sub_checks || 0) + 1;
sub_count = match && match[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;
}
if ( ! sub_count ) { return;
var el = document.querySelector("#ffz-sub-display"); }
if ( el )
el.parentElement.removeChild(el);
if ( f._update_subscribers_timer ) { // Graph this glorious data point
clearTimeout(f._update_subscribers_timer); if ( f._dash_chart ) {
delete f._update_subscribers_timer; if ( ! f._dash_chart.series[3].options.showInLegend ) {
} f._dash_chart.series[3].options.showInLegend = true;
f._dash_chart.legend.renderLegend();
}
return; f._dash_chart.series[3].addPoint({x: utils.last_minute(), y: sub_count});
} }
var el = document.querySelector('#ffz-sub-display span'); el = document.querySelector('#ffz-sub-display span');
if ( ! el ) { if ( ! el ) {
var cont = f.is_dashboard ? document.querySelector("#stats") : document.querySelector("#channel .stats-and-actions .channel-stats"); var cont = f.is_dashboard ? document.querySelector("#stats") : document.querySelector("#channel .stats-and-actions .channel-stats");
if ( ! cont ) if ( ! cont )
return; return;
var stat = document.createElement('span'); var stat = document.createElement('span');
stat.className = 'ffz stat'; stat.className = 'ffz stat';
stat.id = 'ffz-sub-display'; stat.id = 'ffz-sub-display';
stat.title = 'Active Channel Subscribers'; stat.title = 'Subscribers';
stat.innerHTML = constants.STAR + ' '; stat.innerHTML = constants.STAR + ' ';
el = document.createElement('span'); el = document.createElement('span');
stat.appendChild(el); stat.appendChild(el);
Twitch.api.get("chat/" + id + "/badges", null, {version: 3}) utils.api.get("chat/" + id + "/badges", null, {version: 3})
.done(function(data) { .done(function(data) {
if ( data.subscriber && data.subscriber.image ) { if ( data.subscriber && data.subscriber.image ) {
stat.innerHTML = ''; stat.innerHTML = '';
stat.appendChild(el); stat.appendChild(el);
stat.style.backgroundImage = 'url("' + data.subscriber.image + '")'; stat.style.backgroundImage = 'url("' + data.subscriber.image + '")';
stat.style.backgroundRepeat = 'no-repeat'; stat.style.backgroundRepeat = 'no-repeat';
stat.style.paddingLeft = '23px'; stat.style.paddingLeft = '23px';
stat.style.backgroundPosition = '0 50%'; stat.style.backgroundPosition = '0 50%';
} }
}); });
cont.appendChild(stat); cont.appendChild(stat);
jQuery(stat).tipsy({gravity: f.is_dashboard ? "s" : utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); 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(){ }).fail(function(){
var el = document.querySelector("#ffz-sub-display"); var el = document.querySelector("#ffz-sub-display");
if ( el ) if ( el )
el.parentElement.removeChild(el); el.parentElement.removeChild(el);
return; 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) { uncompressEmotes = function(value) {
var output = {}, var output = {},
emotes = value.split("/"), 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_CODEPOINTS = {},
emoji_to_codepoint = function(icon, variant) { emoji_to_codepoint = function(surrogates, sep) {
if ( EMOJI_CODEPOINTS[icon] && EMOJI_CODEPOINTS[icon][variant] ) if ( EMOJI_CODEPOINTS[surrogates] && EMOJI_CODEPOINTS[surrogates][sep] )
return EMOJI_CODEPOINTS[icon][variant]; 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), var input = surrogates.indexOf(U200D) === -1 ? surrogates.replace(UFE0Fg, '') : surrogates,
r = [], c = 0, p = 0, i = 0; out = [],
c = 0, p = 0, i = 0;
while ( i < ico.length ) { while (i < input.length) {
c = ico.charCodeAt(i++); c = input.charCodeAt(i++);
if ( p ) { if ( p ) {
r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16)); out.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
p = 0; p = 0;
} else if ( 0xD800 <= c && c <= 0xDBFF) { } else if ( 0xD800 <= c && c <= 0xDBFF )
p = c; p = c;
} else { else
r.push(c.toString(16)); out.push(c.toString(16));
} }
}
var es = EMOJI_CODEPOINTS[icon] = EMOJI_CODEPOINTS[icon] || {}, var retval = EMOJI_CODEPOINTS[surrogates] = out.join('-');
out = es[variant] = r.join("-"); return retval;
return out;
}, },
// 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 = {}, SRCSETS = {},
build_srcset = function(id) { build_srcset = function(id) {
if ( SRCSETS[id] ) if ( SRCSETS[id] )
return 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; return out;
}, },
data_to_tooltip = function(data) { // Twitch API
var emote_set = data.set,
set_type = data.set_type,
f = FFZ.get(), api_call = function(method, url, data, options, token) {
image = ''; options = options || {};
var headers = options.headers = options.headers || {};
if ( data.id && f.settings.emote_image_hover ) headers['Client-ID'] = constants.CLIENT_ID;
image = '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + data.id + '/3.0?_=preview">'; if ( token )
headers.Authorization = 'OAuth ' + token;
if ( set_type === undefined ) return Twitch.api[method].call(this, url, data, options);
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;
},
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
}
}
}
if ( ! emote_data )
return "???";
if ( typeof emote_data == "string" )
return emote_data;
if ( ! force_update && emote_data.tooltip )
return emote_data.tooltip;
return emote_data.tooltip = data_to_tooltip(emote_data);
},
load_emote_data = function(id, code, success, data) {
if ( ! success )
return code;
if ( code )
data.code = code;
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;
};
module.exports = { // 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'),
closer = function() { container.parentElement.removeChild(container) };
container.className = 'twitch_subwindow_container';
container.id = 'ffz-modal-container';
subwindow.className = 'twitch_subwindow ffz-subwindow';
subwindow.style.width = '100%';
subwindow.style.maxWidth = (width||420) + 'px';
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;
},
ember_lookup = function(thing) {
if ( ! window.App )
return;
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 = 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_srcset: build_srcset,
build_tooltip: build_tooltip, /*build_tooltip: build_tooltip,
load_emote_data: load_emote_data, 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) { update_css: function(element, id, css) {
var all = element.innerHTML, var all = element.innerHTML,
@ -338,7 +334,11 @@ module.exports = {
tooltip_placement: function(margin, prefer) { tooltip_placement: function(margin, prefer) {
return function() { 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), $this = $(this),
half_width = $this.width() / 2, half_width = $this.width() / 2,
half_height = $this.height() / 2, half_height = $this.height() / 2,
@ -354,12 +354,10 @@ module.exports = {
} }
}, },
splitIRCMessage: splitIRCMessage,
parseIRCTags: parseIRCTags,
uncompressEmotes: uncompressEmotes, uncompressEmotes: uncompressEmotes,
emoji_to_codepoint: emoji_to_codepoint, emoji_to_codepoint: emoji_to_codepoint,
codepoint_to_emoji: codepoint_to_emoji,
parse_date: parse_date, parse_date: parse_date,
@ -418,7 +416,7 @@ module.exports = {
var seconds = elapsed % 60, var seconds = elapsed % 60,
minutes = Math.floor(elapsed / 60), minutes = Math.floor(elapsed / 60),
hours = Math.floor(minutes / 60), hours = Math.floor(minutes / 60),
days = ""; days = null;
minutes = minutes % 60; minutes = minutes % 60;
@ -431,7 +429,7 @@ module.exports = {
days = ( days > 0 ) ? days + " days, " : ""; 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) { duration_string: function(val) {

390
style.css
View file

@ -19,6 +19,7 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
cursor: pointer; cursor: pointer;
} }
.ffz-hide-recommended-channels .js-recommended-channels,
.ffz-hide-recent-past-broadcast .recent-past-broadcast, .ffz-hide-recent-past-broadcast .recent-past-broadcast,
.ffz-hide-view-count .stat.twitch-channel-views, .ffz-hide-view-count .stat.twitch-channel-views,
.ffz-minimal-chat-input .chat-interface .emoticon-selector-toggle, .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 { .ffz-button {
float: right; float: right;
@ -216,8 +217,8 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
color: #aaa; color: #aaa;
position: absolute; position: absolute;
top: -25px; top: -20px;
left: 120px; left: 10px;
right: 10px; right: 10px;
z-index: 7; z-index: 7;
opacity: 0.95; opacity: 0.95;
@ -525,6 +526,31 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic
margin: 0; 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; } .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, .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-sub-menu-page,
.ffz-ui-menu-page { overflow-y: auto; } .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 .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, .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, .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 { .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid:first-of-type .heading {
border-top: none; border-top: none;
padding-top: 0; padding-top: 0;
background-position-y: 0; background-position: 20px 0;
} }
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content { .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; list-style-type: none;
border-top: 1px solid rgba(0,0,0,0.2); border-top: 1px solid rgba(0,0,0,0.2);
background-color: #eee; background-color: #eee;
margin: 0 1px 1px;
} }
.ffz-ui-popup ul.menu:not(.sub-menu) { .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; 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, .app-main.theatre .ffz-ui-popup ul.sub-menu,
.chat-container.dark .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); 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 { .ffz-ui-popup ul.menu li.active {
background-color: #fff; background-color: #fff;
} }
@ -898,12 +954,43 @@ span.ffz-handle:after { left: 8px }
/* BTTV Menu Fixes */ /* BTTV Menu Fixes */
.bttv-incompatibility {
padding-top: 8px !important;
}
.ffz-ui-popup.dark .emoticon-grid .heading, .ffz-ui-popup.dark .emoticon-grid .heading,
.ffz-ui-popup.dark li.title { color: #fff; } .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 */ /* Host Menu */
.ffz-subwindow input {
padding: 5px;
}
.ffz-subwindow p {
margin-bottom: 0;
padding: 0 0 10px;
}
.ffz-subwindow .card,
.ffz-channel-selector { .ffz-channel-selector {
background-image: url("//cdn.frankerfacez.com/script/zreknarf-bg.png"); background-image: url("//cdn.frankerfacez.com/script/zreknarf-bg.png");
background-repeat: no-repeat; 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 */ /* Positioning Fixes */
body:not(.ffz-bttv) .ember-chat .chat-settings { bottom: 40px; } 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; } 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 */ /* Menu Scrollbar */
.conversations-list .scroll-container::-webkit-scrollbar,
.chatters-container::-webkit-scrollbar, .chatters-container::-webkit-scrollbar,
.ffz-scrollbar::-webkit-scrollbar, .ffz-scrollbar::-webkit-scrollbar,
.ember-chat .chat-settings::-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, .emoticon-selector-box .all-emotes::-webkit-scrollbar,
.ffz-ui-sub-menu-page::-webkit-scrollbar, .ffz-ui-sub-menu-page::-webkit-scrollbar,
.ffz-ui-menu-page::-webkit-scrollbar { .ffz-ui-menu-page::-webkit-scrollbar {
height: 6px;
width: 6px; width: 6px;
} }
.conversations-list .scroll-container::-webkit-scrollbar-thumb,
.chatters-container::-webkit-scrollbar-thumb, .chatters-container::-webkit-scrollbar-thumb,
.ffz-scrollbar::-webkit-scrollbar-thumb, .ffz-scrollbar::-webkit-scrollbar-thumb,
.ember-chat .chat-settings::-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); 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 .ffz-scrollbar::-webkit-scrollbar-thumb,
.ffz-dark .table::-webkit-scrollbar-thumb, .ffz-dark .table::-webkit-scrollbar-thumb,
.ffz-dark .conversation-window .conversation-content::-webkit-scrollbar-thumb, .ffz-dark .conversation-window .conversation-content::-webkit-scrollbar-thumb,
.ffz-dark .conversations-list .conversations-list-inner::-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, .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 .chatters-container::-webkit-scrollbar-thumb,
.theatre .ffz-scrollbar::-webkit-scrollbar-thumb, .theatre .ffz-scrollbar::-webkit-scrollbar-thumb,
.theatre .ember-chat .chat-settings::-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 */ /* Fix Moderation Cards */
img.channel_background[src=""],
img.channel_background[src="null"] { display: none; } img.channel_background[src="null"] { display: none; }
.ffz-moderation-card { .ffz-moderation-card {
@ -1034,7 +1177,11 @@ img.channel_background[src="null"] { display: none; }
max-width: 340px; 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; padding-top: 0;
} }
@ -1043,7 +1190,9 @@ img.channel_background[src="null"] { display: none; }
} }
.ffz-moderation-card.ffz-has-info h3.name { .ffz-moderation-card.ffz-has-info h3.name {
margin-top: 0; margin-top: -2px;
margin-bottom: 0;
padding-top: 0;
white-space: nowrap; white-space: nowrap;
} }
@ -1054,6 +1203,11 @@ img.channel_background[src="null"] { display: none; }
margin-left: 50px; margin-left: 50px;
height: 18px; height: 18px;
line-height: 18px; line-height: 18px;
pointer-events: none;
}
.ffz-moderation-card .info .stat {
pointer-events: auto;
} }
.ffz-moderation-card .info.channel-stats .stat { .ffz-moderation-card .info.channel-stats .stat {
@ -1108,7 +1262,7 @@ img.channel_background[src="null"] { display: none; }
text-shadow: black 0 0 5px; text-shadow: black 0 0 5px;
} }
.ffz-moderation-card .channel_background { .ember-chat .ffz-moderation-card .channel_background {
width: 100%; width: 100%;
top: 0; top: 0;
} }
@ -1156,13 +1310,14 @@ img.channel_background[src="null"] { display: none; }
text-decoration: none; text-decoration: none;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
margin-top: -1px !important;
color: #888 !important; color: #888 !important;
} }
/* Chat Rows */ /* Chat Rows */
.ffz-alias { font-style: italic; } .ffz-alias-italics .ffz-alias { font-style: italic; }
.ember-chat .chat-messages .chat-line.ffz-has-deleted { .ember-chat .chat-messages .chat-line.ffz-has-deleted {
line-height: 30px; line-height: 30px;
@ -1184,7 +1339,8 @@ img.channel_background[src="null"] { display: none; }
text-decoration: 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. */ /* This looks better when it's full width. */
margin: 0 -20px; margin: 0 -20px;
} }
@ -1199,6 +1355,10 @@ body:not(.ffz-bttv) .more-messages-indicator {
/* Emoticon Tooltips */ /* Emoticon Tooltips */
.ffz-wide-tip hr {
margin: 5px 0;
}
.ffz-wide-tip .tipsy-inner { .ffz-wide-tip .tipsy-inner {
min-width: 300px; min-width: 300px;
max-width: 600px; max-width: 600px;
@ -1310,6 +1470,8 @@ body:not(.ffz-bttv) .more-messages-indicator {
/* Dumb Fixes */ /* Dumb Fixes */
.channel-stats .stat { vertical-align: top; }
.ffz-bttv .no-bttv { display: none; } .ffz-bttv .no-bttv { display: none; }
.chat-container.dark, .app-main.theatre .chat-container, .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 */ /* Unsafe Links */
a.unsafe-link { a.unsafe-link {
@ -1641,7 +1812,11 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
.ember-chat .chat-interface .more-messages-indicator.ffz-freeze-indicator { .ember-chat .chat-interface .more-messages-indicator.ffz-freeze-indicator {
opacity: 1; opacity: 1;
cursor: default; cursor: default;
top: 0; pointer-events: none;
}
.chat-container:not(.chatReplay) .chat-interface .more-messages-indicator.ffz-freeze-indicator {
top: 0;
} }
/* Chat History */ /* Chat History */
@ -1669,6 +1844,9 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
margin-right: 0px; 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 .chat-line.admin .message { color: #666; }
.chat-history .timestamp { .chat-history .timestamp {
@ -1878,13 +2056,21 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
/* No Blue */ /* No Blue */
.ffz-no-blue .theatre .conversations-list-icon, .ffz-no-blue #carousel .nav {
.ffz-no-blue.ffz-dark .conversations-list-icon, 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 .theatre .conversations-list,
.ffz-no-blue.ffz-dark .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 .theatre .conversation-window,
.ffz-no-blue.ffz-dark .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 #large_nav .content,
.ffz-no-blue #small_nav .content, .ffz-no-blue #small_nav .content,
.ffz-no-blue .chat-container.dark, .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 .takeover #carousel,
.ffz-no-blue #carousel_and_background, .ffz-no-blue #carousel_and_background,
.ffz-no-blue #carousel .items .pic img, .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 { .ffz-no-blue #content .turbo_landing {
background-color: #191919; 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 .theatre .moderation-card .back-button,
.ffz-no-blue .dark .moderation-card .back-button, .ffz-no-blue .dark .moderation-card .back-button,
.ffz-no-blue .force-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; background-color: #232323;
} }
.ffz-no-blue .theatre .conversations-list-icon, .ffz-no-blue .theatre .conversations-list-bottom-bar,
.ffz-no-blue.ffz-dark .conversations-list-icon, .ffz-no-blue.ffz-dark .conversations-list-bottom-bar,
.ffz-no-blue .theatre .conversations-list .conversation-preview-line, .ffz-no-blue .theatre .conversations-list .conversation-preview-line,
.ffz-no-blue.ffz-dark .conversations-list .conversation-preview-line, .ffz-no-blue.ffz-dark .conversations-list .conversation-preview-line,
.ffz-no-blue .theatre .conversation-window, .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 .theatre .conversation-header,
.ffz-no-blue.ffz-dark .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.ffz-dark .conversations-list .conversations-list-header,
.ffz-no-blue .theatre .conversation-window.has-focus .conversation-header, .ffz-no-blue .theatre .conversation-window.has-focus .conversation-header,
.ffz-no-blue.ffz-dark .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; padding: 2px 5px;
} }
#large_nav .game_filter.selected .ffz-follow-count { right: 13px; }
#small_nav .ffz-follow-count { #small_nav .ffz-follow-count {
position: absolute; position: absolute;
bottom: 2px; bottom: 2px;
right: 2px; right: 5px;
padding: 0 2px; padding: 2px;
font-size: 10px; font-size: 10px;
line-height: 10px;
background-color: #191919; background-color: #191919;
color: rgba(255,255,255,0.5); color: rgba(255,255,255,0.5);
} }
@ -2053,8 +2247,6 @@ li[data-name="following"] a {
background-color: #101014; background-color: #101014;
} }
#small_nav .game_filter.selected .ffz-follow-count { right: 5px; }
/* Image Tooltips */ /* Image Tooltips */
.ffz-image-hover { .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: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; padding-bottom: 0;
} }
@ -2174,6 +2368,13 @@ li[data-name="following"] a {
/* Directory Logos */ /* 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 .meta p { width: auto; }
.ffz-directory-logo .profile-photo { .ffz-directory-logo .profile-photo {
@ -2240,11 +2441,16 @@ body:not(.ffz-bttv) .conversation-window .new-message-divider + .timestamp-line
margin-top: -3px; margin-top: -3px;
} }
/* Fix the ignore-cta covering up messages with no way to dismiss it. */ /* ignore-cta is a bit silly right now */
body:not(.ffz-bttv) .conversation-window .ignore-cta + .conversation-content { body:not(.ffz-bttv) .conversation-window .ignore-cta:not(.hidden) + .conversation-content {
padding-top: 76px; padding-top: 76px;
} }
.conversation-window .conversation-system-message.ignore-cta-container {
padding-right: 26px;
}
/* Hide that which should be hidden. */ /* Hide that which should be hidden. */
.conversation-window.collapsed .ignore-cta, .conversation-window.collapsed .ignore-cta,
.conversation-chat-line.action .colon, .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; } .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 { /* Top Conversations */
border-bottom: none !important;
}
.ffz-top-conversations:not(.ffz-bttv) .conversations-content .conversations-list-icon, body:not(.ffz-top-conversations) .conversations-list-bottom-bar {
.ffz-top-conversations:not(.ffz-bttv) .conversation-window { border-bottom: none;
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);
} }
.ffz-top-conversations .conversations-content { .ffz-top-conversations .conversations-content {
@ -2287,47 +2479,25 @@ body:not(.ffz-conv-title-clickable) .conversation-header a.conversation-header-n
top: 0px; top: 0px;
} }
.ffz-top-conversations .conversations-list { .ffz-top-conversations .conversation-window.collapsed .conversation-header {
bottom: inherit; box-shadow: none;
top: 54px;
} }
.ffz-top-conversations.ffz-bttv .conversations-list { top: 46px; } .ffz-top-conversations .conversations-list-container.list-displayed {
top: 243px;
.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:not(.ffz-bttv) .conversations-list:before { .ffz-top-conversations .conversations-list-bottom-bar {
border-top-color: transparent; border-top: none;
border-bottom-color: #dedede;
} }
.ffz-top-conversations:not(.ffz-bttv) .conversations-list:after { .ffz-top-conversations .conversations-content .total-unread {
border-top-color: transparent; top: inherit;
border-bottom-color: #f2f2f2; bottom: -10px;
} }
.ffz-top-conversations .theatre .player-controls-bottom,
.ffz-dark.ffz-top-conversations:not(.ffz-bttv) .conversations-list:before, .ffz-top-conversations .theatre .player[data-controls=true] .player-controls-bottom {
.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 {
padding-bottom: 0; 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; } .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 */ /* Following Page */
.directory_header .nav li.ffz-manage-following {
float: right;
margin-right: 0;
}
.user.item { position: relative; } .user.item { position: relative; }
.user.item .overlay_info.length { .user.item .overlay_info.length {
@ -2452,3 +2666,25 @@ body:not(.ffz-creative-showcase) .creative-hero,
color: #fff !important; color: #fff !important;
background-color: #6441a5 !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;
}