1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00

3.5.266. Yeah. This is me remembering to commit again, but only because I told someone to make a pull request.

This commit is contained in:
SirStendec 2016-08-09 20:45:28 -04:00
parent 86546ba7d8
commit 8310f89aa0
25 changed files with 717 additions and 592 deletions

4
.gitignore vendored
View file

@ -2,6 +2,10 @@ node_modules
npm-debug.log
build
Extension Building
Old Files
badges
cdn
.idea
*.iml
script.js

View file

@ -1,3 +1,93 @@
<div class="list-header">3.5.266</div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Option to automatically pause hosted channels as an alternative to disabling hosting entirely. This is enabled by default.</li>
<li>Fixed: The Bits UI would not properly hide when switching to a group chat.</li>
<li>Fixed: Chat Delay would appear as <code>NaN</code> for group chats.</li>
</ul>
<div class="list-header">3.5.265</div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Implement support for Twitch's new room-specific chat delay property.</li>
</ul>
<div class="list-header">3.5.264</div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Check for a bootleg version of the BTTV4FFZ extension and warn users.</li>
<li>Changed: Expand the 24-hour Timestamps option to allow for zero-padding. It has been renamed to Timestamp Format.</li>
<li>Changed: Update the link matching regex to match the one used by Twitch. (Now it matches parenthesis.)</li>
</ul>
<div class="list-header">3.5.263</div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Someone was cheeky enough to make an emote called <code>hasOwnProperty</code>, which is a JavaScript function named and it broke things slightly.</li>
<li>Fixed: The Viewer List component has long been renamed, so the code to put Broadcaster off by itself wasn't working. Refactored to be faster too.</li>
</ul>
<div class="list-header">3.5.262</div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Potential issue that would cause the Ember application to stop responding when browsing the Following directory.</li>
</ul>
<div class="list-header">3.5.261</div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Friends list not being properly hidden when sidebar collapsed with that option enabled.</li>
<li>Fixed: Try fixing the bug causing chat rooms to spontaneously unload for some users. (Still not guaranteed fixed)</li>
</ul>
<div class="list-header">3.5.260</div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Tooltips bugging out for navigation.</li>
<li>Fixed: Sorting order for additional follow buttons beneath a stream.</li>
<li>Fixed: Channel Feed rendering with emoji in messages.</li>
</ul>
<div class="list-header">3.5.259</div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Display Dropped Frames on the Stream Latency tooltip.</li>
<li>Fixed: Use <code>current_bitrate</code> if the more accurate player stat is not available.</li>
<li>Fixed: Unable to open Following link in sidebar in new tab due to click event handler.</li>
<li>Removed: We no longer need the override <code>getVideoInfo</code> function as the new HTML5 player now exposes playback statistics.</li>
</ul>
<div class="list-header">3.5.258</div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Totally forgot to make the chat in Portrait Mode a bit taller when using the new Minimal Channel Title option.</li>
</ul>
<div class="list-header">3.5.257</div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Option to hide Promoted Games in the sidebar.</li>
<li>Added: Option to minimize the channel information at the top of a channel page.</li>
<li>Added: Option to go directly to the Live Channels tab of the Following directory.</li>
<li>Changed: Use the player's own channel name and status display in theater mode on hover.</li>
<li>Fixed: Height of player controls with Whispers Position on Top disabled and Hide Whispers in Theater Mode enabled, when in theater mode.</li>
<li>Fixed: Chat would cover player in theater mode with Swap Sidebars enabled and a custom sidebar width set.</li>
</ul>
<div class="list-header">3.5.256</div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Further Twitch changes (reversion?) caused FFZ loading to break.</li>
<li>Fixed: Missing styles on relevant Follow buttons under streams.</li>
<li>Fixed: Clicking the popup arrow next to a relevant Follow button would not close that button's popup if it was already open.</li>
</ul>
<div class="list-header">3.5.255</div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Style tweaks to the new Chat Rules UI to make it dark and not under chat.</li>
</ul>
<div class="list-header">3.5.254</div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Twitch changes caused chat rendering to break. Fixed.</li>
</ul>
<div class="list-header">3.5.253</div>
<ul class="chat-menu-content menu-side-padding">
<li>Changed: On the Following tooltip, display the number of channels hidden by blocked games.</li>
<li>Fixed: Enhanced Following Control works again.</li>
<li>Fixed: Race condition causing Hosted Channels to not update on the directory.</li>
</ul>
<div class="list-header">3.5.252</div>
<ul class="chat-menu-content menu-side-padding">
<li>Changed: Parse emoji out of text that is pasted into the chat box.</li>

View file

@ -27,8 +27,9 @@ FFZ.settings_info.fix_color = {
2: "HSL Adjustment (Depreciated)",
3: "HSV Adjustment (Depreciated)",
4: "RGB Adjustment (Depreciated)"
//5: "HSL (BTTV-Like)"
},
value: '1',
value: 1,
category: "Chat Appearance",
no_bttv: true,
@ -39,16 +40,18 @@ FFZ.settings_info.fix_color = {
process_value: function(val) {
// Load legacy setting.
if ( val === false )
return '0';
return 0;
else if ( val === true )
return '1';
return 1;
else if ( typeof val === "string" )
return parseInt(val) || 1;
return val;
},
on_update: function(val) {
this.toggle_style('chat-colors-gray', !this.has_bttv && val === '-1');
this.toggle_style('chat-colors-gray', !this.has_bttv && val === -1);
if ( ! this.has_bttv && val !== '-1' )
if ( ! this.has_bttv && val !== -1 )
this._rebuild_colors();
}
};
@ -88,7 +91,7 @@ FFZ.settings_info.luv_contrast = {
this._rebuild_contrast();
this._rebuild_filter_styles();
if ( ! this.has_bttv && this.settings.fix_color == '1' )
if ( ! this.has_bttv && this.settings.fix_color === 1 )
this._rebuild_colors();
}
};
@ -111,7 +114,7 @@ FFZ.settings_info.color_blind = {
help: "Adjust username colors in an attempt to make them more distinct for people with color blindness.",
on_update: function(val) {
if ( ! this.has_bttv && this.settings.fix_color !== '-1' )
if ( ! this.has_bttv && this.settings.fix_color !== -1 )
this._rebuild_colors();
}
};
@ -122,7 +125,7 @@ FFZ.settings_info.color_blind = {
// --------------------
FFZ.prototype.setup_colors = function() {
this.toggle_style('chat-colors-gray', !this.has_bttv && this.settings.fix_color === '-1');
this.toggle_style('chat-colors-gray', !this.has_bttv && this.settings.fix_color === -1);
this._hex_colors = {};
this._rebuild_contrast();
@ -723,7 +726,7 @@ FFZ.prototype._handle_color = function(color) {
// Color Processing - RGB
if ( this.settings.fix_color === '4' ) {
if ( this.settings.fix_color === 4 ) {
var lum = rgb.luminance();
if ( lum > 0.3 ) {
@ -750,8 +753,17 @@ FFZ.prototype._handle_color = function(color) {
}
// Color Processing - HSL BTTV-Like
/*if ( this.settings.fix_color === 5 ) {
var hsl = rgb.toHSLA();
light_color = hsl._l(Math.min(Math.max(0, .9 * (1 - hsl.l)), 1)).toRGBA();
dark_color = hsl._l(Math.min(Math.max(0, 1 - .9 * (1 - hsl.l)), 1)).toRGBA();
}*/
// Color Processing - HSL
if ( this.settings.fix_color === '2' ) {
if ( this.settings.fix_color === 2 ) {
var hsl = rgb.toHSLA();
light_color = hsl._l(Math.min(Math.max(0, 0.7 * hsl.l), 1)).toRGBA();
@ -760,7 +772,7 @@ FFZ.prototype._handle_color = function(color) {
// Color Processing - HSV
if ( this.settings.fix_color === '3' ) {
if ( this.settings.fix_color === 3 ) {
var hsv = rgb.toHSVA();
if ( hsv.s === 0 ) {
@ -775,7 +787,7 @@ FFZ.prototype._handle_color = function(color) {
}
// Color Processing - LUV
if ( this.settings.fix_color === '1' ) {
if ( this.settings.fix_color === 1 ) {
var luv = rgb.toLUVA();
if ( luv.l > this._luv_required_dark )

View file

@ -108,7 +108,7 @@ FFZ.prototype.setup_channel = function() {
if ( f._cindex )
f._cindex.ffzFixTitle();
}.observes("content.status", "content.id", "hostModeTarget.status", "hostModeTarget.id"),
}.observes("content.status", "content.game", "content.id", "hostModeTarget.status", "hostModeTarget.id", "hostModeTarget.game"),
ffzHostTarget: function() {
var target = this.get('content.hostModeTarget'),
@ -528,14 +528,20 @@ FFZ.prototype.modify_channel_index = function(view) {
je = jQuery(stat_el);
var delay = Math.round(stats.hls_latency_broadcaster / 10) / 100,
dropped = utils.number_commas(stats.dropped_frames || 0),
bitrate;
if ( stats.playback_bytes_per_second )
bitrate = Math.round(stats.playback_bytes_per_second * 8 / 10.24) / 100;
else
bitrate = Math.round(stats.current_bitrate * 100) / 100;
if ( delay > 180 ) {
delay = Math.floor(delay);
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' 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<br>Dropped Frames: ' + dropped);
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else {
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps');
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps<br>Dropped Frames: ' + dropped);
delay = delay.toString();
var ind = delay.indexOf('.');
if ( ind === -1 )
@ -597,14 +603,20 @@ FFZ.prototype.modify_channel_index = function(view) {
je = jQuery(stat_el);
var delay = Math.round(stats.hls_latency_broadcaster / 10) / 100,
dropped = utils.number_commas(stats.dropped_frames || 0),
bitrate;
if ( stats.playback_bytes_per_second )
bitrate = Math.round(stats.playback_bytes_per_second * 8 / 10.24) / 100;
else
bitrate = Math.round(stats.current_bitrate * 100) / 100;
if ( delay > 180 ) {
delay = Math.floor(delay);
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' 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<br>Dropped Frames: ' + dropped);
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else {
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps');
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps<br>Dropped Frames: ' + dropped);
delay = delay.toString();
var ind = delay.indexOf('.');
if ( ind === -1 )

View file

@ -10,6 +10,7 @@ var FFZ = window.FrankerFaceZ,
FFZ.basic_settings.delayed_chat = {
type: "select",
options: {
"-1": "Default Delay (Room Specific; Non-Mod Only)",
0: "No Delay",
300: "Minor (Bot Moderation; 0.3s)",
1200: "Normal (Human Moderation; 1.2s)",
@ -137,6 +138,7 @@ FFZ.settings_info.chat_batching = {
FFZ.settings_info.chat_delay = {
type: "select",
options: {
"-1": "Default Delay (Room Specific; Non-Mod Only)",
0: "No Delay",
300: "Minor (Bot Moderation; 0.3s)",
1200: "Normal (Human Moderation; 1.2s)",
@ -147,7 +149,7 @@ FFZ.settings_info.chat_delay = {
30000: "Half a Minute (30s)",
60000: "Why??? (1m)"
},
value: 0,
value: -1,
category: "Chat Appearance",
no_bttv: true,
@ -162,6 +164,12 @@ FFZ.settings_info.chat_delay = {
},
on_update: function (val) {
for(var room_id in this.rooms) {
var room = this.rooms[room_id].room;
if ( room )
Ember.propertyDidChange(room, 'ffz_chat_delay');
}
if ( this._roomv )
this._roomv.ffzUpdateStatus();
}

View file

@ -220,19 +220,18 @@ FFZ.prototype.setup_directory = function() {
} else
this.log("Unable to locate the Ember service:vod-coviews");
this.log("Attempting to modify the Following collection.");
this._modify_following();
this.log("Hooking the Ember Directory views.");
this.update_views('component:stream-preview', function(x) { this.modify_directory_live(x, false) }, true);
this.update_views('component:creative-preview', function(x) { this.modify_directory_live(x, false) }, true);
this.update_views('component:csgo-channel-preview', function(x) { this.modify_directory_live(x, true) }, true);
this.update_views('component:host-preview', this.modify_directory_host, true);
this.update_views('component:host-preview', this.modify_directory_host, true, true);
this.update_views('component:video-preview', this.modify_video_preview, true);
this.update_views('component:game-follow-button', this.modify_game_follow_button);
this.log("Attempting to modify the Following collection.");
this._modify_following();
}
@ -270,7 +269,7 @@ FFZ.prototype._modify_following = function() {
// Don't use FFZ's Client ID because loading hosts is a normal part
// of the dashboard. We're just manipulating the logic a bit.
return Twitch.api.get("/api/users/:login/followed/hosting", t);
return this.get("api").request("get", "/api/users/:login/followed/hosting", t);
},
afterSuccess: function(e) {

View file

@ -2,7 +2,10 @@ var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants'),
createElement = utils.createElement;
createElement = utils.createElement,
FOLLOWING_RE = /^\/kraken\/users\/([^/]+)\/follows\/channels/,
FOLLOWER_RE = /^\/kraken\/channels\/([^/]+)\/follows/;
// --------------------
@ -33,200 +36,73 @@ FFZ.prototype.setup_profile_following = function() {
this._following_cache = {};
this._follower_cache = {};
/*try {
var ChannelSerializer = window.require("web-client/serializers/new-channel"),
BaseSerializer = window.require("web-client/serializers/application"),
process_channel = function(chan) {
var cid = chan.name;
return {
type: "new-channel",
id: cid,
attributes: chan,
relationships: {
following: {
links: {
related: "/kraken/users/" + cid + "/follows/channels?offset=0&on_site=1"
}
// We want to hook the API to gather this information. It's easier than
// modifying the deserialization path.
var process_follows = function(channel_id, data, cache) {
f.log("Loading Follow Information for: " + channel_id, data);
var user_cache = cache[channel_id] = cache[channel_id] || {},
now = Date.now();
for(var i=0; i < data.length; i++) {
var follow = data[i],
user = follow && (follow.user || follow.channel);
if ( ! user || ! user.name )
continue;
if ( user.display_name )
FFZ.capitalization[user.name] = [user.display_name, now];
user_cache[user.name] = [
follow.created_at ? utils.parse_date(follow.created_at) : null,
follow.notifications || false];
}
};
var ServiceAPI = utils.ember_lookup('service:api');
if ( ServiceAPI )
ServiceAPI.reopen({
request: function(method, url, data, options) {
if ( method !== 'get' || url.indexOf('/kraken/') !== 0 )
return this._super(method, url, data, options);
var t = this;
return new Promise(function(success, fail) {
t._super(method, url, data, options).then(function(result) {
if ( result.follows ) {
var match = FOLLOWING_RE.exec(url);
if ( match )
// Following Information
process_follows(match[1], result.follows, f._following_cache);
match = FOLLOWER_RE.exec(url);
if ( match )
// Follower Information
process_follows(match[1], result.follows, f._follower_cache);
}
}
}
};
var ser = ChannelSerializer.default = BaseSerializer.default.extend({
normalizeFindRecordResponse: function(e, t, a, r) {
var l = process_channel(a);
return this._super(e, t, {
data: l
}, r)
},
normalizeResponse: function(e, t, a, r, l) {
if ( ! a.follows )
return this._super.apply(this, arguments);
f.log("Normalizing Response", [e, t, a, r, l]);
var i = this.extractMeta(e, t, a),
o = a.follows.map(function(e) {
return process_channel(e.channel);
}),
d = {
data: o,
meta: i
};
return this._super(e, t, d, r, l);
success(result);
}).catch(function(err) {
fail(result);
})
});
}
});
App.registry.unregister('serializer:new-channel');
App.registry.register('serializer:new-channel', ser);
} catch(err) {
this.error("Unable to modify the Ember new-channel serializer", err);
}*/
else
this.error("Unable to locate the Ember service:api");
// First, we need to hook the model. This is what we'll use to grab the following notification state,
// rather than making potentially hundreds of API requests.
var Following = utils.ember_resolve('model:kraken-channel-following');
if ( Following )
this._hook_following(Following);
var Followers = utils.ember_resolve('model:user-followers');
if ( Followers )
this._hook_followers(Followers);
// Also try hooking that other model.
var Notification = utils.ember_resolve('model:notification');
if ( Notification )
this._hook_following(Notification, true);
// Find the followed item view
var FollowedItem = utils.ember_resolve('component:display-followed-item');
if ( ! FollowedItem )
return;
this._modify_display_followed_item(FollowedItem);
// Now, we need to edit the profile Following view itself.
var ProfileView = utils.ember_resolve('view:channel/following');
if ( ! ProfileView )
return;
ProfileView.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("ProfileView ffzInit: " + err);
}
},
ffzInit: function() {
// Only process our own profile following page.
if ( ! f.settings.enhance_profile_following )
return;
var el = this.get('element'),
user = f.get_user(),
user_id = this.get('context.model.id');
el.classList.add('ffz-enhanced-following');
el.classList.toggle('ffz-my-following', user && user.login === user_id);
el.setAttribute('data-user', user_id);
}
});
// TODO: Add nice Manage Following button to the directory.
// Now, rebuild any views.
try { FollowedItem.create().destroy();
} catch(err) { }
var views = utils.ember_views();
if ( views ) {
for(var key in views) {
var view = views[key];
if ( view instanceof FollowedItem ) {
this.log("Manually updating existing component:display-followed-item.", view);
try {
if ( ! view.ffzInit )
this._modify_display_followed_item(view);
view.ffzInit();
} catch(err) {
this.error("setup: component:display-followed-item ffzInit: " + err);
}
}
}
}
// Refresh all existing following data.
var count = 0,
Channel = utils.ember_resolve('model:deprecated-channel');
if ( Channel && Channel._cache )
for(var key in Channel._cache) {
var chan = Channel._cache[key];
if ( chan instanceof Channel ) {
var following = chan.get('following'),
followers = chan.get('followers'),
refresher = function(x) {
if ( x.get('isLoading') )
setTimeout(refresher.bind(this,x), 25);
x.clear();
x.load();
};
// Make sure this channel's Following collection is modified.
this._hook_following(following);
this._hook_followers(followers);
var counted = false;
if ( following && (following.get('isLoaded') || following.get('isLoading')) ) {
refresher(following);
count++;
counted = true;
}
if ( followers && (followers.get('isLoaded') || followers.get('isLoading')) ) {
refresher(followers);
if ( ! counted )
count++;
}
}
}
f.log("Refreshing previously loaded user following data for " + count + " channels.");
// Modify followed items.
this.update_views('component:display-followed-item', this.modify_display_followed_item);
}
FFZ.prototype._modify_display_followed_item = function(component) {
FFZ.prototype.modify_display_followed_item = function(component) {
var f = this;
component.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("component:display-followed-item ffzInit: " + err);
}
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("component:display-followed-item ffzTeardown: " + err);
}
},
utils.ember_reopen_view(component, {
ffzParentModel: function() {
var x = this.get('parentView');
while(x) {
@ -237,7 +113,7 @@ FFZ.prototype._modify_display_followed_item = function(component) {
}
}.property('parentView'),
ffzInit: function() {
ffz_init: function() {
var el = this.get('element'),
channel_id = this.get('ffzParentModel.id'), //.get('parentView.parentView.parentView.model.id'),
is_following = document.body.getAttribute('data-current-path').indexOf('.following') !== -1,
@ -271,7 +147,7 @@ FFZ.prototype._modify_display_followed_item = function(component) {
if ( age !== undefined ) {
t_el.innerHTML = constants.CLOCK + ' ' + (age < 60 ? 'now' : utils.human_time(age, 10));
t_el.title = 'Following Since: <nobr>' + data[0].toLocaleString() + '</nobr>';
t_el.title = 'Follow' + (is_following ? 'ing' : 'er') + ' Since: <nobr>' + data[0].toLocaleString() + '</nobr>';
t_el.style.display = '';
} else
t_el.style.display = 'none';
@ -347,91 +223,6 @@ FFZ.prototype._modify_display_followed_item = function(component) {
actions.appendChild(notif);
el.appendChild(actions);
},
ffzTeardown: function() {
}
});
}
FFZ.prototype._hook_following = function(Following) {
var f = this;
if ( ! Following || Following.ffz_hooked )
return;
Following.reopen({
ffz_hooked: true,
apiLoad: function(e) {
var channel_id = this.get('id'),
t = this;
f._following_cache[channel_id] = f._following_cache[channel_id] || {};
return new RSVP.Promise(function(success, fail) {
t._super(e).then(function(data) {
if ( data && data.follows ) {
var now = Date.now();
for(var i=0; i < data.follows.length; i++) {
var follow = data.follows[i];
if ( ! follow || ! follow.channel || ! follow.channel.name ) {
continue;
}
if ( follow.channel.display_name )
FFZ.capitalization[follow.channel.name] = [follow.channel.display_name, now];
f._following_cache[channel_id][follow.channel.name] = [follow.created_at ? utils.parse_date(follow.created_at) : null, follow.notifications || false];
}
}
success(data);
}, function(err) {
fail(err);
})
});
}
});
}
FFZ.prototype._hook_followers = function(Followers) {
var f = this;
if ( ! Followers || Followers.ffz_hooked )
return;
Followers.reopen({
ffz_hooked: true,
apiLoad: function(e) {
var channel_id = this.get('id'),
t = this;
f._follower_cache[channel_id] = f._follower_cache[channel_id] || {};
return new RSVP.Promise(function(success, fail) {
t._super(e).then(function(data) {
if ( data && data.follows ) {
var now = Date.now();
for(var i=0; i < data.follows.length; i++) {
var follow = data.follows[i];
if ( ! follow || ! follow.user || ! follow.user.name ) {
continue;
}
if ( follow.user.display_name )
FFZ.capitalization[follow.user.name] = [follow.user.display_name, now];
f._follower_cache[channel_id][follow.user.name] = [follow.created_at ? utils.parse_date(follow.created_at) : null, follow.notifications || false];
}
}
success(data);
}, function(err) {
fail(err);
})
});
}
});
}

View file

@ -136,6 +136,31 @@ FFZ.settings_info.right_column_width = {
};
FFZ.settings_info.minimal_channel_title = {
type: "boolean",
value: false,
category: "Appearance",
no_mobile: true,
no_bttv: true,
name: "Minimal Channel Title",
help: "Hide the channel's name and current game when viewing a channel to maximize player size.",
on_update: function(val) {
if ( this.has_bttv )
return;
var Layout = utils.ember_lookup('service:layout');
if ( ! Layout )
return;
document.body.classList.toggle('ffz-minimal-channel-title', val);
Ember.propertyDidChange(Layout, 'windowHeight');
}
}
// --------------------
// Initialization
// --------------------
@ -145,6 +170,7 @@ FFZ.prototype.setup_layout = function() {
return;
document.body.classList.toggle("ffz-sidebar-swap", this.settings.swap_sidebars);
document.body.classList.toggle('ffz-minimal-channel-title', this.settings.minimal_channel_title);
this.log("Creating layout style element.");
var s = this._layout_style = document.createElement('style');
@ -207,7 +233,7 @@ FFZ.prototype.setup_layout = function() {
r = this.get('contentWidth'),
i = (9 * r / 16) + c,
d = h - 120 - 60,
d = h - (f.settings.minimal_channel_title ? 75 : 120) - 60,
c = h - 94 - 185,
l = Math.floor(r),
@ -261,7 +287,7 @@ FFZ.prototype.setup_layout = function() {
var size = this.get('playerSize'),
video_below = this.get('portraitVideoBelow'),
video_height = size[1] + 120 + 60,
video_height = size[1] + (f.settings.minimal_channel_title ? 75 : 120) + 60,
chat_height = window_height - video_height,
video_top = video_below ? chat_height : 0,
@ -307,6 +333,7 @@ FFZ.prototype.setup_layout = function() {
'#right_col{width:' + width + 'px}' +
'body:not(.ffz-sidebar-swap) #main_col:not(.expandRight){' +
'margin-right:' + width + 'px}' +
'body.ffz-sidebar-swap .theatre #main_col:not(.expandRight),' +
'body.ffz-sidebar-swap #main_col:not(.expandRight){' +
'margin-left:' + width + 'px}';
}

View file

@ -679,9 +679,9 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
Settings = utils.ember_lookup('controller:settings');
component.reopen({
tokenizedMessage: function() {
return [];
}.property('msgObject.message'),
/*tokenizedMessage: function() {
return [{type: 'text', text: 'hi'}];
}.property('msgObject.message'),*/
ffzTokenizedMessage: function() {
try {

View file

@ -1285,7 +1285,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
if ( alias )
out.push('<span class="from ffz-alias html-tooltip' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '" title="' + utils.quote_san(name) + '">' + utils.sanitize(alias) + '</span>');
else
out.push('<span class="from' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">' + utils.sanitize(name ) + '</span>');
out.push('<span class="from' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">' + utils.sanitize(name) + '</span>');
out.push('<span class="colon">:</span> ');
}
@ -1299,7 +1299,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
var message = '<span class="message' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">' +
(msg.style === 'action' && ! show_from ? '*' + name + ' ' : '') + this.render_tokens(msg.cachedTokens) + '</span>';
(msg.style === 'action' && ! show_from ? '*' + name + ' ' : '') + this.render_tokens(msg.cachedTokens, true, false, msg.tags && msg.tags.bits) + '</span>';
if ( msg.deleted )
out.push('<span class="deleted"><a class="undelete" href="#" data-message="' + utils.quote_attr(message) + '">&lt;message deleted&gt;</a></span>');

View file

@ -59,6 +59,27 @@ FFZ.settings_info.player_volume_bar = {
};
FFZ.settings_info.player_pause_hosts = {
type: "select",
options: {
0: "Disabled",
1: "When Hosting Channel was Paused",
2: "Always"
},
value: 1,
process_value: function(val) {
if ( typeof val === "string" )
return parseInt(val) || 0;
return val;
},
category: "Player",
name: "Auto-Pause Hosted Channels",
help: "Automatically pause hosted channels if you paused the channel doing the hosting, or just pause all hosts."
}
// ---------------
// Initialization
// ---------------
@ -72,6 +93,7 @@ FFZ.prototype.setup_player = function() {
Layout.set('PLAYER_CONTROLS_HEIGHT', this.settings.classic_player ? 32 : 0);
this.players = {};
this.players_paused = {};
this.update_views('component:twitch-player2', this.modify_twitch_player);
}
@ -89,7 +111,7 @@ FFZ.prototype.modify_twitch_player = function(player) {
f.players[id] = this;
var player = this.get('player');
if ( player )
if ( player && !this.get('ffz_post_player') )
this.ffzPostPlayer();
},
@ -99,10 +121,33 @@ FFZ.prototype.modify_twitch_player = function(player) {
f.players[id] = undefined;
},
insertPlayer: function(ffz_reset) {
// We want to see if this is a hosted video on a play
var should_start_paused = this.get('shouldStartPaused'),
hosting_channel = this.get('hostChannel.id');
// Always start unpaused if the person used the FFZ setting to Reset Player.
if ( ffz_reset )
this.set('shouldStartPaused', false);
// Alternatively, depending on the setting...
else if (
(f.settings.player_pause_hosts === 1 && f.players_paused[hosting_channel]) ||
(f.settings.player_pause_hosts === 2 && hosting_channel) )
this.set('shouldStartPaused', true);
this._super();
// Restore the previous value so it doesn't mess anything up.
this.set('shouldStartPaused', should_start_paused);
}.on('didInsertElement'),
postPlayerSetup: function() {
this._super();
try {
this.ffzPostPlayer();
if ( ! this.get('ffz_post_player') )
this.ffzPostPlayer();
} catch(err) {
f.error("Player2 postPlayerSetup: " + err);
}
@ -122,7 +167,15 @@ FFZ.prototype.modify_twitch_player = function(player) {
this.set('player', null);
// Now, let Twitch create a new player as usual.
Ember.run.next(this.insertPlayer.bind(this));
Ember.run.next(this.insertPlayer.bind(this, true));
},
ffzUpdatePlayerPaused: function() {
var id = this.get('channel.id'),
is_paused = this.get('player.paused');
f.log("Player Pause State for " + id + ": " + is_paused);
f.players_paused[id] = is_paused;
},
ffzPostPlayer: function() {
@ -130,6 +183,12 @@ FFZ.prototype.modify_twitch_player = function(player) {
if ( ! player )
return;
this.set('ffz_post_player', true);
f.players_paused[this.get('channel.id')] = player.paused;
player.addEventListener('pause', this.ffzUpdatePlayerPaused.bind(this));
player.addEventListener('play', this.ffzUpdatePlayerPaused.bind(this));
// Make the stats window draggable and fix the button.
var stats = this.$('.player .js-playback-stats');
stats.draggable({cancel: 'li', containment: 'parent'});
@ -156,6 +215,7 @@ FFZ.prototype.modify_twitch_player = function(player) {
}
// Check player statistics. If necessary, override getVideoInfo.
/*
if ( ! Object.keys(player.getVideoInfo()).length && ! player.ffz_stats_hooked ) {
f.log("No Video Data. Installing handler.");
player.ffz_stats_hooked = true;
@ -189,10 +249,6 @@ FFZ.prototype.modify_twitch_player = function(player) {
toggle_btn.text('Show Video Stats');
jQuery('.js-stats-close', stats_el).remove();
/*off().on('click', function(e) {
stats_el.classList.add('hidden');
toggle_btn.text('Show Video Stats');
});*/
toggle_btn.off().on('click', function(e) {
var visible = stats_el.classList.contains('hidden');
@ -257,13 +313,6 @@ FFZ.prototype.modify_twitch_player = function(player) {
val2 *= 1000;
}
// Only make this change for known bad versions.
var version = player.getVersion();
if ( version === "0.5.4" ) {
val -= (f._ws_server_offset || 0);
val2 -= (f._ws_server_offset || 0);
}
if ( val > val2 ) {
output.hls_latency_broadcaster = val;
output.hls_latency_encoder = val2;
@ -296,7 +345,7 @@ FFZ.prototype.modify_twitch_player = function(player) {
};
setup_player();
}
}*/
}
});
}

View file

@ -10,7 +10,7 @@ var FFZ = window.FrankerFaceZ,
["sub", "subsOnly", "This room is in subscribers-only mode."],
["slow", "slow", function(room) { return "This room is in slow mode. You may send messages every " + utils.number_commas(room && room.get('slow') || 120) + " seconds." }],
["ban", "ffz_banned", "You have been banned from talking in this room."],
["delay", function() { return this.settings.chat_delay !== 0 }, function() { return "You have enabled artificial chat delay. Messages are displayed after " + (this.settings.chat_delay/1000) + " seconds." }],
["delay", function(room) { return room && room.get('ffz_chat_delay') !== 0 }, function(room) { return "Artificial chat delay is enabled. Messages are displayed after " + (room.get('ffz_chat_delay')/1000) + " seconds." }],
["batch", function() { return this.settings.chat_batching !== 0 }, function() { return "You have enabled chat message batching. Messages are displayed in " + (this.settings.chat_batching/1000) + " second increments." }]
],
@ -158,6 +158,14 @@ FFZ.prototype.modify_room_view = function(view) {
calcRecipientEligibility: function(e) {
// Because this doesn't work properly with multiple channel rooms
// by default, do it ourselves.
if ( controller.get('model.isGroupRoom') ) {
controller.set('isRecipientBitsIneligible', true);
controller.set('isBitsHelperShown', false);
controller.set('minimumBits', 0);
controller.set('isBitsTooltipActive', false);
return;
}
var id = controller.get('model.roomProperties._id'),
update = function(data) {
if ( controller.isDestroyed || controller.get('model.roomProperties._id') !== id )
@ -1008,10 +1016,9 @@ FFZ.prototype._modify_room = function(room) {
user = f.get_user(),
room_id = this.get('id');
/* ???
if ( (Chat && Chat.get('currentChannelRoom') === this) || (user && user.login === room_id) || (f._chatv && f._chatv._ffz_host === room_id) || (f.settings.pinned_rooms && f.settings.pinned_rooms.indexOf(room_id) !== -1) )
f.ws_unsub()
return this.ffzUnsubscribe(true);*/
// Don't destroy the room if it's still relevant.
if ( (Chat && Chat.get('currentChannelRoom') === this) || (user && user.login === room_id) || (f._chatv && f._chatv._ffz_host === room_id) || (f.settings.pinned_rooms && f.settings.pinned_rooms.indexOf(room_id) !== -1) )
return;
this.destroy();
},
@ -1336,8 +1343,24 @@ FFZ.prototype._modify_room = function(room) {
},
// Artificial chat delay
ffz_chat_delay: function() {
var val = f.settings.chat_delay;
if ( val !== -1 )
return val;
val = this.get('roomProperties.chat_delay_duration');
return ( Number.isNaN(val) || ! Number.isFinite(val) || this.get('isModeratorOrHigher') ) ? 0 : val;
}.property('roomProperties.chat_delay_duration', 'isModeratorOrHigher'),
ffz_update_display: function() {
if ( f._roomv )
f._roomv.ffzUpdateStatus();
}.observes('roomProperties.chat_delay_duration'),
pushMessage: function(msg) {
if ( f.settings.chat_batching !== 0 || f.settings.chat_delay !== 0 || (this.ffzPending && this.ffzPending.length) ) {
if ( f.settings.chat_batching !== 0 || this.get('ffz_chat_delay') !== 0 || (this.ffzPending && this.ffzPending.length) ) {
if ( ! this.ffzPending )
this.ffzPending = [];
@ -1381,8 +1404,9 @@ FFZ.prototype._modify_room = function(room) {
// amount of time from the last batch.
now = now || Date.now();
var t = this,
chat_delay = this.get('ffz_chat_delay'),
delay = Math.max(
(f.settings.chat_delay !== 0 ? 50 + Math.max(0, (f.settings.chat_delay + (this.ffzPending[0].time||0)) - now) : 0),
(chat_delay !== 0 ? 50 + Math.max(0, (chat_delay + (this.ffzPending[0].time||0)) - now) : 0),
(f.settings.chat_batching !== 0 ? Math.max(0, f.settings.chat_batching - (now - (this._ffz_last_batch||0))) : 0));
this._ffz_pending_flush = setTimeout(this.ffzPendingFlush.bind(this), delay);
@ -1392,7 +1416,8 @@ FFZ.prototype._modify_room = function(room) {
ffzPendingFlush: function() {
this._ffz_pending_flush = null;
var now = this._ffz_last_batch = Date.now();
var now = this._ffz_last_batch = Date.now(),
chat_delay = this.get('ffz_chat_delay');
for (var i = 0, l = this.ffzPending.length; i < l; i++) {
var msg = this.ffzPending[i];
@ -1404,7 +1429,7 @@ FFZ.prototype._modify_room = function(room) {
continue;
}
if ( f.settings.chat_delay !== 0 && (f.settings.chat_delay + msg.time > now) )
if ( chat_delay !== 0 && (chat_delay + msg.time > now) )
break;
msg.pending = false;
@ -1524,7 +1549,10 @@ FFZ.prototype._modify_room = function(room) {
new_msg = {
from: msg.from,
tags: {'display-name': msg.tags && msg.tags['display-name']},
tags: {
'display-name': msg.tags && msg.tags['display-name'],
bits: msg.tags && msg.tags.bits
},
message: msg.message,
cachedTokens: msg.cachedTokens,
style: msg.style,

View file

@ -54,6 +54,21 @@ FFZ.settings_info.sidebar_hide_recommended_channels = {
};
FFZ.settings_info.sidebar_hide_promoted_games = {
type: "boolean",
value: false,
category: "Sidebar",
no_mobile: true,
name: "Hide Promoted Games",
help: "Hide the Promoted Games section from the sidebar.",
on_update: utils.toggle_cls('ffz-hide-promoted-games')
};
FFZ.settings_info.sidebar_hide_recommended_friends = {
type: "boolean",
value: false,
@ -131,12 +146,25 @@ FFZ.settings_info.sidebar_start_open = {
};
FFZ.settings_info.sidebar_directly_to_followed_channels = {
type: "boolean",
value: false,
category: "Sidebar",
no_mobile: true,
name: "Open Following to Channels",
help: "When going to your Following directory, view the Live Channels tab by default."
};
// --------------------
// Initialization
// --------------------
FFZ.prototype.setup_sidebar = function() {
// CSS to Hide Stuff
utils.toggle_cls('ffz-hide-promoted-games')(this.settings.sidebar_hide_promoted_games);
utils.toggle_cls('ffz-hide-recommended-channels')(this.settings.sidebar_hide_recommended_channels);
utils.toggle_cls('ffz-hide-recommended-friends')(this.settings.sidebar_hide_recommended_friends);
utils.toggle_cls('ffz-hide-friends-collapsed')(this.settings.sidebar_hide_friends_collapsed);
@ -188,51 +216,54 @@ FFZ.prototype.setup_sidebar = function() {
} else
this.error("Unable to load the Ember navigation controller.", null);
/*
var NavView = this._modify_navigation(utils.ember_resolve('component:new-navigation')),
views = utils.ember_views(),
el = document.querySelector('nav#js-warp'),
view = el && views[el.parentElement.id];
if ( view ) {
try {
if ( ! view.ffzInit )
this._modify_navigation(view);
view.ffzInit();
} catch(err) {
this.error("Sidebar Setup", err);
}
}*/
if ( this._views_to_update )
this.update_views('view:navigation', this.modify_navigation, true);
}
/*FFZ.prototype._modify_navigation = function(component) {
FFZ.prototype.setup_following_link = function() {
var f = this,
mutator = {
didInsertElement: function() {
this.ffzInit();
},
ffzInit: function() {
f._nav = this;
f.log("Got New Navigation", this);
var el = this.get("element");
if ( f.settings.sidebar_start_open ) {
}
}
};
if ( component )
component.reopen(mutator);
else if ( window.App && App.__deprecatedInstance__ ) {
component = Ember.Component.extend(mutator);
App.__deprecatedInstance__.registry.register('component:new-navigation', component);
following_link = document.body.querySelector('#header_following');
if ( following_link ) {
following_link.href = '/directory/following' + (f.settings.sidebar_directly_to_followed_channels ? '/live' : '');
following_link.addEventListener('click', function(e) {
following_link.href = '/directory/following' + (f.settings.sidebar_directly_to_followed_channels ? '/live' : '');
});
}
}
return component;
}*/
FFZ.prototype.modify_navigation = function(component) {
var f = this;
utils.ember_reopen_view(component, {
ffz_init: function() {
f._nav = this;
// Override behavior for the Following link.
var el = this.get('element'),
following_link = el && el.querySelector('a[data-href="following"]');
if ( following_link ) {
following_link.href = '/directory/following' + (f.settings.sidebar_directly_to_followed_channels ? '/live' : '');
following_link.addEventListener('click', function(e) {
following_link.href = '/directory/following' + (f.settings.sidebar_directly_to_followed_channels ? '/live' : '');
var router = utils.ember_lookup('router:main');
if ( ! router || e && (e.button !== 0 || e.ctrlKey || e.metaKey) )
return;
router.transitionTo('directory.following.' + (f.settings.sidebar_directly_to_followed_channels ? 'channels' : 'index'));
e.stopImmediatePropagation();
e.preventDefault();
return false;
});
}
},
ffz_destroy: function() {
if ( f._nav === this )
f._nav = null;
}
});
}

View file

@ -1,5 +1,13 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils');
utils = require('../utils'),
VIEWER_CATEGORIES = [
['staff', 'Staff'],
['admins', 'Admins'],
['global_mods', 'Global Moderators'],
['moderators', 'Moderators'],
['viewers', 'Viewers']
];
// --------------------
@ -21,46 +29,27 @@ FFZ.settings_info.sort_viewers = {
// --------------------
FFZ.prototype.setup_viewers = function() {
this.log("Hooking the Ember Viewers controller.");
var Viewers = utils.ember_resolve('controller:viewers');
if ( Viewers )
this._modify_viewers(Viewers);
/* Disable for now because Twitch reverted this change
this.log("Hooking the Ember Viewers view.");
var ViewerView = utils.ember_resolve('view:viewers');
if ( ViewerView )
this._modify_viewer_view(ViewerView);*/
this.update_views('component:chat/twitch-chat-viewers', this.modify_viewer_list);
}
/*FFZ.prototype._modify_viewer_view = function(view) {
view.reopen({
setListDimensions: function(e) {
// Don't set the stupid scroll thing. Don't use the stupid height thing.
this.$(".js-chatters-container").width(e.width).height(e.height);
}
});
}*/
FFZ.prototype._modify_viewers = function(controller) {
FFZ.prototype.modify_viewer_list = function(component) {
var f = this;
controller.reopen({
utils.ember_reopen_view(component, {
lines: function() {
var viewers = this._super();
if ( ! f.settings.sort_viewers )
return viewers;
return this._super();
try {
var categories = [],
data = {},
last_category = null;
var viewers = [],
has_broadcaster = false,
raw_viewers = this.get('model.chatters') || {};
// Get the broadcaster name.
var Channel = utils.ember_lookup('controller:channel'),
room_id = this.get('parentController.model.id'),
room_id = this.get('model.id'),
broadcaster = Channel && Channel.get('model.id');
// We can get capitalization for the broadcaster from the channel.
@ -75,57 +64,45 @@ FFZ.prototype._modify_viewers = function(controller) {
if ( room_id !== broadcaster )
broadcaster = null;
// Now, break the viewer array down into something we can use.
for(var i=0; i < viewers.length; i++) {
var entry = viewers[i];
if ( entry.category ) {
last_category = entry.category;
categories.push(last_category);
data[last_category] = [];
} else {
var viewer = entry.chatter.toLowerCase();
if ( ! viewer )
continue;
// Iterate over everything~!
for(var i=0; i < VIEWER_CATEGORIES.length; i++) {
var data = raw_viewers[VIEWER_CATEGORIES[i][0]],
label = VIEWER_CATEGORIES[i][1],
first_user = true;
// If the viewer is the broadcaster, give them their own
// group. Don't put them with normal mods!
if ( viewer == broadcaster ) {
categories.unshift("Broadcaster");
data["Broadcaster"] = [viewer];
} else if ( data.hasOwnProperty(last_category) )
data[last_category].push(viewer);
}
}
// Now, rebuild the viewer list. However, we're going to actually
// sort it this time.
viewers = [];
for(var i=0; i < categories.length; i++) {
var category = categories[i],
chatters = data[category];
if ( ! chatters || ! chatters.length )
if ( ! data || ! data.length )
continue;
viewers.push({category: category});
viewers.push({chatter: ""});
for(var x=0; x < data.length; x++) {
if ( data[x] === broadcaster ) {
has_broadcaster = true;
continue;
}
// Push the chatters, capitalizing them as we go.
chatters.sort();
while(chatters.length) {
var viewer = chatters.shift();
viewer = FFZ.get_capitalization(viewer);
viewers.push({chatter: viewer});
if ( first_user ) {
viewers.push({category: i18n(label)});
viewers.push({chatter: ""});
first_user = false;
}
viewers.push({chatter: FFZ.get_capitalization(data[x])});
}
}
if ( has_broadcaster )
viewers.splice(0, 0,
{category: i18n("Broadcaster")},
{chatter: ""},
{chatter: FFZ.get_capitalization(broadcaster)});
return viewers;
} catch(err) {
f.error("ViewersController lines: " + err);
return this._super();
}
return viewers;
}.property("content.chatters")
}.property("model.chatters")
});
}

View file

@ -12,7 +12,7 @@ FFZ.prototype.setup_ember_wrapper = function() {
}
FFZ.prototype.update_views = function(klass, modifier, if_not_exists) {
FFZ.prototype.update_views = function(klass, modifier, if_not_exists, immediate) {
var original_klass;
if ( typeof klass === 'string' ) {
original_klass = klass;
@ -33,7 +33,7 @@ FFZ.prototype.update_views = function(klass, modifier, if_not_exists) {
} else
original_klass = klass.toString();
if ( this._ember_finalized )
if ( this._ember_finalized || immediate )
this._update_views([[original_klass, klass, modifier]]);
else
this._views_to_update.push([original_klass, klass, modifier]);

View file

@ -210,7 +210,9 @@ FFZ.ws_commands.load_set = function(set_id) {
// ---------------------
FFZ.prototype.load_emoji_data = function(callback, tries) {
var f = this;
var f = this,
puny = window.punycode && punycode.ucs2;
jQuery.getJSON(constants.SERVER + "emoji/emoji-data.json")
.done(function(data) {
var new_data = {},
@ -237,6 +239,8 @@ FFZ.prototype.load_emoji_data = function(callback, tries) {
type: "emoticon",
imgSrc: true,
length: puny ? puny.decode(emoji.raw).length : emoji.raw.length,
tw_src: emoji.tw_src,
noto_src: emoji.noto_src,
one_src: emoji.one_src,

View file

@ -9,11 +9,30 @@ var FFZ = window.FrankerFaceZ,
};
// ---------------------
// Badware Check
// ---------------------
FFZ.prototype.check_badware = function() {
if ( this.embed_in_dash || ! window.jQuery || ! window.jQuery.noty )
return;
// Check for the stolen version of BTTV4FFZ.
if ( FFZ.settings_info.bttv_global_emotes && FFZ.settings_info.bttv_global_emotes.category === "BetterTTV" ) {
var shown = localStorage.ffz_warning_bttv4ffz_clone;
if ( shown !== "true" ) {
localStorage.ffz_warning_bttv4ffz_clone = "true";
this.show_message("You appear to be using an unofficial version of BTTV4FFZ that was copied without the developer's permission. Please use the official version available at <a href=\"https://lordmau5.com/bttv4ffz/\">https://lordmau5.com/bttv4ffz/</a>");
}
}
}
// ---------------------
// API Constructor
// ---------------------
var API = FFZ.API = function(instance, name, icon, version) {
var API = FFZ.API = function(instance, name, icon, version, name_key) {
this.ffz = instance || FFZ.get();
// Check for a known API!
@ -56,7 +75,7 @@ var API = FFZ.API = function(instance, name, icon, version) {
this.on_room_callbacks = [];
this.name = name || ("Extension#" + this.id);
this.name_key = this.name.replace(/[^A-Z0-9_\-]/g, '').toLowerCase();
this.name_key = name_key || this.name.replace(/[^A-Z0-9_\-]/g, '').toLowerCase();
this.icon = icon || null;
this.version = version || null;

View file

@ -1,7 +1,9 @@
var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
utils = require('../utils'),
SENDER_REGEX = /(\sdata-sender="[^"]*"(?=>))/;
SENDER_REGEX = /(\sdata-sender="[^"]*"(?=>))/,
HOP = Object.prototype.hasOwnProperty;
// --------------------
@ -101,10 +103,12 @@ FFZ.prototype.setup_bttv = function(delay) {
this.toggle_style('badges-sub-notice-on');
// Disable other features too.
document.body.classList.remove('ffz-transparent-badges');
document.body.classList.remove("ffz-sidebar-swap");
document.body.classList.remove("ffz-portrait");
document.body.classList.remove("ffz-flip-dashboard");
var cl = document.body.classList;
cl.remove('ffz-transparent-badges');
cl.remove("ffz-sidebar-swap");
cl.remove("ffz-portrait");
cl.remove("ffz-minimal-channel-title");
cl.remove("ffz-flip-dashboard");
// Remove Following Count
if ( this.settings.following_count ) {
@ -293,7 +297,7 @@ FFZ.prototype.setup_bttv = function(delay) {
if ( emote_set && emote_set.emoticons )
for(var emote_id in emote_set.emoticons) {
emote = emote_set.emoticons[emote_id];
if ( ! emotes.hasOwnProperty(emote.name) )
if ( ! HOP.call(emotes, emote.name) )
emotes[emote.name] = emote;
}
}
@ -311,7 +315,7 @@ FFZ.prototype.setup_bttv = function(delay) {
for(var x=0,y=segments.length; x < y; x++) {
segment = segments[x];
if ( emotes.hasOwnProperty(segment) ) {
if ( HOP.call(emotes, segment) ) {
emote = emotes[segment];
if ( text.length ) {
var toks = parse_emoji(text.join(' ') + ' ');

View file

@ -1,6 +1,3 @@
// Modify Array and others.
// require('./shims');
// ----------------
// The Constructor
// ----------------
@ -37,7 +34,7 @@ FFZ.msg_commands = {};
// Version
var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 252,
major: 3, minor: 5, revision: 266,
toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
}
@ -308,6 +305,8 @@ FFZ.prototype.init_normal = function(delay, no_socket) {
this.setup_css();
this.setup_popups();
this.setup_following_link();
if ( ! no_socket ) {
this.setup_time();
this.ws_create();
@ -355,6 +354,8 @@ FFZ.prototype.init_dashboard = function(delay) {
this.setup_css();
this.setup_popups();
this.setup_following_link();
this.setup_time();
this.ws_create();
@ -460,6 +461,8 @@ FFZ.prototype.init_ember = function(delay) {
this.find_bttv(10);
this.find_emote_menu(10);
setTimeout(this.check_badware.bind(this), 10000);
//this.check_news();
this.check_ff();
this.refresh_chat();

View file

@ -7,13 +7,15 @@ var FFZ = window.FrankerFaceZ,
bits_helpers,
bits_service,
HOP = Object.prototype.hasOwnProperty,
EXPLANATION_WARN = '<hr>This link has been sent to you via a whisper rather than standard chat, and has not been checked or approved of by any moderators or staff members. Please treat this link with caution and do not visit it if you do not trust the sender.',
reg_escape = function(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
},
LINK = /(?:https?:\/\/)?(?:[-a-zA-Z0-9@:%_\+~#=]+\.)+[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/g,
LINK = /(?:https?:\/\/)?(?:[-a-zA-Z0-9@:%_\+~#=]+\.)+[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=()]*)/g,
CLIP_URL = /(?:https?:\/\/)?clips\.twitch\.tv\/(\w+)\/(\w+)/,
@ -101,18 +103,48 @@ FFZ._emote_mirror_swap = function(img) {
// Settings
// ---------------------
var ts = new Date(0).toLocaleTimeString().toUpperCase();
var ts = new Date(0).toLocaleTimeString().toUpperCase(),
default_24 = ts.lastIndexOf('PM') === -1 && ts.lastIndexOf('AM') === -1;
FFZ.settings_info.twenty_four_timestamps = {
type: "boolean",
value: ts.lastIndexOf('PM') === -1 && ts.lastIndexOf('AM') === -1,
type: "select",
options: {
0: "12-Hour" + (default_24 ? '' : ' (Default)'),
1: "12-Hour Zero-Padded",
2: "24-Hour" + (default_24 ? ' (Default)' : ''),
3: "24-Hour Zero-Padded"
},
value: default_24 ? 2 : 0,
process_value: function(val) {
if ( val === false )
return 0;
else if ( val === true )
return 2;
else if ( typeof val === 'string' )
return parseInt(val) || 0;
return val;
},
category: "Chat Appearance",
no_bttv: true,
name: "24hr Timestamps",
help: "Display timestamps in chat in the 24 hour format rather than 12 hour."
};
name: "Timestamp Format",
help: "Display timestamps in chat in the 24 hour format rather than 12 hour.",
on_update: function(val) {
// Update existing chat lines.
var CL = utils.ember_resolve('component:chat/chat-line'),
views = (CL && helpers && helpers.getTime) ? utils.ember_views() : {};
for(var vid in views) {
var view = views[vid];
if ( view instanceof CL )
view.$('.timestamp').text(view.get('timestamp'));
}
}
};
FFZ.settings_info.timestamp_seconds = {
@ -198,18 +230,22 @@ FFZ.prototype.setup_tokenization = function() {
// Timestamp Display
helpers.getTime = function(e) {
if ( e === undefined || e === null )
return '?:??';
return '?:??' + (f.settings.timestamp_seconds ? ':??' : '');
var hours = e.getHours(),
minutes = e.getMinutes(),
seconds = e.getSeconds();
seconds = e.getSeconds(),
if ( hours > 12 && ! f.settings.twenty_four_timestamps )
hours -= 12;
else if ( hours === 0 && ! f.settings.twenty_four_timestamps )
hours = 12;
s = f.settings.twenty_four_timestamps;
return hours + ':' + (minutes < 10 ? '0' : '') + minutes + (f.settings.timestamp_seconds ? ':' + (seconds < 10 ? '0' : '') + seconds : '');
if ( s < 2 ) {
if ( hours > 12 )
hours -= 12;
else if ( hours === 0 )
hours = 12;
}
return ((s === 1 || s === 3) && hours < 10 ? '0' : '') + hours + ':' + (minutes < 10 ? '0' : '') + minutes + (f.settings.timestamp_seconds ? ':' + (seconds < 10 ? '0' : '') + seconds : '');
};
@ -741,6 +777,10 @@ FFZ.prototype.tokenize_feed_body = function(message, emotes, user_id, room_id) {
if ( helpers && helpers.linkifyMessage )
message = helpers.linkifyMessage(message);
// We want to tokenize emoji first to make sure that they don't cause issues
// with the indices used by emoticonizeMessage.
message = this.tokenize_emoji(message);
if ( helpers && helpers.emoticonizeMessage && this.settings.parse_emoticons )
message = helpers.emoticonizeMessage(message, emotes);
@ -771,9 +811,6 @@ FFZ.prototype.tokenize_feed_body = function(message, emotes, user_id, room_id) {
if ( this.settings.parse_emoticons && this.settings.parse_emoticons !== 2 )
tokens = this.tokenize_emotes(user_id, room_id, tokens)
if ( this.settings.parse_emoji )
tokens = this.tokenize_emoji(tokens);
return tokens;
}
@ -996,7 +1033,7 @@ FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
if ( emote_set && emote_set.emoticons )
for(var emote_id in emote_set.emoticons) {
emote = emote_set.emoticons[emote_id];
if ( ! emotes.hasOwnProperty(emote.name) )
if ( ! HOP.call(emotes, emote.name) )
emotes[emote.name] = emote;
}
}
@ -1023,7 +1060,7 @@ FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
for(var x=0,y=segments.length; x < y; x++) {
segment = segments[x];
if ( emotes.hasOwnProperty(segment) ) {
if ( HOP.call(emotes, segment) ) {
emote = emotes[segment];
if ( text.length ) {
@ -1095,7 +1132,7 @@ FFZ.prototype.tokenize_emoji = function(tokens) {
if ( data ) {
if ( text && text.length )
new_tokens.push({type: "text", text: text});
new_tokens.push(text);
new_tokens.push(data.token);
text = null;
} else
@ -1104,7 +1141,7 @@ FFZ.prototype.tokenize_emoji = function(tokens) {
}
if ( text && text.length )
new_tokens.push({type: "text", text: text});
new_tokens.push(text);
}
return new_tokens;

View file

@ -3,21 +3,10 @@ var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
FOLLOWING_CONTAINERS = [
['#small_nav ul.game_filters li[data-name="following"] a', true],
['nav a.warp__tipsy[data-href="following"]', true],
['#large_nav #nav_personal li[data-name="following"] a', false],
['#header_actions #header_following', false]
'.warp__item a[data-href="following"]',
'#header_actions #header_following'
],
FOLLOW_GRAVITY = function(f, el) {
return (f.settings.following_count && (
el.getAttribute('data-href') === 'following' ||
el.parentElement.getAttribute('data-name') === 'following'
) ? 'n' : '') +
(f.settings.swap_sidebars ? 'e' : 'w');
},
WIDE_TIP = function(f, el) {
return (f.settings.following_count && (
el.id === 'header_following' ||
@ -158,13 +147,14 @@ FFZ.prototype._update_following_count = function() {
var Stream = utils.ember_resolve('model:deprecated-stream'),
Live = Stream && Stream.find("live"),
Host = utils.ember_resolve('model:host'),
HostLive = Host && Host.find("following"),
/*Host = utils.ember_resolve('model:host'),
HostLive = Host && Host.find("following"),*/
current_path = document.body.getAttribute('data-current-path') || '',
f = this;
if ( ! this.is_dashboard && HostLive && document.body.getAttribute('data-current-path').indexOf('directory.following') !== -1 )
HostLive.load();
/*if ( ! this.is_dashboard && HostLive && current_path.indexOf('directory.following') !== -1 )
HostLive.load();*/
if ( ! this.is_dashboard && Live )
Live.load();
@ -200,17 +190,24 @@ FFZ.prototype._build_following_tooltip = function(el) {
streams = this._tooltip_streams,
total = this._tooltip_total || (streams && streams.length) || 0,
c = 0;
c = 0,
filtered = 0;
if ( streams && streams.length ) {
for(var i=0, l = streams.length; i < l; i++) {
var stream = streams[i];
if ( ! stream || ! stream.channel || (stream.game && this.settings.banned_games.indexOf(stream.game.toLowerCase()) !== -1) )
if ( ! stream || ! stream.channel )
continue;
if ( stream.game && this.settings.banned_games.indexOf(stream.game.toLowerCase()) !== -1 ) {
filtered++;
continue;
}
c += 1;
if ( c > max_lines ) {
tooltip += '<hr><span>And ' + utils.number_commas(total - max_lines) + ' more...</span>';
tooltip += '<hr><span>And ' + utils.number_commas(total - max_lines) + ' more' + (filtered ? ' (' + filtered + ' hidden)' : '') + '...</span>';
filtered = 0;
break;
}
@ -225,6 +222,10 @@ FFZ.prototype._build_following_tooltip = function(el) {
'<b>' + utils.sanitize(stream.channel.display_name || stream.channel.name) + '</b><br>' +
'<span class="playing">' + (stream.channel.game === 'Creative' ? 'Being Creative' : (stream.channel.game ? 'Playing ' + utils.sanitize(stream.channel.game) : 'Not Playing')) + (tags ? ' | ' + _.pluck(tags, "text").join(" ") : '') + '</span>';
}
if ( filtered )
tooltip += '<hr><span>(' + filtered + ' hidden)';
} else {
c++; // is a terrible programming language
tooltip += "<hr>No one you're following is online.";
@ -284,23 +285,21 @@ FFZ.prototype._build_following_tooltip = function(el) {
FFZ.prototype._install_following_tooltips = function() {
var f = this,
gravity = function() { return FOLLOW_GRAVITY(f, this) },
data = {
html: true,
className: function() { return WIDE_TIP(f, this); },
title: function() { return f._build_following_tooltip(this); }
title: function() { return f._build_following_tooltip(this); },
gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE * 2, 'w')
};
for(var i=0; i < FOLLOWING_CONTAINERS.length; i++) {
var following = jQuery(FOLLOWING_CONTAINERS[i][0]);
var following = jQuery(FOLLOWING_CONTAINERS[i]);
if ( following && following.length ) {
var td = following.data('tipsy');
if ( td && td.options ) {
td.options = _.extend(td.options, data);
if ( FOLLOWING_CONTAINERS[i][1] )
td.options.gravity = gravity;
} else
following.tipsy(FOLLOWING_CONTAINERS[i][1] ? _.extend({gravity: gravity}, data) : data);
following.tipsy(data);
}
}
}
@ -315,7 +314,7 @@ FFZ.prototype._draw_following_channels = function(streams, total) {
FFZ.prototype._draw_following_count = function(count) {
count = count ? utils.format_unread(count) : '';
for(var i=0; i < FOLLOWING_CONTAINERS.length; i++) {
var container = document.querySelector(FOLLOWING_CONTAINERS[i][0]),
var container = document.querySelector(FOLLOWING_CONTAINERS[i]),
badge = container && container.querySelector('.ffz-follow-count');
if ( ! container )
continue;

View file

@ -211,9 +211,18 @@ FFZ.prototype.rebuild_following_ui = function() {
cont = document.createElement('span');
cont.id = 'ffz-ui-following';
var before;
try { before = container.querySelector(':scope > span'); }
catch(err) { before = undefined; }
var before = null;
try {
var before_btn = container.querySelector('.subscribe-button');
if ( before_btn )
before = before_btn.parentElement.nextSibling;
else {
before_btn = container.querySelector('.notification-controls');
if ( before_btn )
before = before_btn.nextSibling;
}
} catch(err) { }
if ( before )
container.insertBefore(cont, before);
@ -250,9 +259,17 @@ FFZ.prototype.rebuild_following_ui = function() {
cont = document.createElement('span');
cont.id = 'ffz-ui-following';
var before;
try { before = container.querySelector(':scope > span'); }
catch(err) { before = undefined; }
var before = null;
try {
var before_btn = container.querySelector('.subscribe-button');
if ( before_btn )
before = before_btn.parentElement.nextSibling;
else {
before_btn = container.querySelector('.notification-controls');
if ( before_btn )
before = before_btn.nextSibling;
}
} catch(err) { }
if ( before )
container.insertBefore(cont, before);
@ -278,21 +295,23 @@ FFZ.prototype.rebuild_following_ui = function() {
// UI Construction
// ---------------
FFZ.prototype._build_following_button = function(container, channel_id) {
FFZ.prototype._build_following_button = function(cont, channel_id) {
if ( ! VALID_CHANNEL.test(channel_id) )
return this.log("Ignoring Invalid Channel: " + utils.sanitize(channel_id));
var btn = document.createElement('a'), f = this,
btn_c = document.createElement('div'),
noti = document.createElement('a'),
noti_c = document.createElement('div'),
var f = this,
btn = utils.createElement('button', 'follow-button button'),
noti = utils.createElement('a', 'toggle-notification-menu js-toggle-notification-menu'),
noti_c = utils.createElement('div', 'notification-controls v2 hidden', noti),
display_name,
following = false,
notifications = false,
update = function() {
btn_c.classList.toggle('is-following', following);
btn.classList.toggle('is-following', following);
btn.classList.toggle('button--status', following);
btn.title = (following ? "Unf" : "F") + "ollow " + utils.sanitize(display_name);
btn.innerHTML = (following ? "" : "Follow ") + utils.sanitize(display_name);
noti_c.classList.toggle('hidden', !following);
@ -303,7 +322,7 @@ FFZ.prototype._build_following_button = function(container, channel_id) {
if ( ! user || ! user.login ) {
following = false;
notification = false;
btn_c.classList.add('is-initialized');
btn.classList.add('is-initialized');
return update();
}
@ -311,12 +330,12 @@ FFZ.prototype._build_following_button = function(container, channel_id) {
.done(function(data) {
following = true;
notifications = data.notifications;
btn_c.classList.add('is-initialized');
btn.classList.add('is-initialized');
update();
}).fail(function(data) {
following = false;
notifications = false;
btn_c.classList.add('is-initialized');
btn.classList.add('is-initialized');
update();
});
},
@ -339,16 +358,9 @@ FFZ.prototype._build_following_button = function(container, channel_id) {
update();
};
btn_c.className = 'ember-follow follow-button';
btn_c.appendChild(btn);
// The drop-down button!
noti.className = 'toggle-notification-menu js-toggle-notification-menu';
noti.href = '#';
noti_c.className = 'notification-controls v2 hidden';
noti_c.appendChild(noti);
// Event Listeners!
btn.addEventListener('click', function(e) {
var user = f.get_user();
@ -399,8 +411,8 @@ FFZ.prototype._build_following_button = function(container, channel_id) {
setTimeout(check_following, Math.random()*5000);
container.appendChild(btn_c);
container.appendChild(noti_c);
cont.appendChild(btn);
cont.appendChild(noti_c);
}
@ -411,11 +423,12 @@ FFZ.prototype._build_following_popup = function(container, channel_id, notificat
if ( popup && popup.id == "ffz-following-popup" && popup.getAttribute('data-channel') === channel_id )
return null;
popup = this._popup = document.createElement('div');
popup = this._popup = utils.createElement('div', 'dropmenu notify-menu js-notify');
popup.id = 'ffz-following-popup';
popup.setAttribute('data-channel', channel_id);
popup.className = (pos >= 300 ? 'right' : 'left') + ' dropmenu notify-menu js-notify';
this._popup_allow_parent = true;
this._popup_parent = container;
out = '<div class="header">You are following ' + FFZ.get_capitalization(channel_id) + '</div>';
out += '<p class="clearfix">';
@ -424,6 +437,6 @@ FFZ.prototype._build_following_popup = function(container, channel_id, notificat
out += '</p>';
popup.innerHTML = out;
container.appendChild(popup);
container.insertBefore(popup, container.firstChild);
return popup.querySelector('a.switch');
}

View file

@ -46,7 +46,8 @@ FFZ.prototype.fix_tooltips = function() {
for(var obj_id in jQuery.cache) {
var obj = jQuery.cache[obj_id];
if ( obj && obj.data && obj.data.tipsy && obj.data.tipsy.options && typeof obj.data.tipsy.options.gravity !== "function" )
if ( obj && obj.data && obj.data.tipsy && obj.data.tipsy.options && typeof obj.data.tipsy.options.gravity !== "function" ) {
obj.data.tipsy.options.gravity = utils.tooltip_placement(constants.TOOLTIP_DISTANCE, obj.data.tipsy.options.gravity || 's');
}
}
}

View file

@ -466,8 +466,19 @@ module.exports = FFZ.utils = {
if ( typeof pref === "function" )
pref = pref.call(this);
var dir = {ns: pref[0], ew: (pref.length > 1 ? pref[1] : false)},
$this = $(this),
var dir = {};
if ( pref.indexOf('n') !== -1 )
dir.ns = 'n';
else if ( pref.indexOf('s') !== -1 )
dir.ns = 's';
if ( pref.indexOf('e') !== -1 )
dir.ew = 'e';
else if ( pref.indexOf('w') !== -1 )
dir.ew = 'w';
var $this = $(this),
half_width = $this.width() / 2,
half_height = $this.height() / 2,
boundTop = $(document).scrollTop() + half_height + (margin*2),
@ -478,7 +489,7 @@ module.exports = FFZ.utils = {
if ($(window).width() + $(document).scrollLeft() - ($this.offset().left + half_width) < margin) dir.ew = 'e';
if ($(window).height() + $(document).scrollTop() - ($this.offset().top + half_height) < (2*margin)) dir.ns = 's';
return dir.ns + (dir.ew ? dir.ew : '');
return (dir.ns ? dir.ns : '') + (dir.ew ? dir.ew : '');
}
},

124
style.css
View file

@ -18,12 +18,14 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
cursor: pointer;
}
.app-main .ad_leader:empty,
body:not(.ffz-show-bits-tags) .bits-tag--container,
.ffz-hide-friends nav .friend-list,
.ffz-hide-friends .warp__status .js-presence-indicator,
.ffz-hide-more-at-twitch nav .tse-content > .warp__list:last-of-type,
.ffz-hide-recommended-friends .recommended-friends,
.ffz-hide-recommended-channels .js-recommended-channels,
.ffz-hide-promoted-games .promotedGames,
.ffz-hide-recent-past-broadcast .recent-past-broadcast,
.ffz-hide-view-count .stat.twitch-channel-views,
.ffz-minimal-chat-input .chat-interface .emoticon-selector-toggle,
@ -216,56 +218,40 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
margin-bottom: -5px;
}
/* Minimal Channel Title */
.ffz-minimal-channel-title #broadcast-meta {
height: 25px;
margin: 20px 0;
}
.ffz-minimal-channel-title #channel #broadcast-meta .profile-link .profile-photo img {
width: 25px; height: 25px;
}
.ffz-minimal-channel-title #channel #broadcast-meta .info {
padding-top: 25px;
padding-left: 50px;
}
.ffz-minimal-channel-title #channel #broadcast-meta .info .title {
padding-top: 0;
margin: 0;
left: calc(25px + 1.5rem);
height: 25px;
min-height: 25px;
}
.ffz-minimal-channel-title #broadcast-meta .profile-photo .goto,
.ffz-minimal-channel-title #broadcast-meta .channel { display: none }
/* Theater Mode hover bar */
.ffz-theater-stats .app-main.theatre .player-column:focus #hostmode > div.target-meta,
.ffz-theater-stats .app-main.theatre .player-column:hover #hostmode > div.target-meta,
.ffz-theater-stats .app-main.theatre #channel .player-column:focus #broadcast-meta,
.ffz-theater-stats .app-main.theatre #channel .player-column:hover #broadcast-meta {
background-color: #19191f;
color: #aaa;
position: absolute;
top: -20px;
left: 10px;
right: 10px;
z-index: 7;
opacity: 0.95;
height: 20px;
}
.ffz-theater-stats.ffz-sidebar-swap .app-main.theatre .player-column:focus #hostmode > div.target-meta,
.ffz-theater-stats.ffz-sidebar-swap .app-main.theatre .player-column:hover #hostmode > div.target-meta,
.ffz-theater-stats.ffz-sidebar-swap .app-main.theatre #channel .player-column:focus #broadcast-meta,
.ffz-theater-stats.ffz-sidebar-swap .app-main.theatre #channel .player-column:hover #broadcast-meta {
left: 145px;
}
.ffz-theater-stats .app-main.theatre #hostmode > div.target-meta div.target-title {
padding: 5px 0 2px 5px;
}
.ffz-theater-stats .app-main.theatre #hostmode > div.target-meta div.target-title,
.ffz-theater-stats .app-main.theatre #channel .player-column #broadcast-meta .info { padding-left: 5px; }
.ffz-theater-stats .app-main.theatre #hostmode > div.target-meta div.target-title,
.ffz-theater-stats .app-main.theatre #channel .player-column #broadcast-meta .info .title {
font-size: 12px;
line-height: 20px;
color: #dedede;
}
.ffz-theater-stats .app-main.theatre #hostmode > div.target-meta div.target-title,
.ffz-theater-stats .app-main.theatre #channel .player-column #broadcast-meta .info .title,
.ffz-theater-stats .app-main.theatre #channel .player-column #broadcast-meta .info .title .over {
background-color: rgba(16,16,16,0.3);
}
.ffz-theater-stats .app-main.theatre #hostmode > div.target-meta .target-user-and-game,
.ffz-theater-stats .app-main.theatre #channel .player-column #broadcast-meta .info .channel,
.ffz-theater-stats .app-main.theatre #channel .player-column #broadcast-meta .info .edit-link,
.ffz-theater-stats .app-main.theatre #broadcast-meta .profile-link {
display: none;
.ffz-theater-stats .app-main.theatre .player-userinfo__game,
.ffz-theater-stats .app-main.theatre .player-controls-top {
display: block
}
.ffz-theater-stats .app-main.theatre .player-column:focus #hostmode > div.clearfix,
@ -326,7 +312,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
background-color: #25252a;
}
.ffz-theater-stats .app-main.theatre .button.button--icon-only svg path {
.ffz-theater-stats .app-main.theatre .button.button--icon-only:not(.follow-button) svg path {
fill: #a68ed2;
}
@ -1336,6 +1322,7 @@ img.channel_background[src="null"] { display: none; }
.ffz-moderation-card .follow-button {
font-size: 0 !important;
padding-right: 0 !important;
animation: none !important;
}
.ffz-moderation-card .button.button--icon-only {
@ -2508,8 +2495,8 @@ li[data-name="following"] a {
transition: margin-bottom .2s ease-out, padding-bottom .2s ease-out;
}
.ffz-classic-player:not(.ffz-top-conversations) .app-main.theatre .player .player-controls-bottom,
.ffz-classic-player:not(.ffz-top-conversations) .app-main.theatre .player[data-controls=true] .player-controls-bottom,
.ffz-classic-player:not(.ffz-top-conversations):not(.ffz-theatre-conversations) .app-main.theatre .player .player-controls-bottom,
.ffz-classic-player:not(.ffz-top-conversations):not(.ffz-theatre-conversations) .app-main.theatre .player[data-controls=true] .player-controls-bottom,
.ffz-classic-player .app-main.theatre .player[data-fullscreen="true"] .player-controls-bottom,
.ffz-classic-player .app-main.theatre .player[data-fullscreen="true"][data-controls=true] .player-controls-bottom {
padding-bottom: 0;
@ -2526,7 +2513,7 @@ li[data-name="following"] a {
margin-bottom: 0;
}
.ffz-classic-player:not(.ffz-top-conversations) .app-main.theatre .player-column:hover .player[data-fullscreen="false"] .player-controls-bottom {
.ffz-classic-player:not(.ffz-top-conversations):not(.ffz-theatre-conversations) .app-main.theatre .player-column:hover .player[data-fullscreen="false"] .player-controls-bottom {
padding-bottom: 40px;
}
@ -2816,6 +2803,7 @@ body:not(.ffz-top-conversations) .conversations-list-bottom-bar {
.user.item .actions .notifications {
flex-grow: 1;
padding: 0;
}
.user.item .actions button:hover,
@ -2842,7 +2830,7 @@ body:not(.ffz-top-conversations) .conversations-list-bottom-bar {
}
.user.item .actions .follow svg {
margin: 4.5px 0 -4.5px -1px;
margin: 4px 0 -5px -5.5px;
}
/* Creative Directory */
@ -2913,13 +2901,17 @@ body:not(.ffz-creative-showcase) .ct-spotlight-container { display: none; }
/* Banned and Spoiler Games */
.ffz-game-spoilered .thumb .cap img,
body:not([data-current-path^="directory.csgo"]):not([data-current-path^="directory.game"]):not([data-current-path^="directory.creative"]) .ffz-game-banned { display: none }
.ffz-game-spoilered .thumb .cap {
position: absolute !important; top: 0; left: 0; bottom: 0; right: 0;
background: url("https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg") no-repeat !important;
background-size: cover !important;
.ffz-game-spoilered .thumb .cap img { visibility: hidden }
.ffz-game-spoilered .thumb .cap:after {
position: absolute;
top: 0; left: 0;
bottom: 0; right: 0;
background: url("https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg") no-repeat #64439a;
background-size: cover;
content: '';
}
.directory_header .ffz-block-button,
@ -2983,7 +2975,7 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
/* New Sidebar */
.ffz-hide-friends-collapsed .drawer--summary.closed .friend-list { display: none }
.ffz-hide-friends-collapsed .drawer.closed .friend-list { display: none }
.warp__anchor { height: 5.5rem }
@ -3181,4 +3173,18 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
.bttv-incompatibility b + b:before {
content: ', ';
font-weight: normal;
}
/* Chat Rules */
.ember-chat .chat-interface .chat-rules {
z-index: 1;
}
.theatre .chat-interface .chat-rules,
.chat-container.dark .chat-rules,
.chat-container.force-dark {
background: #101010;
color: #ccc;
border-color: rgba(255,255,255,0.2);
}