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

3.5.66 to 3.5.77. Fixed a performance issue with chat scrolling. Fixed CSS issues introduced in the refactor. Added ReChat support. Fixed all tooltip positioning. Fixed emote usage reporting. Fix support for conversations beta. Fix Do Not Show Again link on portrait mode warning. Fix following data not loading on non-ember pages. Added emoji rendering when BTTV is detected. Made standard chat settings menu scroll. Added support for multiple commands with one button to in-line moderation icons.

This commit is contained in:
SirStendec 2015-11-14 23:52:49 -05:00
parent 0cabebdf19
commit a050063c81
31 changed files with 885 additions and 275 deletions

View file

@ -902,6 +902,26 @@
/* Conversations */ /* 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 { .ffz-dark .conversation-settings-menu .options-divider {
border-bottom-color: rgba(255,255,255,0.2); border-bottom-color: rgba(255,255,255,0.2);
} }
@ -1012,5 +1032,8 @@
background-color: #6441a5 background-color: #6441a5
} }
.ffz-dark .conversation-window .timestamp-line span,
.ffz-dark .conversation-window .new-message-divider span { background: transparent; } .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; } .ffz-dark .conversation-window .new-message-divider:after { display: none; }

View file

@ -260,6 +260,10 @@ FFZ.prototype.render_badges = function(component, badges) {
var user = component.get('msgObject.from') || component.get('message.from.username'), 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'); 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]; var data = this.users[user];
if ( ! data || ! data.badges ) if ( ! data || ! data.badges )
return badges; return badges;

View file

@ -187,6 +187,43 @@ RGBColor.prototype.eq = function(rgb) {
return rgb.r === this.r && rgb.g === this.g && rgb.b === this.b; 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) { RGBColor.fromHex = function(code) {
var raw = parseInt(code.charAt(0) === '#' ? code.substr(1) : code, 16); var raw = parseInt(code.charAt(0) === '#' ? code.substr(1) : code, 16);
return new RGBColor( return new RGBColor(
@ -587,6 +624,9 @@ FFZ.prototype._update_colors = function(darkness_only) {
FFZ.prototype._handle_color = function(color) { FFZ.prototype._handle_color = function(color) {
if ( color instanceof RGBColor )
color = color.toHex();
if ( ! color || this._colors.hasOwnProperty(color) ) if ( ! color || this._colors.hasOwnProperty(color) )
return this._colors[color]; return this._colors[color];

View file

@ -17,6 +17,8 @@ module.exports = {
2: ["ws://localhost:8001/"] 2: ["ws://localhost:8001/"]
}, },
TOOLTIP_DISTANCE: 50,
KNOWN_CODES: { KNOWN_CODES: {
"#-?[\\\\/]": "#-/", "#-?[\\\\/]": "#-/",
":-?(?:7|L)": ":-7", ":-?(?:7|L)": ":-7",
@ -42,6 +44,7 @@ module.exports = {
"Gr(a|e)yFace": "GrayFace" "Gr(a|e)yFace": "GrayFace"
}, },
TWITCH_BASE: 'http://static-cdn.jtvnw.net/emoticons/v1/',
EMOTE_MIRROR_BASE: SERVER + "twitch-emote-mirror/", EMOTE_MIRROR_BASE: SERVER + "twitch-emote-mirror/",
EMOTE_REPLACEMENT_BASE: SERVER + "script/replacements/", EMOTE_REPLACEMENT_BASE: SERVER + "script/replacements/",

View file

@ -215,7 +215,13 @@ FFZ.prototype._modify_cindex = function(view) {
el.classList.add('ffz-channel'); el.classList.add('ffz-channel');
// Try changing the theater mode tooltip. // 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.ffzFixTitle();
this.ffzUpdateUptime(); this.ffzUpdateUptime();
@ -283,7 +289,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! btn ) { if ( ! btn ) {
btn = document.createElement('span'); btn = document.createElement('span');
btn.id = 'ffz-ui-host-button'; btn.id = 'ffz-ui-host-button';
btn.className = 'button action tooltip'; btn.className = 'button action';
btn.addEventListener('click', this.ffzClickHost.bind(btn, this, false)); btn.addEventListener('click', this.ffzClickHost.bind(btn, this, false));
@ -295,6 +301,8 @@ FFZ.prototype._modify_cindex = function(view) {
container.insertBefore(btn, before); container.insertBefore(btn, before);
else else
container.appendChild(btn); container.appendChild(btn);
jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
} }
btn.classList.remove('disabled'); btn.classList.remove('disabled');
@ -321,7 +329,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! btn ) { if ( ! btn ) {
btn = document.createElement('span'); btn = document.createElement('span');
btn.id = 'ffz-ui-host-button'; btn.id = 'ffz-ui-host-button';
btn.className = 'button action tooltip'; btn.className = 'button action';
btn.addEventListener('click', this.ffzClickHost.bind(btn, this, true)); btn.addEventListener('click', this.ffzClickHost.bind(btn, this, true));
@ -333,6 +341,8 @@ FFZ.prototype._modify_cindex = function(view) {
container.insertBefore(btn, before); container.insertBefore(btn, before);
else else
container.appendChild(btn); container.appendChild(btn);
jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
} }
btn.classList.remove('disabled'); btn.classList.remove('disabled');
@ -406,7 +416,7 @@ FFZ.prototype._modify_cindex = function(view) {
else else
cont.appendChild(stat); 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); el.innerHTML = utils.number_commas(chatter_count);
@ -438,7 +448,7 @@ FFZ.prototype._modify_cindex = function(view) {
else else
cont.appendChild(stat); 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) + ")"; 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 ) { if ( ! stat_el ) {
stat_el = document.createElement('span'); stat_el = document.createElement('span');
stat_el.id = 'ffz-ui-player-stats'; stat_el.id = 'ffz-ui-player-stats';
stat_el.className = 'ffz stat tooltip'; stat_el.className = 'ffz stat';
stat_el.innerHTML = constants.GRAPH + " "; stat_el.innerHTML = constants.GRAPH + " ";
el = document.createElement('span'); el = document.createElement('span');
@ -484,16 +494,18 @@ FFZ.prototype._modify_cindex = function(view) {
container.insertBefore(stat_el, other.nextSibling); container.insertBefore(stat_el, other.nextSibling);
else else
container.appendChild(stat_el); container.appendChild(stat_el);
jQuery(stat_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
} }
var delay = parseFloat(stats.hlsLatencyBroadcaster); var delay = parseFloat(stats.hlsLatencyBroadcaster);
if ( delay > 180 ) { if ( delay > 180 ) {
delay = Math.floor(delay); 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<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps')
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old'; el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else { } 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<br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps');
delay = stats.hlsLatencyBroadcaster; delay = stats.hlsLatencyBroadcaster;
var pos = delay.lastIndexOf('.'); var pos = delay.lastIndexOf('.');
@ -532,7 +544,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! stat_el ) { if ( ! stat_el ) {
stat_el = document.createElement('span'); stat_el = document.createElement('span');
stat_el.id = 'ffz-ui-player-stats'; stat_el.id = 'ffz-ui-player-stats';
stat_el.className = 'ffz stat tooltip'; stat_el.className = 'ffz stat';
stat_el.innerHTML = constants.GRAPH + " "; stat_el.innerHTML = constants.GRAPH + " ";
el = document.createElement('span'); el = document.createElement('span');
@ -543,16 +555,18 @@ FFZ.prototype._modify_cindex = function(view) {
container.insertBefore(stat_el, other.nextSibling); container.insertBefore(stat_el, other.nextSibling);
else else
container.appendChild(stat_el); container.appendChild(stat_el);
jQuery(stat_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
} }
var delay = parseFloat(stats.hlsLatencyBroadcaster); var delay = parseFloat(stats.hlsLatencyBroadcaster);
if ( delay > 180 ) { if ( delay > 180 ) {
delay = Math.floor(delay); 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<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps')
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old'; el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else { } 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<br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps');
delay = stats.hlsLatencyBroadcaster; delay = stats.hlsLatencyBroadcaster;
var pos = delay.lastIndexOf('.'); 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); el.innerHTML = utils.time_to_string(uptime, false, false, false, f.settings.stream_uptime === 1 || f.settings.stream_uptime === 3);

View file

@ -415,7 +415,7 @@ FFZ.prototype._modify_cview = function(view) {
ffzInit: function() { ffzInit: function() {
f._chatv = this; f._chatv = this;
this.$('.textarea-contain').append(f.build_ui_link(this)); this.$('.textarea-contain').append(f.build_ui_link(this));
this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: 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 ) if ( !f.has_bttv && f.settings.group_tabs )
this.ffzEnableTabs(); this.ffzEnableTabs();
@ -447,7 +447,7 @@ FFZ.prototype._modify_cview = function(view) {
var room = this.get('controller.currentRoom'), rows; var room = this.get('controller.currentRoom'), rows;
room && room.resetUnreadCount(); room && room.resetUnreadCount();
if ( room._ffz_was_unread ) { if ( room && room._ffz_was_unread ) {
room._ffz_was_unread = false; room._ffz_was_unread = false;
var el = this.get('element'), var el = this.get('element'),

View file

@ -7,23 +7,11 @@ var FFZ = window.FrankerFaceZ,
// Settings // 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 = { FFZ.settings_info.conv_focus_on_click = {
type: "boolean", type: "boolean",
value: false, value: false,
no_mobile: true, no_mobile: true,
visible: false,
category: "Conversations", category: "Conversations",
name: "Focus Input on Click", 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 // Initialization
@ -63,10 +38,6 @@ FFZ.settings_info.conv_beta_enable = {
FFZ.prototype.setup_conversations = function() { FFZ.prototype.setup_conversations = function() {
document.body.classList.toggle('ffz-top-conversations', this.settings.top_conversations); 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."); this.log("Hooking the Ember Conversation Window component.");
var ConvWindow = App.__container__.resolve('component:conversation-window'); 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'); var ConvLine = App.__container__.resolve('component:conversation-line');
if ( ConvLine ) if ( ConvLine )
this._modify_conversation_line(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'); Settings = App.__container__.lookup('controller:settings');
component.reopen({ component.reopen({
onConversationClick: Ember.on('click', function() { headerBadges: Ember.computed("thread.participants", "currentUsername", function() {
this.markConversationRead(); var e = this.get("thread.participants").rejectBy("username", this.get("currentUsername")).objectAt(0),
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),
badges = {}, badges = {},
ut = e.get("userType"); ut = e.get("userType");
@ -164,28 +132,18 @@ FFZ.prototype._modify_conversation_window = function(component) {
header = el && el.querySelector('.conversation-header'), header = el && el.querySelector('.conversation-header'),
header_name = header && header.querySelector('.conversation-header-name'), header_name = header && header.querySelector('.conversation-header-name'),
new_header_name = document.createElement('span'),
raw_color = this.get('otherUser.color'), raw_color = this.get('otherUser.color'),
colors = raw_color && f._handle_color(raw_color), colors = raw_color && f._handle_color(raw_color),
is_dark = (Layout && Layout.get('isTheatreMode')) || f.settings.dark_twitch; is_dark = (Layout && Layout.get('isTheatreMode')) || f.settings.dark_twitch;
if ( header_name ) { if ( header_name && raw_color ) {
new_header_name.className = 'conversation-header-name'; header_name.style.color = (is_dark ? colors[1] : colors[0]);
new_header_name.textContent = header_name.textContent; header_name.classList.add('has-color');
header.insertBefore(new_header_name, header_name); header_name.setAttribute('data-color', raw_color);
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);
}
} }
jQuery(el).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
} }
}); });
} }

View file

@ -75,7 +75,7 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
var t_el = this._ffz_uptime = document.createElement('div'); var t_el = this._ffz_uptime = document.createElement('div');
t_el.className = 'overlay_info length live'; 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); cap.appendChild(t_el);
this._ffz_uptime_timer = setInterval(this.ffzUpdateUptime.bind(this), 1000); this._ffz_uptime_timer = setInterval(this.ffzUpdateUptime.bind(this), 1000);

View file

@ -159,7 +159,7 @@ FFZ.prototype.setup_profile_following = function() {
return false; return false;
t_el.className = 'overlay_info length'; 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; var age = data[0] ? Math.floor((Date.now() - data[0].getTime()) / 1000) : 0;
if ( age ) { if ( age ) {

View file

@ -228,7 +228,7 @@ FFZ.prototype.setup_layout = function() {
return; return;
f._portrait_warning = true; 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.<br><br>Please <a href="#" onclick="ffz.settings.set(\'portrait_mode\',0);jQuery(this).parents(\'.ffz-noty\').remove();ffz._portrait_warning = false;return false">disable Portrait Mode</a> or make your window narrower.<br><br><a href="#" onclick="onclick="ffz.settings.set(\'portrait_warning\',true);jQuery(this).parents(\'.ffz-noty\').remove();return false">Do not show this message again</a>'); f.show_message('Twitch\'s Chat Sidebar has been hidden as a result of FrankerFaceZ\'s Portrait Mode because the window is too wide.<br><br>Please <a href="#" onclick="ffz.settings.set(\'portrait_mode\',0);jQuery(this).parents(\'.ffz-noty\').remove();ffz._portrait_warning = false;return false">disable Portrait Mode</a> or make your window narrower.<br><br><a href="#" onclick="ffz.settings.set(\'portrait_warning\',true);jQuery(this).parents(\'.ffz-noty\').remove();return false">Do not show this message again</a>');
}.observes("isTooSmallForRightColumn"), }.observes("isTooSmallForRightColumn"),

View file

@ -113,7 +113,7 @@ FFZ.settings_info.scrollback_length = {
for(var room_id in this.rooms) { for(var room_id in this.rooms) {
var room = this.rooms[room_id]; 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", category: "Chat Filtering",
no_bttv: true, no_bttv: true,
//visible: function() { return ! this.has_bttv },
name: "Banned Words", name: "Banned Words",
help: "Set a list of words that will be locally removed from chat messages.", 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", category: "Chat Filtering",
no_bttv: true, no_bttv: true,
//visible: function() { return ! this.has_bttv },
name: "Highlight Keywords", name: "Highlight Keywords",
help: "Set additional keywords that will be highlighted in chat.", 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 = { FFZ.settings_info.image_hover_all_domains = {
type: "boolean", type: "boolean",
value: false, value: false,
@ -402,7 +415,7 @@ FFZ.settings_info.chat_font_family = {
var span = document.createElement('span'); var span = document.createElement('span');
span.style.fontFamily = val; 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); utils.update_css(this._chat_style, "chat_font_family", css);
@ -444,7 +457,7 @@ FFZ.settings_info.chat_font_size = {
else { else {
var lh = Math.max(20, Math.round((20/12)*val)), var lh = Math.max(20, Math.round((20/12)*val)),
pd = Math.floor((lh - 20) / 2); 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 ) if ( pd )
css += ".ember-chat .chat-messages .chat-line .mod-icons, .ember-chat .chat-messages .chat-line .badges { padding-top: " + pd + "px; }"; 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 = ""; css = "";
else { else {
var lh = Math.max(20, Math.round((20/12)*val), Math.round((20/12)*this.settings.chat_font_size)); 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); 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') ) { if ( e.target.classList.contains('custom') ) {
var room_id = this.get('msgObject.room'), var room_id = this.get('msgObject.room'),
room = room_id && f.rooms[room_id] && f.rooms[room_id].room, room = room_id && f.rooms[room_id] && f.rooms[room_id].room,
cmd = e.target.getAttribute('data-cmd'); cmd = e.target.getAttribute('data-cmd');
if ( room ) { 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') ) if ( e.target.classList.contains('is-timeout') )
room.clearMessages(this.get('msgObject.from')); room.clearMessages(this.get('msgObject.from'));
} }
@ -683,8 +698,8 @@ FFZ.prototype._modify_line = function(component) {
else { else {
if ( typeof btn === "string" ) { if ( typeof btn === "string" ) {
cmd = btn.replace(/{user}/g, user); cmd = btn.replace(/{user}/g, user).replace(/ *<LINE> */, "\n");
tip = 'Custom Command\n' + cmd; tip = 'Custom Command' + (cmd.indexOf('\n') !== -1 ? 's' : '') + '\n' + cmd;
} else { } else {
cmd = "/timeout " + user + " " + btn; cmd = "/timeout " + user + " " + btn;
tip = "Timeout User (" + utils.duration_string(btn) + ")"; tip = "Timeout User (" + utils.duration_string(btn) + ")";

View file

@ -182,7 +182,7 @@ FFZ.settings_info.mod_buttons = {
old_val += ' ' + prefix + cmd; 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 \"<BAN>\" 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: <BAN> 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 \"<LINE>\".\n\nNumeric values will become timeout buttons for that number of seconds. The text \"<BAN>\" 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: <BAN> 600", old_val);
if ( new_val === null || new_val === undefined ) if ( new_val === null || new_val === undefined )
return; return;
@ -253,8 +253,15 @@ FFZ.settings_info.mod_buttons = {
} else } else
had_prefix = true; had_prefix = true;
if ( typeof val === "string" && val.indexOf('{user}') === -1 ) if ( typeof val === "string" ) {
val += ' {user}'; // Split it up for this step.
var lines = val.split(/ *<LINE> */);
for(var x=0; x < lines.length; x++) {
if ( lines[x].indexOf('{user}') === -1 )
lines[x] += ' {user}';
}
val = lines.join("<LINE>");
}
final.push([prefix, val, had_prefix]); final.push([prefix, val, had_prefix]);
} }
@ -475,7 +482,7 @@ FFZ.prototype.setup_mod_card = function() {
if ( name ) { if ( name ) {
name.classList.add('ffz-alias'); name.classList.add('ffz-alias');
name.title = utils.sanitize(controller.get('cardInfo.user.display_name') || user_id.capitalize()); 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.innerHTML = utils.sanitize(title);
btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}')); 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)); btn.addEventListener('click', add_btn_click.bind(this, cmd));
return btn; return btn;
}; };
@ -601,7 +608,7 @@ FFZ.prototype.setup_mod_card = function() {
else if ( f.settings.mod_card_hotkeys && timeout === 1 ) else if ( f.settings.mod_card_hotkeys && timeout === 1 )
btn.title = "(P)urge - " + btn.title; 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)); btn.addEventListener('click', btn_click.bind(this, timeout));
return btn; return btn;
@ -637,7 +644,7 @@ FFZ.prototype.setup_mod_card = function() {
unban_btn.innerHTML = CHECK; unban_btn.innerHTML = CHECK;
unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User"; 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)); unban_btn.addEventListener("click", btn_click.bind(this, -1));
jQuery(ban_btn).after(unban_btn); jQuery(ban_btn).after(unban_btn);
@ -661,7 +668,7 @@ FFZ.prototype.setup_mod_card = function() {
msg_btn.classList.add('message'); msg_btn.classList.add('message');
msg_btn.title = "Whisper User"; 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'); var real_msg = document.createElement('button');
@ -846,7 +853,7 @@ FFZ.prototype._build_mod_card_history = function(line) {
// Interactivity // Interactivity
jQuery('a.deleted-link', l_el).click(f._deleted_link_click); jQuery('a.deleted-link', l_el).click(f._deleted_link_click);
jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) }); jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) });
jQuery('.html-tooltip', l_el).tipsy({html:true}); jQuery('.html-tooltip', l_el).tipsy({html:true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
return l_el; return l_el;
} }

