1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-02 16:08:31 +00:00

3.5.169. Oops. Haven't commited in a while :< Did... stuff? And things.

This commit is contained in:
SirStendec 2016-05-06 02:23:12 -04:00
parent 4086a2e9fd
commit 8056463bbe
28 changed files with 908 additions and 508 deletions

View file

@ -72,6 +72,8 @@
} }
/* stats */ /* stats */
.ffz-dark .ct-tags--extracted { border-bottom: none }
.ffz-dark .stats-and-actions, .ffz-dark .stats-and-actions,
.ffz-dark #main_col .content #stats_and_actions { .ffz-dark #main_col .content #stats_and_actions {
border-bottom-color: rgba(255,255,255,0.2); border-bottom-color: rgba(255,255,255,0.2);
@ -115,19 +117,23 @@ body.ffz-dark,
} }
.ffz-dark div.title > span.real, .ffz-dark div.title > span.real,
.ffz-dark div.title > span.over{ .ffz-dark div.title > span.over,
color:rgb(222,222,222)!important; .ffz-dark #broadcast-meta .info .title {
background-color:rgba(16,16,16,0.3)!important; color: #DEDEDE !important;
background-color: rgba(16,16,16,0.3) !important;
} }
.ffz-dark div.title > span.over:hover, .ffz-dark div.title > span.over:hover,
.ffz-dark div.title > span.real:hover { .ffz-dark div.title > span.real:hover,
background-color:rgb(16,16,16)!important; .ffz-dark #broadcast-meta .info .title:hover {
color:rgb(255,255,255)!important; color: #fff !important;
background-color: #101010 !important;
} }
/* Right Column */ /* Right Column */
.ffz-dark .ct-banner--off .ct-banner__toggle,
.ffz-dark #right_col { .ffz-dark #right_col {
background-color: rgb(25,25,31); background-color: rgb(25,25,31);
color: #fff; color: #fff;
@ -175,6 +181,9 @@ body.ffz-dark,
box-shadow: rgba(255,255,255,0.2) 0 0 0 1px inset; box-shadow: rgba(255,255,255,0.2) 0 0 0 1px inset;
} }
.ffz-dark .player-menu__header { color: #c3c3c3 }
.ffz-dark .player-menu__section { border-bottom-color: rgba(255,255,255,0.2) }
.ffz-dark .balloon:after { box-shadow: none } .ffz-dark .balloon:after { box-shadow: none }
.ffz-dark .st-autocomplete-sidebar .label, .ffz-dark .st-autocomplete-sidebar .label,
@ -216,6 +225,7 @@ body.ffz-dark,
} }
.ffz-dark .change-banner .banner-preview, .ffz-dark .change-banner .banner-preview,
.ffz-dark .ct-banner--off .ct-banner__inputbox,
.ffz-dark form.js-new_panel_form input, .ffz-dark form.js-new_panel_form input,
.ffz-dark form.js-new_panel_form textarea, .ffz-dark form.js-new_panel_form textarea,
.ffz-dark .conversation-input-bar textarea, .ffz-dark .conversation-input-bar textarea,
@ -269,10 +279,20 @@ body.ffz-dark,
.ffz-dark .manager .videos-grid .video:hover .meta .actions li a, .ffz-dark .manager .videos-grid .video:hover .meta .actions li a,
.ffz-dark .ember-chat .chat-room-list .room:not(:hover) p.room-title, .ffz-dark .ember-chat .chat-room-list .room:not(:hover) p.room-title,
.ffz-dark .dropmenu_action:not(:hover) span, .ffz-dark .dropmenu_action:not(:hover) span,
.ffz-dark a:not(.filter-item):not(.ui-state-focus):not(.button):not(.switch):not(.follow):not(.fb_button):not(.what) { .ffz-dark a:not(.profile-card__content):not(.filter-item):not(.ui-state-focus):not(.button):not(.switch):not(.follow):not(.fb_button):not(.what) {
color: #a68ed2; color: #a68ed2;
} }
.ffz-dark .balloon--cols .balloon__list~.balloon__list {
box-shadow: -1px 0 0 rgba(255,255,255,0.2);
}
.ffz-dark .warp__item a.js-language-select,
.ffz-dark .warp__item > a { color: #d5d4d9 !important }
.ffz-dark .warp__item--toggled a.js-language-select,
.ffz-dark .warm__item--toggled > a { color: #eae9ec !important }
.ffz-dark .exit-theatre > a { color: #000 !important; } .ffz-dark .exit-theatre > a { color: #000 !important; }
.ffz-dark .follow-button a, .ffz-dark .follow-button a,
@ -297,7 +317,7 @@ body.ffz-dark,
background-color: #25252a; background-color: #25252a;
} }
.ffz-dark .button:not(.primary) { .ffz-dark .button:not(.button--status):not(.primary) {
color: #a68ed2; color: #a68ed2;
} }
@ -1117,6 +1137,29 @@ body.ffz-dark,
} }
/* Creative Tags */
.ffz-dark .ct-tags__tag {
background-color: #191919;
color: #999 !important;
}
.ffz-dark .ct-tag--light .ct-tag__link:hover { color: #ccc !important }
.ffz-dark .ct-tags__tag:hover {
background-color: #000;
color: #ccc !important;
}
.ffz-dark .ct-tags__tag.ct-tags__tag--current {
background-color: #7d5bbc;
color: #fff !important;
}
.ffz-dark .ct-banner--off .ct-banner__header { color: #999 }
.ffz-dark .ct-banner--off .ct-banner__link { color: #ccc }
/* Activity Feeds */ /* Activity Feeds */
.button.alert { color: #fff !important } .button.alert { color: #fff !important }
@ -1128,12 +1171,12 @@ body.ffz-dark,
.ffz-dark .activity-meta:before { background-color: #474747 } .ffz-dark .activity-meta:before { background-color: #474747 }
.ffz-dark .activity-react__like:hover { .ffz-dark .activity-react__item:hover {
border-color: #d5d5d5; border-color: #d5d5d5;
background-color: #242424; background-color: #242424;
} }
.ffz-dark .activity-react__like svg.endorse-icon #head__base { fill: #fff } .ffz-dark .activity-react__item svg.endorse-icon #head__base { fill: #fff }
.ffz-dark .activity-create__actions { .ffz-dark .activity-create__actions {
background-color: #191919; background-color: #191919;
@ -1141,7 +1184,7 @@ body.ffz-dark,
} }
.ffz-dark .activity-create, .ffz-dark .activity-create,
.ffz-dark .activity-react__like { .ffz-dark .activity-react__item {
border-color: #474747; border-color: #474747;
color: #fff !important; color: #fff !important;
background-color: #191919; background-color: #191919;

View file

@ -15,6 +15,8 @@ module.exports = FrankerFaceZ.constants = {
DEBUG: DEBUG, DEBUG: DEBUG,
SERVER: SERVER, SERVER: SERVER,
IS_OSX: navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent),
// Twitch Client ID for API Stuff // Twitch Client ID for API Stuff
CLIENT_ID: "a3bc9znoz6vi8ozsoca0inlcr4fcvkl", CLIENT_ID: "a3bc9znoz6vi8ozsoca0inlcr4fcvkl",
@ -22,11 +24,11 @@ module.exports = FrankerFaceZ.constants = {
WS_SERVER_POOLS: { WS_SERVER_POOLS: {
1: [ 1: [
["wss://catbag.frankerfacez.com/", 0.5], ["wss://catbag.frankerfacez.com/", 0.25],
["wss://andknuckles.frankerfacez.com/", 1], ["wss://andknuckles.frankerfacez.com/", 1],
["wss://tuturu.frankerfacez.com/", 1]], ["wss://tuturu.frankerfacez.com/", 1]],
2: [ 2: [
["ws://localhost:8001/", 1]] ["wss://localhost:8001/", 1]]
}, },
CHAT_COLORS: ["#FF0000", "#0000FF", "#008000", "#B22222", "#FF7F50", "#9ACD32", "#FF4500", "#2E8B57", "#DAA520", "#D2691E", "#5F9EA0", "#1E90FF", "#FF69B4", "#8A2BE2", "#00FF7F"], CHAT_COLORS: ["#FF0000", "#0000FF", "#008000", "#B22222", "#FF7F50", "#9ACD32", "#FF4500", "#2E8B57", "#DAA520", "#D2691E", "#5F9EA0", "#1E90FF", "#FF69B4", "#8A2BE2", "#00FF7F"],

View file

@ -40,16 +40,17 @@ FFZ.prototype.setup_channel = function() {
// Update Existing // Update Existing
var views = utils.ember_views(); var views = utils.ember_views();
for(var key in views) { for(var key in views) {
if ( ! views.hasOwnProperty(key) )
continue;
var view = views[key]; var view = views[key];
if ( !(view instanceof Channel) ) if ( view instanceof Channel ) {
continue; this.log("Manually updating existing Channel Index view.", view);
try {
this.log("Manually updating Channel Index view.", view); if ( ! view.ffzInit )
this._modify_cindex(view); this._modify_cindex(view);
view.ffzInit(); view.ffzInit();
} catch(err) {
this.error("setup: view:channel/index: " + err);
}
}
}; };
@ -145,7 +146,7 @@ FFZ.prototype.setup_channel = function() {
if ( f._cindex ) if ( f._cindex )
f._cindex.ffzFixTitle(); f._cindex.ffzFixTitle();
}.observes("content.status", "content.id"), }.observes("content.status", "content.id", "hostModeTarget.status", "hostModeTarget.id"),
ffzHostTarget: function() { ffzHostTarget: function() {
var target = this.get('content.hostModeTarget'), var target = this.get('content.hostModeTarget'),
@ -245,24 +246,43 @@ FFZ.prototype._modify_cindex = function(view) {
if ( Layout ) if ( Layout )
Layout.set('isTheatreMode', true); Layout.set('isTheatreMode', true);
} }
this.$().on("click", ".ffz-creative-tag-link", function(e) {
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
utils.ember_lookup("router:main").transitionTo('creative.hashtag.index', this.getAttribute('data-tag'));
e.preventDefault();
return false;
});
}, },
ffzFixTitle: function() { ffzFixTitle: function() {
if ( f.has_bttv || ! f.settings.stream_title ) if ( f.has_bttv || ! f.settings.stream_title )
return; return;
var status = this.get("controller.content.status") || this.get("controller.status"), var status = this.get("controller.content.status"),
channel = this.get("controller.content.id") || this.get("controller.id"); channel = this.get("controller.content.id"),
game = this.get("controller.content.game"),
status = f.render_tokens(f.tokenize_line(channel, channel, status, true)); tokens = f.tokenize_line(channel, channel, status, true);
this.$(".title span").each(function(i, el) { if ( game === 'Creative' )
var scripts = el.querySelectorAll("script"); tokens = f.tokenize_ctags(tokens);
if ( ! scripts.length )
el.innerHTML = status; this.$("#broadcast-meta .title").html(f.render_tokens(tokens));
else
el.innerHTML = scripts[0].outerHTML + status + scripts[1].outerHTML; status = this.get('controller.hostModeTarget.status');
}); channel = this.get('controller.hostModeTarget.id');
game = this.get('controller.hostModeTarget.game');
if ( channel ) {
tokens = f.tokenize_line(channel, channel, status, true);
if ( game === 'Creative' )
tokens = f.tokenize_ctags(tokens);
this.$(".target-meta .target-title").html(f.render_tokens(tokens));
}
}, },

View file

@ -871,7 +871,7 @@ FFZ.prototype._modify_chat_input = function(component) {
case KEYCODES.TAB: case KEYCODES.TAB:
// If we do Ctrl-Tab or Alt-Tab. Just don't // If we do Ctrl-Tab or Alt-Tab. Just don't
// even think of doing suggestions. // even think of doing suggestions.
if ( e.ctrlKey || e.altKey ) if ( e.ctrlKey || e.altKey || e.metaKey )
break; break;
e.preventDefault(); e.preventDefault();

View file

@ -366,6 +366,16 @@ FFZ.prototype.setup_chatview = function() {
}.observes("currentChannelRoom", "connectedPrivateGroupRooms"), }.observes("currentChannelRoom", "connectedPrivateGroupRooms"),
ffzSubOwnChannelRoom: function() {
var user = f.get_user(),
room = this.get("currentChannelRoom"),
room_id = room && room.get("id");
if ( user && user.login && user.login === room_id )
room && room.ffzSubscribe && room.ffzSubscribe();
}.observes("currentChannelRoom"),
ffzUpdateInvites: function() { ffzUpdateInvites: function() {
if ( ! f._chatv || f.has_bttv ) if ( ! f._chatv || f.has_bttv )
return; return;
@ -520,8 +530,8 @@ FFZ.prototype._modify_cview = function(view) {
el && el.setAttribute('data-room', room_id || ""); el && el.setAttribute('data-room', room_id || "");
this.$('.textarea-contain').append(f.build_ui_link(this)); this.$('.textarea-contain').append(f.build_ui_link(this));
this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
this.$('.chat-messages').find('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //this.$('.chat-messages').find('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
if ( ! f.has_bttv ) { if ( ! f.has_bttv ) {
if ( f.settings.group_tabs ) if ( f.settings.group_tabs )

View file

@ -101,8 +101,8 @@ FFZ.prototype.setup_conversations = function() {
this.log("Unable to resolve: component:conversation-line"); this.log("Unable to resolve: component:conversation-line");
// TODO: Make this better later. // TODO: Make this better later.
jQuery('.conversations-list').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery('.conversations-list').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery('.conversations-list').find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery('.conversations-list').find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
} }
@ -175,8 +175,8 @@ FFZ.prototype._modify_conversation_window = function(component) {
} }
jQuery('.badge', el).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); jQuery('.badge', el).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
jQuery(el).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery(el).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery(el).find('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery(el).find('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
} }
}); });
} }

View file

@ -55,7 +55,23 @@ FFZ.settings_info.sidebar_hide_recommended_channels = {
}; };
FFZ.settings_info.directory_creative_all_tags = { FFZ.settings_info.sidebar_hide_recommended_friends = {
type: "boolean",
value: true,
category: "Appearance",
no_mobile: true,
name: "Sidebar Recommended Friends",
help: "Display the Recommended Friends section on the sidebar.",
on_update: function(val) {
document.body.classList.toggle('ffz-hide-recommended-friends', !val);
}
};
/*FFZ.settings_info.directory_creative_all_tags = {
type: "boolean", type: "boolean",
value: false, value: false,
@ -68,7 +84,7 @@ FFZ.settings_info.directory_creative_all_tags = {
on_update: function(val) { on_update: function(val) {
document.body.classList.toggle('ffz-creative-tags', val); document.body.classList.toggle('ffz-creative-tags', val);
} }
}; };*/
FFZ.settings_info.directory_creative_showcase = { FFZ.settings_info.directory_creative_showcase = {
@ -123,6 +139,82 @@ FFZ.settings_info.directory_group_hosts = {
}; };
FFZ.settings_info.banned_games = {
type: "button",
value: [],
category: "Directory",
no_mobile: true,
name: "Banned Games",
help: "A list of games that will not be displayed in the Directory.",
on_update: function() {
var banned = this.settings.banned_games,
els = document.querySelectorAll('.ffz-directory-preview');
for(var i=0; i < els.length; i++) {
var el = els[i],
game = el.getAttribute('data-game');
el.classList.toggle('ffz-game-banned', banned.indexOf(game && game.toLowerCase()) !== -1);
}
},
method: function() {
var f = this,
old_val = f.settings.banned_games.join(", ");
utils.prompt(
"Banned Games",
"Please enter a comma-separated list of games that you would like to be banned from viewing in the Directory.</p><p>This is case insensitive, however you must type the full name.</p><p><b>Example:</b> <code>League of Legends, Dota 2, Smite</code>",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
f.settings.set("banned_games", _.unique(new_val.trim().toLowerCase().split(/\s*,\s*/)));
}, 600);
}
}
FFZ.settings_info.spoiler_games = {
type: "button",
value: [],
category: "Directory",
no_mobile: true,
name: "Spoiler Games",
help: "Stream thumbnails will be hidden for games that you add to this list.",
on_update: function() {
var spoiled = this.settings.spoiler_games,
els = document.querySelectorAll('.ffz-directory-preview');
for(var i=0; i < els.length; i++) {
var el = els[i],
game = el.getAttribute('data-game');
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(
"Spoiler 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);
}
}
FFZ.settings_info.directory_host_menus = { FFZ.settings_info.directory_host_menus = {
type: "select", type: "select",
options: { options: {
@ -168,6 +260,7 @@ FFZ.prototype.setup_directory = function() {
document.body.classList.toggle('ffz-creative-tags', this.settings.directory_creative_all_tags); document.body.classList.toggle('ffz-creative-tags', this.settings.directory_creative_all_tags);
document.body.classList.toggle('ffz-creative-showcase', this.settings.directory_creative_showcase); document.body.classList.toggle('ffz-creative-showcase', this.settings.directory_creative_showcase);
document.body.classList.toggle('ffz-hide-recommended-channels', !this.settings.sidebar_hide_recommended_channels); document.body.classList.toggle('ffz-hide-recommended-channels', !this.settings.sidebar_hide_recommended_channels);
document.body.classList.toggle('ffz-hide-recommended-friends', !this.settings.sidebar_hide_recommended_friends);
var GamesFollowing = utils.ember_lookup('controller:games-following'), var GamesFollowing = utils.ember_lookup('controller:games-following'),
f = this; f = this;
@ -197,7 +290,7 @@ FFZ.prototype.setup_directory = function() {
var ChannelView = utils.ember_resolve('component:stream-preview'); var ChannelView = utils.ember_resolve('component:stream-preview');
if ( ChannelView ) { if ( ChannelView ) {
this._modify_directory_live(ChannelView); this._modify_directory_live(ChannelView, false);
try { try {
ChannelView.create().destroy(); ChannelView.create().destroy();
} catch(err) { } } catch(err) { }
@ -205,7 +298,7 @@ FFZ.prototype.setup_directory = function() {
var CreativeChannel = utils.ember_resolve('component:creative-preview'); var CreativeChannel = utils.ember_resolve('component:creative-preview');
if ( CreativeChannel ) { if ( CreativeChannel ) {
this._modify_directory_live(CreativeChannel); this._modify_directory_live(CreativeChannel, false);
try { try {
CreativeChannel.create().destroy(); CreativeChannel.create().destroy();
} catch(err) { } } catch(err) { }
@ -222,20 +315,31 @@ FFZ.prototype.setup_directory = function() {
} catch(err) { } } catch(err) { }
var VideoPreview = utils.ember_resolve('component:video-preview');
if ( VideoPreview ) {
VideoPreview = this._modify_video_preview(VideoPreview);
try { VideoPreview.create().destroy();
} catch(err) { }
}
// Initialize existing views. // Initialize existing views.
var views = utils.ember_views(); var views = utils.ember_views();
for(var key in views) { for(var key in views) {
var view = views[key]; var view = views[key];
if ( (ChannelView && view instanceof ChannelView) || (CreativeChannel && view instanceof CreativeChannel) ) { if ( (ChannelView && view instanceof ChannelView) || (CreativeChannel && view instanceof CreativeChannel) ) {
if ( ! view.ffzInit ) if ( ! view.ffzInit )
this._modify_directory_live(view); this._modify_directory_live(view, false);
} else if ( CSGOChannel && view instanceof CSGOChannel ) { } else if ( CSGOChannel && view instanceof CSGOChannel ) {
if ( ! view.ffzInit ) if ( ! view.ffzInit )
this._modify_directory_live(view, true); this._modify_directory_live(view, true);
} else if ( view instanceof HostView || view.get('tt_content') === 'live_host' ) { } else if ( view instanceof HostView || view.get('tt_content') === 'live_host' ) {
if ( ! view.ffzInit ) if ( ! view.ffzInit )
this._modify_directory_host(view); this._modify_directory_host(view);
} else } else if ( VideoPreview && view instanceof VideoPreview ) {
if ( ! view.ffzInit )
this._modify_video_preview(view);
} else
continue; continue;
try { try {
@ -386,9 +490,15 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
meta = el && el.querySelector('.meta'), meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'), thumb = el && el.querySelector('.thumb'),
cap = thumb && thumb.querySelector('.cap'), cap = thumb && thumb.querySelector('.cap'),
channel_id = this.get(pref + 'channel.name'); channel_id = this.get(pref + 'channel.name'),
game = this.get(pref + 'game');
el.classList.add('ffz-directory-preview');
el.setAttribute('data-channel', channel_id); el.setAttribute('data-channel', channel_id);
el.setAttribute('data-game', game);
el.classList.toggle('ffz-game-banned', f.settings.banned_games.indexOf(game && game.toLowerCase()) !== -1);
el.classList.toggle('ffz-game-spoilered', f.settings.spoiler_games.indexOf(game && game.toLowerCase()) !== -1);
// CSGO doesn't provide the actual uptime information... // CSGO doesn't provide the actual uptime information...
if ( !is_csgo && f.settings.stream_uptime && f.settings.stream_uptime < 3 && cap ) { if ( !is_csgo && f.settings.stream_uptime && f.settings.stream_uptime < 3 && cap ) {
@ -482,6 +592,33 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
} }
FFZ.prototype._modify_video_preview = function(vp) {
var f = this;
vp.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("component:video-preview ffzInit: " + err);
}
},
ffzInit: function() {
var el = this.get('element'),
game = this.get('video.game');
el.classList.add('ffz-directory-preview');
el.setAttribute('data-channel', this.get('video.channel.id'));
el.setAttribute('data-game', game);
el.classList.toggle('ffz-game-banned', f.settings.banned_games.indexOf(game && game.toLowerCase()) !== -1);
el.classList.toggle('ffz-game-spoilered', f.settings.spoiler_games.indexOf(game && game.toLowerCase()) !== -1);
}
});
}
FFZ.prototype._modify_directory_host = function(dir) { FFZ.prototype._modify_directory_host = function(dir) {
var f = this, mutator; var f = this, mutator;
@ -613,11 +750,17 @@ FFZ.prototype._modify_directory_host = function(dir) {
title = meta && meta.querySelector('.title a'), title = meta && meta.querySelector('.title a'),
target = this.get('stream.target.channel'), target = this.get('stream.target.channel'),
game = this.get('stream.target.meta_game'),
hosts = this.get('stream.ffz_hosts'); //, hosts = this.get('stream.ffz_hosts'); //,
//boxart = thumb && thumb.querySelector('.boxart'); //boxart = thumb && thumb.querySelector('.boxart');
el.setAttribute('data-channel', target.name); el.classList.add('ffz-directory-preview');
el.setAttribute('data-channel', target.name);
el.setAttribute('data-game', game);
el.classList.toggle('ffz-game-banned', f.settings.banned_games.indexOf(game && game.toLowerCase()) !== -1);
el.classList.toggle('ffz-game-spoilered', f.settings.spoiler_games.indexOf(game && game.toLowerCase()) !== -1);
this._ffz_image_timer = setInterval(this.ffzRotateImage.bind(this), 30000); this._ffz_image_timer = setInterval(this.ffzRotateImage.bind(this), 30000);
this.ffzRotateImage(); this.ffzRotateImage();

View file

@ -21,10 +21,11 @@ var FFZ = window.FrankerFaceZ,
FFZ.prototype.setup_feed_cards = function() { FFZ.prototype.setup_feed_cards = function() {
var FeedCard = utils.ember_resolve('component:feed-card'); var FeedCard = utils.ember_resolve('component:channel-feed/card');
if ( ! FeedCard ) if ( ! FeedCard )
return; return this.error("Unable to locate component:channel-feed/card");
this.log("Modifying the feed-card component.");
this._modify_feed_card(FeedCard); this._modify_feed_card(FeedCard);
try { FeedCard.create().destroy(); try { FeedCard.create().destroy();
@ -35,7 +36,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:feed-card'), var FeedCard = utils.ember_resolve('component:channel-feed/card'),
views = utils.ember_views(); views = utils.ember_views();
if ( ! FeedCard ) if ( ! FeedCard )
@ -49,7 +50,7 @@ FFZ.prototype.rerender_feed_cards = function(for_set) {
this._modify_feed_card(view); this._modify_feed_card(view);
view.ffzInit(for_set); view.ffzInit(for_set);
} catch(err) { } catch(err) {
this.error("setup component:feed-card ffzInit: " + err) this.error("setup component:channel-feed/card ffzInit: " + err)
} }
} }
} }
@ -64,7 +65,7 @@ FFZ.prototype._modify_feed_card = function(component) {
try { try {
this.ffzInit(); this.ffzInit();
} catch(err) { } catch(err) {
f.error("component:feed-card ffzInit: " + err); f.error("component:channel-feed/card ffzInit: " + err);
} }
}, },
@ -73,7 +74,7 @@ FFZ.prototype._modify_feed_card = function(component) {
message = this.get('post.body'), message = this.get('post.body'),
emotes = parse_emotes(this.get('post.emotes')), emotes = parse_emotes(this.get('post.emotes')),
user_id = this.get('post.user.login'), user_id = this.get('post.user.login'),
room_id = this.get('channelId'), room_id = this.get('channelId') || user_id,
pbody = el && el.querySelector('.activity-body'); pbody = el && el.querySelector('.activity-body');
if ( ! message || ! el || ! pbody ) if ( ! message || ! el || ! pbody )
@ -91,8 +92,8 @@ FFZ.prototype._modify_feed_card = function(component) {
pbody.innerHTML = '<p>' + output + '</p>'; pbody.innerHTML = '<p>' + output + '</p>';
jQuery('.ffz-tooltip', pbody).tipsy({html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery('.ffz-tooltip', pbody).tipsy({html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery('.html-tooltip', pbody).tipsy({html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery('.html-tooltip', pbody).tipsy({html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
} }
}); });
} }

View file

@ -1,6 +1,8 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
utils = require('../utils'), utils = require('../utils'),
constants = require('../constants'); constants = require('../constants'),
createElement = utils.createElement;
// -------------------- // --------------------
@ -13,7 +15,7 @@ FFZ.settings_info.enhance_profile_following = {
category: "Directory", category: "Directory",
name: "Enhanced Following Control", name: "Enhanced Following Control",
help: "Display additional controls on your own profile's Following tab to make management easier." help: "Display additional controls on your own profile's Following tab to make management easier, as well as telling you how long everyone has been following everyone else in the profile."
} }
@ -29,6 +31,7 @@ FFZ.prototype.setup_profile_following = function() {
// Build our is-following cache. // Build our is-following cache.
this._following_cache = {}; this._following_cache = {};
this._follower_cache = {};
// First, we need to hook the model. This is what we'll use to grab the following notification state, // First, we need to hook the model. This is what we'll use to grab the following notification state,
// rather than making potentially hundreds of API requests. // rather than making potentially hundreds of API requests.
@ -36,12 +39,24 @@ FFZ.prototype.setup_profile_following = function() {
if ( Following ) if ( Following )
this._hook_following(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. // Also try hooking that other model.
var Notification = utils.ember_resolve('model:notification'); var Notification = utils.ember_resolve('model:notification');
if ( Notification ) if ( Notification )
this._hook_following(Notification, true); 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. // Now, we need to edit the profile Following view itself.
var ProfileView = utils.ember_resolve('view:channel/following'); var ProfileView = utils.ember_resolve('view:channel/following');
if ( ! ProfileView ) if ( ! ProfileView )
@ -57,155 +72,171 @@ FFZ.prototype.setup_profile_following = function() {
} }
}, },
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: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.get('isLoaded') || following.get('isLoading') ) {
refresher(following);
count++;
counted = true;
}
if ( followers.get('isLoaded') || followers.get('isLoading') ) {
refresher(followers);
if ( ! counted )
count++;
}
}
}
f.log("Refreshing previously loaded user following data for " + count + " channels.");
}
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() { willClearRender: function() {
try { try {
this.ffzTeardown(); this.ffzTeardown();
} catch(err) { } catch(err) {
f.error("ProvileView ffzTeardown: " + err); f.error("component:display-followed-item ffzTeardown: " + err);
} }
this._super();
}, },
ffzInit: function() { ffzInit: function() {
// Only process our own profile following page. var el = this.get('element'),
var user = f.get_user(); channel_id = this.get('parentView.parentView.model.id'),
if ( ! f.settings.enhance_profile_following || ! user || user.login !== this.get('context.model.id') ) is_following = document.body.getAttribute('data-current-path').indexOf('.following') !== -1,
user = f.get_user(),
mine = user && user.login && user.login === channel_id,
big_cache = is_following ? f._following_cache : f._follower_cache;
user_cache = big_cache[channel_id] = big_cache[channel_id] || {},
user_id = this.get('followed.id'),
data = user_cache[user_id];
if ( ! f.settings.enhance_profile_following )
return; return;
var el = this.get('element'), el.classList.add('ffz-processed');
users = el && el.querySelectorAll('.user.item');
el.classList.add('ffz-enhanced-following'); // TODO: REMOVE
window._d = this;
var had_data = true;
if ( users && users.length )
for(var i=0; i < users.length; i++)
had_data = this.ffzProcessUser(users[i]) && had_data;
else
had_data = false;
if ( ! had_data ) {
// Force a refresh.
f.log("Forcing a refresh of user following data.");
var following = this.get('context.following'),
refresher = function() {
if ( following.get('isLoading') )
setTimeout(refresher, 25);
following.clear();
following.load();
}
// Make sure the Following is modified.
f._hook_following(following);
// We use this weird function to prevent trying to load twice mucking things up.
setTimeout(refresher);
}
// Watch for new ones the bad way.
if ( ! this._ffz_observer ) {
var t = this;
var observer = this._ffz_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 it an ember-view? Check its kids.
if ( added.classList.contains('user') )
t.ffzProcessUser(added);
else if ( added.classList.contains('ember-view') ) {
var users = added.querySelectorAll('.user.item');
if ( users )
for(var y=0; y < users.length; y++)
t.ffzProcessUser(users[y]);
}
}
}
});
observer.observe(el, {
childList: true,
subtree: true
});
}
},
ffzTeardown: function() {
if ( this._ffz_observer ) {
this._ffz_observer.disconnect();
this._ffz_observer = null;
}
},
ffzProcessUser: function(user) {
if ( user.classList.contains('ffz-processed') )
return true;
var link = user.querySelector('a'),
link_parts = link && link.href.split("/"),
user_id = link_parts && link_parts[3],
data = f._following_cache[user_id],
t_el = document.createElement('div');
user.classList.add('ffz-processed');
if ( ! data ) if ( ! data )
return false; return false;
t_el.className = 'overlay_info length';
jQuery(t_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 's')});
var now = Date.now() - (f._ws_server_offset || 0), var now = Date.now() - (f._ws_server_offset || 0),
age = data[0] ? Math.floor((now - data[0].getTime()) / 1000) : 0; age = data[0] ? Math.floor((now - data[0].getTime()) / 1000) : 0,
if ( age ) { t_el = createElement('div', 'overlay_info length'),
t_el.innerHTML = constants.CLOCK + ' ' + utils.human_time(age, 10);
t_el.setAttribute('original-title', 'Following Since: <nobr>' + data[0].toLocaleString() + '</nobr>');
} else
t_el.style.display = 'none';
user.appendChild(t_el); update_time = function() {
var now = Date.now() - (f._ws_server_offset || 0),
age = data && data[0] ? Math.floor((now - data[0].getTime()) / 1000) : undefined;
var actions = document.createElement('div'), if ( age !== undefined ) {
follow = document.createElement('button'), t_el.innerHTML = constants.CLOCK + ' ' + (age < 60 ? 'now' : utils.human_time(age, 10));
notif = document.createElement('button'), t_el.title = 'Following Since: <nobr>' + data[0].toLocaleString() + '</nobr>';
t_el.style.display = '';
} else
t_el.style.display = 'none';
};
update_time();
jQuery(t_el).tipsy({html:true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 's')});
el.appendChild(t_el);
if ( ! mine || ! is_following )
return;
var actions = createElement('div', 'actions'),
follow = createElement('button', 'button follow'),
notif = createElement('button', 'button notifications'),
update_follow = function() { update_follow = function() {
data = f._following_cache[user_id]; data = user_cache[user_id];
user.classList.toggle('followed', data); el.classList.toggle('followed', data);
follow.innerHTML = constants.HEART + constants.UNHEART + '<span> Follow</span>'; follow.innerHTML = constants.HEART + constants.UNHEART + '<span> Follow</span>';
if ( t_el ) {
var now = Date.now() - (f._ws_server_offset || 0),
age = data && data[0] ? Math.floor((now - data[0].getTime()) / 1000) : undefined;
if ( age !== undefined ) {
t_el.innerHTML = constants.CLOCK + ' ' + (age < 60 ? 'now' : utils.human_time(age, 10));
t_el.setAttribute('original-title', 'Following Since: <nobr>' + data[0].toLocaleString() + '</nobr>');
t_el.style.display = '';
} else {
t_el.style.display = 'none';
}
}
}, },
update_notif = function() { update_notif = function() {
data = f._following_cache[user_id]; data = user_cache[user_id];
notif.classList.toggle('notifications-on', data && data[1]); notif.classList.toggle('notifications-on', data && data[1]);
notif.textContent = 'Notification ' + (data && data[1] ? 'On' : 'Off'); notif.textContent = 'Notification ' + (data && data[1] ? 'On' : 'Off');
}; };
actions.className = 'actions';
follow.className = 'button follow';
notif.className = 'button notifications';
update_follow(); update_follow();
update_notif(); update_notif();
@ -220,11 +251,12 @@ FFZ.prototype.setup_profile_following = function() {
utils.api.del("users/:login/follows/channels/" + user_id) : utils.api.del("users/:login/follows/channels/" + user_id) :
utils.api.put("users/:login/follows/channels/" + user_id, {notifications: false})) utils.api.put("users/:login/follows/channels/" + user_id, {notifications: false}))
.done(function() { .done(function() {
data = f._following_cache[user_id] = was_following ? null : [new Date(Date.now() - (f._ws_server_offset||0)), false]; data = user_cache[user_id] = was_following ? null : [new Date(Date.now() - (f._ws_server_offset||0)), false];
}) })
.always(function() { .always(function() {
update_follow(); update_follow();
update_notif(); update_notif();
update_time();
follow.disabled = false; follow.disabled = false;
notif.disabled = false; notif.disabled = false;
}) })
@ -250,32 +282,14 @@ FFZ.prototype.setup_profile_following = function() {
actions.appendChild(follow); actions.appendChild(follow);
actions.appendChild(notif); actions.appendChild(notif);
user.appendChild(actions);
return true; el.appendChild(actions);
},
ffzTeardown: function() {
} }
}); });
// TODO: Add nice Manage Following button to the directory.
// Now, rebuild any views.
try {
ProfileView.create().destroy();
} catch(err) { }
var views = utils.ember_views();
if ( views )
for(var key in views) {
var view = views[key];
if ( view instanceof ProfileView ) {
this.log("Manually updating existing Following View.", view);
try {
view.ffzInit();
} catch(err) {
this.error("setup: view:channel/following: " + err);
}
}
}
} }
@ -287,12 +301,10 @@ FFZ.prototype._hook_following = function(Following) {
Following.reopen({ Following.reopen({
ffz_hooked: true, ffz_hooked: true,
apiLoad: function(e) { apiLoad: function(e) {
var user = f.get_user(), var channel_id = this.get('id'),
channel_id = this.get('id'),
t = this; t = this;
if ( ! user || user.login !== channel_id ) f._following_cache[channel_id] = f._following_cache[channel_id] || {};
return this._super(e);
return new RSVP.Promise(function(success, fail) { return new RSVP.Promise(function(success, fail) {
t._super(e).then(function(data) { t._super(e).then(function(data) {
@ -307,7 +319,47 @@ FFZ.prototype._hook_following = function(Following) {
if ( follow.channel.display_name ) if ( follow.channel.display_name )
FFZ.capitalization[follow.channel.name] = [follow.channel.display_name, now]; FFZ.capitalization[follow.channel.name] = [follow.channel.display_name, now];
f._following_cache[follow.channel.name] = [follow.created_at ? utils.parse_date(follow.created_at) : null, follow.notifications || false]; 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.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];
} }
} }

View file

@ -145,7 +145,6 @@ FFZ.prototype.setup_layout = function() {
return; return;
document.body.classList.toggle("ffz-sidebar-swap", this.settings.swap_sidebars); document.body.classList.toggle("ffz-sidebar-swap", this.settings.swap_sidebars);
document.body.classList.toggle("ffz-portrait", this.settings.portrait_mode);
this.log("Creating layout style element."); this.log("Creating layout style element.");
var s = this._layout_style = document.createElement('style'); var s = this._layout_style = document.createElement('style');
@ -328,4 +327,5 @@ FFZ.prototype.setup_layout = function() {
// Force re-calculation of everything. // Force re-calculation of everything.
Ember.propertyDidChange(Layout, 'windowWidth'); Ember.propertyDidChange(Layout, 'windowWidth');
Ember.propertyDidChange(Layout, 'windowHeight'); Ember.propertyDidChange(Layout, 'windowHeight');
Layout.ffzUpdatePortraitCSS();
} }

View file

@ -1024,6 +1024,7 @@ FFZ.prototype._modify_vod_line = function(component) {
FFZ.capitalization = {}; FFZ.capitalization = {};
FFZ._cap_fetching = 0; FFZ._cap_fetching = 0;
FFZ._cap_waiting = {};
FFZ.get_capitalization = function(name, callback) { FFZ.get_capitalization = function(name, callback) {
if ( ! name ) if ( ! name )
@ -1039,13 +1040,25 @@ FFZ.get_capitalization = function(name, callback) {
return old_data[0]; return old_data[0];
} }
if ( FFZ._cap_fetching < 25 ) { if ( FFZ._cap_waiting[name] )
FFZ._cap_waiting[name].push(callback);
else if ( FFZ._cap_fetching < 25 ) {
FFZ._cap_fetching++; FFZ._cap_fetching++;
FFZ.get().ws_send("get_display_name", name, function(success, data) { FFZ._cap_waiting[name] = [callback];
var cap_name = success ? data : name;
FFZ.get().ws_send("get_display_name", name, function(success, data) {
var cap_name = success ? data : name,
waiting = FFZ._cap_waiting[name];
FFZ.capitalization[name] = [cap_name, Date.now()]; FFZ.capitalization[name] = [cap_name, Date.now()];
FFZ._cap_fetching--; FFZ._cap_fetching--;
typeof callback === "function" && callback(cap_name); FFZ._cap_waiting[name] = false;
for(var i=0; i < waiting.length; i++)
try {
typeof waiting[i] === "function" && waiting[i](cap_name);
} catch(err) { }
}); });
} }

View file

@ -704,6 +704,13 @@ FFZ.prototype.setup_mod_card = function() {
} }
// Follow Button
var follow_button = el.querySelector(".interface > .follow-button");
if ( follow_button )
jQuery(follow_button).tipsy({title: function() { return follow_button.classList.contains('is-following') ? "Unfollow" : "Follow"}});
// Whisper and Message Buttons
var msg_btn = el.querySelector(".interface > button.message-button"); var msg_btn = el.querySelector(".interface > button.message-button");
if ( msg_btn ) { if ( msg_btn ) {
msg_btn.innerHTML = 'W'; msg_btn.innerHTML = 'W';
@ -1082,8 +1089,8 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
jQuery('.deleted-word', l_el).click(function(e) { jQuery(this).trigger('mouseout'); this.outerHTML = this.getAttribute('data-text'); }); jQuery('.deleted-word', l_el).click(function(e) { jQuery(this).trigger('mouseout'); this.outerHTML = this.getAttribute('data-text'); });
jQuery('a.deleted-link', l_el).click(f._deleted_link_click); jQuery('a.deleted-link', l_el).click(f._deleted_link_click);
jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) }); jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) });
jQuery('.html-tooltip', l_el).tipsy({html:true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); //jQuery('.html-tooltip', l_el).tipsy({html:true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
jQuery('.ffz-tooltip', l_el).tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); //jQuery('.ffz-tooltip', l_el).tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
if ( modcard ) { if ( modcard ) {
modcard.get('cardInfo.user.id') !== msg.from && jQuery('span.from', l_el).click(function(e) { modcard.get('cardInfo.user.id') !== msg.from && jQuery('span.from', l_el).click(function(e) {

View file

@ -164,9 +164,9 @@ FFZ.prototype._modify_player = function(player) {
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'});
// Give the player time to do stuff before we change this /*// Give the player time to do stuff before we change this
// text. It's a bit weird otherwise. // text. It's a bit weird otherwise.
setTimeout(function(){stats.children('.js-stats-toggle').html(constants.CLOSE);},500); setTimeout(function(){stats.children('.js-stats-toggle').html(constants.CLOSE);},500);*/
// Only set up the stats hooks if we need stats. // Only set up the stats hooks if we need stats.

View file

@ -4,6 +4,15 @@ var FFZ = window.FrankerFaceZ,
utils = require('../utils'), utils = require('../utils'),
helpers, helpers,
STATUS_BADGES = [
["r9k", "r9k", "This room is in R9K-mode."],
["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." }],
["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." }]
],
// StrimBagZ Support // StrimBagZ Support
is_android = navigator.userAgent.indexOf('Android') !== -1, is_android = navigator.userAgent.indexOf('Android') !== -1,
@ -210,152 +219,47 @@ FFZ.prototype._modify_rview = function(view) {
ffzUpdateStatus: function() { ffzUpdateStatus: function() {
var room = this.get('controller.model'), var room = this.get('controller.model'),
el = this.get('element'), el = this.get('element'),
cont = el && el.querySelector('.chat-buttons-container'); cont = el && el.querySelector('.chat-buttons-container');
if ( ! cont ) if ( ! cont )
return; return;
var r9k_badge = cont.querySelector('#ffz-stat-r9k'), var btn = cont.querySelector('button');
sub_badge = cont.querySelector('#ffz-stat-sub'),
emote_badge = cont.querySelector('#ffz-stat-emote'),
slow_badge = cont.querySelector('#ffz-stat-slow'),
banned_badge = cont.querySelector('#ffz-stat-banned'),
delay_badge = cont.querySelector('#ffz-stat-delay'),
batch_badge = cont.querySelector('#ffz-stat-batch'),
btn = cont.querySelector('button');
if ( f.has_bttv || ! f.settings.room_status ) { if ( f.has_bttv || ! f.settings.room_status ) {
if ( r9k_badge ) jQuery(".ffz.room-state", cont).remove();
r9k_badge.parentElement.removeChild(r9k_badge);
if ( sub_badge )
sub_badge.parentElement.removeChild(sub_badge);
if ( emote_badge )
emote_badge.parentElement.removeChild(emote_badge);
if ( slow_badge )
slow_badge.parentElement.removeChild(slow_badge);
if ( delay_badge )
delay_badge.parentElement.removeChild(delay_badge);
if ( batch_badge )
batch_badge.parentElement.removeChild(batch_badge);
if ( btn ) if ( btn )
btn.classList.remove('ffz-waiting'); btn.classList.remove('ffz-waiting');
return; return;
}
if ( ! r9k_badge ) { } else if ( btn ) {
r9k_badge = document.createElement('span');
r9k_badge.className = 'ffz room-state stat float-right';
r9k_badge.id = 'ffz-stat-r9k';
r9k_badge.innerHTML = 'R<span>9K</span>';
r9k_badge.title = "This room is in R9K-mode.";
cont.appendChild(r9k_badge);
jQuery(r9k_badge).tipsy({gravity:"s", offset:15});
}
if ( ! sub_badge ) {
sub_badge = document.createElement('span');
sub_badge.className = 'ffz room-state stat float-right';
sub_badge.id = 'ffz-stat-sub';
sub_badge.innerHTML = 'S<span>UB</span>';
sub_badge.title = "This room is in subscribers-only mode.";
cont.appendChild(sub_badge);
jQuery(sub_badge).tipsy({gravity:"s", offset:15});
}
if ( ! emote_badge ) {
emote_badge = document.createElement('span');
emote_badge.className = 'ffz room-state stat float-right';
emote_badge.id = 'ffz-stat-emote';
emote_badge.innerHTML = 'E<span>MOTE</span>';
emote_badge.title = "This room is in Twitch emote-only mode. Emotes added by extensions are not permitted in this mode.";
cont.appendChild(emote_badge);
jQuery(emote_badge).tipsy({gravity: "s", offset: 15});
}
if ( ! slow_badge ) {
slow_badge = document.createElement('span');
slow_badge.className = 'ffz room-state stat float-right';
slow_badge.id = 'ffz-stat-slow';
slow_badge.innerHTML = 'S<span>LOW</span>';
cont.appendChild(slow_badge);
jQuery(slow_badge).tipsy({gravity:"s", offset:15});
}
if ( ! banned_badge ) {
banned_badge = document.createElement('span');
banned_badge.className = 'ffz room-state stat float-right';
banned_badge.id = 'ffz-stat-banned';
banned_badge.innerHTML = 'B<span>AN</span>';
banned_badge.title = "You have been banned from talking in this room.";
cont.appendChild(banned_badge);
jQuery(banned_badge).tipsy({gravity:"s", offset:15});
}
if ( ! delay_badge ) {
delay_badge = document.createElement('span');
delay_badge.className = 'ffz room-state stat float-right';
delay_badge.id = 'ffz-stat-delay';
delay_badge.innerHTML = 'D<span>ELAY</span>';
cont.appendChild(delay_badge);
jQuery(delay_badge).tipsy({gravity:"s", offset:15});
}
if ( ! batch_badge ) {
batch_badge = document.createElement('span');
batch_badge.className = 'ffz room-state stat float-right';
batch_badge.id = 'ffz-stat-batch';
batch_badge.innerHTML = 'B<span>ATCH</span>';
cont.appendChild(batch_badge);
jQuery(batch_badge).tipsy({gravity:"s", offset:15});
}
var vis_count = 0,
r9k_vis = room && room.get('r9k'),
sub_vis = room && room.get('subsOnly'),
emote_vis = room && room.get('emoteOnly') && room.get('emoteOnly') !== '0',
ban_vis = room && room.get('ffz_banned'),
slow_vis = room && room.get('slowMode'),
delay_vis = f.settings.chat_delay !== 0,
batch_vis = f.settings.chat_batching !== 0;
if ( r9k_vis ) vis_count += 1;
if ( sub_vis ) vis_count += 1;
if ( emote_vis ) vis_count += 1;
if ( ban_vis ) vis_count += 1;
if ( slow_vis ) vis_count += 1;
if ( delay_vis ) vis_count += 1;
if ( batch_vis ) vis_count += 1;
r9k_badge.classList.toggle('truncated', vis_count > 3);
sub_badge.classList.toggle('truncated', vis_count > 3);
emote_badge.classList.toggle('truncated', vis_count > 3);
banned_badge.classList.toggle('truncated', vis_count > 3);
slow_badge.classList.toggle('truncated', vis_count > 3);
delay_badge.classList.toggle('truncated', vis_count > 3);
batch_badge.classList.toggle('truncated', vis_count > 3);
r9k_badge.classList.toggle('hidden', ! r9k_vis);
sub_badge.classList.toggle('hidden', ! sub_vis);
emote_badge.classList.toggle('hidden', ! emote_vis);
banned_badge.classList.toggle('hidden', ! ban_vis);
slow_badge.classList.toggle('hidden', ! slow_vis);
slow_badge.title = "This room is in slow mode. You may send messages every " + utils.number_commas(room && room.get('slow')||120) + " seconds.";
delay_badge.title = "You have enabled artificial chat delay. Messages are displayed after " + (f.settings.chat_delay/1000) + " seconds.";
delay_badge.classList.toggle('hidden', ! delay_vis);
batch_badge.title = "You have enabled chat message batching. Messages are displayed in " + (f.settings.chat_batching/1000) + " second increments.";
batch_badge.classList.toggle('hidden', ! batch_vis);
if ( btn ) {
btn.classList.toggle('ffz-waiting', (room && room.get('slowWait') || 0)); btn.classList.toggle('ffz-waiting', (room && room.get('slowWait') || 0));
btn.classList.toggle('ffz-banned', (room && room.get('ffz_banned'))); btn.classList.toggle('ffz-banned', (room && room.get('ffz_banned')));
} }
var badge, id, info, vis_count = 0;
for(var i=0; i < STATUS_BADGES.length; i++) {
info = STATUS_BADGES[i];
id = 'ffz-stat-' + info[0];
badge = cont.querySelector('#' + id);
visible = typeof info[1] === "function" ? info[1].call(f, room) : room && room.get(info[1]);
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.id = id;
jQuery(badge).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'se')});
cont.appendChild(badge);
}
badge.title = typeof info[2] === "function" ? info[2].call(f, room) : info[2];
badge.classList.toggle('hidden', ! visible);
if ( visible )
vis_count++;
}
jQuery(".ffz.room-state", cont).toggleClass("truncated", vis_count > 3);
}.observes('controller.model'), }.observes('controller.model'),
ffzEnableFreeze: function() { ffzEnableFreeze: function() {
@ -677,7 +581,8 @@ FFZ.prototype.add_room = function(id, room) {
} }
// Let the server know where we are. // Let the server know where we are.
this.ws_send("sub", "room." + id); room && room.ffzSubscribe && room.ffzSubscribe();
//this.ws_send("sub", "room." + id);
// See if we need history? // See if we need history?
if ( ! this.has_bttv && this.settings.chat_history && room && (room.get('messages.length') || 0) < 10 ) { if ( ! this.has_bttv && this.settings.chat_history && room && (room.get('messages.length') || 0) < 10 ) {
@ -983,7 +888,7 @@ FFZ.prototype._modify_room = function(room) {
room_id = this.get('id'); 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) ) 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; return this.ffzUnsubscribe(true);
this.destroy(); this.destroy();
}, },
@ -993,6 +898,24 @@ FFZ.prototype._modify_room = function(room) {
f._roomv.ffzUpdateStatus(); f._roomv.ffzUpdateStatus();
}.observes('r9k', 'subsOnly', 'emoteOnly', 'slow', 'ffz_banned'), }.observes('r9k', 'subsOnly', 'emoteOnly', 'slow', 'ffz_banned'),
ffzShouldSubscribe: function() {
var Chat = utils.ember_lookup('controller:chat'),
room_id = this.get('id');
return (Chat && Chat.get('currentChannelRoom') === this) || (f.settings.pinned_rooms && f.settings.pinned_rooms.indexOf(room_id) !== -1);
},
ffzSubscribe: function() {
if ( this.ffzShouldSubscribe() )
f.ws_send("sub", "room." + this.get('id'));
},
ffzUnsubscribe: function(not_always) {
if ( ! not_always || ! this.ffzShouldSubscribe() )
f.ws_send("unsub", "room." + this.get('id'));
},
// User Level // User Level
ffzUserLevel: function() { ffzUserLevel: function() {
if ( this.get('isStaff') ) if ( this.get('isStaff') )
@ -1300,6 +1223,12 @@ FFZ.prototype._modify_room = function(room) {
if ( msg.color ) if ( msg.color )
f._handle_color(msg.color); f._handle_color(msg.color);
// Message Filtering
var i = f._chat_filters.length;
while(i--)
if ( f._chat_filters[i](msg) === false )
return;
// 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/");
@ -1308,6 +1237,10 @@ FFZ.prototype._modify_room = function(room) {
return this._super(msg); return this._super(msg);
}, },
ffzChatFilters: function(msg) {
var i = f._chat_filters.length;
},
setHostMode: function(e) { setHostMode: function(e) {
this.set('ffz_host_target', e && e.hostTarget || null); this.set('ffz_host_target', e && e.hostTarget || null);
var user = f.get_user(); var user = f.get_user();

View file

@ -154,7 +154,7 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
if ( f.settings.chat_hover_pause ) if ( f.settings.chat_hover_pause )
this.ffzEnableFreeze(); this.ffzEnableFreeze();
this.$('.chat-messages').find('.html-tooltip').tipsy({ /*this.$('.chat-messages').find('.html-tooltip').tipsy({
live: true, html: true, live: true, html: true,
gravity: utils.tooltip_placement(2 * constants.TOOLTIP_DISTANCE, function() { gravity: utils.tooltip_placement(2 * constants.TOOLTIP_DISTANCE, function() {
return this.classList.contains('right') ? 'e' : 'n' return this.classList.contains('right') ? 'e' : 'n'
@ -165,7 +165,7 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
title: f.render_tooltip(), title: f.render_tooltip(),
gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, function() { gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, function() {
return this.classList.contains('right') ? 'e' : 'n' return this.classList.contains('right') ? 'e' : 'n'
})}); })});*/
}, },
ffzTeardown: function() { ffzTeardown: function() {

View file

@ -50,6 +50,8 @@ var API = FFZ.API = function(instance, name, icon, version) {
this.global_sets = []; this.global_sets = [];
this.default_sets = []; this.default_sets = [];
this.users = {};
this.chat_filters = [];
this.on_room_callbacks = []; this.on_room_callbacks = [];
this.name = name || ("Extension#" + this.id); this.name = name || ("Extension#" + this.id);
@ -384,6 +386,76 @@ API.prototype.unregister_room_set = function(room_id, id) {
} }
// -----------------------
// User Modifications
// -----------------------
API.prototype.user_add_set = function(user_name, set_id) {
var user = this.users[user_name] = this.users[user_name] || {},
ffz_user = this.ffz.users[user_name] = this.ffz.users[user_name] || {},
emote_sets = user.sets = user.sets || [],
ffz_sets = ffz_user.sets = ffz_user.sets || [],
exact_id = this.id + '-' + set_id;
if ( emote_sets.indexOf(exact_id) === -1 )
emote_sets.push(exact_id);
if ( ffz_sets.indexOf(exact_id) === -1 )
ffz_sets.push(exact_id);
// Update tab completion.
var user = this.ffz.get_user();
if ( this.ffz._inputv && user && user.login === user_name )
Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
}
API.prototype.user_remove_set = function(user_name, set_id) {
var user = this.users[user_name],
ffz_user = this.ffz.users[user_name],
emote_sets = user && user.sets,
ffz_sets = ffz_user && ffz_user.sets,
exact_id = this.id + '-' + set_id;
var ind = emote_sets ? emote_sets.indexOf(exact_id) : -1;
if ( ind !== -1 )
emote_sets.splice(ind, 1);
ind = ffz_sets ? ffz_sets.indexOf(exact_id) : -1;
if ( ind !== -1 )
ffz_sets.splice(ind, 1);
// Update tab completion.
var user = this.ffz.get_user();
if ( this.ffz._inputv && user && user.login === user_name )
Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
}
// -----------------------
// Chat Callback
// -----------------------
API.prototype.register_chat_filter = function(filter) {
this.chat_filters.push(filter);
this.ffz._chat_filters.push(filter);
}
API.prototype.unregister_chat_filter = function(filter) {
var ind = this.chat_filters.indexOf(filter);
if ( ind !== -1 )
this.chat_filters.splice(ind, 1);
ind = this.ffz._chat_filters.indexOf(filter);
if ( ind !== -1 )
this.ffz._chat_filters.splice(ind, 1);
}
// ----------------------- // -----------------------
// Channel Callbacks // Channel Callbacks
// ----------------------- // -----------------------

View file

@ -102,8 +102,8 @@ FFZ.prototype.find_rechat = function() {
// Tooltips // Tooltips
jQuery(container).find('.tooltip').tipsy({live: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); jQuery(container).find('.tooltip').tipsy({live: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
jQuery(container).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery(container).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery(container).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery(container).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
// Load the room data. // Load the room data.
var room_id = el.getAttribute('data-room'); var room_id = el.getAttribute('data-room');

View file

@ -11,6 +11,7 @@ var FFZ = window.FrankerFaceZ = function() {
// Logging // Logging
this._log_data = []; this._log_data = [];
this._apis = {}; this._apis = {};
this._chat_filters = [];
// Error Logging // Error Logging
var t = this; var t = this;
@ -35,7 +36,7 @@ FFZ.msg_commands = {};
// Version // Version
var VER = FFZ.version_info = { var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 159, major: 3, minor: 5, revision: 169,
toString: function() { toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
} }
@ -91,7 +92,7 @@ FFZ.prototype.paste_logs = function() {
FFZ.prototype._pastebin = function(data, callback) { FFZ.prototype._pastebin = function(data, callback) {
jQuery.ajax({url: "http://putco.de/", type: "PUT", data: data, context: this}) jQuery.ajax({url: "https://putco.de/", type: "PUT", data: data, context: this})
.success(function(e) { .success(function(e) {
callback.call(this, e.trim() + ".log"); callback.call(this, e.trim() + ".log");
}).fail(function(e) { }).fail(function(e) {

View file

@ -413,8 +413,8 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1,
el.appendChild(label); el.appendChild(label);
el.appendChild(help); el.appendChild(help);
menu.appendChild(el); menu.appendChild(el);
jQuery('.html-tooltip', el).tipsy({html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery('.html-tooltip', el).tipsy({html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery('.ffz-tooltip', el).tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery('.ffz-tooltip', el).tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
} }
container.appendChild(menu); container.appendChild(menu);

View file

@ -153,10 +153,12 @@ FFZ.prototype.ws_create = function() {
// Send the current rooms. // Send the current rooms.
for(var room_id in f.rooms) { for(var room_id in f.rooms) {
if ( ! f.rooms.hasOwnProperty(room_id) || ! f.rooms[room_id] ) var room = f.rooms[room_id];
if ( ! f.rooms.hasOwnProperty(room_id) || ! room )
continue; continue;
f.ws_send("sub", "room." + room_id); room.room && room.room.ffzSubscribe && room.room.ffzSubscribe();
//f.ws_send("sub", "room." + room_id);
if ( f.rooms[room_id].needs_history ) { if ( f.rooms[room_id].needs_history ) {
f.rooms[room_id].needs_history = false; f.rooms[room_id].needs_history = false;

View file

@ -719,6 +719,11 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) {
return '<img class="emoticon ffz-tooltip' + (cls||'') + '"' + (extra||'') + ' src="' + utils.quote_attr(src) + '"' + (srcset ? ' srcset="' + utils.quote_attr(srcset) + '"' : '') + ' alt="' + utils.quote_attr(token.altText) + '">'; return '<img class="emoticon ffz-tooltip' + (cls||'') + '"' + (extra||'') + ' src="' + utils.quote_attr(src) + '"' + (srcset ? ' srcset="' + utils.quote_attr(srcset) + '"' : '') + ' alt="' + utils.quote_attr(token.altText) + '">';
} }
else if ( token.type === "tag" ) {
var link = Twitch.uri.game("Creative") + "/" + token.tag;
return '<a href="' + utils.quote_attr(link) + '" data-tag="' + utils.quote_attr(token.tag) + '" class="ffz-creative-tag-link">' + utils.sanitize(token.text) + '</a>';
}
else if ( token.type === "link" ) { else if ( token.type === "link" ) {
var text = token.title || (token.isLong && '<long link>') || (token.isDeleted && '<deleted link>') || (warn_links && '<whispered link>') || token.text; var text = token.title || (token.isLong && '<long link>') || (token.isDeleted && '<deleted link>') || (warn_links && '<whispered link>') || token.text;
@ -757,8 +762,8 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) {
href = '#'; href = '#';
} }
//return `<a class="ffz-tooltip ${cls}" data-text="${utils.quote_attr(token.text)}" data-url="${utils.quote_attr(actual_href)}" href="${utils.quote_attr(href||'#')}" target="_blank">${utils.sanitize(text)}</a>`; //return `<a class="ffz-tooltip ${cls}" 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">' + 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 === "deleted" ) else if ( token.type === "deleted" )
@ -785,6 +790,58 @@ FFZ.prototype.render_tokens = function(tokens, render_links, warn_links) {
} }
// ---------------------
// Creative Tags
// ---------------------
FFZ.prototype.tokenize_ctags = function(tokens) {
"use strict";
if ( typeof tokens === "string" )
tokens = [tokens];
var banned_tags = window.SiteOptions && SiteOptions.creative_banned_tags && SiteOptions.creative_banned_tags.split(',') || [],
new_tokens = [];
for(var i=0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if ( ! token )
continue;
if ( typeof token !== "string" )
if ( token.type === "text" )
token = token.text;
else {
new_tokens.push(token);
continue;
}
var segments = token.split(' '),
text = [], segment, tag;
for(var x=0,y=segments.length; x < y; x++) {
segment = segments[x];
tag = segment.substr(1).toLowerCase();
if ( segment.charAt(0) === '#' && banned_tags.indexOf(tag) === -1 ) {
if ( text.length ) {
new_tokens.push({type: "text", text: text.join(' ') + ' '});
text = [];
}
new_tokens.push({type: "tag", text: segment, tag: tag});
text.push('');
} else
text.push(segment);
}
if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
new_tokens.push({type: "text", text: text.join(' ')});
}
return new_tokens;
}
// --------------------- // ---------------------
// Emoticon Processing // Emoticon Processing
// --------------------- // ---------------------

View file

@ -79,6 +79,41 @@ var include_html = function(heading_text, filename) {
render_news = include_html("news", constants.SERVER + "script/news.html"); render_news = include_html("news", constants.SERVER + "script/news.html");
var make_line = function(key, container) {
var desc = NICE_DESCRIPTION.hasOwnProperty(key) ? NICE_DESCRIPTION[key] : key;
if ( ! desc )
return;
line = createElement('li', null, desc + '<span></span>');
line.setAttribute('data-property', key);
container.appendChild(line);
return line;
};
var update_mem_stats = function(container) {
if ( ! document.querySelector('.ffz-ui-sub-menu-page[data-page="debugging"]') )
return;
setTimeout(update_mem_stats.bind(this, container), 1000);
var mem = window.performance && performance.memory;
if ( ! mem )
return;
var sorted_keys = ['jsHeapSizeLimit', 'totalJSHeapSize', 'usedJSHeapSize'];
for(var i=0; i < sorted_keys.length; i++) {
var key = sorted_keys[i],
data = mem[key],
line = container.querySelector('li[data-property="' + key + '"]');
if ( ! line )
line = make_line(key, container);
if ( line )
line.querySelector('span').textContent = utils.format_size(data) + ' (' + data + ')';
}
};
var update_player_stats = function(player, container) { var update_player_stats = function(player, container) {
if ( ! document.querySelector('.ffz-ui-sub-menu-page[data-page="debugging"]') || ! player.getVideoInfo ) if ( ! document.querySelector('.ffz-ui-sub-menu-page[data-page="debugging"]') || ! player.getVideoInfo )
return; return;
@ -100,17 +135,11 @@ var update_player_stats = function(player, container) {
data = player_data[key], data = player_data[key],
line = container.querySelector('li[data-property="' + key + '"]'); line = container.querySelector('li[data-property="' + key + '"]');
if ( ! line ) { if ( ! line )
var desc = NICE_DESCRIPTION.hasOwnProperty(key) ? NICE_DESCRIPTION[key] : key; line = make_line(key, container);
if ( ! desc )
continue;
line = createElement('li', null, desc + '<span></span>'); if ( line )
line.setAttribute('data-property', key); line.querySelector('span').textContent = data;
container.appendChild(line);
}
line.querySelector('span').textContent = data;
} }
}; };
@ -270,6 +299,10 @@ FFZ.menu_pages.about = {
['Deploy Flavor', SiteOptions.deploy_flavor] ['Deploy Flavor', SiteOptions.deploy_flavor]
], ],
has_memory = window.performance && performance.memory,
mem_head = createElement('div'),
mem_list = createElement('ul'),
player_head = createElement('div'), player_head = createElement('div'),
player_list = createElement('ul'), player_list = createElement('ul'),
@ -279,6 +312,7 @@ FFZ.menu_pages.about = {
vers = createElement('ul'), vers = createElement('ul'),
version_list = [ version_list = [
['Ember', Ember.VERSION], ['Ember', Ember.VERSION],
['Ember Data', window.DS && DS.VERSION || '<i>unknown</i>'],
['GIT Version', EmberENV.GIT_VERSION], ['GIT Version', EmberENV.GIT_VERSION],
null, null,
['FrankerFaceZ', FFZ.version_info.toString()] ['FrankerFaceZ', FFZ.version_info.toString()]
@ -302,9 +336,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 = twitch_head.className = player_head.className = ver_head.className = log_head.className = 'list-header'; info_head.className = mem_head.className = twitch_head.className = player_head.className = ver_head.className = log_head.className = 'list-header';
info.className = twitch.className = player_list.className = vers.className = 'chat-menu-content menu-side-padding version-list'; info.className = mem_list.className = twitch.className = player_list.className = vers.className = 'chat-menu-content menu-side-padding version-list';
info_head.innerHTML = 'Client Status'; info_head.innerHTML = 'Client Status';
@ -351,13 +384,6 @@ FFZ.menu_pages.about = {
twitch.appendChild(line); twitch.appendChild(line);
} }
if ( player_data ) {
player_head.innerHTML = "Player Statistics";
update_player_stats(player, player_list);
}
ver_head.innerHTML = 'Versions'; ver_head.innerHTML = 'Versions';
if ( this.has_bttv ) if ( this.has_bttv )
@ -395,7 +421,18 @@ FFZ.menu_pages.about = {
container.appendChild(twitch_head); container.appendChild(twitch_head);
container.appendChild(twitch); container.appendChild(twitch);
if ( has_memory ) {
mem_head.innerHTML = 'Memory Statistics';
setTimeout(update_mem_stats.bind(this,mem_list),0);
container.appendChild(mem_head);
container.appendChild(mem_list);
}
if ( player_data ) { if ( player_data ) {
player_head.innerHTML = "Player Statistics";
setTimeout(update_player_stats.bind(this,player,player_list),0);
container.appendChild(player_head); container.appendChild(player_head);
container.appendChild(player_list); container.appendChild(player_list);
} }

View file

@ -2,12 +2,29 @@ var FFZ = window.FrankerFaceZ,
utils = require('../utils'), utils = require('../utils'),
constants = require('../constants'), 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]
],
FOLLOW_GRAVITY = function(f, el) { FOLLOW_GRAVITY = function(f, el) {
return (f.settings.following_count && el.parentElement.getAttribute('data-name') === 'following' ? 'n' : '') + (f.settings.swap_sidebars ? 'e' : 'w'); 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) { WIDE_TIP = function(f, el) {
return ( ! f.settings.following_count || (el.id !== 'header_following' && el.parentElement.getAttribute('data-name') !== 'following') ) ? '' : 'ffz-wide-tip'; return (f.settings.following_count && (
el.id === 'header_following' ||
el.getAttribute('data-href') === 'following' ||
el.parentElement.getAttribute('data-name') === 'following'
)) ? 'ffz-wide-tip' : '';
}; };
@ -49,6 +66,7 @@ FFZ.prototype.setup_following_count = function(has_ember) {
// Tooltips~! // Tooltips~!
this._install_following_tooltips(); this._install_following_tooltips();
setTimeout(this._install_following_tooltips.bind(this), 2000);
// If we don't have Ember, no point in trying this stuff. // If we don't have Ember, no point in trying this stuff.
if ( ! has_ember ) if ( ! has_ember )
@ -76,13 +94,18 @@ FFZ.prototype.setup_following_count = function(has_ember) {
if ( HostLive ) if ( HostLive )
HostLive.load();*/ HostLive.load();*/
var total = Live.get('total'), var init = function() {
streams = Live.get('content'); var total = Live.get('total'),
if ( typeof total === "number" ) { streams = Live.get('content');
this._draw_following_count(total); if ( typeof total === "number" ) {
if ( streams && streams.length ) f._draw_following_count(total);
this._draw_following_channels(streams, total); if ( streams && streams.length )
f._draw_following_channels(streams, total);
}
} }
init()
setTimeout(init, 2000);
} }
@ -161,7 +184,7 @@ FFZ.prototype._update_following_count = function() {
FFZ.prototype._build_following_tooltip = function(el) { FFZ.prototype._build_following_tooltip = function(el) {
if ( el.id !== 'header_following' && el.parentElement.getAttribute('data-name') !== 'following' ) if ( el.id !== 'header_following' && el.getAttribute('data-href') !== 'following' && el.parentElement.getAttribute('data-name') !== 'following' )
return el.getAttribute('original-title'); return el.getAttribute('original-title');
if ( ! this.settings.following_count ) if ( ! this.settings.following_count )
@ -262,43 +285,24 @@ FFZ.prototype._build_following_tooltip = function(el) {
FFZ.prototype._install_following_tooltips = function() { FFZ.prototype._install_following_tooltips = function() {
var f = this, var f = this,
gravity = function() { return FOLLOW_GRAVITY(f, this) },
data = { data = {
html: true, html: true,
className: function() { return WIDE_TIP(f, this); }, className: function() { return WIDE_TIP(f, this); },
title: function() { return f._build_following_tooltip(this); } title: function() { return f._build_following_tooltip(this); }
}; };
// Small for(var i=0; i < FOLLOWING_CONTAINERS.length; i++) {
var small_following = jQuery('#small_nav ul.game_filters li[data-name="following"] a'); var following = jQuery(FOLLOWING_CONTAINERS[i][0]);
if ( small_following && small_following.length ) { if ( following && following.length ) {
var td = small_following.data('tipsy'); var td = following.data('tipsy');
if ( td && td.options ) { if ( td && td.options ) {
td.options = _.extend(td.options, data); td.options = _.extend(td.options, data);
td.options.gravity = function() { return FOLLOW_GRAVITY(f, this); }; if ( FOLLOWING_CONTAINERS[i][1] )
} else td.options.gravity = gravity;
small_following.tipsy(_.extend({gravity: function() { return FOLLOW_GRAVITY(f, this); }}, data)); } else
} following.tipsy(FOLLOWING_CONTAINERS[i][1] ? _.extend({gravity: gravity}, data) : data);
}
// Large
var large_following = jQuery('#large_nav #nav_personal li[data-name="following"] a');
if ( large_following && large_following.length ) {
var td = large_following.data('tipsy');
if ( td && td.options )
td.options = _.extend(td.options, data);
else
large_following.tipsy(data);
}
// Heading
var head_following = jQuery('#header_actions #header_following');
if ( head_following && head_following.length ) {
var td = head_following.data('tipsy');
if ( td && td.options )
td.options = _.extend(td.options, data);
else
head_following.tipsy(data);
} }
} }
@ -310,55 +314,22 @@ FFZ.prototype._draw_following_channels = function(streams, total) {
FFZ.prototype._draw_following_count = function(count) { FFZ.prototype._draw_following_count = function(count) {
// Small count = count ? utils.format_unread(count) : '';
var small_following = document.querySelector('#small_nav ul.game_filters li[data-name="following"] a'); for(var i=0; i < FOLLOWING_CONTAINERS.length; i++) {
if ( small_following ) { var container = document.querySelector(FOLLOWING_CONTAINERS[i][0]),
var badge = small_following.querySelector('.ffz-follow-count'); badge = container && container.querySelector('.ffz-follow-count');
if ( this.has_bttv || ! this.settings.following_count ) { if ( ! container )
if ( badge ) continue;
badge.parentElement.removeChild(badge);
} else {
if ( ! badge ) {
badge = document.createElement('span');
badge.className = 'ffz-follow-count';
small_following.appendChild(badge);
}
badge.innerHTML = count ? utils.format_unread(count) : '';
}
}
// Large
var large_following = document.querySelector('#large_nav #nav_personal li[data-name="following"] a');
if ( large_following ) {
var badge = large_following.querySelector('.ffz-follow-count');
if ( this.has_bttv || ! this.settings.following_count ) { if ( this.has_bttv || ! this.settings.following_count ) {
if ( badge ) container.removeChild(badge);
badge.parentElement.removeChild(badge); continue;
} else {
if ( ! badge ) {
badge = document.createElement('span');
badge.className = 'ffz-follow-count';
large_following.appendChild(badge);
}
badge.innerHTML = count ? utils.format_unread(count) : '';
}
}
// Heading } else if ( ! badge ) {
var head_following = document.querySelector('#header_actions #header_following'); badge = utils.createElement('span', 'ffz-follow-count');
if ( head_following ) { container.appendChild(badge);
var badge = head_following.querySelector('.ffz-follow-count');
if ( this.has_bttv || ! this.settings.following_count ) {
if ( badge )
badge.parentElement.removeChild(badge);
} else {
if ( ! badge ) {
badge = document.createElement('span');
badge.className = 'ffz-follow-count';
head_following.appendChild(badge);
}
badge.innerHTML = count ? utils.format_unread(count) : '';
} }
badge.innerHTML = count;
} }
} }

View file

@ -2,6 +2,8 @@ var FFZ = window.FrankerFaceZ,
constants = require('../constants'), constants = require('../constants'),
utils = require('../utils'), utils = require('../utils'),
IS_OSX = constants.IS_OSX,
reported_sets = [], reported_sets = [],
fix_menu_position = function(container) { fix_menu_position = function(container) {
@ -218,8 +220,8 @@ FFZ.prototype.build_ui_popup = function(view) {
container.classList.toggle('dark', dark); container.classList.toggle('dark', dark);
// Stuff // Stuff
jQuery(inner).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); //jQuery(inner).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
jQuery(inner).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); //jQuery(inner).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
// Menu Container // Menu Container
var sub_container = document.createElement('div'); var sub_container = document.createElement('div');
@ -761,7 +763,7 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub
if ( api && api.emote_url_generator ) if ( api && api.emote_url_generator )
url = api.emote_url_generator(set.source_id, id); url = api.emote_url_generator(set.source_id, id);
} else } else
url = "https://www.frankerfacez.com/emoticons/" + id; url = "https://www.frankerfacez.com/emoticon/" + id;
if ( url ) if ( url )
window.open(url); window.open(url);
} else } else
@ -781,7 +783,7 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub
FFZ.prototype._add_emote = function(view, emote, favorites_set, favorites_key, event) { FFZ.prototype._add_emote = function(view, emote, favorites_set, favorites_key, event) {
if ( event && event.ctrlKey ) { if ( event && ((!IS_OSX && event.ctrlKey) || (IS_OSX && event.metaKey)) ) {
var el = event.target; var el = event.target;
if ( ! el.classList.contains('locked') && el.classList.contains('ffz-can-favorite') && favorites_set && favorites_key ) { if ( ! el.classList.contains('locked') && el.classList.contains('ffz-can-favorite') && favorites_set && favorites_key ) {
var favs = this.settings.favorite_emotes[favorites_set] = this.settings.favorite_emotes[favorites_set] || [], var favs = this.settings.favorite_emotes[favorites_set] = this.settings.favorite_emotes[favorites_set] || [],

View file

@ -129,7 +129,7 @@ FFZ.menu_pages.myemotes = {
var el = document.createElement("div"); var el = document.createElement("div");
el.className = "emoticon-grid ffz-no-emotes center"; el.className = "emoticon-grid ffz-no-emotes center";
el.innerHTML = "You have no favorite emoticons.<br> <img src=\"//cdn.frankerfacez.com/emoticon/26608/2\"><br>To make an emote a favorite, find it on the <nobr>All Emoticons</nobr> tab and <nobr>Ctrl-Click</nobr> it."; el.innerHTML = "You have no favorite emoticons.<br> <img src=\"//cdn.frankerfacez.com/emoticon/26608/2\"><br>To make an emote a favorite, find it on the <nobr>All Emoticons</nobr> tab and <nobr>" + (constants.IS_OSX ? '⌘' : 'Ctrl') + "-Click</nobr> it.";
container.appendChild(el); container.appendChild(el);
} }
}, },

View file

@ -8,6 +8,11 @@ var FFZ = window.FrankerFaceZ,
// --------------------- // ---------------------
FFZ.prototype.fix_tooltips = function() { FFZ.prototype.fix_tooltips = function() {
// Add handlers to FFZ's tooltip classes.
jQuery(".html-tooltip").tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
jQuery(".ffz-tooltip").tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
// First, override the tooltip mixin. // First, override the tooltip mixin.
var TipsyTooltip = utils.ember_resolve('component:tipsy-tooltip'); var TipsyTooltip = utils.ember_resolve('component:tipsy-tooltip');
if ( TipsyTooltip ) { if ( TipsyTooltip ) {

View file

@ -467,6 +467,19 @@ module.exports = FFZ.utils = {
return "" + count; return "" + count;
}, },
format_size: function(bits) {
if(Math.abs(bits) < 1024)
return bits + ' b';
var units = ['Kb','Mb','Gb','Tb','Pb','Eb','Zb','Yb'],
u = -1;
do {
bits /= 1024;
++u;
} while(Math.abs(bits) >= 1024 && u < units.length - 1);
return bits.toFixed(1) + ' ' + units[u];
},
escape_regex: escape_regex, escape_regex: escape_regex,
createElement: function(tag, className, content) { createElement: function(tag, className, content) {

View file

@ -19,6 +19,7 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
cursor: pointer; cursor: pointer;
} }
.ffz-hide-recommended-friends .recommended-friends,
.ffz-hide-recommended-channels .js-recommended-channels, .ffz-hide-recommended-channels .js-recommended-channels,
.ffz-hide-recent-past-broadcast .recent-past-broadcast, .ffz-hide-recent-past-broadcast .recent-past-broadcast,
.ffz-hide-view-count .stat.twitch-channel-views, .ffz-hide-view-count .stat.twitch-channel-views,
@ -1235,6 +1236,24 @@ img.channel_background[src="null"] { display: none; }
padding: 0 5px; padding: 0 5px;
} }
.ember-chat .ffz-moderation-card .mod-controls button {
width: auto;
margin-right: 10px;
}
.ffz-moderation-card .follow-button,
.ffz-moderation-card .friend-button { max-height: 30px }
.ffz-moderation-card .right button:last-of-type,
.ffz-moderation-card .mod-controls button:last-of-type { margin-right: 0 }
.ffz-moderation-card .follow-button a {
text-indent: -9999px;
padding-right: 0 !important;
}
.ffz-moderation-card .button.glyph-only { padding: 0 !important }
.ffz-moderation-card button:not(.glyph-only):hover, .ffz-moderation-card button:not(.glyph-only):hover,
.ffz-moderation-card button:not(.glyph-only):focus { .ffz-moderation-card button:not(.glyph-only):focus {
color: #fff; color: #fff;
@ -1242,7 +1261,7 @@ img.channel_background[src="null"] { display: none; }
} }
.ffz-moderation-card button.message { .ffz-moderation-card button.message {
height: 30px; width: 28px; height: 30px;
} }
.ffz-moderation-card.ffz-is-mod .interface .mod-controls:last-of-type, .ffz-moderation-card.ffz-is-mod .interface .mod-controls:last-of-type,
@ -1994,8 +2013,8 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
} }
.ffz-sidebar-swap #left_close { .ffz-sidebar-swap #left_close {
right: auto; right: 5px;
left: -25px; left: auto;
} }
.ffz-sidebar-swap #main_col { .ffz-sidebar-swap #main_col {
@ -2226,11 +2245,13 @@ li[data-name="following"] a {
background-color: rgba(25,25,25,0.5); background-color: rgba(25,25,25,0.5);
} }
#left_col.open .warp__item .ffz-follow-count,
#large_nav .ffz-follow-count, #large_nav .ffz-follow-count,
.ffz-dark #header_following .ffz-follow-count { .ffz-dark #header_following .ffz-follow-count {
background-color: rgba(127,127,127,0.5); background-color: rgba(127,127,127,0.5);
} }
#left_col.open .warp__item .ffz-follow-count,
#large_nav .ffz-follow-count { #large_nav .ffz-follow-count {
position: absolute; position: absolute;
right: 10px; right: 10px;
@ -2240,6 +2261,12 @@ li[data-name="following"] a {
padding: 2px 5px; padding: 2px 5px;
} }
#left_col.open .warp__item .ffz-follow-count {
top: 6px;
right: 20px;
}
#left_col.closed .warp__item .ffz-follow-count,
#small_nav .ffz-follow-count { #small_nav .ffz-follow-count {
position: absolute; position: absolute;
bottom: 2px; bottom: 2px;
@ -2644,41 +2671,13 @@ body:not(.ffz-top-conversations) .conversations-list-bottom-bar {
/* Creative Directory */ /* Creative Directory */
.ffz-creative-tags .creativetag-list.filter-list, .ffz-top-conversations .ct-banner { margin-top: -50px }
.ffz-creative-tags .creativetag-list ul, .ffz-top-conversations .ct-banner__feature { top: 40px; margin-bottom: 0 }
.ffz-creative-tags .creativetag-list-contain {
width: inherit;
height: inherit;
overflow: inherit;
}
body:not(.ffz-creative-showcase) .creative-hero, body:not(.ffz-tags-on-channel) #broadcast-meta .ct-tags,
.ffz-creative-tags .creativetag-list-contain .filter-nav { display: none; } body:not(.ffz-creative-showcase) .ct-gallery { display: none; }
.ffz-creative-tags .creativetag-list ul { body:not(.ffz-tags-on-channel) .tw-title--tall { height: 60px }
display: flex;
flex-wrap: wrap;
position: inherit;
margin-bottom: -5px;
}
.ffz-creative-tags .creativetag-list li {
flex-grow: 1;
height: inherit;
float: none;
margin: 0 0 5px;
}
.ffz-creative-tags .creativetag-list a {
display: block;
text-align: center;
padding: 5px 10px;
}
.ffz-creative-tags .creativetag-list a.active {
color: #fff !important;
background-color: #6441a5 !important;
}
/* Content-Box~! Down with Twitch randomly changing the box-sizing for everything! */ /* Content-Box~! Down with Twitch randomly changing the box-sizing for everything! */
@ -2732,8 +2731,21 @@ body:not(.ffz-creative-showcase) .creative-hero,
line-height: 40px; line-height: 40px;
} }
/* Banned and Spoiler Games */
.ffz-game-spoilered .thumb .cap img,
.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;
}
/* Dashboard Channel Feed */ /* Dashboard Channel Feed */
.activity-card .emoticon-selector .tabs { display: none }
.ffz-feed-button span { line-height: 30px; } .ffz-feed-button span { line-height: 30px; }
#ffz-feed-tabs .tabs { margin-bottom: 10px } #ffz-feed-tabs .tabs { margin-bottom: 10px }
#ffz-feed-tabs textarea { resize: vertical } #ffz-feed-tabs textarea { resize: vertical }
@ -2747,4 +2759,8 @@ body:not(.ffz-creative-showcase) .creative-hero,
body.ffz-bttv #ffz-feed-tabs .tabs li:not(.selected):not(:hover) a { color: #6441a5; } body.ffz-bttv #ffz-feed-tabs .tabs li:not(.selected):not(:hover) a { color: #6441a5; }
body.ffz-bttv-dark #ffz-feed-tabs .tabs li:not(.selected):not(:hover) a { color: #999; } body.ffz-bttv-dark #ffz-feed-tabs .tabs li:not(.selected):not(:hover) a { color: #999; }
body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 } body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
/* New Sidebar */
.warp__anchor { height: 5.5rem }