diff --git a/src/colors.js b/src/colors.js index 4d379204..8d288470 100644 --- a/src/colors.js +++ b/src/colors.js @@ -130,7 +130,7 @@ FFZ.prototype.setup_colors = function() { this._update_colors(); // Events for rebuilding colors. - var Layout = utils.ember_lookup('controller:layout'), + var Layout = utils.ember_lookup('service:layout'), Settings = utils.ember_lookup('controller:settings'); if ( Layout ) @@ -654,7 +654,7 @@ FFZ.prototype._rebuild_colors = function() { FFZ.prototype._update_colors = function(darkness_only) { // Update the lines. ALL of them. - var Layout = utils.ember_lookup('controller:layout'), + var Layout = utils.ember_lookup('service:layout'), Settings = utils.ember_lookup('controller:settings'), is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('settings.darkMode')), diff --git a/src/ember/channel.js b/src/ember/channel.js index 2891095f..e5ad0e10 100644 --- a/src/ember/channel.js +++ b/src/ember/channel.js @@ -233,7 +233,7 @@ FFZ.prototype._modify_cindex = function(view) { f.rebuild_race_ui(); if ( f.settings.auto_theater ) { - var Layout = utils.ember_lookup('controller:layout'); + var Layout = utils.ember_lookup('service:layout'); if ( Layout ) Layout.set('isTheatreMode', true); } diff --git a/src/ember/conversations.js b/src/ember/conversations.js index f23b93aa..81daef62 100644 --- a/src/ember/conversations.js +++ b/src/ember/conversations.js @@ -134,7 +134,7 @@ FFZ.prototype._modify_conversation_menu = function(component) { FFZ.prototype._modify_conversation_window = function(component) { var f = this, - Layout = utils.ember_lookup('controller:layout'); + Layout = utils.ember_lookup('service:layout'); component.reopen({ headerBadges: Ember.computed("thread.participants", "currentUsername", function() { @@ -178,7 +178,7 @@ FFZ.prototype._modify_conversation_window = function(component) { FFZ.prototype._modify_conversation_line = function(component) { var f = this, - Layout = utils.ember_lookup('controller:layout'); + Layout = utils.ember_lookup('service:layout'); component.reopen({ tokenizedMessage: function() { diff --git a/src/ember/following.js b/src/ember/following.js index c7961815..6a5dce64 100644 --- a/src/ember/following.js +++ b/src/ember/following.js @@ -33,6 +33,59 @@ FFZ.prototype.setup_profile_following = function() { this._following_cache = {}; this._follower_cache = {}; + /*try { + var ChannelSerializer = window.require("web-client/serializers/new-channel"), + BaseSerializer = window.require("web-client/serializers/application"), + process_channel = function(chan) { + var cid = chan.name; + return { + type: "new-channel", + id: cid, + attributes: chan, + relationships: { + following: { + links: { + related: "/kraken/users/" + cid + "/follows/channels?offset=0&on_site=1" + } + } + } + } + }; + + var ser = ChannelSerializer.default = BaseSerializer.default.extend({ + normalizeFindRecordResponse: function(e, t, a, r) { + var l = process_channel(a); + return this._super(e, t, { + data: l + }, r) + }, + + normalizeResponse: function(e, t, a, r, l) { + if ( ! a.follows ) + return this._super.apply(this, arguments); + + f.log("Normalizing Response", [e, t, a, r, l]); + var i = this.extractMeta(e, t, a), + o = a.follows.map(function(e) { + return process_channel(e.channel); + }), + d = { + data: o, + meta: i + }; + + return this._super(e, t, d, r, l); + } + }); + + App.registry.unregister('serializer:new-channel'); + App.registry.register('serializer:new-channel', ser); + + } catch(err) { + this.error("Unable to modify the Ember new-channel serializer", err); + }*/ + + // First, we need to hook the model. This is what we'll use to grab the following notification state, // rather than making potentially hundreds of API requests. var Following = utils.ember_resolve('model:kraken-channel-following'); diff --git a/src/ember/layout.js b/src/ember/layout.js index 29d59eae..05f0c89f 100644 --- a/src/ember/layout.js +++ b/src/ember/layout.js @@ -39,7 +39,7 @@ FFZ.settings_info.portrait_mode = { if ( this.has_bttv ) return; - var Layout = utils.ember_lookup('controller:layout'); + var Layout = utils.ember_lookup('service:layout'); if ( ! Layout ) return; @@ -126,7 +126,7 @@ FFZ.settings_info.right_column_width = { if ( this.has_bttv ) return; - var Layout = utils.ember_lookup('controller:layout'); + var Layout = utils.ember_lookup('service:layout'); if ( ! Layout ) return; @@ -151,12 +151,13 @@ FFZ.prototype.setup_layout = function() { s.id = 'ffz-layout-css'; document.head.appendChild(s); - this.log("Hooking the Ember Layout controller."); - var Layout = utils.ember_lookup('controller:layout'), + var Layout = utils.ember_lookup('service:layout'), f = this; if ( ! Layout ) - return; + return this.log("Unable to locate the Ember service:layout"); + + this.log("Hooking the Ember service:layout"); Layout.reopen({ rightColumnWidth: 340, diff --git a/src/ember/line.js b/src/ember/line.js index 4c77e92a..436b2f96 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -637,11 +637,15 @@ FFZ.prototype.save_aliases = function() { FFZ.prototype._modify_chat_line = function(component, is_vod) { var f = this, - Layout = utils.ember_lookup('controller:layout'), + Layout = utils.ember_lookup('service:layout'), Settings = utils.ember_lookup('controller:settings'); component.reopen({ tokenizedMessage: function() { + return []; + }.property('msgObject.message'), + + ffzTokenizedMessage: function() { try { return f.tokenize_chat_line(this.get('msgObject')); } catch(err) { @@ -650,7 +654,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) { } }.property("msgObject.message", "isChannelLinksDisabled", "currentUserNick", "msgObject.from", "msgObject.tags.emotes"), - lineChanged: Ember.observer("msgObject.deleted", "isModeratorOrHigher", "msgObject.ffz_old_messages", function() { + lineChanged: Ember.observer("msgObject.deleted", "isModeratorOrHigher", "msgObject.ffz_old_messages", "ffzTokenizedMessage", function() { this.$(".mod-icons").replaceWith(this.buildModIconsHTML()); if ( this.get("msgObject.deleted") ) { this.$(".message").replaceWith(this.buildDeletedMessageHTML()); @@ -812,7 +816,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) { } else output = ''; - output += f.render_tokens(this.get('tokenizedMessage'), true, is_whisper && f.settings.filter_whispered_links && this.get("ffzUserLevel") < 4); + output += f.render_tokens(this.get('ffzTokenizedMessage'), true, is_whisper && f.settings.filter_whispered_links && this.get("ffzUserLevel") < 4); var old_messages = this.get('msgObject.ffz_old_messages'); if ( old_messages && old_messages.length ) @@ -821,15 +825,10 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) { return output + ''; }, - //tagName: "li", - ffzRender: function() { var el = this.get('element'), output = this.buildSenderHTML(); - if ( el.tagName === 'DIV' ) - return this.rerender(); - if ( this.get('msgObject.deleted') ) output += this.buildDeletedMessageHTML() else @@ -868,7 +867,7 @@ FFZ.prototype._modify_chat_subline = function(component) { this.set('msgObject._line', null); }, - didUpdate: function() { this.ffzRender(); }, + //didUpdate: function() { this.ffzRender(); }, click: function(e) { if ( ! e.target ) @@ -966,6 +965,10 @@ FFZ.prototype._modify_vod_line = function(component) { attributeBindings: ["msgObject.room:data-room", "msgObject.from:data-sender", "msgObject.deleted:data-deleted"], tokenizedMessage: function() { + return []; + }.property('msgObject.message'), + + ffzTokenizedMessage: function() { try { return f.tokenize_vod_line(this.get('msgObject'), !(this.get('enableLinkification') || this.get('isModeratorOrHigher'))); } catch(err) { @@ -992,7 +995,7 @@ FFZ.prototype._modify_vod_line = function(component) { return '<message deleted>'; }, - didUpdate: function() { this.ffzRender() }, + //didUpdate: function() { this.ffzRender() }, didInsertElement: function() { this.ffzRender() }, ffzRender: function() { diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js index 0a3ee963..2910f42d 100644 --- a/src/ember/moderation-card.js +++ b/src/ember/moderation-card.js @@ -1172,7 +1172,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) { var raw_color = msg.color, colors = raw_color && this._handle_color(raw_color), - Layout = utils.ember_lookup('controller:layout'), + Layout = utils.ember_lookup('service:layout'), Settings = utils.ember_lookup('controller:settings'), is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('settings.darkMode')); diff --git a/src/ember/player.js b/src/ember/player.js index 58fbdd33..ee23cd0b 100644 --- a/src/ember/player.js +++ b/src/ember/player.js @@ -44,7 +44,7 @@ FFZ.settings_info.classic_player = { on_update: function(val) { utils.toggle_cls('ffz-classic-player')(val); - var Layout = utils.ember_lookup('controller:layout'); + var Layout = utils.ember_lookup('service:layout'); if ( Layout ) Layout.set('PLAYER_CONTROLS_HEIGHT', val ? 32 : 0); } @@ -73,7 +73,7 @@ FFZ.prototype.setup_player = function() { utils.toggle_cls('ffz-player-volume')(this.settings.player_volume_bar); utils.toggle_cls('ffz-classic-player')(this.settings.classic_player); - var Layout = utils.ember_lookup('controller:layout'); + var Layout = utils.ember_lookup('service:layout'); if ( Layout ) Layout.set('PLAYER_CONTROLS_HEIGHT', this.settings.classic_player ? 32 : 0); diff --git a/src/ember/room.js b/src/ember/room.js index 61432cd2..9388cf6b 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -371,7 +371,8 @@ FFZ.prototype._modify_rview = function(view) { s = this._$chatMessagesScroller; Ember.run.next(function() { - setTimeout(function(){ + // Trying random performance tweaks for fun and profit! + (window.requestAnimationFrame||setTimeout)(function(){ if ( e.ffz_frozen || ! s || ! s.length ) return; @@ -1104,7 +1105,7 @@ FFZ.prototype._modify_room = function(room) { // Now that we've reset the tokens, if there's a line for this, if ( last_ban._line ) - Ember.propertyDidChange(last_ban._line, 'tokenizedMessage'); + Ember.propertyDidChange(last_ban._line, 'ffzTokenizedMessage'); } } @@ -1218,7 +1219,8 @@ FFZ.prototype._modify_room = function(room) { // We need either the amount of chat delay past the first message, if chat_delay is on, or the // amount of time from the last batch. now = now || Date.now(); - var delay = Math.max( + var t = this, + delay = Math.max( (f.settings.chat_delay !== 0 ? 50 + Math.max(0, (f.settings.chat_delay + (this.ffzPending[0].time||0)) - now) : 0), (f.settings.chat_batching !== 0 ? Math.max(0, f.settings.chat_batching - (now - (this._ffz_last_batch||0))) : 0)); @@ -1316,16 +1318,23 @@ FFZ.prototype._modify_room = function(room) { var room = f.rooms && f.rooms[msg.room]; if ( room ) { var chat_history = room.user_history = room.user_history || {}, - user_history = room.user_history[msg.from] = room.user_history[msg.from] || []; + user_history = room.user_history[msg.from] = room.user_history[msg.from] || [], + last_history = user_history.length && user_history[user_history.length - 1], - user_history.push({ - from: msg.from, - tags: {'display-name': msg.tags && msg.tags['display-name']}, - message: msg.message, - cachedTokens: msg.cachedTokens, - style: msg.style, - date: msg.date - }); + new_msg = { + from: msg.from, + tags: {'display-name': msg.tags && msg.tags['display-name']}, + message: msg.message, + cachedTokens: msg.cachedTokens, + style: msg.style, + date: msg.date + }; + + // Preserve message order if we *just* received a ban. + if ( last_history && last_history.is_delete && (msg.date - last_history.date) <= 200 ) { + user_history.splice(user_history.length - 1, 0, new_msg); + } else + user_history.push(new_msg); if ( user_history.length > 20 ) user_history.shift(); diff --git a/src/ember/vod-chat.js b/src/ember/vod-chat.js index dbdceec1..f264439e 100644 --- a/src/ember/vod-chat.js +++ b/src/ember/vod-chat.js @@ -277,7 +277,7 @@ FFZ.prototype._modify_vod_chat_display = function(component) { return; Ember.run.next(function() { - setTimeout(function() { + (window.requestAnimationFrame||setTimeout)(function() { if ( e.ffz_frozen ) return; diff --git a/src/ext/rechat.js b/src/ext/rechat.js index a153a56e..57a3c8e0 100644 --- a/src/ext/rechat.js +++ b/src/ext/rechat.js @@ -151,7 +151,7 @@ FFZ.prototype.process_rechat_line = function(line, reprocess) { user_id = line.getAttribute('data-sender'), room_id = line.getAttribute('data-room'), - Layout = utils.ember_lookup('controller:layout'), + Layout = utils.ember_lookup('service:layout'), Settings = utils.ember_lookup('controller:settings'), is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('settings.darkMode')), diff --git a/src/main.js b/src/main.js index 28364f57..9d836e39 100644 --- a/src/main.js +++ b/src/main.js @@ -37,7 +37,7 @@ FFZ.msg_commands = {}; // Version var VER = FFZ.version_info = { - major: 3, minor: 5, revision: 190, + major: 3, minor: 5, revision: 194, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } @@ -116,7 +116,7 @@ FFZ.prototype.get_user = function(force_reload) { if ( ! force_reload && this.__user ) return this.__user; - var LC = FFZ.utils.ember_lookup('controller:login'), + var LC = FFZ.utils.ember_lookup('service:login'), user = LC ? LC.get('userData') : undefined; if ( ! user && window.PP && PP.login ) @@ -225,10 +225,11 @@ FFZ.prototype.initialize = function(increment, delay) { // Twitch ember application is ready. // Pages we don't want to interact with at all. - if ( location.hostname === 'passport.twitch.tv' || /^\/user\/two_factor/.test(location.pathname) ) { - this.log("Found authentication sub-page. Not initializing."); - return; - } + if ( location.hostname === 'passport.twitch.tv' || /^\/user\/two_factor/.test(location.pathname) ) + return this.log("Found authentication sub-page. Not initializing."); + + if ( ['im.twitch.tv', 'api.twitch.tv'].indexOf(location.hostname) !== -1 ) + return this.log("Found banned sub-domain. Not initializing."); // Check for the player if ( location.hostname === 'player.twitch.tv' ) { diff --git a/src/styles/chat-background.css b/src/styles/chat-background.css index 5b36639e..c4db7d12 100644 --- a/src/styles/chat-background.css +++ b/src/styles/chat-background.css @@ -1,6 +1,5 @@ /* Regular Alternating Background */ .conversation-chat-lines > div:nth-child(2n+0):before, -.chat-lines > div:nth-child(2n+0) > .chat-line:before, .chat-line:nth-child(2n+0):before { background-color: rgba(0,0,0, 0.1); } @@ -12,10 +11,6 @@ .theatre .conversation-chat-lines > div:nth-child(2n+0):before, .theatre .chat-line:nth-child(2n+0):before, -.theatre .chat-lines > div:nth-child(2n+0) > .chat-line:before, - -.dark .chat-lines > div:nth-child(2n+0) > .chat-line:before, -.force-dark .chat-lines > div:nth-child(2n+0) > .chat-line:before, .dark .chat-line:nth-child(2n+0):before, .force-dark .chat-line:nth-child(2n+0):before { @@ -29,7 +24,6 @@ } -.chat-lines > div:nth-child(2n+0) > .chat-line.whisper:nth-child(2n+0):before, .chat-line.whisper:nth-child(2n+0):before { background-color: rgba(185, 163, 227, 0.4); } @@ -40,10 +34,6 @@ background-color: rgba(100, 65, 165, 0.2); } -.theatre .chat-lines > div:nth-child(2n+0) > .chat-line.whisper:before, -.dark .chat-lines > div:nth-child(2n+0) > .chat-line.whisper:before, -.force-dark .chat-lines > div:nth-child(2n+0) > .chat-line.whisper:before, - .theatre .chat-line.whisper:nth-child(2n+0):before, .dark .chat-line.whisper:nth-child(2n+0):before, .force-dark .chat-line.whisper:nth-child(2n+0):before { diff --git a/src/styles/chat-separator.css b/src/styles/chat-separator.css index 63d01bf4..5cba6d2c 100644 --- a/src/styles/chat-separator.css +++ b/src/styles/chat-separator.css @@ -17,14 +17,12 @@ /* Hide First Line */ .conversation-chat-lines > div:first-child:before, -.chat-lines > div:first-of-type > .chat-line:before, -.chatReplay .chat-line:first-of-type:before { +.chat-line:first-of-type:before { border-top-color: transparent !important; } /* Hide Last Line */ .conversation-chat-lines > div:last-child:nth-child(odd):before, -.chat-lines > div:last-of-type:nth-child(odd) > .chat-line:before, -.chatReplay .chat-line:last-of-type:nth-child(odd):before { +.chat-line:last-of-type:nth-child(odd):before { border-bottom-color: transparent !important; } \ No newline at end of file diff --git a/src/tokenize.js b/src/tokenize.js index 8a94b9d4..0be13ccd 100644 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -476,7 +476,7 @@ FFZ.prototype.tokenize_vod_line = function(msgObject, delete_links) { } -FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, delete_links) { +FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, delete_links, use_bits) { if ( msgObject.cachedTokens ) return msgObject.cachedTokens; @@ -489,6 +489,9 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del tokens = [msg]; // Standard tokenization + if ( use_bits && helpers && helpers.tokenizeBits ) + tokens = helpers.tokenizeBits(tokens); + if ( helpers && helpers.linkifyMessage ) { var labels = msgObject.labels || [], mod_or_higher = labels.indexOf("owner") !== -1 || diff --git a/src/ui/about_page.js b/src/ui/about_page.js index 72d00ff7..be95fdd1 100644 --- a/src/ui/about_page.js +++ b/src/ui/about_page.js @@ -52,7 +52,7 @@ FFZ.ws_commands.update_news = function(version) { // About Page // ------------------- -var include_html = function(heading_text, filename) { +var include_html = function(heading_text, filename, callback) { return function(view, container) { var heading = createElement('div', 'chat-menu-content center'); heading.innerHTML = '

FrankerFaceZ

' + (heading_text ? '
' + heading_text + '
' : ''); @@ -67,6 +67,8 @@ var include_html = function(heading_text, filename) { jQuery('#ffz-old-news', container).css('display', 'block'); }); + typeof callback === "function" && callback(view, container); + }).fail(function(data) { var content = createElement('div', 'chat-menu-content menu-side-padding'); content.textContent = 'There was an error loading this page from the server.'; @@ -252,7 +254,7 @@ FFZ.menu_pages.about = { }, changelog: { - name: "Changelog", + name: "Changes", wide: true, render: include_html("change log", constants.SERVER + "script/changelog.html") }, @@ -272,11 +274,19 @@ FFZ.menu_pages.about = { },*/ credits: { - name: "Credits", + name: "Credit", wide: true, render: include_html("credits", constants.SERVER + "script/credits.html") }, + /*status: { + name: "Status", + wide: true, + render: include_html("server status", constants.SERVER + "script/status.html", function(view, container) { + + }) + },*/ + debugging: { name: "Debug", wide: true, diff --git a/src/ui/following.js b/src/ui/following.js index 679e2153..fd49cbfc 100644 --- a/src/ui/following.js +++ b/src/ui/following.js @@ -77,7 +77,7 @@ FFZ.ffz_commands.following = function(room, args) { FFZ.ws_on_close.push(function() { var controller = utils.ember_lookup('controller:channel'), - current_id = controller && controller.get('id'), + current_id = controller && controller.get('content.id'), current_host = controller && controller.get('hostModeTarget.id'), need_update = false; diff --git a/src/ui/menu.js b/src/ui/menu.js index 234bea45..f8198d99 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -46,7 +46,7 @@ FFZ.prototype.setup_menu = function() { this.log("Hooking the Ember Chat Settings view."); var Settings = utils.ember_resolve('view:settings'), - Layout = utils.ember_lookup('controller:layout'), + Layout = utils.ember_lookup('service:layout'), f = this; if ( ! Settings ) diff --git a/src/ui/races.js b/src/ui/races.js index cbd52807..1c4345d4 100644 --- a/src/ui/races.js +++ b/src/ui/races.js @@ -36,7 +36,7 @@ FFZ.settings_info.srl_races = { FFZ.ws_on_close.push(function() { var controller = utils.ember_lookup('controller:channel'), - current_id = controller && controller.get('id'), + current_id = controller && controller.get('content.id'), current_host = controller && controller.get('hostModeTarget.id'), need_update = false; @@ -56,7 +56,7 @@ FFZ.ws_on_close.push(function() { FFZ.ws_commands.srl_race = function(data) { var controller = utils.ember_lookup('controller:channel'), - current_id = controller && controller.get('id'), + current_id = controller && controller.get('content.id'), current_host = controller && controller.get('hostModeTarget.id'), need_update = false; @@ -92,7 +92,7 @@ FFZ.ws_commands.srl_race = function(data) { FFZ.prototype.rebuild_race_ui = function() { var controller = utils.ember_lookup('controller:channel'), - channel_id = controller && controller.get('id'), + channel_id = controller && controller.get('content.id'), hosted_id = controller && controller.get('hostModeTarget.id'); if ( ! this._cindex ) @@ -111,7 +111,7 @@ FFZ.prototype.rebuild_race_ui = function() { } else { if ( ! race_container ) { - race_container = document.createElement('span'); + race_container = utils.createElement('span', 'balloon-wrapper inline'); race_container.id = 'ffz-ui-race'; race_container.setAttribute('data-channel', channel_id); @@ -143,7 +143,7 @@ FFZ.prototype.rebuild_race_ui = function() { } else { if ( ! race_container ) { - race_container = document.createElement('span'); + race_container = utils.createElement('span', 'balloon-wrapper inline'); race_container.id = 'ffz-ui-race'; race_container.setAttribute('data-channel', hosted_id); @@ -191,10 +191,10 @@ FFZ.prototype._build_race_popup = function(container, channel_id) { pos = el.offsetLeft + el.offsetWidth, race = this.srl_races[channel_id]; - var popup = document.createElement('div'), out = ''; + var popup = utils.createElement('div', 'share balloon balloon--md balloon--up balloon--dropmenu'), out = ''; popup.id = 'ffz-race-popup'; popup.setAttribute('data-channel', channel_id); - popup.className = (pos >= 300 ? 'right' : 'left') + ' share dropmenu'; + //popup.className = (pos >= 300 ? 'right' : 'left') + ' share dropmenu'; this._popup_kill = this._race_kill.bind(this); this._popup_allow_parent = true; @@ -212,13 +212,12 @@ FFZ.prototype._build_race_popup = function(container, channel_id) { var height = document.querySelector('.app-main.theatre') ? document.body.clientHeight - 300 : container.parentElement.offsetTop - 175, controller = utils.ember_lookup('controller:channel'), - display_name = controller ? controller.get('display_name') : FFZ.get_capitalization(channel_id), + display_name = controller && controller.get('content.id') === channel_id ? controller.get('content.display_name') : FFZ.get_capitalization(channel_id), tweet = encodeURIComponent("I'm watching " + display_name + " race " + race.goal + " in " + race.game + " on SpeedRunsLive!"); - out = '
'; + out = '
'; out += '
'; out += '
#Entrant Time
'; - out += '
'; out += ''; @@ -266,7 +265,7 @@ FFZ.prototype._update_race = function(container, not_timer) { if ( popup ) { var tbody = popup.querySelector('tbody'), - timer = popup.querySelector('.heading span'), + timer = popup.querySelector('.heading > span'), info = popup.querySelector('.heading div'); tbody.innerHTML = ''; @@ -310,7 +309,7 @@ FFZ.prototype._update_race = function(container, not_timer) { place = utils.place_string(ent.place), comment = ent.comment ? utils.sanitize(ent.comment) : ""; - tbody.innerHTML += '' + place + '' + name + '' + twitch_link + hitbox_link + '' + (ent.state == "forfeit" ? "Forfeit" : time) + ''; + tbody.innerHTML += '' + place + '' + name + '' + twitch_link + hitbox_link + '' + (ent.state == "forfeit" ? "Forfeit" : time) + ''; } if ( this._race_game != race.game || this._race_goal != race.goal ) { @@ -318,9 +317,18 @@ FFZ.prototype._update_race = function(container, not_timer) { this._race_goal = race.goal; var game = utils.sanitize(race.game), - goal = utils.sanitize(race.goal); + goal = utils.unquote_attr(race.goal), + old_goal = popup.getAttribute('data-old-goal'); - info.innerHTML = '

' + game + "

Goal: " + goal; + if ( goal !== old_goal ) { + popup.setAttribute('data-old-goal', goal); + goal = goal ? this.render_tokens(this.tokenize_line("jtv", null, goal, true)) : ''; + info.innerHTML = '

' + game + '

Goal: ' + goal + ''; + } + } + + if ( race.time ) { + timer.title = 'Started at: ' + utils.sanitize(utils.parse_date(1000 * race.time).toLocaleString()) + ''; } if ( ! elapsed ) diff --git a/src/utils.js b/src/utils.js index 76db9267..f1ab59c4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -9,6 +9,10 @@ var sanitize_el = document.createElement('span'), return sanitize_el.innerHTML; }, + unquote_attr = function(msg) { + return msg.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, "'").replace(/>/g, '>').replace(/</g, '<'); + }, + escape_regex = RegExp.escape || function(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }, @@ -388,6 +392,7 @@ module.exports = FFZ.utils = { }, sanitize: sanitize, + unquote_attr: unquote_attr, quote_attr: quote_attr, date_string: function(date) { diff --git a/style.css b/style.css index bb7992b1..90bf8200 100644 --- a/style.css +++ b/style.css @@ -54,12 +54,12 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emotic .ember-chat-container.dark .ffz-ui-toggle svg.svg-emoticons path, .chat-container.dark .ffz-ui-toggle svg.svg-emoticons path, .app-main.theatre .ffz-ui-toggle svg.svg-emoticons path, -body.ffz-bttv-dark .ffz-ui-toggle svg.svg-emoticons path { fill: #888; } +body.ffz-bttv-dark .ffz-ui-toggle svg.svg-emoticons path { fill: #555; } .ember-chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path, .chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path, .app-main.theatre .ffz-ui-toggle:hover svg.svg-emoticons path, -body.ffz-bttv-dark .ffz-ui-toggle:hover svg.svg-emoticons path { fill: #777; } +body.ffz-bttv-dark .ffz-ui-toggle:hover svg.svg-emoticons path { fill: #999; } .ffz-ui-toggle.no-emotes svg.svg-emoticons path { fill: rgba(80,0,0,0.2); } @@ -360,50 +360,79 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: #ffz-race-popup { position: absolute; - bottom: 0; + display: block; background-image: url("//cdn.frankerfacez.com/script/zreknarf-bg.png"); background-repeat: no-repeat; + padding: 1rem; background-position: 115% 110%; } #ffz-race-popup.right { right: 10px; } #ffz-race-popup .heading { - margin: -20px -20px 20px; - width: 340px; height: 65px; + margin: -1rem -1rem 1rem; + height: 65px; position: relative; } #ffz-race-popup .heading div { - padding: 10px 0 0 20px; - max-width: 240px; + padding: 0 1rem; +} + +#ffz-race-popup .heading h2, +#ffz-race-popup .heading .goal { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#ffz-race-popup .heading .goal { + display: block; + margin: calc(-1rem + 1px); + padding: calc(1rem - 1px); +} + +#ffz-race-popup .heading .goal:hover { + white-space: normal; + background-color: rgba(255,255,255,0.9); + border-bottom: 1px solid rgba(0,0,0,0.2); +} + +.theatre #ffz-race-popup .heading .goal:hover, +.ffz-dark #ffz-race-popup .heading .goal:hover { + background-color: rgba(16,16,16,0.9); + border-color: rgba(255,255,255,0.2); } #ffz-race-popup .heading h2 { + max-width: 240px; font-size: 1.5em; padding-bottom: 5px; display: block; - width: 240px; - max-height: 45px; - overflow: hidden; - text-overflow: ellipsis; + width: 200px; + margin-bottom: 0; } -#ffz-race-popup .heading span { +#ffz-race-popup .heading > span { line-height: 30px; position: absolute; - top: 17.5px; - right: 20px; + top: 7.5px; + right: 1rem; padding: 0 5px; background: rgba(0,0,0,0.5); color: #fff; border-radius: 5px; } -#ffz-race-popup .right { text-align: right; } +#ffz-race-popup .right { + padding-top: 0; + text-align: right; +} #ffz-race-popup .table { overflow-y: auto; + border-bottom: 1px solid; + margin-bottom: 1rem; } #ffz-race-popup table { @@ -587,6 +616,16 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: .list-header span.right { float: right; } +.ember-chat .chat-menu .list-header.sub-header { + border-top: none; + margin: 0 20px; + padding: 5px 0 0; +} + +.ember-chat .chat-menu .list-header.sub-header + .chat-menu-content { + padding-top: 0; +} + .chat-menu-content.collapsable .heading span.right { padding-right: 15px; } @@ -635,6 +674,10 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: border-bottom: 1px dotted rgba(127,127,127,0.5); } +.ffz-ui-menu-page .version-list ul { + margin-left: 20px +} + .ffz-ui-menu-page pre { box-shadow: none !important; white-space: pre-wrap; @@ -1255,7 +1298,7 @@ img.channel_background[src="null"] { display: none; } .ffz-moderation-card .mod-controls:last-of-type button:last-of-type { margin-right: 0 } .ffz-moderation-card .follow-button a { - text-indent: -9999px; + font-size: 0 !important; padding-right: 0 !important; } @@ -2795,7 +2838,7 @@ body:not(.ffz-tags-on-channel) .tw-title--tall { height: 60px } /* Banned and Spoiler Games */ .ffz-game-spoilered .thumb .cap img, -body:not([data-current-path^="directory.csgo"]):not([data-current-path^="directory.game-directory"]):not([data-current-path^="directory.creative"]) .ffz-game-banned { display: none } +body:not([data-current-path^="directory.csgo"]):not([data-current-path^="directory.game"]):not([data-current-path^="directory.creative"]) .ffz-game-banned { display: none } .ffz-game-spoilered .thumb .cap { position: absolute !important; top: 0; left: 0; bottom: 0; right: 0;