';
var before = room_list.querySelector('#ffz-channel-table');
@@ -1007,8 +952,7 @@ FFZ.prototype._modify_cview = function(view) {
this.classList.toggle('active', !is_pinned);
});
} else {
- btn = document.createElement('a');
- btn.className = 'leave-chat html-tooltip';
+ btn = utils.createElement('a', 'leave-chat html-tooltip');
btn.innerHTML = constants.CLOSE;
btn.title = 'Leave Group';
@@ -1085,12 +1029,10 @@ FFZ.prototype._modify_cview = function(view) {
if ( f.has_bttv || ! f.settings.group_tabs )
return;
- var link = document.createElement('a'),
+ var link = utils.createElement('a', 'button button--icon-only'),
view = this;
-
// Chat Room Management Button
- link.className = 'button button--icon-only';
link.title = "Chat Room Management";
link.innerHTML = '' + constants.ROOMS + '';
@@ -1105,8 +1047,7 @@ FFZ.prototype._modify_cview = function(view) {
// Invite Button
- link = document.createElement('a'),
- link.className = 'button button--icon-only html-tooltip invite';
+ link = utils.createElement('a', 'button button--icon-only html-tooltip invite');
link.title = "Invite a User";
link.innerHTML = '' + constants.INVITE + '';
@@ -1216,6 +1157,10 @@ FFZ.prototype._modify_cview = function(view) {
now = Date.now();
+ // Non-Existant Rooms
+ if ( ! room )
+ return false;
+
if ( is_current || is_channel || room_id === this._ffz_host || f.settings.group_tabs === 3 )
// Important Tabs
return true;
diff --git a/src/ember/conversations.js b/src/ember/conversations.js
index 55e1bb9d..2fe165fa 100644
--- a/src/ember/conversations.js
+++ b/src/ember/conversations.js
@@ -71,42 +71,16 @@ FFZ.prototype.setup_conversations = function() {
document.body.classList.toggle('ffz-minimize-conversations', this.settings.minimize_conversations);
document.body.classList.toggle('ffz-theatre-conversations', this.settings.hide_conversations_in_theatre);
- var ConvWindow = utils.ember_resolve('component:twitch-conversations/conversation-window');
- if ( ConvWindow ) {
- this.log("Hooking the Ember Conversation Window component.");
- this._modify_conversation_window(ConvWindow);
- try { ConvWindow.create().destroy() }
- catch(err) { }
- } else
- this.log("Unable to resolve: component:twitch-conversations/conversation-window");
-
-
- var ConvSettings = utils.ember_resolve('component:twitch-conversations/conversation-settings-menu');
- if ( ConvSettings ) {
- this.log("Hooking the Ember Conversation Settings Menu component.");
- this._modify_conversation_menu(ConvSettings);
- try { ConvSettings.create().destroy() }
- catch(err) { }
- } else
- this.log("Unable to resolve: component:twitch-conversations/conversation-settings-menu");
-
-
- var ConvLine = utils.ember_resolve('component:twitch-conversations/conversation-line');
- if ( ConvLine ) {
- this.log("Hooking the Ember Conversation Line component.");
- this._modify_conversation_line(ConvLine);
- try { ConvLine.create().destroy() }
- catch(err) { }
- } else
- this.log("Unable to resolve: component:twitch-conversations/conversation-line");
+ this.update_views('component:twitch-conversations/conversation-window', this.modify_conversation_window);
+ this.update_views('component:twitch-conversations/conversation-settings-menu', this.modify_conversation_menu);
+ this.update_views('component:twitch-conversations/conversation-line', this.modify_conversation_line);
}
-FFZ.prototype._modify_conversation_menu = function(component) {
+FFZ.prototype.modify_conversation_menu = function(component) {
var f = this;
-
- component.reopen({
- didInsertElement: function() {
+ utils.ember_reopen_view(component, {
+ ffz_init: function() {
var user = this.get('thread.otherUsername'),
el = this.get('element'),
sections = el && el.querySelectorAll('.options-section');
@@ -132,11 +106,11 @@ FFZ.prototype._modify_conversation_menu = function(component) {
}
-FFZ.prototype._modify_conversation_window = function(component) {
+FFZ.prototype.modify_conversation_window = function(component) {
var f = this,
Layout = utils.ember_lookup('service:layout');
- component.reopen({
+ utils.ember_reopen_view(component, {
headerBadges: Ember.computed("thread.participants", "currentUsername", function() {
return [];
}),
@@ -154,7 +128,7 @@ FFZ.prototype._modify_conversation_window = function(component) {
badge_el.innerHTML = f.render_badges(badges);
}.observes('ffzHeaderBadges'),
- didInsertElement: function() {
+ ffz_init: function() {
var el = this.get('element'),
header = el && el.querySelector('.conversation-header'),
header_name = header && header.querySelector('.conversation-header-name'),
@@ -178,11 +152,11 @@ FFZ.prototype._modify_conversation_window = function(component) {
}
-FFZ.prototype._modify_conversation_line = function(component) {
+FFZ.prototype.modify_conversation_line = function(component) {
var f = this,
Layout = utils.ember_lookup('service:layout');
- component.reopen({
+ utils.ember_reopen_view(component, {
tokenizedMessage: function() {
try {
return f.tokenize_conversation_line(this.get('message'));
@@ -204,7 +178,7 @@ FFZ.prototype._modify_conversation_line = function(component) {
},
didUpdate: function() { this.ffzRender() },
- didInsertElement: function() { this.ffzRender() },
+ ffz_init: function() { this.ffzRender() },
ffzRender: function() {
var el = this.get('element'),
diff --git a/src/ember/directory.js b/src/ember/directory.js
index 47928037..ae10ddab 100644
--- a/src/ember/directory.js
+++ b/src/ember/directory.js
@@ -77,14 +77,44 @@ FFZ.settings_info.directory_group_hosts = {
};
-FFZ.settings_info.banned_games = {
- type: "button",
- value: [],
+FFZ.settings_info.enable_recommended_vods = {
+ type: "boolean",
+ value: true,
category: "Directory",
no_mobile: true,
- name: "Banned Games",
- help: "A list of games that will not be displayed in the Directory.",
+ experiment_warn: true,
+
+ name: 'Show Twitch\'s Recommended Videos',
+ help: 'Show the "Based on your Viewing History" section of the directory rather than Most Recent Videos.',
+
+ on_update: function(val) {
+ Ember.propertyDidChange(utils.ember_lookup('service:vod-coviews'), 'areVodsViewable');
+ }
+}
+
+
+FFZ.settings_info.recommended_above_hosts = {
+ type: "boolean",
+ value: function() { var s = utils.ember_lookup('service:vod-coviews'); return s && s.get('isFollowingAboveHost') },
+
+ category: "Directory",
+ no_mobile: true,
+ experiment_warn: true,
+
+ name: "Show Twitch's Recommended Videos above Hosts",
+ help: 'Enable this to place the "Based on your Viewing History" section above Live Hosts.',
+
+ on_update: function(val) {
+ Ember.propertyDidChange(utils.ember_lookup('service:vod-coviews'), 'isFollowingAboveHost');
+ //utils.ember_lookup('service:vod-coviews').set('isFollowingAboveHost', val);
+ }
+}
+
+
+FFZ.settings_info.banned_games = {
+ visible: false,
+ value: [],
on_update: function() {
var banned = this.settings.banned_games,
@@ -96,34 +126,14 @@ FFZ.settings_info.banned_games = {
el.classList.toggle('ffz-game-banned', banned.indexOf(game && game.toLowerCase()) !== -1);
}
- },
-
- method: function() {
- var f = this,
- old_val = f.settings.banned_games.join(", ");
-
- utils.prompt(
- "Banned Games",
- "Please enter a comma-separated list of games that you would like to be banned from viewing in the Directory.
This is case insensitive, however you must type the full name.
Example:League of Legends, Dota 2, Smite",
- old_val,
- function(new_val) {
- if ( new_val === null || new_val === undefined )
- return;
- f.settings.set("banned_games", _.unique(new_val.trim().toLowerCase().split(/\s*,\s*/)));
- }, 600);
}
}
FFZ.settings_info.spoiler_games = {
- type: "button",
+ visible: false,
value: [],
- category: "Directory",
- no_mobile: true,
- name: "No-Thumbnail Games",
- help: "Stream and video thumbnails will be hidden for games that you add to this list.",
-
on_update: function() {
var spoiled = this.settings.spoiler_games,
els = document.querySelectorAll('.ffz-directory-preview');
@@ -134,21 +144,6 @@ FFZ.settings_info.spoiler_games = {
el.classList.toggle('ffz-game-spoilered', spoiled.indexOf(game && game.toLowerCase()) !== -1);
}
- },
-
- method: function() {
- var f = this,
- old_val = f.settings.spoiler_games.join(", ");
-
- utils.prompt(
- "No-Thumbnail Games",
- "Please enter a comma-separated list of games that you would like to have the thumbnails hidden for in the Directory.
This is case insensitive, however you must type the full name.
Example:Undertale",
- old_val,
- function(new_val) {
- if ( new_val === null || new_val === undefined )
- return;
- f.settings.set("spoiler_games", _.unique(new_val.trim().toLowerCase().split(/\s*,\s*/)));
- }, 600);
}
}
@@ -198,88 +193,46 @@ FFZ.prototype.setup_directory = function() {
document.body.classList.toggle('ffz-creative-tags', this.settings.directory_creative_all_tags);
document.body.classList.toggle('ffz-creative-showcase', this.settings.directory_creative_showcase);
+ var f = this,
+ VodCoviews = utils.ember_lookup('service:vod-coviews');
+
+ if ( VodCoviews ) {
+ VodCoviews.reopen({
+ // checkExperiment likes setting this back. Don't let it.
+ isFollowingAboveHost: Ember.computed('_ffz', {
+ get: function(key) {
+ return f.settings.recommended_above_hosts;
+ },
+ set: function(key, val) {
+ return f.settings.recommended_above_hosts;
+ }
+ }),
+
+ areVodsViewable: function() {
+ var filtered = this.get('filteredVods');
+ return f.settings.enable_recommended_vods && filtered && filtered.length > 0;
+ }.property('filteredVods')
+ });
+
+ Ember.propertyDidChange(VodCoviews, 'isFollowingAboveHost');
+ Ember.propertyDidChange(VodCoviews, 'areVodsViewable');
+
+ } else
+ this.log("Unable to locate the Ember service:vod-coviews");
+
+
this.log("Attempting to modify the Following collection.");
this._modify_following();
this.log("Hooking the Ember Directory views.");
- var ChannelView = utils.ember_resolve('component:stream-preview');
- if ( ChannelView ) {
- this._modify_directory_live(ChannelView, false);
- try {
- ChannelView.create().destroy();
- } catch(err) { }
- } else
- this.log("Unable to locate the Ember component:stream-preview");
+ this.update_views('component:stream-preview', function(x) { this.modify_directory_live(x, false) }, true);
+ this.update_views('component:creative-preview', function(x) { this.modify_directory_live(x, false) }, true);
+ this.update_views('component:csgo-channel-preview', function(x) { this.modify_directory_live(x, true) }, true);
+ this.update_views('component:host-preview', this.modify_directory_host, true);
+ this.update_views('component:video-preview', this.modify_video_preview, true);
- var CreativeChannel = utils.ember_resolve('component:creative-preview');
- if ( CreativeChannel ) {
- this._modify_directory_live(CreativeChannel, false);
- try {
- CreativeChannel.create().destroy();
- } catch(err) { }
- } else
- this.log("Unable to locate the Ember component:creative-preview");
-
- var CSGOChannel = utils.ember_resolve('component:csgo-channel-preview');
- CSGOChannel = this._modify_directory_live(CSGOChannel, true, 'component:csgo-channel-preview');
- try {
- CSGOChannel.create().destroy();
- } catch(err) { }
-
- var HostView = utils.ember_resolve('component:host-preview');
- HostView = this._modify_directory_host(HostView);
- try {
- HostView.create().destroy();
- } catch(err) { }
-
-
- var VideoPreview = utils.ember_resolve('component:video-preview');
- if ( VideoPreview ) {
- this._modify_video_preview(VideoPreview);
- try { VideoPreview.create().destroy();
- } catch(err) { }
- } else
- this.log("Unable to locate the Ember component:video-preview");
-
-
- var GameFollow = utils.ember_resolve('component:game-follow-button');
- if ( GameFollow ) {
- this._modify_game_follow(GameFollow);
- try { GameFollow.create().destroy() }
- catch(err) { }
- } else
- this.log("Unable to locate the Ember component:game-follow-button");
-
-
- // Initialize existing views.
- var views = utils.ember_views();
- for(var key in views) {
- var view = views[key];
- if ( (ChannelView && view instanceof ChannelView) || (CreativeChannel && view instanceof CreativeChannel) ) {
- if ( ! view.ffzInit )
- this._modify_directory_live(view, false);
- } else if ( CSGOChannel && view instanceof CSGOChannel ) {
- if ( ! view.ffzInit )
- this._modify_directory_live(view, true);
- } else if ( view instanceof HostView || view.get('tt_content') === 'live_host' ) {
- if ( ! view.ffzInit )
- this._modify_directory_host(view);
- } else if ( VideoPreview && view instanceof VideoPreview ) {
- if ( ! view.ffzInit )
- this._modify_video_preview(view);
- } else if ( GameFollow && view instanceof GameFollow ) {
- if ( ! view.ffzInit )
- this._modify_game_follow(view);
- } else
- continue;
-
- try {
- view.ffzInit();
- } catch(err) {
- this.error("Directory Setup: " + err);
- }
- }
+ this.update_views('component:game-follow-button', this.modify_game_follow_button);
}
@@ -407,15 +360,10 @@ FFZ.prototype._modify_following = function() {
}
-FFZ.prototype._modify_game_follow = function(component) {
+FFZ.prototype.modify_game_follow_button = function(component) {
var f = this;
- component.reopen({
- didInsertElement: function() {
- this._super();
- this.ffzInit();
- },
-
- ffzInit: function() {
+ utils.ember_reopen_view(component, {
+ ffz_init: function() {
var el = this.get('element'),
game = this.get('game.id').toLowerCase(),
@@ -436,37 +384,33 @@ FFZ.prototype._modify_game_follow = function(component) {
};
// Block Button
- var block = utils.createElement('div', 'follow-button ffz-block-button'),
- block_link = utils.createElement('a', 'tooltip follow block');
-
+ var block = utils.createElement('button', 'button tooltip ffz-block-button'),
update_block = function() {
var is_blocked = f.settings.banned_games.indexOf(game) !== -1;
- block_link.classList.toggle('active', is_blocked);
+ block.classList.toggle('active', is_blocked);
- block_link.innerHTML = '' + (is_blocked ? 'Unblock' : 'Block') + '';
- block_link.title = 'Click to ' + (is_blocked ? 'unblock' : 'block') + " this game.\n\nBlocking a game hides all the streams and videos of the game when you're not viewing it directly.";
+ block.innerHTML = (is_blocked ? 'Unblock' : 'Block');
+ block.title = 'Click to ' + (is_blocked ? 'unblock' : 'block') + " this game.\n\nBlocking a game hides all the streams and videos of the game when you're not viewing it directly.";
+ jQuery(block).trigger('mouseout').trigger('mouseover');
};
update_block();
- block_link.addEventListener('click', click_button('banned_games', update_block));
- block.appendChild(block_link);
+ block.addEventListener('click', click_button('banned_games', update_block));
el.appendChild(block);
// Spoiler Button
- var spoiler = utils.createElement('div', 'follow-button ffz-spoiler-button'),
- spoiler_link = utils.createElement('a', 'tooltip follow spoiler'),
-
+ var spoiler = utils.createElement('button', 'button tooltip ffz-spoiler-button'),
update_spoiler = function() {
var is_spoiled = f.settings.spoiler_games.indexOf(game) !== -1;
- spoiler_link.classList.toggle('active', is_spoiled);
+ spoiler.classList.toggle('active', is_spoiled);
- spoiler_link.innerHTML = '' + (is_spoiled ? 'Show Thumbnails' : 'Hide Thumbnails') + '';
- spoiler_link.title = 'Click to ' + (is_spoiled ? 'show' : 'hide') + " thumbnails for this game.\n\nHiding thumbnails for a game will help you avoid spoilers for a game that you haven't played yet.";
+ spoiler.innerHTML = (is_spoiled ? 'Show Thumbnails' : 'Hide Thumbnails');
+ spoiler.title = 'Click to ' + (is_spoiled ? 'show' : 'hide') + " thumbnails for this game.\n\nHiding thumbnails for a game will help you avoid spoilers for a game that you haven't played yet.";
+ jQuery(spoiler).trigger('mouseout').trigger('mouseover');
}
update_spoiler();
- spoiler_link.addEventListener('click', click_button('spoiler_games', update_spoiler));
- spoiler.appendChild(spoiler_link);
+ spoiler.addEventListener('click', click_button('spoiler_games', update_spoiler));
el.appendChild(spoiler);
jQuery('.tooltip', el).tipsy();
@@ -475,17 +419,12 @@ FFZ.prototype._modify_game_follow = function(component) {
}
-FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
+FFZ.prototype.modify_directory_live = function(component, is_csgo) {
var f = this,
pref = is_csgo ? 'channel.' : 'stream.';
- var mutator = {
- didInsertElement: function() {
- this._super();
- this.ffzInit();
- },
-
- ffzInit: function() {
+ utils.ember_reopen_view(component, {
+ ffz_init: function() {
var el = this.get('element'),
meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'),
@@ -532,12 +471,12 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
- var Channel = utils.ember_resolve('model:channel');
+ var Channel = utils.ember_resolve('model:deprecated-channel');
if ( ! Channel )
return;
- e.preventDefault();
utils.ember_lookup('router:main').transitionTo('channel.index', Channel.find({id: channel_id}).load());
+ e.preventDefault();
return false;
});
@@ -546,7 +485,7 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
}
},
- willClearRender: function() {
+ ffz_destroy: function() {
if ( this._ffz_uptime ) {
this._ffz_uptime.parentElement.removeChild(this._ffz_uptime);
this._ffz_uptime = null;
@@ -557,8 +496,6 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
if ( this._ffz_image_timer )
clearInterval(this._ffz_image_timer);
-
- this._super();
},
ffzRotateImage: function() {
@@ -587,32 +524,14 @@ FFZ.prototype._modify_directory_live = function(dir, is_csgo, component_name) {
this._ffz_uptime.innerHTML = '';
}
}
- };
-
- if ( dir )
- dir.reopen(mutator);
- else {
- dir = Ember.Component.extend(mutator);
- App.__deprecatedInstance__.registry.register(component_name, dir);
- }
-
- return dir;
+ });
}
-FFZ.prototype._modify_video_preview = function(vp) {
+FFZ.prototype.modify_video_preview = function(component) {
var f = this;
- vp.reopen({
- didInsertElement: function() {
- this._super();
- try {
- this.ffzInit();
- } catch(err) {
- f.error("component:video-preview ffzInit: " + err);
- }
- },
-
- ffzInit: function() {
+ utils.ember_reopen_view(component, {
+ ffz_init: function() {
var el = this.get('element'),
game = this.get('video.game'),
@@ -659,30 +578,11 @@ FFZ.prototype._modify_video_preview = function(vp) {
}
-FFZ.prototype._modify_directory_host = function(dir) {
- var f = this, mutator;
-
- mutator = {
- didInsertElement: function() {
- this._super();
- try {
- this.ffzInit();
- } catch(err) {
- f.error("component:host-preview ffzInit: " + err);
- }
- },
-
- willClearRender: function() {
- this._super();
- try {
- this.ffzCleanup();
- } catch(err) {
- f.error("component:host-preview ffzCleanup: " + err);
- }
- },
-
+FFZ.prototype.modify_directory_host = function(component) {
+ var f = this;
+ utils.ember_reopen_view(component, {
ffzVisitChannel: function(target, e) {
- var Channel = utils.ember_resolve('model:channel');
+ var Channel = utils.ember_resolve('model:deprecated-channel');
if ( ! Channel )
return;
@@ -773,7 +673,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
this.$('.thumb .cap img').attr('src', url);
},
- ffzCleanup: function() {
+ ffz_destroy: function() {
var target = this.get('stream.target.channel');
if ( f._popup && f._popup.classList.contains('ffz-channel-selector') && f._popup.getAttribute('data-channel') === target )
f.close_popup();
@@ -782,7 +682,7 @@ FFZ.prototype._modify_directory_host = function(dir) {
clearInterval(this._ffz_image_timer);
},
- ffzInit: function() {
+ ffz_init: function() {
var el = this.get('element'),
meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'),
@@ -839,14 +739,5 @@ FFZ.prototype._modify_directory_host = function(dir) {
cap.addEventListener('click', this.ffzShowHostMenu.bind(this));
}
}
- };
-
- if ( dir )
- dir.reopen(mutator);
- else {
- dir = Ember.Component.extend(mutator);
- App.__deprecatedInstance__.registry.register('component:host-preview', dir);
- }
-
- return dir;
+ });
}
\ No newline at end of file
diff --git a/src/ember/feed-card.js b/src/ember/feed-card.js
index 11d2a157..5600e24c 100644
--- a/src/ember/feed-card.js
+++ b/src/ember/feed-card.js
@@ -21,15 +21,8 @@ var FFZ = window.FrankerFaceZ,
FFZ.prototype.setup_feed_cards = function() {
- var FeedCard = utils.ember_resolve('component:channel-feed/card');
- if ( ! FeedCard )
- return this.error("Unable to locate component:channel-feed/card");
-
- this.log("Modifying the feed-card component.");
- this._modify_feed_card(FeedCard);
-
- try { FeedCard.create().destroy();
- } catch(err) { }
+ this.update_views('component:channel-feed/card', this.modify_feed_card);
+ this.update_views('component:channel-feed/comment', this.modify_feed_comment);
this.rerender_feed_cards();
}
@@ -37,6 +30,7 @@ FFZ.prototype.setup_feed_cards = function() {
FFZ.prototype.rerender_feed_cards = function(for_set) {
var FeedCard = utils.ember_resolve('component:channel-feed/card'),
+ FeedComment = utils.ember_resolve('component:channel-feed/comment'),
views = utils.ember_views();
if ( ! FeedCard )
@@ -46,30 +40,31 @@ FFZ.prototype.rerender_feed_cards = function(for_set) {
var view = views[view_id];
if ( view instanceof FeedCard ) {
try {
- if ( ! view.ffzInit )
- this._modify_feed_card(view);
- view.ffzInit(for_set);
+ if ( ! view.ffz_init )
+ this.modify_feed_card(view);
+ view.ffz_init(for_set);
} catch(err) {
- this.error("setup component:channel-feed/card ffzInit: " + err)
+ this.error("setup component:channel-feed/card ffzInit", err)
+ }
+ }
+
+ if ( FeedComment && view instanceof FeedComment ) {
+ try {
+ if ( ! view.ffz_init )
+ this.modify_feed_comment(view);
+ view.ffz_init(for_set);
+ } catch(err) {
+ this.error("setup component:channel-feed/comment ffzInit", err);
}
}
}
}
-FFZ.prototype._modify_feed_card = function(component) {
+FFZ.prototype.modify_feed_card = function(component) {
var f = this;
- component.reopen({
- didInsertElement: function() {
- this._super();
- try {
- this.ffzInit();
- } catch(err) {
- f.error("component:channel-feed/card ffzInit: " + err);
- }
- },
-
- ffzInit: function(for_set) {
+ utils.ember_reopen_view(component, {
+ ffz_init: function(for_set) {
var el = this.get('element'),
message = this.get('post.body'),
emotes = parse_emotes(this.get('post.emotes')),
@@ -96,4 +91,34 @@ FFZ.prototype._modify_feed_card = function(component) {
//jQuery('.html-tooltip', pbody).tipsy({html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
}
});
+}
+
+
+FFZ.prototype.modify_feed_comment = function(component) {
+ var f = this;
+ utils.ember_reopen_view(component, {
+ ffz_init: function(for_set) {
+ var el = this.get('element'),
+ message = this.get('comment.body'),
+ emotes = parse_emotes(this.get('comment.emotes')),
+ user_id = this.get('comment.user.login'),
+ room_id = this.get('parentView.parentView.channelId') || this.get('parentView.parentView.post.user.login') || null,
+ pbody = el && el.querySelector('.activity-body');
+
+ if ( ! message || ! el || ! pbody )
+ return;
+
+ // If this is for a specific emote set, only rerender if it matters.
+ if ( for_set && f.rooms && f.rooms[room_id] ) {
+ var sets = f.getEmotes(user_id, room_id);
+ if ( sets.indexOf(for_set) === -1 )
+ return;
+ }
+
+ var tokens = f.tokenize_feed_body(message, emotes, user_id, room_id),
+ output = f.render_tokens(tokens, true, false);
+
+ pbody.innerHTML = '
' + output + '
';
+ }
+ })
}
\ No newline at end of file
diff --git a/src/ember/following.js b/src/ember/following.js
index c5aeef6c..229c4c62 100644
--- a/src/ember/following.js
+++ b/src/ember/following.js
@@ -167,7 +167,7 @@ FFZ.prototype.setup_profile_following = function() {
// Refresh all existing following data.
var count = 0,
- Channel = utils.ember_resolve('model:channel');
+ Channel = utils.ember_resolve('model:deprecated-channel');
if ( Channel && Channel._cache )
for(var key in Channel._cache) {
diff --git a/src/ember/layout.js b/src/ember/layout.js
index 05f0c89f..cce6a7e1 100644
--- a/src/ember/layout.js
+++ b/src/ember/layout.js
@@ -240,16 +240,26 @@ FFZ.prototype.setup_layout = function() {
}.observes("isTooSmallForRightColumn"),
ffzUpdateCss: function() {
- var out = '';
+ var window_height = this.get('windowHeight'),
+ window_width = this.get('windowWidth'),
+ out = 'body.ffz-small-player #player .dynamic-player {' +
+ 'position: fixed;' +
+ 'z-index: 9;' +
+ 'box-shadow: 0 0 20px 0 black;';
+
+ if ( .25 * window_width >= .5 * window_height )
+ out += 'width: 25vw !important; height: 14.0625vw !important;';
+ else
+ out += 'width: 50vh !important; height: 28.125vh !important;';
+
if ( ! f.has_bttv ) {
if ( this.get('isRightColumnClosed') )
- out = '';
+ out += 'top: 0; right: 0}';
+
else {
if ( this.get('portraitMode') ) {
var size = this.get('playerSize'),
video_below = this.get('portraitVideoBelow'),
- window_height = this.get('windowHeight'),
- window_width = this.get('windowWidth'),
video_height = size[1] + 120 + 60,
chat_height = window_height - video_height,
@@ -263,7 +273,8 @@ FFZ.prototype.setup_layout = function() {
theatre_video_top = video_below ? theatre_chat_height : 0,
theatre_chat_top = video_below ? 0 : theatre_video_height;
- out = 'body[data-current-path^="user."] #left_col .warp { min-height: inherit }' +
+ out += 'top: ' + video_top + 'px;right: 0}' +
+ 'body[data-current-path^="user."] #left_col .warp { min-height: inherit }' +
'body[data-current-path^="user."] #left_col { overflow: hidden }' +
'body[data-current-path^="user."] #left_col .warp,' +
'body[data-current-path^="user."] #left_col,' +
@@ -290,7 +301,9 @@ FFZ.prototype.setup_layout = function() {
} else {
var width = this.get('rightColumnWidth');
- out = '#main_col.expandRight #right_close{left: none !important}' +
+
+ out += 'top: 0; right: ' + width + 'px}' +
+ '#main_col.expandRight #right_close{left: none !important}' +
'#right_col{width:' + width + 'px}' +
'body:not(.ffz-sidebar-swap) #main_col:not(.expandRight){' +
'margin-right:' + width + 'px}' +
@@ -317,7 +330,8 @@ FFZ.prototype.setup_layout = function() {
ffzFixTabs: function() {
if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) {
setTimeout(function() {
- f._chatv && f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px");
+ var cr = f._chatv && f._chatv.$('.chat-room');
+ cr && cr.css && cr.css('top', f._chatv._ffz_tabs.offsetHeight + "px");
},0);
}
}.observes("isRightColumnClosed", "rightColumnWidth", "portraitMode", "playerSize")
diff --git a/src/ember/line.js b/src/ember/line.js
index ec691607..9a8b022b 100644
--- a/src/ember/line.js
+++ b/src/ember/line.js
@@ -18,9 +18,9 @@ FFZ.settings_info.alias_italics = {
help: "Format the names of users that have aliases with italics to make it obvious at a glance that they have been renamed.",
on_update: function(val) {
- document.body.classList.toggle('ffz-alias-italics', val);
- }
- };
+ document.body.classList.toggle('ffz-alias-italics', val);
+ }
+};
FFZ.settings_info.room_status = {
type: "boolean",
@@ -33,10 +33,10 @@ FFZ.settings_info.room_status = {
help: "Display the current room state (slow mode, sub mode, and r9k mode) next to the Chat button.",
on_update: function() {
- if ( this._roomv )
- this._roomv.ffzUpdateStatus();
- }
- };
+ if ( this._roomv )
+ this._roomv.ffzUpdateStatus();
+ }
+};
FFZ.settings_info.replace_bad_emotes = {
@@ -48,7 +48,19 @@ FFZ.settings_info.replace_bad_emotes = {
name: "Fix Low Quality Twitch Global Emoticons",
help: "Replace emoticons such as DansGame and RedCoat with cleaned up versions that don't have pixels around the edges or white backgrounds for nicer display on dark chat."
- };
+};
+
+
+FFZ.settings_info.parse_emoticons = {
+ type: "boolean",
+ value: true,
+
+ category: "Chat Appearance",
+ no_bttv: true,
+
+ name: "Display Emoticons",
+ help: "Display emoticons in chat messages rather than just text."
+};
FFZ.settings_info.parse_emoji = {
@@ -74,9 +86,9 @@ FFZ.settings_info.parse_emoji = {
category: "Chat Appearance",
- name: "Emoji Display",
+ name: "Display Emoji",
help: "Replace emoji in chat messages with nicer looking images from either Twitter or Google."
- };
+};
FFZ.settings_info.scrollback_length = {
@@ -354,6 +366,19 @@ FFZ.settings_info.old_sub_notices = {
};
+FFZ.settings_info.emote_alignment = {
+ type: "boolean",
+ value: false,
+
+ category: "Chat Appearance",
+ no_bttv: true,
+
+ name: "Baseline Emoticon Alignment",
+ help: "Align emotes on the text baseline, making messages taller but ensuring emotes don't overlap.",
+
+ on_update: function(val) { document.body.classList.toggle('ffz-baseline-emoticons', !this.has_bttv && val) }
+};
+
FFZ.settings_info.chat_padding = {
type: "boolean",
value: false,
@@ -588,6 +613,7 @@ FFZ.prototype.setup_line = function() {
// Chat Enhancements
document.body.classList.toggle('ffz-alias-italics', this.settings.alias_italics);
+ document.body.classList.toggle('ffz-baseline-emoticons', !this.has_bttv && this.settings.emote_alignment);
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);
@@ -710,7 +736,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
if ( is_whisper || this_ul >= other_ul || f.settings.mod_buttons.length === 0 )
return '';
- output = '';
+ output = '';
for(var i=0, l = f.settings.mod_buttons.length; i < l; i++) {
var pair = f.settings.mod_buttons[i],
@@ -720,22 +746,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");
+ cmd = btn.replace(/{user}/g, user).replace(/{id}/g, this.get('msgObject.tags.id')).replace(/ * */, "\n");
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 + '';
}
}
@@ -770,18 +796,18 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
// System Message
if ( system_msg ) {
output += '
' + utils.sanitize(system_msg) + '
';
- if ( this.get('shouldRenderMessageBody') === false )
+ if ( this.get('ffzShouldRenderMessageBody') === false )
return output;
}
// Timestamp
- output += '' + this.get('timestamp') + ' ';
+ output += '' + this.get('timestamp') + ' ';
// Moderator Actions
output += this.buildModIconsHTML();
// Badges
- output += '' + f.render_badges(f.get_line_badges(this.get('msgObject'))) + '';
+ output += '' + f.render_badges(f.get_line_badges(this.get('msgObject'))) + '';
// Alias Support
var alias = f.aliases[user],
@@ -842,7 +868,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
} else
output = '';
- output += f.render_tokens(this.get('ffzTokenizedMessage'), true, is_whisper && f.settings.filter_whispered_links && this.get("ffzUserLevel") < 4);
+ output += f.render_tokens(this.get('ffzTokenizedMessage'), true, is_whisper && f.settings.filter_whispered_links && this.get("ffzUserLevel") < 4, this.get('isBitsEnabled'));
var old_messages = this.get('msgObject.ffz_old_messages');
if ( old_messages && old_messages.length )
@@ -856,7 +882,7 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
output = this.buildSenderHTML();
// If this is a whisper, or if we should render the message body, render it.
- if ( this.get('shouldRenderMessageBody') !== false )
+ if ( this.get('ffzShouldRenderMessageBody') !== false )
if ( this.get('msgObject.deleted') )
output += this.buildDeletedMessageHTML()
else
@@ -865,6 +891,14 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
el.innerHTML = output;
},
+ ffzShouldRenderMessageBody: function() {
+ return ! this.get('hasSystemMsg') || this.get('hasMessageBody');
+ }.property('hasSystemMsg', 'hasMessageBody'),
+
+ shouldRenderMessageBody: function() {
+ return false;
+ }.property('hasSystemMsg', 'hasMessageBody'),
+
ffzWasDeleted: function() {
return f.settings.prevent_clear && this.get("msgObject.ffz_deleted")
}.property("msgObject.ffz_deleted"),
@@ -883,8 +917,8 @@ FFZ.prototype._modify_chat_subline = function(component) {
this._modify_chat_line(component);
component.reopen({
- classNameBindings: [":message-line", ":chat-line", "msgObject.style", "msgObject.ffz_has_mention:ffz-mentioned", "ffzWasDeleted:ffz-deleted", "ffzHasOldMessages:clearfix", "ffzHasOldMessages:ffz-has-deleted"],
- attributeBindings: ["msgObject.room:data-room", "msgObject.from:data-sender", "msgObject.deleted:data-deleted"],
+ classNameBindings: ["msgObject.style", "msgObject.ffz_has_mention:ffz-mentioned", "ffzWasDeleted:ffz-deleted", "ffzHasOldMessages:clearfix", "ffzHasOldMessages:ffz-has-deleted"],
+ attributeBindings: ["msgObject.tags.id:data-id", "msgObject.room:data-room", "msgObject.from:data-sender", "msgObject.deleted:data-deleted"],
didInsertElement: function() {
this.set('msgObject._line', this);
@@ -1024,10 +1058,10 @@ FFZ.prototype._modify_vod_line = function(component) {
if ( ! this.get("isViewerModeratorOrHigher") || this.get("isModeratorOrHigher") )
return "";
- return '' +
+ return '' +
(this.get('msgObject.deleted') ?
- '' :
- 'Delete') + '';
+ '' :
+ 'Delete') + '';
},
buildDeletedMesageHTML: function() {
diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js
index 6d2aade5..d255ea90 100644
--- a/src/ember/moderation-card.js
+++ b/src/ember/moderation-card.js
@@ -353,7 +353,8 @@ FFZ.settings_info.mod_buttons = {
"Custom In-Line Moderation Icons",
"Please 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 name " +
- "into the command, otherwise it will be appended to the end.
Example:!permit \"!reg add {user}\"
To " +
+ "into the command, otherwise it will be appended to the end. Use {id} to insert the unique message ID into the command.
To " +
"send multiple commands, separate them with <LINE>.
Numeric values will become timeout buttons for " +
"that number of seconds. The text <BAN> is a special value that will act like the normal Ban button in chat.
" +
"To assign a specific letter for use as the icon, specify it at the start of the command followed by an equals sign.
" +
@@ -583,7 +584,6 @@ FFZ.prototype.setup_mod_card = function() {
helpers = window.require && window.require("web-client/helpers/chat/chat-line-helpers");
} catch(err) { }
-
this.log("Listening to the Settings controller to catch mod icon state changes.");
var f = this,
Settings = utils.ember_lookup('controller:settings');
@@ -594,7 +594,6 @@ FFZ.prototype.setup_mod_card = function() {
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) {
@@ -609,11 +608,13 @@ FFZ.prototype.setup_mod_card = function() {
el && el.classList.toggle('ffz-flip');
});
-
this.log("Hooking the Ember Moderation Card view.");
- var Card = utils.ember_resolve('component:chat/moderation-card');
+ this.update_views('component:chat/moderation-card', this.modify_moderation_card);
+}
- Card.reopen({
+FFZ.prototype.modify_moderation_card = function(component) {
+ var f = this;
+ utils.ember_reopen_view(component, {
ffzForceRedraw: function() {
this.rerender();
if ( f.settings.mod_card_history )
@@ -667,402 +668,383 @@ FFZ.prototype.setup_mod_card = function() {
return alias || this.get("cardInfo.user.display_name") || user_id.capitalize();
}),
- willDestroy: function() {
+ ffz_destroy: function() {
if ( f._mod_card === this )
f._mod_card = undefined;
utils.update_css(f._chat_style, 'mod-card-highlight');
-
- this._super();
},
- didInsertElement: function() {
- this._super();
- try {
- if ( f.has_bttv )
- return;
+ ffz_init: function() {
+ if ( f.has_bttv )
+ return;
- f._mod_card = this;
+ f._mod_card = this;
- var el = this.get('element'),
- controller = this.get('controller'),
- t = this,
- line,
+ var el = this.get('element'),
+ controller = this.get('controller'),
+ t = this,
+ line,
- is_mod = controller.get('cardInfo.isModeratorOrHigher'),
- ban_reasons,
+ is_mod = controller.get('cardInfo.isModeratorOrHigher'),
+ ban_reasons,
- chat = utils.ember_lookup('controller:chat'),
- user = f.get_user(),
- room_id = chat && chat.get('currentRoom.id'),
- is_broadcaster = user && room_id === user.login,
+ chat = utils.ember_lookup('controller:chat'),
+ user = f.get_user(),
+ room_id = chat && chat.get('currentRoom.id'),
+ is_broadcaster = user && room_id === user.login,
- user_id = controller.get('cardInfo.user.id'),
- alias = f.aliases[user_id],
+ user_id = controller.get('cardInfo.user.id'),
+ alias = f.aliases[user_id],
- handle_key,
+ handle_key,
- ban_reason = function() {
- return ban_reasons && ban_reasons.value ? ' ' + ban_reasons.value : "";
- };
+ ban_reason = function() {
+ return ban_reasons && ban_reasons.value ? ' ' + ban_reasons.value : "";
+ };
- this.ffz_room_id = room_id;
+ 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));
+ // 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) {
- var room = utils.ember_lookup('controller:chat').get('currentRoom');
- room.send("/ban " + e.user + ban_reason(), true);
- });
+ // Action Override
+ this.set('banAction', function(e) {
+ var room = utils.ember_lookup('controller:chat').get('currentRoom');
+ room.send("/ban " + e.user + ban_reason(), true);
+ });
- this.set('timeoutAction', function(e) {
- var room = utils.ember_lookup('controller:chat').get('currentRoom');
- room.send("/timeout " + e.user + " 600 " + ban_reason(), true);
- });
+ this.set('timeoutAction', function(e) {
+ var room = utils.ember_lookup('controller:chat').get('currentRoom');
+ room.send("/timeout " + e.user + " 600 " + ban_reason(), true);
+ });
- // Alias Display
- if ( alias ) {
- var name = el.querySelector('h3.name'),
- link = name && name.querySelector('a');
+ // Alias Display
+ if ( alias ) {
+ var name = el.querySelector('h3.name'),
+ link = name && name.querySelector('a');
- if ( link )
- name = link;
- if ( name ) {
- name.classList.add('ffz-alias');
- name.title = utils.sanitize(controller.get('cardInfo.user.display_name') || user_id.capitalize());
- jQuery(name).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
- }
+ if ( link )
+ name = link;
+ if ( name ) {
+ name.classList.add('ffz-alias');
+ name.title = utils.sanitize(controller.get('cardInfo.user.display_name') || user_id.capitalize());
+ jQuery(name).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
+ }
- // Style it!
- el.classList.add('ffz-moderation-card');
+ // Style it!
+ el.classList.add('ffz-moderation-card');
- // Info-tize it!
- if ( f.settings.mod_card_info ) {
- var info = document.createElement('div'),
- after = el.querySelector('h3.name');
- if ( after ) {
- el.classList.add('ffz-has-info');
- info.className = 'info channel-stats';
- after.parentElement.insertBefore(info, after.nextSibling);
- this.ffzRebuildInfo();
- }
+ // Info-tize it!
+ if ( f.settings.mod_card_info ) {
+ var info = utils.createElement('div', 'info channel-stats'),
+ after = el.querySelector('h3.name');
+ if ( after ) {
+ el.classList.add('ffz-has-info');
+ after.parentElement.insertBefore(info, after.nextSibling);
+ this.ffzRebuildInfo();
}
+ }
- // Additional Buttons
- if ( is_mod && f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) {
- line = document.createElement('div');
- line.className = 'extra-interface interface clearfix';
+ // Additional Buttons
+ if ( is_mod && f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) {
+ line = utils.createElement('div', 'extra-interface interface clearfix');
- var cmds = {},
- add_btn_click = function(cmd) {
- var user_id = controller.get('cardInfo.user.id'),
- cont = utils.ember_lookup('controller:chat'),
- room = cont && cont.get('currentRoom'),
+ var cmds = {},
+ add_btn_click = function(cmd) {
+ var user_id = controller.get('cardInfo.user.id'),
+ cont = utils.ember_lookup('controller:chat'),
+ room = cont && cont.get('currentRoom'),
- cm = cmd.replace(USER_REG, user_id),
- reason = ban_reason();
+ cm = cmd.replace(USER_REG, user_id),
+ reason = ban_reason();
- if ( reason ) {
- var match = TO_REG.exec(cm);
- if ( match ) {
- if ( ! match[2] )
- cm += " 600";
- if ( ! match[3] )
- cm += reason;
+ if ( reason ) {
+ var match = TO_REG.exec(cm);
+ if ( match ) {
+ if ( ! match[2] )
+ cm += " 600";
+ if ( ! match[3] )
+ cm += reason;
- } else {
- match = BAN_REG.exec(cm);
- if ( match && ! match[2] ) {
- cm += reason;
- }
+ } else {
+ match = BAN_REG.exec(cm);
+ if ( match && ! match[2] ) {
+ cm += reason;
}
}
+ }
- room && room.send(cm, true);
- },
+ room && room.send(cm, true);
+ },
- add_btn_make = function(cmd) {
- var btn = utils.createElement('button', 'button ffz-no-bg'),
- segment = cmd.split(' ', 1)[0],
- title = cmds[segment] > 1 ? cmd.split(' ', cmds[segment]) : [segment];
+ add_btn_make = function(cmd) {
+ var btn = utils.createElement('button', 'button ffz-no-bg'),
+ segment = cmd.split(' ', 1)[0],
+ title = cmds[segment] > 1 ? cmd.split(' ', cmds[segment]) : [segment];
- if ( /^[!~./]/.test(title[0]) )
- title[0] = title[0].substr(1);
+ if ( /^[!~./]/.test(title[0]) )
+ title[0] = title[0].substr(1);
- title = _.map(title, function(s){ return s.capitalize() }).join(' ');
+ title = _.map(title, function(s){ return s.capitalize() }).join(' ');
- btn.innerHTML = utils.sanitize(title);
- btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}'));
+ btn.innerHTML = utils.sanitize(title);
+ btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}'));
- jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
- btn.addEventListener('click', add_btn_click.bind(this, cmd));
- return btn;
- };
+ jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
+ btn.addEventListener('click', add_btn_click.bind(this, cmd));
+ return btn;
+ };
- var cmds = {};
- for(var i=0; i < f.settings.mod_card_buttons.length; i++)
- cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] = (cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] || 0) + 1;
+ var cmds = {};
+ for(var i=0; i < f.settings.mod_card_buttons.length; i++)
+ cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] = (cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] || 0) + 1;
- for(var i=0; i < f.settings.mod_card_buttons.length; i++) {
- var cmd = f.settings.mod_card_buttons[i],
- ind = cmd.indexOf('{user}');
+ for(var i=0; i < f.settings.mod_card_buttons.length; i++) {
+ var cmd = f.settings.mod_card_buttons[i],
+ ind = cmd.indexOf('{user}');
- if ( ind === -1 )
- cmd += ' {user}';
+ if ( ind === -1 )
+ cmd += ' {user}';
- line.appendChild(add_btn_make(cmd))
+ line.appendChild(add_btn_make(cmd))
+ }
+
+ el.appendChild(line);
+ }
+
+
+ // Key Handling
+ el.setAttribute('tabindex', 1);
+ if ( f.settings.mod_card_hotkeys ) {
+ el.classList.add('no-mousetrap');
+
+ handle_key = function(e) {
+ var key = e.keyCode || e.which,
+ user_id = controller.get('cardInfo.user.id'),
+ is_mod = controller.get('cardInfo.isModeratorOrHigher'),
+ room = utils.ember_lookup('controller:chat').get('currentRoom');
+
+ if ( is_mod && key == keycodes.P )
+ room.send("/timeout " + user_id + " 1" + ban_reason(), true);
+
+ else if ( is_mod && key == keycodes.B )
+ room.send("/ban " + user_id + ban_reason(), true);
+
+ else if ( is_mod && key == keycodes.T )
+ room.send("/timeout " + user_id + " 600" + ban_reason(), true);
+
+ else if ( is_mod && key == keycodes.U )
+ room.send("/unban " + user_id, true);
+
+ else if ( is_mod && ban_reasons && key == keycodes.R ) {
+ var event = document.createEvent('MouseEvents');
+ event.initMouseEvent('mousedown', true, true, window);
+ ban_reasons.focus();
+ ban_reasons.dispatchEvent(event);
+ return;
+ }
+
+ else if ( key == keycodes.ESC && e.target === ban_reasons ) {
+ el.focus();
+ return;
+ }
+
+ else if ( key != keycodes.ESC )
+ return;
+
+ t.get('closeAction')();
+ };
+
+ el.addEventListener('keyup', handle_key);
+ }
+
+
+ // Only do the big stuff if we're mod.
+ if ( is_mod ) {
+ el.classList.add('ffz-is-mod');
+
+ var btn_click = function(timeout) {
+ var user_id = controller.get('cardInfo.user.id'),
+ room = utils.ember_lookup('controller:chat').get('currentRoom');
+
+ if ( timeout === -1 )
+ room.send("/unban " + user_id, true);
+ else
+ room.send("/timeout " + user_id + " " + timeout + ban_reason(), true);
+ },
+
+ btn_make = function(timeout) {
+ var btn = utils.createElement('button', '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 )
+ btn.title = "(P)urge - " + btn.title;
+
+ jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
+
+ btn.addEventListener('click', btn_click.bind(this, timeout));
+ return btn;
+ };
+
+ if ( f.settings.mod_card_durations && f.settings.mod_card_durations.length ) {
+ // Extra Moderation
+ line = utils.createElement('div', 'extra-interface interface clearfix');
+ line.appendChild(btn_make(1));
+
+ var s = utils.createElement('span', 'right');
+ line.appendChild(s);
+
+ for(var i=0; i < f.settings.mod_card_durations.length; i++)
+ s.appendChild(btn_make(f.settings.mod_card_durations[i]));
+
+ el.appendChild(line);
+
+ // Fix Other Buttons
+ this.$("button.timeout").remove();
+ }
+
+
+ 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', '');
+ line.appendChild(ban_reasons);
+
+ for(var i=0; i < f.settings.mod_card_reasons.length; i++) {
+ var opt = utils.createElement('option'), r = f.settings.mod_card_reasons[i];
+ opt.value = r;
+ opt.textContent = (i+1) + ') ' + r;
+ ban_reasons.appendChild(opt);
}
el.appendChild(line);
}
- // Key Handling
- el.setAttribute('tabindex', 1);
- if ( f.settings.mod_card_hotkeys ) {
- el.classList.add('no-mousetrap');
+ var ban_btn = el.querySelector('button.ban');
+ if ( f.settings.mod_card_hotkeys )
+ ban_btn.setAttribute('title', '(B)an User');
- handle_key = function(e) {
- var key = e.keyCode || e.which,
- user_id = controller.get('cardInfo.user.id'),
- is_mod = controller.get('cardInfo.isModeratorOrHigher'),
- room = utils.ember_lookup('controller:chat').get('currentRoom');
+ // Unban Button
+ var unban_btn = utils.createElement('button', 'unban button button--icon-only light');
+ unban_btn.innerHTML = '' + CHECK + '';
+ unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User";
- if ( is_mod && key == keycodes.P )
- room.send("/timeout " + user_id + " 1" + ban_reason(), true);
+ jQuery(unban_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
+ unban_btn.addEventListener("click", btn_click.bind(this, -1));
- else if ( is_mod && key == keycodes.B )
- room.send("/ban " + user_id + ban_reason(), true);
-
- else if ( is_mod && key == keycodes.T )
- room.send("/timeout " + user_id + " 600" + ban_reason(), true);
-
- else if ( is_mod && key == keycodes.U )
- room.send("/unban " + user_id, true);
-
- else if ( is_mod && ban_reasons && key == keycodes.R ) {
- var event = document.createEvent('MouseEvents');
- event.initMouseEvent('mousedown', true, true, window);
- ban_reasons.focus();
- ban_reasons.dispatchEvent(event);
- return;
- }
-
- else if ( key == keycodes.ESC && e.target === ban_reasons ) {
- el.focus();
- return;
- }
-
- else if ( key != keycodes.ESC )
- return;
-
- t.get('closeAction')();
- };
-
- el.addEventListener('keyup', handle_key);
- }
-
-
- // Only do the big stuff if we're mod.
- if ( is_mod ) {
- el.classList.add('ffz-is-mod');
-
- var btn_click = function(timeout) {
- var user_id = controller.get('cardInfo.user.id'),
- room = utils.ember_lookup('controller:chat').get('currentRoom');
-
- if ( timeout === -1 )
- room.send("/unban " + user_id, true);
- else
- room.send("/timeout " + user_id + " " + timeout + ban_reason(), true);
- },
-
- btn_make = function(timeout) {
- var btn = document.createElement('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 )
- btn.title = "(P)urge - " + btn.title;
-
- jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
-
- btn.addEventListener('click', btn_click.bind(this, timeout));
- return btn;
- };
-
- if ( f.settings.mod_card_durations && f.settings.mod_card_durations.length ) {
- // Extra Moderation
- line = document.createElement('div');
- line.className = 'extra-interface interface clearfix';
-
- line.appendChild(btn_make(1));
-
- var s = document.createElement('span');
- s.className = 'right';
- line.appendChild(s);
-
- for(var i=0; i < f.settings.mod_card_durations.length; i++)
- s.appendChild(btn_make(f.settings.mod_card_durations[i]));
-
- el.appendChild(line);
-
- // Fix Other Buttons
- this.$("button.timeout").remove();
- }
-
-
- 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', '');
- line.appendChild(ban_reasons);
-
- for(var i=0; i < f.settings.mod_card_reasons.length; i++) {
- var opt = utils.createElement('option'), r = f.settings.mod_card_reasons[i];
- opt.value = r;
- opt.textContent = (i+1) + ') ' + r;
- ban_reasons.appendChild(opt);
- }
-
- el.appendChild(line);
- }
-
-
- var ban_btn = el.querySelector('button.ban');
- if ( f.settings.mod_card_hotkeys )
- ban_btn.setAttribute('title', '(B)an User');
-
- // Unban Button
- var unban_btn = document.createElement('button');
- 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')});
- unban_btn.addEventListener("click", btn_click.bind(this, -1));
-
- jQuery(ban_btn).after(unban_btn);
- }
-
-
- // 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 ) {
- var can_op = is_broadcaster || (user && user.is_admin) || (user && user.is_staff);
-
- if ( ! can_op )
- op_btn.parentElement.removeChild(op_btn);
- }
-
-
- // Follow Button
- var follow_button = el.querySelector(".interface > .follow-button");
- if ( follow_button )
- jQuery(follow_button).tipsy({title: function() { return follow_button.classList.contains('is-following') ? "Unfollow" : "Follow"}});
-
-
- // Whisper and Message Buttons
- var msg_btn = el.querySelector(".interface > button.message-button");
- if ( msg_btn ) {
- msg_btn.innerHTML = 'W';
- msg_btn.classList.add('button--icon-only');
- msg_btn.classList.add('message');
-
- msg_btn.title = "Whisper User";
- jQuery(msg_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
-
-
- var real_msg = document.createElement('button');
- 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() {
- window.open('//www.twitch.tv/message/compose?to=' + controller.get('cardInfo.user.id'));
- })
-
- msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling);
- }
-
-
- // Alias Button
- var alias_btn = document.createElement('button');
- 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() {
- var user = controller.get('cardInfo.user.id'),
- alias = f.aliases[user];
-
- utils.prompt(
- "Alias for " + utils.sanitize(controller.get('cardInfo.user.display_name') || user) + "",
- "Please enter an alias for the user. Leave it blank to remove the alias.",
- alias,
- function(new_val) {
- if ( new_val === null || new_val === undefined )
- return;
-
- new_val = new_val.trim();
- if ( ! new_val )
- new_val = undefined;
-
- f.aliases[user] = new_val;
- f.save_aliases();
-
- // Update UI
- f._update_alias(user);
-
- Ember.propertyDidChange(controller, 'cardInfo.user.display_name');
- var name = el.querySelector('h3.name');
- if ( name )
- name.classList.toggle('ffz-alias', new_val);
- });
- });
-
- if ( msg_btn )
- msg_btn.parentElement.insertBefore(alias_btn, msg_btn);
- else {
- var follow_btn = el.querySelector(".interface > .follow-button");
- if ( follow_btn )
- follow_btn.parentElement.insertBefore(alias_btn, follow_btn.nextSibling);
- }
-
-
- // Message History
- if ( f.settings.mod_card_history )
- this.ffzRenderHistory();
-
- // Reposition the menu if it's off-screen.
- this.ffzReposition();
-
- // Focus the Element
- this.$().draggable({
- start: function() {
- el.focus();
- }});
-
- el.focus();
-
- } catch(err) {
- try {
- f.error("ModerationCardView didInsertElement: " + err);
- } catch(err) { }
+ jQuery(ban_btn).after(unban_btn);
}
+
+
+ // 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 ) {
+ var can_op = is_broadcaster || (user && user.is_admin) || (user && user.is_staff);
+
+ if ( ! can_op )
+ op_btn.parentElement.removeChild(op_btn);
+ }
+
+
+ // Follow Button
+ var follow_button = el.querySelector(".follow-button");
+ if ( follow_button )
+ jQuery(follow_button).tipsy({title: function() { return follow_button.classList.contains('is-following') ? "Unfollow" : "Follow"}});
+
+
+ // Whisper and Message Buttons
+ var msg_btn = el.querySelector(".interface > button.message-button");
+ if ( msg_btn ) {
+ msg_btn.innerHTML = 'W';
+ msg_btn.classList.add('button--icon-only');
+ msg_btn.classList.add('message');
+
+ msg_btn.title = "Whisper User";
+ jQuery(msg_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
+
+
+ var real_msg = utils.createElement('button', 'message-button button button--icon-only message html-tooltip');
+ real_msg.innerHTML = '' + MESSAGE + '';
+ real_msg.title = "Message User";
+
+ real_msg.addEventListener('click', function() {
+ window.open('//www.twitch.tv/message/compose?to=' + controller.get('cardInfo.user.id'));
+ })
+
+ msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling);
+ }
+
+
+ // Alias Button
+ var alias_btn = utils.createElement('button', 'alias button button--icon-only html-tooltip');
+ alias_btn.innerHTML = '' + constants.EDIT + '';
+ alias_btn.title = "Set Alias";
+
+ alias_btn.addEventListener('click', function() {
+ var user = controller.get('cardInfo.user.id'),
+ alias = f.aliases[user];
+
+ utils.prompt(
+ "Alias for " + utils.sanitize(controller.get('cardInfo.user.display_name') || user) + "",
+ "Please enter an alias for the user. Leave it blank to remove the alias.",
+ alias,
+ function(new_val) {
+ if ( new_val === null || new_val === undefined )
+ return;
+
+ new_val = new_val.trim();
+ if ( ! new_val )
+ new_val = undefined;
+
+ f.aliases[user] = new_val;
+ f.save_aliases();
+
+ // Update UI
+ f._update_alias(user);
+
+ Ember.propertyDidChange(controller, 'cardInfo.user.display_name');
+ var name = el.querySelector('h3.name');
+ if ( name )
+ name.classList.toggle('ffz-alias', new_val);
+ });
+ });
+
+ if ( msg_btn )
+ msg_btn.parentElement.insertBefore(alias_btn, msg_btn);
+ else {
+ var follow_btn = el.querySelector(".interface > .follow-button");
+ if ( follow_btn )
+ follow_btn.parentElement.insertBefore(alias_btn, follow_btn.nextSibling);
+ }
+
+
+ // Message History
+ if ( f.settings.mod_card_history )
+ this.ffzRenderHistory();
+
+ // Reposition the menu if it's off-screen.
+ this.ffzReposition();
+
+ // Focus the Element
+ this.$().draggable({
+ start: function() {
+ el.focus();
+ }});
+
+ el.focus();
},
ffzReposition: function() {
@@ -1104,8 +1086,7 @@ FFZ.prototype.setup_mod_card = function() {
history = el && el.querySelector('.chat-history');
if ( ! history ) {
- history = document.createElement('ul');
- history.className = 'interface clearfix chat-history';
+ history = utils.createElement('ul', 'interface clearfix chat-history');
el.appendChild(history);
} else {
history.classList.remove('loading');
@@ -1118,7 +1099,7 @@ FFZ.prototype.setup_mod_card = function() {
if ( ! success )
return;
- f.parse_history(data, null, room_id, delete_links, tmiSession);
+ f.parse_history(data, null, null, room_id, delete_links, tmiSession);
var i = data.length,
was_at_top = history && history.scrollTop >= (history.scrollHeight - history.clientHeight),
@@ -1211,10 +1192,9 @@ FFZ.prototype.setup_mod_card = function() {
logs.innerHTML = '';
} else {
- logs = document.createElement('ul');
- back = document.createElement('button');
+ logs = utils.createElement('ul', 'interface clearfix chat-history adjacent-history');
+ back = utils.createElement('button', 'button ffz-no-bg back-button');
- back.className = 'button ffz-no-bg back-button';
back.innerHTML = '« Back';
back.addEventListener('click', function() {
@@ -1222,12 +1202,10 @@ FFZ.prototype.setup_mod_card = function() {
back.parentElement.removeChild(back);
history.classList.remove('hidden');
});
-
- logs.className = 'interface clearfix chat-history adjacent-history';
}
- f.parse_history(data, null, room_id, delete_links, tmiSession, function(msg) {
+ f.parse_history(data, null, null, room_id, delete_links, tmiSession, function(msg) {
msg.from_server = true;
var line_time = line.date.getTime() - (line.from_server ? 0 : (f._ws_server_offset || 0)),
@@ -1276,7 +1254,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
style = '', colored = '';
if ( helpers && helpers.getTime )
- out.push('' + helpers.getTime(msg.date) + '');
+ out.push('' + helpers.getTime(msg.date) + '');
var alias = this.aliases[msg.from],
@@ -1284,7 +1262,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
if ( show_from ) {
// Badges
- out.push('');
+ out.push('');
out.push(this.render_badges(this.get_line_badges(msg, false)));
out.push('');
@@ -1349,6 +1327,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
l_el.setAttribute('data-room', msg.room);
l_el.setAttribute('data-sender', msg.from);
+ l_el.setAttribute('data-id', msg.tags && msg.tags.id);
l_el.setAttribute('data-deleted', msg.deleted || false);
l_el.innerHTML = out.join("");
diff --git a/src/ember/player.js b/src/ember/player.js
index ee23cd0b..bc67023c 100644
--- a/src/ember/player.js
+++ b/src/ember/player.js
@@ -18,12 +18,6 @@ FFZ.settings_info.player_stats = {
help: "Display your current stream latency (how far behind the broadcast you are) under the player, with a few useful statistics in a tooltip.",
on_update: function(val) {
- for(var key in this.players) {
- var player = this.players[key];
- if ( player && player.player && player.player.ffzSetStatsEnabled )
- player.player.ffzSetStatsEnabled(val || player.player.ffz_stats);
- }
-
if ( ! this._cindex )
return;
@@ -79,36 +73,7 @@ FFZ.prototype.setup_player = function() {
this.players = {};
- var Player2 = utils.ember_resolve('component:twitch-player2');
- if ( ! Player2 )
- return this.log("Unable to find twitch-player2 component.");
-
- this.log("Hooking HTML5 Player UI.");
- this._modify_player(Player2)
-
- try {
- Player2.create().destroy();
- } catch(err) { }
-
- // Modify all existing players.
- var views = utils.ember_views();
- for(var key in views) {
- if ( ! views.hasOwnProperty(key) )
- continue;
-
- var view = views[key];
- if ( !(view instanceof Player2) )
- continue;
-
- this.log("Manually updating existing Player instance.", view);
- try {
- this._modify_player(view);
- view.ffzInit();
-
- } catch(err) {
- this.error("Player2 setup ffzInit: " + err);
- }
- }
+ this.update_views('component:twitch-player2', this.modify_twitch_player);
}
@@ -116,29 +81,22 @@ FFZ.prototype.setup_player = function() {
// Component
// ---------------
-FFZ.prototype._modify_player = function(player) {
- var f = this,
- update_stats = function() {
- f._cindex && f._cindex.ffzUpdatePlayerStats();
- };
+FFZ.prototype.modify_twitch_player = function(player) {
+ var f = this;
+ utils.ember_reopen_view(player, {
+ ffz_init: function() {
+ var id = this.get('channel.id');
+ f.players[id] = this;
- player.reopen({
- didInsertElement: function() {
- this._super();
- try {
- this.ffzInit();
- } catch(err) {
- f.error("Player2 didInsertElement: " + err);
- }
+ var player = this.get('player');
+ if ( player )
+ this.ffzPostPlayer();
},
- willClearRender: function() {
- try {
- this.ffzTeardown();
- } catch(err) {
- f.error("Player2 willClearRender: " + err);
- }
- this._super();
+ ffz_destroy: function() {
+ var id = this.get('channel.id');
+ if ( f.players[id] === this )
+ f.players[id] = undefined;
},
postPlayerSetup: function() {
@@ -150,24 +108,21 @@ FFZ.prototype._modify_player = function(player) {
}
},
- ffzInit: function() {
- var id = this.get('channel.id');
- f.players[id] = this;
+ ffzRecreatePlayer: function() {
+ var player = this.get('player'),
+ theatre = player && player.getTheatre();
- var player = this.get('player');
- if ( player )
- this.ffzPostPlayer();
- },
+ // Tell the player to destroy itself.
+ if ( player )
+ player.destroy();
- ffzTeardown: function() {
- var id = this.get('channel.id');
- if ( f.players[id] === this )
- f.players[id] = undefined;
+ // Break down everything left over from that player.
+ this.$('#video-1').html('');
+ Mousetrap.unbind(['alt+x', 'alt+t', 'esc']);
+ this.set('player', null);
- if ( this._ffz_stat_interval ) {
- clearInterval(this._ffz_stat_interval);
- this._ffz_stat_interval = null;
- }
+ // Now, let Twitch create a new player as usual.
+ Ember.run.next(this.insertPlayer.bind(this));
},
ffzPostPlayer: function() {
@@ -175,74 +130,29 @@ FFZ.prototype._modify_player = function(player) {
if ( ! player )
return;
-
// Make the stats window draggable and fix the button.
var stats = this.$('.player .js-playback-stats');
stats.draggable({cancel: 'li', containment: 'parent'});
-
- // Only set up the stats hooks if we need stats.
- var has_video = false;
-
- try {
- has_video = player.getVideo();
- } catch(err) {
- f.error("Player2 ffzPostPlayer: getVideo: " + err);
- }
-
- if ( ! has_video )
- this.ffzInitStats();
- },
-
- ffzInitStats: function() {
- if ( this.get('ffzStatsInitialized') )
- return;
-
+ // Add an option to the menu to recreate the player.
var t = this,
- player = this.get('player');
+ el = this.$('.player-menu .player-menu__item--stats')[0],
+ container = el && el.parentElement;
- if ( ! player )
- return;
+ if ( el && ! container.querySelector('.js-player-reset') ) {
+ var btn_link = utils.createElement('a', 'player-text-link js-player-reset', 'Reset Player'),
+ btn = utils.createElement('p', 'player-menu__item player-menu__item--reset', btn_link);
- this.set('ffzStatsInitialized', true);
+ btn_link.tabindex = '-1';
+ btn_link.href = '#';
- // Make it so stats can no longer be disabled if we want them.
- if ( player.setStatsEnabled ) {
- player.ffzSetStatsEnabled = player.setStatsEnabled;
- try {
- player.ffz_stats = player.getStatsEnabled();
- } catch(err) {
- // Assume stats are off.
- f.log("Player2 ffzInitStats: getStatsEnabled still doesn't work.");
- player.ffz_stats = false;
- }
+ btn_link.addEventListener('click', function(e) {
+ t.ffzRecreatePlayer();
+ e.preventDefault();
+ return false;
+ });
- player.setStatsEnabled = function(e, s) {
- if ( s !== false )
- player.ffz_stats = e;
-
- var out = player.ffzSetStatsEnabled(e || f.settings.player_stats);
-
- if ( ! t._ffz_player_stats_initialized ) {
- t._ffz_player_stats_initialized = true;
- player.addEventListener('statschange', update_stats);
- }
-
- return out;
- }
-
- this._ffz_stat_interval = setInterval(function() {
- if ( f.settings.player_stats || player.ffz_stats ) {
- player.ffzSetStatsEnabled(false);
- player.ffzSetStatsEnabled(true);
- }
- }, 5000);
- }
-
- if ( f.settings.player_stats && ( ! player.setStatsEnabled || ! player.ffz_stats ) ) {
- this._ffz_player_stats_initialized = true;
- player.addEventListener('statschange', update_stats);
- player.ffzSetStatsEnabled(true);
+ container.insertBefore(btn, el.nextSibling);
}
}
});
diff --git a/src/ember/room.js b/src/ember/room.js
index 1a110e8d..6a0e23d6 100644
--- a/src/ember/room.js
+++ b/src/ember/room.js
@@ -50,7 +50,8 @@ FFZ.prototype.setup_room = function() {
if ( RC ) {
var orig_ban = RC._actions.banUser,
- orig_to = RC._actions.timeoutUser;
+ orig_to = RC._actions.timeoutUser,
+ orig_show = RC._actions.showModOverlay;
RC._actions.banUser = function(e) {
orig_ban.call(this, e);
@@ -62,12 +63,15 @@ FFZ.prototype.setup_room = function() {
this.get("model").clearMessages(e.user, null, true);
}
- RC._actions.showModOverlay = function(e) {
- var Channel = utils.ember_resolve('model:channel');
- if ( ! Channel )
- return;
- var chan = Channel.find({id: e.sender});
+ RC._actions.showModOverlay = function(e) {
+ var Channel = utils.ember_resolve('model:deprecated-channel'),
+ chan = Channel && Channel.find && Channel.find({id: e.sender});
+
+ if ( ! chan ) {
+ f.log("Error opening mod card. model:deprecated-channel does not exist or does not have find!");
+ return orig_show.call(this, e);
+ }
// Don't try loading the channel if it's already loaded. Don't make mod cards
// refresh the channel page when you click the broadcaster, basically.
@@ -111,33 +115,7 @@ FFZ.prototype.setup_room = function() {
}
this.log("Hooking the Ember Room view.");
-
- var RoomView = utils.ember_resolve('view:room');
- this._modify_rview(RoomView);
-
- // For some reason, this doesn't work unless we create an instance of the
- // room view and then destroy it immediately.
- try {
- RoomView.create().destroy();
- } catch(err) { }
-
- // Modify all existing Room views.
- var views = utils.ember_views();
- for(var key in views) {
- if ( ! views.hasOwnProperty(key) )
- continue;
-
- var view = views[key];
- if ( !(view instanceof RoomView) )
- continue;
-
- this.log("Manually updating existing Room view.", view);
- try {
- view.ffzInit();
- } catch(err) {
- this.error("RoomView setup ffzInit: " + err);
- }
- }
+ this.update_views('view:room', this.modify_room_view);
}
@@ -145,29 +123,10 @@ FFZ.prototype.setup_room = function() {
// View Customization
// --------------------
-FFZ.prototype._modify_rview = function(view) {
+FFZ.prototype.modify_room_view = function(view) {
var f = this;
- view.reopen({
- didInsertElement: function() {
- this._super();
-
- try {
- this.ffzInit();
- } catch(err) {
- f.error("RoomView didInsertElement: " + err);
- }
- },
-
- willClearRender: function() {
- try {
- this.ffzTeardown();
- } catch(err) {
- f.error("RoomView willClearRender: " + err);
- }
- this._super();
- },
-
- ffzInit: function() {
+ utils.ember_reopen_view(view, {
+ ffz_init: function() {
f._roomv = this;
this.ffz_frozen = false;
@@ -196,6 +155,34 @@ FFZ.prototype._modify_rview = function(view) {
var controller = this.get('controller');
if ( controller ) {
controller.reopen({
+ calcRecipientEligibility: function(e) {
+ // Because this doesn't work properly with multiple channel rooms
+ // by default, do it ourselves.
+ var id = controller.get('model.roomProperties._id'),
+ update = function(data) {
+ if ( controller.isDestroyed || controller.get('model.roomProperties._id') !== id )
+ return;
+
+ controller.set('model._ffz_bits_eligibility', data);
+ controller.set('isRecipientBitsIneligible', ! data.eligible);
+ controller.set('isBitsHelperShown', data.eligible);
+ controller.set('minimumBits', data.minBits);
+
+ if ( ! data.eligible )
+ controller.set('isBitsTooltipActive', false);
+ };
+
+ if ( id === undefined )
+ return;
+
+ var data = controller.get('model._ffz_bits_eligibility');
+ if ( data === undefined )
+ controller.get('bits').loadRecipientEligibility(id).then(update);
+ else
+ update(data);
+
+ },
+
submitButtonText: function() {
if ( this.get("model.isWhisperMessage") && this.get("model.isWhispersEnabled") )
return i18n("Whisper");
@@ -214,7 +201,7 @@ FFZ.prototype._modify_rview = function(view) {
}
},
- ffzTeardown: function() {
+ ffz_destroy: function() {
if ( f._roomv === this )
f._roomv = undefined;
@@ -280,7 +267,7 @@ FFZ.prototype._modify_rview = function(view) {
btn.classList.toggle('ffz-banned', (room && room.get('ffz_banned') || false));
}
- var badge, id, info, vis_count = 0;
+ var badge, id, info, vis_count = 0, label;
for(var i=0; i < STATUS_BADGES.length; i++) {
info = STATUS_BADGES[i];
id = 'ffz-stat-' + info[0];
@@ -289,13 +276,18 @@ FFZ.prototype._modify_rview = function(view) {
if ( typeof visible === "string" )
visible = visible === "1";
+ label = typeof info[3] === "function" ? info[3].call(f, room) : undefined;
+
if ( ! badge ) {
- badge = utils.createElement('span', 'ffz room-state stat float-right', info[0].charAt(0).toUpperCase() + '' + info[0].substr(1).toUpperCase() + '');
+ badge = utils.createElement('span', 'ffz room-state stat float-right', (label || info[0]).charAt(0).toUpperCase() + '' + (label || info[0]).substr(1).toUpperCase() + '');
badge.id = id;
jQuery(badge).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'se')});
cont.appendChild(badge);
}
+ if ( label )
+ badge.innerHTML = (label || info[0]).charAt(0).toUpperCase() + '' + (label || info[0]).substr(1).toUpperCase() + '';
+
badge.title = typeof info[2] === "function" ? info[2].call(f, room) : info[2];
badge.classList.toggle('hidden', ! visible);
if ( visible )
@@ -494,7 +486,6 @@ FFZ.prototype._modify_rview = function(view) {
warning.classList.remove('hidden');
},
-
ffzUnwarnPaused: function() {
var el = this.get('element'),
warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
@@ -502,7 +493,6 @@ FFZ.prototype._modify_rview = function(view) {
if ( warning )
warning.classList.add('hidden');
}
-
});
}
@@ -640,12 +630,13 @@ FFZ.ffz_commands.help.help = "Usage: /ffz help [command]\nList available command
FFZ.prototype.update_room_important = function(id, controller) {
var Chat = controller || utils.ember_lookup('controller:chat'),
+ current_room = Chat && Chat.get('currentChannelRoom'),
room = this.rooms[id];
if ( ! room )
return;
- room.important = (Chat && room.room && Chat.get('currentChannelRoom') === room.room) || (room.room && room.room.get('isGroupRoom')) || (this.settings.pinned_rooms.indexOf(id) !== -1);
+ room.important = (room.room && current_room === room.room) || (current_room && current_room.ffz_host_target === id) || (room.room && room.room.get('isGroupRoom')) || (this.settings.pinned_rooms.indexOf(id) !== -1);
};
@@ -798,7 +789,7 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
before = first_existing.date && first_existing.date.getTime();
- this.parse_history(data, null, room_id, delete_links, tmiSession, function(msg) {
+ this.parse_history(data, null, null, room_id, delete_links, tmiSession, function(msg) {
if ( from_server )
msg.from_server = true;
@@ -815,7 +806,7 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
return true;
// Display the Ban Reason if we're a moderator or that user.
- if ( msg.tags['ban-reason'] && is_mine || r.get('isModeratorOrHigher') ) {
+ if ( msg.tags['ban-reason'] && (is_mine || r.get('isModeratorOrHigher')) ) {
msg.message = msg.message.substr(0, msg.message.length - 1) + ' with reason: ' + msg.tags['ban-reason'];
msg.cachedTokens = [utils.sanitize(msg.message)];
}
@@ -834,6 +825,12 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
if ( ! first_inserted )
first_inserted = msg;
+ // Store the message ID for this message, of course.
+ var msg_id = msg.tags && msg.tags.id,
+ ids = r.ffz_ids = r.ffz_ids || {};
+ if ( msg_id && ! ids[msg_id] )
+ ids[msg_id] = msg;
+
messages.unshiftObject(msg);
inserted += 1;
@@ -870,6 +867,11 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
if ( r.shouldShowMessage(msg) ) {
messages.insertAt(inserted, msg);
while ( messages.length > buffer_size ) {
+ // Remove this message from the ID tracker.
+ var m = messages.get(0);
+ if ( m.tags && m.tags.id && r.ffz_ids && r.ffz_ids[m.tags.id] )
+ delete r.ffz_ids[m.tags.id];
+
messages.removeAt(0);
removed++;
}
@@ -1042,6 +1044,7 @@ FFZ.prototype._modify_room = function(room) {
try {
f.add_room(this.id, this);
this.set("ffz_chatters", {});
+ this.set("ffz_ids", this.get('ffz_ids') || {});
} catch(err) {
f.error("add_room: " + err);
}
@@ -1063,6 +1066,7 @@ FFZ.prototype._modify_room = function(room) {
if ( user ) {
var duration = Infinity,
reason = undefined,
+ msg_id = undefined,
current_user = f.get_user(),
is_me = current_user && current_user.login === user;
@@ -1077,6 +1081,18 @@ FFZ.prototype._modify_room = function(room) {
reason = tags['ban-reason'];
+ // Is there a UUID on the end of the ban reason?
+ if ( reason ) {
+ var match = constants.UUID_TEST.exec(reason);
+ if ( match ) {
+ msg_id = match[1];
+ reason = reason.substr(0, reason.length - match[0].length);
+ if ( ! reason.length )
+ reason = undefined;
+ }
+ }
+
+
// If we were banned, set the state and update the UI.
if ( is_me ) {
t.set('ffz_banned', true);
@@ -1098,39 +1114,81 @@ FFZ.prototype._modify_room = function(room) {
t.ffzRecentlyBanned.shift();
- // Delete Visible Messages
- var msgs = t.get('messages'),
- total = msgs.get('length'),
- i = total,
- removed = 0;
+ // Are we deleting a specific message?
+ if ( msg_id && this.ffz_ids ) {
+ var msg = this.ffz_ids[msg_id];
+ if ( msg && msg.from === user ) {
+ msg.ffz_deleted = true;
+ if ( ! f.settings.prevent_clear )
+ msg.deleted = true;
- while(i--) {
- var msg = msgs.get(i);
+ if ( f.settings.remove_deleted )
+ if ( msg.pending )
+ msg.removed = true;
+ else {
+ var msgs = t.get('messages'),
+ total = msgs.get('length'),
+ i = total;
- if ( msg.from === user ) {
- if ( f.settings.remove_deleted ) {
- msgs.removeAt(i);
- removed++;
- continue;
+ while(i--) {
+ var msg = msgs.get(i);
+ if ( msg.tags && msg.tags.id === msg_id ) {
+ msgs.removeAt(i);
+ delete this.ffz_ids[msg_id];
+ break;
+ }
+ }
+ }
+
+ if ( msg._line ) {
+ Ember.propertyDidChange(msg._line, 'msgObject.ffz_deleted');
+ Ember.propertyDidChange(msg._line, 'msgObject.deleted');
}
- t.set('messages.' + i + '.ffz_deleted', true);
- if ( ! f.settings.prevent_clear )
- t.set('messages.' + i + '.deleted', true);
- }
- }
+ } else if ( msg.from !== user )
+ f.log("Banned Message ID #" + msg_id + " not owned by: " + user);
+ else
+ f.log("Banned Message ID #" + msg_id + " not found in chat.");
+ } else {
+ // Delete all messages from this user / chat.
+ // Delete Visible Messages
+ var msgs = t.get('messages'),
+ total = msgs.get('length'),
+ i = total,
+ removed = 0;
- // Delete Panding Messages
- if ( t.ffzPending ) {
- msgs = t.ffzPending;
- i = msgs.length;
while(i--) {
var msg = msgs.get(i);
- if ( msg.from !== user ) continue;
- msg.ffz_deleted = true;
- msg.deleted = !f.settings.prevent_clear;
- msg.removed = f.settings.remove_deleted;
+ if ( msg.from === user ) {
+ if ( f.settings.remove_deleted ) {
+ // Remove this message from the ID tracker.
+ if ( msg.tags && msg.tags.id && this.ffz_ids && this.ffz_ids[msg.tags.id] )
+ delete this.ffz_ids[msg.tags.id];
+
+ msgs.removeAt(i);
+ removed++;
+ continue;
+ }
+
+ t.set('messages.' + i + '.ffz_deleted', true);
+ if ( ! f.settings.prevent_clear )
+ t.set('messages.' + i + '.deleted', true);
+ }
+ }
+
+
+ // Delete Panding Messages
+ if ( t.ffzPending ) {
+ msgs = t.ffzPending;
+ i = msgs.length;
+ while(i--) {
+ var msg = msgs.get(i);
+ if ( msg.from !== user ) continue;
+ msg.ffz_deleted = true;
+ msg.deleted = !f.settings.prevent_clear;
+ msg.removed = f.settings.remove_deleted;
+ }
}
}
@@ -1164,6 +1222,7 @@ FFZ.prototype._modify_room = function(room) {
date: now,
ffz_ban_target: user,
reasons: reason ? [reason] : [],
+ msg_ids: msg_id ? [msg_id] : [],
durations: [duration],
end_time: end_time,
timeouts: 1,
@@ -1176,6 +1235,9 @@ FFZ.prototype._modify_room = function(room) {
this.addMessage(msg);
} else {
+ if ( msg_id && last_ban.msg_ids.indexOf(msg_id) === -1 )
+ last_ban.msg_ids.push(msg_id);
+
if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason);
@@ -1205,6 +1267,9 @@ FFZ.prototype._modify_room = function(room) {
last_ban = null;
if ( last_ban ) {
+ if ( msg_id && last_ban.msg_ids.indexOf(msg_id) === -1 )
+ last_ban.msg_ids.push(msg_id);
+
if ( reason && last_ban.reasons.indexOf(reason) === -1 )
last_ban.reasons.push(reason);
@@ -1223,6 +1288,7 @@ FFZ.prototype._modify_room = function(room) {
date: now,
ffz_ban_target: user,
reasons: reason ? [reason] : [],
+ msg_ids: msg_id ? [msg_id] : [],
durations: [duration],
end_time: end_time,
timeouts: 1,
@@ -1256,8 +1322,17 @@ FFZ.prototype._modify_room = function(room) {
len = messages.get("length"),
limit = this.get("messageBufferSize");
- if ( len > limit )
- messages.removeAt(0, len - limit);
+ if ( len > limit ) {
+ var to_remove = len - limit;
+ for(var i = 0; i < to_remove; i++) {
+ // Remove this message from the ID tracker.
+ var msg = messages.get(i);
+ if ( msg.tags && msg.tags.id && this.ffz_ids && this.ffz_ids[msg.tags.id] )
+ delete this.ffz_ids[msg.tags.id];
+ }
+
+ messages.removeAt(0, to_remove);
+ }
},
// Artificial chat delay
@@ -1267,6 +1342,7 @@ FFZ.prototype._modify_room = function(room) {
this.ffzPending = [];
var now = msg.time = Date.now();
+ msg.pending = true;
this.ffzPending.push(msg);
this.ffzSchedulePendingFlush(now);
@@ -1320,12 +1396,18 @@ FFZ.prototype._modify_room = function(room) {
for (var i = 0, l = this.ffzPending.length; i < l; i++) {
var msg = this.ffzPending[i];
- if ( msg.removed )
+ if ( msg.removed ) {
+ // Don't keep this message ID around.
+ var msg_id = msg && msg.tags && msg.tags.id;
+ if ( msg_id && this.ffz_ids && this.ffz_ids[msg_id] )
+ delete this.ffz_ids[msg_id];
continue;
+ }
if ( f.settings.chat_delay !== 0 && (f.settings.chat_delay + msg.time > now) )
break;
+ msg.pending = false;
this.ffzActualPushMessage(msg);
}
@@ -1367,10 +1449,16 @@ FFZ.prototype._modify_room = function(room) {
}
},
+ onMessage: function(msg) {
+ // We do our own batching. With blackjack, and hookers. You know what? Forget the batching.
+ this.addMessage(msg);
+ },
+
addMessage: function(msg) {
if ( msg ) {
var is_resub = msg.tags && msg.tags['msg-id'] === 'resub',
- room_id = this.get('id');
+ room_id = this.get('id'),
+ msg_id = msg.tags && msg.tags.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'])) )
@@ -1515,6 +1603,13 @@ FFZ.prototype._modify_room = function(room) {
if ( f._chat_filters[i](msg) === false )
return;
+ // We're past the last return, so store the message
+ // now that we know we're keeping it.
+ if ( msg_id ) {
+ var ids = this.ffz_ids = this.ffz_ids || {};
+ ids[msg_id] = msg;
+ }
+
// Report this message to the dashboard.
if ( window !== window.parent && parent.postMessage && msg.from && msg.from !== "jtv" && msg.from !== "twitchnotify" )
parent.postMessage({from_ffz: true, command: 'chat_message', data: {from: msg.from, room: msg.room}}, "*"); //location.protocol + "//www.twitch.tv/");
diff --git a/src/ember/vod-chat.js b/src/ember/vod-chat.js
index f264439e..020e11e5 100644
--- a/src/ember/vod-chat.js
+++ b/src/ember/vod-chat.js
@@ -12,16 +12,10 @@ var FFZ = window.FrankerFaceZ,
// ---------------------
FFZ.prototype.setup_vod_chat = function() {
- var f = this,
- VRC = utils.ember_resolve('component:vod-right-column');
-
- if ( VRC )
- this._modify_vod_right_column(VRC);
- else
- f.error("Unable to locate VOD Right Column component.");
-
// Get the VOD Chat Service
- var VODService = utils.ember_lookup('service:vod-chat-service');
+ var f = this,
+ VODService = utils.ember_lookup('service:vod-chat-service');
+
if ( VODService )
VODService.reopen({
messageBufferSize: f.settings.scrollback_length,
@@ -50,56 +44,59 @@ FFZ.prototype.setup_vod_chat = function() {
else
f.error("Unable to locate VOD Chat Service.");
- // Get the VOD Chat Display
- var VODChat = utils.ember_resolve('component:vod-chat-display');
-
- if ( VODChat )
- this._modify_vod_chat_display(VODChat);
- else
- f.error("Unable to locate VOD Chat Display component.");
-
- // Modify all existing VOD Chat views.
- var views = utils.ember_views();
- for(var key in views) {
- var view = views[key];
-
- if ( VRC && view instanceof VRC ) {
- this.log("Manually updating existing VOD Right Column.");
- try {
- this._modify_vod_right_column(view);
- view.ffzInit();
- //Ember.propertyDidChange(view, 'canSeeDarkLaunch');
- } catch(err) {
- this.error("setup: setup_vod_chat: " + err);
- }
-
- } else if ( VODChat && view instanceof VODChat ) {
- this.log("Manually updating existing VOD Chat view.", view);
- try {
- this._modify_vod_chat_display(view);
- view.ffzInit();
- } catch(err) {
- this.error("setup: setup_vod_chat: " + err);
- }
- }
- }
+ this.update_views('component:vod-right-column', this.modify_vod_right_column);
+ this.update_views('view:vod', this.modify_vod_view);
+ this.update_views('component:vod-chat-display', this.modify_vod_chat_display);
}
-FFZ.prototype._modify_vod_right_column = function(component) {
+FFZ.prototype.modify_vod_view = function(view) {
var f = this;
+ utils.ember_reopen_view(view, {
+ ffz_init: function() {
+ f._vodv = this;
- component.reopen({
- didInsertElement: function() {
- this._super();
- try {
- this.ffzInit();
- } catch(err) {
- f.error("VODRightColumn didInsertElement: " + err);
- }
+ var channel_id = this.get('context.channel.name');
+
+ if ( f.settings.auto_theater ) {
+ var player = f.players && f.players[channel_id] && f.players[channel_id].get('player');
+ if ( player )
+ player.setTheatre(true);
+ }
+
+ // Listen to scrolling.
+ this._ffz_scroller = this.ffzOnScroll.bind(this);
+ jQuery(this.get('element')).parents('.tse-scroll-content').on('scroll', this._ffz_scroller);
},
- ffzInit: function() {
+ ffz_destroy: function() {
+ if ( f._vodv === this )
+ f._vodv = null;
+
+ if ( this._ffz_scroller ) {
+ jQuery(this.get('element')).parents('.tse-scroll-content').off('scroll', this._ffz_scroller);
+ this._ffz_scroller = null;
+ }
+ },
+
+ ffzOnScroll: function(event) {
+ // When we scroll past the bottom of the player, do stuff!
+ var top = event && event.target && event.target.scrollTop,
+ height = this.get('layout.playerSize.1');
+
+ if ( ! top )
+ top = jQuery(this.get('element')).parents('.tse-scroll-content').scrollTop();
+
+ document.body.classList.toggle('ffz-small-player', f.settings.small_player && top >= height);
+ }
+ });
+}
+
+
+FFZ.prototype.modify_vod_right_column = function(component) {
+ var f = this;
+ utils.ember_reopen_view(component, {
+ ffz_init: function() {
if ( f.settings.dark_twitch ) {
var el = this.get('element'),
cont = el && el.querySelector('.chat-container');
@@ -112,29 +109,11 @@ FFZ.prototype._modify_vod_right_column = function(component) {
}
-FFZ.prototype._modify_vod_chat_display = function(component) {
+FFZ.prototype.modify_vod_chat_display = function(component) {
var f = this,
VODService = utils.ember_lookup('service:vod-chat-service');
- component.reopen({
- didInsertElement: function() {
- this._super();
- try {
- this.ffzInit();
- } catch(err) {
- f.error("VODChat didInsertElement: " + err);
- }
- },
-
- willClearRender: function() {
- try {
- this.ffzTeardown();
- } catch(err) {
- f.error("VODChat willClearRender: " + err);
- }
- this._super();
- },
-
+ utils.ember_reopen_view(component, {
_prepareToolTips: function() {
this.$(".tooltip").tipsy({
live: true,
@@ -142,7 +121,7 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
})
},
- ffzInit: function() {
+ ffz_init: function() {
f._vodc = this;
// Load the room, if necessary
@@ -153,22 +132,9 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
this.ffz_frozen = false;
if ( f.settings.chat_hover_pause )
this.ffzEnableFreeze();
-
- /*this.$('.chat-messages').find('.html-tooltip').tipsy({
- live: true, html: true,
- gravity: utils.tooltip_placement(2 * constants.TOOLTIP_DISTANCE, function() {
- return this.classList.contains('right') ? 'e' : 'n'
- })});
-
- this.$('.chat-messages').find('.ffz-tooltip').tipsy({
- live: true, html: true,
- title: f.render_tooltip(),
- gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, function() {
- return this.classList.contains('right') ? 'e' : 'n'
- })});*/
},
- ffzTeardown: function() {
+ ffz_destroy: function() {
if ( f._vodc === this )
f._vodc = undefined;
@@ -232,14 +198,6 @@ FFZ.prototype._modify_vod_chat_display = function(component) {
}
},
- /*ffzMouseDown: function(event) {
- var scroller = this.get('chatMessagesScroller');
- if ( scroller && scroller[0] && ((!this.ffz_frozen && "mousedown" === event.type) || "mousewheel" === event.type || (is_android && "scroll" === event.type) ) ) {
- var r = scroller[0].scrollHeight - scroller[0].scrollTop - scroller[0].offsetHeight;
- this._setStuckToBottom(10 >= r);
- }
- },*/
-
ffzMouseOut: function(event) {
this._ffz_outside = true;
var e = this;
diff --git a/src/ember/wrapper.js b/src/ember/wrapper.js
new file mode 100644
index 00000000..1451563b
--- /dev/null
+++ b/src/ember/wrapper.js
@@ -0,0 +1,88 @@
+var FFZ = window.FrankerFaceZ,
+ utils = require("../utils"),
+ constants = require("../constants");
+
+// --------------------
+// Initialization
+// --------------------
+
+FFZ.prototype.setup_ember_wrapper = function() {
+ this._views_to_update = [];
+ this._ember_finalized = false;
+}
+
+
+FFZ.prototype.update_views = function(klass, modifier, if_not_exists) {
+ var original_klass;
+ if ( typeof klass === 'string' ) {
+ original_klass = klass;
+ klass = utils.ember_resolve(klass);
+ if ( ! klass && if_not_exists ) {
+ if ( typeof if_not_exists === "function" )
+ if_not_exists.call(this, klass, modifier);
+ else {
+ klass = Ember.Component.extend({});
+ App.__registry__.register(original_klass, klass);
+ }
+ }
+
+ if ( ! klass ) {
+ this.error("Unable to locate the Ember " + original_klass);
+ return false;
+ }
+ } else
+ original_klass = klass.toString();
+
+ if ( this._ember_finalized )
+ this._update_views([[original_klass, klass, modifier]]);
+ else
+ this._views_to_update.push([original_klass, klass, modifier]);
+
+ return true;
+}
+
+
+FFZ.prototype.finalize_ember_wrapper = function() {
+ this._ember_finalized = true;
+ var views = this._views_to_update;
+ this._views_to_update = null;
+ this._update_views(views);
+}
+
+
+FFZ.prototype._update_views = function(klasses) {
+ this.log("Updating Ember classes and instances.", klasses);
+ // Modify all pending classes and clear them from cache.
+ for(var i=0; i < klasses.length; i++) {
+ klasses[i][2].call(this, klasses[i][1]);
+
+ try {
+ klasses[i][1].create().destroy()
+ } catch(err) {
+ this.log("There was an error creating and destroying an instance of the Ember class \"" + klasses[i][0] + "\" to clear its cache.", err);
+ }
+ }
+
+ // Iterate over all existing views and update them as necessary.
+ var views = utils.ember_views();
+ for(var view_id in views) {
+ var view = views[view_id];
+ if ( ! view )
+ continue;
+
+ for(var i=0; i < klasses.length; i++)
+ if ( view instanceof klasses[i][1] ) {
+ try {
+ if ( ! view.ffz_modified )
+ klasses[i][2].call(this, view);
+
+ (view.ffz_update || view.ffz_init).call(view);
+
+ } catch(err) {
+ this.error("An error occured when updating an existing Ember instance of: " + klasses[i][0], err);
+ }
+
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ext/api.js b/src/ext/api.js
index 88420426..2af0ca84 100644
--- a/src/ext/api.js
+++ b/src/ext/api.js
@@ -1,7 +1,6 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
-
build_css = function(emote) {
if ( ! emote.margins && ! emote.css )
return "";
@@ -50,11 +49,15 @@ var API = FFZ.API = function(instance, name, icon, version) {
this.global_sets = [];
this.default_sets = [];
+ this.badges = {};
+
this.users = {};
this.chat_filters = [];
this.on_room_callbacks = [];
this.name = name || ("Extension#" + this.id);
+ this.name_key = this.name.replace(/[^A-Z0-9_\-]/g, '').toLowerCase();
+
this.icon = icon || null;
this.version = version || null;
@@ -386,13 +389,75 @@ API.prototype.unregister_room_set = function(room_id, id) {
}
+// -----------------------
+// Badge APIs
+// -----------------------
+
+API.prototype.add_badge = function(badge_id, badge) {
+ var exact_id = this.id + '-' + badge_id,
+
+ real_badge = {
+ id: exact_id,
+ source_ext: this.id,
+ source_id: badge_id,
+ alpha_image: badge.alpha_image,
+ color: badge.color || "transparent",
+ no_invert: badge.no_invert,
+ invert_invert: badge.invert_invert,
+ css: badge.css,
+ image: badge.image,
+ name: badge.name,
+ title: badge.title,
+ slot: badge.slot,
+ visible: badge.visible,
+ replaces: badge.replaces,
+ replaces_type: badge.replaces_type
+ };
+
+ this.ffz.badges[exact_id] = this.badges[badge_id] = real_badge;
+ utils.update_css(this.ffz._badge_style, exact_id, utils.badge_css(real_badge));
+}
+
+
+API.prototype.remove_badge = function(badge_id) {
+ var exact_id = this.id + '-' + badge_id;
+ this.ffz.badges[exact_id] = this.badges[badge_id] = undefined;
+ utils.update_css(this.ffz._badge_style, exact_id);
+}
+
+
// -----------------------
// User Modifications
// -----------------------
-API.prototype.user_add_set = function(user_name, set_id) {
- var user = this.users[user_name] = this.users[user_name] || {},
- ffz_user = this.ffz.users[user_name] = this.ffz.users[user_name] || {},
+API.prototype.user_add_badge = function(username, slot, badge_id) {
+ var user = this.users[username] = this.users[username] || {},
+ ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
+
+ badges = user.badges = user.badges || {},
+ ffz_badges = ffz_user.badges = ffz_user.badges || {},
+
+ exact_id = this.id + '-' + badge_id,
+ badge = {id: exact_id};
+
+ badges[slot] = ffz_badges[slot] = badge;
+}
+
+
+API.prototype.user_remove_badge = function(username, slot) {
+ var user = this.users[username] = this.users[username] || {},
+ ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
+
+ badges = user.badges = user.badges || {},
+ ffz_badges = ffz_user.badges = ffz_user.badges || {};
+
+ badges[slot] = ffz_badges[slot] = null;
+}
+
+
+API.prototype.user_add_set = function(username, set_id) {
+ var user = this.users[username] = this.users[username] || {},
+ ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
emote_sets = user.sets = user.sets || [],
ffz_sets = ffz_user.sets = ffz_user.sets || [],
@@ -407,14 +472,14 @@ API.prototype.user_add_set = function(user_name, set_id) {
// Update tab completion.
var user = this.ffz.get_user();
- if ( this.ffz._inputv && user && user.login === user_name )
+ if ( this.ffz._inputv && user && user.login === username )
Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
}
-API.prototype.user_remove_set = function(user_name, set_id) {
- var user = this.users[user_name],
- ffz_user = this.ffz.users[user_name],
+API.prototype.user_remove_set = function(username, set_id) {
+ var user = this.users[username],
+ ffz_user = this.ffz.users[username],
emote_sets = user && user.sets,
ffz_sets = ffz_user && ffz_user.sets,
@@ -431,7 +496,7 @@ API.prototype.user_remove_set = function(user_name, set_id) {
// Update tab completion.
var user = this.ffz.get_user();
- if ( this.ffz._inputv && user && user.login === user_name )
+ if ( this.ffz._inputv && user && user.login === username )
Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
}
@@ -455,7 +520,6 @@ API.prototype.unregister_chat_filter = function(filter) {
this.ffz._chat_filters.splice(ind, 1);
}
-
// -----------------------
// Channel Callbacks
// -----------------------
diff --git a/src/ext/betterttv.js b/src/ext/betterttv.js
index 7f441135..18a23a6f 100644
--- a/src/ext/betterttv.js
+++ b/src/ext/betterttv.js
@@ -96,6 +96,10 @@ FFZ.prototype.setup_bttv = function(delay) {
this.toggle_style('chat-hc-background');*/
this.toggle_style('chat-colors-gray');
+ this.toggle_style('badges-rounded');
+ this.toggle_style('badges-circular');
+ this.toggle_style('badges-blank');
+ this.toggle_style('badges-circular-small');
this.toggle_style('badges-transparent');
this.toggle_style('badges-sub-notice');
this.toggle_style('badges-sub-notice-on');
@@ -114,12 +118,15 @@ FFZ.prototype.setup_bttv = function(delay) {
}
// Send Message Behavior
- var original_send = BetterTTV.chat.helpers.sendMessage, f = this;
- BetterTTV.chat.helpers.sendMessage = function(message) {
+ var f = this,
+ BC = BetterTTV.chat,
+ original_send = BC.helpers.sendMessage;
+
+ BC.helpers.sendMessage = function(message) {
var cmd = message.split(' ', 1)[0].toLowerCase();
- if ( cmd === "/ffz" )
- f.run_ffz_command(message.substr(5), BetterTTV.chat.store.currentRoom);
+ if ( cmd === '/ffz' )
+ f.run_ffz_command(message.substr(5), BC.store.currentRoom);
else
return original_send(message);
}
@@ -127,9 +134,10 @@ FFZ.prototype.setup_bttv = function(delay) {
// Ugly Hack for Current Room, as this is stripped out before we get to
// the actual privmsg renderer.
- var original_handler = BetterTTV.chat.handlers.onPrivmsg,
+ var original_handler = BC.handlers.onPrivmsg,
received_room;
- BetterTTV.chat.handlers.onPrivmsg = function(room, data) {
+
+ BC.handlers.onPrivmsg = function(room, data) {
received_room = room;
var output = original_handler(room, data);
received_room = null;
@@ -138,39 +146,48 @@ FFZ.prototype.setup_bttv = function(delay) {
// Message Display Behavior
- var original_privmsg = BetterTTV.chat.templates.privmsg;
- BetterTTV.chat.templates.privmsg = function(highlight, action, server, isMod, data) {
+ var original_privmsg = BC.templates.privmsg;
+ BC.templates.privmsg = function(data, opts) {
try {
+ opts = opts || {};
+
// Handle badges.
f.bttv_badges(data);
// Now, do everything else manually because things are hard-coded.
- return '