From 8056463bbe6136a3b3b9dae253081d0d10cf63c8 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Fri, 6 May 2016 02:23:12 -0400 Subject: [PATCH] 3.5.169. Oops. Haven't commited in a while :< Did... stuff? And things. --- dark.css | 65 +++++-- src/constants.js | 6 +- src/ember/channel.js | 60 ++++-- src/ember/chat-input.js | 2 +- src/ember/chatview.js | 14 +- src/ember/conversations.js | 8 +- src/ember/directory.js | 159 +++++++++++++++- src/ember/feed-card.js | 17 +- src/ember/following.js | 356 ++++++++++++++++++++--------------- src/ember/layout.js | 2 +- src/ember/line.js | 21 ++- src/ember/moderation-card.js | 11 +- src/ember/player.js | 4 +- src/ember/room.js | 195 +++++++------------ src/ember/vod-chat.js | 4 +- src/ext/api.js | 72 +++++++ src/ext/rechat.js | 4 +- src/main.js | 5 +- src/settings.js | 4 +- src/socket.js | 6 +- src/tokenize.js | 61 +++++- src/ui/about_page.js | 77 ++++++-- src/ui/following-count.js | 143 ++++++-------- src/ui/menu.js | 10 +- src/ui/my_emotes.js | 2 +- src/ui/tooltips.js | 5 + src/utils.js | 13 ++ style.css | 90 +++++---- 28 files changed, 908 insertions(+), 508 deletions(-) diff --git a/dark.css b/dark.css index 89ded21a..3acd3273 100644 --- a/dark.css +++ b/dark.css @@ -72,6 +72,8 @@ } /* stats */ +.ffz-dark .ct-tags--extracted { border-bottom: none } + .ffz-dark .stats-and-actions, .ffz-dark #main_col .content #stats_and_actions { 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.over{ - color:rgb(222,222,222)!important; - background-color:rgba(16,16,16,0.3)!important; +.ffz-dark div.title > span.over, +.ffz-dark #broadcast-meta .info .title { + color: #DEDEDE !important; + background-color: rgba(16,16,16,0.3) !important; } .ffz-dark div.title > span.over:hover, -.ffz-dark div.title > span.real:hover { - background-color:rgb(16,16,16)!important; - color:rgb(255,255,255)!important; +.ffz-dark div.title > span.real:hover, +.ffz-dark #broadcast-meta .info .title:hover { + color: #fff !important; + background-color: #101010 !important; } + /* Right Column */ +.ffz-dark .ct-banner--off .ct-banner__toggle, .ffz-dark #right_col { background-color: rgb(25,25,31); color: #fff; @@ -175,6 +181,9 @@ body.ffz-dark, 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 .st-autocomplete-sidebar .label, @@ -216,6 +225,7 @@ body.ffz-dark, } .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 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 .ember-chat .chat-room-list .room:not(:hover) p.room-title, .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; } +.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 .follow-button a, @@ -297,7 +317,7 @@ body.ffz-dark, background-color: #25252a; } -.ffz-dark .button:not(.primary) { +.ffz-dark .button:not(.button--status):not(.primary) { 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 */ .button.alert { color: #fff !important } @@ -1128,12 +1171,12 @@ body.ffz-dark, .ffz-dark .activity-meta:before { background-color: #474747 } -.ffz-dark .activity-react__like:hover { +.ffz-dark .activity-react__item:hover { border-color: #d5d5d5; 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 { background-color: #191919; @@ -1141,7 +1184,7 @@ body.ffz-dark, } .ffz-dark .activity-create, -.ffz-dark .activity-react__like { +.ffz-dark .activity-react__item { border-color: #474747; color: #fff !important; background-color: #191919; diff --git a/src/constants.js b/src/constants.js index ec575f43..c85845ab 100644 --- a/src/constants.js +++ b/src/constants.js @@ -15,6 +15,8 @@ module.exports = FrankerFaceZ.constants = { DEBUG: DEBUG, SERVER: SERVER, + IS_OSX: navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent), + // Twitch Client ID for API Stuff CLIENT_ID: "a3bc9znoz6vi8ozsoca0inlcr4fcvkl", @@ -22,11 +24,11 @@ module.exports = FrankerFaceZ.constants = { WS_SERVER_POOLS: { 1: [ - ["wss://catbag.frankerfacez.com/", 0.5], + ["wss://catbag.frankerfacez.com/", 0.25], ["wss://andknuckles.frankerfacez.com/", 1], ["wss://tuturu.frankerfacez.com/", 1]], 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"], diff --git a/src/ember/channel.js b/src/ember/channel.js index 8ba72a32..1b4e2d95 100644 --- a/src/ember/channel.js +++ b/src/ember/channel.js @@ -40,16 +40,17 @@ FFZ.prototype.setup_channel = function() { // Update Existing var views = utils.ember_views(); for(var key in views) { - if ( ! views.hasOwnProperty(key) ) - continue; - var view = views[key]; - if ( !(view instanceof Channel) ) - continue; - - this.log("Manually updating Channel Index view.", view); - this._modify_cindex(view); - view.ffzInit(); + if ( view instanceof Channel ) { + this.log("Manually updating existing Channel Index view.", view); + try { + if ( ! view.ffzInit ) + this._modify_cindex(view); + view.ffzInit(); + } catch(err) { + this.error("setup: view:channel/index: " + err); + } + } }; @@ -145,7 +146,7 @@ FFZ.prototype.setup_channel = function() { if ( f._cindex ) f._cindex.ffzFixTitle(); - }.observes("content.status", "content.id"), + }.observes("content.status", "content.id", "hostModeTarget.status", "hostModeTarget.id"), ffzHostTarget: function() { var target = this.get('content.hostModeTarget'), @@ -245,24 +246,43 @@ FFZ.prototype._modify_cindex = function(view) { if ( Layout ) 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() { if ( f.has_bttv || ! f.settings.stream_title ) return; - var status = this.get("controller.content.status") || this.get("controller.status"), - channel = this.get("controller.content.id") || this.get("controller.id"); + var status = this.get("controller.content.status"), + 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) { - var scripts = el.querySelectorAll("script"); - if ( ! scripts.length ) - el.innerHTML = status; - else - el.innerHTML = scripts[0].outerHTML + status + scripts[1].outerHTML; - }); + if ( game === 'Creative' ) + tokens = f.tokenize_ctags(tokens); + + this.$("#broadcast-meta .title").html(f.render_tokens(tokens)); + + 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)); + } }, diff --git a/src/ember/chat-input.js b/src/ember/chat-input.js index 6d71caec..41706494 100644 --- a/src/ember/chat-input.js +++ b/src/ember/chat-input.js @@ -871,7 +871,7 @@ FFZ.prototype._modify_chat_input = function(component) { case KEYCODES.TAB: // If we do Ctrl-Tab or Alt-Tab. Just don't // even think of doing suggestions. - if ( e.ctrlKey || e.altKey ) + if ( e.ctrlKey || e.altKey || e.metaKey ) break; e.preventDefault(); diff --git a/src/ember/chatview.js b/src/ember/chatview.js index c90599b6..aab5feec 100644 --- a/src/ember/chatview.js +++ b/src/ember/chatview.js @@ -366,6 +366,16 @@ FFZ.prototype.setup_chatview = function() { }.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() { if ( ! f._chatv || f.has_bttv ) return; @@ -520,8 +530,8 @@ FFZ.prototype._modify_cview = function(view) { el && el.setAttribute('data-room', room_id || ""); 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('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), 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')}); if ( ! f.has_bttv ) { if ( f.settings.group_tabs ) diff --git a/src/ember/conversations.js b/src/ember/conversations.js index aa1d7fc0..9b8a9e98 100644 --- a/src/ember/conversations.js +++ b/src/ember/conversations.js @@ -101,8 +101,8 @@ FFZ.prototype.setup_conversations = function() { this.log("Unable to resolve: component:conversation-line"); // 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('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), 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')}); } @@ -175,8 +175,8 @@ FFZ.prototype._modify_conversation_window = function(component) { } 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('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), 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')}); } }); } diff --git a/src/ember/directory.js b/src/ember/directory.js index 31cb3407..032388be 100644 --- a/src/ember/directory.js +++ b/src/ember/directory.js @@ -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", value: false, @@ -68,7 +84,7 @@ FFZ.settings_info.directory_creative_all_tags = { on_update: function(val) { document.body.classList.toggle('ffz-creative-tags', val); } - }; + };*/ 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.