View file

@ -61,7 +61,7 @@ FFZ.prototype.setup_player = function() {
this.players = {}; this.players = {};
var Player2 = App && App.__container__.resolve('component:twitch-player2'); var Player2 = window.App && App.__container__.resolve('component:twitch-player2');
if ( ! Player2 ) if ( ! Player2 )
return this.log("Unable to find twitch-player2 component."); return this.log("Unable to find twitch-player2 component.");

View file

@ -134,6 +134,15 @@ FFZ.prototype._modify_rview = function(view) {
this._super(); 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() { ffzInit: function() {
f._roomv = this; f._roomv = this;
@ -179,6 +188,9 @@ FFZ.prototype._modify_rview = function(view) {
if ( f._roomv === this ) if ( f._roomv === this )
f._roomv = undefined; f._roomv = undefined;
if ( this._ffz_chat_display )
this._ffz_chat_display = undefined;
this.ffzDisableFreeze(); this.ffzDisableFreeze();
}, },
@ -367,12 +379,12 @@ FFZ.prototype._modify_rview = function(view) {
} }
}, },
ffzUnfreeze: function() { ffzUnfreeze: function(from_stuck) {
this.ffz_frozen = false; this.ffz_frozen = false;
this._ffz_last_move = 0; this._ffz_last_move = 0;
this.ffzUnwarnPaused(); this.ffzUnwarnPaused();
if ( this.get('stuckToBottom') ) if ( ! from_stuck && this.get('stuckToBottom') )
this._scrollToBottom(); this._scrollToBottom();
}, },
@ -434,7 +446,7 @@ FFZ.prototype._modify_rview = function(view) {
this.set("stuckToBottom", val); this.set("stuckToBottom", val);
this.get("controller.model") && this.set("controller.model.messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150)); this.get("controller.model") && this.set("controller.model.messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150));
if ( ! val ) if ( ! val )
this.ffzUnfreeze(); this.ffzUnfreeze(true);
}, },
// Warnings~! // Warnings~!
@ -728,6 +740,7 @@ FFZ.prototype._insert_history = function(room_id, data) {
tmiSession = r.tmiSession || (TMI._sessions && TMI._sessions[0]), tmiSession = r.tmiSession || (TMI._sessions && TMI._sessions[0]),
tmiRoom = r.tmiRoom, tmiRoom = r.tmiRoom,
removed = 0,
inserted = 0, inserted = 0,
purged = {}, 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')); this.tokenize_chat_line(msg, true, r.get('roomProperties.hide_chat_links'));
if ( r.shouldShowMessage(msg) ) { if ( r.shouldShowMessage(msg) ) {
messages.insertAt(inserted, msg); messages.insertAt(inserted, msg);
while( messages.length > r.get('messageBufferSize') ) while( messages.length > r.get('messageBufferSize') ) {
messages.removeAt(0); 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'), var msgs = t.get('messages'),
total = msgs.get('length'), total = msgs.get('length'),
i = total; i = total,
removed = 0;
// Delete visible messages // Delete visible messages
while(i--) { while(i--) {
@ -1004,6 +1023,7 @@ FFZ.prototype._modify_room = function(room) {
if ( msg.from === user ) { if ( msg.from === user ) {
if ( f.settings.remove_deleted ) { if ( f.settings.remove_deleted ) {
msgs.removeAt(i); msgs.removeAt(i);
removed++;
continue; 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 // Delete pending messages
if (t.ffzPending) { if (t.ffzPending) {
msgs = t.ffzPending; msgs = t.ffzPending;
@ -1064,8 +1087,11 @@ FFZ.prototype._modify_room = function(room) {
len = messages.get("length"), len = messages.get("length"),
limit = this.get("messageBufferSize"); limit = this.get("messageBufferSize");
if ( len > limit ) if ( len > limit ) {
messages.removeAt(0, 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 // Artificial chat delay

View file

@ -4,46 +4,11 @@ var FFZ = window.FrankerFaceZ,
constants = require('./constants'), constants = require('./constants'),
utils = require('./utils'), 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) { build_css = function(emote) {
if ( ! emote.margins && ! emote.css ) 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 // Emote Usage
// ------------------------ // ------------------------
FFZ.prototype.add_usage = function(room_id, emote_id, count) { FFZ.prototype.add_usage = function(room_id, emote, count) {
var rooms = this.emote_usage[emote_id] = this.emote_usage[emote_id] || {}; // 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); rooms[room_id] = (rooms[room_id] || 0) + (count || 1);
if ( this._emote_report_scheduled ) if ( this._emote_report_scheduled )
@ -260,12 +232,82 @@ FFZ.prototype._emote_tooltip = function(emote) {
var set = this.emote_sets[emote.set_id], var set = this.emote_sets[emote.set_id],
owner = emote.owner, owner = emote.owner,
title = set && set.title || "Global", title = set && set.title || "Global",
source = set && set.source || "FFZ"; source = set && set.source || "FFZ",
emote._tooltip = "Emoticon: " + (emote.hidden ? "???" : emote.name) + "<br>" + source + " " + title + (owner ? "<br>By: " + owner.display_name : ""); preview_url = this.settings.emote_image_hover ? (emote.urls[4] || emote.urls[2]) : null,
image = preview_url ? '<img class="emoticon ffz-image-hover" src="' + preview_url + '?_=preview">' : '';
emote._tooltip = image + "Emoticon: " + (emote.hidden ? "???" : emote.name) + "<br>" + source + " " + title + (owner ? "<br>By: " + owner.display_name : "");
return emote._tooltip; 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 = '<img class="emoticon ffz-image-hover emoji" src="' + src + '">';
emote.setAttribute('original-title', emoji ? (image + 'Emoji: ' + emote.alt + '<br>Name: ' + emoji.name + (emoji.short_name ? "<br>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 // Emoji Loading

View file

@ -25,8 +25,6 @@ FFZ.prototype.setup_bttv = function(delay) {
this.log("BetterTTV was detected after " + delay + "ms. Hooking."); this.log("BetterTTV was detected after " + delay + "ms. Hooking.");
this.has_bttv = true; this.has_bttv = true;
// this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString());
// Disable Dark if it's enabled. // Disable Dark if it's enabled.
document.body.classList.remove("ffz-dark"); document.body.classList.remove("ffz-dark");
if ( this._dark_style ) { if ( this._dark_style ) {
@ -224,14 +222,14 @@ FFZ.prototype.setup_bttv = function(delay) {
// Why is emote parsing so bad? ;_; // Why is emote parsing so bad? ;_;
_.each(emotes, function(emote) { _.each(emotes, function(emote) {
var tooltip = f._emote_tooltip(emote), var tooltip = f._emote_tooltip(emote),
eo = ['<img class="emoticon" data-ffz-emote="' + emote.id + '" srcset="' + (emote.srcSet || "") + '" src="' + emote.urls[1] + '" data-regex="' + emote.name + '" title="' + tooltip + '" />'], eo = ['<img class="emoticon html-tooltip" data-ffz-emote="' + emote.id + '" srcset="' + utils.quote_attr(emote.srcSet || "") + '" src="' + utils.quote_attr(emote.urls[1]) + '" data-regex="' + utils.quote_attr(emote.name) + '" title="' + utils.quote_attr(tooltip) + '">'],
old_tokens = tokens; old_tokens = tokens;
tokens = []; tokens = [];
for(var i=0; i < old_tokens.length; i++) { for(var i=0; i < old_tokens.length; i++) {
var token = old_tokens[i]; var token = old_tokens[i];
if ( typeof token != "string" ) { if ( typeof token !== "string" ) {
tokens.push(token); tokens.push(token);
continue; continue;
} }
@ -248,7 +246,7 @@ FFZ.prototype.setup_bttv = function(delay) {
tokens.push(eo); tokens.push(eo);
if ( mine && l_room ) if ( mine && l_room )
f.add_usage(l_room, emote.id); f.add_usage(l_room, emote);
} else } else
tokens.push(bit); tokens.push(bit);
@ -258,9 +256,10 @@ FFZ.prototype.setup_bttv = function(delay) {
} }
// Sneak in Emojicon Processing // Sneak in Emojicon Processing
/*
if ( f.settings.parse_emoji && f.emoji_data ) { if ( f.settings.parse_emoji && f.emoji_data ) {
var old_tokens = tokens; var old_tokens = tokens,
setting = f.settings.parse_emoji;
tokens = []; tokens = [];
for(var i=0; i < old_tokens.length; i++) { for(var i=0; i < old_tokens.length; i++) {
@ -280,20 +279,25 @@ FFZ.prototype.setup_bttv = function(delay) {
variant = tbits.shift(); variant = tbits.shift();
if ( variant === '\uFE0E' ) if ( variant === '\uFE0E' )
bits.push(match); tokens.push(match);
else { else {
var eid = utils.emoji_to_codepoint(match, variant), 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 ) { if ( data && src ) {
tokens.push(['<img class="emoticon" height="18px" srcset="' + (data.srcSet || "") + '" src="' + data.src + '" alt="' + alt + '" title="Emoji: ' + data.raw + '\nName: :' + data.short_name + ':">']); var image = src && f.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + src + '">' : '',
tooltip = image + "Emoji: " + data.raw + "<br>Name: " + data.name + (data.short_name ? "<br>Short Name: :" + data.short_name + ":" : ""),
code = utils.quote_attr(data.raw);
tokens.push(['<img class="emoticon emoji html-tooltip" height="18px" data-ffz-emoji="' + eid + '" src="' + utils.quote_attr(src) + '" data-regex="' + code + '" alt="' + code + '" title="' + utils.quote_attr(tooltip) + '">']);
} else } else
tokens.push(match + (variant || "")); tokens.push(match + (variant || ""));
} }
} }
} }
} }
}*/ }
return tokens; return tokens;
} }

277
src/ext/rechat.js Normal file
View file

@ -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(&quot;' + badge.image + '&quot;);' : '';
if ( badge.color )
css += 'background-color:' + badge.color + ';';
if ( badge.extra_css )
css += badge.extra_css;
output += '<div class="badge float-left tooltip ' + badge.klass + '"' + (css ? ' style="' + css + '"' : '') + ' title="' + badge.title + '"></div>';
}
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);
}

View file

@ -22,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; }
// Version // Version
var VER = FFZ.version_info = { var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 65, major: 3, minor: 5, revision: 77,
toString: function() { toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); 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() { FFZ.prototype.get_user = function() {
if ( window.PP && PP.login ) { if ( this.__user )
return PP; return this.__user;
} else if ( window.App ) {
var nc = App.__container__.lookup("controller:login"); var user;
return nc ? nc.get("userData") : undefined; 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('./debug');
require('./ext/rechat');
require('./ext/betterttv'); require('./ext/betterttv');
require('./ext/emote_menu'); require('./ext/emote_menu');
@ -140,6 +151,7 @@ require('./featurefriday');
require('./ui/styles'); require('./ui/styles');
require('./ui/dark'); require('./ui/dark');
require('./ui/tooltips');
require('./ui/notifications'); require('./ui/notifications');
require('./ui/viewer_count'); require('./ui/viewer_count');
require('./ui/sub_count'); require('./ui/sub_count');
@ -256,6 +268,7 @@ FFZ.prototype.init_normal = function(delay, no_socket) {
this.setup_following_count(false); this.setup_following_count(false);
this.setup_menu(); this.setup_menu();
this.fix_tooltips();
this.find_bttv(10); this.find_bttv(10);
var end = (window.performance && performance.now) ? performance.now() : Date.now(), 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. // Set up the FFZ message passer.
this.setup_message_event(); this.setup_message_event();
this.fix_tooltips();
this.find_bttv(10); this.find_bttv(10);
var end = (window.performance && performance.now) ? performance.now() : Date.now(), 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_following_count(true);
this.setup_races(); this.setup_races();
this.fix_tooltips();
this.connect_extra_chat(); this.connect_extra_chat();
this.setup_rechat();
this.find_bttv(10); this.find_bttv(10);
this.find_emote_menu(10); this.find_emote_menu(10);

View file

@ -1,7 +1,10 @@
/* Regular Alternating Background */ /* Regular Alternating Background */
.conversation-chat-lines > div:nth-child(2n+0):before, .conversation-chat-lines > div:nth-child(2n+0):before,
.chat-history .chat-line: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); background-color: rgba(0,0,0, 0.1);
} }
@ -17,7 +20,12 @@
.dark .chat-history .chat-line:nth-child(2n+0):before, .dark .chat-history .chat-line:nth-child(2n+0):before,
.force-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, .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); background-color: rgba(255,255,255, 0.05);
} }

View file

@ -1,7 +1,7 @@
/* High-Contrast Background */ /* High-Contrast Background */
.chat-container, .chat-container,
.ember-chat-container { .ember-chat-container {
background-color: #fff; background-color: #fff !important;
} }
@ -12,5 +12,5 @@
.chat-container.force-dark, .chat-container.force-dark,
.ember-chat-container.dark, .ember-chat-container.dark,
.ember-chat-container.force-dark { .ember-chat-container.force-dark {
background-color: #000; background-color: #000 !important;
} }

View file

@ -1,7 +1,7 @@
/* High-Contrast Text */ /* High-Contrast Text */
.chat-container, .chat-container,
.ember-chat-container { .ember-chat-container {
color: #000; color: #000 !important;
} }
/* Dark: High-Contrast Text */ /* Dark: High-Contrast Text */
@ -10,6 +10,10 @@
.chat-container.dark, .chat-container.dark,
.chat-container.force-dark, .chat-container.force-dark,
.ember-chat-container.dark, .ember-chat-container.dark,
.ember-chat-container.force-dark { .ember-chat-container.force-dark,
color: #fff;
.ffz-dark .ember-chat-container.dark .chat-line,
.ffz-dark .chat-container.dark .chat-line
{
color: #fff !important;
} }

View file

@ -1,76 +1,11 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
utils = require("./utils"), utils = require("./utils"),
constants = require("./constants"), constants = require("./constants"),
TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/",
helpers, helpers,
conv_helpers,
EXPLANATION_TRAIL = '<hr>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.', EXPLANATION_TRAIL = '<hr>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 + "<br>" + (set_type ? set_type + ": " : "") + set + (owner ? "<br>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) { reg_escape = function(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
@ -105,7 +40,7 @@ var FFZ = window.FrankerFaceZ,
return IMGUR_PATH.test(path); return IMGUR_PATH.test(path);
return any_domain ? IMAGE_EXT.test(path) : IMAGE_DOMAINS.indexOf(domain) !== -1; return any_domain ? IMAGE_EXT.test(path) : IMAGE_DOMAINS.indexOf(domain) !== -1;
} },
image_iframe = function(href, extra_class) { image_iframe = function(href, extra_class) {
return '<iframe class="ffz-image-hover' + (extra_class ? ' ' + extra_class : '') + '" allowtransparency="true" src="' + constants.SERVER + 'script/img-proxy.html#' + utils.quote_attr(href) + '"></iframe>'; return '<iframe class="ffz-image-hover' + (extra_class ? ' ' + extra_class : '') + '" allowtransparency="true" src="' + constants.SERVER + 'script/img-proxy.html#' + utils.quote_attr(href) + '"></iframe>';
@ -248,12 +183,12 @@ FFZ._emote_mirror_swap = function(img) {
img.setAttribute('data-alt-attempts', attempts + 1); img.setAttribute('data-alt-attempts', attempts + 1);
var id = img.getAttribute('data-emote'); 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.src = constants.EMOTE_MIRROR_BASE + id + ".png";
img.srcset = ""; img.srcset = "";
} else { } else {
img.src = TWITCH_BASE + id + "/1.0"; img.src = constants.TWITCH_BASE + id + "/1.0";
img.srcset = build_srcset(id); img.srcset = utils.build_srcset(id);
} }
} }
@ -317,6 +252,10 @@ FFZ.prototype.setup_tokenization = function() {
if ( ! helpers ) if ( ! helpers )
return this.log("Unable to get chat helper functions."); 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."); this.log("Hooking Ember chat line helpers.");
var f = this; 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[33] = "--turbo-faces--";
this._twitch_set_to_channel[42] = "--turbo-faces--"; this._twitch_set_to_channel[42] = "--turbo-faces--";
this._reset_tooltips(true);
}).fail(function(data) { }).fail(function(data) {
if ( data.status === 404 ) if ( data.status === 404 )
return; return;
@ -410,6 +351,9 @@ FFZ.prototype.tokenize_conversation_line = function(message, prevent_notificatio
emotes = message.get('tags.emotes'), emotes = message.get('tags.emotes'),
tokens = [msg]; tokens = [msg];
if ( conv_helpers && conv_helpers.checkActionMessage )
tokens = conv_helpers.checkActionMessage(tokens);
// Standard Tokenization // Standard Tokenization
if ( helpers && helpers.linkifyMessage ) if ( helpers && helpers.linkifyMessage )
tokens = helpers.linkifyMessage(tokens); 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) { FFZ.prototype.render_tokens = function(tokens, render_links) {
var f = this; var f = this;
return _.map(tokens, function(token) { return _.map(tokens, function(token) {
if ( token.hidden )
return "";
if ( token.emoticonSrc ) { if ( token.emoticonSrc ) {
var tooltip, src = token.emoticonSrc, srcset, extra; var tooltip, src = token.emoticonSrc, srcset, cls, extra;
if ( token.ffzEmote ) { if ( token.ffzEmote ) {
var emote_set = f.emote_sets && f.emote_sets[token.ffzEmoteSet], var emote_set = f.emote_sets && f.emote_sets[token.ffzEmoteSet],
emote = emote_set && emote_set.emoticons && emote_set.emoticons[token.ffzEmote]; 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 ) { } else if ( token.ffzEmoji ) {
var eid = token.ffzEmoji, var eid = token.ffzEmoji,
emoji = f.emoji_data && f.emoji_data[eid], 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) ) if ( setting === 0 || (setting === 1 && ! emoji.tw) || (setting === 2 && ! emoji.noto) )
return token.altText; 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; src = setting === 2 ? token.noto_src : token.tw_src;
if ( emoji && f.settings.emote_image_hover )
image = '<img class="emoticon ffz-image-hover" src="' + src + '">';
tooltip = emoji ? image + "Emoji: " + token.altText + "<br>Name: " + emoji.name + (emoji.short_name ? "<br>Short Name: :" + emoji.short_name + ":" : "") : token.altText;
extra = ' data-ffz-emoji="' + eid + '" height="18px"';
cls = ' emoji';
} else { } else {
var id = token.replacedId || FFZ.src_to_id(token.emoticonSrc), var id = token.replacedId || FFZ.src_to_id(token.emoticonSrc),
data = id && f._twitch_emotes && f._twitch_emotes[id]; data = id && f._twitch_emotes && f._twitch_emotes[id];
if ( data ) if ( data )
tooltip = data.tooltip ? data.tooltip : token.altText; tooltip = data.tooltip ? data.tooltip : utils.build_tooltip.bind(f)(id, false, token.altText);
else { else {
try { try {
var set_id = f._twitch_emote_to_set[id]; var set_id = f._twitch_emote_to_set[id];
if ( 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, code: token.altText,
id: id, id: id,
set: f._twitch_set_to_channel[set_id], set: f._twitch_set_to_channel[set_id],
@ -637,19 +590,18 @@ FFZ.prototype.render_tokens = function(tokens, render_links) {
}); });
} else { } else {
tooltip = f._twitch_emotes[id] = token.altText; 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) { } catch(err) {
f.error("Error Generating Emote Tooltip: " + 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. extra = ' data-emote="' + id + '"'; // onerror="FrankerFaceZ._emote_mirror_swap(this)"'; // Disable error checking for now.
if ( ! constants.EMOTE_REPLACEMENTS[id] ) if ( ! constants.EMOTE_REPLACEMENTS[id] )
srcset = build_srcset(id); srcset = utils.build_srcset(id);
} }
return '<img class="emoticon html-tooltip' + (cls||"") + '"' + (extra||"") + ' src="' + utils.quote_attr(src) + '" ' + (srcset ? 'srcset="' + utils.quote_attr(srcset) + '" ' : '') + 'alt="' + utils.quote_attr(token.altText) + '" title="' + utils.quote_attr(tooltip) + '">'; return '<img class="emoticon html-tooltip' + (cls||"") + '"' + (extra||"") + ' src="' + utils.quote_attr(src) + '" ' + (srcset ? 'srcset="' + utils.quote_attr(srcset) + '" ' : '') + 'alt="' + utils.quote_attr(token.altText) + '" title="' + utils.quote_attr(tooltip) + '">';
@ -797,7 +749,7 @@ FFZ.prototype.tokenize_title_emotes = function(tokens) {
data = data.emoticon_sets[0]; data = data.emoticon_sets[0];
for(var i=0; i < data.length; i++) { for(var i=0; i < data.length; i++) {
var em = data[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 ) if ( f._cindex )
@ -893,7 +845,7 @@ FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
bits.push(eo); bits.push(eo);
if ( do_report && room ) if ( do_report && room )
f.add_usage(room, emote.id); f.add_usage(room, emote);
} else } else
bits.push(bit); bits.push(bit);

View file

@ -144,6 +144,9 @@ FFZ.settings_info.dark_twitch = {
model && model.set('darkMode', true); model && model.set('darkMode', true);
} else } else
model && model.set('darkMode', this.settings.twitch_chat_dark); 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);
} }
}; };

View file

@ -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 we don't have Ember, no point in trying this stuff.
if ( ! has_ember ) if ( ! has_ember )
return this._update_following_count(); return this._following_get_me();
this.log("Connecting to Live Streams model."); this.log("Connecting to Live Streams model.");
var Stream = window.App && App.__container__.resolve('model:stream'); 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() { FFZ.prototype._schedule_following_count = function() {
if ( ! this.settings.following_count ) { if ( ! this.settings.following_count ) {
if ( this._following_count_timer ) { if ( this._following_count_timer ) {
@ -111,8 +132,13 @@ FFZ.prototype._update_following_count = function() {
if ( Live ) if ( Live )
Live.load(); Live.load();
else else {
Twitch.api && Twitch.api.get("streams/followed", {limit:5, offset:0}, {version:3}) 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) { .done(function(data) {
f._draw_following_count(data._total); f._draw_following_count(data._total);
f._draw_following_channels(data.streams, 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_count();
f._draw_following_channels(); f._draw_following_channels();
}) })
}
} }

View file

@ -2,8 +2,6 @@ var FFZ = window.FrankerFaceZ,
constants = require('../constants'), constants = require('../constants'),
utils = require('../utils'), utils = require('../utils'),
TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/",
fix_menu_position = function(container) { fix_menu_position = function(container) {
var swapped = document.body.classList.contains('ffz-sidebar-swap') && ! document.body.classList.contains('ffz-portrait'); 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."); 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 ) if ( ! Settings )
return; return;
@ -155,6 +154,12 @@ FFZ.prototype.setup_menu = function() {
menu.appendChild(header); menu.appendChild(header);
menu.appendChild(content); menu.appendChild(content);
// Maximum Height
var e = el.querySelector('.chat-settings');
if ( Layout && e )
e.style.maxHeight = (Layout.get('windowHeight') - 90) + 'px';
}, },
ffzTeardown: function() { 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 // For some reason, this doesn't work unless we create an instance of the
// chat settings view and then destroy it immediately. // chat settings view and then destroy it immediately.
try { try {
@ -225,7 +239,7 @@ FFZ.prototype.build_ui_popup = function(view) {
container.classList.toggle('dark', dark); container.classList.toggle('dark', dark);
// Stuff // Stuff
jQuery(inner).find('.html-tooltip').tipsy({live: true, html: true, gravity: 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 // Menu Container
@ -316,7 +330,7 @@ FFZ.prototype.build_ui_popup = function(view) {
link.title = page.name; link.title = page.name;
link.innerHTML = page.icon; 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)); 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'), var s = document.createElement('span'),
can_use = is_subscribed || !emote.subscriber_only, 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 = '-webkit-' + img_set;
s.style.backgroundImage = '-moz-' + img_set; s.style.backgroundImage = '-moz-' + img_set;
s.style.backgroundImage = '-ms-' + img_set; s.style.backgroundImage = '-ms-' + img_set;
@ -450,7 +464,7 @@ FFZ.menu_pages.channel = {
s.style.width = emote.width + "px"; s.style.width = emote.width + "px";
s.style.height = emote.height + "px"; s.style.height = emote.height + "px";
s.title = emote.regex; s.title = (this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + emote.id + '/3.0?_=preview">' : '') + emote.regex;
s.addEventListener('click', function(can_use, id, code, e) { s.addEventListener('click', function(can_use, id, code, e) {
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )

View file

@ -2,7 +2,6 @@ var FFZ = window.FrankerFaceZ,
constants = require("../constants"), constants = require("../constants"),
utils = require("../utils"), utils = require("../utils"),
TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/",
BANNED_SETS = {"00000turbo":true}; BANNED_SETS = {"00000turbo":true};
@ -166,11 +165,14 @@ FFZ.menu_pages.myemotes = {
if ( (settings === 1 && ! emoji.tw) || (settings === 2 && ! emoji.noto) ) if ( (settings === 1 && ! emoji.tw) || (settings === 2 && ! emoji.noto) )
continue; continue;
em.className = 'emoticon html-tooltip'; var src = settings === 2 ? emoji.noto_src : emoji.tw_src,
em.title = 'Emoji: ' + emoji.raw + '\nName: ' + emoji.name + (emoji.short_name ? '\nShort Name: :' + emoji.short_name + ':' : ''); image = this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + src + '">' : '';
em.className = 'emoticon emoji html-tooltip';
em.title = image + 'Emoji: ' + emoji.raw + '<br>Name: ' + emoji.name + (emoji.short_name ? '<br>Short Name: :' + emoji.short_name + ':' : '');
em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw)); 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"; em.style.backgroundSize = "18px";
menu.appendChild(em); menu.appendChild(em);
@ -237,21 +239,21 @@ FFZ.menu_pages.myemotes = {
code = constants.KNOWN_CODES[emote.code] || emote.code, code = constants.KNOWN_CODES[emote.code] || emote.code,
em = document.createElement('span'), 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'; em.className = 'emoticon html-tooltip';
if ( this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[emote.id] ) { if ( this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[emote.id] ) {
em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")'; em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")';
} else { } 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 = '-webkit-' + img_set;
em.style.backgroundImage = '-moz-' + img_set; em.style.backgroundImage = '-moz-' + img_set;
em.style.backgroundImage = '-ms-' + img_set; em.style.backgroundImage = '-ms-' + img_set;
em.style.backgroudnImage = img_set; em.style.backgroudnImage = img_set;
} }
em.title = code; em.title = (this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + emote.id + '/3.0?_=preview">' : '') + code;
em.addEventListener("click", function(id, c, e) { em.addEventListener("click", function(id, c, e) {
e.preventDefault(); e.preventDefault();
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )

View file

@ -84,7 +84,7 @@ FFZ.prototype._update_subscribers = function() {
}); });
cont.appendChild(stat); 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; el.innerHTML = sub_count;

38
src/ui/tooltips.js Normal file
View file

@ -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');
}
}

View file

@ -66,6 +66,6 @@ FFZ.ws_commands.viewers = function(data) {
view_count.innerHTML = content; view_count.innerHTML = content;
parent.appendChild(view_count); 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')});
} }
} }

View file

@ -182,10 +182,98 @@ var sanitize_el = document.createElement('span'),
out = es[variant] = r.join("-"); out = es[variant] = r.join("-");
return out; 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 = '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + data.id + '/3.0?_=preview">';
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 + "<br>" + (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 = { module.exports = {
build_srcset: build_srcset,
build_tooltip: build_tooltip,
load_emote_data: load_emote_data,
update_css: function(element, id, css) { update_css: function(element, id, css) {
var all = element.innerHTML, var all = element.innerHTML,
start = "/*BEGIN " + id + "*/", 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, splitIRCMessage: splitIRCMessage,
parseIRCTags: parseIRCTags, parseIRCTags: parseIRCTags,

View file

@ -21,20 +21,20 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
.ffz-hide-recent-past-broadcast .recent-past-broadcast, .ffz-hide-recent-past-broadcast .recent-past-broadcast,
.ffz-hide-view-count .stat.twitch-channel-views, .ffz-hide-view-count .stat.twitch-channel-views,
.ffz-minimal-chat-input .emoticon-selector-toggle, .ffz-minimal-chat-input .chat-interface .emoticon-selector-toggle,
.ffz-menu-replace .emoticon-selector-toggle { .ffz-menu-replace .chat-interface .emoticon-selector-toggle {
display: none !important; 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) .chat-interface .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 + script + .ffz-ui-toggle svg
{ {
height: 14px; height: 14px;
width: 18px; 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) .chat-interface .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 + script + .ffz-ui-toggle {
height: 14px; height: 14px;
width: 18px; width: 18px;
top: 28px; top: 28px;
@ -263,6 +263,11 @@ body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .emoticon-selector-togg
margin-bottom: 30px; 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:focus #hostmode > div.clearfix,
.ffz-theater-stats .app-main.theatre .player-column:hover #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, .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; } .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 */ /* Menu Scrollbar */
.ffz-scrollbar::-webkit-scrollbar,
.ember-chat .chat-settings::-webkit-scrollbar,
.conversations-list .conversations-list-inner::-webkit-scrollbar, .conversations-list .conversations-list-inner::-webkit-scrollbar,
.conversation-window .conversation-content::-webkit-scrollbar, .conversation-window .conversation-content::-webkit-scrollbar,
.chat-history::-webkit-scrollbar, .chat-history::-webkit-scrollbar,
@ -910,6 +924,8 @@ span.ffz-handle:after { left: 8px }
width: 6px; width: 6px;
} }
.ffz-scrollbar::-webkit-scrollbar-thumb,
.ember-chat .chat-settings::-webkit-scrollbar-thumb,
.conversations-list .conversations-list-inner::-webkit-scrollbar-thumb, .conversations-list .conversations-list-inner::-webkit-scrollbar-thumb,
.conversation-window .conversation-content::-webkit-scrollbar-thumb, .conversation-window .conversation-content::-webkit-scrollbar-thumb,
.chat-history::-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); 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 .table::-webkit-scrollbar-thumb,
.ffz-dark .conversation-window .conversation-content::-webkit-scrollbar-thumb, .ffz-dark .conversation-window .conversation-content::-webkit-scrollbar-thumb,
.ffz-dark .conversations-list .conversations-list-inner::-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 .conversation-window .conversation-content::-webkit-scrollbar-thumb,
.theatre .conversations-list .conversations-list-inner::-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-menu-page::-webkit-scrollbar-thumb,
.theatre .ffz-ui-sub-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 .chat-history::-webkit-scrollbar-thumb,
.dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, .dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
.dark .ffz-ui-menu-page::-webkit-scrollbar-thumb, .dark .ffz-ui-menu-page::-webkit-scrollbar-thumb,
.dark .ffz-ui-sub-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 .chat-history::-webkit-scrollbar-thumb,
.force-dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, .force-dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
.force-dark .ffz-ui-menu-page::-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 { .ffz-moderation-card {
border: 2px solid #cbcbcb; border: 2px solid #cbcbcb;
max-width: 340px; max-width: 340px;
/*box-shadow: #808080 0 0 5px;*/
} }
.ffz-moderation-card .extra-interface { .ffz-moderation-card .extra-interface {
@ -967,6 +990,7 @@ img.channel_background[src="null"] { display: none; }
.ffz-moderation-card.ffz-has-info h3.name { .ffz-moderation-card.ffz-has-info h3.name {
margin-top: 0; margin-top: 0;
white-space: nowrap;
} }
.ffz-moderation-card .info { .ffz-moderation-card .info {
@ -1073,7 +1097,7 @@ img.channel_background[src="null"] { display: none; }
} }
.mod-icons .custom { .mod-icons .custom {
text-indent: 0; text-indent: 0 !important;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
font-size: 18px; font-size: 18px;
@ -1106,7 +1130,7 @@ img.channel_background[src="null"] { display: none; }
text-decoration: none; text-decoration: none;
} }
.more-messages-indicator { body:not(.ffz-bttv) .more-messages-indicator {
/* This looks better when it's full width. */ /* This looks better when it's full width. */
margin: 0 -20px; margin: 0 -20px;
} }
@ -2047,6 +2071,8 @@ li[data-name="following"] a {
/* Conversations */ /* Conversations */
.conversation-chat-line.action .colon,
.conversation-input-bar .emoticon-selector .tabs,
.conversation-preview-line .badges, .conversation-preview-line .badges,
body.ffz-conv-title-clickable .conversation-header span.conversation-header-name, 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 } body:not(.ffz-conv-title-clickable) .conversation-header a.conversation-header-name { display:none }