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