This is case insensitive, however you must type the full name.

Example: League of Legends, Dota 2, Smite", + 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.

This is case insensitive, however you must type the full name.

Example: Undertale", + 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 = { type: "select", 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-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-friends', !this.settings.sidebar_hide_recommended_friends); var GamesFollowing = utils.ember_lookup('controller:games-following'), f = this; @@ -197,7 +290,7 @@ FFZ.prototype.setup_directory = function() { var ChannelView = utils.ember_resolve('component:stream-preview'); if ( ChannelView ) { - this._modify_directory_live(ChannelView); + this._modify_directory_live(ChannelView, false); try { ChannelView.create().destroy(); } catch(err) { } @@ -205,7 +298,7 @@ FFZ.prototype.setup_directory = function() { var CreativeChannel = utils.ember_resolve('component:creative-preview'); if ( CreativeChannel ) { - this._modify_directory_live(CreativeChannel); + this._modify_directory_live(CreativeChannel, false); try { CreativeChannel.create().destroy(); } catch(err) { } @@ -222,20 +315,31 @@ FFZ.prototype.setup_directory = function() { } 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. var views = utils.ember_views(); for(var key in views) { var view = views[key]; if ( (ChannelView && view instanceof ChannelView) || (CreativeChannel && view instanceof CreativeChannel) ) { if ( ! view.ffzInit ) - this._modify_directory_live(view); + this._modify_directory_live(view, false); } else if ( CSGOChannel && view instanceof CSGOChannel ) { if ( ! view.ffzInit ) this._modify_directory_live(view, true); } else if ( view instanceof HostView || view.get('tt_content') === 'live_host' ) { if ( ! view.ffzInit ) this._modify_directory_host(view); - } else + } else if ( VideoPreview && view instanceof VideoPreview ) { + if ( ! view.ffzInit ) + this._modify_video_preview(view); + } else continue; try { @@ -386,9 +490,15 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) { meta = el && el.querySelector('.meta'), thumb = el && el.querySelector('.thumb'), 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-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... 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) { var f = this, mutator; @@ -613,11 +750,17 @@ FFZ.prototype._modify_directory_host = function(dir) { title = meta && meta.querySelector('.title a'), target = this.get('stream.target.channel'), + game = this.get('stream.target.meta_game'), hosts = this.get('stream.ffz_hosts'); //, //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.ffzRotateImage(); diff --git a/src/ember/feed-card.js b/src/ember/feed-card.js index a8bccaaf..11d2a157 100644 --- a/src/ember/feed-card.js +++ b/src/ember/feed-card.js @@ -21,10 +21,11 @@ var FFZ = window.FrankerFaceZ, 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 ) - return; + return this.error("Unable to locate component:channel-feed/card"); + this.log("Modifying the feed-card component."); this._modify_feed_card(FeedCard); try { FeedCard.create().destroy(); @@ -35,7 +36,7 @@ FFZ.prototype.setup_feed_cards = function() { 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(); if ( ! FeedCard ) @@ -49,7 +50,7 @@ FFZ.prototype.rerender_feed_cards = function(for_set) { this._modify_feed_card(view); view.ffzInit(for_set); } 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 { this.ffzInit(); } 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'), emotes = parse_emotes(this.get('post.emotes')), 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'); if ( ! message || ! el || ! pbody ) @@ -91,8 +92,8 @@ FFZ.prototype._modify_feed_card = function(component) { pbody.innerHTML = '

' + output + '

'; - 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('.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')}); } }); } \ No newline at end of file diff --git a/src/ember/following.js b/src/ember/following.js index cbf30e6b..acee365e 100644 --- a/src/ember/following.js +++ b/src/ember/following.js @@ -1,6 +1,8 @@ var FFZ = window.FrankerFaceZ, utils = require('../utils'), - constants = require('../constants'); + constants = require('../constants'), + + createElement = utils.createElement; // -------------------- @@ -13,7 +15,7 @@ FFZ.settings_info.enhance_profile_following = { category: "Directory", 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. 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, // rather than making potentially hundreds of API requests. @@ -36,12 +39,24 @@ FFZ.prototype.setup_profile_following = function() { 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 ) @@ -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() { try { this.ffzTeardown(); } catch(err) { - f.error("ProvileView ffzTeardown: " + err); + f.error("component:display-followed-item ffzTeardown: " + err); } - this._super(); }, ffzInit: function() { - // Only process our own profile following page. - var user = f.get_user(); - if ( ! f.settings.enhance_profile_following || ! user || user.login !== this.get('context.model.id') ) + var el = this.get('element'), + channel_id = this.get('parentView.parentView.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; - var el = this.get('element'), - users = el && el.querySelectorAll('.user.item'); + el.classList.add('ffz-processed'); - 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 ) 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), - age = data[0] ? Math.floor((now - data[0].getTime()) / 1000) : 0; - if ( age ) { - t_el.innerHTML = constants.CLOCK + ' ' + utils.human_time(age, 10); - t_el.setAttribute('original-title', 'Following Since: ' + data[0].toLocaleString() + ''); - } else - t_el.style.display = 'none'; + age = data[0] ? Math.floor((now - data[0].getTime()) / 1000) : 0, + t_el = createElement('div', 'overlay_info length'), - 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'), - follow = document.createElement('button'), - notif = document.createElement('button'), + if ( age !== undefined ) { + t_el.innerHTML = constants.CLOCK + ' ' + (age < 60 ? 'now' : utils.human_time(age, 10)); + t_el.title = 'Following Since: ' + data[0].toLocaleString() + ''; + 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() { - data = f._following_cache[user_id]; - user.classList.toggle('followed', data); + data = user_cache[user_id]; + el.classList.toggle('followed', data); follow.innerHTML = constants.HEART + constants.UNHEART + ' Follow'; - - 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: ' + data[0].toLocaleString() + ''); - t_el.style.display = ''; - } else { - t_el.style.display = 'none'; - } - } }, update_notif = function() { - data = f._following_cache[user_id]; + data = user_cache[user_id]; notif.classList.toggle('notifications-on', data && data[1]); notif.textContent = 'Notification ' + (data && data[1] ? 'On' : 'Off'); }; - actions.className = 'actions'; - - follow.className = 'button follow'; - notif.className = 'button notifications'; - update_follow(); update_notif(); @@ -220,11 +251,12 @@ FFZ.prototype.setup_profile_following = function() { utils.api.del("users/:login/follows/channels/" + user_id) : utils.api.put("users/:login/follows/channels/" + user_id, {notifications: false})) .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() { update_follow(); update_notif(); + update_time(); follow.disabled = false; notif.disabled = false; }) @@ -250,32 +282,14 @@ FFZ.prototype.setup_profile_following = function() { actions.appendChild(follow); 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({ ffz_hooked: true, apiLoad: function(e) { - var user = f.get_user(), - channel_id = this.get('id'), + var channel_id = this.get('id'), t = this; - if ( ! user || user.login !== channel_id ) - return this._super(e); + f._following_cache[channel_id] = f._following_cache[channel_id] || {}; return new RSVP.Promise(function(success, fail) { t._super(e).then(function(data) { @@ -307,7 +319,47 @@ FFZ.prototype._hook_following = function(Following) { if ( follow.channel.display_name ) 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]; } } diff --git a/src/ember/layout.js b/src/ember/layout.js index b6e169ee..8696d20f 100644 --- a/src/ember/layout.js +++ b/src/ember/layout.js @@ -145,7 +145,6 @@ FFZ.prototype.setup_layout = function() { return; 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."); var s = this._layout_style = document.createElement('style'); @@ -328,4 +327,5 @@ FFZ.prototype.setup_layout = function() { // Force re-calculation of everything. Ember.propertyDidChange(Layout, 'windowWidth'); Ember.propertyDidChange(Layout, 'windowHeight'); + Layout.ffzUpdatePortraitCSS(); } \ No newline at end of file diff --git a/src/ember/line.js b/src/ember/line.js index 9d08eaaf..dd006fd9 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -1024,6 +1024,7 @@ FFZ.prototype._modify_vod_line = function(component) { FFZ.capitalization = {}; FFZ._cap_fetching = 0; +FFZ._cap_waiting = {}; FFZ.get_capitalization = function(name, callback) { if ( ! name ) @@ -1039,13 +1040,25 @@ FFZ.get_capitalization = function(name, callback) { 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.get().ws_send("get_display_name", name, function(success, data) { - var cap_name = success ? data : name; + FFZ._cap_waiting[name] = [callback]; + + 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._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) { } }); } diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js index e7f80d7c..d4f8e5a2 100644 --- a/src/ember/moderation-card.js +++ b/src/ember/moderation-card.js @@ -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"); if ( msg_btn ) { 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('a.deleted-link', l_el).click(f._deleted_link_click); 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('.ffz-tooltip', l_el).tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); + //jQuery('.html-tooltip', l_el).tipsy({html:true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); + //jQuery('.ffz-tooltip', l_el).tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); if ( modcard ) { modcard.get('cardInfo.user.id') !== msg.from && jQuery('span.from', l_el).click(function(e) { diff --git a/src/ember/player.js b/src/ember/player.js index 9351ccea..93f4dd13 100644 --- a/src/ember/player.js +++ b/src/ember/player.js @@ -164,9 +164,9 @@ FFZ.prototype._modify_player = function(player) { var stats = this.$('.player .js-playback-stats'); 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. - 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. diff --git a/src/ember/room.js b/src/ember/room.js index 7da44031..7d979f60 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -4,6 +4,15 @@ var FFZ = window.FrankerFaceZ, utils = require('../utils'), 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 is_android = navigator.userAgent.indexOf('Android') !== -1, @@ -210,152 +219,47 @@ FFZ.prototype._modify_rview = function(view) { ffzUpdateStatus: function() { var room = this.get('controller.model'), - el = this.get('element'), cont = el && el.querySelector('.chat-buttons-container'); if ( ! cont ) return; - var r9k_badge = cont.querySelector('#ffz-stat-r9k'), - 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'); + var btn = cont.querySelector('button'); if ( f.has_bttv || ! f.settings.room_status ) { - if ( r9k_badge ) - 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); + jQuery(".ffz.room-state", cont).remove(); if ( btn ) btn.classList.remove('ffz-waiting'); return; - } - if ( ! r9k_badge ) { - r9k_badge = document.createElement('span'); - r9k_badge.className = 'ffz room-state stat float-right'; - r9k_badge.id = 'ffz-stat-r9k'; - r9k_badge.innerHTML = 'R9K'; - 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 = 'SUB'; - 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 = 'EMOTE'; - 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 = 'SLOW'; - 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 = 'BAN'; - 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 = 'DELAY'; - 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 = 'BATCH'; - 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 ) { + } else if ( btn ) { btn.classList.toggle('ffz-waiting', (room && room.get('slowWait') || 0)); 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() + '' + info[0].substr(1).toUpperCase() + ''); + 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'), ffzEnableFreeze: function() { @@ -677,7 +581,8 @@ FFZ.prototype.add_room = function(id, room) { } // 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? 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'); 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(); }, @@ -993,6 +898,24 @@ FFZ.prototype._modify_room = function(room) { f._roomv.ffzUpdateStatus(); }.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 ffzUserLevel: function() { if ( this.get('isStaff') ) @@ -1300,6 +1223,12 @@ FFZ.prototype._modify_room = function(room) { if ( 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. 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/"); @@ -1308,6 +1237,10 @@ FFZ.prototype._modify_room = function(room) { return this._super(msg); }, + ffzChatFilters: function(msg) { + var i = f._chat_filters.length; + }, + setHostMode: function(e) { this.set('ffz_host_target', e && e.hostTarget || null); var user = f.get_user(); diff --git a/src/ember/vod-chat.js b/src/ember/vod-chat.js index 2fd2016a..dbdceec1 100644 --- a/src/ember/vod-chat.js +++ b/src/ember/vod-chat.js @@ -154,7 +154,7 @@ FFZ.prototype._modify_vod_chat_display = function(component) { if ( f.settings.chat_hover_pause ) this.ffzEnableFreeze(); - this.$('.chat-messages').find('.html-tooltip').tipsy({ + /*this.$('.chat-messages').find('.html-tooltip').tipsy({ live: true, html: true, gravity: utils.tooltip_placement(2 * constants.TOOLTIP_DISTANCE, function() { return this.classList.contains('right') ? 'e' : 'n' @@ -165,7 +165,7 @@ FFZ.prototype._modify_vod_chat_display = function(component) { title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, function() { return this.classList.contains('right') ? 'e' : 'n' - })}); + })});*/ }, ffzTeardown: function() { diff --git a/src/ext/api.js b/src/ext/api.js index a0d38f94..88420426 100644 --- a/src/ext/api.js +++ b/src/ext/api.js @@ -50,6 +50,8 @@ var API = FFZ.API = function(instance, name, icon, version) { this.global_sets = []; this.default_sets = []; + this.users = {}; + this.chat_filters = []; this.on_room_callbacks = []; 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 // ----------------------- diff --git a/src/ext/rechat.js b/src/ext/rechat.js index b470dee6..a153a56e 100644 --- a/src/ext/rechat.js +++ b/src/ext/rechat.js @@ -102,8 +102,8 @@ FFZ.prototype.find_rechat = function() { // Tooltips jQuery(container).find('.tooltip').tipsy({live: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); - jQuery(container).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); - jQuery(container).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); + //jQuery(container).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); + //jQuery(container).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); // Load the room data. var room_id = el.getAttribute('data-room'); diff --git a/src/main.js b/src/main.js index 323cc7f0..5c2b5103 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,7 @@ var FFZ = window.FrankerFaceZ = function() { // Logging this._log_data = []; this._apis = {}; + this._chat_filters = []; // Error Logging var t = this; @@ -35,7 +36,7 @@ FFZ.msg_commands = {}; // Version var VER = FFZ.version_info = { - major: 3, minor: 5, revision: 159, + major: 3, minor: 5, revision: 169, toString: function() { 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) { - 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) { callback.call(this, e.trim() + ".log"); }).fail(function(e) { diff --git a/src/settings.js b/src/settings.js index 682ce70f..077ff8b1 100644 --- a/src/settings.js +++ b/src/settings.js @@ -413,8 +413,8 @@ var is_android = navigator.userAgent.indexOf('Android') !== -1, el.appendChild(label); el.appendChild(help); menu.appendChild(el); - 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('.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')}); } container.appendChild(menu); diff --git a/src/socket.js b/src/socket.js index 3644427a..5d46d04b 100644 --- a/src/socket.js +++ b/src/socket.js @@ -153,10 +153,12 @@ FFZ.prototype.ws_create = function() { // Send the current 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; - 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 ) { f.rooms[room_id].needs_history = false; diff --git a/src/tokenize.js b/src/tokenize.js index d247e7be..ce84b58d 100644 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -719,6 +719,11 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) { return '' + utils.quote_attr(token.altText) + ''; } + else if ( token.type === "tag" ) { + var link = Twitch.uri.game("Creative") + "/" + token.tag; + return '' + utils.sanitize(token.text) + ''; + } + else if ( token.type === "link" ) { var text = token.title || (token.isLong && '') || (token.isDeleted && '') || (warn_links && '') || token.text; @@ -757,8 +762,8 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) { href = '#'; } - //return `${utils.sanitize(text)}`; - return '' + utils.sanitize(text) + ''; + //return `${utils.sanitize(text)}`; + return '' + utils.sanitize(text) + ''; } 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 // --------------------- diff --git a/src/ui/about_page.js b/src/ui/about_page.js index b3b298ce..72d00ff7 100644 --- a/src/ui/about_page.js +++ b/src/ui/about_page.js @@ -79,6 +79,41 @@ var include_html = function(heading_text, filename) { 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 + ''); + 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) { if ( ! document.querySelector('.ffz-ui-sub-menu-page[data-page="debugging"]') || ! player.getVideoInfo ) return; @@ -100,17 +135,11 @@ var update_player_stats = function(player, container) { data = player_data[key], line = container.querySelector('li[data-property="' + key + '"]'); - if ( ! line ) { - var desc = NICE_DESCRIPTION.hasOwnProperty(key) ? NICE_DESCRIPTION[key] : key; - if ( ! desc ) - continue; + if ( ! line ) + line = make_line(key, container); - line = createElement('li', null, desc + ''); - line.setAttribute('data-property', key); - container.appendChild(line); - } - - line.querySelector('span').textContent = data; + if ( line ) + line.querySelector('span').textContent = data; } }; @@ -270,6 +299,10 @@ FFZ.menu_pages.about = { ['Deploy Flavor', SiteOptions.deploy_flavor] ], + has_memory = window.performance && performance.memory, + mem_head = createElement('div'), + mem_list = createElement('ul'), + player_head = createElement('div'), player_list = createElement('ul'), @@ -279,6 +312,7 @@ FFZ.menu_pages.about = { vers = createElement('ul'), version_list = [ ['Ember', Ember.VERSION], + ['Ember Data', window.DS && DS.VERSION || 'unknown'], ['GIT Version', EmberENV.GIT_VERSION], null, ['FrankerFaceZ', FFZ.version_info.toString()] @@ -302,9 +336,8 @@ FFZ.menu_pages.about = { heading.className = 'chat-menu-content center'; heading.innerHTML = '

FrankerFaceZ

woofs for nerds
'; - info_head.className = twitch_head.className = player_head.className = ver_head.className = log_head.className = 'list-header'; - info.className = twitch.className = player_list.className = vers.className = 'chat-menu-content menu-side-padding version-list'; - + info_head.className = mem_head.className = twitch_head.className = player_head.className = ver_head.className = log_head.className = 'list-header'; + info.className = mem_list.className = twitch.className = player_list.className = vers.className = 'chat-menu-content menu-side-padding version-list'; info_head.innerHTML = 'Client Status'; @@ -351,13 +384,6 @@ FFZ.menu_pages.about = { twitch.appendChild(line); } - - if ( player_data ) { - player_head.innerHTML = "Player Statistics"; - update_player_stats(player, player_list); - } - - ver_head.innerHTML = 'Versions'; if ( this.has_bttv ) @@ -395,7 +421,18 @@ FFZ.menu_pages.about = { container.appendChild(twitch_head); 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 ) { + player_head.innerHTML = "Player Statistics"; + setTimeout(update_player_stats.bind(this,player,player_list),0); + container.appendChild(player_head); container.appendChild(player_list); } diff --git a/src/ui/following-count.js b/src/ui/following-count.js index 50bb2ef8..75ba6c65 100644 --- a/src/ui/following-count.js +++ b/src/ui/following-count.js @@ -2,12 +2,29 @@ var FFZ = window.FrankerFaceZ, utils = require('../utils'), 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) { - 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) { - 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~! 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 ( ! has_ember ) @@ -76,13 +94,18 @@ FFZ.prototype.setup_following_count = function(has_ember) { if ( HostLive ) HostLive.load();*/ - var total = Live.get('total'), - streams = Live.get('content'); - if ( typeof total === "number" ) { - this._draw_following_count(total); - if ( streams && streams.length ) - this._draw_following_channels(streams, total); + var init = function() { + var total = Live.get('total'), + streams = Live.get('content'); + if ( typeof total === "number" ) { + f._draw_following_count(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) { - 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'); if ( ! this.settings.following_count ) @@ -262,43 +285,24 @@ 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); } }; - // Small - var small_following = jQuery('#small_nav ul.game_filters li[data-name="following"] a'); - if ( small_following && small_following.length ) { - var td = small_following.data('tipsy'); - if ( td && td.options ) { - td.options = _.extend(td.options, data); - td.options.gravity = function() { return FOLLOW_GRAVITY(f, this); }; - } else - small_following.tipsy(_.extend({gravity: function() { return FOLLOW_GRAVITY(f, this); }}, 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); + for(var i=0; i < FOLLOWING_CONTAINERS.length; i++) { + var following = jQuery(FOLLOWING_CONTAINERS[i][0]); + 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); + } } } @@ -310,55 +314,22 @@ FFZ.prototype._draw_following_channels = function(streams, total) { FFZ.prototype._draw_following_count = function(count) { - // Small - var small_following = document.querySelector('#small_nav ul.game_filters li[data-name="following"] a'); - if ( small_following ) { - var badge = small_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'; - small_following.appendChild(badge); - } - badge.innerHTML = count ? utils.format_unread(count) : ''; - } - } + count = count ? utils.format_unread(count) : ''; + for(var i=0; i < FOLLOWING_CONTAINERS.length; i++) { + var container = document.querySelector(FOLLOWING_CONTAINERS[i][0]), + badge = container && container.querySelector('.ffz-follow-count'); + if ( ! container ) + continue; - - // 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 ( badge ) - badge.parentElement.removeChild(badge); - } else { - if ( ! badge ) { - badge = document.createElement('span'); - badge.className = 'ffz-follow-count'; - large_following.appendChild(badge); - } - badge.innerHTML = count ? utils.format_unread(count) : ''; - } - } + container.removeChild(badge); + continue; - // Heading - var head_following = document.querySelector('#header_actions #header_following'); - if ( head_following ) { - 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) : ''; + } else if ( ! badge ) { + badge = utils.createElement('span', 'ffz-follow-count'); + container.appendChild(badge); } + + badge.innerHTML = count; } } \ No newline at end of file diff --git a/src/ui/menu.js b/src/ui/menu.js index 7b563d88..ada92a6d 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -2,6 +2,8 @@ var FFZ = window.FrankerFaceZ, constants = require('../constants'), utils = require('../utils'), + IS_OSX = constants.IS_OSX, + reported_sets = [], fix_menu_position = function(container) { @@ -218,8 +220,8 @@ FFZ.prototype.build_ui_popup = function(view) { container.classList.toggle('dark', dark); // Stuff - 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('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); + //jQuery(inner).find('.ffz-tooltip').tipsy({live: true, html: true, title: this.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); // Menu Container 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 ) url = api.emote_url_generator(set.source_id, id); } else - url = "https://www.frankerfacez.com/emoticons/" + id; + url = "https://www.frankerfacez.com/emoticon/" + id; if ( url ) window.open(url); } 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) { - if ( event && event.ctrlKey ) { + if ( event && ((!IS_OSX && event.ctrlKey) || (IS_OSX && event.metaKey)) ) { var el = event.target; if ( ! el.classList.contains('locked') && el.classList.contains('ffz-can-favorite') && favorites_set && favorites_key ) { var favs = this.settings.favorite_emotes[favorites_set] = this.settings.favorite_emotes[favorites_set] || [], diff --git a/src/ui/my_emotes.js b/src/ui/my_emotes.js index 80cec9c2..b25d8871 100644 --- a/src/ui/my_emotes.js +++ b/src/ui/my_emotes.js @@ -129,7 +129,7 @@ FFZ.menu_pages.myemotes = { var el = document.createElement("div"); el.className = "emoticon-grid ffz-no-emotes center"; - el.innerHTML = "You have no favorite emoticons.

To make an emote a favorite, find it on the All Emoticons tab and Ctrl-Click it."; + el.innerHTML = "You have no favorite emoticons.

To make an emote a favorite, find it on the All Emoticons tab and " + (constants.IS_OSX ? '⌘' : 'Ctrl') + "-Click it."; container.appendChild(el); } }, diff --git a/src/ui/tooltips.js b/src/ui/tooltips.js index 8ea7f616..8028e860 100644 --- a/src/ui/tooltips.js +++ b/src/ui/tooltips.js @@ -8,6 +8,11 @@ var FFZ = window.FrankerFaceZ, // --------------------- 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. var TipsyTooltip = utils.ember_resolve('component:tipsy-tooltip'); if ( TipsyTooltip ) { diff --git a/src/utils.js b/src/utils.js index 0f9af5da..31e0e0e1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -467,6 +467,19 @@ module.exports = FFZ.utils = { 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, createElement: function(tag, className, content) { diff --git a/style.css b/style.css index 49f85790..7ac74f16 100644 --- a/style.css +++ b/style.css @@ -19,6 +19,7 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; } cursor: pointer; } +.ffz-hide-recommended-friends .recommended-friends, .ffz-hide-recommended-channels .js-recommended-channels, .ffz-hide-recent-past-broadcast .recent-past-broadcast, .ffz-hide-view-count .stat.twitch-channel-views, @@ -1235,6 +1236,24 @@ img.channel_background[src="null"] { display: none; } 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):focus { color: #fff; @@ -1242,7 +1261,7 @@ img.channel_background[src="null"] { display: none; } } .ffz-moderation-card button.message { - height: 30px; width: 28px; + height: 30px; } .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 { - right: auto; - left: -25px; + right: 5px; + left: auto; } .ffz-sidebar-swap #main_col { @@ -2226,11 +2245,13 @@ li[data-name="following"] a { background-color: rgba(25,25,25,0.5); } +#left_col.open .warp__item .ffz-follow-count, #large_nav .ffz-follow-count, .ffz-dark #header_following .ffz-follow-count { background-color: rgba(127,127,127,0.5); } +#left_col.open .warp__item .ffz-follow-count, #large_nav .ffz-follow-count { position: absolute; right: 10px; @@ -2240,6 +2261,12 @@ li[data-name="following"] a { 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 { position: absolute; bottom: 2px; @@ -2644,41 +2671,13 @@ body:not(.ffz-top-conversations) .conversations-list-bottom-bar { /* Creative Directory */ -.ffz-creative-tags .creativetag-list.filter-list, -.ffz-creative-tags .creativetag-list ul, -.ffz-creative-tags .creativetag-list-contain { - width: inherit; - height: inherit; - overflow: inherit; -} +.ffz-top-conversations .ct-banner { margin-top: -50px } +.ffz-top-conversations .ct-banner__feature { top: 40px; margin-bottom: 0 } -body:not(.ffz-creative-showcase) .creative-hero, -.ffz-creative-tags .creativetag-list-contain .filter-nav { display: none; } +body:not(.ffz-tags-on-channel) #broadcast-meta .ct-tags, +body:not(.ffz-creative-showcase) .ct-gallery { display: none; } -.ffz-creative-tags .creativetag-list ul { - 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; -} +body:not(.ffz-tags-on-channel) .tw-title--tall { height: 60px } /* 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; } +/* 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 */ +.activity-card .emoticon-selector .tabs { display: none } + .ffz-feed-button span { line-height: 30px; } #ffz-feed-tabs .tabs { margin-bottom: 10px } #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-dark #ffz-feed-tabs .tabs li:not(.selected):not(:hover) a { color: #999; } -body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 } \ No newline at end of file +body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 } + +/* New Sidebar */ + +.warp__anchor { height: 5.5rem } \ No newline at end of file