1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-23 06:20:54 +00:00

Commiting code in 2016 LOL (See changelog.html I'm a bad person)

This commit is contained in:
SirStendec 2016-07-13 02:06:50 -04:00
parent 8cfef363f1
commit 86b66bb8f5
34 changed files with 2175 additions and 1732 deletions

View file

@ -361,11 +361,11 @@ body.ffz-dark:not([data-page="teams#show"]),
color: #a68ed2;
}
.ffz-dark .button.button--icon-only svg path {
.ffz-dark .button.button--icon-only svg {
fill: #a68ed2;
}
.ffz-dark .button.button--icon-only:hover svg path {
.ffz-dark .button.button--icon-only:hover svg {
fill: #fff;
}
@ -483,11 +483,6 @@ body.ffz-dark:not([data-page="teams#show"]),
fill: rgba(255,255,255,0.5);
}
.ffz-dark .following-col .col-header,
.ffz-dark .following-col .header {
border-color: #32323e;
}
.ffz-dark .following-col .following-list .load-more span,
.ffz-dark .viewall a {
background-color: rgb(25,25,31);
@ -1176,10 +1171,73 @@ body.ffz-dark:not([data-page="teams#show"]),
}
/* Creative UI */
.ffz-dark .ct-spotlight__right {
background-color: #121212;
}
.ffz-dark .ct-spotlight__controls-container {
background-color: #161616;
box-shadow: -1px 0 0 #303030 inset;
}
.ffz-dark .ct-bar {
background-color: #121212;
box-shadow:
0 2px 6px -2px rgba(255,255,255,0.1),
0 1px 0 rgba(255,255,255,0.05),
0 -1px 0 rgba(255,255,255,0.05);
}
.ffz-dark .ct-banner__name,
.ffz-dark .ct-banner__status,
.ffz-dark .ct-type-2,
.ffz-dark .ct-type-4 {
color: #aaa;
}
.ffz-dark .ct-type-grey-light {
color: #ccc;
}
.ffz-dark .ct-crumb:after {
box-shadow: 12px -6px 24px -8px rgba(255,255,255,0.1);
border-color: #303030;
}
.ffz-dark .ct-spotlight__avatar {
box-shadow: 0 0 0 1px #303030, 0 2px 3px #303030;
}
.ffz-dark .ct-spotlight__right--col .ct-spotlight__card-container {
box-shadow: 0 -1px 0 #303030 inset;
}
.ffz-dark hr,
.ffz-dark .ct-spotlight__section,
.ffz-dark .balloon__stroke,
.ffz-dark .ct-spotlight__avatar,
.ffz-dark .ct-bar__item {
border-color: #303030;
}
.ffz-dark .ct-crumb--1:after,
.ffz-dark .ct-crumb--1 .ct-crumb__label {
background-color: #121212;
}
.ffz-dark .ct-crumb--2:after,
.ffz-dark .ct-crumb--2 .ct-crumb__label {
background-color: #161616;
}
/* Creative Tags */
.ffz-dark .ct-tags__tag {
background-color: #191919;
background-color: #121212;
color: #999 !important;
}
@ -1228,6 +1286,7 @@ body.ffz-dark:not([data-page="teams#show"]),
}
.ffz-dark .activity-meta-divider:before,
.ffz-dark .list-load-more,
.ffz-dark .activity-card {
border-color: #474747;
}
@ -1244,6 +1303,20 @@ body.ffz-dark:not([data-page="teams#show"]),
.ffz-dark .activity-meta__name { color: #ccc }
.ffz-dark .activity-card__comments {
background-color: #121212;
box-shadow:inset 0 1px 0 #474747;
}
.ffz-dark .activity-add-comment__textarea:before {
background: #1d1d1d;
border-left-color: #474747;
border-bottom-color: #474747;
}
.inherit-color { color: inherit !important }
/* Search Panel */
.ffz-dark[data-current-path="user.channel.index.index"] .searchPanel { background-color: rgba(16,16,16,0.9) }

View file

@ -221,20 +221,6 @@ gulp.task('server', function() {
fs.exists(file, function(exists) {
if ( ! exists ) {
util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.bold.blue("CDN") + " GET " + util.colors.magenta(uri));
/*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);
}

View file

@ -3,7 +3,11 @@ var FFZ = window.FrankerFaceZ,
utils = require('./utils'),
SPECIAL_BADGES = ['staff', 'admin', 'global_mod'],
OTHER_KNOWN = ['turbo', 'warcraft'],
OTHER_KNOWN = ['turbo', 'warcraft', 'bits'],
NO_INVERT_BADGES = ['subscriber', 'ffz-badge-1'],
INVERT_INVERT_BADGES = ['bits'],
TRANSPARENT_BADGES = ['subscriber'],
BTTV_TYPE_REPLACEMENTS = {
'global-moderator': 'global_mod'
@ -25,14 +29,6 @@ var FFZ = window.FrankerFaceZ,
BADGE_KLASSES = {
'global_mod': 'global-moderator'
},
badge_css = function(badge, klass) {
klass = klass || ('ffz-badge-' + badge.id);
var out = ".badges ." + klass + " { background-color: " + badge.color + '; background-image: url("' + badge.image + '"); ' + (badge.css || "") + '}';
if ( badge.alpha_image )
out += ".badges .badge.alpha." + klass + ",.ffz-transparent-badges .badges ." + klass + ' { background-image: url("' + badge.alpha_image + '"); }';
return out;
};
@ -92,8 +88,13 @@ FFZ.settings_info.hidden_badges = {
}
for(var badge_id in f.badges) {
if ( f.badges.hasOwnProperty(badge_id) && f.badges[badge_id].name )
values.push('<code>ffz-' + f.badges[badge_id].name + '</code>');
if ( ! f.badges.hasOwnProperty(badge_id) )
continue;
var badge = f.badges[badge_id],
hide_key = (badge.source_ext ? f._apis[badge.source_ext].name_key : 'ffz') + '-' + (badge.name || badge.id);
values.push('<code>' + hide_key + '</code>');
}
if ( this.has_bttv && window.BetterTTV ) {
@ -129,11 +130,13 @@ FFZ.settings_info.sub_notice_badges = {
category: "Chat Appearance",
name: "Old-Style Subscriber Notice Badges",
no_bttv: true,
help: "Display a subscriber badge on old-style chat messages about new subscribers.",
on_update: function(val) {
this.toggle_style('badges-sub-notice', ! val);
this.toggle_style('badges-sub-notice-on', val);
this.toggle_style('badges-sub-notice', ! this.has_bttv && ! val);
this.toggle_style('badges-sub-notice-on', ! this.has_bttv && val);
}
};
@ -269,7 +272,7 @@ FFZ.ws_commands.set_badge = function(data) {
badges = user.badges = user.badges || {};
if ( badge === undefined || badge === null )
delete badges[slot];
badges[slot] = null;
else
badges[slot] = badge;
}
@ -287,14 +290,16 @@ FFZ.prototype.get_badges = function(user, room_id, badges, msg) {
return badges;
for(var slot in data.badges) {
if ( ! data.badges.hasOwnProperty(slot) )
var badge = data.badges[slot];
if ( ! data.badges.hasOwnProperty(slot) || ! badge )
continue;
var badge = data.badges[slot],
full_badge = this.badges[badge.id] || {},
old_badge = badges[slot];
var full_badge = this.badges[badge.id] || {},
old_badge = badges[slot],
if ( hidden_badges.indexOf('ffz-' + full_badge.name) !== -1 )
hide_key = (full_badge.source_ext ? this._apis[full_badge.source_ext].name_key : 'ffz') + '-' + (full_badge.name || full_badge.id);
if ( hidden_badges.indexOf(hide_key) !== -1 )
continue;
if ( full_badge.visible !== undefined ) {
@ -319,10 +324,13 @@ FFZ.prototype.get_badges = function(user, room_id, badges, msg) {
badges[slot] = {
klass: 'ffz-badge-' + badge.id,
title: badge.title || full_badge.title,
title: badge.title || full_badge.title || ('Unknown FFZ Badge\nID: ' + badge.id),
image: badge.image,
full_image: full_badge.image,
color: badge.color,
no_invert: badge.no_invert || full_badge.no_invert,
invert_invert: badge.invert_invert || full_badge.invert_invert,
transparent: badge.transparent || full_badge.transparent || (badge.color || full_badge.color) === "transparent",
extra_css: badge.extra_css
};
}
@ -390,12 +398,15 @@ FFZ.prototype.get_line_badges = function(msg) {
badges[last_id] = {
klass: (BADGE_KLASSES[badge] || badge) + (is_known ? '' : ' unknown-badge') + ' version-' + version,
title: binfo && binfo.title || BADGE_NAMES[badge] || badge.capitalize(),
click_url: binfo && binfo.click_action === 'visit_url' && binfo.click_url
click_url: binfo && binfo.click_action === 'visit_url' && binfo.click_url,
no_invert: NO_INVERT_BADGES.indexOf(badge) !== -1,
invert_invert: INVERT_INVERT_BADGES.indexOf(badge) !== -1,
transparent: TRANSPARENT_BADGES.indexOf(badge) !== -1
};
if ( ! is_known && binfo ) {
badges[last_id].image = binfo.image_url_1x;
badges[last_id].srcSet = 'url("' + binfo.image_url_1x + '") 1x, url("' + binfo.image_url_2x + '") 2x, url("' + binfo.image_url_3x + '") 4x';
badges[last_id].srcSet = 'url("' + binfo.image_url_1x + '") 1x, url("' + binfo.image_url_2x + '") 2x, url("' + binfo.image_url_4x + '") 4x';
}
}
@ -413,13 +424,13 @@ FFZ.prototype.get_other_badges = function(user_id, room_id, user_type, has_sub,
for(var i=0, l = SPECIAL_BADGES.length; i < l; i++) {
var mb = SPECIAL_BADGES[i];
if ( user_type === mb ) {
badges[0] = {klass: BADGE_KLASSES[mb] || mb, title: BADGE_TITLES[mb] || mb.capitalize()};
badges[0] = {klass: BADGE_KLASSES[mb] || mb, title: BADGE_NAMES[mb] || mb.capitalize()};
break;
}
}
if ( has_sub )
badges[10] = {klass: 'subscriber', title: 'Subscriber'}
badges[10] = {klass: 'subscriber', title: 'Subscriber', no_invert: true, transparent: true}
if ( has_turbo )
badges[15] = {klass: 'turbo', title: 'Turbo'}
@ -450,7 +461,16 @@ FFZ.prototype.render_badges = function(badges) {
if ( badge.click_url )
klass += ' click_url';
out.push('<div class="badge float-left html-tooltip ' + utils.quote_attr(klass) + '"' + (badge.click_url ? ' data-url="' + utils.quote_attr(badge.click_url) + '"' : '') + (css ? ' style="' + utils.quote_attr(css) + '"' : '') + ' title="' + utils.quote_attr(badge.title) + '"></div>');
if ( badge.no_invert )
klass += ' no-invert';
if ( badge.invert_invert )
klass += ' invert-invert';
if ( badge.transparent )
klass += ' transparent';
out.push('<div class="badge html-tooltip ' + utils.quote_attr(klass) + '"' + (badge.click_url ? ' data-url="' + utils.quote_attr(badge.click_url) + '"' : '') + (css ? ' style="' + utils.quote_attr(css) + '"' : '') + ' title="' + utils.quote_attr(badge.title) + '"></div>');
}
return out.join("");
@ -482,17 +502,27 @@ FFZ.prototype.bttv_badges = function(data) {
for(var i=0; i < data.badges.length; i++) {
var badge = data.badges[i],
space_ind = badge.type.indexOf(' '),
hidden_key = BTTV_TYPE_REPLACEMENTS[badge.type] || (space_ind === -1 ? badge.type : badge.type.substr(0, space_ind));
hidden_key = space_ind !== -1 ? badge.type.substr(0, space_ind) : badge.type;
if ( hidden_key.indexOf('twitch-') === 0 )
hidden_key = hidden_key.substr(7);
if ( BTTV_TYPE_REPLACEMENTS.hasOwnProperty(hidden_key) )
hidden_key = BTTV_TYPE_REPLACEMENTS[hidden_key];
else {
var ind = hidden_key.indexOf('-');
if ( ind !== -1 )
hidden_key = hidden_key.substr(0, ind);
}
if ( hidden_badges.indexOf(hidden_key) !== -1 ) {
data.badges.splice(i, 1);
i--;
continue;
}
if ( badge.type === "subscriber" || badge.type === "turbo" || badge.type.substr(0, 8) === 'warcraft' ) {
if ( insert_at === -1 && (badge.type === "subscriber" || badge.type === "turbo" || badge.type.substr(0, 7) === 'twitch-') )
insert_at = i;
break;
}
}
// If there's no user, we're done now.
@ -502,15 +532,17 @@ FFZ.prototype.bttv_badges = function(data) {
// We have a user. Start replacing badges.
for (var slot in user.badges) {
if ( ! user.badges.hasOwnProperty(slot) )
var badge = user.badges[slot];
if ( ! user.badges.hasOwnProperty(slot) || ! badge )
continue;
var badge = user.badges[slot],
full_badge = this.badges[badge.id] || {},
var full_badge = this.badges[badge.id] || {},
desc = badge.title || full_badge.title,
style = "";
style = "",
if ( hidden_badges.indexOf('ffz-' + full_badge.name) !== -1 )
hide_key = (full_badge.source_ext ? this._apis[full_badge.source_ext].name_key : 'ffz') + '-' + (full_badge.name || full_badge.id);
if ( hidden_badges.indexOf(hide_key) !== -1 )
continue;
if ( full_badge.visible !== undefined ) {
@ -565,8 +597,6 @@ FFZ.prototype.bttv_badges = function(data) {
while(badges_out.length)
data.badges.insertAt(insert_at, badges_out.shift()[1]);
}
}
@ -644,10 +674,13 @@ FFZ.prototype._load_badge_json = function(badge_id, data) {
data.replaces = true;
}
if ( data.name === 'developer' )
data.no_invert = true;
if ( data.name === 'bot' )
data.visible = function(r,user) { return !(this.has_bttv && FFZ.bttv_known_bots.indexOf(user)!==-1); };
utils.update_css(this._badge_style, badge_id, badge_css(data));
utils.update_css(this._badge_style, badge_id, utils.badge_css(data));
}

View file

@ -4,7 +4,6 @@ var SVGPATH = 'm120.95 1.74c4.08-0.09 8.33-0.84 12.21 0.82 3.61 1.8 7 4.16 11.01
SERVER = DEBUG ? "//localhost:8000/" : "https://cdn.frankerfacez.com/",
IS_OSX = navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent),
IS_WIN = navigator.platform ? navigator.platform.indexOf('Win') !== -1 : /Windows/.test(navigator.userAgent),
SEPARATORS = "[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]",
@ -44,6 +43,8 @@ module.exports = FrankerFaceZ.constants = {
SEPARATORS: SEPARATORS,
SPLITTER: SPLITTER,
UUID_TEST: /(?:^| +)([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}) *$/i,
KNOWN_CODES: {
"#-?[\\\\/]": "#-/",
":-?(?:7|L)": ":-7",

112
src/ember/bits.js Normal file
View file

@ -0,0 +1,112 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants');
// --------------------
// Settings
// --------------------
FFZ.settings_info.bits_animated = {
type: "boolean",
value: true,
category: "Chat Appearance",
no_bttv: true,
visible: function() {
var globals = utils.ember_lookup('service:globals'),
user = this.get_user();
return (globals && globals.get('isBitsEnabled')) || (user && user.is_staff);
},
name: "Bits Animation",
help: "Display bits with animation.",
on_update: utils.toggle_cls('ffz-animate-bits')
}
// --------------------
// Initialization
// --------------------
FFZ.prototype.setup_bits = function() {
utils.toggle_cls('ffz-animate-bits')(this.settings.bits_animated);
var f = this,
Service = utils.ember_lookup('service:bits-rendering-config');
if ( ! Service )
return this.error("Unable to locate the Ember service:bits-rendering-config");
Service.reopen({
ffz_get_tier: function(amount) {
var config = this.get('config'),
tiers = config.tiers || [],
tier = null,
index = null;
for(var i=0, l = tiers.length; i < l; i++) {
var t = tiers[i];
if ( amount < t.min_bits )
break;
tier = t;
index = i;
}
return [index, tier];
},
ffz_get_preview: function(tier) {
return this._templateUrlConstructor(tier.image, "dark", (f.settings.bits_animated ? 'animated' : 'static'), 4);
},
_ffz_tier_css: function(ind, tier) {
var selector = '.ffz-bit.bit-tier-' + ind,
color = f._handle_color(tier.color),
template = 'url("' + this.get('config.templateUrl').replace('{background}', 'light').replace('{image}', tier.image) + '")',
template_srcset = template.replace('{scale}', 1) + ' 1x, ' + template.replace('{scale}', 2) + ' 2x, ' + template.replace('{scale}', 4) + ' 4x',
output;
output = selector + '{' +
'color: ' + color[0] + ';' +
'background-image: ' + template.replace('{scale}', 1).replace(/{state}/g, 'static') + ';' +
'background-image: -webkit-image-set(' + template_srcset.replace(/{state}/g, 'static') + ');' +
'}.ffz-animate-bits ' + selector + '{' +
'background-image: ' + template.replace('{scale}', 1).replace(/{state}/g, 'animated') + ';' +
'background-image: -webkit-image-set(' + template_srcset.replace(/{state}/g, 'animated') + ');' +
'}';
template = template.replace('/light/', '/dark/');
template_srcset = template_srcset.replace(/\/light\//g, '/dark/');
return output + '.tipsy ' + selector + ',.dark ' + selector + ',.force-dark ' + selector + ',.theatre ' + selector + '{' +
'color: ' + color[1] + ';' +
'background-image: ' + template.replace('{scale}', 1).replace(/{state}/g, 'static') + ';' +
'background-image: -webkit-image-set(' + template_srcset.replace(/{state}/g, 'static') + ');' +
'}.ffz-animate-bits .tipsy ' + selector + ',.ffz-animate-bits .dark ' + selector + ',.ffz-animate-bits .force-dark ' + selector + ',.ffz-animate-bits .theatre ' + selector + '{' +
'background-image: ' + template.replace('{scale}', 1).replace(/{state}/g, 'animated') + ';' +
'background-image: -webkit-image-set(' + template_srcset.replace(/{state}/g, 'animated') + ');' +
'}';
},
ffz_update_css: function() {
var tiers = this.get('config.tiers') || [],
output = [];
for(var i=0, l = tiers.length; i < l; i++)
output.push(this._ffz_tier_css(i, tiers[i]));
utils.update_css(f._chat_style, 'bit-styles', output.join(''));
}.observes('config')
});
if ( ! Service.get('isLoaded') )
Service.loadRenderConfig();
else
Service.ffz_update_css();
}

View file

@ -19,55 +19,24 @@ FFZ.prototype.setup_channel = function() {
document.body.classList.toggle('ffz-theater-stats', this.settings.theater_stats);
this.log("Hooking the Ember Channel Index view.");
var Channel = utils.ember_resolve('view:channel/index'),
f = this;
if ( ! Channel )
if ( ! this.update_views('view:channel/index', this.modify_channel_index) )
return;
this._modify_cindex(Channel);
// The Stupid View Fix. Is this necessary still?
try {
Channel.create().destroy();
} catch(err) { }
// Update Existing
var views = utils.ember_views();
for(var key in views) {
var view = views[key];
if ( view instanceof Channel ) {
this.log("Manually updating existing Channel Index view.", view);
try {
if ( ! view.ffzInit )
this._modify_cindex(view);
view.ffzInit();
} catch(err) {
this.error("setup: view:channel/index: " + err);
}
}
};
this.log("Hooking the Ember Channel model.");
Channel = utils.ember_resolve('model:channel');
var f = this,
Channel = utils.ember_resolve('model:deprecated-channel');
if ( ! Channel )
return;
return this.log("Unable to find the Ember model:deprecated-channel");
Channel.reopen({
ffz_host_target: undefined,
this._modify_cmodel(Channel);
setHostMode: function(e) {
if ( f.settings.hosted_channels ) {
this.set('ffz_host_target', e.target);
return this._super(e);
} else {
this.set('ffz_host_target', undefined);
return this._super({target: void 0, delay: 0});
}
}
});
var Store = utils.ember_lookup('service:store'),
type_map = Store && Store.typeMapFor(Channel);
if ( type_map && type_map.records )
for(var i=0; i < type_map.records.length; i++)
this._modify_cmodel(type_map.records[i]);
this.log("Hooking the Ember Channel controller.");
@ -89,7 +58,7 @@ FFZ.prototype.setup_channel = function() {
if ( ! this.get('content.id') )
return;
this._ffz_update_timer = setTimeout(this.ffzCheckUpdate.bind(this), 60000);
this._ffz_update_timer = setTimeout(this.ffzCheckUpdate.bind(this), 55000 + (Math.random() * 10000));
}.observes("content.id"),
ffzCheckUpdate: function() {
@ -130,7 +99,6 @@ FFZ.prototype.setup_channel = function() {
});
},
ffzUpdateTitle: function() {
var name = this.get('content.name'),
display_name = this.get('content.display_name');
@ -178,29 +146,28 @@ FFZ.prototype.setup_channel = function() {
}
FFZ.prototype._modify_cindex = function(view) {
FFZ.prototype._modify_cmodel = function(model) {
var f = this;
model.reopen({
ffz_host_target: undefined,
view.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("CIndex didInsertElement: " + err);
setHostMode: function(e) {
if ( f.settings.hosted_channels ) {
this.set('ffz_host_target', e.target);
return this._super(e);
} else {
this.set('ffz_host_target', undefined);
return this._super({target: void 0, delay: 0});
}
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("CIndex willClearRender: " + err);
}
return this._super();
},
});
}
ffzInit: function() {
FFZ.prototype.modify_channel_index = function(view) {
var f = this;
utils.ember_reopen_view(view, {
ffz_init: function() {
var id = this.get('controller.content.id') || this.get('controller.id'),
el = this.get('element');
@ -216,6 +183,10 @@ FFZ.prototype._modify_cindex = function(view) {
this.ffzUpdateHostButton();
this.ffzUpdatePlayerStats();
// Listen to scrolling.
this._ffz_scroller = this.ffzOnScroll.bind(this);
jQuery(el).parents('.tse-scroll-content').on('scroll', this._ffz_scroller);
var views = this.get('element').querySelector('.svg-glyph_views:not(.ffz-svg)')
if ( views )
views.parentNode.classList.add('twitch-channel-views');
@ -242,6 +213,41 @@ FFZ.prototype._modify_cindex = function(view) {
});
},
ffz_destroy: function() {
var id = this.get('controller.content.id') || this.get('controller.id');
if ( id )
f.ws_send("unsub", "channel." + id);
this.get('element').setAttribute('data-channel', '');
f._cindex = undefined;
if ( this._ffz_update_uptime )
clearTimeout(this._ffz_update_uptime);
if ( this._ffz_update_stats )
clearTimeout(this._ffz_update_stats);
if ( this._ffz_scroller ) {
jQuery(this.get('element')).parents('.tse-scroll-content').off('scroll', this._ffz_scroller);
this._ffz_scroller = null;
}
document.body.classList.remove('ffz-small-player');
utils.update_css(f._channel_style, id, null);
},
ffzOnScroll: function(event) {
// When we scroll past the bottom of the player, do stuff!
var top = event && event.target && event.target.scrollTop,
height = this.get('layout.playerSize.1');
if ( ! top )
top = jQuery(this.get('element')).parents('.tse-scroll-content').scrollTop();
document.body.classList.toggle('ffz-small-player', f.settings.small_player && top >= height);
},
ffzFixTitle: function() {
if ( f.has_bttv || ! f.settings.stream_title )
return;
@ -463,6 +469,15 @@ FFZ.prototype._modify_cindex = function(view) {
ffzUpdatePlayerStats: function() {
if ( this._ffz_update_stats ) {
clearTimeout(this._ffz_update_stats);
this._ffz_update_stats = null;
}
// Schedule an update.
if ( f.settings.player_stats )
this._ffz_update_stats = setTimeout(this.ffzUpdatePlayerStats.bind(this), 1000);
var channel_id = this.get('controller.content.id') || this.get('controller.id'),
hosted_id = this.get('controller.hostModeTarget.id'),
@ -478,12 +493,12 @@ FFZ.prototype._modify_cindex = function(view) {
try {
player = player_cont && player_cont.get && player_cont.get('player');
stats = player && player.stats;
stats = player && player.getVideoInfo();
} catch(err) {
f.error("Channel ffzUpdatePlayerStats: player.stats: " + err);
f.error("Channel ffzUpdatePlayerStats: player.getVideoInfo: " + err);
}
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hlsLatencyBroadcaster || stats.hlsLatencyBroadcaster === 'NaN' || Number.isNaN(stats.hlsLatencyBroadcaster) ) {
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hls_latency_broadcaster || Number.isNaN(stats.hls_latency_broadcaster) ) {
if ( stat_el )
stat_el.parentElement.removeChild(stat_el);
} else {
@ -505,21 +520,20 @@ FFZ.prototype._modify_cindex = function(view) {
jQuery(stat_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
var delay = parseFloat(stats.hlsLatencyBroadcaster);
var delay = Math.round(stats.hls_latency_broadcaster / 10) / 100,
bitrate = Math.round(stats.current_bitrate * 1000) / 1000;
if ( delay > 180 ) {
delay = Math.floor(delay);
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps')
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps')
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else {
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps');
delay = stats.hlsLatencyBroadcaster;
var pos = delay.lastIndexOf('.');
if ( pos === -1 )
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + stats.current_bitrate + ' Kbps');
delay = delay.toString();
var ind = delay.indexOf('.');
if ( ind === -1 )
delay = delay + '.00';
else if ( delay.length - pos < 3 )
else if ( ind >= delay.length - 2 )
delay = delay + '0';
el.textContent = delay + 's';
@ -544,7 +558,7 @@ FFZ.prototype._modify_cindex = function(view) {
}
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hlsLatencyBroadcaster || stats.hlsLatencyBroadcaster === 'NaN' || Number.isNaN(stats.hlsLatencyBroadcaster) ) {
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hls_latency_broadcaster || Number.isNaN(stats.hls_latency_broadcaster) ) {
if ( stat_el )
stat_el.parentElement.removeChild(stat_el);
} else {
@ -566,21 +580,20 @@ FFZ.prototype._modify_cindex = function(view) {
jQuery(stat_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
var delay = parseFloat(stats.hlsLatencyBroadcaster);
var delay = Math.round(stats.hls_latency_broadcaster / 10) / 100,
bitrate = Math.round(stats.current_bitrate * 1000) / 1000;
if ( delay > 180 ) {
delay = Math.floor(delay);
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps')
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps')
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else {
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps');
delay = stats.hlsLatencyBroadcaster;
var pos = delay.lastIndexOf('.');
if ( pos === -1 )
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + stats.current_bitrate + ' Kbps');
delay = delay.toString();
var ind = delay.indexOf('.');
if ( ind === -1 )
delay = delay + '.00';
else if ( delay.length - pos < 3 )
else if ( ind >= delay.length - 2 )
delay = delay + '0';
el.textContent = delay + 's';
@ -651,19 +664,6 @@ FFZ.prototype._modify_cindex = function(view) {
}
el.innerHTML = utils.time_to_string(uptime, false, false, false, f.settings.stream_uptime === 1 || f.settings.stream_uptime === 3);
},
ffzTeardown: function() {
var id = this.get('controller.content.id') || this.get('controller.id');
if ( id )
f.ws_send("unsub", "channel." + id);
this.get('element').setAttribute('data-channel', '');
f._cindex = undefined;
if ( this._ffz_update_uptime )
clearTimeout(this._ffz_update_uptime);
utils.update_css(f._channel_style, id, null);
}
});
}
@ -686,6 +686,28 @@ FFZ.settings_info.auto_theater = {
};
FFZ.settings_info.small_player = {
type: "boolean",
value: false,
no_mobile: true,
no_bttv: true,
category: "Appearance",
name: "Mini-Player on Scroll",
help: "When you scroll down on the page, shrink the player and put it in the upper right corner so you can still watch.",
on_update: function(val) {
if ( ! val )
return document.body.classList.remove('ffz-small-player');
else if ( this._vodc )
this._vodc.ffzOnScroll();
else if ( this._cindex )
this._cindex.ffzOnScroll();
}
}
FFZ.settings_info.chatter_count = {
type: "boolean",
value: false,

View file

@ -210,35 +210,13 @@ FFZ.settings_info.input_emoji = {
FFZ.prototype.setup_chat_input = function() {
this.log("Hooking the Ember Chat Input component.");
var Input = utils.ember_resolve('component:chat/twitch-chat-input'),
f = this;
if ( ! Input )
return this.log("Unable to get Chat Input component.");
this._modify_chat_input(Input);
try { Input.create().destroy()
} catch(err) { }
var views = utils.ember_views();
for(var key in views) {
var v = views[key];
if ( v instanceof Input ) {
this.log("Manually modifying Chat Input component.", v);
if ( ! v.ffzInit )
this._modify_chat_input(v);
v.ffzInit();
}
}
this.update_views("component:chat/twitch-chat-input", this.modify_chat_input);
}
FFZ.prototype._modify_chat_input = function(component) {
FFZ.prototype.modify_chat_input = function(component) {
var f = this;
component.reopen({
utils.ember_reopen_view(component, {
ffz_mru_index: -1,
ffz_current_suggestion: 0,
ffz_partial_word: '',
@ -249,22 +227,7 @@ FFZ.prototype._modify_chat_input = function(component) {
ffz_name_suggestions: [],
ffz_chatters: [],
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) { f.error("ChatInput didInsertElement: " + err); }
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) { f.error("ChatInput willClearRender: " + err); }
return this._super();
},
ffzInit: function() {
ffz_init: function() {
f._inputv = this;
var s = this._ffz_minimal_style = document.createElement('style');
@ -290,7 +253,7 @@ FFZ.prototype._modify_chat_input = function(component) {
setTimeout(this.ffzResizeInput.bind(this), 500);
},
ffzTeardown: function() {
ffz_destroy: function() {
if ( f._inputv === this )
f._inputv = undefined;
@ -322,11 +285,10 @@ FFZ.prototype._modify_chat_input = function(component) {
return null;
var t = this,
el = document.createElement('div'),
inner = document.createElement('div'),
el = utils.createElement('div', 'suggestion'),
inner = utils.createElement('div'),
width = item.width ? (246 - item.width) + 'px' : null;
el.className = 'suggestion';
el.setAttribute('data-id', i);
el.classList.toggle('ffz-is-favorite', item.favorite || false);
@ -342,7 +304,7 @@ FFZ.prototype._modify_chat_input = function(component) {
el.appendChild(inner);
if ( f.settings.input_complete_emotes && item.info ) {
var info = document.createElement('span');
var info = utils.createElement('span');
info.innerHTML = item.info;
el.classList.add('has-info');
if ( width )
@ -394,8 +356,7 @@ FFZ.prototype._modify_chat_input = function(component) {
current = this.get('ffz_current_suggestion') || 0;
if ( ! el ) {
el = this.ffz_suggestions_el = document.createElement('div');
el.className = 'suggestions ffz-suggestions';
el = this.ffz_suggestions_el = utils.createElement('div', 'suggestions ffz-suggestions');
this.get('element').appendChild(el);
} else
@ -430,8 +391,7 @@ FFZ.prototype._modify_chat_input = function(component) {
}
if ( ! added ) {
var item_el = document.createElement('div');
item_el.className = 'suggestion disabled';
var item_el = utils.createElement('div', 'suggestion disabled');
item_el.textContent = 'No matches.';
el.appendChild(item_el);
}

View file

@ -107,6 +107,7 @@ FFZ.settings_info.chat_batching = {
type: "select",
options: {
0: "No Batching",
125: "Minimal (0.125s)",
250: "Minor (0.25s)",
500: "Normal (0.5s)",
750: "Large (0.75s)",
@ -474,36 +475,7 @@ FFZ.prototype.setup_chatview = function() {
this.log("Hooking the Ember Chat view.");
var Chat = utils.ember_resolve('view:chat');
this._modify_cview(Chat);
// For some reason, this doesn't work unless we create an instance of the
// chat view and then destroy it immediately.
try {
Chat.create().destroy();
} catch(err) { }
// Modify all existing Chat views.
var views = utils.ember_views();
for(var key in views) {
if ( ! views.hasOwnProperty(key) )
continue;
var view = views[key];
if ( !(view instanceof Chat) )
continue;
this.log("Manually updating existing Chat view.", view);
try {
if ( ! view.ffzInit )
this._modify_cview(view);
view.ffzInit();
} catch(err) {
this.error("setup: build_ui_link: " + err);
}
}
this.update_views('view:chat', this.modify_chat_view);
}
@ -511,35 +483,10 @@ FFZ.prototype.setup_chatview = function() {
// Modify Chat View
// --------------------
FFZ.prototype._modify_cview = function(view) {
FFZ.prototype.modify_chat_view = function(view) {
var f = this;
view.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("view:chat ffzInit error: " + err);
}
},
didUpdate: function() {
this._super();
f.log("view:chat didUpdate", this)
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("view:chat ffzTeardown error: " + err);
}
this._super();
},
ffzInit: function() {
utils.ember_reopen_view(view, {
ffz_init: function() {
f._chatv = this;
var room_id = this.get('controller.currentRoom.id'),
@ -567,7 +514,7 @@ FFZ.prototype._modify_cview = function(view) {
}, 1000);
},
ffzTeardown: function() {
ffz_destroy: function() {
if ( f._chatv === this )
f._chatv = null;
@ -868,10 +815,9 @@ FFZ.prototype._modify_cview = function(view) {
chan_table = this._ffz_chan_table || room_list.querySelector('#ffz-channel-table tbody');
if ( ! chan_table ) {
var tbl = document.createElement('table');
var tbl = utils.createElement('table', 'ffz');
tbl.setAttribute('cellspacing', '0');
tbl.id = 'ffz-channel-table';
tbl.className = 'ffz';
tbl.innerHTML = '<thead><tr><th colspan="2">Channels</th><th class="ffz-row-switch" title="Pinning a channel makes it so you always join that channel\'s chat, no matter where you are on Twitch.">Pin</th></tr></thead><tbody></tbody>';
room_list.insertBefore(tbl, room_list.firstChild);
@ -914,10 +860,9 @@ FFZ.prototype._modify_cview = function(view) {
// Group Chat Table
var group_table = this._ffz_group_table || room_list.querySelector('#ffz-group-table tbody');
if ( ! group_table ) {
var tbl = document.createElement('table');
var tbl = utils.createElement('table', 'ffz');
tbl.setAttribute('cellspacing', '0');
tbl.id = 'ffz-group-table';
tbl.className = 'ffz';
tbl.innerHTML = '<thead><tr><th colspan="2">Group Chats</th></tr></thead><tbody></tbody>';
var before = room_list.querySelector('#ffz-channel-table');
@ -1007,8 +952,7 @@ FFZ.prototype._modify_cview = function(view) {
this.classList.toggle('active', !is_pinned);
});
} else {
btn = document.createElement('a');
btn.className = 'leave-chat html-tooltip';
btn = utils.createElement('a', 'leave-chat html-tooltip');
btn.innerHTML = constants.CLOSE;
btn.title = 'Leave Group';
@ -1085,12 +1029,10 @@ FFZ.prototype._modify_cview = function(view) {
if ( f.has_bttv || ! f.settings.group_tabs )
return;
var link = document.createElement('a'),
var link = utils.createElement('a', 'button button--icon-only'),
view = this;
// Chat Room Management Button
link.className = 'button button--icon-only';
link.title = "Chat Room Management";
link.innerHTML = '<figure class="icon">' + constants.ROOMS + '</figure><span class="notifications"></span>';
@ -1105,8 +1047,7 @@ FFZ.prototype._modify_cview = function(view) {
// Invite Button
link = document.createElement('a'),
link.className = 'button button--icon-only html-tooltip invite';
link = utils.createElement('a', 'button button--icon-only html-tooltip invite');
link.title = "Invite a User";
link.innerHTML = '<figure class="icon">' + constants.INVITE + '</figure>';
@ -1216,6 +1157,10 @@ FFZ.prototype._modify_cview = function(view) {
now = Date.now();
// Non-Existant Rooms
if ( ! room )
return false;
if ( is_current || is_channel || room_id === this._ffz_host || f.settings.group_tabs === 3 )
// Important Tabs
return true;

View file

@ -71,42 +71,16 @@ FFZ.prototype.setup_conversations = function() {
document.body.classList.toggle('ffz-minimize-conversations', this.settings.minimize_conversations);
document.body.classList.toggle('ffz-theatre-conversations', this.settings.hide_conversations_in_theatre);
var ConvWindow = utils.ember_resolve('component:twitch-conversations/conversation-window');
if ( ConvWindow ) {
this.log("Hooking the Ember Conversation Window component.");
this._modify_conversation_window(ConvWindow);
try { ConvWindow.create().destroy() }
catch(err) { }
} else
this.log("Unable to resolve: component:twitch-conversations/conversation-window");
var ConvSettings = utils.ember_resolve('component:twitch-conversations/conversation-settings-menu');
if ( ConvSettings ) {
this.log("Hooking the Ember Conversation Settings Menu component.");
this._modify_conversation_menu(ConvSettings);
try { ConvSettings.create().destroy() }
catch(err) { }
} else
this.log("Unable to resolve: component:twitch-conversations/conversation-settings-menu");
var ConvLine = utils.ember_resolve('component:twitch-conversations/conversation-line');
if ( ConvLine ) {
this.log("Hooking the Ember Conversation Line component.");
this._modify_conversation_line(ConvLine);
try { ConvLine.create().destroy() }
catch(err) { }
} else
this.log("Unable to resolve: component:twitch-conversations/conversation-line");
this.update_views('component:twitch-conversations/conversation-window', this.modify_conversation_window);
this.update_views('component:twitch-conversations/conversation-settings-menu', this.modify_conversation_menu);
this.update_views('component:twitch-conversations/conversation-line', this.modify_conversation_line);
}
FFZ.prototype._modify_conversation_menu = function(component) {
FFZ.prototype.modify_conversation_menu = function(component) {
var f = this;
component.reopen({
didInsertElement: function() {
utils.ember_reopen_view(component, {
ffz_init: function() {
var user = this.get('thread.otherUsername'),
el = this.get('element'),
sections = el && el.querySelectorAll('.options-section');
@ -132,11 +106,11 @@ FFZ.prototype._modify_conversation_menu = function(component) {
}
FFZ.prototype._modify_conversation_window = function(component) {
FFZ.prototype.modify_conversation_window = function(component) {
var f = this,
Layout = utils.ember_lookup('service:layout');
component.reopen({
utils.ember_reopen_view(component, {
headerBadges: Ember.computed("thread.participants", "currentUsername", function() {
return [];
}),
@ -154,7 +128,7 @@ FFZ.prototype._modify_conversation_window = function(component) {
badge_el.innerHTML = f.render_badges(badges);
}.observes('ffzHeaderBadges'),
didInsertElement: function() {
ffz_init: function() {
var el = this.get('element'),
header = el && el.querySelector('.conversation-header'),
header_name = header && header.querySelector('.conversation-header-name'),
@ -178,11 +152,11 @@ FFZ.prototype._modify_conversation_window = function(component) {
}
FFZ.prototype._modify_conversation_line = function(component) {
FFZ.prototype.modify_conversation_line = function(component) {
var f = this,
Layout = utils.ember_lookup('service:layout');
component.reopen({
utils.ember_reopen_view(component, {
tokenizedMessage: function() {
try {
return f.tokenize_conversation_line(this.get('message'));
@ -204,7 +178,7 @@ FFZ.prototype._modify_conversation_line = function(component) {
},
didUpdate: function() { this.ffzRender() },
didInsertElement: function() { this.ffzRender() },
ffz_init: function() { this.ffzRender() },
ffzRender: function() {
var el = this.get('element'),

View file

@ -77,14 +77,44 @@ FFZ.settings_info.directory_group_hosts = {
};
FFZ.settings_info.banned_games = {
type: "button",
value: [],
FFZ.settings_info.enable_recommended_vods = {
type: "boolean",
value: true,
category: "Directory",
no_mobile: true,
name: "Banned Games",
help: "A list of games that will not be displayed in the Directory.",
experiment_warn: true,
name: 'Show Twitch\'s Recommended Videos',
help: 'Show the "Based on your Viewing History" section of the directory rather than <nobr>Most Recent Videos.</nobr>',
on_update: function(val) {
Ember.propertyDidChange(utils.ember_lookup('service:vod-coviews'), 'areVodsViewable');
}
}
FFZ.settings_info.recommended_above_hosts = {
type: "boolean",
value: function() { var s = utils.ember_lookup('service:vod-coviews'); return s && s.get('isFollowingAboveHost') },
category: "Directory",
no_mobile: true,
experiment_warn: true,
name: "Show Twitch's Recommended Videos above Hosts",
help: 'Enable this to place the "Based on your Viewing History" section above Live Hosts.',
on_update: function(val) {
Ember.propertyDidChange(utils.ember_lookup('service:vod-coviews'), 'isFollowingAboveHost');
//utils.ember_lookup('service:vod-coviews').set('isFollowingAboveHost', val);
}
}
FFZ.settings_info.banned_games = {
visible: false,
value: [],
on_update: function() {
var banned = this.settings.banned_games,
@ -96,34 +126,14 @@ FFZ.settings_info.banned_games = {
el.classList.toggle('ffz-game-banned', banned.indexOf(game && game.toLowerCase()) !== -1);
}
},
method: function() {
var f = this,
old_val = f.settings.banned_games.join(", ");
utils.prompt(
"Banned Games",
"Please enter a comma-separated list of games that you would like to be banned from viewing in the Directory.</p><p>This is case insensitive, however you must type the full name.</p><p><b>Example:</b> <code>League of Legends, Dota 2, Smite</code>",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
f.settings.set("banned_games", _.unique(new_val.trim().toLowerCase().split(/\s*,\s*/)));
}, 600);
}
}
FFZ.settings_info.spoiler_games = {
type: "button",
visible: false,
value: [],
category: "Directory",
no_mobile: true,
name: "No-Thumbnail Games",
help: "Stream and video thumbnails will be hidden for games that you add to this list.",
on_update: function() {
var spoiled = this.settings.spoiler_games,
els = document.querySelectorAll('.ffz-directory-preview');
@ -134,21 +144,6 @@ FFZ.settings_info.spoiler_games = {
el.classList.toggle('ffz-game-spoilered', spoiled.indexOf(game && game.toLowerCase()) !== -1);
}
},
method: function() {
var f = this,
old_val = f.settings.spoiler_games.join(", ");
utils.prompt(
"No-Thumbnail Games",
"Please enter a comma-separated list of games that you would like to have the thumbnails hidden for in the Directory.</p><p>This is case insensitive, however you must type the full name.</p><p><b>Example:</b> <code>Undertale</code>",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
f.settings.set("spoiler_games", _.unique(new_val.trim().toLowerCase().split(/\s*,\s*/)));
}, 600);
}
}
@ -198,88 +193,46 @@ FFZ.prototype.setup_directory = function() {
document.body.classList.toggle('ffz-creative-tags', this.settings.directory_creative_all_tags);
document.body.classList.toggle('ffz-creative-showcase', this.settings.directory_creative_showcase);
var f = this,
VodCoviews = utils.ember_lookup('service:vod-coviews');
if ( VodCoviews ) {
VodCoviews.reopen({
// checkExperiment likes setting this back. Don't let it.
isFollowingAboveHost: Ember.computed('_ffz', {
get: function(key) {
return f.settings.recommended_above_hosts;
},
set: function(key, val) {
return f.settings.recommended_above_hosts;
}
}),
areVodsViewable: function() {
var filtered = this.get('filteredVods');
return f.settings.enable_recommended_vods && filtered && filtered.length > 0;
}.property('filteredVods')
});
Ember.propertyDidChange(VodCoviews, 'isFollowingAboveHost');
Ember.propertyDidChange(VodCoviews, 'areVodsViewable');
} else
this.log("Unable to locate the Ember service:vod-coviews");
this.log("Attempting to modify the Following collection.");
this._modify_following();
this.log("Hooking the Ember Directory views.");
var ChannelView = utils.ember_resolve('component:stream-preview');
if ( ChannelView ) {
this._modify_directory_live(ChannelView, false);
try {
ChannelView.create().destroy();
} catch(err) { }
} else
this.log("Unable to locate the Ember component:stream-preview");
this.update_views('component:stream-preview', function(x) { this.modify_directory_live(x, false) }, true);
this.update_views('component:creative-preview', function(x) { this.modify_directory_live(x, false) }, true);
this.update_views('component:csgo-channel-preview', function(x) { this.modify_directory_live(x, true) }, true);
this.update_views('component:host-preview', this.modify_directory_host, true);
this.update_views('component:video-preview', this.modify_video_preview, true);
var CreativeChannel = utils.ember_resolve('component:creative-preview');
if ( CreativeChannel ) {
this._modify_directory_live(CreativeChannel, false);
try {
CreativeChannel.create().destroy();
} catch(err) { }
} else
this.log("Unable to locate the Ember component:creative-preview");
var CSGOChannel = utils.ember_resolve('component:csgo-channel-preview');
CSGOChannel = this._modify_directory_live(CSGOChannel, true, 'component:csgo-channel-preview');
try {
CSGOChannel.create().destroy();
} catch(err) { }
var HostView = utils.ember_resolve('component:host-preview');
HostView = this._modify_directory_host(HostView);
try {
HostView.create().destroy();
} catch(err) { }
var VideoPreview = utils.ember_resolve('component:video-preview');
if ( VideoPreview ) {
this._modify_video_preview(VideoPreview);
try { VideoPreview.create().destroy();
} catch(err) { }
} else
this.log("Unable to locate the Ember component:video-preview");
var GameFollow = utils.ember_resolve('component:game-follow-button');
if ( GameFollow ) {
this._modify_game_follow(GameFollow);
try { GameFollow.create().destroy() }
catch(err) { }
} else
this.log("Unable to locate the Ember component:game-follow-button");
// Initialize existing views.
var views = utils.ember_views();
for(var key in views) {
var view = views[key];
if ( (ChannelView && view instanceof ChannelView) || (CreativeChannel && view instanceof CreativeChannel) ) {
if ( ! view.ffzInit )
this._modify_directory_live(view, false);
} else if ( CSGOChannel && view instanceof CSGOChannel ) {
if ( ! view.ffzInit )
this._modify_directory_live(view, true);
} else if ( view instanceof HostView || view.get('tt_content') === 'live_host' ) {
if ( ! view.ffzInit )
this._modify_directory_host(view);
} else if ( VideoPreview && view instanceof VideoPreview ) {
if ( ! view.ffzInit )
this._modify_video_preview(view);
} else if ( GameFollow && view instanceof GameFollow ) {
if ( ! view.ffzInit )
this._modify_game_follow(view);
} else
continue;
try {
view.ffzInit();
} catch(err) {
this.error("Directory Setup: " + err);
}
}
this.update_views('component:game-follow-button', this.modify_game_follow_button);
}
@ -407,15 +360,10 @@ FFZ.prototype._modify_following = function() {
}
FFZ.prototype._modify_game_follow = function(component) {
FFZ.prototype.modify_game_follow_button = function(component) {
var f = this;
component.reopen({
didInsertElement: function() {
this._super();
this.ffzInit();
},
ffzInit: function() {
utils.ember_reopen_view(component, {
ffz_init: function() {
var el = this.get('element'),
game = this.get('game.id').toLowerCase(),
@ -436,37 +384,33 @@ FFZ.prototype._modify_game_follow = function(component) {
};
// Block Button
var block = utils.createElement('div', 'follow-button ffz-block-button'),
block_link = utils.createElement('a', 'tooltip follow block');
var block = utils.createElement('button', 'button tooltip ffz-block-button'),
update_block = function() {
var is_blocked = f.settings.banned_games.indexOf(game) !== -1;
block_link.classList.toggle('active', is_blocked);
block.classList.toggle('active', is_blocked);
block_link.innerHTML = '<span>' + (is_blocked ? 'Unblock' : 'Block') + '</span>';
block_link.title = 'Click to ' + (is_blocked ? 'unblock' : 'block') + " this game.\n\nBlocking a game hides all the streams and videos of the game when you're not viewing it directly.";
block.innerHTML = (is_blocked ? 'Unblock' : 'Block');
block.title = 'Click to ' + (is_blocked ? 'unblock' : 'block') + " this game.\n\nBlocking a game hides all the streams and videos of the game when you're not viewing it directly.";
jQuery(block).trigger('mouseout').trigger('mouseover');
};
update_block();
block_link.addEventListener('click', click_button('banned_games', update_block));
block.appendChild(block_link);
block.addEventListener('click', click_button('banned_games', update_block));
el.appendChild(block);
// Spoiler Button
var spoiler = utils.createElement('div', 'follow-button ffz-spoiler-button'),
spoiler_link = utils.createElement('a', 'tooltip follow spoiler'),
var spoiler = utils.createElement('button', 'button tooltip ffz-spoiler-button'),
update_spoiler = function() {
var is_spoiled = f.settings.spoiler_games.indexOf(game) !== -1;
spoiler_link.classList.toggle('active', is_spoiled);
spoiler.classList.toggle('active', is_spoiled);
spoiler_link.innerHTML = '<span>' + (is_spoiled ? 'Show Thumbnails' : 'Hide Thumbnails') + '</span>';
spoiler_link.title = 'Click to ' + (is_spoiled ? 'show' : 'hide') + " thumbnails for this game.\n\nHiding thumbnails for a game will help you avoid spoilers for a game that you haven't played yet.";
spoiler.innerHTML = (is_spoiled ? 'Show Thumbnails' : 'Hide Thumbnails');
spoiler.title = 'Click to ' + (is_spoiled ? 'show' : 'hide') + " thumbnails for this game.\n\nHiding thumbnails for a game will help you avoid spoilers for a game that you haven't played yet.";
jQuery(spoiler).trigger('mouseout').trigger('mouseover');
}
update_spoiler();
spoiler_link.addEventListener('click', click_button('spoiler_games', update_spoiler));
spoiler.appendChild(spoiler_link);
spoiler.addEventListener('click', click_button('spoiler_games', update_spoiler));
el.appendChild(spoiler);
jQuery('.tooltip', el).tipsy();
@ -475,17 +419,12 @@ FFZ.prototype._modify_game_follow = function(component) {
}
FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
FFZ.prototype.modify_directory_live = function(component, is_csgo) {
var f = this,
pref = is_csgo ? 'channel.' : 'stream.';
var mutator = {
didInsertElement: function() {
this._super();
this.ffzInit();
},
ffzInit: function() {
utils.ember_reopen_view(component, {
ffz_init: function() {
var el = this.get('element'),
meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'),
@ -532,12 +471,12 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
var Channel = utils.ember_resolve('model:channel');
var Channel = utils.ember_resolve('model:deprecated-channel');
if ( ! Channel )
return;
e.preventDefault();
utils.ember_lookup('router:main').transitionTo('channel.index', Channel.find({id: channel_id}).load());
e.preventDefault();
return false;
});
@ -546,7 +485,7 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
}
},
willClearRender: function() {
ffz_destroy: function() {
if ( this._ffz_uptime ) {
this._ffz_uptime.parentElement.removeChild(this._ffz_uptime);
this._ffz_uptime = null;
@ -557,8 +496,6 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
if ( this._ffz_image_timer )
clearInterval(this._ffz_image_timer);
this._super();
},
ffzRotateImage: function() {
@ -587,32 +524,14 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
this._ffz_uptime.innerHTML = '';
}
}
};
if ( dir )
dir.reopen(mutator);
else {
dir = Ember.Component.extend(mutator);
App.__deprecatedInstance__.registry.register(component_name, dir);
}
return dir;
});
}
FFZ.prototype._modify_video_preview = function(vp) {
FFZ.prototype.modify_video_preview = function(component) {
var f = this;
vp.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("component:video-preview ffzInit: " + err);
}
},
ffzInit: function() {
utils.ember_reopen_view(component, {
ffz_init: function() {
var el = this.get('element'),
game = this.get('video.game'),
@ -659,30 +578,11 @@ FFZ.prototype._modify_video_preview = function(vp) {
}
FFZ.prototype._modify_directory_host = function(dir) {
var f = this, mutator;
mutator = {
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("component:host-preview ffzInit: " + err);
}
},
willClearRender: function() {
this._super();
try {
this.ffzCleanup();
} catch(err) {
f.error("component:host-preview ffzCleanup: " + err);
}
},
FFZ.prototype.modify_directory_host = function(component) {
var f = this;
utils.ember_reopen_view(component, {
ffzVisitChannel: function(target, e) {
var Channel = utils.ember_resolve('model:channel');
var Channel = utils.ember_resolve('model:deprecated-channel');
if ( ! Channel )
return;
@ -773,7 +673,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
this.$('.thumb .cap img').attr('src', url);
},
ffzCleanup: function() {
ffz_destroy: function() {
var target = this.get('stream.target.channel');
if ( f._popup && f._popup.classList.contains('ffz-channel-selector') && f._popup.getAttribute('data-channel') === target )
f.close_popup();
@ -782,7 +682,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
clearInterval(this._ffz_image_timer);
},
ffzInit: function() {
ffz_init: function() {
var el = this.get('element'),
meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'),
@ -839,14 +739,5 @@ FFZ.prototype._modify_directory_host = function(dir) {
cap.addEventListener('click', this.ffzShowHostMenu.bind(this));
}
}
};
if ( dir )
dir.reopen(mutator);
else {
dir = Ember.Component.extend(mutator);
App.__deprecatedInstance__.registry.register('component:host-preview', dir);
}
return dir;
});
}

View file

@ -21,15 +21,8 @@ var FFZ = window.FrankerFaceZ,
FFZ.prototype.setup_feed_cards = function() {
var FeedCard = utils.ember_resolve('component:channel-feed/card');
if ( ! FeedCard )
return this.error("Unable to locate component:channel-feed/card");
this.log("Modifying the feed-card component.");
this._modify_feed_card(FeedCard);
try { FeedCard.create().destroy();
} catch(err) { }
this.update_views('component:channel-feed/card', this.modify_feed_card);
this.update_views('component:channel-feed/comment', this.modify_feed_comment);
this.rerender_feed_cards();
}
@ -37,6 +30,7 @@ FFZ.prototype.setup_feed_cards = function() {
FFZ.prototype.rerender_feed_cards = function(for_set) {
var FeedCard = utils.ember_resolve('component:channel-feed/card'),
FeedComment = utils.ember_resolve('component:channel-feed/comment'),
views = utils.ember_views();
if ( ! FeedCard )
@ -46,30 +40,31 @@ FFZ.prototype.rerender_feed_cards = function(for_set) {
var view = views[view_id];
if ( view instanceof FeedCard ) {
try {
if ( ! view.ffzInit )
this._modify_feed_card(view);
view.ffzInit(for_set);
if ( ! view.ffz_init )
this.modify_feed_card(view);
view.ffz_init(for_set);
} catch(err) {
this.error("setup component:channel-feed/card ffzInit: " + err)
this.error("setup component:channel-feed/card ffzInit", err)
}
}
if ( FeedComment && view instanceof FeedComment ) {
try {
if ( ! view.ffz_init )
this.modify_feed_comment(view);
view.ffz_init(for_set);
} catch(err) {
this.error("setup component:channel-feed/comment ffzInit", err);
}
}
}
}
FFZ.prototype._modify_feed_card = function(component) {
FFZ.prototype.modify_feed_card = function(component) {
var f = this;
component.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("component:channel-feed/card ffzInit: " + err);
}
},
ffzInit: function(for_set) {
utils.ember_reopen_view(component, {
ffz_init: function(for_set) {
var el = this.get('element'),
message = this.get('post.body'),
emotes = parse_emotes(this.get('post.emotes')),
@ -97,3 +92,33 @@ FFZ.prototype._modify_feed_card = function(component) {
}
});
}
FFZ.prototype.modify_feed_comment = function(component) {
var f = this;
utils.ember_reopen_view(component, {
ffz_init: function(for_set) {
var el = this.get('element'),
message = this.get('comment.body'),
emotes = parse_emotes(this.get('comment.emotes')),
user_id = this.get('comment.user.login'),
room_id = this.get('parentView.parentView.channelId') || this.get('parentView.parentView.post.user.login') || null,
pbody = el && el.querySelector('.activity-body');
if ( ! message || ! el || ! pbody )
return;
// If this is for a specific emote set, only rerender if it matters.
if ( for_set && f.rooms && f.rooms[room_id] ) {
var sets = f.getEmotes(user_id, room_id);
if ( sets.indexOf(for_set) === -1 )
return;
}
var tokens = f.tokenize_feed_body(message, emotes, user_id, room_id),
output = f.render_tokens(tokens, true, false);
pbody.innerHTML = '<p>' + output + '</p>';
}
})
}

View file

@ -167,7 +167,7 @@ FFZ.prototype.setup_profile_following = function() {
// Refresh all existing following data.
var count = 0,
Channel = utils.ember_resolve('model:channel');
Channel = utils.ember_resolve('model:deprecated-channel');
if ( Channel && Channel._cache )
for(var key in Channel._cache) {

View file

@ -240,16 +240,26 @@ FFZ.prototype.setup_layout = function() {
}.observes("isTooSmallForRightColumn"),
ffzUpdateCss: function() {
var out = '';
var window_height = this.get('windowHeight'),
window_width = this.get('windowWidth'),
out = 'body.ffz-small-player #player .dynamic-player {' +
'position: fixed;' +
'z-index: 9;' +
'box-shadow: 0 0 20px 0 black;';
if ( .25 * window_width >= .5 * window_height )
out += 'width: 25vw !important; height: 14.0625vw !important;';
else
out += 'width: 50vh !important; height: 28.125vh !important;';
if ( ! f.has_bttv ) {
if ( this.get('isRightColumnClosed') )
out = '';
out += 'top: 0; right: 0}';
else {
if ( this.get('portraitMode') ) {
var size = this.get('playerSize'),
video_below = this.get('portraitVideoBelow'),
window_height = this.get('windowHeight'),
window_width = this.get('windowWidth'),
video_height = size[1] + 120 + 60,
chat_height = window_height - video_height,
@ -263,7 +273,8 @@ FFZ.prototype.setup_layout = function() {
theatre_video_top = video_below ? theatre_chat_height : 0,
theatre_chat_top = video_below ? 0 : theatre_video_height;
out = 'body[data-current-path^="user."] #left_col .warp { min-height: inherit }' +
out += 'top: ' + video_top + 'px;right: 0}' +
'body[data-current-path^="user."] #left_col .warp { min-height: inherit }' +
'body[data-current-path^="user."] #left_col { overflow: hidden }' +
'body[data-current-path^="user."] #left_col .warp,' +
'body[data-current-path^="user."] #left_col,' +
@ -290,7 +301,9 @@ FFZ.prototype.setup_layout = function() {
} else {
var width = this.get('rightColumnWidth');
out = '#main_col.expandRight #right_close{left: none !important}' +
out += 'top: 0; right: ' + width + 'px}' +
'#main_col.expandRight #right_close{left: none !important}' +
'#right_col{width:' + width + 'px}' +
'body:not(.ffz-sidebar-swap) #main_col:not(.expandRight){' +
'margin-right:' + width + 'px}' +
@ -317,7 +330,8 @@ FFZ.prototype.setup_layout = function() {
ffzFixTabs: function() {
if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) {
setTimeout(function() {
f._chatv && f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px");
var cr = f._chatv && f._chatv.$('.chat-room');
cr && cr.css && cr.css('top', f._chatv._ffz_tabs.offsetHeight + "px");
},0);
}
}.observes("isRightColumnClosed", "rightColumnWidth", "portraitMode", "playerSize")

View file

@ -20,7 +20,7 @@ FFZ.settings_info.alias_italics = {
on_update: function(val) {
document.body.classList.toggle('ffz-alias-italics', val);
}
};
};
FFZ.settings_info.room_status = {
type: "boolean",
@ -36,7 +36,7 @@ FFZ.settings_info.room_status = {
if ( this._roomv )
this._roomv.ffzUpdateStatus();
}
};
};
FFZ.settings_info.replace_bad_emotes = {
@ -48,7 +48,19 @@ FFZ.settings_info.replace_bad_emotes = {
name: "Fix Low Quality Twitch Global Emoticons",
help: "Replace emoticons such as DansGame and RedCoat with cleaned up versions that don't have pixels around the edges or white backgrounds for nicer display on dark chat."
};
};
FFZ.settings_info.parse_emoticons = {
type: "boolean",
value: true,
category: "Chat Appearance",
no_bttv: true,
name: "Display Emoticons",
help: "Display emoticons in chat messages rather than just text."
};
FFZ.settings_info.parse_emoji = {
@ -74,9 +86,9 @@ FFZ.settings_info.parse_emoji = {
category: "Chat Appearance",
name: "Emoji Display",
name: "Display Emoji",
help: "Replace emoji in chat messages with nicer looking images from either Twitter or Google."
};
};
FFZ.settings_info.scrollback_length = {
@ -354,6 +366,19 @@ FFZ.settings_info.old_sub_notices = {
};
FFZ.settings_info.emote_alignment = {
type: "boolean",
value: false,
category: "Chat Appearance",
no_bttv: true,
name: "Baseline Emoticon Alignment",
help: "Align emotes on the text baseline, making messages taller but ensuring emotes don't overlap.",
on_update: function(val) { document.body.classList.toggle('ffz-baseline-emoticons', !this.has_bttv && val) }
};
FFZ.settings_info.chat_padding = {
type: "boolean",
value: false,
@ -588,6 +613,7 @@ FFZ.prototype.setup_line = function() {
// Chat Enhancements
document.body.classList.toggle('ffz-alias-italics', this.settings.alias_italics);
document.body.classList.toggle('ffz-baseline-emoticons', !this.has_bttv && this.settings.emote_alignment);
this.toggle_style('chat-setup', !this.has_bttv && (this.settings.chat_rows || this.settings.chat_separators || this.settings.highlight_messages_with_mod_card));
this.toggle_style('chat-padding', !this.has_bttv && this.settings.chat_padding);
@ -710,7 +736,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
if ( is_whisper || this_ul >= other_ul || f.settings.mod_buttons.length === 0 )
return '';
output = '<span class="mod-icons float-left">';
output = '<span class="mod-icons">';
for(var i=0, l = f.settings.mod_buttons.length; i < l; i++) {
var pair = f.settings.mod_buttons[i],
@ -720,22 +746,22 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
if ( btn === false ) {
if ( deleted )
output += '<a class="mod-icon float-left html-tooltip unban" title="Unban User" href="#">Unban</a>';
output += '<a class="mod-icon html-tooltip unban" title="Unban User" href="#">Unban</a>';
else
output += '<a class="mod-icon float-left html-tooltip ban" title="Ban User" href="#">Ban</a>';
output += '<a class="mod-icon html-tooltip ban" title="Ban User" href="#">Ban</a>';
} else if ( btn === 600 )
output += '<a class="mod-icon float-left html-tooltip timeout" title="Timeout User (10m)" href="#">Timeout</a>';
output += '<a class="mod-icon html-tooltip timeout" title="Timeout User (10m)" href="#">Timeout</a>';
else {
if ( typeof btn === "string" ) {
cmd = btn.replace(/{user}/g, user).replace(/ *<LINE> */, "\n");
cmd = btn.replace(/{user}/g, user).replace(/{id}/g, this.get('msgObject.tags.id')).replace(/ *<LINE> */, "\n");
tip = "Custom Command" + (cmd.indexOf("\n") !== -1 ? 's' : '') + '<br>' + utils.quote_san(cmd).replace('\n','<br>');
} else {
cmd = "/timeout " + user + " " + btn;
tip = "Timeout User (" + utils.duration_string(btn) + ")";
}
output += '<a class="mod-icon float-left html-tooltip' + (cmd.substr(0,9) === '/timeout' ? ' is-timeout' : '') + ' custom" data-cmd="' + utils.quote_attr(cmd) + '" title="' + tip + '" href="#">' + prefix + '</a>';
output += '<a class="mod-icon html-tooltip' + (cmd.substr(0,9) === '/timeout' ? ' is-timeout' : '') + ' custom" data-cmd="' + utils.quote_attr(cmd) + '" title="' + tip + '" href="#">' + prefix + '</a>';
}
}
@ -770,18 +796,18 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
// System Message
if ( system_msg ) {
output += '<div class="system-msg">' + utils.sanitize(system_msg) + '</div>';
if ( this.get('shouldRenderMessageBody') === false )
if ( this.get('ffzShouldRenderMessageBody') === false )
return output;
}
// Timestamp
output += '<span class="timestamp float-left">' + this.get('timestamp') + '</span> ';
output += '<span class="timestamp">' + this.get('timestamp') + '</span> ';
// Moderator Actions
output += this.buildModIconsHTML();
// Badges
output += '<span class="badges float-left">' + f.render_badges(f.get_line_badges(this.get('msgObject'))) + '</span>';
output += '<span class="badges">' + f.render_badges(f.get_line_badges(this.get('msgObject'))) + '</span>';
// Alias Support
var alias = f.aliases[user],
@ -842,7 +868,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
} else
output = '<span class="message">';
output += f.render_tokens(this.get('ffzTokenizedMessage'), true, is_whisper && f.settings.filter_whispered_links && this.get("ffzUserLevel") < 4);
output += f.render_tokens(this.get('ffzTokenizedMessage'), true, is_whisper && f.settings.filter_whispered_links && this.get("ffzUserLevel") < 4, this.get('isBitsEnabled'));
var old_messages = this.get('msgObject.ffz_old_messages');
if ( old_messages && old_messages.length )
@ -856,7 +882,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
output = this.buildSenderHTML();
// If this is a whisper, or if we should render the message body, render it.
if ( this.get('shouldRenderMessageBody') !== false )
if ( this.get('ffzShouldRenderMessageBody') !== false )
if ( this.get('msgObject.deleted') )
output += this.buildDeletedMessageHTML()
else
@ -865,6 +891,14 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
el.innerHTML = output;
},
ffzShouldRenderMessageBody: function() {
return ! this.get('hasSystemMsg') || this.get('hasMessageBody');
}.property('hasSystemMsg', 'hasMessageBody'),
shouldRenderMessageBody: function() {
return false;
}.property('hasSystemMsg', 'hasMessageBody'),
ffzWasDeleted: function() {
return f.settings.prevent_clear && this.get("msgObject.ffz_deleted")
}.property("msgObject.ffz_deleted"),
@ -883,8 +917,8 @@ FFZ.prototype._modify_chat_subline = function(component) {
this._modify_chat_line(component);
component.reopen({
classNameBindings: [":message-line", ":chat-line", "msgObject.style", "msgObject.ffz_has_mention:ffz-mentioned", "ffzWasDeleted:ffz-deleted", "ffzHasOldMessages:clearfix", "ffzHasOldMessages:ffz-has-deleted"],
attributeBindings: ["msgObject.room:data-room", "msgObject.from:data-sender", "msgObject.deleted:data-deleted"],
classNameBindings: ["msgObject.style", "msgObject.ffz_has_mention:ffz-mentioned", "ffzWasDeleted:ffz-deleted", "ffzHasOldMessages:clearfix", "ffzHasOldMessages:ffz-has-deleted"],
attributeBindings: ["msgObject.tags.id:data-id", "msgObject.room:data-room", "msgObject.from:data-sender", "msgObject.deleted:data-deleted"],
didInsertElement: function() {
this.set('msgObject._line', this);
@ -1024,10 +1058,10 @@ FFZ.prototype._modify_vod_line = function(component) {
if ( ! this.get("isViewerModeratorOrHigher") || this.get("isModeratorOrHigher") )
return "";
return '<span class="mod-icons float-left">' +
return '<span class="mod-icons">' +
(this.get('msgObject.deleted') ?
'<em class="mod-icon float-left unban"></em>' :
'<a class="mod-icon float-left html-tooltip delete" title="Delete Message" href="#">Delete</a>') + '</span>';
'<em class="mod-icon unban"></em>' :
'<a class="mod-icon html-tooltip delete" title="Delete Message" href="#">Delete</a>') + '</span>';
},
buildDeletedMesageHTML: function() {

View file

@ -353,7 +353,8 @@ FFZ.settings_info.mod_buttons = {
"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 " +
"into the command, otherwise it will be appended to the end. Use <code>{id}</code> to insert the unique message ID into the command.</p>" +
"<p><b>Example:</b> <code>!permit \"!reg add {user}\" \"/timeout {user} 1 {id}\"</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>" +
@ -583,7 +584,6 @@ FFZ.prototype.setup_mod_card = function() {
helpers = window.require && window.require("web-client/helpers/chat/chat-line-helpers");
} catch(err) { }
this.log("Listening to the Settings controller to catch mod icon state changes.");
var f = this,
Settings = utils.ember_lookup('controller:settings');
@ -594,7 +594,6 @@ FFZ.prototype.setup_mod_card = function() {
f.settings.set('chat_mod_icon_visibility', 1);
});
this.log("Modifying Mousetrap stopCallback so we can catch ESC.");
var orig_stop = Mousetrap.stopCallback;
Mousetrap.stopCallback = function(e, element, combo) {
@ -609,11 +608,13 @@ FFZ.prototype.setup_mod_card = function() {
el && el.classList.toggle('ffz-flip');
});
this.log("Hooking the Ember Moderation Card view.");
var Card = utils.ember_resolve('component:chat/moderation-card');
this.update_views('component:chat/moderation-card', this.modify_moderation_card);
}
Card.reopen({
FFZ.prototype.modify_moderation_card = function(component) {
var f = this;
utils.ember_reopen_view(component, {
ffzForceRedraw: function() {
this.rerender();
if ( f.settings.mod_card_history )
@ -667,18 +668,14 @@ FFZ.prototype.setup_mod_card = function() {
return alias || this.get("cardInfo.user.display_name") || user_id.capitalize();
}),
willDestroy: function() {
ffz_destroy: function() {
if ( f._mod_card === this )
f._mod_card = undefined;
utils.update_css(f._chat_style, 'mod-card-highlight');
this._super();
},
didInsertElement: function() {
this._super();
try {
ffz_init: function() {
if ( f.has_bttv )
return;
@ -744,11 +741,10 @@ FFZ.prototype.setup_mod_card = function() {
// Info-tize it!
if ( f.settings.mod_card_info ) {
var info = document.createElement('div'),
var info = utils.createElement('div', 'info channel-stats'),
after = el.querySelector('h3.name');
if ( after ) {
el.classList.add('ffz-has-info');
info.className = 'info channel-stats';
after.parentElement.insertBefore(info, after.nextSibling);
this.ffzRebuildInfo();
}
@ -756,8 +752,7 @@ FFZ.prototype.setup_mod_card = function() {
// Additional Buttons
if ( is_mod && f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) {
line = document.createElement('div');
line.className = 'extra-interface interface clearfix';
line = utils.createElement('div', 'extra-interface interface clearfix');
var cmds = {},
add_btn_click = function(cmd) {
@ -884,8 +879,7 @@ FFZ.prototype.setup_mod_card = function() {
},
btn_make = function(timeout) {
var btn = document.createElement('button')
btn.className = 'button ffz-no-bg';
var btn = utils.createElement('button', 'button ffz-no-bg');
btn.innerHTML = utils.duration_string(timeout);
btn.title = "Timeout User for " + utils.number_commas(timeout) + " Second" + (timeout != 1 ? "s" : "");
@ -902,13 +896,10 @@ FFZ.prototype.setup_mod_card = function() {
if ( f.settings.mod_card_durations && f.settings.mod_card_durations.length ) {
// Extra Moderation
line = document.createElement('div');
line.className = 'extra-interface interface clearfix';
line = utils.createElement('div', 'extra-interface interface clearfix');
line.appendChild(btn_make(1));
var s = document.createElement('span');
s.className = 'right';
var s = utils.createElement('span', 'right');
line.appendChild(s);
for(var i=0; i < f.settings.mod_card_durations.length; i++)
@ -943,8 +934,7 @@ FFZ.prototype.setup_mod_card = function() {
ban_btn.setAttribute('title', '(B)an User');
// Unban Button
var unban_btn = document.createElement('button');
unban_btn.className = 'unban button button--icon-only light';
var unban_btn = utils.createElement('button', 'unban button button--icon-only light');
unban_btn.innerHTML = '<figure class="icon">' + CHECK + '</figure>';
unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User";
@ -970,7 +960,7 @@ FFZ.prototype.setup_mod_card = function() {
// Follow Button
var follow_button = el.querySelector(".interface > .follow-button");
var follow_button = el.querySelector(".follow-button");
if ( follow_button )
jQuery(follow_button).tipsy({title: function() { return follow_button.classList.contains('is-following') ? "Unfollow" : "Follow"}});
@ -986,8 +976,7 @@ FFZ.prototype.setup_mod_card = function() {
jQuery(msg_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
var real_msg = document.createElement('button');
real_msg.className = 'message-button button button--icon-only message html-tooltip';
var real_msg = utils.createElement('button', 'message-button button button--icon-only message html-tooltip');
real_msg.innerHTML = '<figure class="icon">' + MESSAGE + '</figure>';
real_msg.title = "Message User";
@ -1000,8 +989,7 @@ FFZ.prototype.setup_mod_card = function() {
// Alias Button
var alias_btn = document.createElement('button');
alias_btn.className = 'alias button button--icon-only html-tooltip';
var alias_btn = utils.createElement('button', 'alias button button--icon-only html-tooltip');
alias_btn.innerHTML = '<figure class="icon">' + constants.EDIT + '</figure>';
alias_btn.title = "Set Alias";
@ -1057,12 +1045,6 @@ FFZ.prototype.setup_mod_card = function() {
}});
el.focus();
} catch(err) {
try {
f.error("ModerationCardView didInsertElement: " + err);
} catch(err) { }
}
},
ffzReposition: function() {
@ -1104,8 +1086,7 @@ FFZ.prototype.setup_mod_card = function() {
history = el && el.querySelector('.chat-history');
if ( ! history ) {
history = document.createElement('ul');
history.className = 'interface clearfix chat-history';
history = utils.createElement('ul', 'interface clearfix chat-history');
el.appendChild(history);
} else {
history.classList.remove('loading');
@ -1118,7 +1099,7 @@ FFZ.prototype.setup_mod_card = function() {
if ( ! success )
return;
f.parse_history(data, null, room_id, delete_links, tmiSession);
f.parse_history(data, null, null, room_id, delete_links, tmiSession);
var i = data.length,
was_at_top = history && history.scrollTop >= (history.scrollHeight - history.clientHeight),
@ -1211,10 +1192,9 @@ FFZ.prototype.setup_mod_card = function() {
logs.innerHTML = '';
} else {
logs = document.createElement('ul');
back = document.createElement('button');
logs = utils.createElement('ul', 'interface clearfix chat-history adjacent-history');
back = utils.createElement('button', 'button ffz-no-bg back-button');
back.className = 'button ffz-no-bg back-button';
back.innerHTML = '&laquo; Back';
back.addEventListener('click', function() {
@ -1222,12 +1202,10 @@ FFZ.prototype.setup_mod_card = function() {
back.parentElement.removeChild(back);
history.classList.remove('hidden');
});
logs.className = 'interface clearfix chat-history adjacent-history';
}
f.parse_history(data, null, room_id, delete_links, tmiSession, function(msg) {
f.parse_history(data, null, null, room_id, delete_links, tmiSession, function(msg) {
msg.from_server = true;
var line_time = line.date.getTime() - (line.from_server ? 0 : (f._ws_server_offset || 0)),
@ -1276,7 +1254,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
style = '', colored = '';
if ( helpers && helpers.getTime )
out.push('<span class="timestamp float-left">' + helpers.getTime(msg.date) + '</span>');
out.push('<span class="timestamp">' + helpers.getTime(msg.date) + '</span>');
var alias = this.aliases[msg.from],
@ -1284,7 +1262,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
if ( show_from ) {
// Badges
out.push('<span class="badges float-left">');
out.push('<span class="badges">');
out.push(this.render_badges(this.get_line_badges(msg, false)));
out.push('</span>');
@ -1349,6 +1327,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
l_el.setAttribute('data-room', msg.room);
l_el.setAttribute('data-sender', msg.from);
l_el.setAttribute('data-id', msg.tags && msg.tags.id);
l_el.setAttribute('data-deleted', msg.deleted || false);
l_el.innerHTML = out.join("");

View file

@ -18,12 +18,6 @@ FFZ.settings_info.player_stats = {
help: "Display your current stream latency (how far behind the broadcast you are) under the player, with a few useful statistics in a tooltip.",
on_update: function(val) {
for(var key in this.players) {
var player = this.players[key];
if ( player && player.player && player.player.ffzSetStatsEnabled )
player.player.ffzSetStatsEnabled(val || player.player.ffz_stats);
}
if ( ! this._cindex )
return;
@ -79,36 +73,7 @@ FFZ.prototype.setup_player = function() {
this.players = {};
var Player2 = utils.ember_resolve('component:twitch-player2');
if ( ! Player2 )
return this.log("Unable to find twitch-player2 component.");
this.log("Hooking HTML5 Player UI.");
this._modify_player(Player2)
try {
Player2.create().destroy();
} catch(err) { }
// Modify all existing players.
var views = utils.ember_views();
for(var key in views) {
if ( ! views.hasOwnProperty(key) )
continue;
var view = views[key];
if ( !(view instanceof Player2) )
continue;
this.log("Manually updating existing Player instance.", view);
try {
this._modify_player(view);
view.ffzInit();
} catch(err) {
this.error("Player2 setup ffzInit: " + err);
}
}
this.update_views('component:twitch-player2', this.modify_twitch_player);
}
@ -116,29 +81,22 @@ FFZ.prototype.setup_player = function() {
// Component
// ---------------
FFZ.prototype._modify_player = function(player) {
var f = this,
update_stats = function() {
f._cindex && f._cindex.ffzUpdatePlayerStats();
};
FFZ.prototype.modify_twitch_player = function(player) {
var f = this;
utils.ember_reopen_view(player, {
ffz_init: function() {
var id = this.get('channel.id');
f.players[id] = this;
player.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("Player2 didInsertElement: " + err);
}
var player = this.get('player');
if ( player )
this.ffzPostPlayer();
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("Player2 willClearRender: " + err);
}
this._super();
ffz_destroy: function() {
var id = this.get('channel.id');
if ( f.players[id] === this )
f.players[id] = undefined;
},
postPlayerSetup: function() {
@ -150,24 +108,21 @@ FFZ.prototype._modify_player = function(player) {
}
},
ffzInit: function() {
var id = this.get('channel.id');
f.players[id] = this;
ffzRecreatePlayer: function() {
var player = this.get('player'),
theatre = player && player.getTheatre();
var player = this.get('player');
// Tell the player to destroy itself.
if ( player )
this.ffzPostPlayer();
},
player.destroy();
ffzTeardown: function() {
var id = this.get('channel.id');
if ( f.players[id] === this )
f.players[id] = undefined;
// Break down everything left over from that player.
this.$('#video-1').html('');
Mousetrap.unbind(['alt+x', 'alt+t', 'esc']);
this.set('player', null);
if ( this._ffz_stat_interval ) {
clearInterval(this._ffz_stat_interval);
this._ffz_stat_interval = null;
}
// Now, let Twitch create a new player as usual.
Ember.run.next(this.insertPlayer.bind(this));
},
ffzPostPlayer: function() {
@ -175,74 +130,29 @@ FFZ.prototype._modify_player = function(player) {
if ( ! player )
return;
// Make the stats window draggable and fix the button.
var stats = this.$('.player .js-playback-stats');
stats.draggable({cancel: 'li', containment: 'parent'});
// Only set up the stats hooks if we need stats.
var has_video = false;
try {
has_video = player.getVideo();
} catch(err) {
f.error("Player2 ffzPostPlayer: getVideo: " + err);
}
if ( ! has_video )
this.ffzInitStats();
},
ffzInitStats: function() {
if ( this.get('ffzStatsInitialized') )
return;
// Add an option to the menu to recreate the player.
var t = this,
player = this.get('player');
el = this.$('.player-menu .player-menu__item--stats')[0],
container = el && el.parentElement;
if ( ! player )
return;
if ( el && ! container.querySelector('.js-player-reset') ) {
var btn_link = utils.createElement('a', 'player-text-link js-player-reset', 'Reset Player'),
btn = utils.createElement('p', 'player-menu__item player-menu__item--reset', btn_link);
this.set('ffzStatsInitialized', true);
btn_link.tabindex = '-1';
btn_link.href = '#';
// Make it so stats can no longer be disabled if we want them.
if ( player.setStatsEnabled ) {
player.ffzSetStatsEnabled = player.setStatsEnabled;
try {
player.ffz_stats = player.getStatsEnabled();
} catch(err) {
// Assume stats are off.
f.log("Player2 ffzInitStats: getStatsEnabled still doesn't work.");
player.ffz_stats = false;
}
btn_link.addEventListener('click', function(e) {
t.ffzRecreatePlayer();
e.preventDefault();
return false;
});
player.setStatsEnabled = function(e, s) {
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);
}
return out;
}
this._ffz_stat_interval = setInterval(function() {
if ( f.settings.player_stats || player.ffz_stats ) {
player.ffzSetStatsEnabled(false);
player.ffzSetStatsEnabled(true);
}
}, 5000);
}
if ( f.settings.player_stats && ( ! player.setStatsEnabled || ! player.ffz_stats ) ) {
this._ffz_player_stats_initialized = true;
player.addEventListener('statschange', update_stats);
player.ffzSetStatsEnabled(true);
container.insertBefore(btn, el.nextSibling);
}
}
});

View file

@ -50,7 +50,8 @@ FFZ.prototype.setup_room = function() {
if ( RC ) {
var orig_ban = RC._actions.banUser,
orig_to = RC._actions.timeoutUser;
orig_to = RC._actions.timeoutUser,
orig_show = RC._actions.showModOverlay;
RC._actions.banUser = function(e) {
orig_ban.call(this, e);
@ -62,12 +63,15 @@ FFZ.prototype.setup_room = function() {
this.get("model").clearMessages(e.user, null, true);
}
RC._actions.showModOverlay = function(e) {
var Channel = utils.ember_resolve('model:channel');
if ( ! Channel )
return;
var chan = Channel.find({id: e.sender});
RC._actions.showModOverlay = function(e) {
var Channel = utils.ember_resolve('model:deprecated-channel'),
chan = Channel && Channel.find && Channel.find({id: e.sender});
if ( ! chan ) {
f.log("Error opening mod card. model:deprecated-channel does not exist or does not have find!");
return orig_show.call(this, e);
}
// Don't try loading the channel if it's already loaded. Don't make mod cards
// refresh the channel page when you click the broadcaster, basically.
@ -111,33 +115,7 @@ FFZ.prototype.setup_room = function() {
}
this.log("Hooking the Ember Room view.");
var RoomView = utils.ember_resolve('view:room');
this._modify_rview(RoomView);
// For some reason, this doesn't work unless we create an instance of the
// room view and then destroy it immediately.
try {
RoomView.create().destroy();
} catch(err) { }
// Modify all existing Room views.
var views = utils.ember_views();
for(var key in views) {
if ( ! views.hasOwnProperty(key) )
continue;
var view = views[key];
if ( !(view instanceof RoomView) )
continue;
this.log("Manually updating existing Room view.", view);
try {
view.ffzInit();
} catch(err) {
this.error("RoomView setup ffzInit: " + err);
}
}
this.update_views('view:room', this.modify_room_view);
}
@ -145,29 +123,10 @@ FFZ.prototype.setup_room = function() {
// View Customization
// --------------------
FFZ.prototype._modify_rview = function(view) {
FFZ.prototype.modify_room_view = function(view) {
var f = this;
view.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("RoomView didInsertElement: " + err);
}
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("RoomView willClearRender: " + err);
}
this._super();
},
ffzInit: function() {
utils.ember_reopen_view(view, {
ffz_init: function() {
f._roomv = this;
this.ffz_frozen = false;
@ -196,6 +155,34 @@ FFZ.prototype._modify_rview = function(view) {
var controller = this.get('controller');
if ( controller ) {
controller.reopen({
calcRecipientEligibility: function(e) {
// Because this doesn't work properly with multiple channel rooms
// by default, do it ourselves.
var id = controller.get('model.roomProperties._id'),
update = function(data) {
if ( controller.isDestroyed || controller.get('model.roomProperties._id') !== id )
return;
controller.set('model._ffz_bits_eligibility', data);
controller.set('isRecipientBitsIneligible', ! data.eligible);
controller.set('isBitsHelperShown', data.eligible);
controller.set('minimumBits', data.minBits);
if ( ! data.eligible )
controller.set('isBitsTooltipActive', false);
};
if ( id === undefined )
return;
var data = controller.get('model._ffz_bits_eligibility');
if ( data === undefined )
controller.get('bits').loadRecipientEligibility(id).then(update);
else
update(data);
},
submitButtonText: function() {
if ( this.get("model.isWhisperMessage") && this.get("model.isWhispersEnabled") )
return i18n("Whisper");
@ -214,7 +201,7 @@ FFZ.prototype._modify_rview = function(view) {
}
},
ffzTeardown: function() {
ffz_destroy: function() {
if ( f._roomv === this )
f._roomv = undefined;
@ -280,7 +267,7 @@ FFZ.prototype._modify_rview = function(view) {
btn.classList.toggle('ffz-banned', (room && room.get('ffz_banned') || false));
}
var badge, id, info, vis_count = 0;
var badge, id, info, vis_count = 0, label;
for(var i=0; i < STATUS_BADGES.length; i++) {
info = STATUS_BADGES[i];
id = 'ffz-stat-' + info[0];
@ -289,13 +276,18 @@ FFZ.prototype._modify_rview = function(view) {
if ( typeof visible === "string" )
visible = visible === "1";
label = typeof info[3] === "function" ? info[3].call(f, room) : undefined;
if ( ! badge ) {
badge = utils.createElement('span', 'ffz room-state stat float-right', info[0].charAt(0).toUpperCase() + '<span>' + info[0].substr(1).toUpperCase() + '</span>');
badge = utils.createElement('span', 'ffz room-state stat float-right', (label || info[0]).charAt(0).toUpperCase() + '<span>' + (label || info[0]).substr(1).toUpperCase() + '</span>');
badge.id = id;
jQuery(badge).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'se')});
cont.appendChild(badge);
}
if ( label )
badge.innerHTML = (label || info[0]).charAt(0).toUpperCase() + '<span>' + (label || info[0]).substr(1).toUpperCase() + '</span>';
badge.title = typeof info[2] === "function" ? info[2].call(f, room) : info[2];
badge.classList.toggle('hidden', ! visible);
if ( visible )
@ -494,7 +486,6 @@ FFZ.prototype._modify_rview = function(view) {
warning.classList.remove('hidden');
},
ffzUnwarnPaused: function() {
var el = this.get('element'),
warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
@ -502,7 +493,6 @@ FFZ.prototype._modify_rview = function(view) {
if ( warning )
warning.classList.add('hidden');
}
});
}
@ -640,12 +630,13 @@ FFZ.ffz_commands.help.help = "Usage: /ffz help [command]\nList available command
FFZ.prototype.update_room_important = function(id, controller) {
var Chat = controller || utils.ember_lookup('controller:chat'),
current_room = Chat && Chat.get('currentChannelRoom'),
room = this.rooms[id];
if ( ! room )
return;
room.important = (Chat && room.room && Chat.get('currentChannelRoom') === room.room) || (room.room && room.room.get('isGroupRoom')) || (this.settings.pinned_rooms.indexOf(id) !== -1);
room.important = (room.room && current_room === room.room) || (current_room && current_room.ffz_host_target === id) || (room.room && room.room.get('isGroupRoom')) || (this.settings.pinned_rooms.indexOf(id) !== -1);
};
@ -798,7 +789,7 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
before = first_existing.date && first_existing.date.getTime();
this.parse_history(data, null, room_id, delete_links, tmiSession, function(msg) {
this.parse_history(data, null, null, room_id, delete_links, tmiSession, function(msg) {
if ( from_server )
msg.from_server = true;
@ -815,7 +806,7 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
return true;
// Display the Ban Reason if we're a moderator or that user.
if ( msg.tags['ban-reason'] && is_mine || r.get('isModeratorOrHigher') ) {
if ( msg.tags['ban-reason'] && (is_mine || r.get('isModeratorOrHigher')) ) {
msg.message = msg.message.substr(0, msg.message.length - 1) + ' with reason: ' + msg.tags['ban-reason'];
msg.cachedTokens = [utils.sanitize(msg.message)];
}
@ -834,6 +825,12 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
if ( ! first_inserted )
first_inserted = msg;
// Store the message ID for this message, of course.
var msg_id = msg.tags && msg.tags.id,
ids = r.ffz_ids = r.ffz_ids || {};
if ( msg_id && ! ids[msg_id] )
ids[msg_id] = msg;
messages.unshiftObject(msg);
inserted += 1;
@ -870,6 +867,11 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
if ( r.shouldShowMessage(msg) ) {
messages.insertAt(inserted, msg);
while ( messages.length > buffer_size ) {
// Remove this message from the ID tracker.
var m = messages.get(0);
if ( m.tags && m.tags.id && r.ffz_ids && r.ffz_ids[m.tags.id] )
delete r.ffz_ids[m.tags.id];
messages.removeAt(0);
removed++;
}
@ -1042,6 +1044,7 @@ FFZ.prototype._modify_room = function(room) {
try {
f.add_room(this.id, this);
this.set("ffz_chatters", {});
this.set("ffz_ids", this.get('ffz_ids') || {});
} catch(err) {
f.error("add_room: " + err);
}
@ -1063,6 +1066,7 @@ FFZ.prototype._modify_room = function(room) {
if ( user ) {
var duration = Infinity,
reason = undefined,
msg_id = undefined,
current_user = f.get_user(),
is_me = current_user && current_user.login === user;
@ -1077,6 +1081,18 @@ FFZ.prototype._modify_room = function(room) {
reason = tags['ban-reason'];
// Is there a UUID on the end of the ban reason?
if ( reason ) {
var match = constants.UUID_TEST.exec(reason);
if ( match ) {
msg_id = match[1];
reason = reason.substr(0, reason.length - match[0].length);
if ( ! reason.length )
reason = undefined;
}
}
// If we were banned, set the state and update the UI.
if ( is_me ) {
t.set('ffz_banned', true);
@ -1098,6 +1114,44 @@ FFZ.prototype._modify_room = function(room) {
t.ffzRecentlyBanned.shift();
// Are we deleting a specific message?
if ( msg_id && this.ffz_ids ) {
var msg = this.ffz_ids[msg_id];
if ( msg && msg.from === user ) {
msg.ffz_deleted = true;
if ( ! f.settings.prevent_clear )
msg.deleted = true;
if ( f.settings.remove_deleted )
if ( msg.pending )
msg.removed = true;
else {
var msgs = t.get('messages'),
total = msgs.get('length'),
i = total;
while(i--) {
var msg = msgs.get(i);
if ( msg.tags && msg.tags.id === msg_id ) {
msgs.removeAt(i);
delete this.ffz_ids[msg_id];
break;
}
}
}
if ( msg._line ) {
Ember.propertyDidChange(msg._line, 'msgObject.ffz_deleted');
Ember.propertyDidChange(msg._line, 'msgObject.deleted');
}
} else if ( msg.from !== user )
f.log("Banned Message ID #" + msg_id + " not owned by: " + user);
else
f.log("Banned Message ID #" + msg_id + " not found in chat.");
} else {
// Delete all messages from this user / chat.
// Delete Visible Messages
var msgs = t.get('messages'),
total = msgs.get('length'),
@ -1106,9 +1160,12 @@ FFZ.prototype._modify_room = function(room) {
while(i--) {
var msg = msgs.get(i);
if ( msg.from === user ) {
if ( f.settings.remove_deleted ) {
// Remove this message from the ID tracker.
if ( msg.tags && msg.tags.id && this.ffz_ids && this.ffz_ids[msg.tags.id] )
delete this.ffz_ids[msg.tags.id];
msgs.removeAt(i);
removed++;
continue;
@ -1133,6 +1190,7 @@ FFZ.prototype._modify_room = function(room) {
msg.removed = f.settings.remove_deleted;
}
}
}
// Now we need to see about displaying a ban notice.
@ -1164,6 +1222,7 @@ FFZ.prototype._modify_room = function(room) {
date: now,
ffz_ban_target: user,
reasons: reason ? [reason] : [],
msg_ids: msg_id ? [msg_id] : [],
durations: [duration],
end_time: end_time,
timeouts: 1,
@ -1176,6 +1235,9 @@ FFZ.prototype._modify_room = function(room) {
this.addMessage(msg);
} else {
if ( msg_id && last_ban.msg_ids.indexOf(msg_id) === -1 )
last_ban.msg_ids.push(msg_id);
if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason);
@ -1205,6 +1267,9 @@ FFZ.prototype._modify_room = function(room) {
last_ban = null;
if ( last_ban ) {
if ( msg_id && last_ban.msg_ids.indexOf(msg_id) === -1 )
last_ban.msg_ids.push(msg_id);
if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason);
@ -1223,6 +1288,7 @@ FFZ.prototype._modify_room = function(room) {
date: now,
ffz_ban_target: user,
reasons: reason ? [reason] : [],
msg_ids: msg_id ? [msg_id] : [],
durations: [duration],
end_time: end_time,
timeouts: 1,
@ -1256,8 +1322,17 @@ FFZ.prototype._modify_room = function(room) {
len = messages.get("length"),
limit = this.get("messageBufferSize");
if ( len > limit )
messages.removeAt(0, len - limit);
if ( len > limit ) {
var to_remove = len - limit;
for(var i = 0; i < to_remove; i++) {
// Remove this message from the ID tracker.
var msg = messages.get(i);
if ( msg.tags && msg.tags.id && this.ffz_ids && this.ffz_ids[msg.tags.id] )
delete this.ffz_ids[msg.tags.id];
}
messages.removeAt(0, to_remove);
}
},
// Artificial chat delay
@ -1267,6 +1342,7 @@ FFZ.prototype._modify_room = function(room) {
this.ffzPending = [];
var now = msg.time = Date.now();
msg.pending = true;
this.ffzPending.push(msg);
this.ffzSchedulePendingFlush(now);
@ -1320,12 +1396,18 @@ FFZ.prototype._modify_room = function(room) {
for (var i = 0, l = this.ffzPending.length; i < l; i++) {
var msg = this.ffzPending[i];
if ( msg.removed )
if ( msg.removed ) {
// Don't keep this message ID around.
var msg_id = msg && msg.tags && msg.tags.id;
if ( msg_id && this.ffz_ids && this.ffz_ids[msg_id] )
delete this.ffz_ids[msg_id];
continue;
}
if ( f.settings.chat_delay !== 0 && (f.settings.chat_delay + msg.time > now) )
break;
msg.pending = false;
this.ffzActualPushMessage(msg);
}
@ -1367,10 +1449,16 @@ FFZ.prototype._modify_room = function(room) {
}
},
onMessage: function(msg) {
// We do our own batching. With blackjack, and hookers. You know what? Forget the batching.
this.addMessage(msg);
},
addMessage: function(msg) {
if ( msg ) {
var is_resub = msg.tags && msg.tags['msg-id'] === 'resub',
room_id = this.get('id');
room_id = this.get('id'),
msg_id = msg.tags && msg.tags.id;
// Ignore resubs in other rooms.
if ( is_resub && ! f.settings.hosted_sub_notices && (msg.tags['room-id'] != this.get('roomProperties._id') || HOSTED_SUB.test(msg.tags['system-msg'])) )
@ -1515,6 +1603,13 @@ FFZ.prototype._modify_room = function(room) {
if ( f._chat_filters[i](msg) === false )
return;
// We're past the last return, so store the message
// now that we know we're keeping it.
if ( msg_id ) {
var ids = this.ffz_ids = this.ffz_ids || {};
ids[msg_id] = msg;
}
// 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/");

View file

@ -12,16 +12,10 @@ var FFZ = window.FrankerFaceZ,
// ---------------------
FFZ.prototype.setup_vod_chat = function() {
var f = this,
VRC = utils.ember_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 = utils.ember_lookup('service:vod-chat-service');
var f = this,
VODService = utils.ember_lookup('service:vod-chat-service');
if ( VODService )
VODService.reopen({
messageBufferSize: f.settings.scrollback_length,
@ -50,56 +44,59 @@ FFZ.prototype.setup_vod_chat = function() {
else
f.error("Unable to locate VOD Chat Service.");
// Get the VOD Chat Display
var VODChat = utils.ember_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 = utils.ember_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);
}
}
}
this.update_views('component:vod-right-column', this.modify_vod_right_column);
this.update_views('view:vod', this.modify_vod_view);
this.update_views('component:vod-chat-display', this.modify_vod_chat_display);
}
FFZ.prototype._modify_vod_right_column = function(component) {
FFZ.prototype.modify_vod_view = function(view) {
var f = this;
utils.ember_reopen_view(view, {
ffz_init: function() {
f._vodv = this;
component.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("VODRightColumn didInsertElement: " + err);
var channel_id = this.get('context.channel.name');
if ( f.settings.auto_theater ) {
var player = f.players && f.players[channel_id] && f.players[channel_id].get('player');
if ( player )
player.setTheatre(true);
}
// Listen to scrolling.
this._ffz_scroller = this.ffzOnScroll.bind(this);
jQuery(this.get('element')).parents('.tse-scroll-content').on('scroll', this._ffz_scroller);
},
ffz_destroy: function() {
if ( f._vodv === this )
f._vodv = null;
if ( this._ffz_scroller ) {
jQuery(this.get('element')).parents('.tse-scroll-content').off('scroll', this._ffz_scroller);
this._ffz_scroller = null;
}
},
ffzInit: function() {
ffzOnScroll: function(event) {
// When we scroll past the bottom of the player, do stuff!
var top = event && event.target && event.target.scrollTop,
height = this.get('layout.playerSize.1');
if ( ! top )
top = jQuery(this.get('element')).parents('.tse-scroll-content').scrollTop();
document.body.classList.toggle('ffz-small-player', f.settings.small_player && top >= height);
}
});
}
FFZ.prototype.modify_vod_right_column = function(component) {
var f = this;
utils.ember_reopen_view(component, {
ffz_init: function() {
if ( f.settings.dark_twitch ) {
var el = this.get('element'),
cont = el && el.querySelector('.chat-container');
@ -112,29 +109,11 @@ FFZ.prototype._modify_vod_right_column = function(component) {
}
FFZ.prototype._modify_vod_chat_display = function(component) {
FFZ.prototype.modify_vod_chat_display = function(component) {
var f = this,
VODService = utils.ember_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();
},
utils.ember_reopen_view(component, {
_prepareToolTips: function() {
this.$(".tooltip").tipsy({
live: true,
@ -142,7 +121,7 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
})
},
ffzInit: function() {
ffz_init: function() {
f._vodc = this;
// Load the room, if necessary
@ -153,22 +132,9 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
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() {
ffz_destroy: function() {
if ( f._vodc === this )
f._vodc = undefined;
@ -232,14 +198,6 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
}
},
/*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;

88
src/ember/wrapper.js Normal file
View file

@ -0,0 +1,88 @@
var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
constants = require("../constants");
// --------------------
// Initialization
// --------------------
FFZ.prototype.setup_ember_wrapper = function() {
this._views_to_update = [];
this._ember_finalized = false;
}
FFZ.prototype.update_views = function(klass, modifier, if_not_exists) {
var original_klass;
if ( typeof klass === 'string' ) {
original_klass = klass;
klass = utils.ember_resolve(klass);
if ( ! klass && if_not_exists ) {
if ( typeof if_not_exists === "function" )
if_not_exists.call(this, klass, modifier);
else {
klass = Ember.Component.extend({});
App.__registry__.register(original_klass, klass);
}
}
if ( ! klass ) {
this.error("Unable to locate the Ember " + original_klass);
return false;
}
} else
original_klass = klass.toString();
if ( this._ember_finalized )
this._update_views([[original_klass, klass, modifier]]);
else
this._views_to_update.push([original_klass, klass, modifier]);
return true;
}
FFZ.prototype.finalize_ember_wrapper = function() {
this._ember_finalized = true;
var views = this._views_to_update;
this._views_to_update = null;
this._update_views(views);
}
FFZ.prototype._update_views = function(klasses) {
this.log("Updating Ember classes and instances.", klasses);
// Modify all pending classes and clear them from cache.
for(var i=0; i < klasses.length; i++) {
klasses[i][2].call(this, klasses[i][1]);
try {
klasses[i][1].create().destroy()
} catch(err) {
this.log("There was an error creating and destroying an instance of the Ember class \"" + klasses[i][0] + "\" to clear its cache.", err);
}
}
// Iterate over all existing views and update them as necessary.
var views = utils.ember_views();
for(var view_id in views) {
var view = views[view_id];
if ( ! view )
continue;
for(var i=0; i < klasses.length; i++)
if ( view instanceof klasses[i][1] ) {
try {
if ( ! view.ffz_modified )
klasses[i][2].call(this, view);
(view.ffz_update || view.ffz_init).call(view);
} catch(err) {
this.error("An error occured when updating an existing Ember instance of: " + klasses[i][0], err);
}
break;
}
}
}

View file

@ -1,7 +1,6 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
build_css = function(emote) {
if ( ! emote.margins && ! emote.css )
return "";
@ -50,11 +49,15 @@ var API = FFZ.API = function(instance, name, icon, version) {
this.global_sets = [];
this.default_sets = [];
this.badges = {};
this.users = {};
this.chat_filters = [];
this.on_room_callbacks = [];
this.name = name || ("Extension#" + this.id);
this.name_key = this.name.replace(/[^A-Z0-9_\-]/g, '').toLowerCase();
this.icon = icon || null;
this.version = version || null;
@ -386,13 +389,75 @@ API.prototype.unregister_room_set = function(room_id, id) {
}
// -----------------------
// Badge APIs
// -----------------------
API.prototype.add_badge = function(badge_id, badge) {
var exact_id = this.id + '-' + badge_id,
real_badge = {
id: exact_id,
source_ext: this.id,
source_id: badge_id,
alpha_image: badge.alpha_image,
color: badge.color || "transparent",
no_invert: badge.no_invert,
invert_invert: badge.invert_invert,
css: badge.css,
image: badge.image,
name: badge.name,
title: badge.title,
slot: badge.slot,
visible: badge.visible,
replaces: badge.replaces,
replaces_type: badge.replaces_type
};
this.ffz.badges[exact_id] = this.badges[badge_id] = real_badge;
utils.update_css(this.ffz._badge_style, exact_id, utils.badge_css(real_badge));
}
API.prototype.remove_badge = function(badge_id) {
var exact_id = this.id + '-' + badge_id;
this.ffz.badges[exact_id] = this.badges[badge_id] = undefined;
utils.update_css(this.ffz._badge_style, exact_id);
}
// -----------------------
// User Modifications
// -----------------------
API.prototype.user_add_set = function(user_name, set_id) {
var user = this.users[user_name] = this.users[user_name] || {},
ffz_user = this.ffz.users[user_name] = this.ffz.users[user_name] || {},
API.prototype.user_add_badge = function(username, slot, badge_id) {
var user = this.users[username] = this.users[username] || {},
ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
badges = user.badges = user.badges || {},
ffz_badges = ffz_user.badges = ffz_user.badges || {},
exact_id = this.id + '-' + badge_id,
badge = {id: exact_id};
badges[slot] = ffz_badges[slot] = badge;
}
API.prototype.user_remove_badge = function(username, slot) {
var user = this.users[username] = this.users[username] || {},
ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
badges = user.badges = user.badges || {},
ffz_badges = ffz_user.badges = ffz_user.badges || {};
badges[slot] = ffz_badges[slot] = null;
}
API.prototype.user_add_set = function(username, set_id) {
var user = this.users[username] = this.users[username] || {},
ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
emote_sets = user.sets = user.sets || [],
ffz_sets = ffz_user.sets = ffz_user.sets || [],
@ -407,14 +472,14 @@ API.prototype.user_add_set = function(user_name, set_id) {
// Update tab completion.
var user = this.ffz.get_user();
if ( this.ffz._inputv && user && user.login === user_name )
if ( this.ffz._inputv && user && user.login === username )
Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
}
API.prototype.user_remove_set = function(user_name, set_id) {
var user = this.users[user_name],
ffz_user = this.ffz.users[user_name],
API.prototype.user_remove_set = function(username, set_id) {
var user = this.users[username],
ffz_user = this.ffz.users[username],
emote_sets = user && user.sets,
ffz_sets = ffz_user && ffz_user.sets,
@ -431,7 +496,7 @@ API.prototype.user_remove_set = function(user_name, set_id) {
// Update tab completion.
var user = this.ffz.get_user();
if ( this.ffz._inputv && user && user.login === user_name )
if ( this.ffz._inputv && user && user.login === username )
Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
}
@ -455,7 +520,6 @@ API.prototype.unregister_chat_filter = function(filter) {
this.ffz._chat_filters.splice(ind, 1);
}
// -----------------------
// Channel Callbacks
// -----------------------

View file

@ -96,6 +96,10 @@ FFZ.prototype.setup_bttv = function(delay) {
this.toggle_style('chat-hc-background');*/
this.toggle_style('chat-colors-gray');
this.toggle_style('badges-rounded');
this.toggle_style('badges-circular');
this.toggle_style('badges-blank');
this.toggle_style('badges-circular-small');
this.toggle_style('badges-transparent');
this.toggle_style('badges-sub-notice');
this.toggle_style('badges-sub-notice-on');
@ -114,12 +118,15 @@ FFZ.prototype.setup_bttv = function(delay) {
}
// Send Message Behavior
var original_send = BetterTTV.chat.helpers.sendMessage, f = this;
BetterTTV.chat.helpers.sendMessage = function(message) {
var f = this,
BC = BetterTTV.chat,
original_send = BC.helpers.sendMessage;
BC.helpers.sendMessage = function(message) {
var cmd = message.split(' ', 1)[0].toLowerCase();
if ( cmd === "/ffz" )
f.run_ffz_command(message.substr(5), BetterTTV.chat.store.currentRoom);
if ( cmd === '/ffz' )
f.run_ffz_command(message.substr(5), BC.store.currentRoom);
else
return original_send(message);
}
@ -127,9 +134,10 @@ FFZ.prototype.setup_bttv = function(delay) {
// Ugly Hack for Current Room, as this is stripped out before we get to
// the actual privmsg renderer.
var original_handler = BetterTTV.chat.handlers.onPrivmsg,
var original_handler = BC.handlers.onPrivmsg,
received_room;
BetterTTV.chat.handlers.onPrivmsg = function(room, data) {
BC.handlers.onPrivmsg = function(room, data) {
received_room = room;
var output = original_handler(room, data);
received_room = null;
@ -138,39 +146,48 @@ FFZ.prototype.setup_bttv = function(delay) {
// Message Display Behavior
var original_privmsg = BetterTTV.chat.templates.privmsg;
BetterTTV.chat.templates.privmsg = function(highlight, action, server, isMod, data) {
var original_privmsg = BC.templates.privmsg;
BC.templates.privmsg = function(data, opts) {
try {
opts = opts || {};
// Handle badges.
f.bttv_badges(data);
// Now, do everything else manually because things are hard-coded.
return '<div class="chat-line'+(highlight?' highlight':'')+(action?' action':'')+(server?' admin':'')+'" data-sender="'+(data.sender||"").toLowerCase()+'" data-room="'+received_room+'">'+
BetterTTV.chat.templates.timestamp(data.time)+' '+
(isMod?BetterTTV.chat.templates.modicons():'')+' '+
BetterTTV.chat.templates.badges(data.badges)+
BetterTTV.chat.templates.from(data.nickname, data.color)+
BetterTTV.chat.templates.message(data.sender, data.message, data.emotes, action?data.color:false)+
return '<div class="chat-line'+(opts.highlight?' highlight':'')+(opts.action?' action':'')+(opts.server?' admin':'')+'" data-sender="'+(data.sender||"").toLowerCase()+'" data-room="'+received_room+'">'+
BC.templates.timestamp(data.time)+' '+
(opts.isMod ? BC.templates.modicons():'')+' '+
BC.templates.badges(data.badges)+
BC.templates.from(data.nickname, data.color)+
BC.templates.message(data.sender, data.message, {
emotes: data.emotes,
colored: (opts.action && !opts.highlight) ? data.color : false,
bits: data.bits
}) +
'</div>';
} catch(err) {
f.log("Error: ", err);
return original_privmsg(highlight, action, server, isMod, data);
return original_privmsg(data, opts);
}
}
// Whispers too!
var original_whisper = BetterTTV.chat.templates.whisper;
BetterTTV.chat.templates.whisper = function(data) {
var original_whisper = BC.templates.whisper;
BC.templates.whisper = function(data) {
try {
// Handle badges.
f.bttv_badges(data);
// Now, do everything else manually because things are hard-coded.
return '<div class="chat-line whisper" data-sender="' + data.sender + '">' +
BetterTTV.chat.templates.timestamp(data.time) + ' ' +
(data.badges && data.badges.length ? BetterTTV.chat.templates.badges(data.badges) : '') +
BetterTTV.chat.templates.whisperName(data.sender, data.receiver, data.from, data.to, data.fromColor, data.toColor) +
BetterTTV.chat.templates.message(data.sender, data.message, data.emotes, false) +
BC.templates.timestamp(data.time) + ' ' +
(data.badges && data.badges.length ? BC.templates.badges(data.badges) : '') +
BC.templates.whisperName(data.sender, data.receiver, data.from, data.to, data.fromColor, data.toColor) +
BC.templates.message(data.sender, data.message, {
emotes: data.emotes,
colored: false
}) +
'</div>';
} catch(err) {
f.log("Error: ", err);
@ -180,22 +197,24 @@ FFZ.prototype.setup_bttv = function(delay) {
// Message Renderer. I had to completely rewrite this method to get it to
// use my replacement emoticonizer.
var original_message = BetterTTV.chat.templates.message,
var original_message = BC.templates.message,
received_sender;
BetterTTV.chat.templates.message = function(sender, message, emotes, colored) {
BC.templates.message = function(sender, message, data) {
try {
colored = colored || false;
var rawMessage = encodeURIComponent(message);
var colored = data.colored || false,
force = data.force || false,
emotes = data.emotes,
rawMessage = encodeURIComponent(message);
if(sender !== 'jtv') {
// Hackilly send our state across.
received_sender = sender;
var tokenizedMessage = BetterTTV.chat.templates.emoticonize(message, emotes);
var tokenizedMessage = BC.templates.emoticonize(message, emotes);
received_sender = null;
for(var i=0; i<tokenizedMessage.length; i++) {
if(typeof tokenizedMessage[i] === 'string') {
tokenizedMessage[i] = BetterTTV.chat.templates.bttvMessageTokenize(sender, tokenizedMessage[i]);
tokenizedMessage[i] = BC.templates.bttvMessageTokenize(sender, tokenizedMessage[i], data.bits);
} else {
tokenizedMessage[i] = tokenizedMessage[i][0];
}
@ -204,7 +223,14 @@ FFZ.prototype.setup_bttv = function(delay) {
message = tokenizedMessage.join(' ');
}
return '<span class="message" '+(colored?'style="color: '+colored+'" ':'')+'data-raw="'+rawMessage+'" data-emotes="'+(emotes ? encodeURIComponent(JSON.stringify(emotes)) : 'false')+'">'+message+'</span>';
var spam = false;
if ( BetterTTV.settings.get('hideSpam') && BC.helpers.isSpammer(sender) && !BC.helpers.isModerator(sender) && !force) {
message = '<span class="deleted">&lt;spam deleted&gt;</span>';
spam = true;
}
return '<span class="message ' + (spam ? 'spam' : '') + '" ' + (colored ? 'style="color: ' + colored + '" ' : '') + 'data-raw="' + rawMessage + '" data-emotes="' + (emotes ? encodeURIComponent(JSON.stringify(emotes)) : 'false') + '">' + message + '</span>';
} catch(err) {
f.log("Error: ", err);
return original_message(sender, message, emotes, colored);
@ -247,8 +273,8 @@ FFZ.prototype.setup_bttv = function(delay) {
}
// Emoticonize
var original_emoticonize = BetterTTV.chat.templates.emoticonize;
BetterTTV.chat.templates.emoticonize = function(message, emotes) {
var original_emoticonize = BC.templates.emoticonize;
BC.templates.emoticonize = function(message, emotes) {
var tokens = original_emoticonize(message, emotes),
room = (received_room || BetterTTV.getChannel()),

View file

@ -1,305 +0,0 @@
var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
utils = require('../utils');
// --------------------
// Initialization
// --------------------
FFZ.prototype.setup_rechat = function() {
if ( this.has_bttv || navigator.userAgent.indexOf('Android') !== -1 )
return;
this._rechat_listening = false;
this.log("Installing ReChat mutation observer.");
var f = this;
this._rechat_observer = new MutationObserver(function(mutations) {
for(var i=0; i < mutations.length; i++) {
var mutation = mutations[i];
if ( mutation.type !== "childList" )
continue;
for(var x=0; x < mutation.addedNodes.length; x++) {
var added = mutation.addedNodes[x];
if ( added.nodeType !== added.ELEMENT_NODE || added.tagName !== "DIV" )
continue;
// Is this a ReChat line?
if ( added.classList.contains('rechat-chat-line') && ! added.classList.contains('ffz-processed') )
f.process_rechat_line(added);
}
}
});
this.log("Starting ReChat check loop.");
this._rechat_interval = setInterval(this.find_rechat.bind(this), 1000);
this.find_rechat();
}
// --------------------
// ReChat Detection
// --------------------
FFZ.prototype.find_rechat = function() {
var el = !this.has_bttv ? document.querySelector('.rechat-chat-line') : null;
if ( ! this._rechat_listening && ! el ) {
// Try darkening a chat container. We don't have chat.
var container = document.querySelector('.chat-container'),
header = container && container.querySelector('.chat-header');
if ( header && header.textContent.indexOf('ReChat') !== -1 ) {
// Look-up dark mode.
var dark_chat = this.settings.dark_twitch;
if ( ! dark_chat ) {
var Settings = utils.ember_lookup('controller:settings'),
model = Settings ? Settings.get('model') : undefined;
dark_chat = model && model.get('darkMode');
}
container.classList.toggle('dark', dark_chat);
jQuery(container).find('.chat-lines').addClass('ffz-scrollbar');
}
return;
}
// If there's no change, don't continue.
if ( !!el === this._rechat_listening )
return;
// If we're no longer listening, stop the observer and quit.
if ( ! el ) {
this._rechat_observer.disconnect();
this._rechat_listening = false;
return;
}
// We're newly listening. Process all existing ReChat chat lines
// and darken the container if required, also enable the observer.
var container = jQuery(el).parents('.chat-container');
if ( ! container.length )
return;
container = container[0];
// Look-up dark mode.
var dark_chat = this.settings.dark_twitch;
if ( ! dark_chat ) {
var Settings = utils.ember_lookup('controller:settings'),
model = Settings ? Settings.get('model') : undefined;
dark_chat = model && model.get('darkMode');
}
container.classList.toggle('dark', dark_chat);
jQuery(container).find('.chat-lines').addClass('ffz-scrollbar');
// Tooltips
jQuery(container).find('.tooltip').tipsy({live: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
//jQuery(container).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
//jQuery(container).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
// Load the room data.
var room_id = el.getAttribute('data-room');
if ( room_id && ! this.rooms[room_id] )
this.load_room(room_id, this._reprocess_rechat.bind(this, container));
// Do stuff.
var lines = container.querySelectorAll('.rechat-chat-line');
for(var i=0; i < lines.length; i++) {
var line = lines[i];
if ( line.classList.contains('ffz-processed') )
continue;
this.process_rechat_line(line);
}
// Start observing.
this._rechat_observer.observe(container, {
childList: true,
subtree: true
});
this._rechat_listening = true;
}
// --------------------
// ReChat Lines
// --------------------
FFZ.prototype._reprocess_rechat = function(container) {
var lines = container.querySelectorAll('.rechat-chat-line');
for(var i=0; i < lines.length; i++)
this.process_rechat_line(lines[i], true);
}
FFZ.prototype.process_rechat_line = function(line, reprocess) {
if ( ! reprocess && line.classList.contains('ffz-processed') )
return;
line.classList.add('ffz-processed');
var f = this,
user_id = line.getAttribute('data-sender'),
room_id = line.getAttribute('data-room'),
Layout = utils.ember_lookup('service:layout'),
Settings = utils.ember_lookup('controller:settings'),
is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('settings.darkMode')),
badges_el = line.querySelector('.badges'),
from_el = line.querySelector('.from'),
message_el = line.querySelector('.message'),
badges = {},
had_badges = !!badges_el,
raw_color = from_el && FFZ.Color.RGB.fromCSS(from_el.style.color),
colors = raw_color && this._handle_color(raw_color),
alias = this.aliases[user_id];
if ( ! badges_el ) {
badges_el = document.createElement('span');
badges_el.className = 'badges float-left';
line.insertBefore(badges_el, from_el || line.firstElementChild);
}
if ( ! reprocess || ! had_badges ) {
// Read existing known badges.
var existing = badges_el.querySelectorAll('.badge');
for(var i=0; i < existing.length; i++) {
var badge = existing[i];
if ( badge.classList.contains('broadcaster') )
badges[0] = {klass: 'broadcaster', title: 'Broadcaster'};
else if ( badge.classList.contains('staff') )
badges[0] = {klass: 'staff', title: 'Staff'};
else if ( badge.classList.contains('admin') )
badges[0] = {klass: 'admin', title: 'Admin'};
else if ( badge.classList.contains('global-moderator') )
badges[0] = {klass: 'global-moderator', title: 'Global Moderator'};
else if ( badge.classList.contains('moderator') )
badges[0] = {klass: 'moderator', title: 'Moderator'};
else if ( badge.classList.contains('subscriber') )
badges[10] = {klass: 'subscriber', title: 'Subscriber'};
else if ( badge.classList.contains('turbo') )
badges[15] = {klass: 'turbo', title: 'Turbo'};
}
if ( user_id && user_id === room_id )
badges[0] = {klass: 'broadcaster', title: 'Broadcaster'};
if ( user_id )
badges = this.get_badges(user_id, room_id, badges, null);
badges_el.innerHTML = this.render_badges(badges);
}
if ( ! reprocess && from_el ) {
from_el.style.fontWeight = "";
if ( colors ) {
from_el.classList.add('has_color');
from_el.style.color = is_dark ? colors[1] : colors[0];
}
if ( alias ) {
from_el.classList.add('ffz-alias');
from_el.title = from_el.textContent;
from_el.textContent = alias;
}
}
if ( ! message_el )
return;
if ( ! reprocess && colors && message_el.style.color ) {
message_el.classList.add('has-color');
message_el.style.color = is_dark ? colors[1] : colors[0];
}
var raw_tokens = line.getAttribute('data-tokens'),
tokens = raw_tokens ? JSON.parse(raw_tokens) : [];
if ( ! raw_tokens ) {
for(var i=0; i < message_el.childNodes.length; i++) {
var node = message_el.childNodes[i];
if ( node.nodeType === node.TEXT_NODE )
tokens.push(node.textContent);
else if ( node.nodeType === node.ELEMENT_NODE ) {
if ( node.tagName === 'IMG' )
tokens.push({
type: "emoticon",
altText: node.alt,
imgSrc: node.src
});
else if ( node.tagName === 'A' )
tokens.push({
type: "link",
isDeleted: false,
isLong: false,
length: node.textContent.length,
link: node.href,
text: node.textContent
});
else if ( node.tagName === 'SPAN' )
tokens.push({
type: "mention",
user: node.textContent,
isOwnMessage: node.classList.contains('mentioning')
});
else {
this.log("Unknown Tag Type: " + node.tagName);
tokens.push({
isRaw: true,
html: node.outerHTML
});
}
} else
this.log("Unknown Node Type Tokenizing Message: " + node.nodeType);
}
}
line.setAttribute('data-tokens', JSON.stringify(tokens));
// Further tokenization~!
if ( this.settings.replace_bad_emotes )
tokens = this.tokenize_replace_emotes(tokens);
tokens = this._remove_banned(tokens);
tokens = this.tokenize_emotes(user_id, room_id, tokens, false);
if ( this.settings.parse_emoji )
tokens = this.tokenize_emoji(tokens);
tokens = this.tokenize_mentions(tokens);
// Check for a mention
if ( ! line.classList.contains('ffz-mentioend') )
for(var i=0; i < tokens.length; i++)
if ( tokens[i].mentionedUser ) {
line.classList.add('ffz-mentioned');
break;
}
// Now, put the content back into the element.
message_el.innerHTML = this.render_tokens(tokens);
// Interactions
jQuery('a.deleted-link', message_el).click(f._deleted_link_click);
jQuery('img.emoticon', message_el).click(function(e) { f._click_emote(e.target, e); });
}

30
src/ext/warpworld.js Normal file
View file

@ -0,0 +1,30 @@
var FFZ = window.FrankerFaceZ;
// --------------------
// Initialization
// --------------------
FFZ.settings_info.warp_world = {
type: "boolean",
value: true,
category: "Channel Metadata",
name: "Warp World <small>(Requires Refresh)</small>",
help: 'Automatically load <a href="https://warp.world" target="_blank">Warp World</a> when viewing a channel that uses Warp World.'
}
FFZ.ws_commands.warp_world = function(data) {
if ( ! data || ! this.settings.warp_world )
return;
// Make sure that Warp World isn't already loaded or loading.
var ww_script = document.querySelector('script#ww_script');
if ( ww_script || window.WarpWorld )
return;
ww_script = document.createElement('script');
ww_script.id = 'ww_script';
ww_script.src = '//cdn.warp.world/twitch_script/main.min.js?_=' + Date.now();
document.head.appendChild(ww_script);
}

View file

@ -37,7 +37,7 @@ FFZ.msg_commands = {};
// Version
var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 216,
major: 3, minor: 5, revision: 247,
toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
}
@ -167,8 +167,9 @@ require('./badges');
require('./tokenize');
//require('./filtering');
require('./ember/wrapper');
require('./ember/router');
require('./ember/bits');
require('./ember/channel');
require('./ember/player');
require('./ember/room');
@ -188,7 +189,6 @@ require('./ember/sidebar');
require('./debug');
//require('./ext/rechat');
require('./ext/betterttv');
require('./ext/emote_menu');
@ -214,6 +214,7 @@ require('./ui/about_page');
require('./commands');
require('./ext/api');
require('./ext/warpworld');
// ---------------
@ -404,6 +405,7 @@ FFZ.prototype.init_ember = function(delay) {
// Initialize all the modules.
this.load_settings();
this.setup_ember_wrapper();
// Start this early, for quick loading.
this.setup_dark();
@ -426,6 +428,7 @@ FFZ.prototype.init_ember = function(delay) {
this.setup_room();
this.setup_vod_chat();
this.setup_line();
this.setup_bits();
this.setup_layout();
this.setup_chatview();
this.setup_conversations();
@ -446,10 +449,13 @@ FFZ.prototype.init_ember = function(delay) {
this.setup_following_count(true);
this.setup_races();
// Do all Ember modification before this point.
this.finalize_ember_wrapper();
this.fix_tooltips();
this.connect_extra_chat();
//this.setup_rechat();
this.setup_message_event();
this.find_bttv(10);
this.find_emote_menu(10);

View file

@ -9,6 +9,23 @@ var FFZ = window.FrankerFaceZ,
return "ffz_setting_" + key;
},
favorite_setting = function(swit, key, info) {
var ind = this.settings.favorite_settings.indexOf(key);
if ( ind === -1 ) {
this.settings.favorite_settings.push(key);
swit.setAttribute('original-title', 'Unfavorite this Setting');
swit.classList.add('active');
} else {
this.settings.favorite_settings.splice(ind,1);
swit.setAttribute('original-title', 'Favorite this Setting');
swit.classList.remove('active');
}
jQuery(swit).trigger('mouseout').trigger('mouseover');
this.settings.set('favorite_settings', this.settings.favorite_settings);
},
toggle_setting = function(swit, key, info) {
var val = !(info.get ? (typeof info.get === 'function' ? info.get.call(this) : this.settings.get(info.get)) : this.settings.get(key));
if ( typeof info.set === "function" )
@ -208,12 +225,71 @@ FFZ.prototype._load_settings_file = function(data, hide_alert) {
// --------------------
var is_android = navigator.userAgent.indexOf('Android') !== -1,
settings_renderer = function(settings_data, collapsable, collapsed_key) {
settings_renderer = function(settings_data, collapsable, collapsed_key, show_pin) {
return function(view, container) {
var f = this,
settings = {},
categories = [];
// Searching!
if ( show_pin ) {
var search_cont = utils.createElement('div', 'ffz-filter-container'),
search_input = utils.createElement('input', 'emoticon-selector__filter-input js-filter-input text text--full-width'),
filtered_cont = utils.createElement('div', 'ffz-filter-children ffz-ui-sub-menu-page');
search_input.placeholder = 'Search for Settings';
search_input.type = 'text';
filtered_cont.style.maxHeight = (parseInt(container.style.maxHeight) - 51) + 'px';
search_cont.appendChild(search_input);
container.appendChild(filtered_cont);
container.appendChild(search_cont);
container = filtered_cont;
search_input.addEventListener('input', function(e) {
var filter = search_input.value || '',
groups = filtered_cont.querySelectorAll('.chat-menu-content');
filter = filter.toLowerCase();
for(var i=0; i < groups.length; i++) {
var el = groups[i],
settings = el.querySelectorAll('.ffz-setting'),
hidden = true;
for(var j=0; j < settings.length; j++) {
var se = settings[j],
shidden = filter.length && se.getAttribute('data-filter').indexOf(filter) === -1;
se.classList.toggle('hidden', shidden);
hidden = hidden && shidden;
}
var incompat = el.querySelector('.bttv-incompatibility'),
settings = incompat && incompat.querySelectorAll('b'),
incompat_hidden = true;
if ( incompat ) {
for(var j=0; j < settings.length; j++) {
var se = settings[j],
shidden = filter.length && se.getAttribute('data-filter').indexOf(filter) === -1;
se.classList.toggle('hidden', shidden);
incompat_hidden = incompat_hidden && shidden;
}
incompat.classList.toggle('hidden', incompat_hidden);
hidden = hidden && incompat_hidden;
}
el.classList.toggle('collapsable', ! filter.length);
el.classList.toggle('hidden', hidden);
}
});
}
for(var key in settings_data) {
var info = settings_data[key],
cat = info.category || "Miscellaneous",
@ -254,7 +330,7 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
return 0;
});
var current_category = (collapsed_key ? this[collapsed_key] : null) || categories[0];
var current_category = collapsed_key ? this[collapsed_key] || true : categories[0];
for(var ci=0; ci < categories.length; ci++) {
var category = categories[ci],
@ -274,15 +350,23 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
if ( collapsable ) {
menu.classList.add('collapsable');
menu.classList.toggle('collapsed', current_category !== category);
menu.addEventListener('click', function() {
menu.addEventListener('click', function(e) {
var t = this;
if ( ! t.classList.contains('collapsed') )
if ( ! t.classList.contains('collapsable') )
return;
else if ( ! t.classList.contains('collapsed') ) {
if ( e.target.classList.contains('heading') ) {
t.classList.add('collapsed');
if ( collapsed_key )
f[collapsed_key] = true;
}
} else {
jQuery(".chat-menu-content:not(.collapsed)", container).addClass("collapsed");
t.classList.remove('collapsed');
if ( collapsed_key )
f[collapsed_key] = t.getAttribute('data-category');
}
setTimeout(function(){t.scrollIntoViewIfNeeded()});
});
@ -313,15 +397,26 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
for(var i=0; i < cset.length; i++) {
var key = cset[i][0],
info = cset[i][1],
el = createElement('p'),
pin_btn = createElement('a'),
val = info.get ? (typeof info.get === 'function' ? info.get.call(this) : this.settings.get(info.get)) : this.settings.get(key);
el.className = 'clearfix';
el.className = 'ffz-setting clearfix';
if ( this.has_bttv && info.no_bttv ) {
bttv_skipped.push([info.name, info.help]);
continue;
} else {
if ( show_pin ) {
var faved = this.settings.favorite_settings.indexOf(key) !== -1;
pin_btn.className = 'pin-switch html-tooltip';
pin_btn.classList.toggle('active', faved);
pin_btn.addEventListener('click', favorite_setting.bind(this, pin_btn, key, info));
pin_btn.title = (faved ? 'Unf' : 'F') + 'avorite this Setting';
pin_btn.innerHTML = constants.STAR;
}
if ( info.type === "boolean" ) {
var swit = createElement('a'),
label = createElement('span');
@ -334,6 +429,8 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
label.innerHTML = info.name;
el.appendChild(swit);
if ( show_pin )
el.appendChild(pin_btn);
el.appendChild(label);
swit.addEventListener('click', toggle_setting.bind(this, swit, key, info))
@ -356,6 +453,8 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
select.addEventListener('change', option_setting.bind(this, select, key, info));
if ( show_pin )
el.appendChild(pin_btn);
el.appendChild(label);
el.appendChild(select);
@ -364,6 +463,9 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
var link = createElement('a');
link.innerHTML = info.name;
link.href = '#';
if ( show_pin )
el.appendChild(pin_btn);
el.appendChild(link);
link.addEventListener('click', info.method.bind(this));
@ -371,14 +473,27 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
} else
continue;
if ( info.help || (this.has_bttv && info.warn_bttv) ) {
if ( info.help || info.experiment_warn || (this.has_bttv && info.warn_bttv) ) {
var help = document.createElement('span');
help.className = 'help';
help.innerHTML = (this.has_bttv && info.warn_bttv ? '<i>' + info.warn_bttv + (info.help ? '</i><br>' : '</i>') : '') + (info.help || "");
var parts = [];
if ( info.experiment_warn )
parts.push('<b>Note:</b> This affects an active Twitch experiment. Give feedback at: <a href="mailto:feedback@twitch.tv">feedback@twitch.tv</a>');
if ( this.has_bttv && info.warn_bttv )
parts.push('<i>' + info.warn_bttv + '</i>');
if ( info.help )
parts.push(info.help);
help.innerHTML = parts.join('<br>');
el.appendChild(help);
}
}
// Search by any of the present text.
el.setAttribute('data-filter', el.textContent.toLowerCase());
added++;
menu.appendChild(el);
}
@ -397,8 +512,9 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
help.className = 'help';
for(var i=0; i < bttv_skipped.length; i++) {
var skipped = bttv_skipped[i];
help.innerHTML += (i > 0 ? ', ' : '') + '<b' + (skipped[1] ? ' class="html-tooltip" title="' + utils.quote_attr(skipped[1]) + '"' : '') + '>' + skipped[0] + '</b>';
var skipped = bttv_skipped[i],
filter_text = skipped[0].toLowerCase() + (skipped[1] ? ' ' + skipped[1].toLowerCase() : '');
help.innerHTML += '<b data-filter="' + utils.quote_attr(filter_text) + '"' + (skipped[1] ? ' class="html-tooltip" title="' + utils.quote_attr(skipped[1]) + '"' : '') + '>' + skipped[0] + '</b>';
}
el.appendChild(label);
@ -414,7 +530,13 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
},
render_basic = settings_renderer(FFZ.basic_settings, false, '_ffz_basic_settings_page'),
render_advanced = settings_renderer(FFZ.settings_info, true, '_ffz_settings_page');
render_advanced = settings_renderer(FFZ.settings_info, true, '_ffz_settings_page', true);
FFZ.settings_info.favorite_settings = {
value: [],
hidden: true
}
FFZ.menu_pages.settings = {
@ -423,12 +545,42 @@ FFZ.menu_pages.settings = {
sort_order: 99999,
wide: true,
default_page: function() { return this.settings.advanced_settings ? 'advanced' : 'basic' },
default_page: function() { return this.settings.favorite_settings.length ? "favorites" : this.settings.advanced_settings ? 'advanced' : 'basic' },
pages: {
favorites: {
name: "Favorites",
sort_order: 1,
render: function(view, container) {
var favorites = this.settings.favorite_settings;
if ( ! favorites.length ) {
var el = utils.createElement('div');
el.className = 'emoticon-grid ffz-no-emotes center';
el.innerHTML = "You have no favorite settings.<br>" +
'<img src=\"//cdn.frankerfacez.com/emoticon/26608/2\"><br>' +
'To make a setting a favorite, find it on the <nobr>Advanced</nobr> tab and click the star icon to the right.';
container.appendChild(el);
return;
}
var favorite_settings = {};
for(var i=0, l = favorites.length; i < l; i++) {
var key = favorites[i],
val = FFZ.settings_info[key];
if ( val )
favorite_settings[key] = val;
}
return settings_renderer(favorite_settings, false, '_ffz_favorite_settings_page').call(this, view, container);
}
},
basic: {
name: "Basic",
sort_order: 1,
sort_order: 2,
render: function(view, container) {
this.settings.set("advanced_settings", false);
@ -438,7 +590,7 @@ FFZ.menu_pages.settings = {
advanced: {
name: "Advanced",
sort_order: 2,
sort_order: 3,
render: function(view, container) {
this.settings.set("advanced_settings", true);
@ -448,7 +600,7 @@ FFZ.menu_pages.settings = {
backup: {
name: "Backup & Restore",
sort_order: 3,
sort_order: 4,
render: function(view, container) {
var backup_head = createElement('div'),

View file

@ -1,3 +1,3 @@
.badges .badge:not(.subscriber):not(.unknown-badge) {
background-size: 0px;
background-size: 0px !important;
}

View file

@ -1,4 +1,4 @@
.badges .badge:not(.subscriber):not(.unknown-badge) {
.badges .badge:not(.unknown-badge):not(.transparent) {
border-radius: 9px;
background-size: 16px;
background-repeat: no-repeat;

View file

@ -4,15 +4,19 @@
/* Invert Some Badges */
.badge:not(.subscriber):not(.ffz-badge-1) {
.ffz-dark .badge.invert-invert,
.theatre .badge.invert-invert,
.dark .badge.invert-invert,
.force-dark .badge.invert-invert,
.badge:not(.no-invert):not(.invert-invert) {
filter: invert(75%);
-webkit-filter: invert(75%);
}
.ffz-dark .badge,
.theatre .badge,
.dark .badge,
.force-dark .badge {
.ffz-dark .badge:not(.invert-invert),
.theatre .badge:not(.invert-invert),
.dark .badge:not(.invert-invert),
.force-dark .badge:not(.invert-invert) {
filter: none !important;
-webkit-filter: none !important;
}

View file

@ -4,6 +4,8 @@ var FFZ = window.FrankerFaceZ,
helpers,
conv_helpers,
emote_helpers,
bits_helpers,
bits_service,
EXPLANATION_WARN = '<hr>This link has been sent to you via a whisper rather than standard chat, and has not been checked or approved of by any moderators or staff members. Please treat this link with caution and do not visit it if you do not trust the sender.',
@ -13,6 +15,8 @@ var FFZ = window.FrankerFaceZ,
LINK = /(?:https?:\/\/)?(?:[-a-zA-Z0-9@:%_\+~#=]+\.)+[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/g,
CLIP_URL = /(?:https?:\/\/)?clips\.twitch\.tv\/(\w+)\/(\w+)/,
LINK_SPLIT = /^(?:(https?):\/\/)?(?:(.*?)@)?([^\/:]+)(?::(\d+))?(.*?)(?:\?(.*?))?(?:\#(.*?))?$/,
YOUTUBE_CHECK = /^(?:https?:\/\/)?(?:m\.|www\.)?youtu(?:be\.com|\.be)\/(?:v\/|watch\/|.*?(?:embed|watch).*?v=)?([a-zA-Z0-9\-_]+)$/,
IMGUR_PATH = /^\/(?:gallery\/)?[A-Za-z0-9]+(?:\.(?:png|jpg|jpeg|gif|gifv|bmp))?$/,
@ -123,6 +127,18 @@ FFZ.settings_info.timestamp_seconds = {
};
FFZ.settings_info.collect_bits = {
type: "boolean",
value: false,
category: "Chat Appearance",
no_bttv: true,
name: "Bits Stacking",
help: "Collect all the bits emoticons in a message into a single one at the start of the message."
};
FFZ.settings_info.show_deleted_links = {
type: "boolean",
value: false,
@ -155,6 +171,14 @@ FFZ.prototype.setup_tokenization = function() {
if ( ! helpers )
return this.log("Unable to get chat helper functions.");
try {
bits_helpers = window.require && window.require("web-client/utilities/bits/tokenize");
} catch(err) {
this.error("Unable to get bits tokenizer.", err);
}
bits_service = utils.ember_lookup('service:bits-rendering-config');
try {
conv_helpers = window.require && window.require("web-client/helpers/twitch-conversations/conversation-line-helpers");
} catch(err) {
@ -268,7 +292,31 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
FFZ.prototype.render_tooltip = function(el) {
var f = this,
func = function() {
if ( this.classList.contains('emoticon') ) {
if ( this.classList.contains('ffz-bit') ) {
var amount = parseInt(this.getAttribute('data-amount').replace(/,/g, '')),
individuals = JSON.parse(this.getAttribute('data-individuals') || "null"),
tier = bits_service.ffz_get_tier(amount),
preview_url,
image,
out = utils.number_commas(amount) + ' Bit' + utils.pluralize(amount);
if ( f.settings.emote_image_hover )
preview_url = bits_service.ffz_get_preview(tier[1]);
if ( individuals && individuals.length > 1 ) {
out += '<br>';
individuals.sort().reverse();
for(var i=0; i < individuals.length && i < 12; i++)
out += f.render_token(false, false, true, {type: "bits", amount: individuals[i]});
if ( individuals.length >= 12 )
out += '<br>(and ' + (individuals.length - 12) + ' more)';
}
image = preview_url ? '<img style="height:112px" class="emoticon ffz-image-hover" src="' + preview_url + '"?_=preview">' : '';
return image + out;
} else if ( this.classList.contains('emoticon') ) {
var preview_url, width=0, height=0, image, set_id, emote, emote_set,
emote_id = this.getAttribute('data-ffz-emote');
if ( emote_id ) {
@ -356,17 +404,29 @@ FFZ.prototype.render_tooltip = function(el) {
} else if ( this.classList.contains('chat-link') ) {
// TODO: A lot of shit. Lookup data.
var url = this.getAttribute("data-url"),
data = url && f._link_data[url],
preview_url = null,
preview_iframe = true,
image = '',
text = '';
if ( ! url )
return;
if ( f.settings.link_image_hover && is_image(url, f.settings.image_hover_all_domains) )
preview_url = url;
else
preview_url = null;
// Do we have data?
if ( data && data !== true ) {
text = data.html;
preview_url = data.image;
preview_iframe = data.image_iframe !== undefined ? data.image_iframe : true;
} else
preview_url = is_image(url, f.settings.image_hover_all_domains) ? url : null;
image = preview_url ? image_iframe(url) : '';
if ( f.settings.link_image_hover && preview_url )
if ( preview_iframe )
image = image_iframe(url);
else
image = '<img class="emoticon ffz-image-hover" src="' + utils.quote_attr(preview_url) + '">';
// If it's not a deleted link, don't waste time showing the URL in the tooltip.
if ( this.classList.contains('deleted-link') )
@ -412,11 +472,13 @@ FFZ.prototype.tokenize_conversation_line = function(message, prevent_notificatio
if ( user && user.login && helpers && helpers.mentionizeMessage )
tokens = helpers.mentionizeMessage(tokens, user.login, from_me);
if ( helpers && helpers.emoticonizeMessage && emotes )
if ( helpers && helpers.emoticonizeMessage && emotes && this.settings.parse_emoticons )
tokens = helpers.emoticonizeMessage(tokens, emotes);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
if ( this.settings.parse_emoticons && this.settings.parse_emoticons !== 2 )
tokens = this.tokenize_emotes(from_user, undefined, tokens, from_me);
if ( this.settings.parse_emoji )
@ -456,11 +518,13 @@ FFZ.prototype.tokenize_vod_line = function(msgObject, delete_links) {
if ( user && user.login && helpers && helpers.mentionizeMessage )
tokens = helpers.mentionizeMessage(tokens, user.login, from_me);
if ( helpers && helpers.emoticonizeMessage && emotes )
if ( helpers && helpers.emoticonizeMessage && emotes && this.settings.parse_emoticons )
tokens = helpers.emoticonizeMessage(tokens, emotes);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
if ( this.settings.parse_emoticons && this.settings.parse_emoticons !== 2 )
tokens = this.tokenize_emotes(from_user, room_id, tokens, from_me);
if ( this.settings.parse_emoji )
@ -486,7 +550,7 @@ FFZ.prototype.tokenize_vod_line = function(msgObject, delete_links) {
}
FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, delete_links, use_bits) {
FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, delete_links) {
if ( msgObject.cachedTokens )
return msgObject.cachedTokens;
@ -495,12 +559,17 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
from_user = msgObject.from,
user = this.get_user(),
from_me = user && from_user === user.login,
emotes = msgObject.tags && msgObject.tags.emotes,
tags = msgObject.tags || {},
emotes = tags.emotes,
tokens = [msg];
// Standard tokenization
if ( use_bits && helpers && helpers.tokenizeBits )
tokens = helpers.tokenizeBits(tokens);
// Standard Tokenization
if ( tags.bits && bits_helpers && bits_helpers.tokenizeBits )
tokens = bits_helpers.tokenizeBits(tokens);
// For Later
//if ( helpers && helpers.tokenizeRichContent )
// tokens = helpers.tokenizeRichContent(tokens, tags.content, delete_links);
if ( helpers && helpers.linkifyMessage ) {
var labels = msgObject.labels || [],
@ -518,18 +587,37 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
if ( user && user.login && helpers && helpers.mentionizeMessage )
tokens = helpers.mentionizeMessage(tokens, user.login, from_me);
if ( helpers && helpers.emoticonizeMessage )
if ( helpers && helpers.emoticonizeMessage && this.settings.parse_emoticons )
tokens = helpers.emoticonizeMessage(tokens, emotes);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
if ( tags.bits && this.settings.collect_bits ) {
var total = 0, individuals = [];
for(var i=0; i < tokens.length; i++)
if ( tokens[i] && tokens[i].type === "bits" ) {
tokens[i].hidden = true;
total += tokens[i].amount || 0;
individuals.push(tokens[i].amount);
}
tokens.splice(0, 0, {
type: "bits",
amount: total,
individuals: individuals,
length: 0
});
}
if ( this.settings.parse_emoticons && this.settings.parse_emoticons !== 2 )
tokens = this.tokenize_emotes(from_user, room_id, tokens, from_me);
if ( this.settings.parse_emoji )
tokens = this.tokenize_emoji(tokens);
// Capitalization
var display = msgObject.tags && msgObject.tags['display-name'];
var display = tags['display-name'];
if ( display && display.length )
FFZ.capitalization[from_user] = [display.trim(), Date.now()];
@ -634,7 +722,7 @@ FFZ.prototype.tokenize_line = function(user, room, message, no_emotes, no_emoji)
message = helpers.mentionizeMessage(message, u.login, user === u.login);
}
if ( ! no_emotes )
if ( ! no_emotes && this.settings.parse_emoticons && this.settings.parse_emoticons !== 2 )
message = this.tokenize_emotes(user, room, message);
if ( this.settings.parse_emoji && ! no_emoji )
@ -653,7 +741,7 @@ FFZ.prototype.tokenize_feed_body = function(message, emotes, user_id, room_id) {
if ( helpers && helpers.linkifyMessage )
message = helpers.linkifyMessage(message);
if ( helpers && helpers.emoticonizeMessage )
if ( helpers && helpers.emoticonizeMessage && this.settings.parse_emoticons )
message = helpers.emoticonizeMessage(message, emotes);
// Tokenize Lines
@ -680,6 +768,7 @@ FFZ.prototype.tokenize_feed_body = function(message, emotes, user_id, room_id) {
}
}
if ( this.settings.parse_emoticons && this.settings.parse_emoticons !== 2 )
tokens = this.tokenize_emotes(user_id, room_id, tokens)
if ( this.settings.parse_emoji )
@ -689,7 +778,7 @@ FFZ.prototype.tokenize_feed_body = function(message, emotes, user_id, room_id) {
}
FFZ.prototype.render_token = function(render_links, warn_links, token) {
FFZ.prototype.render_token = function(render_links, warn_links, render_bits, token) {
if ( ! token )
return "";
@ -764,7 +853,24 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) {
if (!( this._link_data && this._link_data[href] )) {
this._link_data = this._link_data || {};
this._link_data[href] = true;
this.ws_send("get_link", href, load_link_data.bind(this, href));
var success = load_link_data.bind(this, href),
clip_info = CLIP_URL.exec(href);
if ( clip_info ) {
var clips = utils.ember_lookup('service:clips');
clips && clips.getClipInfo(clip_info[1], clip_info[2]).then(function(data) {
success(true, {
image: data.previewImage,
image_iframe: false,
html: '<span class="ffz-clip-title">' + utils.sanitize(data.title) + '</span>' +
'Channel: ' + utils.sanitize(data.broadcasterDisplayName) +
'<br>Game: ' + utils.sanitize(data.game)
});
});
} else
this.ws_send("get_link", href, success);
}
}
}
@ -784,6 +890,14 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) {
return '<a class="ffz-tooltip' + (cls ? ' ' + cls : '') + '" data-text="' + utils.quote_attr(token.text) + '" data-url="' + utils.quote_attr(actual_href) + '" href="' + utils.quote_attr(href||'#') + '" target="_blank" rel="noopener">' + utils.sanitize(text) + '</a>';
}
else if ( token.type === "bits" ) {
var tier = render_bits && bits_service.ffz_get_tier(token.amount) || [null, null];
if ( ! tier[1] )
return 'cheer' + token.amount;
return '<span class="emoticon ffz-bit ffz-tooltip bit-tier-' + tier[0] + '"' + (token.individuals ? ' data-individuals="' + utils.quote_attr(JSON.stringify(token.individuals)) + '"' : '') + ' data-amount="' + utils.number_commas(token.amount) + '" alt="cheer' + token.amount + '"></span>';
}
else if ( token.type === "deleted" )
return '<span class="deleted-word html-tooltip" title="' + utils.quote_san(token.text) + '" data-text="' + utils.sanitize(token.text) + '">&times;&times;&times;</span>';
//return `<span class="deleted-word html-tooltip" title="${utils.quote_attr(token.text)}" data-text="${utils.sanitize(token.text)}">&times;&times;&times;</span>`;
@ -796,15 +910,15 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) {
return utils.sanitize(token.text);
else if ( typeof token !== "string" )
return '<b class="html-tooltip" title="<div style=&quot;text-align:left&quot;>' + utils.quote_attr(JSON.stringify(token,null,2)) + '</div>">[invalid token]</b>';
//return `<b class="html-tooltip" title="<div style=&quot;text-align:left&quot;>${utils.quote_attr(JSON.stringify(token,null,2))}</div>">[invalid token]</b>`;
return '<b class="html-tooltip" title="<div style=&quot;text-align:left&quot;>' + utils.quote_attr(JSON.stringify(token,null,2)) + '</div>">[unknown token]</b>';
//return `<b class="html-tooltip" title="<div style=&quot;text-align:left&quot;>${utils.quote_attr(JSON.stringify(token,null,2))}</div>">[unknown token]</b>`;
return utils.sanitize(token);
}
FFZ.prototype.render_tokens = function(tokens, render_links, warn_links) {
return _.map(tokens, this.render_token.bind(this, render_links, warn_links)).join("");
FFZ.prototype.render_tokens = function(tokens, render_links, warn_links, render_bits) {
return _.map(tokens, this.render_token.bind(this, render_links, warn_links, render_bits)).join("");
}
@ -1100,13 +1214,15 @@ FFZ.prototype._deleted_link_click = function(e) {
// History Loading
// ---------------------
FFZ.prototype.parse_history = function(history, purged, room_id, delete_links, tmiSession, per_line) {
FFZ.prototype.parse_history = function(history, purged, bad_ids, room_id, delete_links, tmiSession, per_line) {
var i = history.length, was_cleared = false;
purged = purged || {};
bad_ids = bad_ids || {};
while(i--) {
var msg = history[i],
is_deleted = msg.ffz_deleted = purged[msg.from] || false;
msg_id = msg.tags && msg.tags.id,
is_deleted = msg.ffz_deleted = purged[msg.from] || (msg_id && bad_ids[msg_id]) || false;
if ( is_deleted && ! this.settings.prevent_clear )
msg.deleted = true;
@ -1159,8 +1275,17 @@ FFZ.prototype.parse_history = function(history, purged, room_id, delete_links, t
if ( msg.tags && msg.tags.target === '@@' )
was_cleared = true;
else if ( msg.tags && msg.tags.target )
else if ( msg.tags && msg.tags.target ) {
var ban_reason = msg.tags && msg.tags['ban-reason'],
ban_id = ban_reason && constants.UUID_TEST.exec(ban_reason);
if ( ban_id ) {
bad_ids[ban_id[1]] = true;
ban_reason = ban_reason.substr(0, ban_reason.length - ban_id[0].length);
msg.tags['ban-reason'] = ban_reason ? ban_reason : undefined;
} else
purged[msg.tags.target] = true;
}
// Per-line
if ( per_line && ! per_line(msg) )

View file

@ -131,6 +131,10 @@ var update_player_stats = function(player, container) {
if ( ! player_data )
return;
try {
player_data.backend = player.getBackend();
} catch(err) { player_data.backend = undefined }
var sorted_keys = Object.keys(player_data).sort();
for(var i=0; i < sorted_keys.length; i++) {
var key = sorted_keys[i],
@ -309,6 +313,9 @@ FFZ.menu_pages.about = {
['Deploy Flavor', SiteOptions.deploy_flavor]
],
exp_head = createElement('div'),
experiments = createElement('ul'),
has_memory = window.performance && performance.memory,
mem_head = createElement('div'),
mem_list = createElement('ul'),
@ -346,8 +353,8 @@ FFZ.menu_pages.about = {
heading.className = 'chat-menu-content center';
heading.innerHTML = '<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">woofs for nerds</div>';
info_head.className = mem_head.className = twitch_head.className = player_head.className = ver_head.className = log_head.className = 'list-header';
info.className = mem_list.className = twitch.className = player_list.className = vers.className = 'chat-menu-content menu-side-padding version-list';
info_head.className = exp_head.className = mem_head.className = twitch_head.className = player_head.className = ver_head.className = log_head.className = 'list-header';
info.className = mem_list.className = twitch.className = experiments.className = player_list.className = vers.className = 'chat-menu-content menu-side-padding version-list';
info_head.innerHTML = 'Client Status';
@ -394,6 +401,21 @@ FFZ.menu_pages.about = {
twitch.appendChild(line);
}
var exp_service = utils.ember_lookup('service:experiments');
if ( exp_service ) {
exp_head.innerHTML = 'Twitch Experiments';
for(var key in exp_service.values) {
if ( ! exp_service.values.hasOwnProperty(key) )
continue;
var val = exp_service.values[key],
line = createElement('li');
line.innerHTML = key + '<span>' + utils.sanitize(val) + '</span>';
experiments.appendChild(line);
}
}
ver_head.innerHTML = 'Versions';
if ( this.has_bttv )
@ -431,6 +453,11 @@ FFZ.menu_pages.about = {
container.appendChild(twitch_head);
container.appendChild(twitch);
if ( exp_service ) {
container.appendChild(exp_head);
container.appendChild(experiments);
}
if ( has_memory ) {
mem_head.innerHTML = 'Memory Statistics';
setTimeout(update_mem_stats.bind(this,mem_list),0);

View file

@ -73,7 +73,7 @@ FFZ.prototype.setup_following_count = function(has_ember) {
return this._following_get_me();
this.log("Connecting to Live Streams model.");
var Stream = utils.ember_resolve('model:stream');
var Stream = utils.ember_resolve('model:deprecated-stream');
if ( ! Stream )
return this.log("Unable to find Stream model.");

View file

@ -30,6 +30,15 @@ FFZ.prototype.fix_tooltips = function() {
})
}
// Fix tipsy invalidation
if ( window.jQuery && jQuery.fn && jQuery.fn.tipsy )
jQuery.fn.tipsy.revalidate = function() {
jQuery(".tipsy").each(function() {
var t = jQuery.data(this, "tipsy-pointee");
(!t || !t[0] || !document.contains(t[0])) && jQuery(this).remove();
})
};
// Iterate all existing tipsy stuff~!
this.log('Fixing already existing tooltips.');
if ( ! window.jQuery || ! jQuery.cache )

View file

@ -2,7 +2,20 @@ var FFZ = window.FrankerFaceZ,
constants = require('./constants');
var sanitize_el = document.createElement('span'),
var createElement = function(tag, className, content) {
var out = document.createElement(tag);
if ( className )
out.className = className;
if ( content )
if ( content.nodeType )
out.appendChild(content);
else
out.innerHTML = content;
return out;
},
sanitize_el = createElement('span'),
sanitize = function(msg) {
sanitize_el.textContent = msg;
@ -206,25 +219,18 @@ var sanitize_el = document.createElement('span'),
// 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'),
var container = createElement('div', 'twitch_subwindow_container'),
subwindow = createElement('div', 'twitch_subwindow ffz-subwindow'),
card = createElement('div', 'card'),
close_button = createElement('div', 'modal-close-button', constants.CLOSE),
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 )
@ -265,6 +271,7 @@ var sanitize_el = document.createElement('span'),
module.exports = FFZ.utils = {
// Ember Manipulation
ember_views: function() {
return ember_lookup('-view-registry:main') || {};
},
@ -281,6 +288,38 @@ module.exports = FFZ.utils = {
return App.__container__.resolve(thing);
},
ember_reopen_view: function(component, data) {
if ( typeof component === 'string' )
component = ember_resolve(component);
data.ffz_modified = true;
if ( data.ffz_init && ! data.didInsertElement )
data.didInsertElement = function() {
this._super();
try {
this.ffz_init();
} catch(err) {
FFZ.get().error("An error occured running ffz_init on " + this.toString(), err);
}
};
if ( data.ffz_destroy && ! data.willClearRender )
data.willClearRender = function() {
try {
this.ffz_destroy();
} catch(err) {
FFZ.get().error("An error occured running ffz_destroy on " + this.toString(), err);
}
this._super();
};
return component.reopen(data);
},
// Other Stuff
build_srcset: build_srcset,
/*build_tooltip: build_tooltip,
load_emote_data: load_emote_data,*/
@ -294,18 +333,52 @@ module.exports = FFZ.utils = {
show_modal: show_modal,
confirm: function(title, description, callback) {
var contents = createElement('div', 'text-content'),
heading = title ? createElement('div', 'content-header', '<h4>' + title + '</h4>') : null,
body = createElement('div', 'item'),
buttons = createElement('div', 'buttons', '<a class="js-subwindow-close button"><span>Cancel</span></a><button class="button primary" type="submit"><span>OK</span></button>'),
close_btn = buttons.querySelector('.js-subwindow-close'),
okay_btn = buttons.querySelector('.button.primary');
if ( heading )
contents.appendChild(heading);
if ( description ) {
if ( description.nodeType )
body.appendChild(description);
else
body.innerHTML = '<p>' + description + '</p>';
contents.appendChild(body);
}
contents.appendChild(buttons);
var closer,
cb = function(success) {
closer();
if ( ! callback )
return;
callback(success);
};
closer = show_modal(contents, cb);
okay_btn.addEventListener('click', function(e) { e.preventDefault(); cb(true); return false });
close_btn.addEventListener('click', function(e) { e.preventDefault(); cb(false); return false });
},
prompt: function(title, description, old_value, callback, width, input) {
var contents = document.createElement('div'),
heading = document.createElement('div'),
form = document.createElement('form'),
var contents = createElement('div', 'text-content'),
heading = createElement('div', 'content-header', '<h4>' + title + '</h4>'),
form = createElement('form'),
close_btn, okay_btn;
contents.className = 'text-content';
heading.className = 'content-header';
heading.innerHTML = '<h4>' + title + '</h4>';
if ( ! input ) {
input = document.createElement('input');
input = createElement('input');
input.type = 'text';
}
@ -540,18 +613,19 @@ module.exports = FFZ.utils = {
escape_regex: escape_regex,
createElement: function(tag, className, content) {
var out = document.createElement(tag);
if ( className )
out.className = className;
if ( content )
out.innerHTML = content;
return out;
},
createElement: createElement,
toggle_cls: function(cls) {
return function(val) {
document.body.classList.toggle(cls, val);
}
},
badge_css: function(badge, klass) {
klass = klass || ('ffz-badge-' + badge.id);
var out = ".badges ." + klass + " { background-color: " + badge.color + '; background-image: url("' + badge.image + '"); ' + (badge.css || "") + '}';
if ( badge.alpha_image )
out += ".badges .badge.alpha." + klass + ",.ffz-transparent-badges .badges ." + klass + ' { background-image: url("' + badge.alpha_image + '"); }';
return out;
}
}

197
style.css
View file

@ -321,10 +321,6 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
background-color: #25252a;
}
.ffz-theater-stats .app-main.theatre .button:not(.primary) {
color: #a68ed2;
}
.ffz-theater-stats .app-main.theatre .button.button--icon-only svg path {
fill: #a68ed2;
}
@ -501,6 +497,11 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
border-top-color: rgba(255,255,255, 0.2);
}
.ffz-dark .ffz-ui-menu-page .ffz-filter-container,
.theatre .ffz-ui-menu-page .ffz-filter-container,
.dark .ffz-ui-menu-page .ffz-filter-container,
.force-dark .ffz-ui-menu-page .ffz-filter-container,
.ffz-dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
.theatre .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
.dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
@ -598,17 +599,18 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
.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,
.chat-menu-content.collapsed p { display: none; }
.emoticon-grid.collapsable.collapsed span,
.chat-menu-content.collapsable.collapsed p { display: none; }
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content.collapsed .heading,
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid.collapsed .heading {
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content.collapsable.collapsed .heading,
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid.collapsable.collapsed .heading {
padding-bottom: 0;
}
.emoticon-grid.collapsable .heading,
.emoticon-grid.collapsed,
.chat-menu-content.collapsed {
.emoticon-grid.collapsable.collapsed,
.chat-menu-content.collapsable.collapsed,
.chat-menu-content.collapsable .heading {
cursor: pointer;
}
@ -617,6 +619,25 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
position: relative;
}
.pin-switch {
display: block;
height: 16px;
padding: 2px;
margin: 0 !important;
cursor: pointer;
}
.pin-switch:hover svg path,
.pin-switch.active svg path { fill: #14b866 !important }
.pin-switch.active:hover svg path { fill: #fc3636 !important }
.pin-switch svg path { fill: rgba(0,0,0,0.2); }
.dark .pin-switch svg path,
.force-dark .pin-switch svg path,
.theatre .dark .pin-switch svg path { fill: rgba(255,255,255,0.2) }
.pin-switch,
.list-header span.right { float: right; }
.ember-chat .chat-menu .list-header.sub-header {
@ -1307,7 +1328,7 @@ img.channel_background[src="null"] { display: none; }
.ffz-moderation-card .right button:last-of-type,
.ffz-moderation-card .mod-controls:last-of-type button:last-of-type { margin-right: 0 }
.ffz-moderation-card .follow-button a {
.ffz-moderation-card .follow-button {
font-size: 0 !important;
padding-right: 0 !important;
}
@ -1413,6 +1434,8 @@ img.channel_background[src="null"] { display: none; }
/* Chat Rows */
.chat-line { overflow: hidden }
.theatre .conversation-window .conversation-chat-line,
.dark .chat-line,
.force-dark .chat-line,
@ -1456,6 +1479,7 @@ body:not(.ffz-bttv) .chat-container:not(.chatReplay) .more-messages-indicator {
padding: 0;
}
/* Emoticon Tooltips */
.ffz-wide-tip hr {
@ -2170,6 +2194,14 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
.ffz-no-blue .theatre .conversation-system-message,
.ffz-no-blue.ffz-dark .conversation-system-message,
.ffz-no-blue .theatre .bits-card,
.ffz-no-blue .dark .bits-card,
.ffz-no-blue .force-dark .bits-card,
.ffz-no-blue .theatre .bits-card--standard,
.ffz-no-blue .dark .bits-card--standard,
.ffz-no-blue .force-dark .bits-card--standard,
.ffz-no-blue .warp,
.ffz-no-blue #large_nav .content,
.ffz-no-blue #small_nav .content,
@ -2370,6 +2402,14 @@ li[data-name="following"] a {
/* Image Tooltips */
.ffz-clip-title {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 5px;
}
.ffz-image-hover {
border:none;
max-width: 186px;
@ -2802,14 +2842,17 @@ body:not(.ffz-top-conversations) .conversations-list-bottom-bar {
/* Creative Directory */
.ffz-top-conversations .ct-banner { margin-top: -50px }
.ffz-top-conversations .ct-banner__feature { top: 40px; margin-bottom: 0 }
body:not(.ffz-tags-on-channel) #broadcast-meta .ct-tags,
body:not(.ffz-creative-showcase) .ct-gallery { display: none; }
/* Make sure the fancy banner art goes all the way up */
.ffz-top-conversations .ct-banner-container { margin-top: -50px }
body:not(.ffz-tags-on-channel) .tw-title--tall { height: 60px }
body:not(.ffz-tags-on-channel) #broadcast-meta .ct-tags,
/*body:not(.ffz-creative-showcase) .ct-banner-handler .tower > div:last-child,*/
body:not(.ffz-creative-showcase) .ct-spotlight-container { display: none; }
/* Content-Box~! Down with Twitch randomly changing the box-sizing for everything! */
#ffz-channel-table *,
@ -2874,33 +2917,42 @@ body:not([data-current-path^="directory.csgo"]):not([data-current-path^="directo
background-size: cover !important;
}
.follow-button.ffz-block-button {
margin-left: 2px;
.directory_header .ffz-block-button,
.directory_header .ffz-spoiler-button {
margin-left: 1rem;
vertical-align: top;
}
.balloon .balloon__title button {
display: block;
}
.balloon .balloon__title button:not(:last-of-type) {
margin-bottom: 1rem;
}
body[data-current-path^="directory.creative"] .follow-button.ffz-block-button {
margin: 0 1rem;
}
.follow-button.ffz-spoiler-button .spoiler,
.follow-button.ffz-block-button .block {
.button.ffz-spoiler-button,
.button.ffz-block-button {
background: #555;
padding: 0 10px;
}
.follow-button.ffz-spoiler-button .spoiler.active {
.button.ffz-spoiler-button.active {
background: #006700;
}
.follow-button.ffz-spoiler-button .spoiler:not(.disabled):hover {
.button.ffz-spoiler-button:not(.disabled):hover {
background: #247324;
}
.follow-button.ffz-block-button .block.active {
.button.ffz-block-button.active {
background: #973333;
}
.follow-button.ffz-block-button .block:not(.disabled):hover {
.button.ffz-block-button:not(.disabled):hover {
background: #a94444;
}
@ -2946,6 +2998,8 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
/* Badges */
.badges .badge { background-size: 18px 18px }
/*.badges .badge {
height: 18px;
min-width: 18px;
@ -3001,18 +3055,41 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
/* Odd Badges */
.badge.click_url { cursor: pointer }
.badge.warcraft.version-alliance {
background: url("https://cdn.frankerfacez.com/badges/twitch/warcraft/alliance/1.png") #004094;
background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/warcraft/alliance/1.png") 1x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/alliance/2.png") 2x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/alliance/4.png") 4x);
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/warcraft/alliance/1.png") 1x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/alliance/2.png") 2x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/alliance/4.png") 4x);
.badge.bits.version-1 {
background: url("https://cdn.frankerfacez.com/badges/twitch/bits/1/1.png") #cbc8d0;
background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/1/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1/4.png") 4x);
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/1/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1/4.png") 4x);
}
.badge.warcraft.version-horde {
background: url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/1.png") #ab0016;
background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/1.png") 1x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/2.png") 2x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/4.png") 4x);
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/1.png") 1x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/2.png") 2x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/4.png") 4x);
.badge.bits.version-100 {
background: url("https://cdn.frankerfacez.com/badges/twitch/bits/100/1.png") #ca7eff;
background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/100/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100/4.png") 4x);
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/100/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100/4.png") 4x);
}
.badge.bits.version-1000 {
background: url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/1.png") #3ed8b3;
background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/4.png") 4x);
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/4.png") 4x);
}
.badge.bits.version-5000 {
background: url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/1.png") #49acff;
background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/4.png") 4x);
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/4.png") 4x);
}
.badge.bits.version-10000 {
background: url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/1.png") #ff271e;
background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/4.png") 4x);
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/4.png") 4x);
}
.badge.bits.version-100000 {
background: url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/1.png") #ffcb13;
background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/4.png") 4x);
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/4.png") 4x);
}
/* New Resub Banner */
@ -3046,3 +3123,57 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
.force-dark .chat-room .show-mod-icons .chat-line:not(.admin) .mod-icons {
background-color: rgba(0,0,0,0.8);
}
/* Bits */
.bits-card { z-index: 10 }
.ffz-bit {
display: inline-block;
height: 28px;
line-height: 28px;
font-weight: bold;
padding-left: 30px;
margin: -5px 5px;
box-sizing: content-box;
background-position: 0;
}
.tipsy .ffz-bit {
margin: 0 5px;
}
.ffz-bit:after {
content: attr(data-amount);
}
/* New Chat Formatting */
.ember-chat .chat-messages .timestamp { margin-right: 0; }
.badges { display: inline-block }
.badges .badge {
float: none;
margin: -1px 3px 0 0;
}
.ffz-baseline-emoticons .activity-body .emoticon,
.ffz-baseline-emoticons .chat-line .emoticon {
vertical-align: baseline;
padding-top: 5px;
}
/* Settings Filtering */
.ffz-ui-menu-page .ffz-filter-container {
padding: 10px;
border-top: 1px solid rgba(0,0,0,0.2);
}
.ffz-ui-menu-page input.js-filter-input { margin: 0 }
.bttv-incompatibility b + b:before {
content: ', ';
font-weight: normal;
}