mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
3.5.313. Logviewer integration.
This commit is contained in:
parent
d12277776e
commit
695a013f4a
12 changed files with 1222 additions and 94 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
<div class="list-header">3.5.313 <time datetime="2016-10-03">(2016-10-05)</time></div>
|
||||||
|
<ul class="chat-menu-content menu-side-padding">
|
||||||
|
<li>Added: Logviewer Integration (Beta)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="list-header">3.5.312 <time datetime="2016-10-03">(2016-10-04)</time></div>
|
<div class="list-header">3.5.312 <time datetime="2016-10-03">(2016-10-04)</time></div>
|
||||||
<ul class="chat-menu-content menu-side-padding">
|
<ul class="chat-menu-content menu-side-padding">
|
||||||
<li>Fixed: Typo causing chat lines to not render correctly if a message is deleted.</li>
|
<li>Fixed: Typo causing chat lines to not render correctly if a message is deleted.</li>
|
||||||
|
@ -9,11 +14,6 @@
|
||||||
<li>Updated Fix: Message Doubling. Properly check a message's <code>hasSystemMsg</code> property as <code>systemMsg</code> will always contain a string now.</li>
|
<li>Updated Fix: Message Doubling. Properly check a message's <code>hasSystemMsg</code> property as <code>systemMsg</code> will always contain a string now.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="list-header">3.5.310 <time datetime="2016-10-03">(2016-10-04)</time></div>
|
|
||||||
<ul class="chat-menu-content menu-side-padding">
|
|
||||||
<li>Fixed: Twitch broke message handling by deciding <em>every</em> message is a system message. I'll fix it properly later.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="list-header">3.5.309 <time datetime="2016-10-03">(2016-10-03)</time></div>
|
<div class="list-header">3.5.309 <time datetime="2016-10-03">(2016-10-03)</time></div>
|
||||||
<ul class="chat-menu-content menu-side-padding">
|
<ul class="chat-menu-content menu-side-padding">
|
||||||
<li>Fixed: Properly position the Theater Mode metadata bar when whispers are at the bottom of the screen.</li>
|
<li>Fixed: Properly position the Theater Mode metadata bar when whispers are at the bottom of the screen.</li>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
<div class="list-header">Awesome Resources</div>
|
<div class="list-header">Awesome Resources</div>
|
||||||
<ul class="chat-menu-content menu-side-padding">
|
<ul class="chat-menu-content menu-side-padding">
|
||||||
|
<li><a href="https://cbenni.com/" target="_blank">CBenni's logviewer</a> - Chat moderation logs for Twitch</li>
|
||||||
<li><a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a> - Free and Automated SSL for Everyone</li>
|
<li><a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a> - Free and Automated SSL for Everyone</li>
|
||||||
<li><a href="https://github.com/eligrey/FileSaver.js" target="_blank">FileSaver.js</a> - saveAs() implementation</li>
|
<li><a href="https://github.com/eligrey/FileSaver.js" target="_blank">FileSaver.js</a> - saveAs() implementation</li>
|
||||||
<li><a href="http://objectpath.org/" target="_blank">ObjectPath</a> - Lightweight query language for JSON</li>
|
<li><a href="http://objectpath.org/" target="_blank">ObjectPath</a> - Lightweight query language for JSON</li>
|
||||||
|
|
|
@ -385,15 +385,9 @@ FFZ.prototype.get_twitch_badges = function(badge_tag) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VoD Chat lines don't have the badges pre-parsed for some reason.
|
// VoD Chat lines don't have the badges pre-parsed for some reason.
|
||||||
else if ( typeof badge_tag === 'string' ) {
|
else if ( typeof badge_tag === 'string' )
|
||||||
var val = badge_tag.split(',');
|
badge_tag = utils.parse_badge_tag(badge_tag);
|
||||||
badge_tag = {};
|
|
||||||
for(var i=0; i < val.length; i++) {
|
|
||||||
var parts = val[i].split('/');
|
|
||||||
if ( parts.length === 2 )
|
|
||||||
badge_tag[parts[0]] = parts[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(var badge in badge_tag) {
|
for(var badge in badge_tag) {
|
||||||
var version = badge_tag[badge];
|
var version = badge_tag[badge];
|
||||||
|
|
|
@ -18,6 +18,8 @@ module.exports = FrankerFaceZ.constants = {
|
||||||
DEBUG: DEBUG,
|
DEBUG: DEBUG,
|
||||||
SERVER: SERVER,
|
SERVER: SERVER,
|
||||||
|
|
||||||
|
LV_SOCKET_SERVER: "wss://cbenni.com/socket.io/",
|
||||||
|
|
||||||
IS_OSX: IS_OSX,
|
IS_OSX: IS_OSX,
|
||||||
IS_WIN: IS_WIN,
|
IS_WIN: IS_WIN,
|
||||||
META_NAME: IS_OSX ? "⌘" : (IS_WIN ? "Win" : "Meta"),
|
META_NAME: IS_OSX ? "⌘" : (IS_WIN ? "Win" : "Meta"),
|
||||||
|
|
|
@ -887,9 +887,9 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
|
||||||
|
|
||||||
output += body;
|
output += body;
|
||||||
|
|
||||||
var old_messages = this.get('msgObject.ffz_old_messages');
|
/*var old_messages = this.get('msgObject.ffz_old_messages');
|
||||||
if ( old_messages && old_messages.length )
|
if ( old_messages && old_messages.length )
|
||||||
output += '<div class="button primary float-right ffz-old-messages">Show ' + utils.number_commas(old_messages.length) + ' Old</div>';
|
output += '<div class="button primary float-right ffz-old-messages">Show ' + utils.number_commas(old_messages.length) + ' Old</div>';*/
|
||||||
|
|
||||||
return output + '</span>';
|
return output + '</span>';
|
||||||
},
|
},
|
||||||
|
@ -909,8 +909,12 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
|
||||||
el.innerHTML = output;
|
el.innerHTML = output;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
systemMsg: function() {
|
||||||
|
return this.get('msgObject.tags.system-msg')
|
||||||
|
}.property('msgObject.tags.system-msg'),
|
||||||
|
|
||||||
ffzShouldRenderMessageBody: function() {
|
ffzShouldRenderMessageBody: function() {
|
||||||
return !this.get("hasSystemMsg") || this.get("hasMessageBody");
|
return ! this.get('hasSystemMsg') || this.get('hasMessageBody');
|
||||||
}.property('hasSystemMsg', 'hasMessageBody'),
|
}.property('hasSystemMsg', 'hasMessageBody'),
|
||||||
|
|
||||||
hasSystemMsg: function() {
|
hasSystemMsg: function() {
|
||||||
|
@ -1090,10 +1094,10 @@ FFZ.prototype._modify_chat_subline = function(component) {
|
||||||
var cl = e.target.classList,
|
var cl = e.target.classList,
|
||||||
from = this.get("msgObject.from");
|
from = this.get("msgObject.from");
|
||||||
|
|
||||||
if ( cl.contains('ffz-old-messages') )
|
/*if ( cl.contains('ffz-old-messages') )
|
||||||
return f._show_deleted(this.get('msgObject.room'));
|
return f._show_deleted(this.get('msgObject.room'));*/
|
||||||
|
|
||||||
else if ( cl.contains('deleted-word') ) {
|
if ( cl.contains('deleted-word') ) {
|
||||||
jQuery(e.target).trigger('mouseout');
|
jQuery(e.target).trigger('mouseout');
|
||||||
e.target.outerHTML = e.target.getAttribute('data-text');
|
e.target.outerHTML = e.target.getAttribute('data-text');
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,11 @@ var FFZ = window.FrankerFaceZ,
|
||||||
P: 80,
|
P: 80,
|
||||||
B: 66,
|
B: 66,
|
||||||
T: 84,
|
T: 84,
|
||||||
U: 85
|
U: 85,
|
||||||
|
C: 67,
|
||||||
|
H: 72,
|
||||||
|
S: 83,
|
||||||
|
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>',
|
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>',
|
||||||
|
@ -89,6 +93,18 @@ FFZ.settings_info.highlight_messages_with_mod_card = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
FFZ.settings_info.logviewer_test = {
|
||||||
|
type: "boolean",
|
||||||
|
value: false,
|
||||||
|
|
||||||
|
no_bttv: true,
|
||||||
|
|
||||||
|
category: "Chat Moderation",
|
||||||
|
name: "Logviewer Integration <small>Beta</small>",
|
||||||
|
help: "Display information from CBenni's logviewer directly on moderation cards."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
FFZ.settings_info.chat_mod_icon_visibility = {
|
FFZ.settings_info.chat_mod_icon_visibility = {
|
||||||
type: "select",
|
type: "select",
|
||||||
options: {
|
options: {
|
||||||
|
@ -630,8 +646,19 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
utils.ember_reopen_view(component, {
|
utils.ember_reopen_view(component, {
|
||||||
ffzForceRedraw: function() {
|
ffzForceRedraw: function() {
|
||||||
this.rerender();
|
this.rerender();
|
||||||
if ( f.settings.mod_card_history )
|
|
||||||
this.ffzRenderHistory();
|
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(this._lv_sock_room + '-' + this._lv_sock_user);
|
||||||
|
this._lv_sock_room = null;
|
||||||
|
this._lv_sock_user = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Highlight this user's chat messages.
|
// Highlight this user's chat messages.
|
||||||
if ( f.settings.highlight_messages_with_mod_card )
|
if ( f.settings.highlight_messages_with_mod_card )
|
||||||
|
@ -674,10 +701,124 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
info.innerHTML = out;
|
info.innerHTML = out;
|
||||||
}.observes("cardInfo.user.views"),
|
}.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(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)
|
||||||
|
// Make sure:
|
||||||
|
// 1) It's a log-add command.
|
||||||
|
// 2) We have logs loaded already.
|
||||||
|
// 3) The loaded logs are for this user.
|
||||||
|
if ( cmd !== 'log-add' || ! this._lv_logs || ! this._lv_logs.data || data.nick !== this._lv_logs.data.user.nick )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Parse the message. Store the data.
|
||||||
|
var t,
|
||||||
|
message = f.lv_parse_message(data);
|
||||||
|
this._lv_logs.data.before.push(message);
|
||||||
|
this._lv_logs.data.user[message.is_ban ? 'timeouts' : 'messages'] += 1;
|
||||||
|
|
||||||
|
// 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('.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.toLocaleDateString();
|
||||||
|
|
||||||
|
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, t, 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');
|
||||||
|
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() {
|
ffz_destroy: function() {
|
||||||
if ( f._mod_card === this )
|
if ( f._mod_card === this )
|
||||||
f._mod_card = undefined;
|
f._mod_card = undefined;
|
||||||
|
|
||||||
|
if ( this._lv_sock_room && this._lv_sock_user ) {
|
||||||
|
f.lv_ws_unsub(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');
|
utils.update_css(f._chat_style, 'mod-card-highlight');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -687,6 +828,11 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
|
|
||||||
f._mod_card = this;
|
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'),
|
var el = this.get('element'),
|
||||||
controller = this.get('controller'),
|
controller = this.get('controller'),
|
||||||
t = this,
|
t = this,
|
||||||
|
@ -699,6 +845,7 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
user = f.get_user(),
|
user = f.get_user(),
|
||||||
room = chat && chat.get('currentRoom'),
|
room = chat && chat.get('currentRoom'),
|
||||||
room_id = room && room.get('id'),
|
room_id = room && room.get('id'),
|
||||||
|
ffz_room = f.rooms && f.rooms[room_id] || {},
|
||||||
is_broadcaster = user && room_id === user.login,
|
is_broadcaster = user && room_id === user.login,
|
||||||
|
|
||||||
user_id = controller.get('cardInfo.user.id'),
|
user_id = controller.get('cardInfo.user.id'),
|
||||||
|
@ -713,6 +860,23 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
|
|
||||||
this.ffz_room_id = room_id;
|
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 )
|
||||||
|
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.
|
// Highlight this user's chat messages.
|
||||||
if ( f.settings.highlight_messages_with_mod_card )
|
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));
|
utils.update_css(f._chat_style, 'mod-card-highlight', styles['chat-user-bg'].replace(/{user_id}/g, user_id));
|
||||||
|
@ -848,6 +1012,18 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
is_mod = controller.get('cardInfo.isModeratorOrHigher'),
|
is_mod = controller.get('cardInfo.isModeratorOrHigher'),
|
||||||
room = utils.ember_lookup('controller:chat').get('currentRoom');
|
room = utils.ember_lookup('controller:chat').get('currentRoom');
|
||||||
|
|
||||||
|
if ( key === keycodes.C )
|
||||||
|
return t.ffzChangePage('default');
|
||||||
|
|
||||||
|
else if ( t.lv_view && key === keycodes.H )
|
||||||
|
return t.ffzChangePage('history');
|
||||||
|
|
||||||
|
else if ( t.lv_view && key === keycodes.S )
|
||||||
|
return t.ffzChangePage('stats');
|
||||||
|
|
||||||
|
else if ( t.lv_view_notes && key === keycodes.N )
|
||||||
|
return t.ffzChangePage('notes');
|
||||||
|
|
||||||
if ( is_mod && key == keycodes.P )
|
if ( is_mod && key == keycodes.P )
|
||||||
room.send("/timeout " + user_id + " 1" + ban_reason(), true);
|
room.send("/timeout " + user_id + " 1" + ban_reason(), true);
|
||||||
|
|
||||||
|
@ -1057,9 +1233,42 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Tabbed Content
|
||||||
|
var tabs = utils.createElement('ul', 'interface 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 && (page_id !== 'history' || f.settings.mod_card_history) ) {
|
||||||
|
var tab = utils.createElement('li', 'item', page.title);
|
||||||
|
if ( page_id === 'default' )
|
||||||
|
tab.classList.add('active');
|
||||||
|
|
||||||
|
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('.interface'));
|
||||||
|
el.insertBefore(tabs, tab_container);
|
||||||
|
|
||||||
|
el.classList.add('ffz-default-tab');
|
||||||
|
|
||||||
// Message History
|
// Message History
|
||||||
if ( f.settings.mod_card_history )
|
if ( f.settings.mod_card_history ) {
|
||||||
this.ffzRenderHistory();
|
var history = utils.createElement('ul', 'interface chat-history live-history');
|
||||||
|
el.appendChild(history);
|
||||||
|
|
||||||
|
var chat_history = ffz_room.user_history && ffz_room.user_history[user_id] || [];
|
||||||
|
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;});
|
||||||
|
}
|
||||||
|
|
||||||
// Reposition the menu if it's off-screen.
|
// Reposition the menu if it's off-screen.
|
||||||
this.ffzReposition();
|
this.ffzReposition();
|
||||||
|
@ -1073,6 +1282,31 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
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() {
|
ffzReposition: function() {
|
||||||
var el = this.get('element'),
|
var el = this.get('element'),
|
||||||
el_bound = el.getBoundingClientRect(),
|
el_bound = el.getBoundingClientRect(),
|
||||||
|
@ -1097,7 +1331,7 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
}
|
}
|
||||||
}.observes('cardInfo.renderTop', 'cardInfo.renderLeft', 'cardInfo.renderRight', 'cardInfo.renderBottom'),
|
}.observes('cardInfo.renderTop', 'cardInfo.renderLeft', 'cardInfo.renderRight', 'cardInfo.renderBottom'),
|
||||||
|
|
||||||
ffzRenderHistory: function() {
|
/*ffzRenderHistory: function() {
|
||||||
var t = this,
|
var t = this,
|
||||||
Chat = utils.ember_lookup('controller:chat'),
|
Chat = utils.ember_lookup('controller:chat'),
|
||||||
room = Chat && Chat.get('currentRoom'),
|
room = Chat && Chat.get('currentRoom'),
|
||||||
|
@ -1109,17 +1343,22 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
user_history = ffz_room && ffz_room.user_history && ffz_room.user_history[user_id] || [],
|
user_history = ffz_room && ffz_room.user_history && ffz_room.user_history[user_id] || [],
|
||||||
el = this.get('element'),
|
el = this.get('element'),
|
||||||
|
|
||||||
history = el && el.querySelector('.chat-history');
|
history = el && el.querySelector('.ffz-tab-container');
|
||||||
|
|
||||||
|
if ( history && ! history.classList.contains('chat-history') ) {
|
||||||
|
history.parentElement.removeChild(history);
|
||||||
|
history = null;
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! history ) {
|
if ( ! history ) {
|
||||||
history = utils.createElement('ul', 'interface clearfix chat-history');
|
history = utils.createElement('ul', 'interface clearfix ffz-tab-container chat-history');
|
||||||
el.appendChild(history);
|
el.appendChild(history);
|
||||||
} else {
|
} else {
|
||||||
history.classList.remove('loading');
|
history.classList.remove('loading');
|
||||||
history.innerHTML = '';
|
history.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( user_history.length < 50 ) {
|
/*if ( user_history.length < 50 ) {
|
||||||
var before = (user_history.length > 0 && user_history[0].date ? user_history[0].date.getTime() : Date.now()) - (f._ws_server_offset || 0);
|
var before = (user_history.length > 0 && user_history[0].date ? user_history[0].date.getTime() : Date.now()) - (f._ws_server_offset || 0);
|
||||||
f.ws_send("user_history", [room_id, user_id, 50 - user_history.length], function(success, data) {
|
f.ws_send("user_history", [room_id, user_id, 50 - user_history.length], function(success, data) {
|
||||||
if ( ! success )
|
if ( ! success )
|
||||||
|
@ -1157,16 +1396,16 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
if ( was_at_top )
|
if ( was_at_top )
|
||||||
setTimeout(function() { history.scrollTop = history.scrollHeight; });
|
setTimeout(function() { history.scrollTop = history.scrollHeight; });
|
||||||
});
|
});
|
||||||
}
|
}*//*
|
||||||
|
|
||||||
for(var i=0; i < user_history.length; i++)
|
for(var i=0; i < user_history.length; i++)
|
||||||
history.appendChild(f._build_mod_card_history(user_history[i], t));
|
history.appendChild(f._build_mod_card_history(user_history[i], t));
|
||||||
|
|
||||||
// Lazy scroll-to-bottom
|
// Lazy scroll-to-bottom
|
||||||
history.scrollTop = history.scrollHeight;
|
history.scrollTop = history.scrollHeight;
|
||||||
},
|
},*/
|
||||||
|
|
||||||
ffzAdjacentHistory: function(line) {
|
/*ffzAdjacentHistory: function(line) {
|
||||||
var Chat = utils.ember_lookup('controller:chat'),
|
var Chat = utils.ember_lookup('controller:chat'),
|
||||||
t = this,
|
t = this,
|
||||||
|
|
||||||
|
@ -1267,12 +1506,12 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
||||||
history.classList.remove('loading');
|
history.classList.remove('loading');
|
||||||
history.scrollTop = scroll_top;
|
history.scrollTop = scroll_top;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
|
FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from, ts_click) {
|
||||||
var l_el = document.createElement('li'),
|
var l_el = document.createElement('li'),
|
||||||
out = [],
|
out = [],
|
||||||
f = this;
|
f = this;
|
||||||
|
@ -1280,7 +1519,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
|
||||||
style = '', colored = '';
|
style = '', colored = '';
|
||||||
|
|
||||||
if ( helpers && helpers.getTime )
|
if ( helpers && helpers.getTime )
|
||||||
out.push('<span class="timestamp">' + helpers.getTime(msg.date) + '</span>');
|
out.push('<span class="timestamp' + (ts_click ? ' ts-action' : '') + '">' + helpers.getTime(msg.date) + '</span>');
|
||||||
|
|
||||||
if ( show_from ) {
|
if ( show_from ) {
|
||||||
var alias = this.aliases[msg.from],
|
var alias = this.aliases[msg.from],
|
||||||
|
@ -1315,7 +1554,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
|
||||||
(results[1] ? ' title="' + utils.quote_attr(results[1]) + '"' : '') + '>'
|
(results[1] ? ' title="' + utils.quote_attr(results[1]) + '"' : '') + '>'
|
||||||
+ results[0] + '</span>');
|
+ results[0] + '</span>');
|
||||||
|
|
||||||
out.push('<span class="colon">:</span> ');
|
out.push(msg.style !== 'action' ? '<span class="colon">:</span> ' : ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1356,6 +1595,8 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
|
||||||
l_el.setAttribute('data-room', msg.room);
|
l_el.setAttribute('data-room', msg.room);
|
||||||
l_el.setAttribute('data-sender', msg.from);
|
l_el.setAttribute('data-sender', msg.from);
|
||||||
l_el.setAttribute('data-id', msg.tags && msg.tags.id);
|
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.toLocaleDateString());
|
||||||
l_el.setAttribute('data-deleted', msg.deleted || false);
|
l_el.setAttribute('data-deleted', msg.deleted || false);
|
||||||
|
|
||||||
l_el.innerHTML = out.join("");
|
l_el.innerHTML = out.join("");
|
||||||
|
@ -1379,9 +1620,9 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
l_el.querySelector('.timestamp').addEventListener('click', function(e) {
|
ts_click && l_el.querySelector('.timestamp').addEventListener('click', function(e) {
|
||||||
if ( e.button === 0 )
|
if ( e.button === 0 )
|
||||||
modcard.ffzAdjacentHistory(msg);
|
return ts_click.call(this, e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1482,4 +1723,329 @@ FFZ.chat_commands.u = function(room, args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FFZ.chat_commands.u.enabled = function() { return this.settings.short_commands; }
|
FFZ.chat_commands.u.enabled = function() { return this.settings.short_commands; }
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// Moderation Card Pages
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
FFZ.mod_card_pages = {};
|
||||||
|
|
||||||
|
FFZ.mod_card_pages.default = {
|
||||||
|
title: "<span>C</span>ontrols",
|
||||||
|
render: function(mod_card, el) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
FFZ.mod_card_pages.history = {
|
||||||
|
title: "Chat <span>H</span>istory",
|
||||||
|
|
||||||
|
render_more: function(mod_card, el, history, ref_id, is_user, is_after) {
|
||||||
|
var f = this,
|
||||||
|
controller = utils.ember_lookup('controller:chat'),
|
||||||
|
user_id = mod_card.get('cardInfo.user.id'),
|
||||||
|
room_id = controller && controller.get('currentRoom.id'),
|
||||||
|
|
||||||
|
btn_more = utils.createElement('li', 'button ffz-load-more' + (is_after ? ' load-after' : ''), '<span class="ffz-chevron"></span> Load More <span class="ffz-chevron"></span>');
|
||||||
|
|
||||||
|
if ( is_after )
|
||||||
|
history.appendChild(btn_more);
|
||||||
|
else
|
||||||
|
history.insertBefore(btn_more, history.firstElementChild);
|
||||||
|
|
||||||
|
btn_more.addEventListener('click', function() {
|
||||||
|
history.scrollTop = 0;
|
||||||
|
history.classList.add('loading');
|
||||||
|
f.lv_get_logs(room_id, is_user ? user_id : null, ref_id, is_after ? 0 : 10, is_after ? 10 : 0).then(function(data) {
|
||||||
|
history.removeChild(btn_more);
|
||||||
|
history.classList.remove('loading');
|
||||||
|
|
||||||
|
var messages = is_after ? data.after : data.before,
|
||||||
|
last_message = history.querySelector('.chat-line:' + (is_after ? 'last' : 'first') + '-of-type'),
|
||||||
|
last_date = last_message ? last_message.getAttribute('data-date') : (new Date).toLocaleDateString();
|
||||||
|
|
||||||
|
if ( last_message.classList.contains('timestamp-line') )
|
||||||
|
last_message.parentElement.removeChild(last_message);
|
||||||
|
|
||||||
|
if ( ! is_after )
|
||||||
|
messages.reverse();
|
||||||
|
|
||||||
|
var original_message = history.querySelector('.original-msg'),
|
||||||
|
original_sender = original_message && original_message.getAttribute('data-sender');
|
||||||
|
|
||||||
|
for(var i=0; i < messages.length; i++) {
|
||||||
|
var new_message = messages[i],
|
||||||
|
date = new_message.date.toLocaleDateString(),
|
||||||
|
date_line = null;
|
||||||
|
|
||||||
|
new_message.original_sender = original_sender === new_message.from;
|
||||||
|
|
||||||
|
var new_line = f._build_mod_card_history(
|
||||||
|
new_message, mod_card, !is_user,
|
||||||
|
FFZ.mod_card_pages.history.render_adjacent.bind(f, mod_card, el, new_message)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_user )
|
||||||
|
new_line.classList.remove('ffz-mentioned');
|
||||||
|
|
||||||
|
new_message.original_sender = null;
|
||||||
|
|
||||||
|
if ( last_date !== date ) {
|
||||||
|
date_line = utils.createElement('li', 'chat-line timestamp-line', is_after ? date : last_date);
|
||||||
|
date_line.setAttribute('data-date', is_after ? date : last_date);
|
||||||
|
last_date = date;
|
||||||
|
if ( is_after )
|
||||||
|
history.appendChild(date_line);
|
||||||
|
else
|
||||||
|
history.insertBefore(date_line, history.firstElementChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_after )
|
||||||
|
history.appendChild(new_line);
|
||||||
|
else
|
||||||
|
history.insertBefore(new_line, history.firstElementChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! is_after && last_date !== (new Date).toLocaleDateString() ) {
|
||||||
|
var date_line = utils.createElement('li', 'chat-line timestamp-line', last_date);
|
||||||
|
date_line.setAttribute('data-date', last_date);
|
||||||
|
history.insertBefore(date_line, history.firstElementChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add the button back if there are even more messages to load.
|
||||||
|
if ( messages.length >= 10 )
|
||||||
|
if ( is_after )
|
||||||
|
history.appendChild(btn_more);
|
||||||
|
else
|
||||||
|
history.insertBefore(btn_more, history.firstElementChild);
|
||||||
|
|
||||||
|
var original = history.querySelector('.chat-line[data-lv-id="' + ref_id + '"]');
|
||||||
|
if ( original )
|
||||||
|
setTimeout(function() {
|
||||||
|
history.scrollTop = (original.offsetTop - history.offsetTop) - (history.clientHeight - original.clientHeight) / 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
ref_id = messages[messages.length-1].lv_id;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
render_adjacent: function(mod_card, el, message) {
|
||||||
|
var f = this,
|
||||||
|
controller = utils.ember_lookup('controller:chat'),
|
||||||
|
user_id = mod_card.get('cardInfo.user.id'),
|
||||||
|
room_id = controller && controller.get('currentRoom.id'),
|
||||||
|
ffz_room = this.rooms[room_id],
|
||||||
|
|
||||||
|
old_history = el.querySelector('.chat-history'),
|
||||||
|
history = el.querySelector('.adjacent-history');
|
||||||
|
|
||||||
|
old_history.classList.add('hidden');
|
||||||
|
|
||||||
|
if ( history )
|
||||||
|
history.innerHTML = '';
|
||||||
|
else {
|
||||||
|
var btn_hide = utils.createElement('li', 'button ffz-back-button', '<span class="ffz-chevron"></span> Back'),
|
||||||
|
btn_container = utils.createElement('ul', 'interface chat-history chat-back-button', btn_hide);
|
||||||
|
|
||||||
|
btn_hide.addEventListener('click', function() {
|
||||||
|
el.removeChild(history);
|
||||||
|
el.removeChild(btn_container);
|
||||||
|
old_history.classList.remove('hidden');
|
||||||
|
})
|
||||||
|
|
||||||
|
history = utils.createElement('ul', 'interface chat-history adjacent-history');
|
||||||
|
el.appendChild(btn_container);
|
||||||
|
el.appendChild(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
history.classList.add('loading');
|
||||||
|
|
||||||
|
f.lv_get_logs(room_id, null, message.lv_id, 10, 10).then(function(data) {
|
||||||
|
history.classList.remove('loading');
|
||||||
|
|
||||||
|
// Should we display more?
|
||||||
|
if ( data.before.length >= 10 )
|
||||||
|
FFZ.mod_card_pages.history.render_more.call(
|
||||||
|
f, mod_card, el, history, data.before[0].lv_id, false, false);
|
||||||
|
|
||||||
|
var last_date = (new Date).toLocaleDateString(),
|
||||||
|
messages = _.union(data.before, [message], data.after);
|
||||||
|
|
||||||
|
for(var i=0; i < messages.length; i++) {
|
||||||
|
var new_message = messages[i],
|
||||||
|
date = new_message.date.toLocaleDateString();
|
||||||
|
|
||||||
|
if ( date !== last_date ) {
|
||||||
|
var date_line = utils.createElement('li', 'chat-line timestamp-line', date);
|
||||||
|
date_line.setAttribute('data-date', date);
|
||||||
|
history.appendChild(date_line);
|
||||||
|
last_date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_message.is_original = new_message.lv_id === message.lv_id;
|
||||||
|
new_message.original_sender = new_message.from === message.from;
|
||||||
|
|
||||||
|
var msg_line = f._build_mod_card_history(new_message, mod_card, true,
|
||||||
|
FFZ.mod_card_pages.history.render_adjacent.bind(f, mod_card, el, new_message));
|
||||||
|
|
||||||
|
if ( new_message.is_original )
|
||||||
|
msg_line.classList.remove('ffz-mentioned');
|
||||||
|
|
||||||
|
history.appendChild(msg_line);
|
||||||
|
|
||||||
|
// These objects can be persistent, so clear these.
|
||||||
|
new_message.is_original = null;
|
||||||
|
new_message.original_sender = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( data.after.length >= 10 )
|
||||||
|
FFZ.mod_card_pages.history.render_more.call(
|
||||||
|
f, mod_card, el, history, data.after[data.after.length-1].lv_id, false, true);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
var original = history.querySelector('.original-msg');
|
||||||
|
if ( original )
|
||||||
|
history.scrollTop = (original.offsetTop - history.offsetTop) - (history.clientHeight - original.clientHeight) / 2;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(mod_card, el) {
|
||||||
|
var f = this,
|
||||||
|
controller = utils.ember_lookup('controller:chat'),
|
||||||
|
user_id = mod_card.get('cardInfo.user.id'),
|
||||||
|
room_id = controller && controller.get('currentRoom.id'),
|
||||||
|
ffz_room = this.rooms[room_id],
|
||||||
|
|
||||||
|
history = utils.createElement('ul', 'interface chat-history lv-history');
|
||||||
|
|
||||||
|
el.appendChild(history);
|
||||||
|
|
||||||
|
// Are we relying on LogViewer here?
|
||||||
|
if ( ! ffz_room.has_logs || ! mod_card.lv_view ) {
|
||||||
|
history.innerHTML = '<li class="chat-line admin"><span class="message">You do not have permission to view chat history in this channel.</span></li>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading!
|
||||||
|
history.classList.add('loading');
|
||||||
|
|
||||||
|
mod_card.lvGetLogs().then(function(data) {
|
||||||
|
f.log("[LV] Logs: " + user_id + " in " + room_id, data);
|
||||||
|
history.classList.remove('loading');
|
||||||
|
|
||||||
|
// Should we display more?
|
||||||
|
if ( data.before.length >= 10 )
|
||||||
|
FFZ.mod_card_pages.history.render_more.call(
|
||||||
|
f, mod_card, el, history, data.before[0].lv_id, true, false);
|
||||||
|
|
||||||
|
var last_date = (new Date).toLocaleDateString();
|
||||||
|
|
||||||
|
for(var i=0; i < data.before.length; i++) {
|
||||||
|
var message = data.before[i],
|
||||||
|
date = message.date.toLocaleDateString();
|
||||||
|
|
||||||
|
if ( date !== last_date ) {
|
||||||
|
var date_line = utils.createElement('li', 'chat-line timestamp-line', date);
|
||||||
|
date_line.setAttribute('data-date', date);
|
||||||
|
history.appendChild(date_line);
|
||||||
|
last_date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg_line = f._build_mod_card_history(message, mod_card, false,
|
||||||
|
FFZ.mod_card_pages.history.render_adjacent.bind(f, mod_card, el, message));
|
||||||
|
|
||||||
|
msg_line.classList.remove('ffz-mentioned');
|
||||||
|
history.appendChild(msg_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
history.scrollTop = history.scrollHeight;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FFZ.mod_card_pages.stats = {
|
||||||
|
title: "<span>S</span>tatistics",
|
||||||
|
render: function(mod_card, el) {
|
||||||
|
var f = this,
|
||||||
|
controller = utils.ember_lookup('controller:chat'),
|
||||||
|
room_id = controller && controller.get('currentRoom.id'),
|
||||||
|
user_id = mod_card.get('cardInfo.user.id'),
|
||||||
|
ffz_room = f.rooms && f.rooms[room_id];
|
||||||
|
|
||||||
|
var container = utils.createElement('ul', 'interface version-list');
|
||||||
|
el.appendChild(container);
|
||||||
|
|
||||||
|
if ( ffz_room.has_logs && mod_card.lv_view ) {
|
||||||
|
container.classList.add('loading');
|
||||||
|
|
||||||
|
mod_card.lvGetLogs().then(function(data) {
|
||||||
|
container.classList.remove('loading');
|
||||||
|
container.innerHTML = '<li>Messages <span>' + utils.number_commas(data.user.messages) + '</span></li><li>Timeouts <span> ' + utils.number_commas(data.user.timeouts) + '</span></li>';
|
||||||
|
});
|
||||||
|
|
||||||
|
var notice = utils.createElement('div', 'interface');
|
||||||
|
notice.innerHTML = 'Chat Log Source: <a target="_blank" href="https://cbenni.com/' + room_id + '?user=' + user_id + '">CBenni\'s logviewer</a>';
|
||||||
|
el.appendChild(notice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FFZ.mod_card_pages.notes = {
|
||||||
|
title: "<span>N</span>otes",
|
||||||
|
render: function(mod_card, el) {
|
||||||
|
var f = this,
|
||||||
|
controller = utils.ember_lookup('controller:chat'),
|
||||||
|
room = controller && controller.get('currentRoom'),
|
||||||
|
tmiSession = room.tmiSession || (window.TMI && TMI._sessions && TMI._sessions[0]),
|
||||||
|
|
||||||
|
room_id = room && room.get('id'),
|
||||||
|
user_id = mod_card.get('cardInfo.user.id'),
|
||||||
|
|
||||||
|
ffz_room = this.rooms[room_id],
|
||||||
|
history = utils.createElement('ul', 'interface chat-history user-notes');
|
||||||
|
|
||||||
|
|
||||||
|
el.appendChild(history);
|
||||||
|
|
||||||
|
if ( ! ffz_room.has_logs || ! mod_card.lv_view_notes ) {
|
||||||
|
history.innerHTML = '<li class="chat-line admin"><span class="message">You do not have permission to view notes in this channel.</span></li>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
history.classList.add('loading');
|
||||||
|
|
||||||
|
this.lv_get_token().then(function(token) {
|
||||||
|
utils.logviewer.get("comments/" + room_id + "?topic=" + user_id, token)
|
||||||
|
.then(utils.json).then(function(data) {
|
||||||
|
|
||||||
|
f.log("[LV] Comments: " + user_id + " in " + room_id, data);
|
||||||
|
history.classList.remove('loading');
|
||||||
|
|
||||||
|
if ( data.length )
|
||||||
|
for(var i=0; i < data.length; i++) {
|
||||||
|
var raw = data[i],
|
||||||
|
msg = {
|
||||||
|
date: new Date(raw.added * 1000),
|
||||||
|
from: raw.author,
|
||||||
|
room: raw.channel,
|
||||||
|
lv_id: raw.id,
|
||||||
|
message: raw.text,
|
||||||
|
tags: {},
|
||||||
|
color: tmiSession && raw.author ? tmiSession.getColor(raw.author) : "#755000"
|
||||||
|
};
|
||||||
|
|
||||||
|
f.tokenize_chat_line(msg, true, false);
|
||||||
|
history.appendChild(f._build_mod_card_history(msg, mod_card, true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
history.appendChild(utils.createElement('li', 'chat-line message-line admin',
|
||||||
|
'<span class="message">There are no notes on this user.</span>'));
|
||||||
|
|
||||||
|
history.appendChild(utils.createElement('li', 'chat-line message-line admin',
|
||||||
|
'<span clss="message">Create notes for this user at <a target="_blank" href="https://cbenni.com/' + room_id + '?user=' + user_id + '">CBenni\'s logviewer.</a></span>'));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -827,6 +827,16 @@ FFZ.prototype.add_room = function(id, room) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look up if the room has moderation logs.
|
||||||
|
var f = this;
|
||||||
|
this.ws_send("has_logs", id, function(success, response) {
|
||||||
|
if ( ! success )
|
||||||
|
return;
|
||||||
|
|
||||||
|
data.has_logs = response.has_logs;
|
||||||
|
data.log_source = response.log_source;
|
||||||
|
}, true);
|
||||||
|
|
||||||
// Is the room important?
|
// Is the room important?
|
||||||
this.update_room_important(id);
|
this.update_room_important(id);
|
||||||
|
|
||||||
|
@ -905,7 +915,7 @@ FFZ.prototype.remove_room = function(id) {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
|
||||||
FFZ.prototype._show_deleted = function(room_id) {
|
/*FFZ.prototype._show_deleted = function(room_id) {
|
||||||
var room = this.rooms[room_id];
|
var room = this.rooms[room_id];
|
||||||
if ( ! room || ! room.room )
|
if ( ! room || ! room.room )
|
||||||
return;
|
return;
|
||||||
|
@ -1047,7 +1057,7 @@ FFZ.prototype._insert_history = function(room_id, data, from_server) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
|
@ -1084,8 +1094,15 @@ FFZ.prototype._load_room_json = function(room_id, callback, data) {
|
||||||
|
|
||||||
data = data.room;
|
data = data.room;
|
||||||
|
|
||||||
|
// Apply the data we've received to the room data model.
|
||||||
|
var model = this.rooms[room_id] = this.rooms[room_id] || {};
|
||||||
|
|
||||||
|
for(var key in data)
|
||||||
|
if ( key !== 'room' && data.hasOwnProperty(key) )
|
||||||
|
model[key] = data[key];
|
||||||
|
|
||||||
// Preserve the pointer to the Room instance.
|
// Preserve the pointer to the Room instance.
|
||||||
if ( this.rooms[room_id] )
|
/*if ( this.rooms[room_id] )
|
||||||
data.room = this.rooms[room_id].room;
|
data.room = this.rooms[room_id].room;
|
||||||
|
|
||||||
// Preserve everything else.
|
// Preserve everything else.
|
||||||
|
@ -1096,26 +1113,26 @@ FFZ.prototype._load_room_json = function(room_id, callback, data) {
|
||||||
|
|
||||||
data.needs_history = this.rooms[room_id] && this.rooms[room_id].needs_history || false;
|
data.needs_history = this.rooms[room_id] && this.rooms[room_id].needs_history || false;
|
||||||
|
|
||||||
this.rooms[room_id] = data;
|
this.rooms[room_id] = data;*/
|
||||||
|
|
||||||
if ( data.css || data.moderator_badge )
|
if ( model.css || model.moderator_badge )
|
||||||
utils.update_css(this._room_style, room_id, moderator_css(data) + (data.css||""));
|
utils.update_css(this._room_style, room_id, moderator_css(model) + (model.css || ""));
|
||||||
|
|
||||||
if ( ! this.emote_sets.hasOwnProperty(data.set) )
|
if ( ! this.emote_sets.hasOwnProperty(model.set) )
|
||||||
this.load_set(data.set, function(success, set) {
|
this.load_set(model.set, function(success, set) {
|
||||||
if ( set.users.indexOf(room_id) === -1 )
|
if ( set.users.indexOf(room_id) === -1 )
|
||||||
set.users.push(room_id);
|
set.users.push(room_id);
|
||||||
});
|
});
|
||||||
else if ( this.emote_sets[data.set].users.indexOf(room_id) === -1 )
|
else if ( this.emote_sets[model.set].users.indexOf(room_id) === -1 )
|
||||||
this.emote_sets[data.set].users.push(room_id);
|
this.emote_sets[model.set].users.push(room_id);
|
||||||
|
|
||||||
this.update_ui_link();
|
this.update_ui_link();
|
||||||
|
|
||||||
if ( data.set )
|
if ( model.set )
|
||||||
this.rerender_feed_cards(data.set);
|
this.rerender_feed_cards(model.set);
|
||||||
|
|
||||||
if ( callback )
|
if ( callback )
|
||||||
callback(true, data);
|
callback(true, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1569,8 +1586,8 @@ FFZ.prototype._modify_room = function(room) {
|
||||||
t.set("messages", []);
|
t.set("messages", []);
|
||||||
t.addMessage({
|
t.addMessage({
|
||||||
style: 'admin',
|
style: 'admin',
|
||||||
message: i18n("Chat was cleared by a moderator"),
|
message: i18n("Chat was cleared by a moderator")
|
||||||
ffz_old_messages: msgs
|
//ffz_old_messages: msgs
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1962,11 +1979,12 @@ FFZ.prototype._modify_room = function(room) {
|
||||||
|
|
||||||
if ( f._mod_card && f._mod_card.ffz_room_id === msg.room && f._mod_card.get('cardInfo.user.id') === msg.from ) {
|
if ( f._mod_card && f._mod_card.ffz_room_id === msg.room && f._mod_card.get('cardInfo.user.id') === msg.from ) {
|
||||||
var el = f._mod_card.get('element'),
|
var el = f._mod_card.get('element'),
|
||||||
history = el && el.querySelector('.chat-history:not(.adjacent-history)'),
|
history = el && el.querySelector('.chat-history.live-history');
|
||||||
was_at_top = history && history.scrollTop >= (history.scrollHeight - history.clientHeight);
|
|
||||||
|
|
||||||
if ( history ) {
|
if ( history ) {
|
||||||
var el = f._build_mod_card_history(msg, f._mod_card);
|
var was_at_top = history.scrollTop >= (history.scrollHeight - history.clientHeight),
|
||||||
|
el = f._build_mod_card_history(msg, f._mod_card);
|
||||||
|
|
||||||
if ( msg.tags && msg.tags.historical )
|
if ( msg.tags && msg.tags.historical )
|
||||||
history.insertBefore(el, history.firstElementChild);
|
history.insertBefore(el, history.firstElementChild);
|
||||||
else
|
else
|
||||||
|
|
|
@ -34,7 +34,7 @@ FFZ.msg_commands = {};
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
var VER = FFZ.version_info = {
|
var VER = FFZ.version_info = {
|
||||||
major: 3, minor: 5, revision: 312,
|
major: 3, minor: 5, revision: 313,
|
||||||
toString: function() {
|
toString: function() {
|
||||||
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
|
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,7 @@ require('./ext/emote_menu');
|
||||||
|
|
||||||
require('./featurefriday');
|
require('./featurefriday');
|
||||||
|
|
||||||
|
require('./ui/logviewer');
|
||||||
//require('./ui/chatpane');
|
//require('./ui/chatpane');
|
||||||
require('./ui/popups');
|
require('./ui/popups');
|
||||||
require('./ui/styles');
|
require('./ui/styles');
|
||||||
|
|
|
@ -659,17 +659,8 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
|
||||||
//if ( helpers && helpers.tokenizeRichContent )
|
//if ( helpers && helpers.tokenizeRichContent )
|
||||||
// tokens = helpers.tokenizeRichContent(tokens, tags.content, delete_links);
|
// tokens = helpers.tokenizeRichContent(tokens, tags.content, delete_links);
|
||||||
|
|
||||||
if ( helpers && helpers.linkifyMessage ) {
|
if ( helpers && helpers.linkifyMessage )
|
||||||
var labels = msgObject.labels || [],
|
tokens = helpers.linkifyMessage(tokens, delete_links && ! tags.mod);
|
||||||
mod_or_higher = labels.indexOf("owner") !== -1 ||
|
|
||||||
labels.indexOf("staff") !== -1 ||
|
|
||||||
labels.indexOf("admin") !== -1 ||
|
|
||||||
labels.indexOf("global_mod") !== -1 ||
|
|
||||||
labels.indexOf("mod") !== -1 ||
|
|
||||||
msgObject.style === 'admin';
|
|
||||||
|
|
||||||
tokens = helpers.linkifyMessage(tokens, delete_links && !mod_or_higher);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ( user && user.login && helpers && helpers.mentionizeMessage ) {
|
if ( user && user.login && helpers && helpers.mentionizeMessage ) {
|
||||||
|
@ -1317,7 +1308,7 @@ FFZ.prototype._deleted_link_click = function(e) {
|
||||||
// History Loading
|
// History Loading
|
||||||
// ---------------------
|
// ---------------------
|
||||||
|
|
||||||
FFZ.prototype.parse_history = function(history, purged, bad_ids, room_id, delete_links, tmiSession, per_line) {
|
/*FFZ.prototype.parse_history = function(history, purged, bad_ids, room_id, delete_links, tmiSession, per_line) {
|
||||||
var i = history.length, was_cleared = false;
|
var i = history.length, was_cleared = false;
|
||||||
purged = purged || {};
|
purged = purged || {};
|
||||||
bad_ids = bad_ids || {};
|
bad_ids = bad_ids || {};
|
||||||
|
@ -1396,4 +1387,4 @@ FFZ.prototype.parse_history = function(history, purged, bad_ids, room_id, delete
|
||||||
}
|
}
|
||||||
|
|
||||||
return [history, purged, was_cleared];
|
return [history, purged, was_cleared];
|
||||||
}
|
}*/
|
249
src/ui/logviewer.js
Normal file
249
src/ui/logviewer.js
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
var FFZ = window.FrankerFaceZ,
|
||||||
|
utils = require('../utils'),
|
||||||
|
constants = require('../constants');
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// Token Request
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
FFZ.prototype._lv_token_requests = null;
|
||||||
|
FFZ.prototype._lv_token = {"token": null, "expires": 0};
|
||||||
|
|
||||||
|
FFZ.prototype.lv_get_token = function() {
|
||||||
|
var f = this,
|
||||||
|
token = this._lv_token,
|
||||||
|
now = Date.now() / 1000;
|
||||||
|
|
||||||
|
return new Promise(function(succeed, fail) {
|
||||||
|
// Make sure the token will be valid for at least 5 more minutes.
|
||||||
|
if ( token.token && token.expires > (now + 300) )
|
||||||
|
return succeed(token.token);
|
||||||
|
|
||||||
|
// If we're already making a request, don't duplicate it.
|
||||||
|
if ( f._lv_token_requests )
|
||||||
|
return f._lv_token_requests.push([succeed, fail]);
|
||||||
|
|
||||||
|
// Nope, new request.
|
||||||
|
f._lv_token_requests = [[succeed, fail]];
|
||||||
|
|
||||||
|
f.ws_send("get_logviewer_token", undefined, function(succeeded, data) {
|
||||||
|
token = succeeded ? data : token = {"token": null, "expires": 0};
|
||||||
|
|
||||||
|
var requests = f._lv_token_requests;
|
||||||
|
f._lv_token = token;
|
||||||
|
f._lv_token_requests = null;
|
||||||
|
|
||||||
|
for(var i=0; i < requests.length; i++) {
|
||||||
|
requests[i][succeeded ? 0 : 1](token.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// Log Requests
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
FFZ.prototype.lv_get_logs = function(room_id, user_id, ref_id, before_cnt, after_cnt) {
|
||||||
|
var f = this;
|
||||||
|
return new Promise(function(succeed, fail) {
|
||||||
|
f.lv_get_token().then(function(token) {
|
||||||
|
args = [];
|
||||||
|
|
||||||
|
user_id !== undefined && user_id !== null && args.push('nick=' + user_id);
|
||||||
|
ref_id !== undefined && ref_id !== null && args.push('id=' + ref_id);
|
||||||
|
before_cnt !== undefined && before_cnt !== null && args.push('before=' + before_cnt);
|
||||||
|
after_cnt !== undefined && after_cnt !== null && args.push('after=' + after_cnt)
|
||||||
|
|
||||||
|
utils.logviewer.get("logs/" + room_id + "?" + args.join('&'), token)
|
||||||
|
.then(utils.json).then(function(data) {
|
||||||
|
// Parse every message immediately.
|
||||||
|
var bound = f.lv_parse_message.bind(f);
|
||||||
|
data.before = _.map(data.before, bound);
|
||||||
|
data.after = _.map(data.after, bound);
|
||||||
|
|
||||||
|
succeed(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// Message Processing
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
FFZ.prototype.lv_parse_message = function(message) {
|
||||||
|
var parsed = utils.parse_irc_privmsg(message.text),
|
||||||
|
ffz_room = this.rooms && this.rooms[parsed.room],
|
||||||
|
room = ffz_room && ffz_room.room;
|
||||||
|
|
||||||
|
parsed.lv_id = message.id;
|
||||||
|
parsed.date = new Date(message.time * 1000);
|
||||||
|
|
||||||
|
// Check for ban notices. Those are identifiable via display-name.
|
||||||
|
parsed.is_ban = parsed.tags['display-name'] === 'jtv';
|
||||||
|
if ( parsed.is_ban )
|
||||||
|
parsed.style = 'admin';
|
||||||
|
|
||||||
|
if ( parsed.tags.color )
|
||||||
|
parsed.color = parsed.tags.color;
|
||||||
|
else {
|
||||||
|
var tmiSession = room && room.tmiSession || window.TMI && TMI._sessions && TMI._sessions[0];
|
||||||
|
parsed.color = tmiSession && parsed.from ? tmiSession.getColor(parsed.from) : "#755000";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! parsed.tags.hasOwnProperty('mod') ) {
|
||||||
|
var badges = parsed.tags.badges || {};
|
||||||
|
parsed.tags.mod = parsed.from === parsed.room || badges.hasOwnProperty('staff') || badges.hasOwnProperty('admin') || badges.hasOwnProperty('global_mod') || badges.hasOwnProperty('moderator');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tokenize_chat_line(parsed, true, room && room.get('roomProperties.hide_chat_links'));
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------
|
||||||
|
// WebSocket Connection
|
||||||
|
// ---------------------
|
||||||
|
|
||||||
|
FFZ.prototype.lv_ws_create = function() {
|
||||||
|
var f = this, ws,
|
||||||
|
server = constants.LV_SOCKET_SERVER + '?EIO=3&transport=websocket';
|
||||||
|
|
||||||
|
if ( this._lv_ws_recreate_timer ) {
|
||||||
|
clearTimeout(this._lv_ws_recreate_timer);
|
||||||
|
this._lv_ws_recreate_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this._lv_ws_connecting || this._lv_ws_open )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._lv_ws_topics = this._lv_ws_topics || [];
|
||||||
|
this._lv_ws_ping_timer = null;
|
||||||
|
this._lv_ws_connecting = true;
|
||||||
|
|
||||||
|
this.log('[LV] Using Socket Server: ' + server);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ws = this._lv_ws_sock = new WebSocket(server);
|
||||||
|
} catch(err) {
|
||||||
|
this._lv_ws_sock = null;
|
||||||
|
return this.error('[LV] Error creating WebSocket', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onopen = function(e) {
|
||||||
|
f._lv_ws_connecting = false;
|
||||||
|
f._lv_ws_open = true;
|
||||||
|
f.log('[LV] Socket connected.');
|
||||||
|
|
||||||
|
ws.send('2probe');
|
||||||
|
ws.send('5');
|
||||||
|
|
||||||
|
// Ping every 10 seconds just to be safe.
|
||||||
|
f._lv_ws_ping_timer = setInterval(function() {
|
||||||
|
ws.send('2');
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
if ( f._lv_ws_topics.length )
|
||||||
|
for(var i=0; i < f._lv_ws_topics.length; i++)
|
||||||
|
ws.send('42' + JSON.stringify(["subscribe", f._lv_ws_topics[i]]));
|
||||||
|
else
|
||||||
|
f._lv_ws_close_timer = setTimeout(f.lv_ws_maybe_close.bind(f), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onclose = function(e) {
|
||||||
|
var was_open = f._lv_ws_open;
|
||||||
|
f.log('[LV] Socket closed. (Code: ' + e.code + ', Reason: ' + e.reason + ')');
|
||||||
|
|
||||||
|
f._lv_ws_open = false;
|
||||||
|
f._lv_ws_connecting = false;
|
||||||
|
f._lv_ws_sock = null;
|
||||||
|
if ( f._lv_ws_ping_timer ) {
|
||||||
|
clearInterval(f._lv_ws_ping_timer);
|
||||||
|
f._lv_ws_ping_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we care about reconnecting? We only do if we have topics?
|
||||||
|
if ( ! f._lv_ws_topics || ! f._lv_ws_topics.length )
|
||||||
|
return;
|
||||||
|
|
||||||
|
f._lv_ws_recreate_timer = setTimeout(f.lv_ws_create.bind(f), 500 + Math.random() * 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = function(e) {
|
||||||
|
var message = e.data;
|
||||||
|
// We only care about the meaning of life.
|
||||||
|
if ( message.substr(0,2) !== '42' )
|
||||||
|
return;
|
||||||
|
|
||||||
|
var data = JSON.parse(message.substr(2));
|
||||||
|
|
||||||
|
if ( f._lv_ws_callbacks && f._lv_ws_callbacks.length )
|
||||||
|
for(var i=0; i < f._lv_ws_callbacks.length; i++)
|
||||||
|
f._lv_ws_callbacks[i](data[0], data[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FFZ.prototype.lv_ws_add_callback = function(callback) {
|
||||||
|
this._lv_ws_callbacks = this._lv_ws_callbacks || [];
|
||||||
|
if ( this._lv_ws_callbacks.indexOf(callback) === -1 )
|
||||||
|
this._lv_ws_callbacks.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FFZ.prototype.lv_ws_remove_callback = function(callback) {
|
||||||
|
this._lv_ws_callbacks = this._lv_ws_callbacks || [];
|
||||||
|
var ind = this._lv_ws_callbacks.indexOf(callback);
|
||||||
|
if ( ind !== -1 )
|
||||||
|
this._lv_ws_callbacks.splice(ind, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FFZ.prototype.lv_ws_sub = function(topic) {
|
||||||
|
this._lv_ws_topics = this._lv_ws_topics || [];
|
||||||
|
if ( this._lv_ws_topics.indexOf(topic) !== -1 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._lv_ws_topics.push(topic);
|
||||||
|
if ( this._lv_ws_close_timer ) {
|
||||||
|
clearTimeout(this._lv_ws_close_timer);
|
||||||
|
this._lv_ws_close_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this._lv_ws_open )
|
||||||
|
this._lv_ws_sock.send("42" + JSON.stringify(['subscribe', topic]));
|
||||||
|
|
||||||
|
else if ( ! this._lv_ws_connecting )
|
||||||
|
this.lv_ws_create();
|
||||||
|
}
|
||||||
|
|
||||||
|
FFZ.prototype.lv_ws_unsub = function(topic) {
|
||||||
|
this._lv_ws_topics = this._lv_ws_topics || [];
|
||||||
|
var ind = this._lv_ws_topics.indexOf(topic);
|
||||||
|
if ( ind === -1 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._lv_ws_topics.splice(ind, 1);
|
||||||
|
|
||||||
|
if ( this._lv_ws_open ) {
|
||||||
|
this._lv_ws_sock.send("42" + JSON.stringify(['unsubscribe', topic]));
|
||||||
|
if ( this._lv_ws_topics.length === 0 )
|
||||||
|
this._lv_ws_close_timer = setTimeout(this.lv_ws_maybe_close.bind(this), 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FFZ.prototype.lv_ws_maybe_close = function() {
|
||||||
|
if ( this._lv_ws_close_timer ) {
|
||||||
|
clearTimeout(this._lv_ws_close_timer);
|
||||||
|
this._lv_ws_close_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (! this._lv_ws_topics || ! this._lv_ws_topics.length) && this._lv_ws_open )
|
||||||
|
this._lv_ws_sock.close();
|
||||||
|
}
|
186
src/utils.js
186
src/utils.js
|
@ -93,6 +93,162 @@ var createElement = function(tag, className, content) {
|
||||||
return new Date(unix);
|
return new Date(unix);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/* IRC Processing */
|
||||||
|
|
||||||
|
irc_regex = /^(?:@([^ ]+) )?(?:[:](\S+) )?(\S+)(?: (?!:)(.+?))?(?: [:](.+))?$/,
|
||||||
|
tag_regex = /([^=;]+)=([^;]*)/g,
|
||||||
|
|
||||||
|
parse_badge_tag = function(tag) {
|
||||||
|
var badges = {},
|
||||||
|
values = tag.split(',');
|
||||||
|
|
||||||
|
for(var i=0; i < values.length; i++) {
|
||||||
|
var parts = values[i].split('/');
|
||||||
|
if ( parts.length === 2 )
|
||||||
|
badges[parts[0]] = parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return badges;
|
||||||
|
},
|
||||||
|
|
||||||
|
parse_emote_tag = function(tag) {
|
||||||
|
var emotes = {},
|
||||||
|
values = tag.split("/"),
|
||||||
|
i = values.length;
|
||||||
|
|
||||||
|
while(i--) {
|
||||||
|
var parts = values[i].split(":");
|
||||||
|
if ( parts.length !== 2 )
|
||||||
|
return {};
|
||||||
|
|
||||||
|
var emote_id = parts[0],
|
||||||
|
matches = emotes[emote_id] = [],
|
||||||
|
indices = parts[1].split(",");
|
||||||
|
|
||||||
|
for(var j=0, jl = indices.length; j < jl; j++) {
|
||||||
|
var pair = indices[j].split("-");
|
||||||
|
if ( pair.length !== 2 )
|
||||||
|
return {};
|
||||||
|
|
||||||
|
var start = parseInt(pair[0]),
|
||||||
|
end = parseInt(pair[1]);
|
||||||
|
|
||||||
|
matches.push([start,end]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emotes;
|
||||||
|
},
|
||||||
|
|
||||||
|
parse_tag = function(tag, value) {
|
||||||
|
switch (tag) {
|
||||||
|
case "badges":
|
||||||
|
return parse_badge_tag(value);
|
||||||
|
case "emotes":
|
||||||
|
return parse_emote_tag(value);
|
||||||
|
case "sent-ts":
|
||||||
|
case "sent-tmi-ts":
|
||||||
|
case "slow":
|
||||||
|
return +value;
|
||||||
|
case "subscriber":
|
||||||
|
case "mod":
|
||||||
|
case "turbo":
|
||||||
|
case "r9k":
|
||||||
|
case "subs-only":
|
||||||
|
case "historical":
|
||||||
|
return value === "1";
|
||||||
|
default:
|
||||||
|
// Try to unescape the value.
|
||||||
|
try {
|
||||||
|
return value;
|
||||||
|
} catch(err) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parse_tags = function(raw_tags) {
|
||||||
|
var m, tags = {};
|
||||||
|
do {
|
||||||
|
m = tag_regex.exec(raw_tags);
|
||||||
|
if ( m )
|
||||||
|
tags[m[1]] = parse_tag(m[1], m[2]);
|
||||||
|
|
||||||
|
} while(m);
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
},
|
||||||
|
|
||||||
|
parse_sender = function(prefix, tags) {
|
||||||
|
var ind = prefix.indexOf('!');
|
||||||
|
if ( ind !== -1 )
|
||||||
|
return prefix.substr(0, ind);
|
||||||
|
if ( prefix === "tmi.twitch.tv" && tags.login )
|
||||||
|
return tags.login;
|
||||||
|
return prefix;
|
||||||
|
},
|
||||||
|
|
||||||
|
parse_irc_message = function(message) {
|
||||||
|
var data = irc_regex.exec(message);
|
||||||
|
if ( ! data )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var m,
|
||||||
|
tags = {},
|
||||||
|
output = {
|
||||||
|
tags: tags,
|
||||||
|
prefix: data[2],
|
||||||
|
command: data[3],
|
||||||
|
params: data[4],
|
||||||
|
trailing: data[5]
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( data[1] )
|
||||||
|
output.tags = parse_tags(data[1]);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
parse_irc_privmsg = function(message) {
|
||||||
|
var parsed = parse_irc_message(message);
|
||||||
|
if ( parsed.command.toLowerCase() !== "privmsg" )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var params = (parsed.params || "").split(' '),
|
||||||
|
target = params.shift();
|
||||||
|
|
||||||
|
if ( target.charAt(0) !== '#' )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if ( parsed.trailing )
|
||||||
|
params.push(parsed.trailing);
|
||||||
|
|
||||||
|
var from = parse_sender(parsed.prefix),
|
||||||
|
message = params.join(' '),
|
||||||
|
style = '';
|
||||||
|
|
||||||
|
if ( from === 'jtv' )
|
||||||
|
style = 'admin';
|
||||||
|
else if ( from === 'twitchnotify' )
|
||||||
|
style = 'notification';
|
||||||
|
|
||||||
|
if ( message.substr(0,8) === '\u0001ACTION ' && message.charAt(message.length-1) === '\u0001' ) {
|
||||||
|
message = message.substr(8, message.length - 9);
|
||||||
|
style += (style ? ' ' : '') + 'action';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tags: parsed.tags,
|
||||||
|
from: parse_sender(parsed.prefix),
|
||||||
|
room: target.substr(1),
|
||||||
|
message: message,
|
||||||
|
style: style
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
BADGE_REV = {
|
BADGE_REV = {
|
||||||
'b': 'broadcaster',
|
'b': 'broadcaster',
|
||||||
's': 'staff',
|
's': 'staff',
|
||||||
|
@ -217,6 +373,16 @@ var createElement = function(tag, className, content) {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
logviewer_call = function(method, url, token, info) {
|
||||||
|
info = info || {};
|
||||||
|
info['method'] = method;
|
||||||
|
if ( token )
|
||||||
|
url += (url.indexOf('?') === -1 ? '?' : '&') + 'token=' + token;
|
||||||
|
|
||||||
|
return fetch("https://cbenni.com/api/" + url, info);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
show_modal = function(contents, on_close, width) {
|
show_modal = function(contents, on_close, width) {
|
||||||
var container = createElement('div', 'twitch_subwindow_container'),
|
var container = createElement('div', 'twitch_subwindow_container'),
|
||||||
|
@ -354,6 +520,19 @@ module.exports = FFZ.utils = {
|
||||||
put: function(u,d,o,t) { return api_call('put', u,d,o,t); }
|
put: function(u,d,o,t) { return api_call('put', u,d,o,t); }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
logviewer: {
|
||||||
|
del: function(u,t,i) { return logviewer_call('del', u,t,i) },
|
||||||
|
get: function(u,t,i) { return logviewer_call('get', u,t,i) },
|
||||||
|
post: function(u,t,i) { return logviewer_call('post', u,t,i) },
|
||||||
|
put: function(u,t,i) { return logviewer_call('put', u,t,i) },
|
||||||
|
},
|
||||||
|
|
||||||
|
json: function(response) {
|
||||||
|
if ( ! response.ok )
|
||||||
|
return Promise.resolve(null);
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
find_parent: function(el, klass) {
|
find_parent: function(el, klass) {
|
||||||
while (el && el.parentNode) {
|
while (el && el.parentNode) {
|
||||||
|
@ -366,6 +545,13 @@ module.exports = FFZ.utils = {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
parse_badge_tag: parse_badge_tag,
|
||||||
|
parse_emote_tag: parse_emote_tag,
|
||||||
|
parse_tags: parse_tags,
|
||||||
|
parse_irc_message: parse_irc_message,
|
||||||
|
parse_irc_privmsg: parse_irc_privmsg,
|
||||||
|
|
||||||
|
|
||||||
CMD_VAR_REGEX: CMD_VAR_REGEX,
|
CMD_VAR_REGEX: CMD_VAR_REGEX,
|
||||||
|
|
||||||
replace_cmd_variables: function(command, user, room, message, args) {
|
replace_cmd_variables: function(command, user, room, message, args) {
|
||||||
|
|
162
style.css
162
style.css
|
@ -18,6 +18,10 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz-moderation-card:not(.lv-notes) ul.menu li[data-page="notes"],
|
||||||
|
.ffz-moderation-card:not(.lv-logs) ul.menu li[data-page="history"],
|
||||||
|
.ffz-moderation-card:not(.lv-logs) ul.menu li[data-page="stats"],
|
||||||
|
.ffz-moderation-card:not(.lv-tabs) ul.menu,
|
||||||
.ffz-hide-prime .drawer .warp__list.js-offers,
|
.ffz-hide-prime .drawer .warp__list.js-offers,
|
||||||
.ffz-hide-prime-collapsed .drawer.closed .js-offers,
|
.ffz-hide-prime-collapsed .drawer.closed .js-offers,
|
||||||
.ffz-hide-friends-collapsed .drawer.closed .friend-list,
|
.ffz-hide-friends-collapsed .drawer.closed .friend-list,
|
||||||
|
@ -713,9 +717,12 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
||||||
.ffz-ui-sub-menu-page[data-page="about"],
|
.ffz-ui-sub-menu-page[data-page="about"],
|
||||||
.ffz-ui-menu-page .chat-menu-content p { padding: 0 20px; }
|
.ffz-ui-menu-page .chat-menu-content p { padding: 0 20px; }
|
||||||
|
|
||||||
|
.ffz-moderation-card .version-list span,
|
||||||
.ffz-ui-menu-page .version-list span { float: right }
|
.ffz-ui-menu-page .version-list span { float: right }
|
||||||
|
|
||||||
|
.ffz-moderation-card .version-list li:not(:last-of-type),
|
||||||
.ffz-ui-menu-page .version-list li:not(:last-of-type) {
|
.ffz-ui-menu-page .version-list li:not(:last-of-type) {
|
||||||
border-bottom: 1px dotted rgba(127,127,127,0.5);
|
border-bottom: 1px dotted rgba(127,127,127,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ffz-ui-menu-page .version-list ul {
|
.ffz-ui-menu-page .version-list ul {
|
||||||
|
@ -723,14 +730,14 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
||||||
}
|
}
|
||||||
|
|
||||||
.ffz-ui-menu-page pre {
|
.ffz-ui-menu-page pre {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ffz-old-news-button {
|
#ffz-old-news-button {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ffz-old-news { display: none }
|
#ffz-old-news { display: none }
|
||||||
|
@ -809,7 +816,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
border-top: 1px solid rgba(0,0,0,0.2);
|
border-top: 1px solid rgba(0,0,0,0.2);
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
margin: 0 1px 1px;
|
margin: 0 1px 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ffz-ui-popup ul.menu:not(.sub-menu) {
|
.ffz-ui-popup ul.menu:not(.sub-menu) {
|
||||||
|
@ -824,12 +831,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
||||||
|
|
||||||
.ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon-grid { background-color: transparent !important; }
|
.ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon-grid { background-color: transparent !important; }
|
||||||
|
|
||||||
.app-main.theatre .ffz-ui-popup ul.menu,
|
.ffz-bttv-dark .ffz-ui-popup ul.menu {
|
||||||
.chat-container.dark .ffz-ui-popup ul.menu,
|
|
||||||
.chat-container.force-dark .ffz-ui-popup ul.menu,
|
|
||||||
.ember-chat-container.dark .ffz-ui-popup ul.menu,
|
|
||||||
.ember-chat-container.force-dark .ffz-ui-popup ul.menu,
|
|
||||||
body.ffz-bttv-dark .ffz-ui-popup ul.menu {
|
|
||||||
background-color: #282828;
|
background-color: #282828;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -845,8 +847,8 @@ body.ffz-bttv-dark .ffz-ui-popup ul.menu {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ffz-ui-popup ul.sub-menu {
|
.ffz-ui-popup ul.sub-menu {
|
||||||
background-color: #dfdfdf;
|
background-color: #dfdfdf;
|
||||||
margin: 0 1px;
|
margin: 0 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main.theatre .ffz-ui-popup ul.sub-menu,
|
.app-main.theatre .ffz-ui-popup ul.sub-menu,
|
||||||
|
@ -1281,8 +1283,7 @@ img.channel_background[src="null"] { display: none; }
|
||||||
|
|
||||||
.ffz-moderation-card {
|
.ffz-moderation-card {
|
||||||
border: 2px solid #cbcbcb;
|
border: 2px solid #cbcbcb;
|
||||||
max-width: 350px;
|
width: 340px;
|
||||||
min-width: 325px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ember-chat .ffz-moderation-card .close-button {
|
.ember-chat .ffz-moderation-card .close-button {
|
||||||
|
@ -1385,14 +1386,12 @@ img.channel_background[src="null"] { display: none; }
|
||||||
/*box-shadow: #000 0 0 5px;*/
|
/*box-shadow: #000 0 0 5px;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz-moderation-card ul.menu + .interface { border-top: none }
|
||||||
|
|
||||||
.ffz-moderation-card .interface:not(:last-of-type) {
|
.ffz-moderation-card .interface:not(:last-of-type) {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ffz-moderation-card .interface {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ffz-moderation-card h4.name { display: inline-block; }
|
.ffz-moderation-card h4.name { display: inline-block; }
|
||||||
|
|
||||||
.ffz-moderation-card .info,
|
.ffz-moderation-card .info,
|
||||||
|
@ -1410,6 +1409,60 @@ img.channel_background[src="null"] { display: none; }
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz-moderation-card ul.menu {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-moderation-card ul.menu span { text-decoration: underline }
|
||||||
|
|
||||||
|
.ffz-dark .ffz-moderation-card ul.menu,
|
||||||
|
.theatre .ffz-moderation-card ul.menu,
|
||||||
|
.dark .ffz-moderation-card ul.menu,
|
||||||
|
.force-dark .ffz-moderation-card ul.menu {
|
||||||
|
border-bottom-color: rgba(255,255,255,0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-moderation-card ul.menu li:first-of-type { margin-left: 5px }
|
||||||
|
.ffz-moderation-card ul.menu li:last-of-type { margin-right: 5px }
|
||||||
|
|
||||||
|
.ffz-moderation-card ul.menu li {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
float: left;
|
||||||
|
padding: 5px 5px 3px;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-moderation-card ul.menu li:hover {
|
||||||
|
color: #7d5bbe;
|
||||||
|
border-bottom-color: #7d5bbe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-dark .ffz-moderation-card ul.menu li,
|
||||||
|
.theatre .ffz-moderation-card ul.menu li,
|
||||||
|
.dark .ffz-moderation-card ul.menu li,
|
||||||
|
.force-dark .ffz-moderation-card ul.menu li {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-dark .ffz-moderation-card ul.menu li:hover,
|
||||||
|
.theatre .ffz-moderation-card ul.menu li:hover,
|
||||||
|
.dark .ffz-moderation-card ul.menu li:hover,
|
||||||
|
.force-dark .ffz-moderation-card ul.menu li:hover {
|
||||||
|
color: #a68ed2;
|
||||||
|
border-bottom-color: #a68ed2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-moderation-card ul.menu li.active { border-bottom-color: #6441a4 }
|
||||||
|
|
||||||
|
.ffz-moderation-card:not(.ffz-default-tab) > .interface:not(.menu) {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* dark moderation card */
|
/* dark moderation card */
|
||||||
|
|
||||||
|
@ -1984,10 +2037,14 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
|
||||||
.chat-history {
|
.chat-history {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
max-height: 200px;
|
max-height: 320px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.moderation-card .chat-history.live-history {
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-history.interface li:first-child { padding-top: 10px; }
|
.chat-history.interface li:first-child { padding-top: 10px; }
|
||||||
.chat-history.interface li:last-child { padding-bottom: 10px; }
|
.chat-history.interface li:last-child { padding-bottom: 10px; }
|
||||||
|
|
||||||
|
@ -1998,6 +2055,12 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
|
||||||
list-style-position: unset;
|
list-style-position: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-history .timestamp-line {
|
||||||
|
text-align: center;
|
||||||
|
color: #8c8c8c;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ember-chat .moderation-card .interface.chat-history .chat-line.action {
|
.ember-chat .moderation-card .interface.chat-history .chat-line.action {
|
||||||
float: none;
|
float: none;
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
|
@ -2014,13 +2077,22 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-history .chat-line:not(.original-sender) span.from:hover,
|
.chat-history .chat-line:not(.original-sender) span.from:hover,
|
||||||
.chat-history .timestamp:hover {
|
.chat-history .timestamp.ts-action:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-history .chat-line.admin.original-msg .message { color: #000 }
|
||||||
|
|
||||||
|
.ffz-dark .chat-history .chat-line.admin.original-msg .message,
|
||||||
|
.theatre .chat-history .chat-line.admin.original-msg .message,
|
||||||
|
.dark .chat-history .chat-line.admin.original-msg .message,
|
||||||
|
.force-dark .chat-history .chat-line.admin.original-msg .message { color: #fff }
|
||||||
|
|
||||||
|
|
||||||
.chat-history.loading {
|
.chat-history.loading {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 160px;
|
||||||
overflow-y: hidden !important;
|
overflow-y: hidden !important;
|
||||||
}
|
}
|
||||||
.chat-history.loading li { pointer-events: none; }
|
.chat-history.loading li { pointer-events: none; }
|
||||||
|
@ -2053,6 +2125,50 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.moderation-card .ffz-back-button,
|
||||||
|
.moderation-card .ffz-load-more {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 1rem !important;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-moderation-card .chat-back-button {
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-chevron {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
height: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
margin: 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-chevron:before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
border: .5rem solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-back-button .ffz-chevron {
|
||||||
|
margin-left: -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-back-button .ffz-chevron:before {
|
||||||
|
border-right-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-load-more:not(.load-after) .ffz-chevron:before {
|
||||||
|
top: -.25rem;
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
}
|
||||||
|
.ffz-load-more.load-after .ffz-chevron:before {
|
||||||
|
top: .25rem;
|
||||||
|
border-top-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.ember-chat .moderation-card .back-button {
|
.ember-chat .moderation-card .back-button {
|
||||||
border: 1px solid rgba(0,0,0,0.2);
|
border: 1px solid rgba(0,0,0,0.2);
|
||||||
border-top: none;
|
border-top: none;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue