1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-19 04: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; color: #a68ed2;
} }
.ffz-dark .button.button--icon-only svg path { .ffz-dark .button.button--icon-only svg {
fill: #a68ed2; fill: #a68ed2;
} }
.ffz-dark .button.button--icon-only:hover svg path { .ffz-dark .button.button--icon-only:hover svg {
fill: #fff; fill: #fff;
} }
@ -483,11 +483,6 @@ body.ffz-dark:not([data-page="teams#show"]),
fill: rgba(255,255,255,0.5); 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 .following-col .following-list .load-more span,
.ffz-dark .viewall a { .ffz-dark .viewall a {
background-color: rgb(25,25,31); 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 */ /* Creative Tags */
.ffz-dark .ct-tags__tag { .ffz-dark .ct-tags__tag {
background-color: #191919; background-color: #121212;
color: #999 !important; color: #999 !important;
} }
@ -1228,6 +1286,7 @@ body.ffz-dark:not([data-page="teams#show"]),
} }
.ffz-dark .activity-meta-divider:before, .ffz-dark .activity-meta-divider:before,
.ffz-dark .list-load-more,
.ffz-dark .activity-card { .ffz-dark .activity-card {
border-color: #474747; 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-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 */ /* Search Panel */
.ffz-dark[data-current-path="user.channel.index.index"] .searchPanel { background-color: rgba(16,16,16,0.9) } .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) { fs.exists(file, function(exists) {
if ( ! exists ) { if ( ! exists ) {
util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.bold.blue("CDN") + " GET " + util.colors.magenta(uri)); util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.bold.blue("CDN") + " GET " + util.colors.magenta(uri));
/*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); 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'), utils = require('./utils'),
SPECIAL_BADGES = ['staff', 'admin', 'global_mod'], 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 = { BTTV_TYPE_REPLACEMENTS = {
'global-moderator': 'global_mod' 'global-moderator': 'global_mod'
@ -25,14 +29,6 @@ var FFZ = window.FrankerFaceZ,
BADGE_KLASSES = { BADGE_KLASSES = {
'global_mod': 'global-moderator' '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) { for(var badge_id in f.badges) {
if ( f.badges.hasOwnProperty(badge_id) && f.badges[badge_id].name ) if ( ! f.badges.hasOwnProperty(badge_id) )
values.push('<code>ffz-' + f.badges[badge_id].name + '</code>'); 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 ) { if ( this.has_bttv && window.BetterTTV ) {
@ -129,11 +130,13 @@ FFZ.settings_info.sub_notice_badges = {
category: "Chat Appearance", category: "Chat Appearance",
name: "Old-Style Subscriber Notice Badges", name: "Old-Style Subscriber Notice Badges",
no_bttv: true,
help: "Display a subscriber badge on old-style chat messages about new subscribers.", help: "Display a subscriber badge on old-style chat messages about new subscribers.",
on_update: function(val) { on_update: function(val) {
this.toggle_style('badges-sub-notice', ! val); this.toggle_style('badges-sub-notice', ! this.has_bttv && ! val);
this.toggle_style('badges-sub-notice-on', 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 || {}; badges = user.badges = user.badges || {};
if ( badge === undefined || badge === null ) if ( badge === undefined || badge === null )
delete badges[slot]; badges[slot] = null;
else else
badges[slot] = badge; badges[slot] = badge;
} }
@ -287,14 +290,16 @@ FFZ.prototype.get_badges = function(user, room_id, badges, msg) {
return badges; return badges;
for(var slot in data.badges) { for(var slot in data.badges) {
if ( ! data.badges.hasOwnProperty(slot) ) var badge = data.badges[slot];
if ( ! data.badges.hasOwnProperty(slot) || ! badge )
continue; continue;
var badge = data.badges[slot], var full_badge = this.badges[badge.id] || {},
full_badge = this.badges[badge.id] || {}, old_badge = badges[slot],
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; continue;
if ( full_badge.visible !== undefined ) { if ( full_badge.visible !== undefined ) {
@ -319,10 +324,13 @@ FFZ.prototype.get_badges = function(user, room_id, badges, msg) {
badges[slot] = { badges[slot] = {
klass: 'ffz-badge-' + badge.id, 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, image: badge.image,
full_image: full_badge.image, full_image: full_badge.image,
color: badge.color, 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 extra_css: badge.extra_css
}; };
} }
@ -390,12 +398,15 @@ FFZ.prototype.get_line_badges = function(msg) {
badges[last_id] = { badges[last_id] = {
klass: (BADGE_KLASSES[badge] || badge) + (is_known ? '' : ' unknown-badge') + ' version-' + version, klass: (BADGE_KLASSES[badge] || badge) + (is_known ? '' : ' unknown-badge') + ' version-' + version,
title: binfo && binfo.title || BADGE_NAMES[badge] || badge.capitalize(), 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 ) { if ( ! is_known && binfo ) {
badges[last_id].image = binfo.image_url_1x; 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++) { for(var i=0, l = SPECIAL_BADGES.length; i < l; i++) {
var mb = SPECIAL_BADGES[i]; var mb = SPECIAL_BADGES[i];
if ( user_type === mb ) { 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; break;
} }
} }
if ( has_sub ) if ( has_sub )
badges[10] = {klass: 'subscriber', title: 'Subscriber'} badges[10] = {klass: 'subscriber', title: 'Subscriber', no_invert: true, transparent: true}
if ( has_turbo ) if ( has_turbo )
badges[15] = {klass: 'turbo', title: 'Turbo'} badges[15] = {klass: 'turbo', title: 'Turbo'}
@ -450,7 +461,16 @@ FFZ.prototype.render_badges = function(badges) {
if ( badge.click_url ) if ( badge.click_url )
klass += ' 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(""); return out.join("");
@ -482,17 +502,27 @@ FFZ.prototype.bttv_badges = function(data) {
for(var i=0; i < data.badges.length; i++) { for(var i=0; i < data.badges.length; i++) {
var badge = data.badges[i], var badge = data.badges[i],
space_ind = badge.type.indexOf(' '), 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 ) { if ( hidden_badges.indexOf(hidden_key) !== -1 ) {
data.badges.splice(i, 1); data.badges.splice(i, 1);
i--;
continue; 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; insert_at = i;
break;
}
} }
// If there's no user, we're done now. // 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. // We have a user. Start replacing badges.
for (var slot in user.badges) { for (var slot in user.badges) {
if ( ! user.badges.hasOwnProperty(slot) ) var badge = user.badges[slot];
if ( ! user.badges.hasOwnProperty(slot) || ! badge )
continue; continue;
var badge = user.badges[slot], var full_badge = this.badges[badge.id] || {},
full_badge = this.badges[badge.id] || {},
desc = badge.title || full_badge.title, 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; continue;
if ( full_badge.visible !== undefined ) { if ( full_badge.visible !== undefined ) {
@ -565,8 +597,6 @@ FFZ.prototype.bttv_badges = function(data) {
while(badges_out.length) while(badges_out.length)
data.badges.insertAt(insert_at, badges_out.shift()[1]); 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; data.replaces = true;
} }
if ( data.name === 'developer' )
data.no_invert = true;
if ( data.name === 'bot' ) if ( data.name === 'bot' )
data.visible = function(r,user) { return !(this.has_bttv && FFZ.bttv_known_bots.indexOf(user)!==-1); }; 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/", SERVER = DEBUG ? "//localhost:8000/" : "https://cdn.frankerfacez.com/",
IS_OSX = navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent), 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), 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]", 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, SEPARATORS: SEPARATORS,
SPLITTER: SPLITTER, 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: { KNOWN_CODES: {
"#-?[\\\\/]": "#-/", "#-?[\\\\/]": "#-/",
":-?(?:7|L)": ":-7", ":-?(?: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); document.body.classList.toggle('ffz-theater-stats', this.settings.theater_stats);
this.log("Hooking the Ember Channel Index view."); this.log("Hooking the Ember Channel Index view.");
var Channel = utils.ember_resolve('view:channel/index'), if ( ! this.update_views('view:channel/index', this.modify_channel_index) )
f = this;
if ( ! Channel )
return; 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."); 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 ) if ( ! Channel )
return; return this.log("Unable to find the Ember model:deprecated-channel");
Channel.reopen({ this._modify_cmodel(Channel);
ffz_host_target: undefined,
setHostMode: function(e) { var Store = utils.ember_lookup('service:store'),
if ( f.settings.hosted_channels ) { type_map = Store && Store.typeMapFor(Channel);
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});
}
}
});
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."); this.log("Hooking the Ember Channel controller.");
@ -89,7 +58,7 @@ FFZ.prototype.setup_channel = function() {
if ( ! this.get('content.id') ) if ( ! this.get('content.id') )
return; 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"), }.observes("content.id"),
ffzCheckUpdate: function() { ffzCheckUpdate: function() {
@ -130,7 +99,6 @@ FFZ.prototype.setup_channel = function() {
}); });
}, },
ffzUpdateTitle: function() { ffzUpdateTitle: function() {
var name = this.get('content.name'), var name = this.get('content.name'),
display_name = this.get('content.display_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; var f = this;
model.reopen({
ffz_host_target: undefined,
view.reopen({ setHostMode: function(e) {
didInsertElement: function() { if ( f.settings.hosted_channels ) {
this._super(); this.set('ffz_host_target', e.target);
try { return this._super(e);
this.ffzInit(); } else {
} catch(err) { this.set('ffz_host_target', undefined);
f.error("CIndex didInsertElement: " + err); 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'), var id = this.get('controller.content.id') || this.get('controller.id'),
el = this.get('element'); el = this.get('element');
@ -216,6 +183,10 @@ FFZ.prototype._modify_cindex = function(view) {
this.ffzUpdateHostButton(); this.ffzUpdateHostButton();
this.ffzUpdatePlayerStats(); 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)') var views = this.get('element').querySelector('.svg-glyph_views:not(.ffz-svg)')
if ( views ) if ( views )
views.parentNode.classList.add('twitch-channel-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() { ffzFixTitle: function() {
if ( f.has_bttv || ! f.settings.stream_title ) if ( f.has_bttv || ! f.settings.stream_title )
return; return;
@ -463,6 +469,15 @@ FFZ.prototype._modify_cindex = function(view) {
ffzUpdatePlayerStats: function() { 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'), var channel_id = this.get('controller.content.id') || this.get('controller.id'),
hosted_id = this.get('controller.hostModeTarget.id'), hosted_id = this.get('controller.hostModeTarget.id'),
@ -478,12 +493,12 @@ FFZ.prototype._modify_cindex = function(view) {
try { try {
player = player_cont && player_cont.get && player_cont.get('player'); player = player_cont && player_cont.get && player_cont.get('player');
stats = player && player.stats; stats = player && player.getVideoInfo();
} catch(err) { } 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 ) if ( stat_el )
stat_el.parentElement.removeChild(stat_el); stat_el.parentElement.removeChild(stat_el);
} else { } 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')}); 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 ) { if ( delay > 180 ) {
delay = Math.floor(delay); 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'; el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else { } else {
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps'); 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();
delay = stats.hlsLatencyBroadcaster; var ind = delay.indexOf('.');
var pos = delay.lastIndexOf('.'); if ( ind === -1 )
if ( pos === -1 )
delay = delay + '.00'; delay = delay + '.00';
else if ( delay.length - pos < 3 ) else if ( ind >= delay.length - 2 )
delay = delay + '0'; delay = delay + '0';
el.textContent = delay + 's'; 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 ) if ( stat_el )
stat_el.parentElement.removeChild(stat_el); stat_el.parentElement.removeChild(stat_el);
} else { } 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')}); 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 ) { if ( delay > 180 ) {
delay = Math.floor(delay); 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'; el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else { } else {
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps'); 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();
delay = stats.hlsLatencyBroadcaster; var ind = delay.indexOf('.');
var pos = delay.lastIndexOf('.'); if ( ind === -1 )
if ( pos === -1 )
delay = delay + '.00'; delay = delay + '.00';
else if ( delay.length - pos < 3 ) else if ( ind >= delay.length - 2 )
delay = delay + '0'; delay = delay + '0';
el.textContent = delay + 's'; 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); 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 = { FFZ.settings_info.chatter_count = {
type: "boolean", type: "boolean",
value: false, value: false,

View file

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

View file

@ -107,6 +107,7 @@ FFZ.settings_info.chat_batching = {
type: "select", type: "select",
options: { options: {
0: "No Batching", 0: "No Batching",
125: "Minimal (0.125s)",
250: "Minor (0.25s)", 250: "Minor (0.25s)",
500: "Normal (0.5s)", 500: "Normal (0.5s)",
750: "Large (0.75s)", 750: "Large (0.75s)",
@ -474,36 +475,7 @@ FFZ.prototype.setup_chatview = function() {
this.log("Hooking the Ember Chat view."); this.log("Hooking the Ember Chat view.");
this.update_views('view:chat', this.modify_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);
}
}
} }
@ -511,35 +483,10 @@ FFZ.prototype.setup_chatview = function() {
// Modify Chat View // Modify Chat View
// -------------------- // --------------------
FFZ.prototype._modify_cview = function(view) { FFZ.prototype.modify_chat_view = function(view) {
var f = this; var f = this;
utils.ember_reopen_view(view, {
view.reopen({ ffz_init: function() {
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() {
f._chatv = this; f._chatv = this;
var room_id = this.get('controller.currentRoom.id'), var room_id = this.get('controller.currentRoom.id'),
@ -567,7 +514,7 @@ FFZ.prototype._modify_cview = function(view) {
}, 1000); }, 1000);
}, },
ffzTeardown: function() { ffz_destroy: function() {
if ( f._chatv === this ) if ( f._chatv === this )
f._chatv = null; 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'); chan_table = this._ffz_chan_table || room_list.querySelector('#ffz-channel-table tbody');
if ( ! chan_table ) { if ( ! chan_table ) {
var tbl = document.createElement('table'); var tbl = utils.createElement('table', 'ffz');
tbl.setAttribute('cellspacing', '0'); tbl.setAttribute('cellspacing', '0');
tbl.id = 'ffz-channel-table'; 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>'; 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); room_list.insertBefore(tbl, room_list.firstChild);
@ -914,10 +860,9 @@ FFZ.prototype._modify_cview = function(view) {
// Group Chat Table // Group Chat Table
var group_table = this._ffz_group_table || room_list.querySelector('#ffz-group-table tbody'); var group_table = this._ffz_group_table || room_list.querySelector('#ffz-group-table tbody');
if ( ! group_table ) { if ( ! group_table ) {
var tbl = document.createElement('table'); var tbl = utils.createElement('table', 'ffz');
tbl.setAttribute('cellspacing', '0'); tbl.setAttribute('cellspacing', '0');
tbl.id = 'ffz-group-table'; tbl.id = 'ffz-group-table';
tbl.className = 'ffz';
tbl.innerHTML = '<thead><tr><th colspan="2">Group Chats</th></tr></thead><tbody></tbody>'; tbl.innerHTML = '<thead><tr><th colspan="2">Group Chats</th></tr></thead><tbody></tbody>';
var before = room_list.querySelector('#ffz-channel-table'); var before = room_list.querySelector('#ffz-channel-table');
@ -1007,8 +952,7 @@ FFZ.prototype._modify_cview = function(view) {
this.classList.toggle('active', !is_pinned); this.classList.toggle('active', !is_pinned);
}); });
} else { } else {
btn = document.createElement('a'); btn = utils.createElement('a', 'leave-chat html-tooltip');
btn.className = 'leave-chat html-tooltip';
btn.innerHTML = constants.CLOSE; btn.innerHTML = constants.CLOSE;
btn.title = 'Leave Group'; btn.title = 'Leave Group';
@ -1085,12 +1029,10 @@ FFZ.prototype._modify_cview = function(view) {
if ( f.has_bttv || ! f.settings.group_tabs ) if ( f.has_bttv || ! f.settings.group_tabs )
return; return;
var link = document.createElement('a'), var link = utils.createElement('a', 'button button--icon-only'),
view = this; view = this;
// Chat Room Management Button // Chat Room Management Button
link.className = 'button button--icon-only';
link.title = "Chat Room Management"; link.title = "Chat Room Management";
link.innerHTML = '<figure class="icon">' + constants.ROOMS + '</figure><span class="notifications"></span>'; link.innerHTML = '<figure class="icon">' + constants.ROOMS + '</figure><span class="notifications"></span>';
@ -1105,8 +1047,7 @@ FFZ.prototype._modify_cview = function(view) {
// Invite Button // Invite Button
link = document.createElement('a'), link = utils.createElement('a', 'button button--icon-only html-tooltip invite');
link.className = 'button button--icon-only html-tooltip invite';
link.title = "Invite a User"; link.title = "Invite a User";
link.innerHTML = '<figure class="icon">' + constants.INVITE + '</figure>'; link.innerHTML = '<figure class="icon">' + constants.INVITE + '</figure>';
@ -1216,6 +1157,10 @@ FFZ.prototype._modify_cview = function(view) {
now = Date.now(); 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 ) if ( is_current || is_channel || room_id === this._ffz_host || f.settings.group_tabs === 3 )
// Important Tabs // Important Tabs
return true; 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-minimize-conversations', this.settings.minimize_conversations);
document.body.classList.toggle('ffz-theatre-conversations', this.settings.hide_conversations_in_theatre); document.body.classList.toggle('ffz-theatre-conversations', this.settings.hide_conversations_in_theatre);
var ConvWindow = utils.ember_resolve('component:twitch-conversations/conversation-window'); this.update_views('component:twitch-conversations/conversation-window', this.modify_conversation_window);
if ( ConvWindow ) { this.update_views('component:twitch-conversations/conversation-settings-menu', this.modify_conversation_menu);
this.log("Hooking the Ember Conversation Window component."); this.update_views('component:twitch-conversations/conversation-line', this.modify_conversation_line);
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");
} }
FFZ.prototype._modify_conversation_menu = function(component) { FFZ.prototype.modify_conversation_menu = function(component) {
var f = this; var f = this;
utils.ember_reopen_view(component, {
component.reopen({ ffz_init: function() {
didInsertElement: function() {
var user = this.get('thread.otherUsername'), var user = this.get('thread.otherUsername'),
el = this.get('element'), el = this.get('element'),
sections = el && el.querySelectorAll('.options-section'); 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, var f = this,
Layout = utils.ember_lookup('service:layout'); Layout = utils.ember_lookup('service:layout');
component.reopen({ utils.ember_reopen_view(component, {
headerBadges: Ember.computed("thread.participants", "currentUsername", function() { headerBadges: Ember.computed("thread.participants", "currentUsername", function() {
return []; return [];
}), }),
@ -154,7 +128,7 @@ FFZ.prototype._modify_conversation_window = function(component) {
badge_el.innerHTML = f.render_badges(badges); badge_el.innerHTML = f.render_badges(badges);
}.observes('ffzHeaderBadges'), }.observes('ffzHeaderBadges'),
didInsertElement: function() { ffz_init: function() {
var el = this.get('element'), var el = this.get('element'),
header = el && el.querySelector('.conversation-header'), header = el && el.querySelector('.conversation-header'),
header_name = header && header.querySelector('.conversation-header-name'), 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, var f = this,
Layout = utils.ember_lookup('service:layout'); Layout = utils.ember_lookup('service:layout');
component.reopen({ utils.ember_reopen_view(component, {
tokenizedMessage: function() { tokenizedMessage: function() {
try { try {
return f.tokenize_conversation_line(this.get('message')); return f.tokenize_conversation_line(this.get('message'));
@ -204,7 +178,7 @@ FFZ.prototype._modify_conversation_line = function(component) {
}, },
didUpdate: function() { this.ffzRender() }, didUpdate: function() { this.ffzRender() },
didInsertElement: function() { this.ffzRender() }, ffz_init: function() { this.ffzRender() },
ffzRender: function() { ffzRender: function() {
var el = this.get('element'), var el = this.get('element'),

View file

@ -77,14 +77,44 @@ FFZ.settings_info.directory_group_hosts = {
}; };
FFZ.settings_info.banned_games = { FFZ.settings_info.enable_recommended_vods = {
type: "button", type: "boolean",
value: [], value: true,
category: "Directory", category: "Directory",
no_mobile: true, no_mobile: true,
name: "Banned Games", experiment_warn: true,
help: "A list of games that will not be displayed in the Directory.",
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() { on_update: function() {
var banned = this.settings.banned_games, 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); 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 = { FFZ.settings_info.spoiler_games = {
type: "button", visible: false,
value: [], 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() { on_update: function() {
var spoiled = this.settings.spoiler_games, var spoiled = this.settings.spoiler_games,
els = document.querySelectorAll('.ffz-directory-preview'); 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); 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-tags', this.settings.directory_creative_all_tags);
document.body.classList.toggle('ffz-creative-showcase', this.settings.directory_creative_showcase); document.body.classList.toggle('ffz-creative-showcase', this.settings.directory_creative_showcase);
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.log("Attempting to modify the Following collection.");
this._modify_following(); this._modify_following();
this.log("Hooking the Ember Directory views."); this.log("Hooking the Ember Directory views.");
var ChannelView = utils.ember_resolve('component:stream-preview'); this.update_views('component:stream-preview', function(x) { this.modify_directory_live(x, false) }, true);
if ( ChannelView ) { this.update_views('component:creative-preview', function(x) { this.modify_directory_live(x, false) }, true);
this._modify_directory_live(ChannelView, false); this.update_views('component:csgo-channel-preview', function(x) { this.modify_directory_live(x, true) }, true);
try { this.update_views('component:host-preview', this.modify_directory_host, true);
ChannelView.create().destroy(); this.update_views('component:video-preview', this.modify_video_preview, true);
} catch(err) { }
} else
this.log("Unable to locate the Ember component:stream-preview");
var CreativeChannel = utils.ember_resolve('component:creative-preview'); this.update_views('component:game-follow-button', this.modify_game_follow_button);
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);
}
}
} }
@ -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; var f = this;
component.reopen({ utils.ember_reopen_view(component, {
didInsertElement: function() { ffz_init: function() {
this._super();
this.ffzInit();
},
ffzInit: function() {
var el = this.get('element'), var el = this.get('element'),
game = this.get('game.id').toLowerCase(), game = this.get('game.id').toLowerCase(),
@ -436,37 +384,33 @@ FFZ.prototype._modify_game_follow = function(component) {
}; };
// Block Button // Block Button
var block = utils.createElement('div', 'follow-button ffz-block-button'), var block = utils.createElement('button', 'button tooltip ffz-block-button'),
block_link = utils.createElement('a', 'tooltip follow block');
update_block = function() { update_block = function() {
var is_blocked = f.settings.banned_games.indexOf(game) !== -1; 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.innerHTML = (is_blocked ? 'Unblock' : 'Block');
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.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(); update_block();
block_link.addEventListener('click', click_button('banned_games', update_block)); block.addEventListener('click', click_button('banned_games', update_block));
block.appendChild(block_link);
el.appendChild(block); el.appendChild(block);
// Spoiler Button // Spoiler Button
var spoiler = utils.createElement('div', 'follow-button ffz-spoiler-button'), var spoiler = utils.createElement('button', 'button tooltip ffz-spoiler-button'),
spoiler_link = utils.createElement('a', 'tooltip follow spoiler'),
update_spoiler = function() { update_spoiler = function() {
var is_spoiled = f.settings.spoiler_games.indexOf(game) !== -1; 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.innerHTML = (is_spoiled ? 'Show Thumbnails' : 'Hide Thumbnails');
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.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(); update_spoiler();
spoiler_link.addEventListener('click', click_button('spoiler_games', update_spoiler)); spoiler.addEventListener('click', click_button('spoiler_games', update_spoiler));
spoiler.appendChild(spoiler_link);
el.appendChild(spoiler); el.appendChild(spoiler);
jQuery('.tooltip', el).tipsy(); 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, var f = this,
pref = is_csgo ? 'channel.' : 'stream.'; pref = is_csgo ? 'channel.' : 'stream.';
var mutator = { utils.ember_reopen_view(component, {
didInsertElement: function() { ffz_init: function() {
this._super();
this.ffzInit();
},
ffzInit: function() {
var el = this.get('element'), var el = this.get('element'),
meta = el && el.querySelector('.meta'), meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'), thumb = el && el.querySelector('.thumb'),
@ -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 ) if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return; return;
var Channel = utils.ember_resolve('model:channel'); var Channel = utils.ember_resolve('model:deprecated-channel');
if ( ! Channel ) if ( ! Channel )
return; return;
e.preventDefault();
utils.ember_lookup('router:main').transitionTo('channel.index', Channel.find({id: channel_id}).load()); utils.ember_lookup('router:main').transitionTo('channel.index', Channel.find({id: channel_id}).load());
e.preventDefault();
return false; 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 ) { if ( this._ffz_uptime ) {
this._ffz_uptime.parentElement.removeChild(this._ffz_uptime); this._ffz_uptime.parentElement.removeChild(this._ffz_uptime);
this._ffz_uptime = null; this._ffz_uptime = null;
@ -557,8 +496,6 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
if ( this._ffz_image_timer ) if ( this._ffz_image_timer )
clearInterval(this._ffz_image_timer); clearInterval(this._ffz_image_timer);
this._super();
}, },
ffzRotateImage: function() { ffzRotateImage: function() {
@ -587,32 +524,14 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
this._ffz_uptime.innerHTML = ''; 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; var f = this;
vp.reopen({ utils.ember_reopen_view(component, {
didInsertElement: function() { ffz_init: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("component:video-preview ffzInit: " + err);
}
},
ffzInit: function() {
var el = this.get('element'), var el = this.get('element'),
game = this.get('video.game'), game = this.get('video.game'),
@ -659,30 +578,11 @@ FFZ.prototype._modify_video_preview = function(vp) {
} }
FFZ.prototype._modify_directory_host = function(dir) { FFZ.prototype.modify_directory_host = function(component) {
var f = this, mutator; var f = this;
utils.ember_reopen_view(component, {
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);
}
},
ffzVisitChannel: function(target, e) { ffzVisitChannel: function(target, e) {
var Channel = utils.ember_resolve('model:channel'); var Channel = utils.ember_resolve('model:deprecated-channel');
if ( ! Channel ) if ( ! Channel )
return; return;
@ -773,7 +673,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
this.$('.thumb .cap img').attr('src', url); this.$('.thumb .cap img').attr('src', url);
}, },
ffzCleanup: function() { ffz_destroy: function() {
var target = this.get('stream.target.channel'); var target = this.get('stream.target.channel');
if ( f._popup && f._popup.classList.contains('ffz-channel-selector') && f._popup.getAttribute('data-channel') === target ) if ( f._popup && f._popup.classList.contains('ffz-channel-selector') && f._popup.getAttribute('data-channel') === target )
f.close_popup(); f.close_popup();
@ -782,7 +682,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
clearInterval(this._ffz_image_timer); clearInterval(this._ffz_image_timer);
}, },
ffzInit: function() { ffz_init: function() {
var el = this.get('element'), var el = this.get('element'),
meta = el && el.querySelector('.meta'), meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'), thumb = el && el.querySelector('.thumb'),
@ -839,14 +739,5 @@ FFZ.prototype._modify_directory_host = function(dir) {
cap.addEventListener('click', this.ffzShowHostMenu.bind(this)); 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() { FFZ.prototype.setup_feed_cards = function() {
var FeedCard = utils.ember_resolve('component:channel-feed/card'); this.update_views('component:channel-feed/card', this.modify_feed_card);
if ( ! FeedCard ) this.update_views('component:channel-feed/comment', this.modify_feed_comment);
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.rerender_feed_cards(); this.rerender_feed_cards();
} }
@ -37,6 +30,7 @@ FFZ.prototype.setup_feed_cards = function() {
FFZ.prototype.rerender_feed_cards = function(for_set) { FFZ.prototype.rerender_feed_cards = function(for_set) {
var FeedCard = utils.ember_resolve('component:channel-feed/card'), var FeedCard = utils.ember_resolve('component:channel-feed/card'),
FeedComment = utils.ember_resolve('component:channel-feed/comment'),
views = utils.ember_views(); views = utils.ember_views();
if ( ! FeedCard ) if ( ! FeedCard )
@ -46,30 +40,31 @@ FFZ.prototype.rerender_feed_cards = function(for_set) {
var view = views[view_id]; var view = views[view_id];
if ( view instanceof FeedCard ) { if ( view instanceof FeedCard ) {
try { try {
if ( ! view.ffzInit ) if ( ! view.ffz_init )
this._modify_feed_card(view); this.modify_feed_card(view);
view.ffzInit(for_set); view.ffz_init(for_set);
} catch(err) { } 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 ) {
FFZ.prototype._modify_feed_card = function(component) {
var f = this;
component.reopen({
didInsertElement: function() {
this._super();
try { try {
this.ffzInit(); if ( ! view.ffz_init )
this.modify_feed_comment(view);
view.ffz_init(for_set);
} catch(err) { } catch(err) {
f.error("component:channel-feed/card ffzInit: " + err); this.error("setup component:channel-feed/comment ffzInit", err);
}
}
}
} }
},
ffzInit: function(for_set) {
FFZ.prototype.modify_feed_card = function(component) {
var f = this;
utils.ember_reopen_view(component, {
ffz_init: function(for_set) {
var el = this.get('element'), var el = this.get('element'),
message = this.get('post.body'), message = this.get('post.body'),
emotes = parse_emotes(this.get('post.emotes')), 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. // Refresh all existing following data.
var count = 0, var count = 0,
Channel = utils.ember_resolve('model:channel'); Channel = utils.ember_resolve('model:deprecated-channel');
if ( Channel && Channel._cache ) if ( Channel && Channel._cache )
for(var key in Channel._cache) { for(var key in Channel._cache) {

View file

@ -240,16 +240,26 @@ FFZ.prototype.setup_layout = function() {
}.observes("isTooSmallForRightColumn"), }.observes("isTooSmallForRightColumn"),
ffzUpdateCss: function() { 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 ( ! f.has_bttv ) {
if ( this.get('isRightColumnClosed') ) if ( this.get('isRightColumnClosed') )
out = ''; out += 'top: 0; right: 0}';
else { else {
if ( this.get('portraitMode') ) { if ( this.get('portraitMode') ) {
var size = this.get('playerSize'), var size = this.get('playerSize'),
video_below = this.get('portraitVideoBelow'), video_below = this.get('portraitVideoBelow'),
window_height = this.get('windowHeight'),
window_width = this.get('windowWidth'),
video_height = size[1] + 120 + 60, video_height = size[1] + 120 + 60,
chat_height = window_height - video_height, 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_video_top = video_below ? theatre_chat_height : 0,
theatre_chat_top = video_below ? 0 : theatre_video_height; 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 { overflow: hidden }' +
'body[data-current-path^="user."] #left_col .warp,' + 'body[data-current-path^="user."] #left_col .warp,' +
'body[data-current-path^="user."] #left_col,' + 'body[data-current-path^="user."] #left_col,' +
@ -290,7 +301,9 @@ FFZ.prototype.setup_layout = function() {
} else { } else {
var width = this.get('rightColumnWidth'); 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}' + '#right_col{width:' + width + 'px}' +
'body:not(.ffz-sidebar-swap) #main_col:not(.expandRight){' + 'body:not(.ffz-sidebar-swap) #main_col:not(.expandRight){' +
'margin-right:' + width + 'px}' + 'margin-right:' + width + 'px}' +
@ -317,7 +330,8 @@ FFZ.prototype.setup_layout = function() {
ffzFixTabs: function() { ffzFixTabs: function() {
if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) { if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) {
setTimeout(function() { 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); },0);
} }
}.observes("isRightColumnClosed", "rightColumnWidth", "portraitMode", "playerSize") }.observes("isRightColumnClosed", "rightColumnWidth", "portraitMode", "playerSize")

View file

@ -51,6 +51,18 @@ FFZ.settings_info.replace_bad_emotes = {
}; };
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 = { FFZ.settings_info.parse_emoji = {
type: "select", type: "select",
options: { options: {
@ -74,7 +86,7 @@ FFZ.settings_info.parse_emoji = {
category: "Chat Appearance", category: "Chat Appearance",
name: "Emoji Display", name: "Display Emoji",
help: "Replace emoji in chat messages with nicer looking images from either Twitter or Google." help: "Replace emoji in chat messages with nicer looking images from either Twitter or Google."
}; };
@ -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 = { FFZ.settings_info.chat_padding = {
type: "boolean", type: "boolean",
value: false, value: false,
@ -588,6 +613,7 @@ FFZ.prototype.setup_line = function() {
// Chat Enhancements // Chat Enhancements
document.body.classList.toggle('ffz-alias-italics', this.settings.alias_italics); 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-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); 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 ) if ( is_whisper || this_ul >= other_ul || f.settings.mod_buttons.length === 0 )
return ''; 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++) { for(var i=0, l = f.settings.mod_buttons.length; i < l; i++) {
var pair = f.settings.mod_buttons[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 ( btn === false ) {
if ( deleted ) 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 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 ) } 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 { else {
if ( typeof btn === "string" ) { 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>'); tip = "Custom Command" + (cmd.indexOf("\n") !== -1 ? 's' : '') + '<br>' + utils.quote_san(cmd).replace('\n','<br>');
} else { } else {
cmd = "/timeout " + user + " " + btn; cmd = "/timeout " + user + " " + btn;
tip = "Timeout User (" + utils.duration_string(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 // System Message
if ( system_msg ) { if ( system_msg ) {
output += '<div class="system-msg">' + utils.sanitize(system_msg) + '</div>'; output += '<div class="system-msg">' + utils.sanitize(system_msg) + '</div>';
if ( this.get('shouldRenderMessageBody') === false ) if ( this.get('ffzShouldRenderMessageBody') === false )
return output; return output;
} }
// Timestamp // Timestamp
output += '<span class="timestamp float-left">' + this.get('timestamp') + '</span> '; output += '<span class="timestamp">' + this.get('timestamp') + '</span> ';
// Moderator Actions // Moderator Actions
output += this.buildModIconsHTML(); output += this.buildModIconsHTML();
// Badges // 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 // Alias Support
var alias = f.aliases[user], var alias = f.aliases[user],
@ -842,7 +868,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
} else } else
output = '<span class="message">'; 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'); var old_messages = this.get('msgObject.ffz_old_messages');
if ( old_messages && old_messages.length ) if ( old_messages && old_messages.length )
@ -856,7 +882,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
output = this.buildSenderHTML(); output = this.buildSenderHTML();
// If this is a whisper, or if we should render the message body, render it. // 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') ) if ( this.get('msgObject.deleted') )
output += this.buildDeletedMessageHTML() output += this.buildDeletedMessageHTML()
else else
@ -865,6 +891,14 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
el.innerHTML = output; el.innerHTML = output;
}, },
ffzShouldRenderMessageBody: function() {
return ! this.get('hasSystemMsg') || this.get('hasMessageBody');
}.property('hasSystemMsg', 'hasMessageBody'),
shouldRenderMessageBody: function() {
return false;
}.property('hasSystemMsg', 'hasMessageBody'),
ffzWasDeleted: function() { ffzWasDeleted: function() {
return f.settings.prevent_clear && this.get("msgObject.ffz_deleted") return f.settings.prevent_clear && this.get("msgObject.ffz_deleted")
}.property("msgObject.ffz_deleted"), }.property("msgObject.ffz_deleted"),
@ -883,8 +917,8 @@ FFZ.prototype._modify_chat_subline = function(component) {
this._modify_chat_line(component); this._modify_chat_line(component);
component.reopen({ component.reopen({
classNameBindings: [":message-line", ":chat-line", "msgObject.style", "msgObject.ffz_has_mention:ffz-mentioned", "ffzWasDeleted:ffz-deleted", "ffzHasOldMessages:clearfix", "ffzHasOldMessages:ffz-has-deleted"], classNameBindings: ["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"], attributeBindings: ["msgObject.tags.id:data-id", "msgObject.room:data-room", "msgObject.from:data-sender", "msgObject.deleted:data-deleted"],
didInsertElement: function() { didInsertElement: function() {
this.set('msgObject._line', this); this.set('msgObject._line', this);
@ -1024,10 +1058,10 @@ FFZ.prototype._modify_vod_line = function(component) {
if ( ! this.get("isViewerModeratorOrHigher") || this.get("isModeratorOrHigher") ) if ( ! this.get("isViewerModeratorOrHigher") || this.get("isModeratorOrHigher") )
return ""; return "";
return '<span class="mod-icons float-left">' + return '<span class="mod-icons">' +
(this.get('msgObject.deleted') ? (this.get('msgObject.deleted') ?
'<em class="mod-icon float-left unban"></em>' : '<em class="mod-icon unban"></em>' :
'<a class="mod-icon float-left html-tooltip delete" title="Delete Message" href="#">Delete</a>') + '</span>'; '<a class="mod-icon html-tooltip delete" title="Delete Message" href="#">Delete</a>') + '</span>';
}, },
buildDeletedMesageHTML: function() { buildDeletedMesageHTML: function() {

View file

@ -353,7 +353,8 @@ FFZ.settings_info.mod_buttons = {
"Custom In-Line Moderation Icons", "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. " + "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 " + "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 " + "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>" + "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>" + "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"); helpers = window.require && window.require("web-client/helpers/chat/chat-line-helpers");
} catch(err) { } } catch(err) { }
this.log("Listening to the Settings controller to catch mod icon state changes."); this.log("Listening to the Settings controller to catch mod icon state changes.");
var f = this, var f = this,
Settings = utils.ember_lookup('controller:settings'); Settings = utils.ember_lookup('controller:settings');
@ -594,7 +594,6 @@ FFZ.prototype.setup_mod_card = function() {
f.settings.set('chat_mod_icon_visibility', 1); f.settings.set('chat_mod_icon_visibility', 1);
}); });
this.log("Modifying Mousetrap stopCallback so we can catch ESC."); this.log("Modifying Mousetrap stopCallback so we can catch ESC.");
var orig_stop = Mousetrap.stopCallback; var orig_stop = Mousetrap.stopCallback;
Mousetrap.stopCallback = function(e, element, combo) { Mousetrap.stopCallback = function(e, element, combo) {
@ -609,11 +608,13 @@ FFZ.prototype.setup_mod_card = function() {
el && el.classList.toggle('ffz-flip'); el && el.classList.toggle('ffz-flip');
}); });
this.log("Hooking the Ember Moderation Card view."); 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() { ffzForceRedraw: function() {
this.rerender(); this.rerender();
if ( f.settings.mod_card_history ) 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(); return alias || this.get("cardInfo.user.display_name") || user_id.capitalize();
}), }),
willDestroy: function() { ffz_destroy: function() {
if ( f._mod_card === this ) if ( f._mod_card === this )
f._mod_card = undefined; f._mod_card = undefined;
utils.update_css(f._chat_style, 'mod-card-highlight'); utils.update_css(f._chat_style, 'mod-card-highlight');
this._super();
}, },
didInsertElement: function() { ffz_init: function() {
this._super();
try {
if ( f.has_bttv ) if ( f.has_bttv )
return; return;
@ -744,11 +741,10 @@ FFZ.prototype.setup_mod_card = function() {
// Info-tize it! // Info-tize it!
if ( f.settings.mod_card_info ) { if ( f.settings.mod_card_info ) {
var info = document.createElement('div'), var info = utils.createElement('div', 'info channel-stats'),
after = el.querySelector('h3.name'); after = el.querySelector('h3.name');
if ( after ) { if ( after ) {
el.classList.add('ffz-has-info'); el.classList.add('ffz-has-info');
info.className = 'info channel-stats';
after.parentElement.insertBefore(info, after.nextSibling); after.parentElement.insertBefore(info, after.nextSibling);
this.ffzRebuildInfo(); this.ffzRebuildInfo();
} }
@ -756,8 +752,7 @@ FFZ.prototype.setup_mod_card = function() {
// Additional Buttons // Additional Buttons
if ( is_mod && f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) { if ( is_mod && f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) {
line = document.createElement('div'); line = utils.createElement('div', 'extra-interface interface clearfix');
line.className = 'extra-interface interface clearfix';
var cmds = {}, var cmds = {},
add_btn_click = function(cmd) { add_btn_click = function(cmd) {
@ -884,8 +879,7 @@ FFZ.prototype.setup_mod_card = function() {
}, },
btn_make = function(timeout) { btn_make = function(timeout) {
var btn = document.createElement('button') var btn = utils.createElement('button', 'button ffz-no-bg');
btn.className = 'button ffz-no-bg';
btn.innerHTML = utils.duration_string(timeout); btn.innerHTML = utils.duration_string(timeout);
btn.title = "Timeout User for " + utils.number_commas(timeout) + " Second" + (timeout != 1 ? "s" : ""); 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 ) { if ( f.settings.mod_card_durations && f.settings.mod_card_durations.length ) {
// Extra Moderation // Extra Moderation
line = document.createElement('div'); line = utils.createElement('div', 'extra-interface interface clearfix');
line.className = 'extra-interface interface clearfix';
line.appendChild(btn_make(1)); line.appendChild(btn_make(1));
var s = document.createElement('span'); var s = utils.createElement('span', 'right');
s.className = 'right';
line.appendChild(s); line.appendChild(s);
for(var i=0; i < f.settings.mod_card_durations.length; i++) 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'); ban_btn.setAttribute('title', '(B)an User');
// Unban Button // Unban Button
var unban_btn = document.createElement('button'); var unban_btn = utils.createElement('button', 'unban button button--icon-only light');
unban_btn.className = 'unban button button--icon-only light';
unban_btn.innerHTML = '<figure class="icon">' + CHECK + '</figure>'; unban_btn.innerHTML = '<figure class="icon">' + CHECK + '</figure>';
unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User"; unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User";
@ -970,7 +960,7 @@ FFZ.prototype.setup_mod_card = function() {
// Follow Button // Follow Button
var follow_button = el.querySelector(".interface > .follow-button"); var follow_button = el.querySelector(".follow-button");
if ( follow_button ) if ( follow_button )
jQuery(follow_button).tipsy({title: function() { return follow_button.classList.contains('is-following') ? "Unfollow" : "Follow"}}); 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')}); jQuery(msg_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
var real_msg = document.createElement('button'); var real_msg = utils.createElement('button', 'message-button button button--icon-only message html-tooltip');
real_msg.className = 'message-button button button--icon-only message html-tooltip';
real_msg.innerHTML = '<figure class="icon">' + MESSAGE + '</figure>'; real_msg.innerHTML = '<figure class="icon">' + MESSAGE + '</figure>';
real_msg.title = "Message User"; real_msg.title = "Message User";
@ -1000,8 +989,7 @@ FFZ.prototype.setup_mod_card = function() {
// Alias Button // Alias Button
var alias_btn = document.createElement('button'); var alias_btn = utils.createElement('button', 'alias button button--icon-only html-tooltip');
alias_btn.className = 'alias button button--icon-only html-tooltip';
alias_btn.innerHTML = '<figure class="icon">' + constants.EDIT + '</figure>'; alias_btn.innerHTML = '<figure class="icon">' + constants.EDIT + '</figure>';
alias_btn.title = "Set Alias"; alias_btn.title = "Set Alias";
@ -1057,12 +1045,6 @@ FFZ.prototype.setup_mod_card = function() {
}}); }});
el.focus(); el.focus();
} catch(err) {
try {
f.error("ModerationCardView didInsertElement: " + err);
} catch(err) { }
}
}, },
ffzReposition: function() { ffzReposition: function() {
@ -1104,8 +1086,7 @@ FFZ.prototype.setup_mod_card = function() {
history = el && el.querySelector('.chat-history'); history = el && el.querySelector('.chat-history');
if ( ! history ) { if ( ! history ) {
history = document.createElement('ul'); history = utils.createElement('ul', 'interface clearfix chat-history');
history.className = 'interface clearfix chat-history';
el.appendChild(history); el.appendChild(history);
} else { } else {
history.classList.remove('loading'); history.classList.remove('loading');
@ -1118,7 +1099,7 @@ FFZ.prototype.setup_mod_card = function() {
if ( ! success ) if ( ! success )
return; 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, var i = data.length,
was_at_top = history && history.scrollTop >= (history.scrollHeight - history.clientHeight), was_at_top = history && history.scrollTop >= (history.scrollHeight - history.clientHeight),
@ -1211,10 +1192,9 @@ FFZ.prototype.setup_mod_card = function() {
logs.innerHTML = ''; logs.innerHTML = '';
} else { } else {
logs = document.createElement('ul'); logs = utils.createElement('ul', 'interface clearfix chat-history adjacent-history');
back = document.createElement('button'); back = utils.createElement('button', 'button ffz-no-bg back-button');
back.className = 'button ffz-no-bg back-button';
back.innerHTML = '&laquo; Back'; back.innerHTML = '&laquo; Back';
back.addEventListener('click', function() { back.addEventListener('click', function() {
@ -1222,12 +1202,10 @@ FFZ.prototype.setup_mod_card = function() {
back.parentElement.removeChild(back); back.parentElement.removeChild(back);
history.classList.remove('hidden'); 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; msg.from_server = true;
var line_time = line.date.getTime() - (line.from_server ? 0 : (f._ws_server_offset || 0)), 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 = ''; style = '', colored = '';
if ( helpers && helpers.getTime ) 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], var alias = this.aliases[msg.from],
@ -1284,7 +1262,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
if ( show_from ) { if ( show_from ) {
// Badges // 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(this.render_badges(this.get_line_badges(msg, false)));
out.push('</span>'); 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-room', msg.room);
l_el.setAttribute('data-sender', msg.from); 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.setAttribute('data-deleted', msg.deleted || false);
l_el.innerHTML = out.join(""); 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.", 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) { 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 ) if ( ! this._cindex )
return; return;
@ -79,36 +73,7 @@ FFZ.prototype.setup_player = function() {
this.players = {}; this.players = {};
var Player2 = utils.ember_resolve('component:twitch-player2'); this.update_views('component:twitch-player2', this.modify_twitch_player);
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);
}
}
} }
@ -116,29 +81,22 @@ FFZ.prototype.setup_player = function() {
// Component // Component
// --------------- // ---------------
FFZ.prototype._modify_player = function(player) { FFZ.prototype.modify_twitch_player = function(player) {
var f = this, var f = this;
update_stats = function() { utils.ember_reopen_view(player, {
f._cindex && f._cindex.ffzUpdatePlayerStats(); ffz_init: function() {
}; var id = this.get('channel.id');
f.players[id] = this;
player.reopen({ var player = this.get('player');
didInsertElement: function() { if ( player )
this._super(); this.ffzPostPlayer();
try {
this.ffzInit();
} catch(err) {
f.error("Player2 didInsertElement: " + err);
}
}, },
willClearRender: function() { ffz_destroy: function() {
try { var id = this.get('channel.id');
this.ffzTeardown(); if ( f.players[id] === this )
} catch(err) { f.players[id] = undefined;
f.error("Player2 willClearRender: " + err);
}
this._super();
}, },
postPlayerSetup: function() { postPlayerSetup: function() {
@ -150,24 +108,21 @@ FFZ.prototype._modify_player = function(player) {
} }
}, },
ffzInit: function() { ffzRecreatePlayer: function() {
var id = this.get('channel.id'); var player = this.get('player'),
f.players[id] = this; theatre = player && player.getTheatre();
var player = this.get('player'); // Tell the player to destroy itself.
if ( player ) if ( player )
this.ffzPostPlayer(); player.destroy();
},
ffzTeardown: function() { // Break down everything left over from that player.
var id = this.get('channel.id'); this.$('#video-1').html('');
if ( f.players[id] === this ) Mousetrap.unbind(['alt+x', 'alt+t', 'esc']);
f.players[id] = undefined; this.set('player', null);
if ( this._ffz_stat_interval ) { // Now, let Twitch create a new player as usual.
clearInterval(this._ffz_stat_interval); Ember.run.next(this.insertPlayer.bind(this));
this._ffz_stat_interval = null;
}
}, },
ffzPostPlayer: function() { ffzPostPlayer: function() {
@ -175,74 +130,29 @@ FFZ.prototype._modify_player = function(player) {
if ( ! player ) if ( ! player )
return; return;
// Make the stats window draggable and fix the button. // Make the stats window draggable and fix the button.
var stats = this.$('.player .js-playback-stats'); var stats = this.$('.player .js-playback-stats');
stats.draggable({cancel: 'li', containment: 'parent'}); stats.draggable({cancel: 'li', containment: 'parent'});
// Add an option to the menu to recreate the player.
// 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;
var t = this, var t = this,
player = this.get('player'); el = this.$('.player-menu .player-menu__item--stats')[0],
container = el && el.parentElement;
if ( ! player ) if ( el && ! container.querySelector('.js-player-reset') ) {
return; 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. btn_link.addEventListener('click', function(e) {
if ( player.setStatsEnabled ) { t.ffzRecreatePlayer();
player.ffzSetStatsEnabled = player.setStatsEnabled; e.preventDefault();
try { return false;
player.ffz_stats = player.getStatsEnabled(); });
} catch(err) {
// Assume stats are off.
f.log("Player2 ffzInitStats: getStatsEnabled still doesn't work.");
player.ffz_stats = false;
}
player.setStatsEnabled = function(e, s) { container.insertBefore(btn, el.nextSibling);
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);
} }
} }
}); });

View file

@ -50,7 +50,8 @@ FFZ.prototype.setup_room = function() {
if ( RC ) { if ( RC ) {
var orig_ban = RC._actions.banUser, var orig_ban = RC._actions.banUser,
orig_to = RC._actions.timeoutUser; orig_to = RC._actions.timeoutUser,
orig_show = RC._actions.showModOverlay;
RC._actions.banUser = function(e) { RC._actions.banUser = function(e) {
orig_ban.call(this, e); orig_ban.call(this, e);
@ -62,12 +63,15 @@ FFZ.prototype.setup_room = function() {
this.get("model").clearMessages(e.user, null, true); 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 // 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. // 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."); this.log("Hooking the Ember Room view.");
this.update_views('view:room', this.modify_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);
}
}
} }
@ -145,29 +123,10 @@ FFZ.prototype.setup_room = function() {
// View Customization // View Customization
// -------------------- // --------------------
FFZ.prototype._modify_rview = function(view) { FFZ.prototype.modify_room_view = function(view) {
var f = this; var f = this;
view.reopen({ utils.ember_reopen_view(view, {
didInsertElement: function() { ffz_init: 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() {
f._roomv = this; f._roomv = this;
this.ffz_frozen = false; this.ffz_frozen = false;
@ -196,6 +155,34 @@ FFZ.prototype._modify_rview = function(view) {
var controller = this.get('controller'); var controller = this.get('controller');
if ( controller ) { if ( controller ) {
controller.reopen({ 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() { submitButtonText: function() {
if ( this.get("model.isWhisperMessage") && this.get("model.isWhispersEnabled") ) if ( this.get("model.isWhisperMessage") && this.get("model.isWhispersEnabled") )
return i18n("Whisper"); return i18n("Whisper");
@ -214,7 +201,7 @@ FFZ.prototype._modify_rview = function(view) {
} }
}, },
ffzTeardown: function() { ffz_destroy: function() {
if ( f._roomv === this ) if ( f._roomv === this )
f._roomv = undefined; f._roomv = undefined;
@ -280,7 +267,7 @@ FFZ.prototype._modify_rview = function(view) {
btn.classList.toggle('ffz-banned', (room && room.get('ffz_banned') || false)); 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++) { for(var i=0; i < STATUS_BADGES.length; i++) {
info = STATUS_BADGES[i]; info = STATUS_BADGES[i];
id = 'ffz-stat-' + info[0]; id = 'ffz-stat-' + info[0];
@ -289,13 +276,18 @@ FFZ.prototype._modify_rview = function(view) {
if ( typeof visible === "string" ) if ( typeof visible === "string" )
visible = visible === "1"; visible = visible === "1";
label = typeof info[3] === "function" ? info[3].call(f, room) : undefined;
if ( ! badge ) { 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; badge.id = id;
jQuery(badge).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'se')}); jQuery(badge).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'se')});
cont.appendChild(badge); 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.title = typeof info[2] === "function" ? info[2].call(f, room) : info[2];
badge.classList.toggle('hidden', ! visible); badge.classList.toggle('hidden', ! visible);
if ( visible ) if ( visible )
@ -494,7 +486,6 @@ FFZ.prototype._modify_rview = function(view) {
warning.classList.remove('hidden'); warning.classList.remove('hidden');
}, },
ffzUnwarnPaused: function() { ffzUnwarnPaused: function() {
var el = this.get('element'), var el = this.get('element'),
warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator'); warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
@ -502,7 +493,6 @@ FFZ.prototype._modify_rview = function(view) {
if ( warning ) if ( warning )
warning.classList.add('hidden'); 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) { FFZ.prototype.update_room_important = function(id, controller) {
var Chat = controller || utils.ember_lookup('controller:chat'), var Chat = controller || utils.ember_lookup('controller:chat'),
current_room = Chat && Chat.get('currentChannelRoom'),
room = this.rooms[id]; room = this.rooms[id];
if ( ! room ) if ( ! room )
return; 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(); 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 ) if ( from_server )
msg.from_server = true; msg.from_server = true;
@ -815,7 +806,7 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
return true; return true;
// Display the Ban Reason if we're a moderator or that user. // 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.message = msg.message.substr(0, msg.message.length - 1) + ' with reason: ' + msg.tags['ban-reason'];
msg.cachedTokens = [utils.sanitize(msg.message)]; msg.cachedTokens = [utils.sanitize(msg.message)];
} }
@ -834,6 +825,12 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
if ( ! first_inserted ) if ( ! first_inserted )
first_inserted = msg; 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); messages.unshiftObject(msg);
inserted += 1; inserted += 1;
@ -870,6 +867,11 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
if ( r.shouldShowMessage(msg) ) { if ( r.shouldShowMessage(msg) ) {
messages.insertAt(inserted, msg); messages.insertAt(inserted, msg);
while ( messages.length > buffer_size ) { 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); messages.removeAt(0);
removed++; removed++;
} }
@ -1042,6 +1044,7 @@ FFZ.prototype._modify_room = function(room) {
try { try {
f.add_room(this.id, this); f.add_room(this.id, this);
this.set("ffz_chatters", {}); this.set("ffz_chatters", {});
this.set("ffz_ids", this.get('ffz_ids') || {});
} catch(err) { } catch(err) {
f.error("add_room: " + err); f.error("add_room: " + err);
} }
@ -1063,6 +1066,7 @@ FFZ.prototype._modify_room = function(room) {
if ( user ) { if ( user ) {
var duration = Infinity, var duration = Infinity,
reason = undefined, reason = undefined,
msg_id = undefined,
current_user = f.get_user(), current_user = f.get_user(),
is_me = current_user && current_user.login === user; is_me = current_user && current_user.login === user;
@ -1077,6 +1081,18 @@ FFZ.prototype._modify_room = function(room) {
reason = tags['ban-reason']; 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 we were banned, set the state and update the UI.
if ( is_me ) { if ( is_me ) {
t.set('ffz_banned', true); t.set('ffz_banned', true);
@ -1098,6 +1114,44 @@ FFZ.prototype._modify_room = function(room) {
t.ffzRecentlyBanned.shift(); 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 // Delete Visible Messages
var msgs = t.get('messages'), var msgs = t.get('messages'),
total = msgs.get('length'), total = msgs.get('length'),
@ -1106,9 +1160,12 @@ FFZ.prototype._modify_room = function(room) {
while(i--) { while(i--) {
var msg = msgs.get(i); var msg = msgs.get(i);
if ( msg.from === user ) { if ( msg.from === user ) {
if ( f.settings.remove_deleted ) { 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); msgs.removeAt(i);
removed++; removed++;
continue; continue;
@ -1133,6 +1190,7 @@ FFZ.prototype._modify_room = function(room) {
msg.removed = f.settings.remove_deleted; msg.removed = f.settings.remove_deleted;
} }
} }
}
// Now we need to see about displaying a ban notice. // Now we need to see about displaying a ban notice.
@ -1164,6 +1222,7 @@ FFZ.prototype._modify_room = function(room) {
date: now, date: now,
ffz_ban_target: user, ffz_ban_target: user,
reasons: reason ? [reason] : [], reasons: reason ? [reason] : [],
msg_ids: msg_id ? [msg_id] : [],
durations: [duration], durations: [duration],
end_time: end_time, end_time: end_time,
timeouts: 1, timeouts: 1,
@ -1176,6 +1235,9 @@ FFZ.prototype._modify_room = function(room) {
this.addMessage(msg); this.addMessage(msg);
} else { } 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 ) if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason); last_ban.reasons.push(reason);
@ -1205,6 +1267,9 @@ FFZ.prototype._modify_room = function(room) {
last_ban = null; last_ban = null;
if ( last_ban ) { 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 ) if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason); last_ban.reasons.push(reason);
@ -1223,6 +1288,7 @@ FFZ.prototype._modify_room = function(room) {
date: now, date: now,
ffz_ban_target: user, ffz_ban_target: user,
reasons: reason ? [reason] : [], reasons: reason ? [reason] : [],
msg_ids: msg_id ? [msg_id] : [],
durations: [duration], durations: [duration],
end_time: end_time, end_time: end_time,
timeouts: 1, timeouts: 1,
@ -1256,8 +1322,17 @@ FFZ.prototype._modify_room = function(room) {
len = messages.get("length"), len = messages.get("length"),
limit = this.get("messageBufferSize"); limit = this.get("messageBufferSize");
if ( len > limit ) if ( len > limit ) {
messages.removeAt(0, len - limit); 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 // Artificial chat delay
@ -1267,6 +1342,7 @@ FFZ.prototype._modify_room = function(room) {
this.ffzPending = []; this.ffzPending = [];
var now = msg.time = Date.now(); var now = msg.time = Date.now();
msg.pending = true;
this.ffzPending.push(msg); this.ffzPending.push(msg);
this.ffzSchedulePendingFlush(now); this.ffzSchedulePendingFlush(now);
@ -1320,12 +1396,18 @@ FFZ.prototype._modify_room = function(room) {
for (var i = 0, l = this.ffzPending.length; i < l; i++) { for (var i = 0, l = this.ffzPending.length; i < l; i++) {
var msg = this.ffzPending[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; continue;
}
if ( f.settings.chat_delay !== 0 && (f.settings.chat_delay + msg.time > now) ) if ( f.settings.chat_delay !== 0 && (f.settings.chat_delay + msg.time > now) )
break; break;
msg.pending = false;
this.ffzActualPushMessage(msg); 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) { addMessage: function(msg) {
if ( msg ) { if ( msg ) {
var is_resub = msg.tags && msg.tags['msg-id'] === 'resub', 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. // 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'])) ) 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 ) if ( f._chat_filters[i](msg) === false )
return; 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. // Report this message to the dashboard.
if ( window !== window.parent && parent.postMessage && msg.from && msg.from !== "jtv" && msg.from !== "twitchnotify" ) 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/"); 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() { 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 // 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 ) if ( VODService )
VODService.reopen({ VODService.reopen({
messageBufferSize: f.settings.scrollback_length, messageBufferSize: f.settings.scrollback_length,
@ -50,56 +44,59 @@ FFZ.prototype.setup_vod_chat = function() {
else else
f.error("Unable to locate VOD Chat Service."); f.error("Unable to locate VOD Chat Service.");
// Get the VOD Chat Display this.update_views('component:vod-right-column', this.modify_vod_right_column);
var VODChat = utils.ember_resolve('component:vod-chat-display'); this.update_views('view:vod', this.modify_vod_view);
this.update_views('component:vod-chat-display', this.modify_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);
}
}
}
} }
FFZ.prototype._modify_vod_right_column = function(component) { FFZ.prototype.modify_vod_view = function(view) {
var f = this; var f = this;
utils.ember_reopen_view(view, {
ffz_init: function() {
f._vodv = this;
component.reopen({ var channel_id = this.get('context.channel.name');
didInsertElement: function() {
this._super(); if ( f.settings.auto_theater ) {
try { var player = f.players && f.players[channel_id] && f.players[channel_id].get('player');
this.ffzInit(); if ( player )
} catch(err) { player.setTheatre(true);
f.error("VODRightColumn didInsertElement: " + err); }
// 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 ) { if ( f.settings.dark_twitch ) {
var el = this.get('element'), var el = this.get('element'),
cont = el && el.querySelector('.chat-container'); 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, var f = this,
VODService = utils.ember_lookup('service:vod-chat-service'); VODService = utils.ember_lookup('service:vod-chat-service');
component.reopen({ utils.ember_reopen_view(component, {
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("VODChat didInsertElement: " + err);
}
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("VODChat willClearRender: " + err);
}
this._super();
},
_prepareToolTips: function() { _prepareToolTips: function() {
this.$(".tooltip").tipsy({ this.$(".tooltip").tipsy({
live: true, live: true,
@ -142,7 +121,7 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
}) })
}, },
ffzInit: function() { ffz_init: function() {
f._vodc = this; f._vodc = this;
// Load the room, if necessary // Load the room, if necessary
@ -153,22 +132,9 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
this.ffz_frozen = false; this.ffz_frozen = false;
if ( f.settings.chat_hover_pause ) if ( f.settings.chat_hover_pause )
this.ffzEnableFreeze(); 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 ) if ( f._vodc === this )
f._vodc = undefined; 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) { ffzMouseOut: function(event) {
this._ffz_outside = true; this._ffz_outside = true;
var e = this; 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, var FFZ = window.FrankerFaceZ,
utils = require('../utils'), utils = require('../utils'),
build_css = function(emote) { build_css = function(emote) {
if ( ! emote.margins && ! emote.css ) if ( ! emote.margins && ! emote.css )
return ""; return "";
@ -50,11 +49,15 @@ var API = FFZ.API = function(instance, name, icon, version) {
this.global_sets = []; this.global_sets = [];
this.default_sets = []; this.default_sets = [];
this.badges = {};
this.users = {}; this.users = {};
this.chat_filters = []; this.chat_filters = [];
this.on_room_callbacks = []; this.on_room_callbacks = [];
this.name = name || ("Extension#" + this.id); this.name = name || ("Extension#" + this.id);
this.name_key = this.name.replace(/[^A-Z0-9_\-]/g, '').toLowerCase();
this.icon = icon || null; this.icon = icon || null;
this.version = version || 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 // User Modifications
// ----------------------- // -----------------------
API.prototype.user_add_set = function(user_name, set_id) { API.prototype.user_add_badge = function(username, slot, badge_id) {
var user = this.users[user_name] = this.users[user_name] || {}, var user = this.users[username] = this.users[username] || {},
ffz_user = this.ffz.users[user_name] = this.ffz.users[user_name] || {}, 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 || [], emote_sets = user.sets = user.sets || [],
ffz_sets = ffz_user.sets = ffz_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. // Update tab completion.
var user = this.ffz.get_user(); 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'); Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
} }
API.prototype.user_remove_set = function(user_name, set_id) { API.prototype.user_remove_set = function(username, set_id) {
var user = this.users[user_name], var user = this.users[username],
ffz_user = this.ffz.users[user_name], ffz_user = this.ffz.users[username],
emote_sets = user && user.sets, emote_sets = user && user.sets,
ffz_sets = ffz_user && ffz_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. // Update tab completion.
var user = this.ffz.get_user(); 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'); 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); this.ffz._chat_filters.splice(ind, 1);
} }
// ----------------------- // -----------------------
// Channel Callbacks // Channel Callbacks
// ----------------------- // -----------------------

View file

@ -96,6 +96,10 @@ FFZ.prototype.setup_bttv = function(delay) {
this.toggle_style('chat-hc-background');*/ this.toggle_style('chat-hc-background');*/
this.toggle_style('chat-colors-gray'); 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-transparent');
this.toggle_style('badges-sub-notice'); this.toggle_style('badges-sub-notice');
this.toggle_style('badges-sub-notice-on'); this.toggle_style('badges-sub-notice-on');
@ -114,12 +118,15 @@ FFZ.prototype.setup_bttv = function(delay) {
} }
// Send Message Behavior // Send Message Behavior
var original_send = BetterTTV.chat.helpers.sendMessage, f = this; var f = this,
BetterTTV.chat.helpers.sendMessage = function(message) { BC = BetterTTV.chat,
original_send = BC.helpers.sendMessage;
BC.helpers.sendMessage = function(message) {
var cmd = message.split(' ', 1)[0].toLowerCase(); var cmd = message.split(' ', 1)[0].toLowerCase();
if ( cmd === "/ffz" ) if ( cmd === '/ffz' )
f.run_ffz_command(message.substr(5), BetterTTV.chat.store.currentRoom); f.run_ffz_command(message.substr(5), BC.store.currentRoom);
else else
return original_send(message); 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 // Ugly Hack for Current Room, as this is stripped out before we get to
// the actual privmsg renderer. // the actual privmsg renderer.
var original_handler = BetterTTV.chat.handlers.onPrivmsg, var original_handler = BC.handlers.onPrivmsg,
received_room; received_room;
BetterTTV.chat.handlers.onPrivmsg = function(room, data) {
BC.handlers.onPrivmsg = function(room, data) {
received_room = room; received_room = room;
var output = original_handler(room, data); var output = original_handler(room, data);
received_room = null; received_room = null;
@ -138,39 +146,48 @@ FFZ.prototype.setup_bttv = function(delay) {
// Message Display Behavior // Message Display Behavior
var original_privmsg = BetterTTV.chat.templates.privmsg; var original_privmsg = BC.templates.privmsg;
BetterTTV.chat.templates.privmsg = function(highlight, action, server, isMod, data) { BC.templates.privmsg = function(data, opts) {
try { try {
opts = opts || {};
// Handle badges. // Handle badges.
f.bttv_badges(data); f.bttv_badges(data);
// Now, do everything else manually because things are hard-coded. // 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+'">'+ return '<div class="chat-line'+(opts.highlight?' highlight':'')+(opts.action?' action':'')+(opts.server?' admin':'')+'" data-sender="'+(data.sender||"").toLowerCase()+'" data-room="'+received_room+'">'+
BetterTTV.chat.templates.timestamp(data.time)+' '+ BC.templates.timestamp(data.time)+' '+
(isMod?BetterTTV.chat.templates.modicons():'')+' '+ (opts.isMod ? BC.templates.modicons():'')+' '+
BetterTTV.chat.templates.badges(data.badges)+ BC.templates.badges(data.badges)+
BetterTTV.chat.templates.from(data.nickname, data.color)+ BC.templates.from(data.nickname, data.color)+
BetterTTV.chat.templates.message(data.sender, data.message, data.emotes, action?data.color:false)+ BC.templates.message(data.sender, data.message, {
emotes: data.emotes,
colored: (opts.action && !opts.highlight) ? data.color : false,
bits: data.bits
}) +
'</div>'; '</div>';
} catch(err) { } catch(err) {
f.log("Error: ", err); f.log("Error: ", err);
return original_privmsg(highlight, action, server, isMod, data); return original_privmsg(data, opts);
} }
} }
// Whispers too! // Whispers too!
var original_whisper = BetterTTV.chat.templates.whisper; var original_whisper = BC.templates.whisper;
BetterTTV.chat.templates.whisper = function(data) { BC.templates.whisper = function(data) {
try { try {
// Handle badges. // Handle badges.
f.bttv_badges(data); f.bttv_badges(data);
// Now, do everything else manually because things are hard-coded. // Now, do everything else manually because things are hard-coded.
return '<div class="chat-line whisper" data-sender="' + data.sender + '">' + return '<div class="chat-line whisper" data-sender="' + data.sender + '">' +
BetterTTV.chat.templates.timestamp(data.time) + ' ' + BC.templates.timestamp(data.time) + ' ' +
(data.badges && data.badges.length ? BetterTTV.chat.templates.badges(data.badges) : '') + (data.badges && data.badges.length ? BC.templates.badges(data.badges) : '') +
BetterTTV.chat.templates.whisperName(data.sender, data.receiver, data.from, data.to, data.fromColor, data.toColor) + BC.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.message(data.sender, data.message, {
emotes: data.emotes,
colored: false
}) +
'</div>'; '</div>';
} catch(err) { } catch(err) {
f.log("Error: ", 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 // Message Renderer. I had to completely rewrite this method to get it to
// use my replacement emoticonizer. // use my replacement emoticonizer.
var original_message = BetterTTV.chat.templates.message, var original_message = BC.templates.message,
received_sender; received_sender;
BetterTTV.chat.templates.message = function(sender, message, emotes, colored) { BC.templates.message = function(sender, message, data) {
try { try {
colored = colored || false; var colored = data.colored || false,
var rawMessage = encodeURIComponent(message); force = data.force || false,
emotes = data.emotes,
rawMessage = encodeURIComponent(message);
if(sender !== 'jtv') { if(sender !== 'jtv') {
// Hackilly send our state across. // Hackilly send our state across.
received_sender = sender; received_sender = sender;
var tokenizedMessage = BetterTTV.chat.templates.emoticonize(message, emotes); var tokenizedMessage = BC.templates.emoticonize(message, emotes);
received_sender = null; received_sender = null;
for(var i=0; i<tokenizedMessage.length; i++) { for(var i=0; i<tokenizedMessage.length; i++) {
if(typeof tokenizedMessage[i] === 'string') { 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 { } else {
tokenizedMessage[i] = tokenizedMessage[i][0]; tokenizedMessage[i] = tokenizedMessage[i][0];
} }
@ -204,7 +223,14 @@ FFZ.prototype.setup_bttv = function(delay) {
message = tokenizedMessage.join(' '); 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) { } catch(err) {
f.log("Error: ", err); f.log("Error: ", err);
return original_message(sender, message, emotes, colored); return original_message(sender, message, emotes, colored);
@ -247,8 +273,8 @@ FFZ.prototype.setup_bttv = function(delay) {
} }
// Emoticonize // Emoticonize
var original_emoticonize = BetterTTV.chat.templates.emoticonize; var original_emoticonize = BC.templates.emoticonize;
BetterTTV.chat.templates.emoticonize = function(message, emotes) { BC.templates.emoticonize = function(message, emotes) {
var tokens = original_emoticonize(message, emotes), var tokens = original_emoticonize(message, emotes),
room = (received_room || BetterTTV.getChannel()), 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 // Version
var VER = FFZ.version_info = { var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 216, major: 3, minor: 5, revision: 247,
toString: function() { toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
} }
@ -167,8 +167,9 @@ require('./badges');
require('./tokenize'); require('./tokenize');
//require('./filtering'); //require('./filtering');
require('./ember/wrapper');
require('./ember/router'); require('./ember/router');
require('./ember/bits');
require('./ember/channel'); require('./ember/channel');
require('./ember/player'); require('./ember/player');
require('./ember/room'); require('./ember/room');
@ -188,7 +189,6 @@ require('./ember/sidebar');
require('./debug'); require('./debug');
//require('./ext/rechat');
require('./ext/betterttv'); require('./ext/betterttv');
require('./ext/emote_menu'); require('./ext/emote_menu');
@ -214,6 +214,7 @@ require('./ui/about_page');
require('./commands'); require('./commands');
require('./ext/api'); require('./ext/api');
require('./ext/warpworld');
// --------------- // ---------------
@ -404,6 +405,7 @@ FFZ.prototype.init_ember = function(delay) {
// Initialize all the modules. // Initialize all the modules.
this.load_settings(); this.load_settings();
this.setup_ember_wrapper();
// Start this early, for quick loading. // Start this early, for quick loading.
this.setup_dark(); this.setup_dark();
@ -426,6 +428,7 @@ FFZ.prototype.init_ember = function(delay) {
this.setup_room(); this.setup_room();
this.setup_vod_chat(); this.setup_vod_chat();
this.setup_line(); this.setup_line();
this.setup_bits();
this.setup_layout(); this.setup_layout();
this.setup_chatview(); this.setup_chatview();
this.setup_conversations(); this.setup_conversations();
@ -446,10 +449,13 @@ FFZ.prototype.init_ember = function(delay) {
this.setup_following_count(true); this.setup_following_count(true);
this.setup_races(); this.setup_races();
// Do all Ember modification before this point.
this.finalize_ember_wrapper();
this.fix_tooltips(); this.fix_tooltips();
this.connect_extra_chat(); this.connect_extra_chat();
//this.setup_rechat();
this.setup_message_event(); this.setup_message_event();
this.find_bttv(10); this.find_bttv(10);
this.find_emote_menu(10); this.find_emote_menu(10);

View file

@ -9,6 +9,23 @@ var FFZ = window.FrankerFaceZ,
return "ffz_setting_" + key; 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) { 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)); 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" ) 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, 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) { return function(view, container) {
var f = this, var f = this,
settings = {}, settings = {},
categories = []; 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) { for(var key in settings_data) {
var info = settings_data[key], var info = settings_data[key],
cat = info.category || "Miscellaneous", cat = info.category || "Miscellaneous",
@ -254,7 +330,7 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
return 0; 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++) { for(var ci=0; ci < categories.length; ci++) {
var category = categories[ci], var category = categories[ci],
@ -274,15 +350,23 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
if ( collapsable ) { if ( collapsable ) {
menu.classList.add('collapsable'); menu.classList.add('collapsable');
menu.classList.toggle('collapsed', current_category !== category); menu.classList.toggle('collapsed', current_category !== category);
menu.addEventListener('click', function() { menu.addEventListener('click', function(e) {
var t = this; var t = this;
if ( ! t.classList.contains('collapsed') ) if ( ! t.classList.contains('collapsable') )
return; 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"); jQuery(".chat-menu-content:not(.collapsed)", container).addClass("collapsed");
t.classList.remove('collapsed'); t.classList.remove('collapsed');
if ( collapsed_key ) if ( collapsed_key )
f[collapsed_key] = t.getAttribute('data-category'); f[collapsed_key] = t.getAttribute('data-category');
}
setTimeout(function(){t.scrollIntoViewIfNeeded()}); setTimeout(function(){t.scrollIntoViewIfNeeded()});
}); });
@ -313,15 +397,26 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
for(var i=0; i < cset.length; i++) { for(var i=0; i < cset.length; i++) {
var key = cset[i][0], var key = cset[i][0],
info = cset[i][1], info = cset[i][1],
el = createElement('p'), 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); 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 ) { if ( this.has_bttv && info.no_bttv ) {
bttv_skipped.push([info.name, info.help]); bttv_skipped.push([info.name, info.help]);
continue; continue;
} else { } 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" ) { if ( info.type === "boolean" ) {
var swit = createElement('a'), var swit = createElement('a'),
label = createElement('span'); label = createElement('span');
@ -334,6 +429,8 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
label.innerHTML = info.name; label.innerHTML = info.name;
el.appendChild(swit); el.appendChild(swit);
if ( show_pin )
el.appendChild(pin_btn);
el.appendChild(label); el.appendChild(label);
swit.addEventListener('click', toggle_setting.bind(this, swit, key, info)) 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)); select.addEventListener('change', option_setting.bind(this, select, key, info));
if ( show_pin )
el.appendChild(pin_btn);
el.appendChild(label); el.appendChild(label);
el.appendChild(select); el.appendChild(select);
@ -364,6 +463,9 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
var link = createElement('a'); var link = createElement('a');
link.innerHTML = info.name; link.innerHTML = info.name;
link.href = '#'; link.href = '#';
if ( show_pin )
el.appendChild(pin_btn);
el.appendChild(link); el.appendChild(link);
link.addEventListener('click', info.method.bind(this)); link.addEventListener('click', info.method.bind(this));
@ -371,14 +473,27 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
} else } else
continue; 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'); var help = document.createElement('span');
help.className = 'help'; 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); el.appendChild(help);
} }
} }
// Search by any of the present text.
el.setAttribute('data-filter', el.textContent.toLowerCase());
added++; added++;
menu.appendChild(el); menu.appendChild(el);
} }
@ -397,8 +512,9 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
help.className = 'help'; help.className = 'help';
for(var i=0; i < bttv_skipped.length; i++) { for(var i=0; i < bttv_skipped.length; i++) {
var skipped = bttv_skipped[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>'; 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); 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_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 = { FFZ.menu_pages.settings = {
@ -423,12 +545,42 @@ FFZ.menu_pages.settings = {
sort_order: 99999, sort_order: 99999,
wide: true, 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: { 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: { basic: {
name: "Basic", name: "Basic",
sort_order: 1, sort_order: 2,
render: function(view, container) { render: function(view, container) {
this.settings.set("advanced_settings", false); this.settings.set("advanced_settings", false);
@ -438,7 +590,7 @@ FFZ.menu_pages.settings = {
advanced: { advanced: {
name: "Advanced", name: "Advanced",
sort_order: 2, sort_order: 3,
render: function(view, container) { render: function(view, container) {
this.settings.set("advanced_settings", true); this.settings.set("advanced_settings", true);
@ -448,7 +600,7 @@ FFZ.menu_pages.settings = {
backup: { backup: {
name: "Backup & Restore", name: "Backup & Restore",
sort_order: 3, sort_order: 4,
render: function(view, container) { render: function(view, container) {
var backup_head = createElement('div'), var backup_head = createElement('div'),

View file

@ -1,3 +1,3 @@
.badges .badge:not(.subscriber):not(.unknown-badge) { .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; border-radius: 9px;
background-size: 16px; background-size: 16px;
background-repeat: no-repeat; background-repeat: no-repeat;

View file

@ -4,15 +4,19 @@
/* Invert Some Badges */ /* 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%); filter: invert(75%);
-webkit-filter: invert(75%); -webkit-filter: invert(75%);
} }
.ffz-dark .badge, .ffz-dark .badge:not(.invert-invert),
.theatre .badge, .theatre .badge:not(.invert-invert),
.dark .badge, .dark .badge:not(.invert-invert),
.force-dark .badge { .force-dark .badge:not(.invert-invert) {
filter: none !important; filter: none !important;
-webkit-filter: none !important; -webkit-filter: none !important;
} }

View file

@ -4,6 +4,8 @@ var FFZ = window.FrankerFaceZ,
helpers, helpers,
conv_helpers, conv_helpers,
emote_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.', 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, 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+))?(.*?)(?:\?(.*?))?(?:\#(.*?))?$/, LINK_SPLIT = /^(?:(https?):\/\/)?(?:(.*?)@)?([^\/:]+)(?::(\d+))?(.*?)(?:\?(.*?))?(?:\#(.*?))?$/,
YOUTUBE_CHECK = /^(?:https?:\/\/)?(?:m\.|www\.)?youtu(?:be\.com|\.be)\/(?:v\/|watch\/|.*?(?:embed|watch).*?v=)?([a-zA-Z0-9\-_]+)$/, 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))?$/, 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 = { FFZ.settings_info.show_deleted_links = {
type: "boolean", type: "boolean",
value: false, value: false,
@ -155,6 +171,14 @@ FFZ.prototype.setup_tokenization = function() {
if ( ! helpers ) if ( ! helpers )
return this.log("Unable to get chat helper functions."); 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 { try {
conv_helpers = window.require && window.require("web-client/helpers/twitch-conversations/conversation-line-helpers"); conv_helpers = window.require && window.require("web-client/helpers/twitch-conversations/conversation-line-helpers");
} catch(err) { } catch(err) {
@ -268,7 +292,31 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
FFZ.prototype.render_tooltip = function(el) { FFZ.prototype.render_tooltip = function(el) {
var f = this, var f = this,
func = function() { 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, var preview_url, width=0, height=0, image, set_id, emote, emote_set,
emote_id = this.getAttribute('data-ffz-emote'); emote_id = this.getAttribute('data-ffz-emote');
if ( emote_id ) { if ( emote_id ) {
@ -356,17 +404,29 @@ FFZ.prototype.render_tooltip = function(el) {
} else if ( this.classList.contains('chat-link') ) { } else if ( this.classList.contains('chat-link') ) {
// TODO: A lot of shit. Lookup data. // TODO: A lot of shit. Lookup data.
var url = this.getAttribute("data-url"), var url = this.getAttribute("data-url"),
data = url && f._link_data[url],
preview_url = null,
preview_iframe = true,
image = '',
text = ''; text = '';
if ( ! url ) if ( ! url )
return; return;
if ( f.settings.link_image_hover && is_image(url, f.settings.image_hover_all_domains) ) // Do we have data?
preview_url = url; if ( data && data !== true ) {
else text = data.html;
preview_url = null; 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 it's not a deleted link, don't waste time showing the URL in the tooltip.
if ( this.classList.contains('deleted-link') ) 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 ) if ( user && user.login && helpers && helpers.mentionizeMessage )
tokens = helpers.mentionizeMessage(tokens, user.login, from_me); 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); tokens = helpers.emoticonizeMessage(tokens, emotes);
// FrankerFaceZ Extras // FrankerFaceZ Extras
tokens = this._remove_banned(tokens); 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); tokens = this.tokenize_emotes(from_user, undefined, tokens, from_me);
if ( this.settings.parse_emoji ) 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 ) if ( user && user.login && helpers && helpers.mentionizeMessage )
tokens = helpers.mentionizeMessage(tokens, user.login, from_me); 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); tokens = helpers.emoticonizeMessage(tokens, emotes);
// FrankerFaceZ Extras // FrankerFaceZ Extras
tokens = this._remove_banned(tokens); 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); tokens = this.tokenize_emotes(from_user, room_id, tokens, from_me);
if ( this.settings.parse_emoji ) 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 ) if ( msgObject.cachedTokens )
return msgObject.cachedTokens; return msgObject.cachedTokens;
@ -495,12 +559,17 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
from_user = msgObject.from, from_user = msgObject.from,
user = this.get_user(), user = this.get_user(),
from_me = user && from_user === user.login, from_me = user && from_user === user.login,
emotes = msgObject.tags && msgObject.tags.emotes, tags = msgObject.tags || {},
emotes = tags.emotes,
tokens = [msg]; tokens = [msg];
// Standard tokenization // Standard Tokenization
if ( use_bits && helpers && helpers.tokenizeBits ) if ( tags.bits && bits_helpers && bits_helpers.tokenizeBits )
tokens = helpers.tokenizeBits(tokens); tokens = bits_helpers.tokenizeBits(tokens);
// For Later
//if ( helpers && helpers.tokenizeRichContent )
// tokens = helpers.tokenizeRichContent(tokens, tags.content, delete_links);
if ( helpers && helpers.linkifyMessage ) { if ( helpers && helpers.linkifyMessage ) {
var labels = msgObject.labels || [], 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 ) if ( user && user.login && helpers && helpers.mentionizeMessage )
tokens = helpers.mentionizeMessage(tokens, user.login, from_me); 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); tokens = helpers.emoticonizeMessage(tokens, emotes);
// FrankerFaceZ Extras // FrankerFaceZ Extras
tokens = this._remove_banned(tokens); 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); tokens = this.tokenize_emotes(from_user, room_id, tokens, from_me);
if ( this.settings.parse_emoji ) if ( this.settings.parse_emoji )
tokens = this.tokenize_emoji(tokens); tokens = this.tokenize_emoji(tokens);
// Capitalization // Capitalization
var display = msgObject.tags && msgObject.tags['display-name']; var display = tags['display-name'];
if ( display && display.length ) if ( display && display.length )
FFZ.capitalization[from_user] = [display.trim(), Date.now()]; 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); 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); message = this.tokenize_emotes(user, room, message);
if ( this.settings.parse_emoji && ! no_emoji ) 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 ) if ( helpers && helpers.linkifyMessage )
message = helpers.linkifyMessage(message); message = helpers.linkifyMessage(message);
if ( helpers && helpers.emoticonizeMessage ) if ( helpers && helpers.emoticonizeMessage && this.settings.parse_emoticons )
message = helpers.emoticonizeMessage(message, emotes); message = helpers.emoticonizeMessage(message, emotes);
// Tokenize Lines // 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) tokens = this.tokenize_emotes(user_id, room_id, tokens)
if ( this.settings.parse_emoji ) 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 ) if ( ! token )
return ""; return "";
@ -764,7 +853,24 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) {
if (!( this._link_data && this._link_data[href] )) { if (!( this._link_data && this._link_data[href] )) {
this._link_data = this._link_data || {}; this._link_data = this._link_data || {};
this._link_data[href] = true; 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>'; 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" ) 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_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>`; //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); return utils.sanitize(token.text);
else if ( typeof token !== "string" ) 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>">[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>">[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 utils.sanitize(token); return utils.sanitize(token);
} }
FFZ.prototype.render_tokens = function(tokens, render_links, warn_links) { FFZ.prototype.render_tokens = function(tokens, render_links, warn_links, render_bits) {
return _.map(tokens, this.render_token.bind(this, render_links, warn_links)).join(""); 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 // 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; var i = history.length, was_cleared = false;
purged = purged || {}; purged = purged || {};
bad_ids = bad_ids || {};
while(i--) { while(i--) {
var msg = history[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 ) if ( is_deleted && ! this.settings.prevent_clear )
msg.deleted = true; 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 === '@@' ) if ( msg.tags && msg.tags.target === '@@' )
was_cleared = true; 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; purged[msg.tags.target] = true;
}
// Per-line // Per-line
if ( per_line && ! per_line(msg) ) if ( per_line && ! per_line(msg) )

View file

@ -131,6 +131,10 @@ var update_player_stats = function(player, container) {
if ( ! player_data ) if ( ! player_data )
return; return;
try {
player_data.backend = player.getBackend();
} catch(err) { player_data.backend = undefined }
var sorted_keys = Object.keys(player_data).sort(); var sorted_keys = Object.keys(player_data).sort();
for(var i=0; i < sorted_keys.length; i++) { for(var i=0; i < sorted_keys.length; i++) {
var key = sorted_keys[i], var key = sorted_keys[i],
@ -309,6 +313,9 @@ FFZ.menu_pages.about = {
['Deploy Flavor', SiteOptions.deploy_flavor] ['Deploy Flavor', SiteOptions.deploy_flavor]
], ],
exp_head = createElement('div'),
experiments = createElement('ul'),
has_memory = window.performance && performance.memory, has_memory = window.performance && performance.memory,
mem_head = createElement('div'), mem_head = createElement('div'),
mem_list = createElement('ul'), mem_list = createElement('ul'),
@ -346,8 +353,8 @@ FFZ.menu_pages.about = {
heading.className = 'chat-menu-content center'; heading.className = 'chat-menu-content center';
heading.innerHTML = '<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">woofs for nerds</div>'; 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_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 = player_list.className = vers.className = 'chat-menu-content menu-side-padding version-list'; 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'; info_head.innerHTML = 'Client Status';
@ -394,6 +401,21 @@ FFZ.menu_pages.about = {
twitch.appendChild(line); 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'; ver_head.innerHTML = 'Versions';
if ( this.has_bttv ) if ( this.has_bttv )
@ -431,6 +453,11 @@ FFZ.menu_pages.about = {
container.appendChild(twitch_head); container.appendChild(twitch_head);
container.appendChild(twitch); container.appendChild(twitch);
if ( exp_service ) {
container.appendChild(exp_head);
container.appendChild(experiments);
}
if ( has_memory ) { if ( has_memory ) {
mem_head.innerHTML = 'Memory Statistics'; mem_head.innerHTML = 'Memory Statistics';
setTimeout(update_mem_stats.bind(this,mem_list),0); 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(); return this._following_get_me();
this.log("Connecting to Live Streams model."); this.log("Connecting to Live Streams model.");
var Stream = utils.ember_resolve('model:stream'); var Stream = utils.ember_resolve('model:deprecated-stream');
if ( ! Stream ) if ( ! Stream )
return this.log("Unable to find Stream model."); 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~! // Iterate all existing tipsy stuff~!
this.log('Fixing already existing tooltips.'); this.log('Fixing already existing tooltips.');
if ( ! window.jQuery || ! jQuery.cache ) if ( ! window.jQuery || ! jQuery.cache )

View file

@ -2,7 +2,20 @@ var FFZ = window.FrankerFaceZ,
constants = require('./constants'); 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 = function(msg) {
sanitize_el.textContent = msg; sanitize_el.textContent = msg;
@ -206,25 +219,18 @@ var sanitize_el = document.createElement('span'),
// Dialogs // Dialogs
show_modal = function(contents, on_close, width) { show_modal = function(contents, on_close, width) {
var container = document.createElement('div'), var container = createElement('div', 'twitch_subwindow_container'),
subwindow = document.createElement('div'), subwindow = createElement('div', 'twitch_subwindow ffz-subwindow'),
card = document.createElement('div'), card = createElement('div', 'card'),
close_button = document.createElement('div'), close_button = createElement('div', 'modal-close-button', constants.CLOSE),
closer = function() { container.parentElement.removeChild(container) }; closer = function() { container.parentElement.removeChild(container) };
container.className = 'twitch_subwindow_container';
container.id = 'ffz-modal-container'; container.id = 'ffz-modal-container';
subwindow.className = 'twitch_subwindow ffz-subwindow';
subwindow.style.width = '100%'; subwindow.style.width = '100%';
subwindow.style.maxWidth = (width||420) + 'px'; 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() { close_button.addEventListener('click', function() {
closer(); closer();
if ( on_close ) if ( on_close )
@ -265,6 +271,7 @@ var sanitize_el = document.createElement('span'),
module.exports = FFZ.utils = { module.exports = FFZ.utils = {
// Ember Manipulation
ember_views: function() { ember_views: function() {
return ember_lookup('-view-registry:main') || {}; return ember_lookup('-view-registry:main') || {};
}, },
@ -281,6 +288,38 @@ module.exports = FFZ.utils = {
return App.__container__.resolve(thing); 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_srcset: build_srcset,
/*build_tooltip: build_tooltip, /*build_tooltip: build_tooltip,
load_emote_data: load_emote_data,*/ load_emote_data: load_emote_data,*/
@ -294,18 +333,52 @@ module.exports = FFZ.utils = {
show_modal: show_modal, 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) { prompt: function(title, description, old_value, callback, width, input) {
var contents = document.createElement('div'), var contents = createElement('div', 'text-content'),
heading = document.createElement('div'), heading = createElement('div', 'content-header', '<h4>' + title + '</h4>'),
form = document.createElement('form'), form = createElement('form'),
close_btn, okay_btn; close_btn, okay_btn;
contents.className = 'text-content';
heading.className = 'content-header';
heading.innerHTML = '<h4>' + title + '</h4>';
if ( ! input ) { if ( ! input ) {
input = document.createElement('input'); input = createElement('input');
input.type = 'text'; input.type = 'text';
} }
@ -540,18 +613,19 @@ module.exports = FFZ.utils = {
escape_regex: escape_regex, escape_regex: escape_regex,
createElement: function(tag, className, content) { createElement: createElement,
var out = document.createElement(tag);
if ( className )
out.className = className;
if ( content )
out.innerHTML = content;
return out;
},
toggle_cls: function(cls) { toggle_cls: function(cls) {
return function(val) { return function(val) {
document.body.classList.toggle(cls, 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; 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 { .ffz-theater-stats .app-main.theatre .button.button--icon-only svg path {
fill: #a68ed2; 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); 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, .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, .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, .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; } .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content.menu-side-padding { padding-left: 20px; padding-right: 20px; }
.emoticon-grid.collapsed span, .emoticon-grid.collapsable.collapsed span,
.chat-menu-content.collapsed p { display: none; } .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 .chat-menu-content.collapsable.collapsed .heading,
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid.collapsed .heading { .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid.collapsable.collapsed .heading {
padding-bottom: 0; padding-bottom: 0;
} }
.emoticon-grid.collapsable .heading, .emoticon-grid.collapsable .heading,
.emoticon-grid.collapsed, .emoticon-grid.collapsable.collapsed,
.chat-menu-content.collapsed { .chat-menu-content.collapsable.collapsed,
.chat-menu-content.collapsable .heading {
cursor: pointer; cursor: pointer;
} }
@ -617,6 +619,25 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
position: relative; 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; } .list-header span.right { float: right; }
.ember-chat .chat-menu .list-header.sub-header { .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 .right button:last-of-type,
.ffz-moderation-card .mod-controls:last-of-type button:last-of-type { margin-right: 0 } .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; font-size: 0 !important;
padding-right: 0 !important; padding-right: 0 !important;
} }
@ -1413,6 +1434,8 @@ img.channel_background[src="null"] { display: none; }
/* Chat Rows */ /* Chat Rows */
.chat-line { overflow: hidden }
.theatre .conversation-window .conversation-chat-line, .theatre .conversation-window .conversation-chat-line,
.dark .chat-line, .dark .chat-line,
.force-dark .chat-line, .force-dark .chat-line,
@ -1456,6 +1479,7 @@ body:not(.ffz-bttv) .chat-container:not(.chatReplay) .more-messages-indicator {
padding: 0; padding: 0;
} }
/* Emoticon Tooltips */ /* Emoticon Tooltips */
.ffz-wide-tip hr { .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 .theatre .conversation-system-message,
.ffz-no-blue.ffz-dark .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 .warp,
.ffz-no-blue #large_nav .content, .ffz-no-blue #large_nav .content,
.ffz-no-blue #small_nav .content, .ffz-no-blue #small_nav .content,
@ -2370,6 +2402,14 @@ li[data-name="following"] a {
/* Image Tooltips */ /* Image Tooltips */
.ffz-clip-title {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 5px;
}
.ffz-image-hover { .ffz-image-hover {
border:none; border:none;
max-width: 186px; max-width: 186px;
@ -2802,14 +2842,17 @@ body:not(.ffz-top-conversations) .conversations-list-bottom-bar {
/* Creative Directory */ /* Creative Directory */
.ffz-top-conversations .ct-banner { margin-top: -50px } /* Make sure the fancy banner art goes all the way up */
.ffz-top-conversations .ct-banner__feature { top: 40px; margin-bottom: 0 } .ffz-top-conversations .ct-banner-container { margin-top: -50px }
body:not(.ffz-tags-on-channel) #broadcast-meta .ct-tags,
body:not(.ffz-creative-showcase) .ct-gallery { display: none; }
body:not(.ffz-tags-on-channel) .tw-title--tall { height: 60px } 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! */ /* Content-Box~! Down with Twitch randomly changing the box-sizing for everything! */
#ffz-channel-table *, #ffz-channel-table *,
@ -2874,33 +2917,42 @@ body:not([data-current-path^="directory.csgo"]):not([data-current-path^="directo
background-size: cover !important; background-size: cover !important;
} }
.follow-button.ffz-block-button { .directory_header .ffz-block-button,
margin-left: 2px; .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 { body[data-current-path^="directory.creative"] .follow-button.ffz-block-button {
margin: 0 1rem; margin: 0 1rem;
} }
.follow-button.ffz-spoiler-button .spoiler, .button.ffz-spoiler-button,
.follow-button.ffz-block-button .block { .button.ffz-block-button {
background: #555; background: #555;
padding: 0 10px;
} }
.follow-button.ffz-spoiler-button .spoiler.active { .button.ffz-spoiler-button.active {
background: #006700; background: #006700;
} }
.follow-button.ffz-spoiler-button .spoiler:not(.disabled):hover { .button.ffz-spoiler-button:not(.disabled):hover {
background: #247324; background: #247324;
} }
.follow-button.ffz-block-button .block.active { .button.ffz-block-button.active {
background: #973333; background: #973333;
} }
.follow-button.ffz-block-button .block:not(.disabled):hover { .button.ffz-block-button:not(.disabled):hover {
background: #a94444; background: #a94444;
} }
@ -2946,6 +2998,8 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
/* Badges */ /* Badges */
.badges .badge { background-size: 18px 18px }
/*.badges .badge { /*.badges .badge {
height: 18px; height: 18px;
min-width: 18px; min-width: 18px;
@ -3001,18 +3055,41 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
/* Odd Badges */ /* Odd Badges */
.badge.click_url { cursor: pointer } .badge.click_url { cursor: pointer }
.badge.warcraft.version-alliance { .badge.bits.version-1 {
background: url("https://cdn.frankerfacez.com/badges/twitch/warcraft/alliance/1.png") #004094; 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/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: -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/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/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 { .badge.bits.version-100 {
background: url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/1.png") #ab0016; 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/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: -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/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/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 */ /* 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 { .force-dark .chat-room .show-mod-icons .chat-line:not(.admin) .mod-icons {
background-color: rgba(0,0,0,0.8); 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;
}