4chan/team/js/appeals.js
2025-04-17 18:12:08 -05:00

1601 lines
33 KiB
JavaScript

var APP = {
PASS_NONE: 0,
PASS_SAME: 1,
PASS_OTHER: 2
};
APP.init = function() {
this.settings = this.getSettings();
this.xhr = {};
this.focusedAppeal = null;
this.clickCommands = {
'refresh': APP.refreshAppeals,
'details' : APP.onDetailsClick,
'contact' : APP.onContactClick,
'accept' : APP.onAcceptClick,
'deny' : APP.onDenyClick,
'edit' : APP.onEditClick,
'edit-deny' : APP.onEditDenyClick,
'edit-cancel': APP.closeEditForm,
'edit-preset': APP.onEditPresetClick,
'toggle-checked': APP.onCheckboxClick,
'show-settings': APP.showSettings,
'save-settings': APP.onSaveSettingsClick,
'focus': APP.onFocusClick,
'shift-panel': APP.shiftPanel,
'toggle-dt': APP.onToggleDTClick
};
this.delayedJob = null;
this.panelStack = null;
this.currentCount = 0;
this.refPreviewNode = null;
this.refs = {};
this.refsHist = {};
this.scrollX = 0;
this.scrollY = 0;
Keybinds.init(this);
Tip.init();
if (localStorage.getItem('dark-theme')) {
$.addClass($.docEl, 'dark-theme');
}
$.on(document, 'click', APP.onClick);
$.on(document, 'DOMContentLoaded', APP.run);
$.on(document, 'mouseover', APP.onMouseOver);
$.on(document, 'mouseout', APP.onMouseOut);
$.on(window, 'beforeunload', APP.onBeforeUnload);
};
APP.run = function() {
var el;
$.off(document, 'DOMContentLoaded', APP.run);
if (el = $.id('js-refs')) {
try {
APP.refs = JSON.parse(el.textContent);
}
catch (err) {
console.log(err);
}
}
if (APP.settings.enableKeybinds) {
if (APP.settings.keyRemap) {
Keybinds.remap(APP.settings.keyRemap);
}
Keybinds.enable();
}
if (localStorage.getItem('dark-theme')) {
$.id('cfg-cb-dt').checked = true;
}
$.on($.id('edit-len-field'), 'keyup', APP.onEditLengthChange);
$.on($.id('edit-len-field'), 'blur', APP.onEditLengthBlur);
APP.currentCount = $.id('items').children.length;
APP.panelStack = $.id('panel-stack');
};
APP.derefer = function(url) {
return 'https://www.4chan.org/derefer?url=' + encodeURIComponent(url);
};
APP.onScrollLocked = function() {
window.scrollTo(APP.scrollX, APP.scrollY);
};
APP.disableScroll = function() {
APP.scrollX = window.scrollX;
APP.scrollY = window.scrollY;
$.on(window, 'scroll', APP.onScrollLocked);
};
APP.enableScroll = function() {
$.off(window, 'scroll', APP.onScrollLocked);
};
// ---
APP.addCommands = function(cmds) {
var key;
for (key in cmds) {
APP.clickCommands[key] = cmds[key];
}
};
APP.onToggleDTClick = function() {
var el = $.id('cfg-cb-dt');
if (el.checked !== $.hasClass($.docEl, 'dark-theme')) {
if (el.checked) {
$.addClass($.docEl, 'dark-theme');
localStorage.setItem('dark-theme', '1');
}
else {
$.removeClass($.docEl, 'dark-theme');
localStorage.removeItem('dark-theme');
}
}
};
APP.parseResponse = function(data) {
try {
return JSON.parse(data);
}
catch (e) {
return {
status: 'error',
message: 'Something went wrong (' + e.toString() + ')'
};
}
};
APP.getItemNode = function(id) {
return $.id('appeal-' + id);
};
APP.getItemUID = function(el) {
var uid = el.id.split('-');
if (uid[0] == 'appeal') {
return uid[1];
}
return null;
};
APP.formatHost = function(host) {
var bits = host.split(/\./);
return (bits.length > 3 ? '*.' : '') + bits.slice(-3).join('.');
};
/**
* Event handlers
*/
APP.onClick = function(e) {
var t, cmd;
if (e.which != 1 || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) {
return;
}
if ((t = e.target) == document) {
return;
}
if ((cmd = t.getAttribute('data-cmd')) && (cmd = APP.clickCommands[cmd])) {
e.stopPropagation();
cmd(t, e);
}
};
APP.onMouseOver = function(e) {
if (e.target.hasAttribute('data-ref')) {
APP.showRefPreview(e.target);
}
};
APP.onMouseOut = function() {
APP.removeRefPreview();
};
APP.disableItem = function(el) {
if ($.hasClass(el, 'disabled')) {
return;
}
$.addClass(el, 'disabled');
APP.currentCount--;
APP.setPageTitle(APP.currentCount);
};
// ---
APP.onCheckboxClick = function(button) {
var klass = 'checked', check = '✔';
if ($.hasClass(button, klass)) {
$.removeClass(button, klass);
button.textContent = '';
}
else {
$.addClass(button, klass);
button.textContent = check;
}
};
APP.setPageTitle = function(count) {
if (count < 0) {
APP.currentCount = count = 0;
}
document.title = 'Appeals (' + count + ')';
};
APP.refreshAppeals = function() {
window.location = window.location;
};
/**
* UI Panels
*/
APP.createPanel = function(id, html, attributes) {
var attr, panel;
panel = document.createElement('div');
panel.id = id + '-panel';
for (attr in attributes) {
panel.setAttribute(attr, attributes[attr]);
}
$.addClass(panel, 'panel');
if (html) {
panel.innerHTML = html;
}
return panel;
};
APP.showPanel = function(id, html, title, attributes) {
var previous, panel, content;
ImageHover.hide();
if (previous = APP.panelStack.lastElementChild) {
previous.style.display = 'none';
}
if (!$.hasClass(APP.panelStack, 'backdrop')) {
$.addClass(APP.panelStack, 'backdrop');
}
if (title) {
html = '<div class="panel-header">'
+ '<span data-cmd="shift-panel" class="button clickbox">&times;</span>'
+ '<h3>' + title + '</h3>'
+ '</div>' + (html || '');
}
panel = APP.createPanel(id, html, attributes);
APP.panelStack.appendChild(panel);
if (content = $.cls('panel-content', panel)[0]) {
content.focus();
content.style.maxHeight =
($.docEl.clientHeight - content.getBoundingClientRect().top * 2) + 'px';
}
//$.addClass($.docEl, 'no-scroll');
APP.disableScroll();
};
APP.isPanelStackEmpty = function() {
var i, panel, nodes;
nodes = APP.panelStack.children;
for (i = 0; panel = nodes[i]; ++i) {
if (!panel.hasAttribute('data-processing')) {
return false;
}
}
return true;
};
APP.getPreviousPanel = function() {
var i, panel, nodes;
nodes = APP.panelStack.children;
for (i = nodes.length - 1; panel = nodes[i]; i--) {
if (panel.style.display == 'none' && !panel.hasAttribute('data-processing')) {
return panel;
}
}
return null;
};
APP.closePanel = function(id) {
var previous, panel;
if (panel = APP.getPanel(id)) {
if (!panel.hasAttribute('data-processing')) {
if (previous = APP.getPreviousPanel()) {
previous.style.display = 'block';
}
else {
$.removeClass(APP.panelStack, 'backdrop');
//$.removeClass($.docEl, 'no-scroll');
APP.enableScroll();
}
}
APP.panelStack.removeChild(panel);
}
};
APP.hidePanel = function(id) {
var previous, panel;
if (panel = APP.getPanel(id)) {
if (previous = APP.getPreviousPanel()) {
previous.style.display = 'block';
}
else {
$.removeClass(APP.panelStack, 'backdrop');
}
panel.style.display = 'none';
panel.setAttribute('data-processing', '1');
}
};
APP.shiftPanel = function() {
var cb, panel = APP.panelStack.lastElementChild;
if (!panel) {
return;
}
if (cb = panel.getAttribute('data-close-cb')) {
APP['close' + cb]();
}
else {
APP.closePanel(APP.getPanelId(panel));
}
};
APP.getPanelId = function(el) {
return el.id.split('-').slice(0, -1).join('-');
};
APP.getPanel = function(id) {
return $.id(id + '-panel');
};
APP.showPanelError = function(id, msg) {
var panel, cnt;
panel = $.id(id + '-panel');
if (!panel) {
return;
}
cnt = $.cls('panel-content', panel)[0];
if (!cnt) {
return;
}
cnt.innerHTML = '<div class="panel-error">' + msg + '</div>';
};
/**
* Notifications
*/
APP.messageTimeout = null;
APP.showMessage = function(msg, type, timeout) {
var el;
APP.hideMessage();
el = document.createElement('div');
el.id = 'feedback';
el.title = 'Dismiss';
el.innerHTML = '<span class="feedback-' + type + '">' + msg + '</span>';
$.on(el, 'click', APP.hideMessage);
document.body.appendChild(el);
if (timeout) {
APP.messageTimeout = setTimeout(APP.hideMessage, timeout);
}
};
APP.hideMessage = function() {
var el = $.id('feedback');
if (el) {
if (APP.messageTimeout) {
clearTimeout(APP.messageTimeout);
APP.messageTimeout = null;
}
$.off(el, 'click', APP.hideMessage);
document.body.removeChild(el);
}
};
APP.error = function(msg) {
APP.showMessage(msg || 'Something went wrong', 'error', 5000);
};
APP.notify = function(msg) {
APP.showMessage(msg, 'notify', 3000);
};
/**
* Settings
*/
APP.settingsList = {
enableKeybinds: [ 'Enable keyboard shortcuts', true ],
};
APP.getSettings = function() {
var key, settings, keyRemap;
if (settings = $.getItem('appeals-settings')) {
settings = JSON.parse(settings);
}
else {
settings = {};
}
for (key in APP.settingsList) {
if (settings[key] === undefined) {
settings[key] = APP.settingsList[key][1];
}
}
if (keyRemap = $.getItem('appeals-keyremap')) {
settings.keyRemap = JSON.parse(keyRemap);
}
return settings;
};
APP.saveSettings = function(settings, keyMap) {
var i, json, clear;
clear = true;
for (i in settings) {
json = JSON.stringify(settings);
$.setItem('appeals-settings', json);
clear = false;
break;
}
if (clear) {
$.removeItem('appeals-settings');
}
if (keyMap) {
json = JSON.stringify(keyMap);
$.setItem('appeals-keyremap', json);
}
else {
$.removeItem('appeals-keyremap');
}
};
APP.onSaveSettingsClick = function() {
var i, el, settings, panel, opts, nodes, keyRemap, keyMap,
fromCode, toCode;
settings = {};
panel = APP.getPanel('settings');
opts = $.cls('option-item', panel);
for (i = 0; el = opts[i]; ++i) {
settings[el.getAttribute('data-key')] = $.hasClass(el, 'checked');
}
Keybinds.disable();
nodes = $.tag('kbd', panel);
keyRemap = [];
keyMap = [];
for (i = 0; el = nodes[i]; ++i) {
if (toCode = +el.getAttribute('data-remap')) {
fromCode = +el.getAttribute('data-id');
if (fromCode != toCode) {
keyMap.push([ fromCode, toCode ]);
}
keyRemap.push([ fromCode, toCode ]);
}
}
if (keyRemap.length > 0) {
if (!Keybinds.remap(keyRemap, true)) {
return;
}
}
if (keyMap.length === 0) {
keyMap = null;
}
if (settings.enableKeybinds) {
Keybinds.enable();
}
APP.settings = settings;
APP.saveSettings(settings, keyMap);
APP.closeSettings();
};
APP.showSettings = function() {
var label, key, html;
if (APP.getPanel('settings')) {
APP.closeSettings();
return;
}
html = '<div class="panel-content"><h4>Options</h4>'
+ '<ul class="options-set">';
for (key in APP.settingsList) {
html += '<li><span data-cmd="toggle-checked"'
+ ' data-key="' + key + '"'
+ ' class="option-item button clickbox'
+ (APP.settings[key] ? ' checked">✔' : '">')
+ '</span> ' + APP.settingsList[key][0] + '</li>';
}
html += '</ul><h4>Keyboard Shortcuts (click key icon to change)</h4><ul class="options-set">';
for (key in Keybinds.labels) {
label = Keybinds.labels[key];
html += '<li><kbd data-cmd="prompt-key" data-id="'
+ key + '"' + (label[2] ? ('data-remap="' + label[2] + '"') : '') + '>'
+ label[0] + '</kbd> '
+ label[1] + '</li>';
}
html += '</div><div class="panel-footer">'
+ '<span data-cmd="save-settings" class="button">Save</span>'
+ '</div>';
APP.showPanel('settings', html, 'Settings');
};
APP.closeSettings = function() {
APP.closePanel('settings');
};
/**
* Focusing
*/
APP.onFocusClick = function(el) {
APP.focusItem(el.parentNode);
};
APP.focusNext = function() {
var el, node;
node = null;
if (el = APP.focusedAppeal) {
while (el = el.nextElementSibling) {
if (!$.hasClass(el, 'disabled') && !$.hasClass(el, 'processing')) {
node = el;
break;
}
}
}
APP.focusItem(node || $.id('items').firstElementChild);
};
APP.focusPrevious = function() {
var el, node;
node = null;
if (el = APP.focusedAppeal) {
while (el = el.previousElementSibling) {
if (!$.hasClass(el, 'disabled') && !$.hasClass(el, 'processing')) {
node = el;
break;
}
}
APP.focusItem(node);
}
};
APP.focusItem = function(el) {
var rect, focusMargin, focused;
focusMargin = 10;
ImageHover.hide();
focused = APP.getFocusedItem();
if (focused) {
$.removeClass(focused, 'focused');
}
if (!el) {
APP.setFocusedItem(null);
return;
}
$.addClass(el, 'focused');
rect = el.getBoundingClientRect();
if (rect.top < 0 || rect.bottom > $.docEl.clientHeight) {
window.scrollBy(0, rect.top - focusMargin);
}
APP.setFocusedItem(el);
};
APP.setFocusedItem = function(el) {
APP.focusedAppeal = el;
};
APP.getFocusedItem = function() {
return APP.focusedAppeal;
};
APP.setDisabled = function(uid) {
var el;
el = $.id('appeal-' + uid);
if (!el) {
return;
}
$.removeClass(el, 'processing');
if ($.hasClass(el, 'disabled')) {
return;
}
APP.currentCount--;
APP.setPageTitle(APP.currentCount);
$.addClass(el, 'disabled');
};
APP.setProcessing = function(uid) {
var el;
if (el = $.id('appeal-' + uid)) {
$.addClass(el, 'processing');
}
};
APP.unsetProcessing = function(uid) {
var el;
if (el = $.id('appeal-' + uid)) {
$.removeClass(el, 'processing');
}
};
APP.onXhrError = function() {
APP.error('Something went wrong');
};
/**
* Ban summary tooltip
*/
APP.showBanTip = function(t) {
var ban_summary, ban_tip;
ban_summary = $.tag('SCRIPT', t.parentNode)[0];
if (!ban_summary) {
return null;
}
ban_summary = JSON.parse(ban_summary.text);
ban_tip = [];
if (ban_summary.recent_bans > 0) {
ban_tip.push(ban_summary.recent_bans + ' ban'
+ $.pluralise(ban_summary.recent_bans));
}
if (ban_summary.recent_warns > 0) {
ban_tip.push(ban_summary.recent_warns + ' warning'
+ $.pluralise(ban_summary.recent_warns));
}
if (ban_summary.recent_days > 0) {
ban_tip.push(ban_summary.recent_days + ' day'
+ $.pluralise(ban_summary.recent_days)
+ ' spent banned');
}
if (!ban_tip.length) {
ban_tip.push('None');
}
return '<strong>Past 12 months history</strong><ul class="ban-tip-cnt"><li>'
+ ban_tip.join('</li><li>') + '</li></ul>';
};
/**
* Details
*/
APP.onDetailsClick = function(button) {
var appeal, id;
appeal = button.parentNode.parentNode;
if (id = APP.getItemUID(appeal)) {
APP.showDetails(id);
}
};
APP.showFocusedDetails = function() {
var id;
if (APP.focusedAppeal) {
id = APP.getItemUID(APP.focusedAppeal);
APP.showDetails(id);
}
};
APP.showDetails = function(id) {
var query, html;
APP.closeDetails();
html = '<div class="panel-content" tabindex="-1" id="ban-details">'
+ '<div class="spinner"></div>'
+ '</div>';
APP.showPanel('details', html, 'History', { 'data-close-cb': 'Details' });
query = '?action=details&id=' + id;
APP.xhr.get = $.xhr('GET', query, {
onload: APP.onDetailsLoaded,
onerror: APP.onDetailsError
});
};
APP.closeDetails = function() {
if (APP.xhr.get) {
APP.xhr.get.abort();
APP.xhr.get = null;
}
APP.closePanel('details');
};
APP.onDetailsLoaded = function() {
var resp;
APP.xhr.get = null;
resp = APP.parseResponse(this.responseText);
if (resp.status === 'success') {
APP.buildDetails(resp.data);
}
else {
APP.showPanelError('details', resp.message);
}
};
APP.onDetailsError = function() {
APP.showPanelError('details', 'Error retrieving details');
APP.xhr.get = null;
};
APP.getPassStatus = function(status) {
if (status == APP.PASS_SAME) {
return '<span data-tip="Same Pass in the appealed ban" class="str-xs d-line pass-status pass-status-'
+ status + '">Pass</span>';
}
if (status == APP.PASS_OTHER) {
return '<span data-tip="Different Pass in the appealed ban" class="str-xs pass-status d-line pass-status-'
+ status + '">Pass</span>';
}
return '';
};
APP.buildDetails = function(data) {
var i, appeal, ban, cnt, reasons, plea_history, html, hist, scope, aip, apass, post;
APP.refsHist = {};
appeal = data.appeal;
cnt = $.id('ban-details');
html = '';
if (appeal.plea_history !== '') {
plea_history = appeal.plea_history;
html += '<h4>Appeals</h4><table class="history-table items-table"><thead>'
+ '<tr>'
+ '<th>Plea</th>'
+ '<th>Date</th>'
+ '<th>Denied By</th>'
+ '</tr>'
+ '</thead><tbody>';
for (i = 0; hist = plea_history[i]; ++i) {
html += '<tr>'
+ '<td class="history-plea">' + hist.plea + '</td>'
+ '<td>' + hist.denied_on + '</td>'
+ '<td>' + hist.denied_by + '</td>'
+ '</tr>';
}
html += '</tbody></table>';
}
if (data.history.length) {
aip = '<a data-tip="Detailed IP ban history" target="_blank" href="bans?action=search&amp;ip='
+ data.ip + '">IP</a>' + ' (' + data.ip + ')';
if (appeal.pass_ban) {
apass = '<a data-tip="Detailed Pass ban history" target="_blank" href="bans?action=search&amp;pass_ref='
+ appeal.no + '">Pass</a>';
}
html += '<h4>Bans for this ' + aip + (appeal.pass_ban ? (' and ' + apass) : '') + '</h4><table class="history-table items-table"><thead>'
+ '<tr>'
+ '<th>Board</th>'
+ '<th>Name / IP</th>'
+ '<th>Reason</th>'
+ '<th>Date</th>'
+ '<th>Banned By</th>'
+ '<th></th>'
+ '</tr>'
+ '</thead><tbody>';
for (i = 0; ban = data.history[i]; ++i) {
if (ban.post_json) {
post = JSON.parse(ban.post_json);
APP.refsHist[ban.no] = post;
}
else {
post = null;
}
reasons = ban.reason.split('<>');
if (ban.ban_end != '0' && (ban.ban_end - ban.ban_start < 1)) {
scope = '<div class="d-line ban-warn">warn</div>';
}
else if (ban.global === '1') {
scope = '<div class="d-line ban-global">global</div>';
}
else {
scope = '';
}
html += '<tr>'
+ '<td class="col-board">'
+ (ban.board ? (post ? (
'<div class="ban-board str-other">/<span data-ref="hist-' + ban.no + '">' + ban.board + '</span>/</div>'
) : ('<div class="ban-board">/' + ban.board + '/</div>')) : '')
+ scope
+ APP.getPassStatus(ban.pass_status)
+ '</td>'
+ '<td class="col-name">'
+ '<div class="user-name">' + ban.name
+ (ban.tripcode ? (' <span class="user-tripcode">' + ban.tripcode + '</span>') : '')
+ '</div><div class="d-line str-xs' + (ban.host !== data.ip ? ' str-st' : '') + '">' + ban.host + '</div>'
+ '</td>'
+ '<td>'
+ '<div class="public-reason">' + reasons[0] + '</div>'
+ (reasons[1] ? ('<div class="private-reason">' + reasons[1] + '</div>') : '')
+ '</td>'
+ '<td class="col-date">'
+ '<div class="ban-start">' + ban.ban_date + '</div><div class="d-line str-xs">' + ban.ban_length + '</div>'
+ '</td>'
+ '<td class="col-name"><div>' + ban.admin + '</div>'
+ (ban.unbannedby ? ('<div data-tip="Unbanned by" class="d-line str-xs str-accept">' + ban.unbannedby + '</div>') : '') + '</td>'
+ '<td class="col-edit">'
+ '<div><a target="_blank" href="/bans?action=update&amp;id=' + ban.no + '">Details</a></div>'
+ (ban.link && ban.post_num !== '0' ? ('<div class="d-line str-xs"><a data-tip="Archive Link" href="' + APP.derefer(ban.link)
+ '" target="_blank">No.' + ban.post_num + '</a></div>') : '')
+ '</td>'
+ '</tr>';
}
html += '</tbody></table>';
}
if (html === '') {
APP.showPanelError('details', 'No previous bans');
return;
}
cnt.innerHTML = html;
};
/**
* Contact
*/
APP.onContactClick = function(button) {
var appeal, id;
appeal = button.parentNode.parentNode;
if (id = APP.getItemUID(appeal)) {
APP.showContact(id, button.getAttribute('data-email'));
}
};
APP.contactFocused = function() {
var id, button;
if (APP.focusedAppeal) {
id = APP.getItemUID(APP.focusedAppeal);
button = $.qs('span[data-cmd="contact"]', APP.focusedAppeal);
if (!button) {
return;
}
APP.showContact(id, button.getAttribute('data-email'));
}
};
APP.showContact = function(id, email) {
var html;
APP.closeContact();
html = '<div class="panel-content" tabindex="-1" id="contact-form">'
+ '<div id="contact-email"></div>'
+ '<div><span class="contact-label">From:</span> 4chan Ban Appeals &lt;appeals@4chan.org&gt;</div>'
+ '<div><span class="contact-label">Subject:</span> Your 4chan Ban Appeal ' + id + '</div>'
+ '<div class="contact-label">Message:</div>'
+ '<textarea tabindex="-1" id="contact-message"></textarea>'
+ '<span data-id="' + id
+ '" class="button btn-other" tabindex="1" id="contact-submit">Send</span>'
+ '</div>';
APP.showPanel('contact', html, 'Contact', { 'data-close-cb': 'Contact' });
$.id('contact-message').focus();
$.id('contact-email').textContent = email;
$.on($.id('contact-submit'), 'click', APP.onSendClick);
};
APP.closeContact = function() {
var el = $.id('contact-submit');
if (el) {
$.off(el, 'click', APP.onSendClick);
APP.closePanel('contact');
}
};
APP.onSendClick = function() {
if ($.id('contact-message').value === '') {
return APP.error('Message is empty');
}
$.xhr('POST', '',
{
onload: APP.onMailSent,
onerror: APP.onXHRError
},
{
action: 'contact',
message: $.id('contact-message').value,
id: this.getAttribute('data-id'),
'_tkn': $.getToken()
}
);
APP.closeContact();
};
APP.onMailSent = function() {
var resp;
resp = APP.parseResponse(this.responseText);
if (resp.status === 'success') {
APP.notify('Email sent');
}
else {
APP.error(resp.message);
}
};
/**
* Accept
*/
APP.acceptFocused = function() {
var uid;
if (APP.focusedAppeal) {
uid = APP.getItemUID(APP.focusedAppeal);
$.addClass(APP.focusedAppeal, 'processing');
APP.addDelayedJob(APP.acceptAppeal, uid);
APP.focusNext();
}
};
APP.onAcceptClick = function(button) {
var appeal, uid;
appeal = button.parentNode.parentNode;
if (uid = APP.getItemUID(appeal)) {
$.addClass(appeal, 'processing');
APP.addDelayedJob(APP.acceptAppeal, uid);
}
};
APP.acceptAppeal = function(id) {
$.xhr('POST', '',
{
onload: APP.onAppealAccepted,
onerror: APP.onXhrError,
id: id
},
{
action: 'accept',
id: id,
'_tkn': $.getToken()
}
);
};
APP.onAppealAccepted = function() {
var resp, el;
resp = APP.parseResponse(this.responseText);
el = APP.getItemNode(this.id);
$.removeClass(el, 'processing');
if (resp.status === 'success') {
APP.disableItem(el);
}
else {
APP.error(resp.message);
}
};
/**
* Deny
*/
APP.denyFocused = function() {
var uid;
if (APP.focusedAppeal) {
uid = APP.getItemUID(APP.focusedAppeal);
$.addClass(APP.focusedAppeal, 'processing');
APP.addDelayedJob(APP.denyAppeal, uid);
APP.focusNext();
}
};
APP.onDenyClick = function(button) {
var appeal, uid, days;
appeal = $.parentTag(button, 'TR');
if (uid = APP.getItemUID(appeal)) {
days = button.getAttribute('data-amend');
$.addClass(appeal, 'processing');
APP.addDelayedJob(APP.denyAppeal, uid, days);
}
};
APP.denyAppeal = function(id, days) {
var params = {
action: 'deny',
id: id,
'_tkn': $.getToken()
};
if (days) {
params.days = days;
}
$.xhr('POST', '',
{
onload: APP.onAppealDenied,
onerror: APP.onXhrError,
id: id
},
params
);
};
APP.onAppealDenied = function() {
var resp, el;
resp = APP.parseResponse(this.responseText);
el = APP.getItemNode(this.id);
$.removeClass(el, 'processing');
if (resp.status === 'success') {
APP.disableItem(el);
}
else {
APP.error(resp.message);
}
};
/**
* Edit
*/
APP.closeEditForm = function() {
$.id('edit-len-field').value = '';
$.id('edit-id-field').value = '';
$.addClass($.id('edit-form-cnt'), 'hidden');
};
APP.getDaysDuration = function(start, end) {
var delta = end - start;
if (delta < 0) {
return 0;
}
return 0 | (delta / 86400);
};
APP.onEditClick = function(button) {
var el, rect, appeal, uid;
APP.closeEditForm();
appeal = button.parentNode.parentNode;
uid = APP.getItemUID(appeal);
$.id('edit-id-field').value = uid;
$.id('edit-len-field').value = appeal.getAttribute('data-len');
$.id('edit-len-field').setAttribute('data-orig', appeal.getAttribute('data-len'));
$.id('js-eb-fa').textContent = $.ago(button.getAttribute('data-start'));
$.id('js-eb-ne').setAttribute('data-start', button.getAttribute('data-start'));
APP.onEditLengthChange();
el = $.id('edit-form-cnt');
$.removeClass(el, 'hidden');
rect = button.getBoundingClientRect();
el.style.top = rect.top - el.offsetHeight + window.pageYOffset - 10 + 'px';
el.style.right = ($.docEl.clientWidth - rect.right) + 'px';
if (el.offsetTop < window.pageYOffset) {
el.scrollIntoView(true);
}
};
APP.onEditPresetClick = function(btn) {
$.id('edit-len-field').value = btn.getAttribute('data-len');
APP.onEditLengthChange();
};
APP.onEditLengthChange = function() {
var el, input, days, start, end;
el = $.id('js-eb-ne');
input = $.id('edit-len-field');
if (input.value === '') {
el.textContent = '0';
return;
}
if (input.value[0] == '+') {
days = +input.value.slice(1);
APP.editDenyCheckChanges(days);
el.textContent = days;
return;
}
start = +el.getAttribute('data-start');
end = start + (+input.value * 86400);
start = 0 | (Date.now() / 1000);
days = APP.getDaysDuration(start, end);
APP.editDenyCheckChanges(days, input.value, input.getAttribute('data-orig'));
el.textContent = days;
};
APP.onEditLengthBlur = function() {
var el, input;
el = $.id('js-eb-ne');
input = $.id('edit-len-field');
if (input.value === '') {
input.value = input.getAttribute('data-orig');
APP.onEditLengthChange();
}
};
APP.editDenyCheckChanges = function(days, newDate, origDate) {
var el = $.id('js-eb-ne');
$.removeClass(el, 'val-deny');
$.removeClass(el, 'val-changed');
if (days === undefined) {
return;
}
if (days < 1) {
$.addClass(el, 'val-deny');
}
if (newDate === undefined || newDate != origDate) {
$.addClass(el, 'val-changed');
}
};
APP.onEditDenyClick = function() {
var id, len, appeal;
id = +$.id('edit-id-field').value;
len = $.id('edit-len-field').value;
appeal = APP.getItemNode(id);
if (!appeal) {
APP.closeEditForm();
return;
}
if ((+len) === 0) {
alert('Invalid length.');
return;
}
$.addClass(appeal, 'processing');
APP.closeEditForm();
APP.denyAppeal(id, len);
};
/**
* Delayed jobs
*/
APP.delayedJobTimeout = 3000;
APP.addDelayedJob = function() {
var args;
if (APP.delayedJob) {
APP.runDelayedJob();
}
args = Array.prototype.slice.call(arguments);
APP.delayedJob = {
fn: args.shift(),
args: args,
timeOut: setTimeout(APP.runDelayedJob, APP.delayedJobTimeout)
};
};
APP.runDelayedJob = function() {
var job = APP.delayedJob;
clearTimeout(job.timeOut);
job.fn.apply(this, job.args);
APP.delayedJob = null;
};
APP.cancelDelayedJob = function() {
var args, item;
if (!APP.delayedJob) {
return;
}
args = APP.delayedJob.args;
clearTimeout(APP.delayedJob.timeOut);
item = APP.getItemNode(args[0]);
$.removeClass(item, 'processing');
if (APP.getFocusedItem()) {
APP.focusItem(item);
}
APP.delayedJob = null;
};
/**
* Ref previews
*/
APP.removeRefPreview = function() {
if (APP.refPreviewNode) {
document.body.removeChild(APP.refPreviewNode);
APP.refPreviewNode = null;
}
};
APP.showRefPreview = function(tgt) {
var el, refType, refId, html;
APP.removeRefPreview();
[refType, refId] = tgt.getAttribute('data-ref').split('-');
el = $.el('div');
el.className = 'ref-preview';
if (refType === 'bid' || refType === 'md5') {
html = APP.buildBanPreviewHTML(refId);
}
else if (refType === 'hist') {
html = APP.buildHistPreviewHTML(refId);
}
else if (refType === 'fid') {
html = APP.buildFilterPreviewHTML(refId);
}
if (!html) {
return false;
}
el.innerHTML = html;
document.body.appendChild(el);
APP.refPreviewNode = el;
APP.alignQuotePreview(el, tgt);
};
APP.buildBanPreviewHTML = function(refId) {
var html, post, ua;
post = (APP.refs.bid && APP.refs.bid[refId]) || (APP.refs.md5 && APP.refs.md5[refId]);
if (!post) {
return null;
}
html = '<div class="ref-hdr">';
html += `<div class="ref-ago">${$.ago(+post.created_on)} by ${post.created_by}</div>`;
html += `<div class="ref-pub-reason">${post.public_reason}</div>`;
if (post.private_reason) {
html += `<div class="private-reason">(${post.private_reason})</div>`;
}
html += '</div><div class="ref-user">';
if (post.board) {
html += `<span class="ban-board">/${post.board}/</span> `;
}
if (post.name) {
html += `<span class="user-name">${post.name}`;
if (post.tripcode) {
html += `<span class="user-tripcode">${post.tripcode}</span>`;
}
html += '</span>';
}
if (post.ua) {
ua = `<span class="sxt" data-tip="Browser ID">(${post.ua})</span>`;
}
else {
ua = '';
}
html += `<div class="user-net-cnt">
<div class="user-host">${post.ip}<span class="user-country">(${post.geo_loc})</span>${ua}</div>
<div class="user-reverse">${post.reverse}</div>
<div class="user-asn">${post.asn_name}</div>
</div></div><div class="ban-post">`;
if (post.sub !== undefined) {
html += `<div class="post-subject">${post.sub}</div>`;
}
if (post.filename) {
html += `<div class="filename">${post.filename}</div>`;
}
if (post.com !== undefined) {
html += `<div class="ban-comment">${post.com}</div>`;
}
html += '</div>';
return html;
};
APP.buildHistPreviewHTML = function(refId) {
var html, post;
post = APP.refsHist[refId];
if (!post) {
return null;
}
html = '<div class="ref-user">';
if (post.name) {
html += `<span class="user-name">${post.name}`;
if (post.tripcode) {
html += `<span class="user-tripcode">${post.tripcode}</span>`;
}
html += '</span>';
}
html += '</div><div class="ban-post">';
if (post.sub !== undefined) {
html += `<div class="post-subject">${post.sub}</div>`;
}
if (post.filename) {
html += `<div class="filename">${post.filename}${post.ext}</div>`;
}
if (post.com !== undefined) {
html += `<div class="ban-comment">${post.com}</div>`;
}
html += '</div>';
return html;
};
APP.buildFilterPreviewHTML = function(refId) {
var html, filter;
filter = APP.refs.fid && APP.refs.fid[refId];
if (!filter) {
return null;
}
html = `<div class="ref-hdr"><div class="ref-description">${filter.description}</div>
<div class="ref-pattern">${$.escapeHTML(filter.pattern)}</div></div>`;
return html;
};
APP.alignQuotePreview = function(el, tgt) {
var elAABB, tgtAABB, pad, left, top;
pad = 6;
elAABB = el.getBoundingClientRect();
tgtAABB = tgt.getBoundingClientRect();
left = tgtAABB.right + pad;
top = tgtAABB.top + window.pageYOffset;
if (left + elAABB.width + pad > $.docEl.clientWidth) {
left = tgtAABB.left - pad - elAABB.width;
}
if (tgtAABB.top + elAABB.height > $.docEl.clientHeight) {
top = tgtAABB.top + ($.docEl.clientHeight - tgtAABB.top - elAABB.height - pad)
+ window.pageYOffset;
}
el.style.left = left + 'px';
el.style.top = top + 'px';
};
/**
* Keybinds
*/
Keybinds.map = {
// Esc
27: APP.shiftPanel,
// Left arrow
37: APP.focusPrevious,
// Right arrow
39: APP.focusNext,
// A
65: APP.acceptFocused,
// C
67: APP.contactFocused,
// D
68: APP.denyFocused,
// H
72: APP.showFocusedDetails,
// R
82: APP.refreshAppeals,
// U
85: APP.cancelDelayedJob,
};
Keybinds.labels = {
82: [ 'R', 'Refresh page' ],
39: [ '&#8594;', 'Focus next appeal' ],
37: [ '&#8592;', 'Focus previous appeal' ],
27: [ 'Esc', 'Close panel' ],
65: [ 'A', 'Accept focused' ],
68: [ 'D', 'Deny focused' ],
67: [ 'C', 'Contact focused' ],
72: [ 'H', 'Show ban history' ],
85: [ 'U', 'Undo last action' ]
};
APP.closeKeyPrompt = function() {
$.off(document, 'keydown', Keybinds.resolvePrompt);
if (APP.settings.enableKeybinds) {
Keybinds.enable();
}
APP.closePanel('key-prompt');
};
/**
* Init
*/
APP.init();