var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants');
// --------------------
// Settings
// --------------------
FFZ.basic_settings.delayed_chat = {
type: "select",
options: {
0: "No Delay",
300: "Minor (Bot Moderation; 0.3s)",
1200: "Normal (Human Moderation; 1.2s)",
5000: "Large (Spoiler Removal / Really Slow Mods; 5s)",
10000: "Extra Large (10s)",
15000: "Extremely Large (15s)",
20000: "Mods Asleep; Delay Chat (20s)",
30000: "Half a Minute (30s)",
60000: "Why??? (1m)"
},
category: "Chat",
no_bttv: true,
name: "Delay and Filter Chat",
help: "Delay the appearance of chat messages to allow time for moderation and completely hide removed messages.",
get: function() {
if ( ! this.settings.remove_deleted || ! this.settings.remove_bot_ban_notices )
return 0;
return this.settings.chat_delay;
},
set: function(val) {
val = +val;
this.settings.set('remove_deleted', val !== 0);
this.settings.set('remove_bot_ban_notices', val !== 0);
this.settings.set('chat_delay', val);
}
};
FFZ.settings_info.minimal_chat = {
type: "select",
options: {
0: "Disabled",
1: "No Heading",
2: "Minimalistic Input",
3: "All"
},
value: 0,
category: "Chat Appearance",
name: "Minimalistic Chat",
help: "Hide all of the chat user interface, only showing messages and an input box.",
process_value: function(val) {
if ( val === false )
return 0;
else if ( val === true )
return 3;
else if ( typeof val === "string" )
return parseInt(val) || 0;
return val;
},
on_update: function(val) {
document.body.classList.toggle("ffz-minimal-chat-head", val === 1 || val === 3);
document.body.classList.toggle("ffz-minimal-chat-input", val > 1);
if ( this.settings.group_tabs && this._chatv && this._chatv._ffz_tabs ) {
var f = this;
setTimeout(function() {
f._chatv && f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px");
f._roomv && f._roomv.get('stuckToBottom') && f._roomv._scrollToBottom();
},0);
}
if ( (val === 1 || val === 3) && this._chatv && this._chatv.get('controller.showList') )
this._chatv.set('controller.showList', false);
// Remove the style if we have it.
if ( ! (val > 1) && this._chat_style ) {
if ( this._inputv ) {
if ( this._inputv._ffz_minimal_style )
this._inputv._ffz_minimal_style.innerHTML = '';
this._inputv._ffz_last_height = undefined;
}
utils.update_css(this._chat_style, "input_height", '');
this._roomv && this._roomv.get('stuckToBottom') && this._roomv._scrollToBottom();
} else if ( val > 1 && this._inputv )
this._inputv.ffzResizeInput();
}
};
FFZ.settings_info.chat_batching = {
type: "select",
options: {
0: "No Batching",
250: "Minor (0.25s)",
500: "Normal (0.5s)",
750: "Large (0.75s)",
1000: "Extreme (1s)"
},
value: 0,
category: "Chat Appearance",
no_bttv: true,
name: "Chat Message Batching",
help: "Display chat messages in batches to improve performance in extremely fast chats.",
process_value: function(val) {
if ( typeof val === "string" )
return parseInt(val) || 0;
return val;
},
on_update: function(val) {
if ( this._roomv )
this._roomv.ffzUpdateStatus();
}
};
FFZ.settings_info.chat_delay = {
type: "select",
options: {
0: "No Delay",
300: "Minor (Bot Moderation; 0.3s)",
1200: "Normal (Human Moderation; 1.2s)",
5000: "Large (Spoiler Removal / Really Slow Mods; 5s)",
10000: "Extra Large (10s)",
15000: "Extremely Large (15s)",
20000: "Mods Asleep; Delay Chat (20s)",
30000: "Half a Minute (30s)",
60000: "Why??? (1m)"
},
value: 0,
category: "Chat Appearance",
no_bttv: true,
name: "Artificial Chat Delay",
help: "Delay the appearance of chat messages to allow for moderation before you see them.",
process_value: function(val) {
if ( typeof val === "string" )
return parseInt(val || "0");
return val;
},
on_update: function (val) {
if ( this._roomv )
this._roomv.ffzUpdateStatus();
}
};
FFZ.settings_info.remove_deleted = {
type: "boolean",
value: false,
no_bttv: true,
category: "Chat Filtering",
name: "Remove Deleted Messages",
help: "Remove deleted messages from chat entirely rather than leaving behind a clickable <deleted message>.",
on_update: function(val) {
if ( this.has_bttv || ! this.rooms || ! val )
return;
for(var room_id in this.rooms) {
var ffz_room = this.rooms[room_id],
room = ffz_room && ffz_room.room;
if ( ! room )
continue;
var msgs = room.get('messages'),
total = msgs.get('length'),
i = total,
alternate;
while(i--) {
var msg = msgs.get(i);
if ( msg.ffz_deleted || msg.deleted ) {
if ( alternate === undefined )
alternate = msg.ffz_alternate;
msgs.removeAt(i);
continue;
}
if ( alternate === undefined )
alternate = msg.ffz_alternate;
else {
alternate = ! alternate;
room.set('messages.' + i + '.ffz_alternate', alternate);
}
}
}
}
};
FFZ.settings_info.remove_bot_ban_notices = {
type: "boolean",
value: false,
no_bttv: true,
category: "Chat Filtering",
name: "Remove Bot Ban Notices",
help: "Remove messages from bots announcing who was banned for what reason and for how long.",
};
FFZ.settings_info.prevent_clear = {
type: "boolean",
value: false,
no_bttv: true,
category: "Chat Filtering",
name: "Show Deleted Messages",
help: "Fade deleted messages instead of replacing them, and prevent chat from being cleared.",
on_update: function(val) {
if ( this.has_bttv || ! this.rooms )
return;
for(var room_id in this.rooms) {
var ffz_room = this.rooms[room_id],
room = ffz_room && ffz_room.room;
if ( ! room )
continue;
room.get("messages").forEach(function(s, n) {
if ( val && ! s.ffz_deleted && s.deleted )
room.set("messages." + n + ".deleted", false);
else if ( s.ffz_deleted && ! val && ! s.deleted )
room.set("messages." + n + ".deleted", true);
});
}
}
};
FFZ.settings_info.chat_history = {
type: "boolean",
value: true,
visible: false,
category: "Chat Appearance",
name: "Chat History Alpha",
help: "Load previous chat messages when loading a chat room so you can see what people have been talking about. This currently only works in a handful of channels due to server capacity.",
};
FFZ.settings_info.group_tabs = {
type: "select",
options: {
0: "Disabled",
1: "Rooms with Recent Activity",
2: "Rooms with Recent Mentions",
3: "All Rooms"
},
value: 0,
process_value: function(val) {
if ( val === false )
return 0;
else if ( val === true )
return 3;
else if ( typeof val === "string" )
return parseInt(val) || 0;
return val;
},
no_bttv: true,
category: "Chat Appearance",
name: "Chat Room Tabs",
help: "Display tabs for chat rooms with recent activity at the top of the chat window for more convenient chatting.",
on_update: function(val) {
if ( this.has_bttv || ! this._chatv )
return;
if ( val )
if ( this._chatv._ffz_tabs )
this._chatv.ffzRebuildTabs();
else
this._chatv.ffzEnableTabs();
else
this._chatv.ffzDisableTabs();
this._chatv.ffzUpdateMenuUnread();
}
};
FFZ.settings_info.pinned_rooms = {
value: [],
visible: false,
};
FFZ.settings_info.visible_rooms = {
value: [],
visible: false,
};
// --------------------
// Initialization
// --------------------
FFZ.prototype.refresh_chat = function() {
var parents, lines = jQuery('ul.chat-lines');
if ( this.has_bttv || ! lines || ! lines.length )
return;
parents = lines.parents('.chatReplay');
if ( parents && parents.length )
return;
// There are chat-lines in the DOM and they aren't chat replay.
var controller = utils.ember_lookup('controller:chat');
if ( ! controller )
return;
var current_room = controller.get("currentRoom");
controller.blurRoom();
controller.focusRoom(current_room);
}
FFZ.prototype.setup_chatview = function() {
document.body.classList.toggle("ffz-minimal-chat-head", this.settings.minimal_chat === 1 || this.settings.minimal_chat === 3);
document.body.classList.toggle("ffz-minimal-chat-input", this.settings.minimal_chat === 2 || this.settings.minimal_chat === 3);
this.log("Hooking the Ember Chat controller.");
var Chat = utils.ember_lookup('controller:chat'),
f = this;
if ( Chat ) {
Chat.reopen({
ffzUpdateChannels: function() {
if ( ! f._chatv || f.has_bttv )
return;
f._chatv.ffzRebuildMenu();
if ( f.settings.group_tabs )
f._chatv.ffzRebuildTabs();
}.observes("currentChannelRoom", "connectedPrivateGroupRooms"),
ffzUpdateInvites: function() {
if ( ! f._chatv || f.has_bttv )
return;
f._chatv.ffzUpdateMenuUnread();
}.observes("invitedPrivateGroupRooms"),
ffzChangedRoom: function() {
if ( f._inputv )
Ember.propertyDidChange(f._inputv, 'ffz_emoticons');
}.observes('currentRoom'),
notificationsCount: function() {
if ( ! f._chatv || f.has_bttv )
return this._super();
var total = this.get('invitedPrivateGroupRooms.length') || 0;
if ( ! f._chatv._ffz_tabs && f._chatv.ffz_unread )
for(var room_id in f._chatv.ffz_unread)
if ( f._chatv.ffz_unread[room_id] )
total++;
return total;
}.property("currentRoom", "currentChannelRoom", "currentChannelRoom.unreadCount", "invitedPrivateGroupRooms.length", "connectedPrivateGroupRooms.@each.unreadCount"),
_kickUserFromRoomNoLongerInList: function() {
// Remove an unread notice for any missing channels.
if ( f._chatv && f._chatv.ffz_unread ) {
var updated = false;
for(var room_id in f._chatv.ffz_unread)
if ( f._chatv.ffz_unread[room_id] && (!f.rooms[room_id] || !f.rooms[room_id].room) ) {
f._chatv.ffz_unread[room_id] = false;
updated = true;
}
if ( updated )
f._chatv.ffzUpdateMenuUnread();
}
var room = this.get("currentRoom"),
room_id = room && room.get('id'),
channel_room = this.get("currentChannelRoom"),
is_group = room && _.contains(this.get("privateGroupRooms.content") || [], room);
if ( room === channel_room || is_group || (f._chatv && f._chatv._ffz_host === room_id) || (f.settings.pinned_rooms && f.settings.pinned_rooms.indexOf(room_id) !== -1) )
return;
this.blurRoom();
if ( ! this.get("showList") )
this.send("toggleMode");
}.observes("privateGroupRooms.@each"),
removeCurrentChannelRoom: function() {
if ( f.has_bttv )
return this._super();
var room = this.get("currentChannelRoom"),
room_id = room && room.get('id'),
user = f.get_user();
// Don't clean up pinned rooms or the current host target.
if ( !((f._chatv && f._chatv._ffz_host === room_id) || (f.settings.pinned_rooms && f.settings.pinned_rooms.indexOf(room_id) !== -1)) ) {
if ( room === this.get("currentRoom") )
this.blurRoom();
// Don't destroy it if it's the user's room.
if ( room && user && user.login !== room_id )
room.ffzScheduleDestroy();
}
this.set("currentChannelRoom", void 0);
}
});
}
this.log("Hooking the Ember Chat view.");
var Chat = utils.ember_resolve('view:chat');
this._modify_cview(Chat);
// For some reason, this doesn't work unless we create an instance of the
// chat view and then destroy it immediately.
try {
Chat.create().destroy();
} catch(err) { }
// Modify all existing Chat views.
var views = utils.ember_views();
for(var key in views) {
if ( ! views.hasOwnProperty(key) )
continue;
var view = views[key];
if ( !(view instanceof Chat) )
continue;
this.log("Manually updating existing Chat view.", view);
try {
if ( ! view.ffzInit )
this._modify_cview(view);
view.ffzInit();
} catch(err) {
this.error("setup: build_ui_link: " + err);
}
}
}
// --------------------
// Modify Chat View
// --------------------
FFZ.prototype._modify_cview = function(view) {
var f = this;
view.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("view:chat ffzInit error: " + err);
}
},
didUpdate: function() {
this._super();
f.log("view:chat didUpdate", this)
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("view:chat ffzTeardown error: " + err);
}
this._super();
},
ffzInit: function() {
f._chatv = this;
this.$('.textarea-contain').append(f.build_ui_link(this));
this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
this.$('.chat-messages').find('.ffz-tooltip').tipsy({live: true, html: true, title: f.render_tooltip(), gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
if ( ! f.has_bttv ) {
if ( f.settings.group_tabs )
this.ffzEnableTabs();
this.ffzRebuildMenu();
}
this.ffz_pruner = setInterval(this.ffzPruneTabs.bind(this), 10000);
setTimeout(function() {
if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs )
f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px");
var controller = f._chatv && f._chatv.get('controller');
controller && controller.set('showList', false);
}, 1000);
},
ffzTeardown: function() {
if ( f._chatv === this )
f._chatv = null;
if ( this.ffz_pruner ) {
clearInterval(this.ffz_pruner);
this.ffz_pruner = null;
}
this.$('.textarea-contain .ffz-ui-toggle').remove();
if ( f.settings.group_tabs )
this.ffzDisableTabs();
this.ffzTeardownMenu();
this.ffzUnloadHost();
},
ffzPruneTabs: function() {
if ( ! this._ffz_tabs )
return;
var elements = this._ffz_tabs.querySelectorAll('.ffz-chat-tab:not(.hidden):not(.active)'),
update_height = false;
for(var i=0; i < elements.length; i++) {
var el = elements[i],
room_id = el.getAttribute('data-room'),
was_hidden = el.classList.contains('hidden'),
is_hidden = ! this.ffzTabVisible(room_id);
if ( was_hidden !== is_hidden ) {
el.classList.toggle('hidden', is_hidden);
update_height = true;
}
}
if ( update_height )
this.$('.chat-room').css('top', this._ffz_tabs.offsetHeight + "px");
},
ffzChangeRoom: Ember.observer('controller.currentRoom', function() {
f.update_ui_link();
this.ffz_unread = this.ffz_unread || {};
// Close mod cards when changing to a new room.
if ( f._mod_card )
f._mod_card.send('close');
var room = this.get('controller.currentRoom'),
room_id = room && room.get('id'),
was_unread = room_id && this.ffz_unread[room_id],
update_height = false;
if ( room ) {
room.resetUnreadCount();
room.ffz_last_view = Date.now();
}
if ( room && room._ffz_tab ) {
var was_hidden = room._ffz_tab.classList.contains('hidden'),
is_hidden = ! this.ffzTabVisible(room_id);
if ( was_hidden !== is_hidden ) {
room._ffz_tab.classList.toggle('hidden', is_hidden);
update_height = true;
}
}
if ( was_unread && room_id ) {
this.ffz_unread[room_id] = false;
this.ffzUpdateMenuUnread();
}
if ( this._ffz_chan_table )
jQuery('.ffz-room-row.active', this._ffz_chan_table).removeClass('active');
if ( this._ffz_group_table )
jQuery('.ffz-room-row.active', this._ffz_group_table).removeClass('active');
if ( this._ffz_tabs ) {
jQuery('.ffz-chat-tab.active', this._ffz_tabs).removeClass('active');
// Invite Link
var can_invite = room && room.get('canInvite');
if ( this._ffz_invite )
this._ffz_invite.classList.toggle('hidden', ! can_invite);
this.set('controller.showInviteUser', can_invite && this.get('controller.showInviteUser'));
update_height = true;
}
if ( room && room._ffz_tab ) {
room._ffz_tab.classList.remove('tab-mentioned');
room._ffz_tab.classList.add('active');
var sp = room._ffz_tab.querySelector('span');
if ( sp )
sp.innerHTML = '';
}
if ( room && room._ffz_row ) {
room._ffz_row.classList.remove('row-mentioned');
room._ffz_row.classList.add('active');
var sp = room._ffz_row.querySelector('span');
if ( sp )
sp.innerHTML = '';
}
if ( update_height )
this.$('.chat-room').css('top', this._ffz_tabs.offsetHeight + "px");
}),
// Hosted Channel Chat
ffzUnloadHost: function() {
if ( ! this._ffz_host )
return;
if ( f.settings.pinned_rooms.indexOf(this._ffz_host) === -1 ) {
if ( this.get('controller.currentRoom') === this._ffz_host_room )
this.get('controller').blurRoom();
// Schedule the room to be destroyed. This is after a short
// delay to make sure we aren't just loading the room in a
// new way.
this._ffz_host_room.ffzScheduleDestroy();
}
this._ffz_host = null;
this._ffz_host_room = null;
},
ffzUpdateHost: function() {
var Channel = utils.ember_lookup('controller:channel'),
Room = utils.ember_resolve('model:room'),
target = Room && Channel && Channel.get('hostModeTarget'),
updated = false;
if ( f.has_bttv )
return;
if ( target ) {
var target_id = target.get('id');
if ( this._ffz_host !== target_id ) {
this.ffzUnloadHost();
this._ffz_host = target_id;
this._ffz_host_room = Room.findOne(target_id);
updated = true;
}
} else if ( this._ffz_host ) {
this.ffzUnloadHost();
updated = true;
}
if ( updated ) {
this.ffzRebuildMenu();
this.ffzRebuildTabs();
}
},
// Unread Handling
ffzUpdateMenuUnread: function() {
var el = this.get('element'),
controller = this.get('controller'),
unread_display = el && el.querySelector('#ffz-group-tabs .button .notifications');
Ember.propertyDidChange(controller, 'notificationsCount');
if ( unread_display )
unread_display.innerHTML = utils.format_unread(controller.get('notificationsCount'));
},
ffzUpdateUnread: function(target_id) {
var current_id = this.get('controller.currentRoom.id');
this.ffz_unread = this.ffz_unread || {};
if ( target_id === current_id )
// We don't care about updates to the current room.
return;
var to_update,
update_unread = false,
update_height = false;
// If we DO have a room ID, only update that room.
if ( target_id )
to_update = [target_id];
else
to_update = Object.keys(f.rooms);
for(var i=0; i < to_update.length; i++) {
var room_id = to_update[i],
room = f.rooms[room_id] && f.rooms[room_id].room,
row = room && room._ffz_row,
tab = room && room._ffz_tab,
unread_count = room_id === current_id ? 0 : room.get('unreadCount'),
is_unread = unread_count > 0,
unread = utils.format_unread(unread_count);
if ( this.ffz_unread[room_id] !== is_unread ) {
this.ffz_unread[room_id] = is_unread;
update_unread = true;
}
if ( row ) {
var sp = row.querySelector('span');
if ( sp )
sp.innerHTML = unread;
}
if ( tab ) {
var was_hidden = tab.classList.contains('hidden'),
is_hidden = ! this.ffzTabVisible(room_id),
sp = tab.querySelector('span');
if ( was_hidden !== is_hidden ) {
tab.classList.toggle('hidden', is_hidden);
update_height = true;
}
if ( sp )
sp.innerHTML = unread;
}
}
if ( update_height )
this.$('.chat-room').css('top', this._ffz_tabs.offsetHeight + "px");
if ( update_unread )
this.ffzUpdateMenuUnread();
},
// Menu Rendering
ffzTeardownMenu: function() {
var el = this.get('element'),
room_list = el && el.querySelector('.chat-rooms .tse-content'),
chan_table = room_list && room_list.querySelector('#ffz-channel-table'),
group_table = room_list && room_list.querySelector('#ffz-group-table');
if ( chan_table )
chan_table.parentElement.removeChild(chan_table);
if ( group_table )
group_table.parentElement.removeChild(group_table);
this._ffz_chan_table = null;
this._ffz_group_table = null;
if ( room_list && room_list.classList.contains('ffz-room-list') ) {
room_list.classList.remove('ffz-room-list');
jQuery('.ffz', room_list).removeClass('ffz');
}
for(var room_id in f.rooms)
if ( f.rooms[room_id] && f.rooms[room_id].room && f.rooms[room_id].room._ffz_row )
f.rooms[room_id].room._ffz_row = null;
},
ffzRebuildMenu: function() {
var el = this.get('element'),
room_list = el && el.querySelector('.chat-rooms .tse-content');
if ( ! room_list )
return;
if ( ! room_list.classList.contains('ffz-room-list') ) {
room_list.classList.add('ffz-room-list');
// Find the Pending Invitations
var headers = room_list.querySelectorAll('.list-header'),
hdr = headers.length ? headers[headers.length-1] : undefined;
if ( hdr ) {
hdr.classList.add('ffz');
if ( hdr.nextSibling && hdr.nextSibling.classList )
hdr.nextSibling.classList.add('ffz');
}
}
// Channel Table
var view = this,
chan_table = this._ffz_chan_table || room_list.querySelector('#ffz-channel-table tbody');
if ( ! chan_table ) {
var tbl = document.createElement('table');
tbl.setAttribute('cellspacing', '0');
tbl.id = 'ffz-channel-table';
tbl.className = 'ffz';
tbl.innerHTML = 'Channels Pin