diff --git a/dark.css b/dark.css
index e2081742..d2df4c16 100644
--- a/dark.css
+++ b/dark.css
@@ -67,7 +67,7 @@
color:rgb(222,222,222)!important;
}
-.ffz-dark .moderation-card .button:not(.glyph-only):hover {
+.ffz-dark .moderation-card .button:not(.button--icon-only):hover {
color: #fff !important;
}
@@ -204,10 +204,16 @@ body.ffz-dark:not([data-page="teams#show"]),
.ffz-dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box { border: none }
.ffz-dark .balloon--up:after { box-shadow: 1px 1px 0 rgba(255,255,255,0.2) }
+.ffz-dark .balloon--right.balloon--down:after,
.ffz-dark .balloon--down:after { box-shadow: -1px -1px 0 rgba(255,255,255,0.2) }
.ffz-dark .balloon--left:after { box-shadow: 1px -1px 0 rgba(255,255,255,0.2) }
.ffz-dark .balloon--right:after { box-shadow: -1px 1px 0 rgba(255,255,255,0.2) }
+.ffz-dark .balloon__footer {
+ background-color: rgb(25,25,25);
+ border: 1px solid rgba(255,255,255,0.2);
+}
+
.ffz-dark .player-menu__header { color: #c3c3c3 }
.ffz-dark .player-menu__section { border-bottom-color: rgba(255,255,255,0.2) }
@@ -349,15 +355,17 @@ body.ffz-dark:not([data-page="teams#show"]),
background-color: #25252a;
}
-.ffz-dark .button:not(.button--status):not(.primary) {
+.ffz-dark .message-button,
+.ffz-dark .button--text,
+.ffz-dark .button.ffz-no-bg {
color: #a68ed2;
}
-.ffz-dark .button.glyph-only svg path {
+.ffz-dark .button.button--icon-only svg path {
fill: #a68ed2;
}
-.ffz-dark .button.glyph-only:hover svg path {
+.ffz-dark .button.button--icon-only:hover svg path {
fill: #fff;
}
@@ -565,10 +573,6 @@ body.ffz-dark:not([data-page="teams#show"]),
.ffz-dark .toggle-darkmode { display: none; }
/* Chat Text Contrast */
-.ffz-dark .ember-chat-container.dark .chat-line,
-.ffz-dark .chat-container.dark .chat-line {
- color: #acacbf;
-}
.ffz-dark .ember-chat .chat-settings .chat-colors .chat-colors-swatch:hover,
.ffz-dark .ember-chat .chat-settings .chat-colors .chat-colors-switch.selected {
@@ -956,6 +960,7 @@ body.ffz-dark:not([data-page="teams#show"]),
.ffz-dark #mantle_skin ul.vtabs li .not_linked,
.ffz-dark #mantle_skin ul.vtabs li.selected a,
.ffz-dark #mantle_skin ul.submenu li a:hover,
+.ffz-dark .sort-contain .sort-options li a:hover,
.ffz-dark #mantle_skin ul.submenu .active a {
color: #fff !important;
}
@@ -1128,8 +1133,8 @@ body.ffz-dark:not([data-page="teams#show"]),
fill: rgba(255,255,255,0.5);
}
-.ffz-dark .conversation-input-bar .emoticon-selector-box .emote-set {
- border-color: #323232;
+.ffz-dark .emoticon-selector-box .emote-set {
+ border-color: #323232 !important;
}
.ffz-dark .conversation-input-bar .emoticon-selector-box .emoticon-grid {
@@ -1203,28 +1208,26 @@ body.ffz-dark:not([data-page="teams#show"]),
.ffz-dark .activity-list-end svg { fill: #474747 }
-.ffz-dark .activity-meta:before { background-color: #474747 }
+.ffz-dark .activity-meta-divider:before { background-color: #474747 }
-.ffz-dark .activity-react__item:hover {
+.ffz-dark .activity-button:hover {
border-color: #d5d5d5;
background-color: #242424;
}
-.ffz-dark .activity-react__item svg.endorse-icon #head__base { fill: #fff }
-
.ffz-dark .activity-create__actions {
background-color: #191919;
box-shadow: 0 -1px 0 #474747;
}
.ffz-dark .activity-create,
-.ffz-dark .activity-react__item {
+.ffz-dark .activity-button {
border-color: #474747;
color: #fff !important;
background-color: #191919;
}
-.ffz-dark .activity-meta:before,
+.ffz-dark .activity-meta-divider:before,
.ffz-dark .activity-card {
border-color: #474747;
}
@@ -1233,11 +1236,12 @@ body.ffz-dark:not([data-page="teams#show"]),
background-color: #191919;
}
-.ffz-dark .activity-meta {
+.ffz-dark .activity-meta-divider {
box-shadow: inset 0 -1px 0 #474747;
}
.ffz-dark a.balloon__link:hover { color: #fff !important }
+.ffz-dark .activity-meta__name { color: #ccc }
/* Search Panel */
diff --git a/src/badges.js b/src/badges.js
index dcdc2fa1..e788c832 100644
--- a/src/badges.js
+++ b/src/badges.js
@@ -128,8 +128,8 @@ FFZ.settings_info.sub_notice_badges = {
value: false,
category: "Chat Appearance",
- name: "Subscriber Notice Badges",
- help: "Display a subscriber badge on chat messages about new subscribers.",
+ name: "Old-Style Subscriber Notice Badges",
+ help: "Display a subscriber badge on old-style chat messages about new subscribers.",
on_update: function(val) {
this.toggle_style('badges-sub-notice', ! val);
@@ -348,8 +348,16 @@ FFZ.prototype.get_line_badges = function(msg) {
globals = badgeCollection && badgeCollection.global || {},
channel = badgeCollection && badgeCollection.channel || {};
+ // Whisper Chat Lines have a non-associative array for some reason.
+ if ( Array.isArray(badge_tag) ) {
+ var val = badge_tag;
+ badge_tag = {};
+ for(var i=0; i < val.length; i++)
+ badge_tag[val[i].id] = val[i].version;
+ }
+
// VoD Chat lines don't have the badges pre-parsed for some reason.
- if ( typeof badge_tag === 'string' ) {
+ else if ( typeof badge_tag === 'string' ) {
var val = badge_tag.split(',');
badge_tag = {};
for(var i=0; i < val.length; i++) {
@@ -367,20 +375,6 @@ FFZ.prototype.get_line_badges = function(msg) {
var versions = channel[badge] || globals[badge],
binfo = versions && versions.versions && versions.versions[version];
- if ( from === 'sirstendec' && badge === 'turbo' && globals.warcraft ) {
- badge = 'warcraft';
- version = 'protoss';
- binfo = {
- click_action: 'visit_url',
- click_url: 'https://www.youtube.com/watch?v=dpBM2FIHprM',
- description: 'My life for Aiur!',
- title: 'Protoss',
- image_url_1x: 'https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/1.png',
- image_url_2x: 'https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/2.png',
- image_url_3x: 'https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/4.png'
- }
- }
-
if ( hidden_badges.indexOf(badge) !== -1 )
continue;
@@ -456,7 +450,7 @@ FFZ.prototype.render_badges = function(badges) {
if ( badge.click_url )
klass += ' click_url';
- out.push('
');
+ out.push('');
}
return out.join("");
diff --git a/src/colors.js b/src/colors.js
index 8d288470..38b1abbb 100644
--- a/src/colors.js
+++ b/src/colors.js
@@ -46,12 +46,12 @@ FFZ.settings_info.fix_color = {
},
on_update: function(val) {
- this.toggle_style('chat-colors-gray', !this.has_bttv && val === '-1');
+ this.toggle_style('chat-colors-gray', !this.has_bttv && val === '-1');
- if ( ! this.has_bttv && val !== '-1' )
- this._rebuild_colors();
- }
- };
+ if ( ! this.has_bttv && val !== '-1' )
+ this._rebuild_colors();
+ }
+};
FFZ.settings_info.luv_contrast = {
diff --git a/src/constants.js b/src/constants.js
index c85845ab..f3d5eabe 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -3,6 +3,10 @@ var SVGPATH = 'm120.95 1.74c4.08-0.09 8.33-0.84 12.21 0.82 3.61 1.8 7 4.16 11.01
DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev'),
SERVER = DEBUG ? "//localhost:8000/" : "https://cdn.frankerfacez.com/",
+ IS_OSX = navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent),
+
+ IS_WIN = navigator.platform ? navigator.platform.indexOf('Win') !== -1 : /Windows/.test(navigator.userAgent),
+
SEPARATORS = "[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]",
SPLITTER = new RegExp(SEPARATORS + "*," + SEPARATORS + "*"),
@@ -15,7 +19,9 @@ module.exports = FrankerFaceZ.constants = {
DEBUG: DEBUG,
SERVER: SERVER,
- IS_OSX: navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent),
+ IS_OSX: IS_OSX,
+ IS_WIN: IS_WIN,
+ META_NAME: IS_OSX ? "⌘" : (IS_WIN ? "Win" : "Meta"),
// Twitch Client ID for API Stuff
CLIENT_ID: "a3bc9znoz6vi8ozsoca0inlcr4fcvkl",
diff --git a/src/ember/channel.js b/src/ember/channel.js
index 56b0168d..c27db74b 100644
--- a/src/ember/channel.js
+++ b/src/ember/channel.js
@@ -111,7 +111,6 @@ FFZ.prototype.setup_channel = function() {
var game = data.stream.game || (data.stream.channel && data.stream.channel.game);
if ( game ) {
t.set('content.game', game);
- t.set('content.rollbackData.game', game);
}
if ( data.stream.channel ) {
@@ -228,9 +227,9 @@ FFZ.prototype._modify_cindex = function(view) {
f.rebuild_race_ui();
if ( f.settings.auto_theater ) {
- var Layout = utils.ember_lookup('service:layout');
- if ( Layout )
- Layout.set('isTheatreMode', true);
+ var player = f.players && f.players[id] && f.players[id].get('player');
+ if ( player )
+ player.setTheatre(true);
}
this.$().on("click", ".ffz-creative-tag-link", function(e) {
@@ -296,7 +295,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! btn ) {
btn = document.createElement('span');
btn.id = 'ffz-ui-host-button';
- btn.className = 'button action';
+ btn.className = 'button button--text';
btn.addEventListener('click', this.ffzClickHost.bind(this, false));
@@ -336,7 +335,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! btn ) {
btn = document.createElement('span');
btn.id = 'ffz-ui-host-button';
- btn.className = 'button action';
+ btn.className = 'button button--text';
btn.addEventListener('click', this.ffzClickHost.bind(this, true));
diff --git a/src/ember/chat-input.js b/src/ember/chat-input.js
index 2f8168b2..f1a37c7c 100644
--- a/src/ember/chat-input.js
+++ b/src/ember/chat-input.js
@@ -121,6 +121,35 @@ FFZ.settings_info.input_complete_emotes = {
}
+FFZ.settings_info.input_complete_aliases = {
+ type: "select",
+ options: {
+ 0: "Disabled",
+ 1: "By Name or Alias",
+ 2: "Aliases Only"
+ },
+
+ value: 1,
+
+ process_value: function(val) {
+ if ( typeof val === 'string' )
+ return parseInt(val) || 0;
+ return val;
+ },
+
+ category: "Chat Input",
+ no_bttv: true,
+
+ name: "Tab-Complete User Aliases",
+ help: "Use tab completion to complete aliases you've given to users rather than their username.",
+
+ on_update: function(val) {
+ if ( this._inputv )
+ Ember.propertyDidChange(this._inputv, 'ffz_name_suggestions');
+ }
+}
+
+
FFZ.settings_info.input_complete_name_at = {
type: "boolean",
value: true,
@@ -479,8 +508,11 @@ FFZ.prototype._modify_chat_input = function(component) {
ind = this.get('ffz_partial_word_start'),
text = this.get('textareaValue'),
- content = ((f.settings.input_complete_name_at && item.type === 'user' && this.get('ffz_partial_word').charAt(0) === '@') ? '@' : '') +
- ((item.command_content && text.charAt(0) === '/' ?
+ first_char = text.charAt(0),
+ is_cmd = first_char === '/' || first_char === '.',
+
+ content = ((f.settings.input_complete_name_at && ! is_cmd && item.type === 'user' && this.get('ffz_partial_word').charAt(0) === '@') ? '@' : '') +
+ ((item.command_content && is_cmd ?
item.command_content : item.content) || item.label),
trail = text.substr(ind + this.get('ffz_partial_word').length),
@@ -664,26 +696,50 @@ FFZ.prototype._modify_chat_input = function(component) {
// Always include Users
- var user_output = {};
+ var user_output = {},
+ alias_setting = f.settings.input_complete_aliases;
+
for(var i=0; i < suggestions.length; i++) {
var suggestion = suggestions[i],
- name = suggestion.id;
+ name = suggestion.id,
+ alias = f.aliases[name];
- if ( user_output[name] ) {
+ if ( user_output[name] && ! user_output[name].is_alias ) {
var token = user_output[name];
token.whispered |= suggestion.whispered;
if ( suggestion.timestamp > token.timestamp )
token.timestamp = suggestion.timestamp;
- } else
- output.push(user_output[name] = {
- type: "user",
- command_content: name,
- label: FFZ.get_capitalization(name),
- whispered: suggestion.whispered,
- timestamp: suggestion.timestamp || new Date(0),
- info: 'User'
- });
+ } else {
+ if ( alias_setting !== 2 )
+ output.push(user_output[name] = {
+ type: "user",
+ command_content: name,
+ label: FFZ.get_capitalization(name),
+ whispered: suggestion.whispered,
+ timestamp: suggestion.timestamp || new Date(0),
+ info: 'User',
+ is_alias: false
+ });
+
+ if ( alias && alias_setting ) {
+ if ( user_output[alias] && user_output[alias].is_alias ) {
+ var token = user_output[name];
+ token.whispered |= suggestion.whispered;
+ token.timestamp = Math.max(token.timestamp, suggestion.timestamp);
+
+ } else if ( ! user_output[alias] )
+ output.push(user_output[alias] = {
+ type: "user",
+ command_content: name,
+ label: alias,
+ whispered: suggestion.whispered,
+ timestamp: suggestion.timestamp || new Date(0),
+ info: 'User Alias',
+ is_alias: true
+ });
+ }
+ }
}
return output;
diff --git a/src/ember/chatview.js b/src/ember/chatview.js
index e54ccf3d..5ae03593 100644
--- a/src/ember/chatview.js
+++ b/src/ember/chatview.js
@@ -974,12 +974,12 @@ FFZ.prototype._modify_cview = function(view) {
if ( current_channel ) {
icon.innerHTML = constants.CAMERA;
row.title = "Current Channel";
- row.classList.add('tooltip');
+ row.classList.add('html-tooltip');
} else if ( host_channel ) {
icon.innerHTML = constants.EYE;
row.title = "Hosted Channel";
- row.classList.add('tooltip');
+ row.classList.add('html-tooltip');
}
name_el.className = 'ffz-room';
@@ -1008,7 +1008,7 @@ FFZ.prototype._modify_cview = function(view) {
});
} else {
btn = document.createElement('a');
- btn.className = 'leave-chat tooltip';
+ btn.className = 'leave-chat html-tooltip';
btn.innerHTML = constants.CLOSE;
btn.title = 'Leave Group';
@@ -1090,9 +1090,9 @@ FFZ.prototype._modify_cview = function(view) {
// Chat Room Management Button
- link.className = 'button glyph-only';
+ link.className = 'button button--icon-only';
link.title = "Chat Room Management";
- link.innerHTML = constants.ROOMS + '';
+ link.innerHTML = '' + constants.ROOMS + '';
jQuery(link).tipsy({gravity: "n", offset: 5});
@@ -1106,9 +1106,9 @@ FFZ.prototype._modify_cview = function(view) {
// Invite Button
link = document.createElement('a'),
- link.className = 'button glyph-only tooltip invite';
+ link.className = 'button button--icon-only html-tooltip invite';
link.title = "Invite a User";
- link.innerHTML = constants.INVITE;
+ link.innerHTML = '' + constants.INVITE + '';
link.addEventListener('click', function() {
var controller = view.get('controller');
@@ -1170,7 +1170,7 @@ FFZ.prototype._modify_cview = function(view) {
tab.setAttribute('data-room', room_id);
- tab.className = 'ffz-chat-tab tooltip';
+ tab.className = 'ffz-chat-tab html-tooltip';
tab.classList.toggle('current-channel', current_channel);
tab.classList.toggle('host-channel', host_channel);
tab.classList.toggle('group-chat', group);
diff --git a/src/ember/conversations.js b/src/ember/conversations.js
index 8d879f7c..55e1bb9d 100644
--- a/src/ember/conversations.js
+++ b/src/ember/conversations.js
@@ -224,7 +224,7 @@ FFZ.prototype._modify_conversation_line = function(component) {
colored = style ? ' has-color' : '';
if ( alias )
- e.push('' + utils.sanitize(alias) + '');
+ e.push('' + utils.sanitize(alias) + '');
else
e.push('' + utils.sanitize(name) + '');
diff --git a/src/ember/following.js b/src/ember/following.js
index 6a5dce64..c5aeef6c 100644
--- a/src/ember/following.js
+++ b/src/ember/following.js
@@ -189,13 +189,13 @@ FFZ.prototype.setup_profile_following = function() {
this._hook_followers(followers);
var counted = false;
- if ( following.get('isLoaded') || following.get('isLoading') ) {
+ if ( following && (following.get('isLoaded') || following.get('isLoading')) ) {
refresher(following);
count++;
counted = true;
}
- if ( followers.get('isLoaded') || followers.get('isLoading') ) {
+ if ( followers && (followers.get('isLoaded') || followers.get('isLoading')) ) {
refresher(followers);
if ( ! counted )
count++;
@@ -285,8 +285,8 @@ FFZ.prototype._modify_display_followed_item = function(component) {
return;
var actions = createElement('div', 'actions'),
- follow = createElement('button', 'button follow'),
- notif = createElement('button', 'button notifications'),
+ follow = createElement('button', 'button ffz-no-bg follow'),
+ notif = createElement('button', 'button ffz-no-bg notifications'),
update_follow = function() {
data = user_cache[user_id];
@@ -358,7 +358,7 @@ FFZ.prototype._modify_display_followed_item = function(component) {
FFZ.prototype._hook_following = function(Following) {
var f = this;
- if ( Following.ffz_hooked )
+ if ( ! Following || Following.ffz_hooked )
return;
Following.reopen({
@@ -398,7 +398,7 @@ FFZ.prototype._hook_following = function(Following) {
FFZ.prototype._hook_followers = function(Followers) {
var f = this;
- if ( Followers.ffz_hooked )
+ if ( ! Followers || Followers.ffz_hooked )
return;
Followers.reopen({
diff --git a/src/ember/line.js b/src/ember/line.js
index 10217bb2..ec691607 100644
--- a/src/ember/line.js
+++ b/src/ember/line.js
@@ -299,7 +299,7 @@ FFZ.settings_info.chat_rows = {
on_update: function(val) {
this.toggle_style('chat-background', !this.has_bttv && val);
- this.toggle_style('chat-setup', !this.has_bttv && (val || this.settings.chat_separators));
+ this.toggle_style('chat-setup', !this.has_bttv && (val || this.settings.chat_separators || this.settings.highlight_messages_with_mod_card));
}
};
@@ -332,7 +332,7 @@ FFZ.settings_info.chat_separators = {
help: "Display thin lines between chat messages for further visual separation.",
on_update: function(val) {
- this.toggle_style('chat-setup', !this.has_bttv && (val || this.settings.chat_rows));
+ this.toggle_style('chat-setup', !this.has_bttv && (val || this.settings.chat_rows || this.settings.highlight_messages_with_mod_card));
this.toggle_style('chat-separator', !this.has_bttv && val);
this.toggle_style('chat-separator-3d', !this.has_bttv && val === 2);
@@ -342,6 +342,18 @@ FFZ.settings_info.chat_separators = {
};
+FFZ.settings_info.old_sub_notices = {
+ type: "boolean",
+ value: false,
+
+ category: "Chat Appearance",
+ no_bttv: true,
+
+ name: "Old-Style Subscriber Notices",
+ help: "Display the old style subscriber notices, with the message on a separate line."
+};
+
+
FFZ.settings_info.chat_padding = {
type: "boolean",
value: false,
@@ -577,7 +589,7 @@ FFZ.prototype.setup_line = function() {
// Chat Enhancements
document.body.classList.toggle('ffz-alias-italics', this.settings.alias_italics);
- this.toggle_style('chat-setup', !this.has_bttv && (this.settings.chat_rows || this.settings.chat_separators));
+ this.toggle_style('chat-setup', !this.has_bttv && (this.settings.chat_rows || this.settings.chat_separators || this.settings.highlight_messages_with_mod_card));
this.toggle_style('chat-padding', !this.has_bttv && this.settings.chat_padding);
this.toggle_style('chat-background', !this.has_bttv && this.settings.chat_rows);
@@ -708,22 +720,22 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
if ( btn === false ) {
if ( deleted )
- output += 'Unban';
+ output += 'Unban';
else
- output += 'Ban';
+ output += 'Ban';
} else if ( btn === 600 )
- output += 'Timeout';
+ output += 'Timeout';
else {
if ( typeof btn === "string" ) {
cmd = btn.replace(/{user}/g, user).replace(/ * */, "\n");
- tip = "Custom Command" + (cmd.indexOf("\n") !== -1 ? 's' : '') + '\n' + cmd;
+ tip = "Custom Command" + (cmd.indexOf("\n") !== -1 ? 's' : '') + '
' + utils.quote_san(cmd).replace('\n','
');
} else {
cmd = "/timeout " + user + " " + btn;
tip = "Timeout User (" + utils.duration_string(btn) + ")";
}
- output += '' + prefix + '';
+ output += '' + prefix + '';
}
}
@@ -750,10 +762,20 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
is_dark = (Layout && Layout.get('isTheatreMode')) || (is_replay ? f.settings.dark_twitch : (Settings && Settings.get('settings.darkMode'))),
+ system_msg = this.get('systemMsg'),
output = '';
+ output = '';
- output = '' + this.get('timestamp') + ' ';
+ // System Message
+ if ( system_msg ) {
+ output += '' + utils.sanitize(system_msg) + '
';
+ if ( this.get('shouldRenderMessageBody') === false )
+ return output;
+ }
+
+ // Timestamp
+ output += '' + this.get('timestamp') + ' ';
// Moderator Actions
output += this.buildModIconsHTML();
@@ -767,10 +789,10 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
style = colors && 'color:' + (is_dark ? colors[1] : colors[0]) || '',
colored = style ? ' has-color' + (is_replay ? ' replay-color' : '') : '';
- output += '' + utils.sanitize(alias);
+ output += '" title="' + utils.quote_san(name) + '">' + utils.sanitize(alias);
else
output += '">' + utils.sanitize(name);
@@ -787,10 +809,10 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
to_colored = to_style ? ' has-color' : '';
output += "";
- output += '' + utils.sanitize(to_alias);
+ output += '" title="' + utils.quote_san(to_name) + '">' + utils.sanitize(to_alias);
else
output += '">' + utils.sanitize(to_name);
}
@@ -833,10 +855,12 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
var el = this.get('element'),
output = this.buildSenderHTML();
- if ( this.get('msgObject.deleted') )
- output += this.buildDeletedMessageHTML()
- else
- output += this.buildMessageHTML();
+ // If this is a whisper, or if we should render the message body, render it.
+ if ( this.get('shouldRenderMessageBody') !== false )
+ if ( this.get('msgObject.deleted') )
+ output += this.buildDeletedMessageHTML()
+ else
+ output += this.buildMessageHTML();
el.innerHTML = output;
},
@@ -932,10 +956,18 @@ FFZ.prototype._modify_chat_subline = function(component) {
return;
else if ( e.target.classList.contains('from') ) {
- var n = this.$();
+ var n = this.get('element'),
+ bounds = n && n.getBoundingClientRect() || document.body.getBoundingClientRect(),
+ x = 0, right;
+
+ if ( bounds.left > 400 )
+ right = bounds.left - 40;
+
this.sendAction("showModOverlay", {
- left: n.offset().left,
- top: n.offset().top + n.height(),
+ left: bounds.left,
+ right: right,
+ top: bounds.top + bounds.height,
+ real_top: bounds.top,
sender: from
});
@@ -995,7 +1027,7 @@ FFZ.prototype._modify_vod_line = function(component) {
return '' +
(this.get('msgObject.deleted') ?
'' :
- 'Delete') + '';
+ 'Delete') + '';
},
buildDeletedMesageHTML: function() {
diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js
index 2910f42d..6d2aade5 100644
--- a/src/ember/moderation-card.js
+++ b/src/ember/moderation-card.js
@@ -1,6 +1,7 @@
var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
constants = require("../constants"),
+ styles = require("../compiled_styles"),
helpers,
TO_REG = /^\/t(?:imeout)? +([^ ]+)(?: +(\d+)(?: +(.+))?)?$/,
@@ -66,24 +67,112 @@ FFZ.basic_settings.chat_hover_pause = {
};
-FFZ.settings_info.chat_hover_pause = {
+FFZ.settings_info.highlight_messages_with_mod_card = {
type: "boolean",
value: false,
+ no_bttv: true,
+ category: "Chat Moderation",
+ name: "Highlight Messages with Mod Card Open",
+ help: "Highlight a user's messages in chat when their moderation card is open.",
+
+ on_update: function(val) {
+ this.toggle_style('chat-setup', !this.has_bttv && (this.settings.chat_rows || this.settings.chat_separators || val));
+
+ if ( ! this._mod_card )
+ return;
+
+ if ( val )
+ utils.update_css(this._chat_style, 'mod-card-highlight', styles['chat-user-bg'].replace(/{user_id}/g, this._mod_card.get('cardInfo.user.id')));
+ else
+ utils.update_css(this._chat_style, 'mod-card-highlight');
+ }
+};
+
+
+FFZ.settings_info.chat_mod_icon_visibility = {
+ type: "select",
+ options: {
+ 0: "Disabled",
+ 1: "Enabled",
+ 2: "When Ctrl is Held",
+ 3: "When " + constants.META_NAME + " is Held",
+ 4: "When Alt is Held",
+ 5: "When Shift is Held"
+ },
+
+ value: function() {
+ var settings = utils.ember_lookup('controller:settings');
+ return (settings && settings.get('settings.showModIcons')) ? 1 : 0;
+ },
+
+ process_value: function(val) {
+ if ( typeof val === "string" )
+ return parseInt(val) || 0;
+ return val;
+ },
+
no_bttv: true,
category: "Chat Moderation",
- name: "Pause Chat Scrolling on Mouse Hover",
- help: "Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link misclicks.",
+ name: "Display In-Line Mod Icons",
+ help: "Choose when you should see in-line moderation icons in chat.",
+
+ on_update: function(val) {
+ var settings = utils.ember_lookup('controller:settings');
+ if ( settings )
+ settings.set('settings.showModIcons', val === 1);
+ }
+}
+
+
+FFZ.settings_info.chat_hover_pause = {
+ type: "select",
+ options: {
+ 0: "Disabled",
+ 1: "On Hover",
+ 2: "When Ctrl is Held",
+ 3: "When " + constants.META_NAME + " is Held",
+ 4: "When Alt is Held",
+ 5: "When Shift is Held",
+
+ 6: "Ctrl or Hover",
+ 7: constants.META_NAME + " or Hover",
+ 8: "Alt or Hover",
+ 9: "Shift or Hover"
+ },
+ value: 0,
+
+ process_value: function(val) {
+ if ( val === true )
+ return 1;
+ else if ( val === false )
+ return 0;
+ else if ( typeof val === "string" )
+ return parseInt(val) || 0;
+ return val;
+ },
+
+ no_bttv: true,
+
+ category: "Chat Moderation",
+ name: "Pause Chat Scrolling",
+ help: "Automatically prevent the chat from scrolling when moving the mouse over it or holding Ctrl to prevent moderation mistakes and link misclicks.",
on_update: function(val) {
if ( ! this._roomv )
return;
+ this._roomv.ffzDisableFreeze();
+
+ // Remove the old warning to make sure the label updates.
+ var el = this._roomv.get('element'),
+ warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
+ if ( warning )
+ warning.parentElement.removeChild(warning);
+
if ( val )
this._roomv.ffzEnableFreeze();
- else
- this._roomv.ffzDisableFreeze();
}
};
@@ -495,6 +584,17 @@ FFZ.prototype.setup_mod_card = function() {
} catch(err) { }
+ this.log("Listening to the Settings controller to catch mod icon state changes.");
+ var f = this,
+ Settings = utils.ember_lookup('controller:settings');
+
+ if ( Settings )
+ Settings.addObserver('settings.showModIcons', function() {
+ if ( Settings.get('settings.showModIcons') )
+ f.settings.set('chat_mod_icon_visibility', 1);
+ });
+
+
this.log("Modifying Mousetrap stopCallback so we can catch ESC.");
var orig_stop = Mousetrap.stopCallback;
Mousetrap.stopCallback = function(e, element, combo) {
@@ -511,8 +611,7 @@ FFZ.prototype.setup_mod_card = function() {
this.log("Hooking the Ember Moderation Card view.");
- var Card = utils.ember_resolve('component:chat/moderation-card'),
- f = this;
+ var Card = utils.ember_resolve('component:chat/moderation-card');
Card.reopen({
ffzForceRedraw: function() {
@@ -520,6 +619,10 @@ FFZ.prototype.setup_mod_card = function() {
if ( f.settings.mod_card_history )
this.ffzRenderHistory();
+ // Highlight this user's chat messages.
+ if ( f.settings.highlight_messages_with_mod_card )
+ utils.update_css(f._chat_style, 'mod-card-highlight', styles['chat-user-bg'].replace(/{user_id}/g, this.get('cardInfo.user.id')));
+
}.observes("cardInfo.isModeratorOrHigher", "cardInfo.user.id"),
ffzRebuildInfo: function() {
@@ -528,12 +631,12 @@ FFZ.prototype.setup_mod_card = function() {
if ( ! info )
return;
- var out = '' + constants.EYE + ' ' + utils.number_commas(this.get('cardInfo.user.views') || 0) + '',
+ var out = '' + constants.EYE + ' ' + utils.number_commas(this.get('cardInfo.user.views') || 0) + '',
since = utils.parse_date(this.get('cardInfo.user.created_at') || ''),
followers = this.get('cardInfo.user.ffz_followers');
if ( typeof followers === "number" ) {
- out += '' + constants.HEART + ' ' + utils.number_commas(followers || 0) + '';
+ out += '' + constants.HEART + ' ' + utils.number_commas(followers || 0) + '';
} else if ( followers === undefined ) {
var t = this;
@@ -550,7 +653,7 @@ FFZ.prototype.setup_mod_card = function() {
var now = Date.now() - (f._ws_server_offset || 0),
age = Math.floor((now - since.getTime()) / 1000);
if ( age > 0 ) {
- out += '' + constants.CLOCK + ' ' + utils.human_time(age, 10) + '';
+ out += '' + constants.CLOCK + ' ' + utils.human_time(age, 10) + '';
}
}
@@ -567,6 +670,9 @@ FFZ.prototype.setup_mod_card = function() {
willDestroy: function() {
if ( f._mod_card === this )
f._mod_card = undefined;
+
+ utils.update_css(f._chat_style, 'mod-card-highlight');
+
this._super();
},
@@ -594,6 +700,8 @@ FFZ.prototype.setup_mod_card = function() {
user_id = controller.get('cardInfo.user.id'),
alias = f.aliases[user_id],
+ handle_key,
+
ban_reason = function() {
return ban_reasons && ban_reasons.value ? ' ' + ban_reasons.value : "";
};
@@ -601,6 +709,9 @@ FFZ.prototype.setup_mod_card = function() {
this.ffz_room_id = room_id;
+ // Highlight this user's chat messages.
+ if ( f.settings.highlight_messages_with_mod_card )
+ utils.update_css(f._chat_style, 'mod-card-highlight', styles['chat-user-bg'].replace(/{user_id}/g, user_id));
// Action Override
this.set('banAction', function(e) {
@@ -677,7 +788,7 @@ FFZ.prototype.setup_mod_card = function() {
},
add_btn_make = function(cmd) {
- var btn = document.createElement('button'),
+ var btn = utils.createElement('button', 'button ffz-no-bg'),
segment = cmd.split(' ', 1)[0],
title = cmds[segment] > 1 ? cmd.split(' ', cmds[segment]) : [segment];
@@ -686,7 +797,6 @@ FFZ.prototype.setup_mod_card = function() {
title = _.map(title, function(s){ return s.capitalize() }).join(' ');
- btn.className = 'button';
btn.innerHTML = utils.sanitize(title);
btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}'));
@@ -718,7 +828,7 @@ FFZ.prototype.setup_mod_card = function() {
if ( f.settings.mod_card_hotkeys ) {
el.classList.add('no-mousetrap');
- el.addEventListener('keyup', function(e) {
+ handle_key = function(e) {
var key = e.keyCode || e.which,
user_id = controller.get('cardInfo.user.id'),
is_mod = controller.get('cardInfo.isModeratorOrHigher'),
@@ -753,7 +863,9 @@ FFZ.prototype.setup_mod_card = function() {
return;
t.get('closeAction')();
- });
+ };
+
+ el.addEventListener('keyup', handle_key);
}
@@ -773,13 +885,13 @@ FFZ.prototype.setup_mod_card = function() {
btn_make = function(timeout) {
var btn = document.createElement('button')
- btn.className = 'button';
+ btn.className = 'button ffz-no-bg';
btn.innerHTML = utils.duration_string(timeout);
btn.title = "Timeout User for " + utils.number_commas(timeout) + " Second" + (timeout != 1 ? "s" : "");
if ( f.settings.mod_card_hotkeys && timeout === 600 )
btn.title = "(T)" + btn.title.substr(1);
- else if ( f.settings.mod_card_hotkeys && timeout === 1 )
+ else if ( f.settings.mod_card_hotkeys && timeout === 1 )
btn.title = "(P)urge - " + btn.title;
jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
@@ -812,7 +924,7 @@ FFZ.prototype.setup_mod_card = function() {
if ( f.settings.mod_card_reasons && f.settings.mod_card_reasons.length ) {
// Moderation Reasons
line = utils.createElement('div', 'extra-interface interface clearfix');
- ban_reasons = utils.createElement('select', 'ffz-ban-reasons', '');
+ ban_reasons = utils.createElement('select', 'ffz-ban-reasons', '');
line.appendChild(ban_reasons);
for(var i=0; i < f.settings.mod_card_reasons.length; i++) {
@@ -832,8 +944,8 @@ FFZ.prototype.setup_mod_card = function() {
// Unban Button
var unban_btn = document.createElement('button');
- unban_btn.className = 'unban button glyph-only light';
- unban_btn.innerHTML = CHECK;
+ unban_btn.className = 'unban button button--icon-only light';
+ unban_btn.innerHTML = '' + CHECK + '';
unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User";
jQuery(unban_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
@@ -843,6 +955,10 @@ FFZ.prototype.setup_mod_card = function() {
}
+ // Tooltips for ban and ignore.
+ jQuery("button.ignore, button.ban").tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
+
+
// More Fixing Other Buttons
var op_btn = el.querySelector('button.mod');
if ( op_btn ) {
@@ -863,7 +979,7 @@ FFZ.prototype.setup_mod_card = function() {
var msg_btn = el.querySelector(".interface > button.message-button");
if ( msg_btn ) {
msg_btn.innerHTML = 'W';
- msg_btn.classList.add('glyph-only');
+ msg_btn.classList.add('button--icon-only');
msg_btn.classList.add('message');
msg_btn.title = "Whisper User";
@@ -871,8 +987,8 @@ FFZ.prototype.setup_mod_card = function() {
var real_msg = document.createElement('button');
- real_msg.className = 'message-button button glyph-only message tooltip';
- real_msg.innerHTML = MESSAGE;
+ real_msg.className = 'message-button button button--icon-only message html-tooltip';
+ real_msg.innerHTML = '' + MESSAGE + '';
real_msg.title = "Message User";
real_msg.addEventListener('click', function() {
@@ -885,8 +1001,8 @@ FFZ.prototype.setup_mod_card = function() {
// Alias Button
var alias_btn = document.createElement('button');
- alias_btn.className = 'alias button glyph-only tooltip';
- alias_btn.innerHTML = constants.EDIT;
+ alias_btn.className = 'alias button button--icon-only html-tooltip';
+ alias_btn.innerHTML = '' + constants.EDIT + '';
alias_btn.title = "Set Alias";
alias_btn.addEventListener('click', function() {
@@ -932,26 +1048,7 @@ FFZ.prototype.setup_mod_card = function() {
this.ffzRenderHistory();
// Reposition the menu if it's off-screen.
- var el_bound = el.getBoundingClientRect(),
- body_bound = document.body.getBoundingClientRect(),
-
- renderBottom = this.get('cardInfo.renderBottom'),
- renderRight = this.get('cardInfo.renderRight');
-
- if ( renderRight ) {
- var offset = (el_bound.left + el_bound.width) - renderRight;
- el.style.left = (el_bound.left - offset) + "px";
- }
-
- if ( renderBottom ) {
- var offset = el_bound.bottom - renderBottom;
- el.style.top = (el_bound.top - offset) + "px";
-
- } else if ( el_bound.bottom > body_bound.bottom ) {
- var offset = el_bound.bottom - body_bound.bottom;
- if ( el_bound.top - offset > body_bound.top )
- el.style.top = (el_bound.top - offset) + "px";
- }
+ this.ffzReposition();
// Focus the Element
this.$().draggable({
@@ -968,6 +1065,30 @@ FFZ.prototype.setup_mod_card = function() {
}
},
+ ffzReposition: function() {
+ var el = this.get('element'),
+ el_bound = el.getBoundingClientRect(),
+ body_bound = document.body.getBoundingClientRect(),
+
+ renderBottom = this.get('cardInfo.renderBottom'),
+ renderRight = this.get('cardInfo.renderRight');
+
+ if ( renderRight ) {
+ var offset = (el_bound.left + el_bound.width) - renderRight;
+ el.style.left = (el_bound.left - offset) + "px";
+ }
+
+ if ( renderBottom ) {
+ var offset = el_bound.bottom - renderBottom;
+ el.style.top = (el_bound.top - offset) + "px";
+
+ } else if ( el_bound.bottom > body_bound.bottom ) {
+ var offset = el_bound.bottom - body_bound.bottom;
+ if ( el_bound.top - offset > body_bound.top )
+ el.style.top = (el_bound.top - offset) + "px";
+ }
+ }.observes('cardInfo.renderTop', 'cardInfo.renderLeft', 'cardInfo.renderRight', 'cardInfo.renderBottom'),
+
ffzRenderHistory: function() {
var t = this,
Chat = utils.ember_lookup('controller:chat'),
@@ -1093,7 +1214,7 @@ FFZ.prototype.setup_mod_card = function() {
logs = document.createElement('ul');
back = document.createElement('button');
- back.className = 'button back-button';
+ back.className = 'button ffz-no-bg back-button';
back.innerHTML = '« Back';
back.addEventListener('click', function() {
@@ -1184,7 +1305,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
if ( alias )
- out.push('' + utils.sanitize(alias) + '');
+ out.push('' + utils.sanitize(alias) + '');
else
out.push('' + utils.sanitize(name ) + '');
@@ -1287,6 +1408,11 @@ FFZ.prototype._update_alias = function(user) {
el_from.title = alias ? cap_name : '';
}
+
+ // Update tab completion.
+ if ( this._inputv )
+ Ember.propertyDidChange(this._inputv, 'ffz_name_suggestions');
+
// TODO: Update conversations~
}
diff --git a/src/ember/room.js b/src/ember/room.js
index db74e14b..1a110e8d 100644
--- a/src/ember/room.js
+++ b/src/ember/room.js
@@ -77,10 +77,10 @@ FFZ.prototype.setup_room = function() {
this.set("showModerationCard", true);
// We pass in renderBottom and renderRight, which we use to reposition the window
- // after we know how big it actually is.
+ // after we know how big it actually is. This doesn't work a lot of the time.
this.set("moderationCardInfo", {
user: chan,
- renderTop: e.top,
+ renderTop: e.real_top || e.top,
renderLeft: e.left,
renderBottom: e.bottom,
renderRight: e.right,
@@ -171,6 +171,12 @@ FFZ.prototype._modify_rview = function(view) {
f._roomv = this;
this.ffz_frozen = false;
+ this.ffz_ctrl = false;
+
+ // Monitor the Ctrl key.
+ this._ffz_keyw = this.ffzOnKey.bind(this);
+ document.body.addEventListener('keydown', this._ffz_keyw);
+ document.body.addEventListener('keyup', this._ffz_keyw);
// Fix scrolling.
this._ffz_mouse_down = this.ffzMouseDown.bind(this);
@@ -215,9 +221,43 @@ FFZ.prototype._modify_rview = function(view) {
if ( this._ffz_chat_display )
this._ffz_chat_display = undefined;
+ if ( this._ffz_keyw ) {
+ document.body.removeEventListener('keydown', this._ffz_keyw);
+ document.body.removeEventListener('keyup', this._ffz_keyw);
+ this._ffz_keyw = undefined;
+ }
+
this.ffzDisableFreeze();
},
+ ffzOnKey: function(event) {
+ this.ffz_ctrl = event.ctrlKey;
+ this.ffz_alt = event.altKey;
+ this.ffz_shift = event.shiftKey;
+ this.ffz_meta = event.metaKey;
+
+ var cmi = f.settings.chat_mod_icon_visibility;
+ if ( ! this._ffz_outside && cmi > 1 )
+ this.get('element').classList.toggle('show-mod-icons',
+ cmi === 2 ? this.ffz_ctrl :
+ cmi === 3 ? this.ffz_meta :
+ cmi === 4 ? this.ffz_alt :
+ this.ffz_shift);
+
+ if ( this._ffz_outside || f.settings.chat_hover_pause < 2 )
+ return;
+
+ // Okay, so at this point we should change the state of the freeze?
+ var should_freeze = this.ffzShouldBeFrozen(),
+ freeze_change = this.ffz_frozen !== should_freeze;
+
+ if ( freeze_change )
+ if ( should_freeze )
+ this.ffzFreeze();
+ else
+ this.ffzUnfreeze();
+ },
+
ffzUpdateStatus: function() {
var room = this.get('controller.model'),
el = this.get('element'),
@@ -273,8 +313,8 @@ FFZ.prototype._modify_rview = function(view) {
if ( ! messages )
return;
- this._ffz_interval = setInterval(this.ffzPulse.bind(this), 200);
this._ffz_messages = messages;
+ this._ffz_interval = setInterval(this.ffzPulse.bind(this), 200);
this._ffz_mouse_move = this.ffzMouseMove.bind(this);
this._ffz_mouse_out = this.ffzMouseOut.bind(this);
@@ -311,11 +351,8 @@ FFZ.prototype._modify_rview = function(view) {
},
ffzPulse: function() {
- if ( this.ffz_frozen ) {
- var elapsed = Date.now() - this._ffz_last_move;
- if ( elapsed > 750 )
- this.ffzUnfreeze();
- }
+ if ( this.ffz_frozen && ! this.ffzShouldBeFrozen() )
+ this.ffzUnfreeze();
},
ffzUnfreeze: function(from_stuck) {
@@ -327,11 +364,17 @@ FFZ.prototype._modify_rview = function(view) {
this._scrollToBottom();
},
+ ffzFreeze: function() {
+ this.ffz_frozen = true;
+ if ( this.get('stuckToBottom') ) {
+ this.set('controller.model.messageBufferSize', f.settings.scrollback_length + 150);
+ this.ffzWarnPaused();
+ }
+ },
+
ffzMouseDown: function(event) {
var t = this._$chatMessagesScroller;
if ( t && t[0] && ((!this.ffz_frozen && "mousedown" === event.type) || "mousewheel" === event.type || (is_android && "scroll" === event.type) ) ) {
- if ( event.type === "mousedown" )
- f.log("Freezing from mouse down!", event);
var r = t[0].scrollHeight - t[0].scrollTop - t[0].offsetHeight;
this._setStuckToBottom(10 >= r);
}
@@ -341,29 +384,57 @@ FFZ.prototype._modify_rview = function(view) {
this._ffz_outside = true;
var e = this;
setTimeout(function() {
- if ( e._ffz_outside )
+ if ( e._ffz_outside ) {
+ if ( f.settings.chat_mod_icon_visibility > 1 )
+ e.get('element').classList.toggle('show-mod-icons', false);
e.ffzUnfreeze();
+ }
}, 25);
},
+ ffzShouldBeFrozen: function(since) {
+ if ( since === undefined )
+ since = Date.now() - this._ffz_last_move;
+
+ var hp = f.settings.chat_hover_pause;
+ return (this.ffz_ctrl && (hp === 2 || hp === 6)) || (this.ffz_meta && (hp === 3 || hp === 7)) || (this.ffz_alt && (hp === 4 || hp === 8)) || (this.ffz_shift && (hp === 5 || hp === 9)) || (since < 750 && (hp === 1 || hp > 5));
+ },
+
ffzMouseMove: function(event) {
+ // Store the last move time.
this._ffz_last_move = Date.now();
this._ffz_outside = false;
- if ( event.screenX === this._ffz_last_screenx && event.screenY === this._ffz_last_screeny )
+ // If nothing of interest has happened, stop.
+ if ( event.altKey === this.ffz_alt && event.shiftKey === this.ffz_shift && event.ctrlKey === this.ffz_ctrl && event.metaKey === this.ffz_meta && event.screenX === this._ffz_last_screenx && event.screenY === this._ffz_last_screeny )
return;
+ // Grab a bit of state.
+ this.ffz_ctrl = event.ctrlKey;
+ this.ffz_alt = event.altKey;
+ this.ffz_shift = event.shiftKey;
+ this.ffz_meta = event.metaKey;
+
this._ffz_last_screenx = event.screenX;
this._ffz_last_screeny = event.screenY;
- if ( this.ffz_frozen )
- return;
+ var cmi = f.settings.chat_mod_icon_visibility;
+ if ( ! this._ffz_outside && cmi > 1 )
+ this.get('element').classList.toggle('show-mod-icons',
+ cmi === 2 ? this.ffz_ctrl :
+ cmi === 3 ? this.ffz_meta :
+ cmi === 4 ? this.ffz_alt :
+ this.ffz_shift);
- this.ffz_frozen = true;
- if ( this.get('stuckToBottom') ) {
- this.set('controller.model.messageBufferSize', f.settings.scrollback_length + 150);
- this.ffzWarnPaused();
- }
+ // Should the state have changed?
+ var should_freeze = this.ffzShouldBeFrozen(),
+ freeze_change = this.ffz_frozen !== should_freeze;
+
+ if ( freeze_change )
+ if ( should_freeze )
+ this.ffzFreeze();
+ else
+ this.ffzUnfreeze();
},
_scrollToBottom: _.throttle(function() {
@@ -400,7 +471,19 @@ FFZ.prototype._modify_rview = function(view) {
if ( ! warning ) {
warning = document.createElement('div');
warning.className = 'more-messages-indicator ffz-freeze-indicator';
- warning.innerHTML = '(Chat Paused Due to Mouse Movement)';
+
+ var hp = f.settings.chat_hover_pause,
+ label = hp === 2 ? 'Ctrl Key' :
+ hp === 3 ? (constants.META_NAME + ' Key') :
+ hp === 4 ? 'Alt Key' :
+ hp === 5 ? 'Shift Key' :
+ hp === 6 ? 'Ctrl or Mouse' :
+ hp === 7 ? (constants.META_NAME + ' or Mouse') :
+ hp === 8 ? 'Alt or Mouse' :
+ hp === 9 ? 'Shift or Mouse' :
+ 'Mouse Movement';
+
+ warning.innerHTML = '(Chat Paused Due to ' + label + ')';
var cont = el.querySelector('.chat-interface');
if ( ! cont )
@@ -923,8 +1006,10 @@ FFZ.prototype._modify_room = function(room) {
user = f.get_user(),
room_id = this.get('id');
- if ( (Chat && Chat.get('currentChannelRoom') === this) || (user && user.login === room_id) || (f._chatv && f._chatv._ffz_host === room_id) || (f.settings.pinned_rooms && f.settings.pinned_rooms.indexOf(room_id) !== -1) )
- return this.ffzUnsubscribe(true);
+ /* ???
+ if ( (Chat && Chat.get('currentChannelRoom') === this) || (user && user.login === room_id) || (f._chatv && f._chatv._ffz_host === room_id) || (f.settings.pinned_rooms && f.settings.pinned_rooms.indexOf(room_id) !== -1) )
+ f.ws_unsub()
+ return this.ffzUnsubscribe(true);*/
this.destroy();
},
@@ -1284,6 +1369,31 @@ FFZ.prototype._modify_room = function(room) {
addMessage: function(msg) {
if ( msg ) {
+ var is_resub = msg.tags && msg.tags['msg-id'] === 'resub',
+ room_id = this.get('id');
+
+ // Ignore resubs in other rooms.
+ if ( is_resub && ! f.settings.hosted_sub_notices && (msg.tags['room-id'] != this.get('roomProperties._id') || HOSTED_SUB.test(msg.tags['system-msg'])) )
+ return;
+
+ // Split this into two messages if requested.
+ if ( is_resub && f.settings.old_sub_notices ) {
+ this.addMessage({
+ style: "notification",
+ from: "twitchnotify",
+ date: msg.date || new Date,
+ room: room_id,
+ message: msg.tags['system-msg']
+ });
+
+ // If there's no message just quit now.
+ if ( ! msg.message )
+ return;
+
+ // And delete the system message so it won't render weirdly.
+ msg.tags['system-msg'] = '';
+ }
+
var is_whisper = msg.style === 'whisper';
// Ignore whispers if conversations are enabled.
@@ -1291,7 +1401,7 @@ FFZ.prototype._modify_room = function(room) {
return;
if ( ! is_whisper )
- msg.room = this.get('id');
+ msg.room = room_id;
// Look up color and labels.
if ( this.tmiRoom && msg.from ) {
@@ -1434,6 +1544,13 @@ FFZ.prototype._modify_room = function(room) {
try {
this.ffz_last_input = Date.now();
+ var first_char = text.charAt(0),
+ is_cmd = first_char === '/' || first_char === '.';
+
+ // Strip trailing whitespace from commands.
+ if ( is_cmd )
+ text = text.replace(/\s+$/, '');
+
if ( text && ! ignore_history ) {
// Command History
var mru = this.get('mru_list'),
diff --git a/src/main.js b/src/main.js
index 9a3e5e62..5de67534 100644
--- a/src/main.js
+++ b/src/main.js
@@ -37,7 +37,7 @@ FFZ.msg_commands = {};
// Version
var VER = FFZ.version_info = {
- major: 3, minor: 5, revision: 203,
+ major: 3, minor: 5, revision: 216,
toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
}
@@ -228,7 +228,7 @@ FFZ.prototype.initialize = function(increment, delay) {
if ( location.hostname === 'passport.twitch.tv' || /^\/user\/two_factor/.test(location.pathname) )
return this.log("Found authentication sub-page. Not initializing.");
- if ( ['im.twitch.tv', 'api.twitch.tv'].indexOf(location.hostname) !== -1 )
+ if ( ['im.twitch.tv', 'api.twitch.tv'].indexOf(location.hostname) !== -1 || /^\/products\//.test(location.pathname) )
return this.log("Found banned sub-domain. Not initializing.");
// Check for the player
diff --git a/src/styles/chat-padding.css b/src/styles/chat-padding.css
index e89f59a3..4f9f3250 100644
--- a/src/styles/chat-padding.css
+++ b/src/styles/chat-padding.css
@@ -9,6 +9,9 @@
}
+.ember-chat .chat-messages .chat-line.brick--marked { padding-left: 8px }
+
+
/* Remove Extra Conversation Padding */
.conversation-window .conversation-chat-lines {
padding-top: 0;
diff --git a/src/styles/chat-setup.css b/src/styles/chat-setup.css
index ed57617a..5c3aba69 100644
--- a/src/styles/chat-setup.css
+++ b/src/styles/chat-setup.css
@@ -15,4 +15,12 @@
.chat-history .chat-line:before {
top: 0; bottom: 0;
+}
+
+.chat-line.brick--marked {
+ box-shadow: none;
+}
+
+.chat-line.brick--marked:before {
+ box-shadow: 3px 0 0 #6441a4 inset;
}
\ No newline at end of file
diff --git a/src/styles/chat-user-bg.css b/src/styles/chat-user-bg.css
new file mode 100644
index 00000000..8649b6c6
--- /dev/null
+++ b/src/styles/chat-user-bg.css
@@ -0,0 +1,19 @@
+.chat-lines .chat-line[data-sender="{user_id}"]:before {
+ background-color: rgba(0,127,255, 0.2);
+}
+
+.chat-lines .chat-line[data-sender="{user_id}"]:nth-child(2n+0):before {
+ background-color: rgba(0,127,255, 0.4);
+}
+
+.theatre .chat-lines .chat-line[data-sender="{user_id}"]:before,
+.dark .chat-lines .chat-line[data-sender="{user_id}"]:before,
+.force-dark .chat-lines .chat-line[data-sender="{user_id}"]:before {
+ background-color: rgba(0,63,127, 0.2);
+}
+
+.theatre .chat-lines .chat-line[data-sender="{user_id}"]:nth-child(2n+0):before,
+.dark .chat-lines .chat-line[data-sender="{user_id}"]:nth-child(2n+0):before,
+.force-dark .chat-lines .chat-line[data-sender="{user_id}"]:nth-child(2n+0):before {
+ background-color: rgba(0,63,127, 0.4);
+}
\ No newline at end of file
diff --git a/src/tokenize.js b/src/tokenize.js
index 14db7224..713ca2c7 100644
--- a/src/tokenize.js
+++ b/src/tokenize.js
@@ -3,6 +3,7 @@ var FFZ = window.FrankerFaceZ,
constants = require("./constants"),
helpers,
conv_helpers,
+ emote_helpers,
EXPLANATION_WARN = '
This link has been sent to you via a whisper rather than standard chat, and has not been checked or approved of by any moderators or staff members. Please treat this link with caution and do not visit it if you do not trust the sender.',
@@ -160,6 +161,12 @@ FFZ.prototype.setup_tokenization = function() {
this.error("Unable to get conversation helper functions.", err);
}
+ try {
+ emote_helpers = window.require && window.require("web-client/utilities/tmi-emotes").default;
+ } catch(err) {
+ this.error("Unable to get tmi-emotes helper function.", err);
+ }
+
this.log("Hooking Ember chat line helpers.");
var f = this;
@@ -395,6 +402,9 @@ FFZ.prototype.tokenize_conversation_line = function(message, prevent_notificatio
if ( conv_helpers && conv_helpers.checkActionMessage )
tokens = conv_helpers.checkActionMessage(tokens);
+ if ( emote_helpers )
+ emotes = emote_helpers(emotes);
+
// Standard Tokenization
if ( helpers && helpers.linkifyMessage )
tokens = helpers.linkifyMessage(tokens);
@@ -775,8 +785,8 @@ FFZ.prototype.render_token = function(render_links, warn_links, token) {
}
else if ( token.type === "deleted" )
- return '×××';
- //return `×××`;
+ return '×××';
+ //return `×××`;
else if ( token.type === "mention" )
return '' + utils.sanitize(token.user) + '';
diff --git a/src/ui/following-count.js b/src/ui/following-count.js
index eae293d7..89be25d4 100644
--- a/src/ui/following-count.js
+++ b/src/ui/following-count.js
@@ -69,7 +69,7 @@ FFZ.prototype.setup_following_count = function(has_ember) {
setTimeout(this._install_following_tooltips.bind(this), 2000);
// If we don't have Ember, no point in trying this stuff.
- if ( ! has_ember )
+ if ( this.is_dashboard || ! has_ember )
return this._following_get_me();
this.log("Connecting to Live Streams model.");
@@ -163,10 +163,10 @@ FFZ.prototype._update_following_count = function() {
f = this;
- if ( HostLive && document.body.getAttribute('data-current-path').indexOf('directory.following') !== -1 )
+ if ( ! this.is_dashboard && HostLive && document.body.getAttribute('data-current-path').indexOf('directory.following') !== -1 )
HostLive.load();
- if ( Live )
+ if ( ! this.is_dashboard && Live )
Live.load();
else {
var u = this.get_user();
diff --git a/src/ui/menu.js b/src/ui/menu.js
index f8198d99..bce622bc 100644
--- a/src/ui/menu.js
+++ b/src/ui/menu.js
@@ -612,9 +612,9 @@ FFZ.menu_pages.channel = {
unlock_text.innerHTML = "Subscribe to unlock Emoticons";
nonsub_message.appendChild(unlock_text);
- sub_link.className = "action subscribe-button button primary";
+ sub_link.className = "action js-sub-button subscribe-button button button--purchase";
sub_link.href = product.get("product_url");
- sub_link.innerHTML = 'Subscribe' + product.get("price") + '';
+ sub_link.innerHTML = 'Subscribe' + product.get("price") + '';
nonsub_message.appendChild(sub_link);
inner.appendChild(sub_message);
diff --git a/src/ui/races.js b/src/ui/races.js
index 39abd185..bcd1ad8b 100644
--- a/src/ui/races.js
+++ b/src/ui/races.js
@@ -116,7 +116,7 @@ FFZ.prototype.rebuild_race_ui = function() {
race_container.setAttribute('data-channel', channel_id);
var btn = document.createElement('span');
- btn.className = 'button drop action';
+ btn.className = 'button button--text button--dropmenu';
btn.title = "SpeedRunsLive Race";
btn.innerHTML = '';
@@ -148,7 +148,7 @@ FFZ.prototype.rebuild_race_ui = function() {
race_container.setAttribute('data-channel', hosted_id);
var btn = document.createElement('span');
- btn.className = 'button drop action';
+ btn.className = 'button button--text button--dropmenu';
btn.title = "SpeedRunsLive Race";
btn.innerHTML = '';
@@ -271,7 +271,7 @@ FFZ.prototype._update_race = function(container, not_timer) {
// Make sure we don't leave any tooltips lying around when we update.
// Of course, we should just rewrite logic to not constantly mutilate
// rows.
- jQuery('.tooltip', tbody).trigger('mouseout');
+ jQuery('.html-tooltip', tbody).trigger('mouseout');
tbody.innerHTML = '';
var entrants = [], done = true;
@@ -312,23 +312,23 @@ FFZ.prototype._update_race = function(container, not_timer) {
hitbox_link = ent.hitbox ? '' : '',
time = elapsed ? utils.time_to_string(ent.time||elapsed) : "",
place = utils.place_string(ent.place),
- comment = ent.comment ? utils.sanitize(ent.comment) : "";
+ comment = ent.comment ? utils.quote_san(ent.comment) : "";
- tbody.innerHTML += '';
+ tbody.innerHTML += '';
}
if ( this._race_game != race.game || this._race_goal != race.goal ) {
this._race_game = race.game;
this._race_goal = race.goal;
- var game = utils.sanitize(race.game),
+ var game = utils.quote_san(race.game),
goal = utils.unquote_attr(race.goal),
old_goal = popup.getAttribute('data-old-goal');
if ( goal !== old_goal ) {
popup.setAttribute('data-old-goal', goal);
goal = goal ? this.render_tokens(this.tokenize_line("jtv", null, goal, true)) : '';
- info.innerHTML = 'Goal: ' + goal + '';
+ info.innerHTML = 'Goal: ' + goal + '';
}
}
diff --git a/src/utils.js b/src/utils.js
index 02098620..b3fd18f0 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -29,6 +29,10 @@ var sanitize_el = document.createElement('span'),
return msg.replace(R_AMP, "&").replace(R_QUOTE, """).replace(R_SQUOTE, "'").replace(R_LT, "<").replace(R_GT, ">");
},
+ quote_san = function(msg) {
+ return sanitize(msg).replace(R_QUOTE, """).replace(R_SQUOTE, "'");
+ },
+
HUMAN_NUMBERS = [
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
],
@@ -333,6 +337,10 @@ module.exports = FFZ.utils = {
closer = show_modal(contents, cb, width);
+ try {
+ input.focus();
+ } catch(err) { }
+
form.addEventListener('submit', function(e) { e.preventDefault(); cb(true); return false });
okay_btn.addEventListener('click', function(e) { e.preventDefault(); cb(true); return false });
close_btn.addEventListener('click', function(e) { e.preventDefault(); cb(false); return false });
@@ -423,6 +431,7 @@ module.exports = FFZ.utils = {
sanitize: sanitize,
unquote_attr: unquote_attr,
quote_attr: quote_attr,
+ quote_san: quote_san,
date_string: function(date) {
return date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate();
diff --git a/style.css b/style.css
index 39434302..1661e30a 100644
--- a/style.css
+++ b/style.css
@@ -325,7 +325,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
color: #a68ed2;
}
-.ffz-theater-stats .app-main.theatre .button.glyph-only svg path {
+.ffz-theater-stats .app-main.theatre .button.button--icon-only svg path {
fill: #a68ed2;
}
@@ -336,6 +336,8 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
/* SRL Race Support */
+#ffz-ui-host-button { vertical-align: middle }
+
#ffz-following-popup.right {
right: 0;
left: auto;
@@ -354,6 +356,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
#ffz-ui-race .button span.logo {
padding-left: 44px;
+ margin-bottom: -10px;
background-image: url("//cdn.frankerfacez.com/script/srl_button.png");
}
@@ -1288,8 +1291,11 @@ img.channel_background[src="null"] { display: none; }
.ffz-moderation-card button {
margin: 0;
padding: 0 5px;
+ color: #6441a4;
}
+.ffz-moderation-card .mod-controls button figure { padding: 0 }
+
.ember-chat .ffz-moderation-card .mod-controls button {
width: auto;
margin-right: 10px;
@@ -1306,10 +1312,13 @@ img.channel_background[src="null"] { display: none; }
padding-right: 0 !important;
}
-.ffz-moderation-card .button.glyph-only { padding: 0 !important }
+.ffz-moderation-card .button.button--icon-only {
+ padding: 0 !important;
+ margin-right: 5px;
+}
-.ffz-moderation-card button:not(.glyph-only):hover,
-.ffz-moderation-card button:not(.glyph-only):focus {
+.ffz-moderation-card button:not(.button--icon-only):hover,
+.ffz-moderation-card button:not(.button--icon-only):focus {
color: #fff;
background-color: rgba(117,80,186, 1);
}
@@ -1404,6 +1413,13 @@ img.channel_background[src="null"] { display: none; }
/* Chat Rows */
+.theatre .conversation-window .conversation-chat-line,
+.dark .chat-line,
+.force-dark .chat-line,
+.theatre .chat-line {
+ color: #acacbf;
+}
+
.ffz-alias-italics .ffz-alias { font-style: italic; }
.ember-chat .chat-messages .chat-line.ffz-has-deleted {
@@ -1720,7 +1736,7 @@ th.ffz-row-switch {
margin-right: 4px;
}
-#ffz-group-tabs .button.glyph-only svg {
+#ffz-group-tabs .button.button--icon-only svg {
margin: 6px 0;
}
@@ -2216,6 +2232,14 @@ body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-inter
background-color: #191919;
}
+.ffz-no-blue .theatre .conversation-input-bar textarea,
+.ffz-no-blue .theatre input.text,
+.ffz-no-blue .theatre input.countries-input,
+.ffz-no-blue .theatre .recurly input,
+.ffz-no-blue .recurly .theatre input {
+ background-color: #202020;
+}
+
.ffz-no-blue .warp__anchor,
.ffz-no-blue .warp__item--anchor,
.ffz-no-blue .warp__drawer,
@@ -2966,6 +2990,14 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
background-size: 100%
}*/
+/* Button Fix */
+
+.ffz-no-bg {
+ background: transparent;
+
+}
+
+
/* Odd Badges */
.badge.click_url { cursor: pointer }
@@ -2981,8 +3013,36 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/1.png") 1x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/2.png") 2x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/horde/4.png") 4x);
}
-.badge.warcraft.version-protoss {
- background: url("https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/1.png") #5bc7ff;
- background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/1.png") 1x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/2.png") 2x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/4.png") 4x);
- background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/1.png") 1x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/2.png") 2x,url("https://cdn.frankerfacez.com/badges/twitch/warcraft/protoss/4.png") 4x);
+
+/* New Resub Banner */
+
+.top-notification--resub {
+ position: relative;
+ z-index: 10;
+ pointer-events: none;
+}
+
+.top-notification--resub > * {
+ pointer-events: auto;
+}
+
+.sticky-message {
+ padding: 5px 10px;
+}
+
+.chat-room .show-mod-icons .chat-line:not(.admin) .mod-icons {
+ display: block !important;
+ position: absolute;
+ top: 2px;
+ bottom: 1px;
+ padding: 4px 5px 0;
+ left: 0;
+ z-index: 99;
+ background-color: rgba(255,255,255,0.8);
+}
+
+.theatre .chat-room .show-mod-icons .chat-line:not(.admin) .mod-icons,
+.dark .chat-room .show-mod-icons .chat-line:not(.admin) .mod-icons,
+.force-dark .chat-room .show-mod-icons .chat-line:not(.admin) .mod-icons {
+ background-color: rgba(0,0,0,0.8);
}
\ No newline at end of file