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();
 |