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>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<div class="list-header">Awesome Resources</div>
|
||||
<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://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>
|
||||
|
|
|
@ -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.
|
||||
else if ( typeof badge_tag === 'string' ) {
|
||||
var val = badge_tag.split(',');
|
||||
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];
|
||||
}
|
||||
}
|
||||
else if ( typeof badge_tag === 'string' )
|
||||
badge_tag = utils.parse_badge_tag(badge_tag);
|
||||
|
||||
|
||||
for(var badge in badge_tag) {
|
||||
var version = badge_tag[badge];
|
||||
|
|
|
@ -18,6 +18,8 @@ module.exports = FrankerFaceZ.constants = {
|
|||
DEBUG: DEBUG,
|
||||
SERVER: SERVER,
|
||||
|
||||
LV_SOCKET_SERVER: "wss://cbenni.com/socket.io/",
|
||||
|
||||
IS_OSX: IS_OSX,
|
||||
IS_WIN: IS_WIN,
|
||||
META_NAME: IS_OSX ? "⌘" : (IS_WIN ? "Win" : "Meta"),
|
||||
|
|
|
@ -887,9 +887,9 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
|
|||
|
||||
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 )
|
||||
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>';
|
||||
},
|
||||
|
@ -909,8 +909,12 @@ FFZ.prototype._modify_chat_line = function(component, is_vod) {
|
|||
el.innerHTML = output;
|
||||
},
|
||||
|
||||
systemMsg: function() {
|
||||
return this.get('msgObject.tags.system-msg')
|
||||
}.property('msgObject.tags.system-msg'),
|
||||
|
||||
ffzShouldRenderMessageBody: function() {
|
||||
return !this.get("hasSystemMsg") || this.get("hasMessageBody");
|
||||
return ! this.get('hasSystemMsg') || this.get('hasMessageBody');
|
||||
}.property('hasSystemMsg', 'hasMessageBody'),
|
||||
|
||||
hasSystemMsg: function() {
|
||||
|
@ -1090,10 +1094,10 @@ FFZ.prototype._modify_chat_subline = function(component) {
|
|||
var cl = e.target.classList,
|
||||
from = this.get("msgObject.from");
|
||||
|
||||
if ( cl.contains('ffz-old-messages') )
|
||||
return f._show_deleted(this.get('msgObject.room'));
|
||||
/*if ( cl.contains('ffz-old-messages') )
|
||||
return f._show_deleted(this.get('msgObject.room'));*/
|
||||
|
||||
else if ( cl.contains('deleted-word') ) {
|
||||
if ( cl.contains('deleted-word') ) {
|
||||
jQuery(e.target).trigger('mouseout');
|
||||
e.target.outerHTML = e.target.getAttribute('data-text');
|
||||
|
||||
|
|
|
@ -13,7 +13,11 @@ var FFZ = window.FrankerFaceZ,
|
|||
P: 80,
|
||||
B: 66,
|
||||
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>',
|
||||
|
@ -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 = {
|
||||
type: "select",
|
||||
options: {
|
||||
|
@ -630,8 +646,19 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
utils.ember_reopen_view(component, {
|
||||
ffzForceRedraw: function() {
|
||||
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.
|
||||
if ( f.settings.highlight_messages_with_mod_card )
|
||||
|
@ -674,10 +701,124 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
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(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() {
|
||||
if ( f._mod_card === this )
|
||||
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');
|
||||
},
|
||||
|
||||
|
@ -687,6 +828,11 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
|
||||
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'),
|
||||
controller = this.get('controller'),
|
||||
t = this,
|
||||
|
@ -699,6 +845,7 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
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 = controller.get('cardInfo.user.id'),
|
||||
|
@ -713,6 +860,23 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
|
||||
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.
|
||||
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));
|
||||
|
@ -848,6 +1012,18 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
is_mod = controller.get('cardInfo.isModeratorOrHigher'),
|
||||
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 )
|
||||
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
|
||||
if ( f.settings.mod_card_history )
|
||||
this.ffzRenderHistory();
|
||||
if ( f.settings.mod_card_history ) {
|
||||
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.
|
||||
this.ffzReposition();
|
||||
|
@ -1073,6 +1282,31 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
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(),
|
||||
|
@ -1097,7 +1331,7 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
}
|
||||
}.observes('cardInfo.renderTop', 'cardInfo.renderLeft', 'cardInfo.renderRight', 'cardInfo.renderBottom'),
|
||||
|
||||
ffzRenderHistory: function() {
|
||||
/*ffzRenderHistory: function() {
|
||||
var t = this,
|
||||
Chat = utils.ember_lookup('controller:chat'),
|
||||
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] || [],
|
||||
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 ) {
|
||||
history = utils.createElement('ul', 'interface clearfix chat-history');
|
||||
history = utils.createElement('ul', 'interface clearfix ffz-tab-container chat-history');
|
||||
el.appendChild(history);
|
||||
} else {
|
||||
history.classList.remove('loading');
|
||||
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);
|
||||
f.ws_send("user_history", [room_id, user_id, 50 - user_history.length], function(success, data) {
|
||||
if ( ! success )
|
||||
|
@ -1157,16 +1396,16 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
if ( was_at_top )
|
||||
setTimeout(function() { history.scrollTop = history.scrollHeight; });
|
||||
});
|
||||
}
|
||||
}*//*
|
||||
|
||||
for(var i=0; i < user_history.length; i++)
|
||||
history.appendChild(f._build_mod_card_history(user_history[i], t));
|
||||
|
||||
// Lazy scroll-to-bottom
|
||||
history.scrollTop = history.scrollHeight;
|
||||
},
|
||||
},*/
|
||||
|
||||
ffzAdjacentHistory: function(line) {
|
||||
/*ffzAdjacentHistory: function(line) {
|
||||
var Chat = utils.ember_lookup('controller:chat'),
|
||||
t = this,
|
||||
|
||||
|
@ -1267,12 +1506,12 @@ FFZ.prototype.modify_moderation_card = function(component) {
|
|||
history.classList.remove('loading');
|
||||
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'),
|
||||
out = [],
|
||||
f = this;
|
||||
|
@ -1280,7 +1519,7 @@ FFZ.prototype._build_mod_card_history = function(msg, modcard, show_from) {
|
|||
style = '', colored = '';
|
||||
|
||||
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 ) {
|
||||
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[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-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.toLocaleDateString());
|
||||
l_el.setAttribute('data-deleted', msg.deleted || false);
|
||||
|
||||
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 )
|
||||
modcard.ffzAdjacentHistory(msg);
|
||||
return ts_click.call(this, e);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1483,3 +1724,328 @@ FFZ.chat_commands.u = function(room, args) {
|
|||
}
|
||||
|
||||
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?
|
||||
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];
|
||||
if ( ! room || ! room.room )
|
||||
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;
|
||||
|
||||
// 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.
|
||||
if ( this.rooms[room_id] )
|
||||
/*if ( this.rooms[room_id] )
|
||||
data.room = this.rooms[room_id].room;
|
||||
|
||||
// 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;
|
||||
|
||||
this.rooms[room_id] = data;
|
||||
this.rooms[room_id] = data;*/
|
||||
|
||||
if ( data.css || data.moderator_badge )
|
||||
utils.update_css(this._room_style, room_id, moderator_css(data) + (data.css||""));
|
||||
if ( model.css || model.moderator_badge )
|
||||
utils.update_css(this._room_style, room_id, moderator_css(model) + (model.css || ""));
|
||||
|
||||
if ( ! this.emote_sets.hasOwnProperty(data.set) )
|
||||
this.load_set(data.set, function(success, set) {
|
||||
if ( ! this.emote_sets.hasOwnProperty(model.set) )
|
||||
this.load_set(model.set, function(success, set) {
|
||||
if ( set.users.indexOf(room_id) === -1 )
|
||||
set.users.push(room_id);
|
||||
});
|
||||
else if ( this.emote_sets[data.set].users.indexOf(room_id) === -1 )
|
||||
this.emote_sets[data.set].users.push(room_id);
|
||||
else if ( this.emote_sets[model.set].users.indexOf(room_id) === -1 )
|
||||
this.emote_sets[model.set].users.push(room_id);
|
||||
|
||||
this.update_ui_link();
|
||||
|
||||
if ( data.set )
|
||||
this.rerender_feed_cards(data.set);
|
||||
if ( model.set )
|
||||
this.rerender_feed_cards(model.set);
|
||||
|
||||
if ( callback )
|
||||
callback(true, data);
|
||||
callback(true, model);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1569,8 +1586,8 @@ FFZ.prototype._modify_room = function(room) {
|
|||
t.set("messages", []);
|
||||
t.addMessage({
|
||||
style: 'admin',
|
||||
message: i18n("Chat was cleared by a moderator"),
|
||||
ffz_old_messages: msgs
|
||||
message: i18n("Chat was cleared by a moderator")
|
||||
//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 ) {
|
||||
var el = f._mod_card.get('element'),
|
||||
history = el && el.querySelector('.chat-history:not(.adjacent-history)'),
|
||||
was_at_top = history && history.scrollTop >= (history.scrollHeight - history.clientHeight);
|
||||
history = el && el.querySelector('.chat-history.live-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 )
|
||||
history.insertBefore(el, history.firstElementChild);
|
||||
else
|
||||
|
|
|
@ -34,7 +34,7 @@ FFZ.msg_commands = {};
|
|||
|
||||
// Version
|
||||
var VER = FFZ.version_info = {
|
||||
major: 3, minor: 5, revision: 312,
|
||||
major: 3, minor: 5, revision: 313,
|
||||
toString: function() {
|
||||
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
|
||||
}
|
||||
|
@ -202,6 +202,7 @@ require('./ext/emote_menu');
|
|||
|
||||
require('./featurefriday');
|
||||
|
||||
require('./ui/logviewer');
|
||||
//require('./ui/chatpane');
|
||||
require('./ui/popups');
|
||||
require('./ui/styles');
|
||||
|
|
|
@ -659,17 +659,8 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
|
|||
//if ( helpers && helpers.tokenizeRichContent )
|
||||
// tokens = helpers.tokenizeRichContent(tokens, tags.content, delete_links);
|
||||
|
||||
if ( helpers && helpers.linkifyMessage ) {
|
||||
var labels = msgObject.labels || [],
|
||||
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 ( helpers && helpers.linkifyMessage )
|
||||
tokens = helpers.linkifyMessage(tokens, delete_links && ! tags.mod);
|
||||
|
||||
|
||||
if ( user && user.login && helpers && helpers.mentionizeMessage ) {
|
||||
|
@ -1317,7 +1308,7 @@ FFZ.prototype._deleted_link_click = function(e) {
|
|||
// 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;
|
||||
purged = purged || {};
|
||||
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];
|
||||
}
|
||||
}*/
|
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);
|
||||
},
|
||||
|
||||
|
||||
/* 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 = {
|
||||
'b': 'broadcaster',
|
||||
'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
|
||||
show_modal = function(contents, on_close, width) {
|
||||
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); }
|
||||
},
|
||||
|
||||
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) {
|
||||
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,
|
||||
|
||||
replace_cmd_variables: function(command, user, room, message, args) {
|
||||
|
|
144
style.css
144
style.css
|
@ -18,6 +18,10 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
|
|||
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-collapsed .drawer.closed .js-offers,
|
||||
.ffz-hide-friends-collapsed .drawer.closed .friend-list,
|
||||
|
@ -713,7 +717,10 @@ 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-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-moderation-card .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);
|
||||
}
|
||||
|
@ -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; }
|
||||
|
||||
.app-main.theatre .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 {
|
||||
.ffz-bttv-dark .ffz-ui-popup ul.menu {
|
||||
background-color: #282828;
|
||||
}
|
||||
|
||||
|
@ -1281,8 +1283,7 @@ img.channel_background[src="null"] { display: none; }
|
|||
|
||||
.ffz-moderation-card {
|
||||
border: 2px solid #cbcbcb;
|
||||
max-width: 350px;
|
||||
min-width: 325px;
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
.ember-chat .ffz-moderation-card .close-button {
|
||||
|
@ -1385,14 +1386,12 @@ img.channel_background[src="null"] { display: none; }
|
|||
/*box-shadow: #000 0 0 5px;*/
|
||||
}
|
||||
|
||||
.ffz-moderation-card ul.menu + .interface { border-top: none }
|
||||
|
||||
.ffz-moderation-card .interface:not(:last-of-type) {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.ffz-moderation-card .interface {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.ffz-moderation-card h4.name { display: inline-block; }
|
||||
|
||||
.ffz-moderation-card .info,
|
||||
|
@ -1410,6 +1409,60 @@ img.channel_background[src="null"] { display: none; }
|
|||
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 */
|
||||
|
||||
|
@ -1984,10 +2037,14 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar
|
|||
.chat-history {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
max-height: 200px;
|
||||
max-height: 320px;
|
||||
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: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;
|
||||
}
|
||||
|
||||
.chat-history .timestamp-line {
|
||||
text-align: center;
|
||||
color: #8c8c8c;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.ember-chat .moderation-card .interface.chat-history .chat-line.action {
|
||||
float: none;
|
||||
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 .timestamp:hover {
|
||||
.chat-history .timestamp.ts-action:hover {
|
||||
cursor: pointer;
|
||||
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 {
|
||||
position: relative;
|
||||
min-height: 160px;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
border: 1px solid rgba(0,0,0,0.2);
|
||||
border-top: none;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue