1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
FrankerFaceZ/src/ember/moderation-card.js
SirStendec 630d2830ec The "Oh God Why Didn't I Commit Sooner" Edition
v3.5.494.

Added: Chat Filtering > Remove Messages from Moderators
Added: Recent Highlights
Added: Support for room-specific badges from the API.

Fixed: Custom bits badges. Lots of styling. Chat bugs. Dark theme issues. My Emoticons menu not rendering. Reset Player not rendering. Twitch5 extension compatibility. Logviewer messages and Name History. Bits UI breaking when you switch rooms. Player state when you reset the player.

Closes #173
Closes #169
Closes #164
2017-06-11 13:30:37 -04:00

1762 lines
No EOL
52 KiB
JavaScript

var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
constants = require("../constants"),
styles = require("../compiled_styles"),
helpers,
TO_REG = /^\/t(?:imeout)? +([^ ]+)(?: +(\d+)(?: +(.+))?)?$/,
BAN_REG = /^\/b(?:an)? +([^ ]+)(?: +(.+))?$/,
keycodes = {
ESC: 27,
R: 82,
P: 80,
B: 66,
T: 84,
U: 85,
C: 67,
H: 72,
S: 83,
Y: 89,
N: 78
},
MESSAGE = '<svg class="svg-messages" height="16px" version="1.1" viewBox="0 0 18 18" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M1,15V3h16v12H1z M15.354,5.354l-0.707-0.707L9,10.293L3.354,4.646L2.646,5.354L6.293,9l-3.646,3.646l0.707,0.707L7,9.707l1.646,1.646h0.707L11,9.707l3.646,3.646l0.707-0.707L11.707,9L15.354,5.354z" fill-rule="evenodd"></path></svg>',
CHECK = '<svg class="svg-unban" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path fill-rule="evenodd" clip-rule="evenodd" fill="#888888" d="M6.5,12.75L2,8.25l2-2l2.5,2.5l5.5-5.5l2,2L6.5,12.75z"/></svg>';
try {
helpers = window.require && window.require("web-client/helpers/chat/chat-line-helpers");
} catch(err) { }
// ----------------
// Settings
// ----------------
FFZ.settings_info.disable_bttv_mod_cards = {
type: "boolean",
value: false,
require_bttv: 7,
category: "Chat Moderation",
name: "Disable BTTV Mod Cards",
help: "This disables mod cards from BetterTTV, forcing FFZ mod cards to show instead.",
on_update: function(val) {
var CL = utils.ember_resolve('component:chat/chat-line'),
views = CL ? utils.ember_views() : [];
for(var vid in views) {
var view = views[vid];
if ( view instanceof CL && view.buildFromHTML ) {
view.$('.from').replaceWith(view.buildFromHTML());
if ( view.get('msgObject.to') )
view.$('.to').replaceWith(view.buildFromHTML(true));
}
}
}
};
FFZ.basic_settings.enhanced_moderation_cards = {
type: "boolean",
no_bttv: true,
category: "Chat",
name: "Enhanced Moderation Cards",
help: "Improve moderation cards with hotkeys, additional buttons, chat history, and other information to make moderating easier.",
get: function() {
return this.settings.mod_card_hotkeys &&
this.settings.mod_card_info &&
this.settings.mod_card_history;
},
set: function(val) {
this.settings.set('mod_card_hotkeys', val);
this.settings.set('mod_card_info', val);
this.settings.set('mod_card_history', val);
}
};
FFZ.basic_settings.chat_hover_pause = {
type: "boolean",
no_bttv: 6,
category: "Chat",
name: "Pause Chat Scrolling on Mouse Hover",
help: "Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link misclicks.",
get: 'chat_hover_pause',
set: 'chat_hover_pause'
};
FFZ.settings_info.highlight_messages_with_mod_card = {
type: "boolean",
value: false,
no_bttv: true,
category: "Chat Moderation",
name: "Highlight Messages with Mod Card Open",
help: "Highlight a user's messages in chat when their moderation card is open.",
on_update: function(val) {
if ( ! this._mod_card )
return;
if ( val )
utils.update_css(this._chat_style, 'mod-card-highlight', styles['chat-user-bg'].replace(/{user_id}/g, this._mod_card.get('cardInfo.user.id')));
else
utils.update_css(this._chat_style, 'mod-card-highlight');
}
};
FFZ.settings_info.logviewer_test = {
type: "boolean",
value: true,
no_bttv: true,
category: "Chat Moderation",
name: "Logviewer Integration",
help: "Display information from CBenni's Logviewer directly on moderation cards."
}
FFZ.settings_info.chat_mod_icon_visibility = {
type: "select",
options: {
0: "Disabled",
1: "Enabled",
2: "When Ctrl is Held",
3: "When " + constants.META_NAME + " is Held",
4: "When Alt is Held",
5: "When Shift is Held"
},
value: function() {
return this.settings.get_twitch("showModIcons") ? 1 : 0;
},
process_value: utils.process_int(0),
no_bttv: 6,
category: "Chat Moderation",
name: "Display In-Line Mod Icons",
help: "Choose when you should see in-line moderation icons in chat.",
on_update: function(val) {
var settings = utils.ember_settings();
if ( settings )
settings.set('showModIcons', val === 1);
}
}
FFZ.settings_info.chat_hover_pause = {
type: "select",
options: {
0: "Disabled",
1: "On Hover",
2: "When Ctrl is Held",
3: "When " + constants.META_NAME + " is Held",
4: "When Alt is Held",
5: "When Shift is Held",
6: "Ctrl or Hover",
7: constants.META_NAME + " or Hover",
8: "Alt or Hover",
9: "Shift or Hover"
},
value: 0,
process_value: utils.process_int(0, 0, 1),
no_bttv: 6,
category: "Chat Moderation",
name: "Pause Chat Scrolling",
help: "Automatically prevent the chat from scrolling when moving the mouse over it or holding Ctrl to prevent moderation mistakes and link misclicks.",
on_update: function(val) {
if ( ! this._roomv )
return;
this._roomv.ffzDisableFreeze();
// Remove the old warning to make sure the label updates.
var el = this._roomv.get('element'),
warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator');
if ( warning )
warning.parentElement.removeChild(warning);
if ( val )
this._roomv.ffzEnableFreeze();
}
};
FFZ.settings_info.short_commands = {
type: "boolean",
value: true,
no_bttv: 6,
category: "Chat Moderation",
name: "Short Moderation Commands",
help: "Use /t, /b, and /u in chat in place of /timeout, /ban, /unban for quicker moderation, and use /p for 1 second timeouts."
};
FFZ.settings_info.mod_card_hotkeys = {
type: "boolean",
value: false,
no_bttv: true,
category: "Chat Moderation",
name: "Moderation Card Hotkeys",
help: "With a moderation card selected, press B to ban the user, T to time them out for 10 minutes, P to time them out for 1 second, or U to unban them. ESC closes the card."
};
FFZ.settings_info.mod_card_info = {
type: "boolean",
value: true,
no_bttv: true,
category: "Chat Moderation",
name: "Moderation Card Additional Information",
help: "Display a channel's follower count, view count, and account age on moderation cards."
};
FFZ.settings_info.timeout_notices = {
type: "select",
options: {
0: "Disabled",
1: "If I'm a Moderator",
2: "Always"
},
value: 1,
process_value: utils.process_int(1),
no_bttv: 6,
category: "Chat Moderation",
name: "Display Timeout / Ban Notices",
help: "Display notices in chat when a user is timed out or banned. (You always see your own bans.)"
};
FFZ.settings_info.mod_card_history = {
type: "select",
options: {
0: "Disabled",
1: "On Rooms without Logviewer",
2: "Always"
},
value: 0,
process_value: utils.process_int(0, 0, 1),
no_bttv: true,
category: "Chat Moderation",
name: "Moderation Card History",
help: "Display a few of the user's previously sent messages on moderation cards.",
on_update: function(val) {
if ( val === 2 || ! this.rooms )
return;
// Delete all history~!
for(var room_id in this.rooms) {
var room = this.rooms[room_id];
if ( room && (val === 0 || room.has_logs) )
room.user_history = undefined;
}
}
};
FFZ.settings_info.mod_button_context = {
type: "select",
options: {
0: "Disabled",
1: "Show Ban Reasons Only",
2: "Show Chat Rules Only",
3: "Ban Reasons + Chat Rules"
},
value: 3,
process_value: utils.process_int(3),
no_bttv: 6,
category: "Chat Moderation",
name: "Mod Icon Context Menus",
help: "Choose the available options when right-clicking an in-line moderation icon."
};
FFZ.settings_info.mod_card_reasons = {
type: "button",
value: [
"One-Man Spam",
"Posting Bad Links",
"Ban Evasion",
"Threats / Personal Info",
"Hate / Harassment",
"Ignoring Broadcaster / Moderators"
],
category: "Chat Moderation",
no_bttv: 6,
name: "Ban / Timeout Reasons",
help: "Change the available options in the chat ban reasons list shown in moderation cards and when right-clicking an in-line ban or timeout button.",
method: function() {
var f = this,
old_val = this.settings.mod_card_reasons.join("\n"),
input = utils.createElement('textarea');
input.style.marginBottom = "20px";
utils.prompt(
"Moderation Card Ban Reasons",
"Please enter a list of ban reasons to select from. One item per line.",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
var vals = new_val.trim().split(/\s*\n\s*/g),
i = vals.length;
while(i--)
if ( vals[i].length === 0 )
vals.splice(i,1);
f.settings.set('mod_card_reasons', vals);
},
600, input
);
}
};
FFZ.settings_info.mod_buttons = {
type: "button",
// Special Values
// false = Ban/Unban
// integer = Timeout (that amount of time)
value: [['', false, false], ['',600, false]], //, ['', 1, false]],
no_bttv: 6,
category: "Chat Moderation",
name: "Custom In-Line Moderation Icons",
help: "Change out the different in-line moderation icons to use any command quickly.",
method: function() {
var f = this,
old_val = "",
input = utils.createElement('textarea');
input.style.marginBottom = '20px';
input.placeholder = '/ban\n600';
for(var i=0; i < this.settings.mod_buttons.length; i++) {
var pair = this.settings.mod_buttons[i],
prefix = pair[0], cmd = pair[1], had_prefix = pair[2], non_mod = pair[4];
if ( cmd === false )
cmd = "/ban";
else if ( cmd === 600 )
cmd = "/timeout";
else if ( typeof cmd !== "string" )
cmd = '' + cmd;
prefix = had_prefix ? 'name:' + prefix + '=' : '';
old_val += (old_val.length ? '\n' : '') + (non_mod ? 'nonmod:' : '') + prefix + cmd;
}
utils.prompt(
"Custom In-Line Moderation Icons",
"Please enter a list of commands to be displayed as moderation buttons within chat lines. " +
"One item per line. As a shortcut for specific duration timeouts, you can enter the number of seconds by itself. " +
" To send multiple commands, separate them with <code>&lt;LINE&gt;</code>. " +
"Variables, such as the target user's name, can be inserted into your commands. If no variables are detected " +
"in a line, <code>{user}</code> will be added to the end of the first command.<hr>" +
"To set a custom label for the button, start your line with <code>name:</code> followed by the " +
"name of the button. End the name with an equals sign. Only the first character will be displayed.<br>" +
"<strong>Example:</strong> <code>name:B=/ban {user}</code><hr>" +
"To create a button that will be visible even if you don't have moderator privileges over a user, " +
"start your line with <code>nonmod:</code><br>" +
"<strong>Example:</strong> <code>nonmod:/w some_bot !info {user}</code><hr>" +
"<strong>Allowed Variables</strong><br><table><tbody>" +
"<tr><td><code>{user}</code></td><td>target user's name</td>" +
"<td><code>{user_name}</code></td><td>target user's name</td></tr>" +
"<tr><td><code>{user_display_name}</code></td><td>target user's display name</td>" +
"<td><code>{user_id}</code></td><td>target user's numeric ID</td></tr>" +
"<tr><td><code>{room}</code></td><td>chat room's name</td>" +
"<td><code>{room_name}</code></td><td>chat room's name</td></tr>" +
"<tr><td><code>{room_display_name}</code></td><td>chat room's display name</td>" +
"<td><code>{room_id}</code></td><td>chat room's numeric ID</td></tr>" +
"<tr><td><code>{id}</code></td><td>message's UUID</td></tr>" +
"</tbody></table>",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
var vals = new_val.trim().split(/\s*\n\s*/g),
output = [];
for(var i=0; i < vals.length; i++) {
var cmd = vals[i],
prefix,
is_emoji = false,
non_mod = /^nonmod:/.test(cmd);
if ( ! cmd || ! cmd.length )
continue;
if ( non_mod )
cmd = cmd.substr(7).trim();
var name_match = /^name:([^=]+)=/.exec(cmd);
if ( name_match ) {
label = name_match[1];
if ( window.punycode && punycode.ucs2 )
label = punycode.ucs2.encode([punycode.ucs2.decode(label)[0]]);
// Check for an emoji
var tokens = f.tokenize_emoji(label);
if ( tokens && tokens[0] && tokens[0].ffzEmoji )
is_emoji = tokens[0].ffzEmoji;
cmd = cmd.substr(name_match[0].length).trim();
if ( ! non_mod ) {
non_mod = /^nonmod:/.test(cmd);
if ( non_mod )
cmd = cmd.substr(7).trim();
}
} else
label = undefined;
// Check for a plain ban.
if ( /^\/b(?:an)?(?:\s+{user(?:_name)?})?\s*$/.test(cmd) )
cmd = false;
// Numeric Timeout
else if ( /^\d+$/.test(cmd) )
cmd = parseInt(cmd);
// Command Timeout
else if ( /^\/t(?:imeout)?(?:\s+{user(?:_name)?}(?:\s+(\d+))?)?\s*$/.test(cmd) ) {
cmd = parseInt(/^\/t(?:imeout)?(?:\s+{user(?:_name)?}(?:\s+(\d+))?)?\s*$/.exec(cmd)[1]);
if ( isNaN(cmd) || ! isFinite(cmd) )
cmd = 600;
}
// Okay. Do we still need a prefix?
if ( label === undefined ) {
var tmp;
if ( typeof cmd === "string" )
tmp = /\w/.exec(cmd);
else
tmp = utils.duration_string(cmd);
label = tmp && tmp.length ? tmp[0].toUpperCase() : 'C';
}
// Add {user} to the first command if it's a custom command and missing.
if ( typeof cmd === "string" ) {
utils.CMD_VAR_REGEX.lastIndex = 0;
if ( ! utils.CMD_VAR_REGEX.test(cmd) ) {
var lines = cmd.split(/\s*<LINE>\s*/g);
lines[0] += ' {user}';
cmd = lines.join("<LINE>");
}
}
output.push([label, cmd, name_match != null, is_emoji, non_mod]);
}
f.settings.set('mod_buttons', output);
// Update existing chat lines.
var CL = utils.ember_resolve('component:chat/chat-line'),
views = CL ? utils.ember_views() : [];
for(var vid in views) {
var view = views[vid];
if ( view instanceof CL && view.buildModIconsHTML )
view.$('.mod-icons').replaceWith(view.buildModIconsHTML());
}
}, 600, input);
}
};
FFZ.settings_info.mod_card_buttons = {
type: "button",
value: [],
category: "Chat Moderation",
no_bttv: true,
name: "Moderation Card Additional Buttons",
help: "Add additional buttons to moderation cards for running chat commands on those users.",
method: function() {
var f = this,
old_val = "",
input = utils.createElement('textarea');
input.style.marginBottom = '20px';
for(var i=0; i < this.settings.mod_card_buttons.length; i++) {
var label, cmd, had_label, pair = this.settings.mod_card_buttons[i];
if ( Array.isArray(pair) ) {
label = pair[0];
cmd = pair[1];
had_label = pair[2];
} else {
cmd = pair;
had_label = false;
}
label = had_label ? 'name:' + label + '=' : '';
old_val += (old_val.length ? '\n' : '') + label + cmd;
}
utils.prompt(
"Moderation Card Additional Buttons",
"Please enter a list of additional commands to display buttons for on moderation cards. " +
"One item per line. To send multiple commands, separate them with <code>&lt;LINE&gt;</code>. " +
"Variables, such as the target user's name, can be inserted into your commands. If no variables are detected " +
"in a line, <code>{user}</code> will be added to the end of the first command.<hr>" +
"To set a custom label for the button, start your line with <code>name:</code> followed by the name of the button. " +
"End the name with an equals sign.<br>" +
"<strong>Example:</strong> <code>name:Boop=/timeout {user} 15 Boop!</code><hr>" +
"<strong>Allowed Variables</strong><br><table><tbody>" +
"<tr><td><code>{user}</code></td><td>target user's name</td>" +
"<td><code>{user_name}</code></td><td>target user's name</td></tr>" +
"<tr><td><code>{user_display_name}</code></td><td>target user's display name</td>" +
"<td><code>{user_id}</code></td><td>target user's numeric ID</td></tr>" +
"<tr><td><code>{room}</code></td><td>chat room's name</td>" +
"<td><code>{room_name}</code></td><td>chat room's name</td></tr>" +
"<tr><td><code>{room_display_name}</code></td><td>chat room's display name</td>" +
"<td><code>{room_id}</code></td><td>chat room's numeric ID</td></tr>" +
"</tbody></table>",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
var vals = new_val.trim().split(/\s*\n\s*/g),
output = [];
for(var i=0; i < vals.length; i++) {
var cmd = vals[i],
label,
name_match = /^name:([^=]+)=/.exec(cmd);
if ( ! cmd || ! cmd.length )
continue;
if ( name_match ) {
label = name_match[1];
cmd = cmd.substr(name_match[0].length);
} else
label = cmd.split(' ', 1)[0]
output.push([label, cmd, name_match != null]);
}
f.settings.set("mod_card_buttons", output);
}, 600, input);
}
};
FFZ.settings_info.mod_card_durations = {
type: "button",
value: [300, 600, 3600, 43200, 86400, 604800],
category: "Chat Moderation",
no_bttv: true,
name: "Moderation Card Timeout Buttons",
help: "Add additional timeout buttons to moderation cards with specific durations.",
method: function() {
var f = this,
old_val = this.settings.mod_card_durations.join(", ");
utils.prompt(
"Moderation Card Timeout Buttons",
"Please enter a comma-separated list of durations that you would like to have timeout buttons for. " +
"Durations must be expressed in seconds.</p><p><b>Default:</b> 300, 600, 3600, 43200, 86400, 604800",
old_val,
function(new_val) {
if ( new_val === null || new_val === undefined )
return;
if ( new_val === "reset" )
new_val = FFZ.settings_info.mod_card_durations.value.join(", ");
// Split them up.
new_val = new_val.trim().split(/[ ,]+/);
var vals = [];
for(var i=0; i < new_val.length; i++) {
var val = parseInt(new_val[i]);
if ( val === 0 )
val = 1;
if ( ! Number.isNaN(val) && val > 0 )
vals.push(val);
}
f.settings.set("mod_card_durations", vals);
}, 600);
}
};
// ----------------
// Initialization
// ----------------
FFZ.prototype.setup_mod_card = function() {
try {
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_settings();
if ( Settings )
Settings.addObserver('showModIcons', function() {
if ( Settings.get('showModIcons') )
f.settings.set('chat_mod_icon_visibility', 1);
});
this.log("Modifying Mousetrap stopCallback so we can catch ESC.");
var orig_stop = Mousetrap.stopCallback;
Mousetrap.stopCallback = function(e, element, combo) {
if ( element.classList.contains('no-mousetrap') )
return true;
return orig_stop(e, element, combo);
}
Mousetrap.bind("up up down down left right left right b a", function() {
var el = document.querySelector(".app-main") || document.querySelector(".ember-chat-container");
el && el.classList.toggle('ffz-flip');
});
this.log("Hooking the Ember Moderation Card view.");
this.update_views('component:chat/moderation-card', this.modify_moderation_card);
}
FFZ.prototype.modify_moderation_card = function(component) {
var f = this;
utils.ember_reopen_view(component, {
ffzForceRedraw: function() {
this.rerender();
var el = this.get('element');
this.ffzChangePage(null);
var chat = utils.ember_lookup('controller:chat'),
room_id = chat && chat.get('currentRoom.id'),
user_id = this.get('cardInfo.user.id');
if (( this._lv_sock_room && this._lv_sock_room !== room_id ) || (this._lv_sock_user && this._lv_sock_user !== user_id) ) {
f.lv_ws_unsub('logs-' + this._lv_sock_room + '-' + this._lv_sock_user);
this._lv_sock_room = null;
this._lv_sock_user = null;
}
if ( f.settings.mod_card_history )
this.ffzRenderHistory();
// Highlight this user's chat messages.
if ( f.settings.highlight_messages_with_mod_card )
utils.update_css(f._chat_style, 'mod-card-highlight', styles['chat-user-bg'].replace(/{user_id}/g, this.get('cardInfo.user.id')));
}.observes("cardInfo.isModeratorOrHigher", "cardInfo.user.id"),
ffzRebuildInfo: function() {
var el = this.get('element'),
info = el && el.querySelector('.info');
if ( ! info )
return;
var out = '<span class="stat html-tooltip" title="Total Views">' + constants.EYE + ' ' + utils.number_commas(this.get('cardInfo.user.views') || 0) + '</span>',
since = utils.parse_date(this.get('cardInfo.user.created_at') || ''),
followers = this.get('cardInfo.user.ffz_followers');
if ( typeof followers === "number" ) {
out += '<span class="stat html-tooltip" title="Followers">' + constants.HEART + ' ' + utils.number_commas(followers || 0) + '</span>';
} else if ( followers === undefined ) {
var t = this;
this.set('cardInfo.user.ffz_followers', false);
utils.api.get("channels/" + this.get('cardInfo.user.id') + '/follows', {limit:1}).done(function(data) {
t.set('cardInfo.user.ffz_followers', data._total);
t.ffzRebuildInfo();
}).fail(function(data) {
t.set('cardInfo.user.ffz_followers', undefined);
});
}
if ( since ) {
var now = Date.now() - (f._ws_server_offset || 0),
age = Math.floor((now - since.getTime()) / 1000);
if ( age > 0 ) {
out += '<span class="stat html-tooltip" title="Member Since: ' + utils.quote_san(age > 86400 ? since.toLocaleDateString() : since.toLocaleString()) + '">' + constants.CLOCK + ' ' + utils.human_time(age, 10) + '</span>';
}
}
info.innerHTML = out;
}.observes("cardInfo.user.views"),
lvGetLogs: function() {
var t = this,
logs = this._lv_logs,
chat = utils.ember_lookup('controller:chat'),
room_id = chat && chat.get('currentRoom.id'),
user_id = this.get('cardInfo.user.id');
return new Promise(function(succeed, fail) {
// Don't expire data if we're connected to the websocket.
if ( logs && (f._lv_ws_open || logs.expires > Date.now()) && logs.room === room_id && logs.user === user_id )
return succeed(logs.data);
if ( t._lv_log_requests )
return t._lv_log_requests.push(succeed);
t._lv_log_requests = [succeed];
f.lv_get_logs(room_id, user_id).then(function(data) {
var new_room_id = chat && chat.get('currentRoom.id'),
new_user_id = t.get('cardInfo.user.id');
if ( user_id !== new_user_id || room_id !== new_room_id )
return;
t._lv_logs = {
expires: Date.now() + 30000,
room: room_id,
user: user_id,
data: data
};
t._lv_sock_room = room_id;
t._lv_sock_user = user_id;
f.lv_ws_sub('logs-' + room_id + '-' + user_id);
var requests = t._lv_log_requests;
t._lv_log_requests = null;
for(var i=0; i < requests.length; i++)
requests[i](data);
});
});
},
lvOnMessage: function(cmd, data) {
//f.log("[LV] Socket Message: " + cmd, data)
if ( cmd === "comment-add" ) {
if ( data.topic !== this.get('cardInfo.user.id') )
return;
FFZ.mod_card_pages.notes.add_note.call(f, this, this.get('element'), data);
} else if ( cmd === "comment-update" ) {
var el = this.get('element'),
line = el && el.querySelector('.user-notes .chat-line[data-lv-id="' + data.id + '"]');
if ( ! line )
return;
var new_line = FFZ.mod_card_pages.notes.build_note.call(f, this, data);
line.outerHTML = new_line.outerHTML;
} else if ( cmd === "comment-delete" ) {
var el = this.get('element'),
line = el && el.querySelector('.user-notes .chat-line[data-lv-id="' + data.id + '"]');
if ( ! line )
return;
// If we're the only message on this date, remove the timestamp line.
var before_line = line.previousElementSibling,
after_line = line.nextElementSibling;
if ( before_line && before_line.classList.contains('timestamp-line') &&
(! after_line || after_line.classList.contains('timestamp-line')) )
before_line.parentElement.removeChild(before_line);
// Remove the line itself.
line.parentElement.removeChild(line);
} else if ( cmd === "log-update" ) {
if ( ! this._lv_logs || ! this._lv_logs.data || data.nick !== this._lv_logs.data.user.nick )
return;
// Parse the message. Store the data.
var message = f.lv_parse_message(data),
msgs = this._lv_logs.data.before,
ind = -1,
i = msgs.length;
// Find the existing entry.
while(--i) {
var msg = msgs[i];
if ( msg.lv_id === message.lv_id ) {
ind = i;
break;
}
}
// Nothing to update, so don't.
if ( ind === -1 )
return;
msgs[ind] = message;
var el = this.get('element'),
container = el && el.querySelector('.ffz-tab-container'),
line = container && container.querySelector('.lv-history .chat-line[data-lv-id="' + message.lv_id + '"]');
if ( ! line )
return;
var new_line = f._build_mod_card_history(message, this, false,
FFZ.mod_card_pages.history.render_adjacent.bind(f, this, container, message));
line.parentElement.insertBefore(new_line, line);
line.parentElement.removeChild(line);
} else if ( cmd === "log-add" ) {
if ( ! this._lv_logs || ! this._lv_logs.data || data.nick !== this._lv_logs.data.user.nick )
return;
// Parse the message. Store the data.
var message = f.lv_parse_message(data);
this._lv_logs.data.before.push(message);
if ( message.is_ban )
this._lv_logs.data.user.timeouts++;
else if ( ! message.is_admin && (! message.is_notice || message.message.indexOf('Message: ') !== -1) )
this._lv_logs.data.user.messages++;
// If we're viewing the chat history, update it.
var el = this.get('element'),
container = el && el.querySelector('.ffz-tab-container'),
history = container && container.querySelector('.ffz-tab-container[data-page="history"] .chat-history.lv-history');
if ( history ) {
var was_at_bottom = history.scrollTop >= (history.scrollHeight - history.clientHeight),
last_line = history.querySelector('.chat-line:last-of-type'),
ll_date = last_line && last_line.getAttribute('data-date'),
date = message.date && message.date.toLocaleDateString();
if ( last_line.classList.contains('no-messages') ) {
last_line.parentElement.removeChild(last_line);
last_line = null;
ll_date = null;
}
if ( last_line && ll_date !== date ) {
var date_line = utils.createElement('li', 'chat-line timestamp-line', date);
date_line.setAttribute('data-date', date);
history.appendChild(date_line);
}
history.appendChild(f._build_mod_card_history(message, this, false,
FFZ.mod_card_pages.history.render_adjacent.bind(f, this, container, message)));
if ( was_at_bottom )
setTimeout(function() { history.scrollTop = history.scrollHeight; })
}
}
},
lvUpdateLevels: function(levels) {
var channel = levels.channel,
user = levels.me,
level = user && user.level || 0;
this.lv_view = level >= channel.viewlogs;
this.lv_view_mod = level >= channel.viewmodlogs;
this.lv_view_notes = level >= channel.viewcomments;
this.lv_write_notes = level >= channel.writecomments;
this.lv_delete_notes = level >= channel.deletecomments;
var el = this.get('element');
if ( el ) {
el.classList.toggle('lv-notes', this.lv_view_notes);
el.classList.toggle('lv-logs', this.lv_view);
el.classList.toggle('lv-tabs', this.lv_view || this.lv_view_notes);
}
},
ffz_destroy: function() {
if ( f._mod_card === this )
f._mod_card = undefined;
if ( this._lv_sock_room && this._lv_sock_user ) {
f.lv_ws_unsub('logs-' + this._lv_sock_room + '-' + this._lv_sock_user);
this._lv_sock_room = null;
this._lv_sock_user = null;
}
if ( this._lv_callback ) {
f.lv_ws_remove_callback(this._lv_callback);
this._lv_callback = null;
}
utils.update_css(f._chat_style, 'mod-card-highlight');
},
ffz_init: function() {
if ( f.has_bttv_6 )
return;
f._mod_card = this;
if ( f.settings.logviewer_test ) {
this._lv_callback = this.lvOnMessage.bind(this);
f.lv_ws_add_callback(this._lv_callback);
}
var el = this.get('element'),
t = this,
line,
is_mod = t.get('cardInfo.isModeratorOrHigher'),
ban_reasons,
chat = utils.ember_lookup('controller:chat'),
user = f.get_user(),
room = chat && chat.get('currentRoom'),
room_id = room && room.get('id'),
ffz_room = f.rooms && f.rooms[room_id] || {},
is_broadcaster = user && room_id === user.login,
user_id = this.get('cardInfo.user.id'),
alias = f.aliases[user_id],
handle_key,
ban_reason = function() {
return ban_reasons && ban_reasons.value ? ' ' + ban_reasons.value : "";
};
this.ffz_room_id = room_id;
// Should we be requesting a token? How about access levels?
if ( f.settings.logviewer_test && ffz_room.has_logs )
if ( ! ffz_room.logviewer_levels || (user && user.login && ! ffz_room.logviewer_levels.me.valid ) )
f.lv_get_token().then(function(token) {
if ( ! token )
return;
utils.logviewer.get("channel/" + room_id, token)
.then(utils.json).then(function(result) {
f.log("[LV] Channel Info: " + room_id, result);
ffz_room.logviewer_levels = result;
t.lvUpdateLevels(result);
});
})
else
t.lvUpdateLevels(ffz_room.logviewer_levels);
// 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);
});
this.set('timeoutAction', function(e) {
var room = utils.ember_lookup('controller:chat').get('currentRoom');
room.send("/timeout " + e.user + " 600 " + ban_reason(), true);
});
// Move the default buttons.
var def_actions = el.querySelector('.moderation-card__actions');
if ( def_actions ) {
var def_line = def_actions.querySelector('.clearfix'),
bad_line = def_actions.querySelector('.moderation-card__controls');
if ( def_line && bad_line ) {
var children = bad_line.querySelectorAll('button');
for(var i=0; i < children.length; i++) {
bad_line.removeChild(children[i]);
def_line.appendChild(children[i]);
}
bad_line.classList.add('hidden');
}
}
// Alias Display
if ( alias ) {
var name = el.querySelector('.moderation-card__name a');
if ( name ) {
name.classList.add('ffz-alias');
var results = f.format_display_name(this.get('cardInfo.user.display_name'), user_id);
name.innerHTML = results[0];
name.title = results[1] || '';
if ( results[1] )
jQuery(name).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
}
// Style it!
el.classList.add('ffz-moderation-card');
// Info-tize it!
if ( f.settings.mod_card_info ) {
var info = utils.createElement('div', 'info channel-stats'),
after = el.querySelector('.moderation-card__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 = utils.createElement('div', 'extra-interface moderation-card__actions clearfix');
var build_cmd = function(user, room, cmd) {
var lines = utils.replace_cmd_variables(cmd, user, room).split(/\s*<LINE>\s*/g),
reason = ban_reason();
if ( reason ) {
for(var i=0; i < lines.length; i++) {
var match = TO_REG.exec(lines[i]);
if ( match ) {
if ( ! match[2] )
lines[i] += ' 600';
if ( ! match[3] )
lines[i] += reason;
break;
} else {
match = BAN_REG.exec(lines[i]);
if ( match ) {
if ( ! match[2] )
lines[i] += reason;
break;
}
}
}
}
return lines;
},
add_btn_click = function(cmd) {
var user = t.get('cardInfo.user'),
chat_controller = utils.ember_lookup('controller:chat'),
room = chat_controller && chat_controller.get('currentRoom');
if ( ! room )
return;
var lines = build_cmd(user, room, cmd);
for(var i=0; i < lines.length; i++)
room.send(lines[i], true);
},
add_btn_make = function(label, cmd) {
var btn = utils.createElement('button', 'button ffz-no-bg', utils.sanitize(label));
jQuery(btn).tipsy({
html: true,
gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n'),
title: function() {
var user = t.get('cardInfo.user'),
chat_controller = utils.ember_lookup('controller:chat'),
room = chat_controller && chat_controller.get('currentRoom');
lines = build_cmd(user, room, cmd),
title = _.map(lines, utils.sanitize).join('<br>');
return "Custom Command" + (lines.length > 1 ? 's' : '') +
"<br>" + title;
}
});
btn.addEventListener('click', add_btn_click.bind(this, cmd));
return btn;
};
for(var i=0; i < f.settings.mod_card_buttons.length; i++) {
var label, cmd, pair = f.settings.mod_card_buttons[i];
if ( ! Array.isArray(pair) ) {
cmd = pair;
label = cmd.split(' ', 1)[0];
} else {
label = pair[0];
cmd = pair[1];
}
utils.CMD_VAR_REGEX.lastIndex = 0;
if ( ! utils.CMD_VAR_REGEX.test(cmd) ) {
var lines = cmd.split(/\s*<LINE>\s*/g);
lines[0] += ' {user}';
cmd = lines.join("<LINE>");
}
line.appendChild(add_btn_make(label, 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,
is_meta = e.ctrlKey || e.altKey || e.metaKey,
tag = e.target && e.target.tagName,
user_id = t.get('cardInfo.user.id'),
is_mod = t.get('cardInfo.isModeratorOrHigher'),
room = utils.ember_lookup('controller:chat').get('currentRoom');
// We don't want modifier keys. Also don't override input to input elements.
if ( is_meta || tag === 'TEXTAREA' || tag === 'INPUT')
return;
if ( key === keycodes.C )
return t.ffzChangePage('default');
else if ( t.lv_view && key === keycodes.H )
return t.ffzChangePage('history');
else if ( key === keycodes.S )
return t.ffzChangePage('stats');
else if ( key === keycodes.Y )
return t.ffzChangePage('name_history');
else if ( t.lv_view_notes && key === keycodes.N )
return t.ffzChangePage('notes');
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 = t.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 moderation-card__actions 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 moderation-card__actions clearfix');
ban_reasons = utils.createElement('select', 'ffz-ban-reasons', '<option value="">Select a Ban ' + (f.settings.mod_card_hotkeys ? '(R)' : 'R') + 'eason</option>');
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 = utils.createElement('button', 'unban button button--icon-only light');
unban_btn.innerHTML = '<figure class="icon">' + CHECK + '</figure>';
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(".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("button.message-button");
if ( msg_btn ) {
msg_btn.innerHTML = 'W';
msg_btn.classList.remove('button--hollow');
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 float-left button--icon-only message html-tooltip');
real_msg.innerHTML = '<figure class="icon">' + MESSAGE + '</figure>';
real_msg.title = "Message User";
real_msg.addEventListener('click', function() {
window.open('//www.twitch.tv/message/compose?to=' + t.get('cardInfo.user.id'));
})
msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling);
}
// Alias Button
var alias_btn = utils.createElement('button', 'alias button float-left button--icon-only html-tooltip');
alias_btn.innerHTML = '<figure class="icon">' + constants.EDIT + '</figure>';
alias_btn.title = "Set Alias";
alias_btn.addEventListener('click', function() {
var user = t.get('cardInfo.user.id'),
alias = f.aliases[user],
results = f.format_display_name(t.get('cardInfo.user.display_name'), user, true);
utils.prompt(
"Alias for <b" + (results[1] ? ' class="html-tooltip" title="' + utils.quote_attr(results[1]) + '">' : '>') + results[0] + "</b>",
"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);
var name = el.querySelector('h4.name');
if ( name ) {
name.classList.toggle('ffz-alias', new_val);
var results = f.format_display_name(t.get('cardInfo.user.display_name'), user_id);
name.innerHTML = results[0];
name.title = results[1] || '';
if ( results[1] )
jQuery(name).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
});
});
if ( msg_btn )
msg_btn.parentElement.insertBefore(alias_btn, msg_btn);
else {
var follow_btn = el.querySelector(".friend-button");
if ( follow_btn )
follow_btn.parentElement.insertBefore(alias_btn, follow_btn.nextSibling);
}
// Tabbed Content
var tabs = utils.createElement('ul', 'moderation-card__actions menu clearfix'),
tab_container = utils.createElement('div', 'ffz-tab-container');
for(var page_id in FFZ.mod_card_pages) {
var page = FFZ.mod_card_pages[page_id];
if ( page && page.title ) {
var tab = utils.createElement('li', 'item', page.title);
if ( page_id === 'default' )
tab.classList.add('active');
if ( page.needs_lv )
tab.classList.add('needs-lv');
tab.setAttribute('data-page', page_id);
tabs.appendChild(tab);
tab.addEventListener('click', function(e) {
t.ffzChangePage(this.getAttribute('data-page'), tabs, tab_container);
});
}
}
el.insertBefore(tab_container, el.querySelector('.moderation-card__actions'));
el.insertBefore(tabs, tab_container);
el.classList.add('ffz-default-tab');
// 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();
},
ffzChangePage: function(page_id, tabs, tab_container) {
if ( ! tabs || ! tab_container ) {
var el = this.get('element');
tabs = el.querySelector('ul.menu');
tab_container = el.querySelector('.ffz-tab-container');
}
var active_page = tab_container.getAttribute('data-page');
if ( active_page === page_id )
return;
if ( page_id === null )
page_id = active_page;
jQuery('.item', tabs).removeClass('active');
jQuery('.item[data-page="' + page_id + '"]').addClass('active');
this.get('element').classList.toggle('ffz-default-tab', page_id === 'default');
tab_container.setAttribute('data-page', page_id);
tab_container.innerHTML = '';
FFZ.mod_card_pages[page_id].render.call(f, this, tab_container);
},
ffzReposition: function() {
var el = this.get('element'),
el_bound = el.getBoundingClientRect(),
body_bound = document.body.getBoundingClientRect(),
renderBottom = this.get('cardInfo.renderBottom'),
renderRight = this.get('cardInfo.renderRight');
if ( renderRight ) {
var offset = (el_bound.left + el_bound.width) - renderRight;
el.style.left = (el_bound.left - offset) + "px";
}
if ( renderBottom ) {
var offset = el_bound.bottom - renderBottom;
el.style.top = (el_bound.top - offset) + "px";
} else if ( el_bound.bottom > body_bound.bottom ) {
var offset = el_bound.bottom - body_bound.bottom;
if ( el_bound.top - offset > body_bound.top )
el.style.top = (el_bound.top - offset) + "px";
}
}.observes('cardInfo.renderTop', 'cardInfo.renderLeft', 'cardInfo.renderRight', 'cardInfo.renderBottom'),
ffzRenderHistory: function() {
var t = this,
Chat = utils.ember_lookup('controller:chat'),
room_id = Chat && Chat.get('currentRoom.id'),
user_id = this.get('cardInfo.user.id'),
ffz_room = f.rooms && f.rooms[room_id],
chat_history = ffz_room && ffz_room.user_history ? (ffz_room.user_history[user_id] || []) : null,
el = this.get('element'),
history = el.querySelector('.chat-history.live-history');
if ( chat_history === null ) {
if ( history )
jQuery(history).remove();
return;
}
if ( ! history ) {
history = utils.createElement('ul', 'moderation-card__actions chat-history live-history');
el.appendChild(history);
} else
history.innerHTML = '';
for(var i=0; i < chat_history.length; i++)
history.appendChild(f._build_mod_card_history(chat_history[i], t, false));
setTimeout(function(){history.scrollTop = history.scrollHeight});
}
});
}
FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from, ts_click, mod_icons) {
var l_el = utils.createElement('li', 'message-line chat-line clearfix'),
out = [],
f = this,
is_notice = msg.style === 'admin' || msg.style === 'notification',
style = '', colored = '';
if ( helpers && helpers.getTime )
out.push('<span class="timestamp' + (ts_click ? ' ts-action' : '') + '">' + helpers.getTime(msg.date, true) + '</span>');
var alias = this.aliases[msg.from],
results = this.format_display_name(msg.tags && msg.tags['display-name'], msg.from);
if ( mod_icons ) {
out.push('<span class="mod-icons">');
if ( typeof mod_icons === "string" )
out.push(mod_icons);
out.push('</span>');
}
if ( show_from && ! is_notice ) {
// Badges
out.push('<span class="badges">');
out.push(this.render_badges(this.get_line_badges(msg, false)));
out.push('</span>');
// Colors
var raw_color = msg.color,
colors = raw_color && this._handle_color(raw_color),
Layout = utils.ember_lookup('service:layout'),
Settings = utils.ember_settings(),
is_dark = (Layout && Layout.get('isTheatreMode')) || this.settings.get_twitch("darkMode");
// Styling
var style = colors && 'color:' + (is_dark ? colors[1] : colors[0]),
colored = style ? ' has-color' : '';
out.push('<span class="from' +
(alias ? ' ffz-alias' : '') +
(results[1] ? ' html-tooltip' : '') +
(style ? ' has-color' : '') +
'" style="' + style + '"' +
(colors ? ' data-color="' + raw_color + '"' : '') +
(results[1] ? ' title="' + utils.quote_attr(results[1]) + '"' : '') + '>'
+ results[0] + '</span>');
out.push(msg.style !== 'action' ? '<span class="colon">:</span> ' : ' ');
} else if ( ! is_notice )
out.push('<span class="cp-hidden"> ' + results[0] + (msg.style === 'action' ? '' : ':') + ' </span>');
// The message itself.
if ( msg.style !== 'action' ) {
style = '';
colored = '';
}
// Use cached tokens on the off chance we have them, but don't count on them.
var tokens = msg.cachedTokens || this.tokenize_chat_line(msg, true, false, true),
message = '<span class="message' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">' +
(msg.style === 'action' && ! show_from ? '*' + name + ' ' : '') + this.render_tokens(tokens, true, false, msg.tags && msg.tags.bits) + '</span>';
if ( msg.deleted )
out.push('<span class="deleted"><a class="undelete" href="#" data-message="' + utils.quote_attr(message) + '">&lt;message deleted&gt;</a></span>');
else
out.push(message);
// Line attributes and classes.
if ( msg.style )
l_el.className += ' ' + msg.style;
if ( msg.original_sender )
l_el.classList.add('original-sender');
if ( msg.is_original )
l_el.classList.add('original-msg');
if ( msg.ffz_has_mention )
l_el.classList.add('ffz-mentioned');
if ( this.settings.prevent_clear && msg.ffz_deleted )
l_el.classList.add('ffz-deleted');
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-lv-id', msg.lv_id);
l_el.setAttribute('data-date', msg.date && msg.date.toLocaleDateString());
l_el.setAttribute('data-deleted', msg.deleted || false);
l_el.innerHTML = out.join("");
// Interactivity
jQuery('a.undelete', l_el).click(function(e) { this.parentElement.outerHTML = this.getAttribute('data-message'); });
jQuery('.deleted-word', l_el).click(function(e) { jQuery(this).trigger('mouseout'); this.outerHTML = this.getAttribute('data-text'); });
jQuery('a.deleted-link', l_el).click(f._deleted_link_click);
jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) });
if ( modcard ) {
modcard.get('cardInfo.user.id') !== msg.from && jQuery('.from', l_el).click(function(e) {
var el = modcard.get('element');
el && f._roomv && f._roomv.get('room.id') === msg.room && f._roomv.actions.showModOverlay.call(f._roomv, {
sender: msg.from,
top: parseInt(el.style.top),
left: parseInt(el.style.left)
});
});
ts_click && l_el.querySelector('.timestamp').addEventListener('click', function(e) {
if ( e.button === 0 )
return ts_click.call(this, e);
});
}
return l_el;
}
// ----------------
// Aliases
// ----------------
FFZ.prototype._update_alias = function(user) {
var alias = this.aliases && this.aliases[user],
results = this.format_display_name(FFZ.get_capitalization(user), user),
el = this._roomv && this._roomv.get('element'),
lines = el && el.querySelectorAll('.chat-line[data-sender="' + user + '"]');
if ( ! lines )
return;
for(var i=0, l = lines.length; i < l; i++) {
var line = lines[i],
el_from = line.querySelector('.from');
if ( ! el_from )
continue;
el_from.classList.toggle('ffz-alias', alias);
el_from.classList.toggle('html-tooltip', results[1] || false);
el_from.innerHTML = results[0];
el_from.title = results[1] || '';
}
// Update tab completion.
if ( this._inputv )
Ember.propertyDidChange(this._inputv, 'ffz_name_suggestions');
// TODO: Update conversations~
}
// ----------------
// Chat Commands
// ----------------
FFZ.chat_commands.purge = function(room, args) {
if ( ! args || ! args.length )
return "Usage: /purge <user> [ban reason]";
var name = args.shift(),
reason = args.length ? args.join(" ") : "";
room.room.send("/timeout " + name + " 1 " + reason, true);
}
FFZ.chat_commands.purge.label = '/purge &lt;user&gt; <i>[reason]</i>';
FFZ.chat_commands.purge.info = 'Ban User for 1 Second';
FFZ.chat_commands.p = function(room, args) {
return FFZ.chat_commands.purge.call(this, room, args);
}
FFZ.chat_commands.p.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.p.short = true;
FFZ.chat_commands.p.label = '/p &lt;user&gt; <i>[reason]</i>';
FFZ.chat_commands.p.info = 'Ban User for 1 Second';
FFZ.chat_commands.t = function(room, args) {
if ( ! args || ! args.length )
return "Usage: /t <user> [duration=600] [reason]";
room.room.send("/timeout " + args.join(" "), true);
}
FFZ.chat_commands.t.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.t.short = true;
FFZ.chat_commands.t.label = '/t &lt;user&gt; <i>[duration=600] [reason]</i>';
FFZ.chat_commands.t.info = 'Temporarily Ban User';
FFZ.chat_commands.b = function(room, args) {
if ( ! args || ! args.length )
return "Usage: /b <user> [reason]";
var name = args.shift(),
reason = args.length ? args.join(" ") : "";
room.room.send("/ban " + name + " " + reason, true);
}
FFZ.chat_commands.b.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.b.short = true;
FFZ.chat_commands.b.label = '/b &lt;user&gt; <i>[reason]</i>';
FFZ.chat_commands.b.info = 'Permanently Ban User';
FFZ.chat_commands.u = function(room, args) {
if ( ! args || ! args.length )
return "Usage: /u <user> [more usernames separated by spaces]";
if ( args.length > 10 )
return "Please only unban up to 10 users at once.";
for(var i=0; i < args.length; i++) {
var name = args[i];
if ( name )
room.room.send("/unban " + name, true);
}
}
FFZ.chat_commands.u.enabled = function() { return this.settings.short_commands; }
FFZ.chat_commands.u.short = true;
FFZ.chat_commands.u.label = '/u &lt;user&gt; <i>[&lt;user&gt; ...]';
FFZ.chat_commands.u.info = 'Unban User(s)';