1601 lines
33 KiB
JavaScript
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">×</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&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&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&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 <appeals@4chan.org></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: [ '→', 'Focus next appeal' ],
|
|
37: [ '←', '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();
|