diff --git a/dark.css b/dark.css index 696bc969..40e00dd1 100644 --- a/dark.css +++ b/dark.css @@ -902,6 +902,26 @@ /* Conversations */ +.ffz-dark .conversation-input-bar .emoticon-selector-toggle svg path { + fill: rgba(255,255,255,0.2); +} + +.ffz-dark .conversation-input-bar .emoticon-selector-toggle:hover svg path { + fill: rgba(255,255,255,0.5); +} + +.ffz-dark .conversation-input-bar .emoticon-selector-box .emote-set { + border-color: #323232; +} + +.ffz-dark .conversation-input-bar .emoticon-selector-box .emoticon-grid { + background-color: #191919; +} + +.ffz-dark .ember-chat .chat-settings .experimental-options { + border-top-color: rgba(255,255,255, 0.2); +} + .ffz-dark .conversation-settings-menu .options-divider { border-bottom-color: rgba(255,255,255,0.2); } @@ -1012,5 +1032,8 @@ background-color: #6441a5 } +.ffz-dark .conversation-window .timestamp-line span, .ffz-dark .conversation-window .new-message-divider span { background: transparent; } + +.ffz-dark .conversation-window .timestamp-line:after, .ffz-dark .conversation-window .new-message-divider:after { display: none; } \ No newline at end of file diff --git a/src/badges.js b/src/badges.js index 453e0301..1cf59849 100644 --- a/src/badges.js +++ b/src/badges.js @@ -260,6 +260,10 @@ FFZ.prototype.render_badges = function(component, badges) { var user = component.get('msgObject.from') || component.get('message.from.username'), room_id = component.get('msgObject.room') || App.__container__.lookup('controller:chat').get('currentRoom.id'); + return this._render_badges(user, room_id, badges, component); +} + +FFZ.prototype._render_badges = function(user, room_id, badges, component) { var data = this.users[user]; if ( ! data || ! data.badges ) return badges; diff --git a/src/colors.js b/src/colors.js index e05ece22..8b844339 100644 --- a/src/colors.js +++ b/src/colors.js @@ -187,6 +187,43 @@ RGBColor.prototype.eq = function(rgb) { return rgb.r === this.r && rgb.g === this.g && rgb.b === this.b; } +RGBColor.fromCSS = function(rgb) { + rgb = rgb.trim(); + + if ( rgb.charAt(0) === '#' ) + return RGBColor.fromHex(rgb); + + var match = /rgba?\( *(\d+%?) *, *(\d+%?) *, *(\d+%?) *(?:,[^\)]+)?\)/.exec(rgb); + if ( match ) { + var r = match[1], + g = match[2], + b = match[3]; + + if ( r.charAt(r.length-1) === '%' ) + r = 255 * (parseInt(r) / 100); + else + r = parseInt(r); + + if ( g.charAt(g.length-1) === '%' ) + g = 255 * (parseInt(g) / 100); + else + g = parseInt(g); + + if ( b.charAt(b.length-1) === '%' ) + b = 255 * (parseInt(b) / 100); + else + b = parseInt(b); + + return new RGBColor( + Math.min(Math.max(0, r), 255), + Math.min(Math.max(0, g), 255), + Math.min(Math.max(0, b), 255) + ); + } + + return null; +} + RGBColor.fromHex = function(code) { var raw = parseInt(code.charAt(0) === '#' ? code.substr(1) : code, 16); return new RGBColor( @@ -587,6 +624,9 @@ FFZ.prototype._update_colors = function(darkness_only) { FFZ.prototype._handle_color = function(color) { + if ( color instanceof RGBColor ) + color = color.toHex(); + if ( ! color || this._colors.hasOwnProperty(color) ) return this._colors[color]; diff --git a/src/constants.js b/src/constants.js index d8422d59..66f91022 100644 --- a/src/constants.js +++ b/src/constants.js @@ -17,6 +17,8 @@ module.exports = { 2: ["ws://localhost:8001/"] }, + TOOLTIP_DISTANCE: 50, + KNOWN_CODES: { "#-?[\\\\/]": "#-/", ":-?(?:7|L)": ":-7", @@ -42,6 +44,7 @@ module.exports = { "Gr(a|e)yFace": "GrayFace" }, + TWITCH_BASE: 'http://static-cdn.jtvnw.net/emoticons/v1/', EMOTE_MIRROR_BASE: SERVER + "twitch-emote-mirror/", EMOTE_REPLACEMENT_BASE: SERVER + "script/replacements/", diff --git a/src/ember/channel.js b/src/ember/channel.js index 20f02e1e..f7c6f07b 100644 --- a/src/ember/channel.js +++ b/src/ember/channel.js @@ -215,7 +215,13 @@ FFZ.prototype._modify_cindex = function(view) { el.classList.add('ffz-channel'); // Try changing the theater mode tooltip. - this.$('.theatre-button a').attr('title', 'Theater Mode (Alt+T)'); + var tb = this.$('.theatre-button > a'), + opts = tb.data('tipsy'); + + tb.attr('title', 'Theater Mode (Alt+T)'); + if ( opts && opts.options && typeof opts.options.gravity !== "function" ) + opts.options.gravity = utils.tooltip_placement(constants.TOOLTIP_DISTANCE, opts.options.gravity || 'n'); + this.ffzFixTitle(); this.ffzUpdateUptime(); @@ -283,7 +289,7 @@ FFZ.prototype._modify_cindex = function(view) { if ( ! btn ) { btn = document.createElement('span'); btn.id = 'ffz-ui-host-button'; - btn.className = 'button action tooltip'; + btn.className = 'button action'; btn.addEventListener('click', this.ffzClickHost.bind(btn, this, false)); @@ -295,6 +301,8 @@ FFZ.prototype._modify_cindex = function(view) { container.insertBefore(btn, before); else container.appendChild(btn); + + jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } btn.classList.remove('disabled'); @@ -321,7 +329,7 @@ FFZ.prototype._modify_cindex = function(view) { if ( ! btn ) { btn = document.createElement('span'); btn.id = 'ffz-ui-host-button'; - btn.className = 'button action tooltip'; + btn.className = 'button action'; btn.addEventListener('click', this.ffzClickHost.bind(btn, this, true)); @@ -333,6 +341,8 @@ FFZ.prototype._modify_cindex = function(view) { container.insertBefore(btn, before); else container.appendChild(btn); + + jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } btn.classList.remove('disabled'); @@ -406,7 +416,7 @@ FFZ.prototype._modify_cindex = function(view) { else cont.appendChild(stat); - jQuery(stat).tipsy(); + jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } el.innerHTML = utils.number_commas(chatter_count); @@ -438,7 +448,7 @@ FFZ.prototype._modify_cindex = function(view) { else cont.appendChild(stat); - jQuery(stat).tipsy(); + jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } el.innerHTML = utils.number_commas(ffz_viewers) + " (" + utils.number_commas(ffz_chatters) + ")"; @@ -473,7 +483,7 @@ FFZ.prototype._modify_cindex = function(view) { if ( ! stat_el ) { stat_el = document.createElement('span'); stat_el.id = 'ffz-ui-player-stats'; - stat_el.className = 'ffz stat tooltip'; + stat_el.className = 'ffz stat'; stat_el.innerHTML = constants.GRAPH + " "; el = document.createElement('span'); @@ -484,16 +494,18 @@ FFZ.prototype._modify_cindex = function(view) { container.insertBefore(stat_el, other.nextSibling); else container.appendChild(stat_el); + + jQuery(stat_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } var delay = parseFloat(stats.hlsLatencyBroadcaster); if ( delay > 180 ) { delay = Math.floor(delay); - stat_el.setAttribute('original-title', 'Video Information\nBroadcast ' + utils.time_to_string(delay, true) + ' Ago\n\nVideo: ' + stats.videoResolution + 'p @ ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps') + stat_el.setAttribute('original-title', 'Video Information
Broadcast ' + utils.time_to_string(delay, true) + ' Ago

Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '
Playback Rate: ' + stats.playbackRate + ' Kbps') el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old'; } else { - stat_el.setAttribute('original-title', 'Stream Latency\nVideo: ' + stats.videoResolution + 'p @ ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps'); + stat_el.setAttribute('original-title', 'Stream Latency
Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '
Playback Rate: ' + stats.playbackRate + ' Kbps'); delay = stats.hlsLatencyBroadcaster; var pos = delay.lastIndexOf('.'); @@ -532,7 +544,7 @@ FFZ.prototype._modify_cindex = function(view) { if ( ! stat_el ) { stat_el = document.createElement('span'); stat_el.id = 'ffz-ui-player-stats'; - stat_el.className = 'ffz stat tooltip'; + stat_el.className = 'ffz stat'; stat_el.innerHTML = constants.GRAPH + " "; el = document.createElement('span'); @@ -543,16 +555,18 @@ FFZ.prototype._modify_cindex = function(view) { container.insertBefore(stat_el, other.nextSibling); else container.appendChild(stat_el); + + jQuery(stat_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } var delay = parseFloat(stats.hlsLatencyBroadcaster); if ( delay > 180 ) { delay = Math.floor(delay); - stat_el.setAttribute('original-title', 'Video Information\nBroadcast ' + utils.time_to_string(delay, true) + ' Ago\n\nVideo: ' + stats.videoResolution + 'p @ ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps') + stat_el.setAttribute('original-title', 'Video Information
Broadcast ' + utils.time_to_string(delay, true) + ' Ago

Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '
Playback Rate: ' + stats.playbackRate + ' Kbps') el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old'; } else { - stat_el.setAttribute('original-title', 'Stream Latency\nVideo: ' + stats.videoResolution + 'p @ ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps'); + stat_el.setAttribute('original-title', 'Stream Latency
Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '
Playback Rate: ' + stats.playbackRate + ' Kbps'); delay = stats.hlsLatencyBroadcaster; var pos = delay.lastIndexOf('.'); @@ -624,7 +638,7 @@ FFZ.prototype._modify_cindex = function(view) { } } - jQuery(stat).tipsy({html: true}); + jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } el.innerHTML = utils.time_to_string(uptime, false, false, false, f.settings.stream_uptime === 1 || f.settings.stream_uptime === 3); diff --git a/src/ember/chatview.js b/src/ember/chatview.js index e49f0c1d..9cfee7e4 100644 --- a/src/ember/chatview.js +++ b/src/ember/chatview.js @@ -415,7 +415,7 @@ FFZ.prototype._modify_cview = function(view) { ffzInit: function() { f._chatv = this; this.$('.textarea-contain').append(f.build_ui_link(this)); - this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: jQuery.fn.tipsy.autoNS}); + this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); if ( !f.has_bttv && f.settings.group_tabs ) this.ffzEnableTabs(); @@ -447,7 +447,7 @@ FFZ.prototype._modify_cview = function(view) { var room = this.get('controller.currentRoom'), rows; room && room.resetUnreadCount(); - if ( room._ffz_was_unread ) { + if ( room && room._ffz_was_unread ) { room._ffz_was_unread = false; var el = this.get('element'), diff --git a/src/ember/conversations.js b/src/ember/conversations.js index f73996b2..b9c1ef2a 100644 --- a/src/ember/conversations.js +++ b/src/ember/conversations.js @@ -7,23 +7,11 @@ var FFZ = window.FrankerFaceZ, // Settings // --------------- -FFZ.settings_info.conv_title_clickable = { - type: "boolean", - value: false, - no_mobile: true, - - category: "Conversations", - name: "Clickable Header Name", - help: "Make the conversation header a link that takes you to that person's page.", - on_update: function(val) { - document.body.classList.toggle('ffz-conv-title-clickable', val); - } - }; - FFZ.settings_info.conv_focus_on_click = { type: "boolean", value: false, no_mobile: true, + visible: false, category: "Conversations", name: "Focus Input on Click", @@ -43,19 +31,6 @@ FFZ.settings_info.top_conversations = { } }; -FFZ.settings_info.conv_beta_enable = { - type: "boolean", - value: false, - no_mobile: true, - - category: "Conversations", - name: "Enable Conversations", - help: "Twitch hasn't enabled them yet, but they're in the code for testing. Try them out!", - on_update: function(val) { - App.__container__.lookup('route:application').controller.set('isConversationsEnabled', val); - } - }; - // --------------- // Initialization @@ -63,10 +38,6 @@ FFZ.settings_info.conv_beta_enable = { FFZ.prototype.setup_conversations = function() { document.body.classList.toggle('ffz-top-conversations', this.settings.top_conversations); - document.body.classList.toggle('ffz-conv-title-clickable', this.settings.conv_title_clickable);; - - if ( this.settings.conv_beta_enable ) - App.__container__.lookup('route:application').controller.set('isConversationsEnabled', true); this.log("Hooking the Ember Conversation Window component."); var ConvWindow = App.__container__.resolve('component:conversation-window'); @@ -78,6 +49,9 @@ FFZ.prototype.setup_conversations = function() { var ConvLine = App.__container__.resolve('component:conversation-line'); if ( ConvLine ) this._modify_conversation_line(ConvLine); + + // 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')}); } @@ -88,14 +62,8 @@ FFZ.prototype._modify_conversation_window = function(component) { Settings = App.__container__.lookup('controller:settings'); component.reopen({ - onConversationClick: Ember.on('click', function() { - this.markConversationRead(); - if ( f.settings.conv_focus_on_click ) - this.$(".conversation-input-bar textarea").focus(); - }), - - headerBadges: Ember.computed("conversation.participants", "currentUsername", function() { - var e = this.get("conversation.participants").rejectBy("username", this.get("currentUsername")).objectAt(0), + headerBadges: Ember.computed("thread.participants", "currentUsername", function() { + var e = this.get("thread.participants").rejectBy("username", this.get("currentUsername")).objectAt(0), badges = {}, ut = e.get("userType"); @@ -164,28 +132,18 @@ FFZ.prototype._modify_conversation_window = function(component) { header = el && el.querySelector('.conversation-header'), header_name = header && header.querySelector('.conversation-header-name'), - new_header_name = document.createElement('span'), - raw_color = this.get('otherUser.color'), colors = raw_color && f._handle_color(raw_color), is_dark = (Layout && Layout.get('isTheatreMode')) || f.settings.dark_twitch; - if ( header_name ) { - new_header_name.className = 'conversation-header-name'; - new_header_name.textContent = header_name.textContent; - header.insertBefore(new_header_name, header_name); - - if ( raw_color ) { - header_name.style.color = (is_dark ? colors[1] : colors[0]); - header_name.classList.add('has-color'); - header_name.setAttribute('data-color', raw_color); - - new_header_name.style.color = (is_dark ? colors[1] : colors[0]); - new_header_name.classList.add('has-color'); - new_header_name.setAttribute('data-color', raw_color); - } + if ( header_name && raw_color ) { + header_name.style.color = (is_dark ? colors[1] : colors[0]); + header_name.classList.add('has-color'); + header_name.setAttribute('data-color', raw_color); } + + jQuery(el).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); } }); } diff --git a/src/ember/directory.js b/src/ember/directory.js index f3704d36..eba12dd4 100644 --- a/src/ember/directory.js +++ b/src/ember/directory.js @@ -75,7 +75,7 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) { var t_el = this._ffz_uptime = document.createElement('div'); t_el.className = 'overlay_info length live'; - jQuery(t_el).tipsy({html: true}); + jQuery(t_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 's')}); cap.appendChild(t_el); this._ffz_uptime_timer = setInterval(this.ffzUpdateUptime.bind(this), 1000); diff --git a/src/ember/following.js b/src/ember/following.js index a743749e..21d20985 100644 --- a/src/ember/following.js +++ b/src/ember/following.js @@ -159,7 +159,7 @@ FFZ.prototype.setup_profile_following = function() { return false; t_el.className = 'overlay_info length'; - jQuery(t_el).tipsy({html: true}); + jQuery(t_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 's')}); var age = data[0] ? Math.floor((Date.now() - data[0].getTime()) / 1000) : 0; if ( age ) { diff --git a/src/ember/layout.js b/src/ember/layout.js index c77207a7..5da9d789 100644 --- a/src/ember/layout.js +++ b/src/ember/layout.js @@ -228,7 +228,7 @@ FFZ.prototype.setup_layout = function() { return; f._portrait_warning = true; - f.show_message('Twitch\'s Chat Sidebar has been hidden as a result of FrankerFaceZ\'s Portrait Mode because the window is too wide.

Please disable Portrait Mode or make your window narrower.

Do not show this message again'); + f.show_message('Twitch\'s Chat Sidebar has been hidden as a result of FrankerFaceZ\'s Portrait Mode because the window is too wide.

Please disable Portrait Mode or make your window narrower.

Do not show this message again'); }.observes("isTooSmallForRightColumn"), diff --git a/src/ember/line.js b/src/ember/line.js index b2e7b53a..52543528 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -113,7 +113,7 @@ FFZ.settings_info.scrollback_length = { for(var room_id in this.rooms) { var room = this.rooms[room_id]; - room.room.set('messageBufferSize', new_val + ((this._roomv && !this._roomv.get('stuckToBottom') && current_id === room_id) ? 150 : 0)); + room.room && room.room.set('messageBufferSize', new_val + ((this._roomv && !this._roomv.get('stuckToBottom') && current_id === room_id) ? 150 : 0)); } } }; @@ -149,7 +149,6 @@ FFZ.settings_info.banned_words = { category: "Chat Filtering", no_bttv: true, - //visible: function() { return ! this.has_bttv }, name: "Banned Words", help: "Set a list of words that will be locally removed from chat messages.", @@ -181,7 +180,6 @@ FFZ.settings_info.keywords = { category: "Chat Filtering", no_bttv: true, - //visible: function() { return ! this.has_bttv }, name: "Highlight Keywords", help: "Set additional keywords that will be highlighted in chat.", @@ -246,6 +244,21 @@ FFZ.settings_info.link_image_hover = { }; +FFZ.settings_info.emote_image_hover = { + type: "boolean", + value: false, + + category: "Chat Tooltips", + no_mobile: true, + + name: "Emote Preview", + help: "Display scaled up high-DPI emoticon images in tooltips to help see details on low-resolution monitors.", + on_update: function(val) { + this._reset_tooltips(); + } + }; + + FFZ.settings_info.image_hover_all_domains = { type: "boolean", value: false, @@ -402,7 +415,7 @@ FFZ.settings_info.chat_font_family = { var span = document.createElement('span'); span.style.fontFamily = val; - css = ".ember-chat .chat-messages {" + span.style.cssText + "}"; + css = ".timestamp-line,.conversation-chat-line,.conversation-system-messages,.chat-history,.ember-chat .chat-messages {" + span.style.cssText + "}"; } utils.update_css(this._chat_style, "chat_font_family", css); @@ -444,7 +457,7 @@ FFZ.settings_info.chat_font_size = { else { var lh = Math.max(20, Math.round((20/12)*val)), pd = Math.floor((lh - 20) / 2); - css = ".ember-chat .chat-messages .chat-line { font-size: " + val + "px !important; line-height: " + lh + "px !important; }"; + css = ".timestamp-line,.conversation-chat-line,.conversation-system-messages,.chat-history .chat-line,.ember-chat .chat-messages .chat-line { font-size: " + val + "px !important; line-height: " + lh + "px !important; }"; if ( pd ) css += ".ember-chat .chat-messages .chat-line .mod-icons, .ember-chat .chat-messages .chat-line .badges { padding-top: " + pd + "px; }"; } @@ -492,7 +505,7 @@ FFZ.settings_info.chat_ts_size = { css = ""; else { var lh = Math.max(20, Math.round((20/12)*val), Math.round((20/12)*this.settings.chat_font_size)); - css = ".ember-chat .chat-messages .timestamp { font-size: " + val + "px !important; line-height: " + lh + "px !important; }"; + css = ".chat-history .timestamp,.ember-chat .chat-messages .timestamp { font-size: " + val + "px !important; line-height: " + lh + "px !important; }"; } utils.update_css(this._chat_style, "chat_ts_font_size", css); @@ -607,11 +620,13 @@ FFZ.prototype._modify_line = function(component) { if ( e.target.classList.contains('custom') ) { var room_id = this.get('msgObject.room'), room = room_id && f.rooms[room_id] && f.rooms[room_id].room, - cmd = e.target.getAttribute('data-cmd'); if ( room ) { - room.send(cmd, true); + var lines = cmd.split("\n"); + for(var i=0; i < lines.length; i++) + room.send(lines[i], true); + if ( e.target.classList.contains('is-timeout') ) room.clearMessages(this.get('msgObject.from')); } @@ -683,8 +698,8 @@ FFZ.prototype._modify_line = function(component) { else { if ( typeof btn === "string" ) { - cmd = btn.replace(/{user}/g, user); - tip = 'Custom Command\n' + cmd; + cmd = btn.replace(/{user}/g, user).replace(/ * */, "\n"); + tip = 'Custom Command' + (cmd.indexOf('\n') !== -1 ? 's' : '') + '\n' + cmd; } else { cmd = "/timeout " + user + " " + btn; tip = "Timeout User (" + utils.duration_string(btn) + ")"; diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js index 536dafc8..dda690af 100644 --- a/src/ember/moderation-card.js +++ b/src/ember/moderation-card.js @@ -182,7 +182,7 @@ FFZ.settings_info.mod_buttons = { old_val += ' ' + prefix + cmd; } - var new_val = prompt("Custom In-Line Moderation Icons\n\nPlease enter a list of commands to be made available as mod icons within chat lines. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"\n\nNumeric values will become timeout buttons for that number of seconds. The text \"\" is a special value that will act like the normal Ban button in chat.\n\nTo assign a specific letter for use as the icon, specify it at the start of the command followed by an equals sign.\n\nExample: A=\"!reg add\"\n\nDefault: 600", old_val); + var new_val = prompt("Custom In-Line Moderation Icons\n\nPlease enter a list of commands to be made available as mod icons within chat lines. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"\n\nTo send multiple commands, separate them with \"\".\n\nNumeric values will become timeout buttons for that number of seconds. The text \"\" is a special value that will act like the normal Ban button in chat.\n\nTo assign a specific letter for use as the icon, specify it at the start of the command followed by an equals sign.\n\nExample: A=\"!reg add\"\n\nDefault: 600", old_val); if ( new_val === null || new_val === undefined ) return; @@ -253,8 +253,15 @@ FFZ.settings_info.mod_buttons = { } else had_prefix = true; - if ( typeof val === "string" && val.indexOf('{user}') === -1 ) - val += ' {user}'; + if ( typeof val === "string" ) { + // Split it up for this step. + var lines = val.split(/ * */); + for(var x=0; x < lines.length; x++) { + if ( lines[x].indexOf('{user}') === -1 ) + lines[x] += ' {user}'; + } + val = lines.join(""); + } final.push([prefix, val, had_prefix]); } @@ -475,7 +482,7 @@ FFZ.prototype.setup_mod_card = function() { if ( name ) { name.classList.add('ffz-alias'); name.title = utils.sanitize(controller.get('cardInfo.user.display_name') || user_id.capitalize()); - jQuery(name).tipsy(); + jQuery(name).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } } @@ -522,7 +529,7 @@ FFZ.prototype.setup_mod_card = function() { btn.innerHTML = utils.sanitize(title); btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}')); - jQuery(btn).tipsy(); + jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); btn.addEventListener('click', add_btn_click.bind(this, cmd)); return btn; }; @@ -601,7 +608,7 @@ FFZ.prototype.setup_mod_card = function() { else if ( f.settings.mod_card_hotkeys && timeout === 1 ) btn.title = "(P)urge - " + btn.title; - jQuery(btn).tipsy(); + jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); btn.addEventListener('click', btn_click.bind(this, timeout)); return btn; @@ -637,7 +644,7 @@ FFZ.prototype.setup_mod_card = function() { unban_btn.innerHTML = CHECK; unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User"; - jQuery(unban_btn).tipsy(); + jQuery(unban_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); unban_btn.addEventListener("click", btn_click.bind(this, -1)); jQuery(ban_btn).after(unban_btn); @@ -661,7 +668,7 @@ FFZ.prototype.setup_mod_card = function() { msg_btn.classList.add('message'); msg_btn.title = "Whisper User"; - jQuery(msg_btn).tipsy(); + jQuery(msg_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); var real_msg = document.createElement('button'); @@ -846,7 +853,7 @@ FFZ.prototype._build_mod_card_history = function(line) { // Interactivity 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}); + jQuery('.html-tooltip', l_el).tipsy({html:true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); return l_el; } diff --git a/src/ember/player.js b/src/ember/player.js index 67c8e888..245dadaf 100644 --- a/src/ember/player.js +++ b/src/ember/player.js @@ -61,7 +61,7 @@ FFZ.prototype.setup_player = function() { this.players = {}; - var Player2 = App && App.__container__.resolve('component:twitch-player2'); + var Player2 = window.App && App.__container__.resolve('component:twitch-player2'); if ( ! Player2 ) return this.log("Unable to find twitch-player2 component."); diff --git a/src/ember/room.js b/src/ember/room.js index 885366d7..d045f5c1 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -134,6 +134,15 @@ FFZ.prototype._modify_rview = function(view) { this._super(); }, + ffzAlternate: function() { + /*if ( ! this._ffz_chat_display ) { + var el = this.get('element'); + this._ffz_chat_display = el && el.querySelector('ul.chat-lines'); + } + + this._ffz_chat_display && this._ffz_chat_display.classList.toggle('ffz-should-alternate');*/ + }, + ffzInit: function() { f._roomv = this; @@ -179,6 +188,9 @@ FFZ.prototype._modify_rview = function(view) { if ( f._roomv === this ) f._roomv = undefined; + if ( this._ffz_chat_display ) + this._ffz_chat_display = undefined; + this.ffzDisableFreeze(); }, @@ -367,12 +379,12 @@ FFZ.prototype._modify_rview = function(view) { } }, - ffzUnfreeze: function() { + ffzUnfreeze: function(from_stuck) { this.ffz_frozen = false; this._ffz_last_move = 0; this.ffzUnwarnPaused(); - if ( this.get('stuckToBottom') ) + if ( ! from_stuck && this.get('stuckToBottom') ) this._scrollToBottom(); }, @@ -434,7 +446,7 @@ FFZ.prototype._modify_rview = function(view) { this.set("stuckToBottom", val); this.get("controller.model") && this.set("controller.model.messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150)); if ( ! val ) - this.ffzUnfreeze(); + this.ffzUnfreeze(true); }, // Warnings~! @@ -728,6 +740,7 @@ FFZ.prototype._insert_history = function(room_id, data) { tmiSession = r.tmiSession || (TMI._sessions && TMI._sessions[0]), tmiRoom = r.tmiRoom, + removed = 0, inserted = 0, purged = {}, @@ -828,10 +841,15 @@ FFZ.prototype._insert_history = function(room_id, data) { this.tokenize_chat_line(msg, true, r.get('roomProperties.hide_chat_links')); if ( r.shouldShowMessage(msg) ) { messages.insertAt(inserted, msg); - while( messages.length > r.get('messageBufferSize') ) + while( messages.length > r.get('messageBufferSize') ) { messages.removeAt(0); + removed++; + } } } + + if ( (removed % 2) && this._roomv && this._roomv.get('context.model.id') === room_id ) + this._roomv.ffzAlternate(); } @@ -995,7 +1013,8 @@ FFZ.prototype._modify_room = function(room) { var msgs = t.get('messages'), total = msgs.get('length'), - i = total; + i = total, + removed = 0; // Delete visible messages while(i--) { @@ -1004,6 +1023,7 @@ FFZ.prototype._modify_room = function(room) { if ( msg.from === user ) { if ( f.settings.remove_deleted ) { msgs.removeAt(i); + removed++; continue; } @@ -1013,6 +1033,9 @@ FFZ.prototype._modify_room = function(room) { } } + if ( (removed % 2) && f._roomv && f._roomv.get('context.model.id') === this.get('id') ) + f._roomv.ffzAlternate(); + // Delete pending messages if (t.ffzPending) { msgs = t.ffzPending; @@ -1064,8 +1087,11 @@ FFZ.prototype._modify_room = function(room) { len = messages.get("length"), limit = this.get("messageBufferSize"); - if ( len > limit ) + if ( len > limit ) { messages.removeAt(0, len - limit); + if ( ((len - limit) % 2) && f._roomv && f._roomv.get('context.model.id') === this.get('id') ) + f._roomv.ffzAlternate(); + } }, // Artificial chat delay diff --git a/src/emoticons.js b/src/emoticons.js index e7b7c9be..34bcea8a 100644 --- a/src/emoticons.js +++ b/src/emoticons.js @@ -4,46 +4,11 @@ var FFZ = window.FrankerFaceZ, constants = require('./constants'), utils = require('./utils'), - - /*check_margins = function(margins, height) { - var mlist = margins.split(/ +/); - if ( mlist.length != 2 ) - return margins; - - mlist[0] = parseFloat(mlist[0]); - mlist[1] = parseFloat(mlist[1]); - - if ( mlist[0] == (height - 18) / -2 && mlist[1] == 0 ) - return null; - - return margins; - }, - - - build_legacy_css = function(emote) { - var margin = emote.margins, srcset = ""; - if ( ! margin ) - margin = ((emote.height - 18) / -2) + "px 0"; - - if ( emote.urls[2] || emote.urls[4] ) { - srcset = 'url("' + emote.urls[1] + '") 1x'; - if ( emote.urls[2] ) - srcset += ', url("' + emote.urls[2] + '") 2x'; - if ( emote.urls[4] ) - srcset += ', url("' + emote.urls[4] + '") 4x'; - - srcset = '-webkit-image-set(' + srcset + '); image-set(' + srcset + ');'; - } - - return ".ffz-emote-" + emote.id + ' { background-image: url("' + emote.urls[1] + '"); height: ' + emote.height + "px; width: " + emote.width + "px; margin: " + margin + (srcset ? '; ' + srcset : '') + (emote.css ? "; " + emote.css : "") + "}\n"; - },*/ - - build_css = function(emote) { if ( ! emote.margins && ! emote.css ) - return ""; //build_legacy_css(emote); + return ""; - return /*build_legacy_css(emote) +*/ 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n"; + return 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n"; }, @@ -120,8 +85,15 @@ FFZ.prototype.setup_emoticons = function() { // Emote Usage // ------------------------ -FFZ.prototype.add_usage = function(room_id, emote_id, count) { - var rooms = this.emote_usage[emote_id] = this.emote_usage[emote_id] || {}; +FFZ.prototype.add_usage = function(room_id, emote, count) { + // Only report usage from FFZ emotes. Not extensions to FFZ. + var emote_set = this.emote_sets[emote.set_id]; + if ( ! emote_set || emote_set.source_ext ) + return; + + var emote_id = emote.id, + rooms = this.emote_usage[emote_id] = this.emote_usage[emote_id] || {}; + rooms[room_id] = (rooms[room_id] || 0) + (count || 1); if ( this._emote_report_scheduled ) @@ -260,12 +232,82 @@ FFZ.prototype._emote_tooltip = function(emote) { var set = this.emote_sets[emote.set_id], owner = emote.owner, title = set && set.title || "Global", - source = set && set.source || "FFZ"; + source = set && set.source || "FFZ", - emote._tooltip = "Emoticon: " + (emote.hidden ? "???" : emote.name) + "
" + source + " " + title + (owner ? "
By: " + owner.display_name : ""); + preview_url = this.settings.emote_image_hover ? (emote.urls[4] || emote.urls[2]) : null, + image = preview_url ? '' : ''; + + emote._tooltip = image + "Emoticon: " + (emote.hidden ? "???" : emote.name) + "
" + source + " " + title + (owner ? "
By: " + owner.display_name : ""); return emote._tooltip; } +FFZ.prototype._reset_tooltips = function(twitch_only) { + for(var emote_id in this._twitch_emotes) { + var data = this._twitch_emotes[emote_id]; + if ( data && data.tooltip ) + data.tooltip = null; + } + + if ( ! twitch_only ) { + for(var set_id in this.emote_sets) { + var emote_set = this.emote_sets[set_id]; + for(var emote_id in emote_set.emoticons) { + var emote = emote_set.emoticons[emote_id]; + if ( emote._tooltip ) + emote._tooltip = null; + } + } + } + + var emotes = document.querySelectorAll('img.emoticon'); + for(var i=0; i < emotes.length; i++) { + var emote = emotes[i]; + if ( emote.classList.contains('ffz-image-hover') ) + continue; + + var set_id, + emote_id = emote.getAttribute('data-emote'); + + if ( emote_id ) { + // Twitch Emotes + if ( this.has_bttv ) + continue; + + emote.setAttribute('original-title', utils.build_tooltip.bind(this)(emote_id, false, emote.alt)); + continue; + } + + if ( twitch_only ) + continue; + + // FFZ Emoji + emote_id = emote.getAttribute('data-ffz-emoji'); + if ( emote_id ) { + var emoji = this.emoji_data && this.emoji_data[emote_id], + setting = this.settings.parse_emoji, + + src = emoji ? (setting === 2 ? emoji.noto_src : emoji.tw_src) : null, + image = ''; + + if ( src && this.settings.emote_image_hover ) + image = ''; + + emote.setAttribute('original-title', emoji ? (image + 'Emoji: ' + emote.alt + '
Name: ' + emoji.name + (emoji.short_name ? "
Short Name: :" + emoji.short_name + ":" : "")) : emote.alt); + continue; + } + + // FFZ Emotes + emote_id = emote.getAttribute('data-ffz-emote'); + set_id = emote.getAttribute('data-ffz-set'); + + var emote_set = this.emote_sets[set_id]; + if ( ! emote_set || ! emote_set.emoticons || ! emote_set.emoticons[emote_id] ) + continue; + + emote.setAttribute('original-title', this._emote_tooltip(emote_set.emoticons[emote_id])); + } +} + // --------------------- // Emoji Loading diff --git a/src/ext/betterttv.js b/src/ext/betterttv.js index 06d2637c..8a421127 100644 --- a/src/ext/betterttv.js +++ b/src/ext/betterttv.js @@ -25,8 +25,6 @@ FFZ.prototype.setup_bttv = function(delay) { this.log("BetterTTV was detected after " + delay + "ms. Hooking."); this.has_bttv = true; - // this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString()); - // Disable Dark if it's enabled. document.body.classList.remove("ffz-dark"); if ( this._dark_style ) { @@ -224,14 +222,14 @@ FFZ.prototype.setup_bttv = function(delay) { // Why is emote parsing so bad? ;_; _.each(emotes, function(emote) { var tooltip = f._emote_tooltip(emote), - eo = [''], + eo = [''], old_tokens = tokens; tokens = []; for(var i=0; i < old_tokens.length; i++) { var token = old_tokens[i]; - if ( typeof token != "string" ) { + if ( typeof token !== "string" ) { tokens.push(token); continue; } @@ -248,7 +246,7 @@ FFZ.prototype.setup_bttv = function(delay) { tokens.push(eo); if ( mine && l_room ) - f.add_usage(l_room, emote.id); + f.add_usage(l_room, emote); } else tokens.push(bit); @@ -258,9 +256,10 @@ FFZ.prototype.setup_bttv = function(delay) { } // Sneak in Emojicon Processing - /* if ( f.settings.parse_emoji && f.emoji_data ) { - var old_tokens = tokens; + var old_tokens = tokens, + setting = f.settings.parse_emoji; + tokens = []; for(var i=0; i < old_tokens.length; i++) { @@ -280,20 +279,25 @@ FFZ.prototype.setup_bttv = function(delay) { variant = tbits.shift(); if ( variant === '\uFE0E' ) - bits.push(match); + tokens.push(match); else { var eid = utils.emoji_to_codepoint(match, variant), - data = f.emoji_data[eid]; + data = f.emoji_data[eid], + src = data && (setting === 2 ? data.noto_src : data.tw_src); - if ( data ) { - tokens.push(['' + alt + '']); + if ( data && src ) { + var image = src && f.settings.emote_image_hover ? '' : '', + tooltip = image + "Emoji: " + data.raw + "
Name: " + data.name + (data.short_name ? "
Short Name: :" + data.short_name + ":" : ""), + code = utils.quote_attr(data.raw); + + tokens.push(['' + code + '']); } else tokens.push(match + (variant || "")); } } } } - }*/ + } return tokens; } diff --git a/src/ext/rechat.js b/src/ext/rechat.js new file mode 100644 index 00000000..f024a7f8 --- /dev/null +++ b/src/ext/rechat.js @@ -0,0 +1,277 @@ +var FFZ = window.FrankerFaceZ, + constants = require('../constants'), + utils = require('../utils'); + + +// -------------------- +// Initialization +// -------------------- + +FFZ.prototype.setup_rechat = function() { + if ( this.has_bttv || navigator.userAgent.indexOf('Android') !== -1 ) + return; + + this._rechat_listening = false; + + this.log("Installing ReChat mutation observer."); + + var f = this; + this._rechat_observer = new MutationObserver(function(mutations) { + for(var i=0; i < mutations.length; i++) { + var mutation = mutations[i]; + if ( mutation.type !== "childList" ) + continue; + + for(var x=0; x < mutation.addedNodes.length; x++) { + var added = mutation.addedNodes[x]; + if ( added.nodeType !== added.ELEMENT_NODE || added.tagName !== "DIV" ) + continue; + + // Is this a ReChat line? + if ( added.classList.contains('rechat-chat-line') && ! added.classList.contains('ffz-processed') ) + f.process_rechat_line(added); + } + } + }); + + this.log("Starting ReChat check loop."); + this._rechat_interval = setInterval(this.find_rechat.bind(this), 1000); + this.find_rechat(); +} + + +// -------------------- +// ReChat Detection +// -------------------- + +FFZ.prototype.find_rechat = function() { + var el = !this.has_bttv ? document.querySelector('.rechat-chat-line') : null; + + // If there's no change, don't continue. + if ( !!el === this._rechat_listening ) + return; + + // If we're no longer listening, stop the observer and quit. + if ( ! el ) { + this._rechat_observer.disconnect(); + this._rechat_listening = false; + return; + } + + // We're newly listening. Process all existing ReChat chat lines + // and darken the container if required, also enable the observer. + var container = jQuery(el).parents('.chat-container'); + if ( ! container.length ) + return; + + container = container[0]; + + // Look-up dark mode. + var dark_chat = this.settings.dark_twitch; + if ( ! dark_chat ) { + var model = window.App ? App.__container__.lookup('controller:settings').get('model') : undefined; + dark_chat = model && model.get('darkMode'); + } + + container.classList.toggle('dark', dark_chat); + jQuery(container).find('.chat-lines').addClass('ffz-scrollbar'); + + // Tooltips + jQuery(container).find('.tooltip').tipsy({live: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); + jQuery(container).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')}); + + // Load the room data. + var room_id = el.getAttribute('data-room'); + if ( room_id && ! this.rooms[room_id] ) + this.load_room(room_id, this._reprocess_rechat.bind(this, container)); + + // Do stuff. + var lines = container.querySelectorAll('.rechat-chat-line'); + for(var i=0; i < lines.length; i++) { + var line = lines[i]; + if ( line.classList.contains('ffz-processed') ) + continue; + + this.process_rechat_line(line); + } + + // Start observing. + this._rechat_observer.observe(container, { + childList: true, + subtree: true + }); + + this._rechat_listening = true; +} + + +// -------------------- +// ReChat Lines +// -------------------- + +FFZ.prototype._reprocess_rechat = function(container) { + var lines = container.querySelectorAll('.rechat-chat-line'); + for(var i=0; i < lines.length; i++) + this.process_rechat_line(lines[i], true); +} + + +FFZ.prototype.process_rechat_line = function(line, reprocess) { + if ( ! reprocess && line.classList.contains('ffz-processed') ) + return; + + line.classList.add('ffz-processed'); + + var user_id = line.getAttribute('data-sender'), + room_id = line.getAttribute('data-room'), + + Layout = App.__container__.lookup('controller:layout'), + Settings = App.__container__.lookup('controller:settings'), + is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('model.darkMode')), + + badges_el = line.querySelector('.badges'), + from_el = line.querySelector('.from'), + message_el = line.querySelector('.message'), + + badges = {}, + had_badges = !!badges_el, + + raw_color = from_el && FFZ.Color.RGB.fromCSS(from_el.style.color), + colors = raw_color && this._handle_color(raw_color), + + alias = this.aliases[user_id]; + + + if ( ! badges_el ) { + badges_el = document.createElement('span'); + badges_el.className = 'badges float-left'; + line.insertBefore(badges_el, from_el || line.firstElementChild); + } + + if ( ! reprocess || ! had_badges ) { + // Read existing known badges. + var existing = badges_el.querySelectorAll('.badge'); + for(var i=0; i < existing.length; i++) { + var badge = existing[i]; + if ( badge.classList.contains('broadcaster') ) + badges[0] = {klass: 'broadcaster', title: 'Broadcaster'}; + else if ( badge.classList.contains('staff') ) + badges[0] = {klass: 'staff', title: 'Staff'}; + else if ( badge.classList.contains('admin') ) + badges[0] = {klass: 'admin', title: 'Admin'}; + else if ( badge.classList.contains('global-moderator') ) + badges[0] = {klass: 'global-moderator', title: 'Global Moderator'}; + else if ( badge.classList.contains('moderator') ) + badges[0] = {klass: 'moderator', title: 'Moderator'}; + else if ( badge.classList.contains('subscriber') ) + badges[10] = {klass: 'subscriber', title: 'Subscriber'}; + else if ( badge.classList.contains('turbo') ) + badges[15] = {klass: 'turbo', title: 'Turbo'}; + } + + if ( user_id && user_id === room_id ) + badges[0] = {klass: 'broadcaster', title: 'Broadcaster'}; + + if ( user_id ) + badges = this._render_badges(user_id, room_id, badges); + + var output = ''; + for(var key in badges) { + var badge = badges[key], + css = badge.iamge ? 'background-image:url("' + badge.image + '");' : ''; + + if ( badge.color ) + css += 'background-color:' + badge.color + ';'; + + if ( badge.extra_css ) + css += badge.extra_css; + + output += '
'; + } + + badges_el.innerHTML = output; + } + + if ( ! reprocess && from_el ) { + from_el.style.fontWeight = ""; + if ( colors ) { + from_el.classList.add('has_color'); + from_el.style.color = is_dark ? colors[1] : colors[0]; + } + + if ( alias ) { + from_el.classList.add('ffz-alias'); + from_el.title = from_el.textContent; + from_el.textContent = alias; + } + } + + if ( ! message_el ) + return; + + if ( ! reprocess && message_el.style.color ) { + message_el.classList.add('has-color'); + message_el.style.color = is_dark ? colors[1] : colors[0]; + } + + var raw_tokens = line.getAttribute('data-tokens'), + tokens = raw_tokens ? JSON.parse(raw_tokens) : []; + + if ( ! raw_tokens ) { + for(var i=0; i < message_el.childNodes.length; i++) { + var node = message_el.childNodes[i]; + + if ( node.nodeType === node.TEXT_NODE ) + tokens.push(node.textContent); + + else if ( node.nodeType === node.ELEMENT_NODE ) { + if ( node.tagName === 'IMG' ) + tokens.push({ + altText: node.alt, + emoticonSrc: node.src + }); + + else if ( node.tagName === 'A' ) + tokens.push({ + isLink: true, + href: node.textContent + }); + + else if ( node.tagName === 'SPAN' ) + tokens.push({ + mentionedUser: node.textContent, + own: node.classList.contains('mentioning') + }); + + else + this.log("Unknown Tag Type: " + node.tagName); + } else + this.log("Unknown Node Type Tokenizing Message: " + node.nodeType); + } + } + + line.setAttribute('data-tokens', JSON.stringify(tokens)); + + // Further tokenization~! + if ( this.settings.replace_bad_emotes ) + tokens = this.tokenize_replace_emotes(tokens); + + tokens = this._remove_banned(tokens); + tokens = this.tokenize_emotes(user_id, room_id, tokens, false); + + if ( this.settings.parse_emoji ) + tokens = this.tokenize_emoji(tokens); + + tokens = this.tokenize_mentions(tokens); + + // Check for a mention + if ( ! line.classList.contains('ffz-mentioend') ) + for(var i=0; i < tokens.length; i++) + if ( tokens[i].mentionedUser ) { + line.classList.add('ffz-mentioned'); + break; + } + + // Now, put the content back into the element. + message_el.innerHTML = this.render_tokens(tokens); +} \ No newline at end of file diff --git a/src/main.js b/src/main.js index ed9f7ae0..666649d3 100644 --- a/src/main.js +++ b/src/main.js @@ -22,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; } // Version var VER = FFZ.version_info = { - major: 3, minor: 5, revision: 65, + major: 3, minor: 5, revision: 77, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } @@ -90,12 +90,22 @@ FFZ.prototype._pastebin = function(data, callback) { // ------------------- FFZ.prototype.get_user = function() { - if ( window.PP && PP.login ) { - return PP; - } else if ( window.App ) { - var nc = App.__container__.lookup("controller:login"); - return nc ? nc.get("userData") : undefined; + if ( this.__user ) + return this.__user; + + var user; + if ( window.App ) { + var nc = App.__container__.lookup('controller:login'); + user = nc ? nc.get('userData') : undefined; } + + if ( ! user && window.PP && PP.login ) + user = PP; + + if ( user ) + this.__user = user; + + return user; } @@ -133,6 +143,7 @@ require('./ember/following'); require('./debug'); +require('./ext/rechat'); require('./ext/betterttv'); require('./ext/emote_menu'); @@ -140,6 +151,7 @@ require('./featurefriday'); require('./ui/styles'); require('./ui/dark'); +require('./ui/tooltips'); require('./ui/notifications'); require('./ui/viewer_count'); require('./ui/sub_count'); @@ -256,6 +268,7 @@ FFZ.prototype.init_normal = function(delay, no_socket) { this.setup_following_count(false); this.setup_menu(); + this.fix_tooltips(); this.find_bttv(10); var end = (window.performance && performance.now) ? performance.now() : Date.now(), @@ -297,6 +310,7 @@ FFZ.prototype.init_dashboard = function(delay) { // Set up the FFZ message passer. this.setup_message_event(); + this.fix_tooltips(); this.find_bttv(10); var end = (window.performance && performance.now) ? performance.now() : Date.now(), @@ -354,8 +368,10 @@ FFZ.prototype.init_ember = function(delay) { this.setup_following_count(true); this.setup_races(); + this.fix_tooltips(); this.connect_extra_chat(); + this.setup_rechat(); this.find_bttv(10); this.find_emote_menu(10); diff --git a/src/styles/chat-background.css b/src/styles/chat-background.css index 1b6246bc..8f162569 100644 --- a/src/styles/chat-background.css +++ b/src/styles/chat-background.css @@ -1,7 +1,10 @@ /* Regular Alternating Background */ .conversation-chat-lines > div:nth-child(2n+0):before, .chat-history .chat-line:nth-child(2n+0):before, -.ember-chat .chat-lines > div:nth-child(2n+0) .chat-line:before { +.ember-chat .chat-lines > div:nth-child(2n+0) .chat-line:before, + +/* ReChat */ +.ember-chat.chat-messages > .rechat-chat-line:nth-child(2n+0):before { background-color: rgba(0,0,0, 0.1); } @@ -17,7 +20,12 @@ .dark .chat-history .chat-line:nth-child(2n+0):before, .force-dark .chat-history .chat-line:nth-child(2n+0):before, .dark .chat-lines > div:nth-child(2n+0) .chat-line:before, -.force-dark .chat-lines > div:nth-child(2n+0) .chat-line:before { +.force-dark .chat-lines > div:nth-child(2n+0) .chat-line:before, + +/* ReChat */ +.theatre .chat-lines > .rechat-chat-line:nth-child(2n+0):before, +.dark .chat-lines > .rechat-chat-line:nth-child(2n+0):before, +.force-dark .chat-lines > .rechat-chat-line:nth-child(2n+0):before { background-color: rgba(255,255,255, 0.05); } diff --git a/src/styles/chat-hc-background.css b/src/styles/chat-hc-background.css index 71ff193a..87096e93 100644 --- a/src/styles/chat-hc-background.css +++ b/src/styles/chat-hc-background.css @@ -1,7 +1,7 @@ /* High-Contrast Background */ .chat-container, .ember-chat-container { - background-color: #fff; + background-color: #fff !important; } @@ -12,5 +12,5 @@ .chat-container.force-dark, .ember-chat-container.dark, .ember-chat-container.force-dark { - background-color: #000; + background-color: #000 !important; } \ No newline at end of file diff --git a/src/styles/chat-hc-text.css b/src/styles/chat-hc-text.css index 687bd48c..867b0e6e 100644 --- a/src/styles/chat-hc-text.css +++ b/src/styles/chat-hc-text.css @@ -1,7 +1,7 @@ /* High-Contrast Text */ .chat-container, .ember-chat-container { - color: #000; + color: #000 !important; } /* Dark: High-Contrast Text */ @@ -10,6 +10,10 @@ .chat-container.dark, .chat-container.force-dark, .ember-chat-container.dark, -.ember-chat-container.force-dark { - color: #fff; +.ember-chat-container.force-dark, + +.ffz-dark .ember-chat-container.dark .chat-line, +.ffz-dark .chat-container.dark .chat-line + { + color: #fff !important; } \ No newline at end of file diff --git a/src/tokenize.js b/src/tokenize.js index 1f3d1d51..bd877874 100644 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -1,76 +1,11 @@ var FFZ = window.FrankerFaceZ, utils = require("./utils"), constants = require("./constants"), - TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/", helpers, + conv_helpers, EXPLANATION_TRAIL = '
FFZ is hiding this link because this url shortener is known to be used by Twitch spam bots posting malicious links. Please use caution when visiting shortened links.', - SRCSETS = {}; - build_srcset = function(id) { - if ( SRCSETS[id] ) - return SRCSETS[id]; - var out = SRCSETS[id] = TWITCH_BASE + id + "/1.0 1x, " + TWITCH_BASE + id + "/2.0 2x, " + TWITCH_BASE + id + "/3.0 4x"; - return out; - }, - - - data_to_tooltip = function(data) { - var set = data.set, - set_type = data.set_type, - owner = data.owner; - - if ( set_type === undefined ) - set_type = "Channel"; - - if ( ! set ) - return data.code; - - else if ( set === "--global--" ) { - set = "Twitch Global"; - set_type = null; - - } else if ( set == "--twitch-turbo--" || set == "turbo" || set == "--turbo-faces--" ) { - set = "Twitch Turbo"; - set_type = null; - } - - return "Emoticon: " + data.code + "
" + (set_type ? set_type + ": " : "") + set + (owner ? "
By: " + owner.display_name : ""); - }, - - build_tooltip = function(id) { - var emote_data = this._twitch_emotes[id], - set = emote_data ? emote_data.set : null; - - if ( ! emote_data ) - return "???"; - - if ( typeof emote_data == "string" ) - return emote_data; - - if ( emote_data.tooltip ) - return emote_data.tooltip; - - return emote_data.tooltip = data_to_tooltip(emote_data); - }, - - load_emote_data = function(id, code, success, data) { - if ( ! success ) - return code; - - if ( code ) - data.code = code; - - this._twitch_emotes[id] = data; - var tooltip = build_tooltip.bind(this)(id); - - var images = document.querySelectorAll('img[data-emote="' + id + '"]'); - for(var x=0; x < images.length; x++) - images[x].title = tooltip; - - return tooltip; - }, - reg_escape = function(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); @@ -105,7 +40,7 @@ var FFZ = window.FrankerFaceZ, return IMGUR_PATH.test(path); return any_domain ? IMAGE_EXT.test(path) : IMAGE_DOMAINS.indexOf(domain) !== -1; - } + }, image_iframe = function(href, extra_class) { return ''; @@ -248,12 +183,12 @@ FFZ._emote_mirror_swap = function(img) { img.setAttribute('data-alt-attempts', attempts + 1); var id = img.getAttribute('data-emote'); - if ( img.src.substr(0, TWITCH_BASE.length) === TWITCH_BASE ) { + if ( img.src.substr(0, constants.TWITCH_BASE.length) === constants.TWITCH_BASE ) { img.src = constants.EMOTE_MIRROR_BASE + id + ".png"; img.srcset = ""; } else { - img.src = TWITCH_BASE + id + "/1.0"; - img.srcset = build_srcset(id); + img.src = constants.TWITCH_BASE + id + "/1.0"; + img.srcset = utils.build_srcset(id); } } @@ -317,6 +252,10 @@ FFZ.prototype.setup_tokenization = function() { if ( ! helpers ) return this.log("Unable to get chat helper functions."); + conv_helpers = window.require && window.require("ember-twitch-conversations/helpers/conversation-line-helpers"); + if ( ! conv_helpers ) + this.log("Unable to get conversation helper functions."); + this.log("Hooking Ember chat line helpers."); var f = this; @@ -386,6 +325,8 @@ FFZ.prototype.load_twitch_emote_data = function(tries) { this._twitch_set_to_channel[33] = "--turbo-faces--"; this._twitch_set_to_channel[42] = "--turbo-faces--"; + this._reset_tooltips(true); + }).fail(function(data) { if ( data.status === 404 ) return; @@ -410,6 +351,9 @@ FFZ.prototype.tokenize_conversation_line = function(message, prevent_notificatio emotes = message.get('tags.emotes'), tokens = [msg]; + if ( conv_helpers && conv_helpers.checkActionMessage ) + tokens = conv_helpers.checkActionMessage(tokens); + // Standard Tokenization if ( helpers && helpers.linkifyMessage ) tokens = helpers.linkifyMessage(tokens); @@ -597,8 +541,11 @@ FFZ.prototype.tokenize_line = function(user, room, message, no_emotes, no_emoji) FFZ.prototype.render_tokens = function(tokens, render_links) { var f = this; return _.map(tokens, function(token) { + if ( token.hidden ) + return ""; + if ( token.emoticonSrc ) { - var tooltip, src = token.emoticonSrc, srcset, extra; + var tooltip, src = token.emoticonSrc, srcset, cls, extra; if ( token.ffzEmote ) { var emote_set = f.emote_sets && f.emote_sets[token.ffzEmoteSet], emote = emote_set && emote_set.emoticons && emote_set.emoticons[token.ffzEmote]; @@ -610,26 +557,32 @@ FFZ.prototype.render_tokens = function(tokens, render_links) { } else if ( token.ffzEmoji ) { var eid = token.ffzEmoji, emoji = f.emoji_data && f.emoji_data[eid], - setting = f.settings.parse_emoji; + setting = f.settings.parse_emoji, + image = ''; if ( setting === 0 || (setting === 1 && ! emoji.tw) || (setting === 2 && ! emoji.noto) ) return token.altText; - tooltip = emoji ? "Emoji: " + token.altText + "\nName: " + emoji.name + (emoji.short_name ? "\nShort Name: :" + emoji.short_name + ":" : "") : token.altText; - extra = ' data-ffz-emoji="' + eid + '" height="18px"'; src = setting === 2 ? token.noto_src : token.tw_src; + if ( emoji && f.settings.emote_image_hover ) + image = ''; + + tooltip = emoji ? image + "Emoji: " + token.altText + "
Name: " + emoji.name + (emoji.short_name ? "
Short Name: :" + emoji.short_name + ":" : "") : token.altText; + extra = ' data-ffz-emoji="' + eid + '" height="18px"'; + cls = ' emoji'; + } else { var id = token.replacedId || FFZ.src_to_id(token.emoticonSrc), data = id && f._twitch_emotes && f._twitch_emotes[id]; if ( data ) - tooltip = data.tooltip ? data.tooltip : token.altText; + tooltip = data.tooltip ? data.tooltip : utils.build_tooltip.bind(f)(id, false, token.altText); else { try { var set_id = f._twitch_emote_to_set[id]; if ( set_id ) { - tooltip = load_emote_data.bind(f)(id, token.altText, true, { + tooltip = utils.load_emote_data.bind(f)(id, token.altText, true, { code: token.altText, id: id, set: f._twitch_set_to_channel[set_id], @@ -637,19 +590,18 @@ FFZ.prototype.render_tokens = function(tokens, render_links) { }); } else { tooltip = f._twitch_emotes[id] = token.altText; - f.ws_send("twitch_emote", id, load_emote_data.bind(f, id, token.altText)); + f.ws_send("twitch_emote", id, utils.load_emote_data.bind(f, id, token.altText)); } } catch(err) { f.error("Error Generating Emote Tooltip: " + err); } } - var mirror_url = utils.quote_attr(constants.EMOTE_MIRROR_BASE + id + '.png'); - + //var mirror_url = utils.quote_attr(constants.EMOTE_MIRROR_BASE + id + '.png'); extra = ' data-emote="' + id + '"'; // onerror="FrankerFaceZ._emote_mirror_swap(this)"'; // Disable error checking for now. if ( ! constants.EMOTE_REPLACEMENTS[id] ) - srcset = build_srcset(id); + srcset = utils.build_srcset(id); } return ''; @@ -797,7 +749,7 @@ FFZ.prototype.tokenize_title_emotes = function(tokens) { data = data.emoticon_sets[0]; for(var i=0; i < data.length; i++) { var em = data[i]; - emotes.push({regex: em.code, url: TWITCH_BASE + em.id + "/1.0"}); + emotes.push({regex: em.code, url: utils.TWITCH_BASE + em.id + "/1.0"}); } if ( f._cindex ) @@ -893,7 +845,7 @@ FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) { bits.push(eo); if ( do_report && room ) - f.add_usage(room, emote.id); + f.add_usage(room, emote); } else bits.push(bit); diff --git a/src/ui/dark.js b/src/ui/dark.js index 3c7f5260..3a515933 100644 --- a/src/ui/dark.js +++ b/src/ui/dark.js @@ -144,6 +144,9 @@ FFZ.settings_info.dark_twitch = { model && model.set('darkMode', true); } else model && model.set('darkMode', this.settings.twitch_chat_dark); + + // Try coloring ReChat + jQuery('.rechat-chat-line').parents('.chat-container').toggleClass('dark', val || this.settings.twitch_chat_dark); } }; diff --git a/src/ui/following-count.js b/src/ui/following-count.js index c4ec99be..43da8b50 100644 --- a/src/ui/following-count.js +++ b/src/ui/following-count.js @@ -52,7 +52,7 @@ FFZ.prototype.setup_following_count = function(has_ember) { // If we don't have Ember, no point in trying this stuff. if ( ! has_ember ) - return this._update_following_count(); + return this._following_get_me(); this.log("Connecting to Live Streams model."); var Stream = window.App && App.__container__.resolve('model:stream'); @@ -80,6 +80,27 @@ FFZ.prototype.setup_following_count = function(has_ember) { } +FFZ.prototype._following_get_me = function(tries) { + // get_user doesn't properly return an oauth token any longer, so we need to get me manually. + if ( ! window.Twitch ) + // Wait around till the API shows up. + return setTimeout(this._following_get_me.bind(this, tries), Math.floor(2000*Math.random()) + 500); + + var f = this; + Twitch.api.get("/api/me").done(function(data) { + f.log("Fetched User Data -- " + (data.name || data.login)); + f.__user = data; + f._update_following_count(); + + }).fail(function() { + tries = (tries||0) + 1; + if ( tries < 5 ) + return setTimeout(f._following_get_me.bind(f, tries), Math.floor(2000*Math.random()) + 500); + f.log("Failed to get proper user object."); + }); +} + + FFZ.prototype._schedule_following_count = function() { if ( ! this.settings.following_count ) { if ( this._following_count_timer ) { @@ -111,8 +132,13 @@ FFZ.prototype._update_following_count = function() { if ( Live ) Live.load(); - else - Twitch.api && Twitch.api.get("streams/followed", {limit:5, offset:0}, {version:3}) + else { + var a = {}, + u = this.get_user(); + + a.Authorization = "OAuth " + u.chat_oauth_token; + + Twitch.api && Twitch.api.get("streams/followed", {limit:20, offset:0}, {version:3, headers: a}) .done(function(data) { f._draw_following_count(data._total); f._draw_following_channels(data.streams, data._total); @@ -120,6 +146,7 @@ FFZ.prototype._update_following_count = function() { f._draw_following_count(); f._draw_following_channels(); }) + } } diff --git a/src/ui/menu.js b/src/ui/menu.js index ff29cf0d..9a38f083 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -2,8 +2,6 @@ var FFZ = window.FrankerFaceZ, constants = require('../constants'), utils = require('../utils'), - TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/", - fix_menu_position = function(container) { var swapped = document.body.classList.contains('ffz-sidebar-swap') && ! document.body.classList.contains('ffz-portrait'); @@ -63,7 +61,8 @@ FFZ.prototype.setup_menu = function() { this.log("Hooking the Ember Chat Settings view."); - var Settings = window.App && App.__container__.resolve('view:settings'); + var Settings = window.App && App.__container__.resolve('view:settings'), + Layout = App.__container__.lookup('controller:layout'); if ( ! Settings ) return; @@ -155,6 +154,12 @@ FFZ.prototype.setup_menu = function() { menu.appendChild(header); menu.appendChild(content); + + // Maximum Height + var e = el.querySelector('.chat-settings'); + if ( Layout && e ) + e.style.maxHeight = (Layout.get('windowHeight') - 90) + 'px'; + }, ffzTeardown: function() { @@ -162,6 +167,15 @@ FFZ.prototype.setup_menu = function() { } }); + // Maximum height~! + if ( Layout ) + Layout.addObserver('windowHeight', function() { + var el = document.querySelector('.ember-chat .chat-settings'); + if ( el ) + el.style.maxHeight = (Layout.get('windowHeight') - 90) + 'px'; + }); + + // For some reason, this doesn't work unless we create an instance of the // chat settings view and then destroy it immediately. try { @@ -225,7 +239,7 @@ FFZ.prototype.build_ui_popup = function(view) { container.classList.toggle('dark', dark); // Stuff - jQuery(inner).find('.html-tooltip').tipsy({live: true, html: true, gravity: jQuery.fn.tipsy.autoNS}); + jQuery(inner).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')}); // Menu Container @@ -316,7 +330,7 @@ FFZ.prototype.build_ui_popup = function(view) { link.title = page.name; link.innerHTML = page.icon; - jQuery(link).tipsy(); + jQuery(link).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); link.addEventListener("click", this._ui_change_page.bind(this, view, inner, menu, sub_container, key)); @@ -438,11 +452,11 @@ FFZ.menu_pages.channel = { var s = document.createElement('span'), can_use = is_subscribed || !emote.subscriber_only, - img_set = 'image-set(url("' + TWITCH_BASE + emote.id + '/1.0") 1x, url("' + TWITCH_BASE + emote.id + '/2.0") 2x, url("' + TWITCH_BASE + emote.id + '/3.0") 4x)'; + img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x), url("' + constants.TWITCH_BASE + emote.id + '/3.0") 4x)'; - s.className = 'emoticon tooltip' + (!can_use ? " locked" : ""); + s.className = 'emoticon html-tooltip' + (!can_use ? " locked" : ""); - s.style.backgroundImage = 'url("' + TWITCH_BASE + emote.id + '/1.0")'; + s.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")'; s.style.backgroundImage = '-webkit-' + img_set; s.style.backgroundImage = '-moz-' + img_set; s.style.backgroundImage = '-ms-' + img_set; @@ -450,7 +464,7 @@ FFZ.menu_pages.channel = { s.style.width = emote.width + "px"; s.style.height = emote.height + "px"; - s.title = emote.regex; + s.title = (this.settings.emote_image_hover ? '' : '') + emote.regex; s.addEventListener('click', function(can_use, id, code, e) { if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) diff --git a/src/ui/my_emotes.js b/src/ui/my_emotes.js index 9d12f761..36954713 100644 --- a/src/ui/my_emotes.js +++ b/src/ui/my_emotes.js @@ -2,7 +2,6 @@ var FFZ = window.FrankerFaceZ, constants = require("../constants"), utils = require("../utils"), - TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/", BANNED_SETS = {"00000turbo":true}; @@ -166,11 +165,14 @@ FFZ.menu_pages.myemotes = { if ( (settings === 1 && ! emoji.tw) || (settings === 2 && ! emoji.noto) ) continue; - em.className = 'emoticon html-tooltip'; - em.title = 'Emoji: ' + emoji.raw + '\nName: ' + emoji.name + (emoji.short_name ? '\nShort Name: :' + emoji.short_name + ':' : ''); + var src = settings === 2 ? emoji.noto_src : emoji.tw_src, + image = this.settings.emote_image_hover ? '' : ''; + + em.className = 'emoticon emoji html-tooltip'; + em.title = image + 'Emoji: ' + emoji.raw + '
Name: ' + emoji.name + (emoji.short_name ? '
Short Name: :' + emoji.short_name + ':' : ''); em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw)); - em.style.backgroundImage = 'url("' + (settings === 2 ? emoji.noto_src : emoji.tw_src) + '")'; + em.style.backgroundImage = 'url("' + src + '")'; em.style.backgroundSize = "18px"; menu.appendChild(em); @@ -237,21 +239,21 @@ FFZ.menu_pages.myemotes = { code = constants.KNOWN_CODES[emote.code] || emote.code, em = document.createElement('span'), - img_set = 'image-set(url("' + TWITCH_BASE + emote.id + '/1.0") 1x, url("' + TWITCH_BASE + emote.id + '/2.0") 2x, url("' + TWITCH_BASE + emote.id + '/3.0") 4x)'; + img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x, url("' + constants.TWITCH_BASE + emote.id + '/3.0") 4x)'; em.className = 'emoticon html-tooltip'; if ( this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[emote.id] ) { em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")'; } else { - em.style.backgroundImage = 'url("' + TWITCH_BASE + emote.id + '/1.0")'; + em.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")'; em.style.backgroundImage = '-webkit-' + img_set; em.style.backgroundImage = '-moz-' + img_set; em.style.backgroundImage = '-ms-' + img_set; em.style.backgroudnImage = img_set; } - em.title = code; + em.title = (this.settings.emote_image_hover ? '' : '') + code; em.addEventListener("click", function(id, c, e) { e.preventDefault(); if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) diff --git a/src/ui/sub_count.js b/src/ui/sub_count.js index e8c85976..e2be80a5 100644 --- a/src/ui/sub_count.js +++ b/src/ui/sub_count.js @@ -84,7 +84,7 @@ FFZ.prototype._update_subscribers = function() { }); cont.appendChild(stat); - jQuery(stat).tipsy(f.is_dashboard ? {"gravity":"s"} : undefined); + jQuery(stat).tipsy({gravity: f.is_dashboard ? "s" : utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } el.innerHTML = sub_count; diff --git a/src/ui/tooltips.js b/src/ui/tooltips.js new file mode 100644 index 00000000..5e2c27b0 --- /dev/null +++ b/src/ui/tooltips.js @@ -0,0 +1,38 @@ +var FFZ = window.FrankerFaceZ, + utils = require('../utils'), + constants = require('../constants'); + + +// --------------------- +// Initialization +// --------------------- + +FFZ.prototype.fix_tooltips = function() { + // First, override the tooltip mixin. + var TipsyTooltip = window.App && App.__container__.resolve('component:tipsy-tooltip'); + if ( TipsyTooltip ) { + this.log("Modifying Tipsy-Tooltip component to use gravity."); + TipsyTooltip.reopen({ + didInsertElement: function() { + var gravity = this.get("gravity"); + if ( ! gravity || typeof gravity === "string" ) + gravity = utils.tooltip_placement(constants.TOOLTIP_DISTANCE, gravity || 's'); + + this.$().tipsy({ + gravity: gravity + }); + } + }) + } + + // Iterate all existing tipsy stuff~! + this.log('Fixing already existing tooltips.'); + if ( ! window.jQuery || ! jQuery.cache ) + return; + + for(var obj_id in jQuery.cache) { + var obj = jQuery.cache[obj_id]; + if ( obj && obj.data && obj.data.tipsy && obj.data.tipsy.options && typeof obj.data.tipsy.options.gravity !== "function" ) + obj.data.tipsy.options.gravity = utils.tooltip_placement(constants.TOOLTIP_DISTANCE, obj.data.tipsy.options.gravity || 's'); + } +} \ No newline at end of file diff --git a/src/ui/viewer_count.js b/src/ui/viewer_count.js index 98d75edb..4227b078 100644 --- a/src/ui/viewer_count.js +++ b/src/ui/viewer_count.js @@ -66,6 +66,6 @@ FFZ.ws_commands.viewers = function(data) { view_count.innerHTML = content; parent.appendChild(view_count); - jQuery(view_count).tipsy(this.is_dashboard ? {"gravity":"s"} : undefined); + jQuery(view_count).tipsy({gravity: this.is_dashboard ? "s" : utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')}); } } \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index b1cce141..364e8d05 100644 --- a/src/utils.js +++ b/src/utils.js @@ -182,10 +182,98 @@ var sanitize_el = document.createElement('span'), out = es[variant] = r.join("-"); return out; + }, + + // Twitch Emote Tooltips + + SRCSETS = {}, + build_srcset = function(id) { + if ( SRCSETS[id] ) + return SRCSETS[id]; + var out = SRCSETS[id] = constants.TWITCH_BASE + id + "/1.0 1x, " + constants.TWITCH_BASE + id + "/2.0 2x, " + constants.TWITCH_BASE + id + "/3.0 4x"; + return out; + }, + + + data_to_tooltip = function(data) { + var emote_set = data.set, + set_type = data.set_type, + + f = FFZ.get(), + image = ''; + + if ( data.id && f.settings.emote_image_hover ) + image = ''; + + if ( set_type === undefined ) + set_type = "Channel"; + + if ( ! emote_set ) + return image + data.code; + + else if ( emote_set === "--global--" ) { + emote_set = "Twitch Global"; + set_type = null; + + } else if ( emote_set == "--twitch-turbo--" || emote_set == "turbo" || emote_set == "--turbo-faces--" ) { + emote_set = "Twitch Turbo"; + set_type = null; + } + + return image + "Emoticon: " + data.code + "
" + (set_type ? set_type + ": " : "") + emote_set; + }, + + build_tooltip = function(id, force_update, code) { + var emote_data = this._twitch_emotes[id]; + + if ( ! emote_data && code ) { + var set_id = this._twitch_emote_to_set[id]; + if ( set_id ) { + emote_data = this._twitch_emotes[id] = { + code: code, + id: id, + set: this._twitch_set_to_channel[set_id], + set_id: set_id + } + } + } + + if ( ! emote_data ) + return "???"; + + if ( typeof emote_data == "string" ) + return emote_data; + + if ( ! force_update && emote_data.tooltip ) + return emote_data.tooltip; + + return emote_data.tooltip = data_to_tooltip(emote_data); + }, + + load_emote_data = function(id, code, success, data) { + if ( ! success ) + return code; + + if ( code ) + data.code = code; + + this._twitch_emotes[id] = data; + var tooltip = build_tooltip.bind(this)(id); + + var images = document.querySelectorAll('img[data-emote="' + id + '"]'); + for(var x=0; x < images.length; x++) + images[x].title = tooltip; + + return tooltip; }; module.exports = { + build_srcset: build_srcset, + build_tooltip: build_tooltip, + load_emote_data: load_emote_data, + + update_css: function(element, id, css) { var all = element.innerHTML, start = "/*BEGIN " + id + "*/", @@ -207,6 +295,25 @@ module.exports = { }, + tooltip_placement: function(margin, prefer) { + return function() { + var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)}, + $this = $(this), + half_width = $this.width() / 2, + half_height = $this.height() / 2, + boundTop = $(document).scrollTop() + half_height + (margin*2), + boundLeft = $(document).scrollLeft() + half_width + margin; + + if ($this.offset().top < boundTop) dir.ns = 'n'; + if ($this.offset().left < boundLeft) dir.ew = 'w'; + if ($(window).width() + $(document).scrollLeft() - ($this.offset().left + half_width) < margin) dir.ew = 'e'; + if ($(window).height() + $(document).scrollTop() - ($this.offset().top + half_height) < (2*margin)) dir.ns = 's'; + + return dir.ns + (dir.ew ? dir.ew : ''); + } + }, + + splitIRCMessage: splitIRCMessage, parseIRCTags: parseIRCTags, diff --git a/style.css b/style.css index 0a3af431..651bf347 100644 --- a/style.css +++ b/style.css @@ -21,20 +21,20 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; } .ffz-hide-recent-past-broadcast .recent-past-broadcast, .ffz-hide-view-count .stat.twitch-channel-views, -.ffz-minimal-chat-input .emoticon-selector-toggle, -.ffz-menu-replace .emoticon-selector-toggle { +.ffz-minimal-chat-input .chat-interface .emoticon-selector-toggle, +.ffz-menu-replace .chat-interface .emoticon-selector-toggle { display: none !important; } -body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .emoticon-selector-toggle + .ffz-ui-toggle svg, -body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .emoticon-selector-toggle + script + .ffz-ui-toggle svg +body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emoticon-selector-toggle + .ffz-ui-toggle svg, +body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emoticon-selector-toggle + script + .ffz-ui-toggle svg { height: 14px; width: 18px; } -body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .emoticon-selector-toggle + .ffz-ui-toggle, -body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .emoticon-selector-toggle + script + .ffz-ui-toggle { +body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emoticon-selector-toggle + .ffz-ui-toggle, +body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emoticon-selector-toggle + script + .ffz-ui-toggle { height: 14px; width: 18px; top: 28px; @@ -263,6 +263,11 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .emoticon-selector-togg margin-bottom: 30px; } +.ffz-theater-stats .app-main.theatre #channel .player-column:focus .archive_info + .stats-and-actions, +.ffz-theater-stats .app-main.theatre #channel .player-column:hover .archive_info + .stats-and-actions { + display: none; +} + .ffz-theater-stats .app-main.theatre .player-column:focus #hostmode > div.clearfix, .ffz-theater-stats .app-main.theatre .player-column:hover #hostmode > div.clearfix, .ffz-theater-stats .app-main.theatre .player-column:focus .stats-and-actions, @@ -898,8 +903,17 @@ span.ffz-handle:after { left: 8px } .ffz-ui-popup.dark .ffz-ui-menu-page { background-color: #1e1e1e; } +/* Positioning Fixes */ + +body:not(.ffz-bttv) .ember-chat .chat-settings { bottom: 40px; } +body:not(.ffz-bttv) .notification-controls .notify-menu { bottom: 25px; } +body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; } + + /* Menu Scrollbar */ +.ffz-scrollbar::-webkit-scrollbar, +.ember-chat .chat-settings::-webkit-scrollbar, .conversations-list .conversations-list-inner::-webkit-scrollbar, .conversation-window .conversation-content::-webkit-scrollbar, .chat-history::-webkit-scrollbar, @@ -910,6 +924,8 @@ span.ffz-handle:after { left: 8px } width: 6px; } +.ffz-scrollbar::-webkit-scrollbar-thumb, +.ember-chat .chat-settings::-webkit-scrollbar-thumb, .conversations-list .conversations-list-inner::-webkit-scrollbar-thumb, .conversation-window .conversation-content::-webkit-scrollbar-thumb, .chat-history::-webkit-scrollbar-thumb, @@ -922,10 +938,14 @@ span.ffz-handle:after { left: 8px } box-shadow: 0 0 1px 1px rgba(255,255,255,0.25); } +.ffz-dark .ffz-scrollbar::-webkit-scrollbar-thumb, .ffz-dark .table::-webkit-scrollbar-thumb, .ffz-dark .conversation-window .conversation-content::-webkit-scrollbar-thumb, .ffz-dark .conversations-list .conversations-list-inner::-webkit-scrollbar-thumb, +.ffz-dark .conversation-input-bar .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, +.theatre .ffz-scrollbar::-webkit-scrollbar-thumb, +.theatre .ember-chat .chat-settings::-webkit-scrollbar-thumb, .theatre .conversation-window .conversation-content::-webkit-scrollbar-thumb, .theatre .conversations-list .conversations-list-inner::-webkit-scrollbar-thumb, @@ -934,11 +954,15 @@ span.ffz-handle:after { left: 8px } .theatre .ffz-ui-menu-page::-webkit-scrollbar-thumb, .theatre .ffz-ui-sub-menu-page::-webkit-scrollbar-thumb +.dark .ffz-scrollbar::-webkit-scrollbar-thumb, +.dark .ember-chat .chat-settings::-webkit-scrollbar-thumb, .dark .chat-history::-webkit-scrollbar-thumb, .dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, .dark .ffz-ui-menu-page::-webkit-scrollbar-thumb, .dark .ffz-ui-sub-menu-page::-webkit-scrollbar-thumb, +.force-dark .ffz-scrollbar::-webkit-scrollbar-thumb, +.force-dark .ember-chat .chat-settings::-webkit-scrollbar-thumb, .force-dark .chat-history::-webkit-scrollbar-thumb, .force-dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, .force-dark .ffz-ui-menu-page::-webkit-scrollbar-thumb, @@ -954,7 +978,6 @@ img.channel_background[src="null"] { display: none; } .ffz-moderation-card { border: 2px solid #cbcbcb; max-width: 340px; - /*box-shadow: #808080 0 0 5px;*/ } .ffz-moderation-card .extra-interface { @@ -967,6 +990,7 @@ img.channel_background[src="null"] { display: none; } .ffz-moderation-card.ffz-has-info h3.name { margin-top: 0; + white-space: nowrap; } .ffz-moderation-card .info { @@ -1073,7 +1097,7 @@ img.channel_background[src="null"] { display: none; } } .mod-icons .custom { - text-indent: 0; + text-indent: 0 !important; text-align: center; text-decoration: none; font-size: 18px; @@ -1106,7 +1130,7 @@ img.channel_background[src="null"] { display: none; } text-decoration: none; } -.more-messages-indicator { +body:not(.ffz-bttv) .more-messages-indicator { /* This looks better when it's full width. */ margin: 0 -20px; } @@ -2047,6 +2071,8 @@ li[data-name="following"] a { /* Conversations */ +.conversation-chat-line.action .colon, +.conversation-input-bar .emoticon-selector .tabs, .conversation-preview-line .badges, body.ffz-conv-title-clickable .conversation-header span.conversation-header-name, body:not(.ffz-conv-title-clickable) .conversation-header a.conversation-header-name { display:none }