Tag:
%g
Literal %
: %%
Quick Reply Personas One item per line. Items will be added in the relevant input's auto-completion list. Password items will always be used, since there is no password input. Lines starting with a #
will be ignored.
You can use these settings with each item, separate them with semicolons:Possible items are: name
, options
(or equivalently email
), subject
and password
. Wrap values of items with quotes, like this: options:"sage"
. Force values as defaults with the always
keyword, for example: options:"sage";always
. Select specific boards for an item, separated with commas, for example: options:"sage";boards:jp;always
. Unread Favicon is disabled. ferongr xat- 4chanJS Mayhem Original Metro Thread Updater is disabled. Interval: seconds
Custom Cooldown Time Seconds:
Custom CSSFor more information about customizing 4chan X's CSS, see the
styling guide .
Apply CSS Javascript Whitelist Sources from which Javascript is allowed to be loaded by
Content Security Policy .
Lines starting with a
#
will be ignored.
Known Banners List of known banners, used for click-to-change feature.
"});
+ ref = $$('.warning', section);
+ for (j = 0, len = ref.length; j < len; j++) {
+ warning = ref[j];
+ warning.hidden = Conf[warning.dataset.feature];
+ }
+ inputs = $.dict();
+ ref1 = $$('[name]', section);
+ for (k = 0, len1 = ref1.length; k < len1; k++) {
+ input = ref1[k];
+ inputs[input.name] = input;
+ }
+ $.on(inputs['archiveLists'], 'change', function() {
+ $.set('lastarchivecheck', 0);
+ Conf['lastarchivecheck'] = 0;
+ return $.id('lastarchivecheck').textContent = 'never';
+ });
+ items = $.dict();
+ for (name in inputs) {
+ input = inputs[name];
+ if (!(name !== 'Interval' && name !== 'Custom CSS')) {
+ continue;
+ }
+ items[name] = Conf[name];
+ event = (input.nodeName === 'SELECT' || ((ref2 = input.type) === 'checkbox' || ref2 === 'radio') || (input.nodeName === 'TEXTAREA' && !(name in Settings))) ? 'change' : 'input';
+ $.on(input, event, $.cb[input.type === 'checkbox' ? 'checked' : 'value']);
+ if (name in Settings) {
+ $.on(input, event, Settings[name]);
+ }
+ }
+ $.get(items, function(items) {
+ var key, val;
+ for (key in items) {
+ val = items[key];
+ input = inputs[key];
+ input[input.type === 'checkbox' ? 'checked' : 'value'] = val;
+ input.hidden = false;
+ if (key in Settings) {
+ Settings[key].call(input);
+ }
+ }
+ });
+ listImageHost = $.id('list-fourchanImageHost');
+ ref3 = ImageHost.suggestions;
+ for (l = 0, len2 = ref3.length; l < len2; l++) {
+ textContent = ref3[l];
+ $.add(listImageHost, $.el('option', {
+ textContent: textContent
+ }));
+ }
+ interval = inputs['Interval'];
+ customCSS = inputs['Custom CSS'];
+ applyCSS = $('#apply-css', section);
+ interval.value = Conf['Interval'];
+ customCSS.checked = Conf['Custom CSS'];
+ inputs['usercss'].disabled = !Conf['Custom CSS'];
+ applyCSS.disabled = !Conf['Custom CSS'];
+ $.on(interval, 'change', ThreadUpdater.cb.interval);
+ $.on(customCSS, 'change', Settings.togglecss);
+ $.on(applyCSS, 'click', function() {
+ return CustomCSS.update();
+ });
+ itemsArchive = $.dict();
+ ref4 = ['archives', 'selectedArchives', 'lastarchivecheck'];
+ for (m = 0, len3 = ref4.length; m < len3; m++) {
+ name = ref4[m];
+ itemsArchive[name] = Conf[name];
+ }
+ $.get(itemsArchive, function(itemsArchive) {
+ $.extend(Conf, itemsArchive);
+ Redirect.selectArchives();
+ return Settings.addArchiveTable(section);
+ });
+ boardSelect = $('#archive-board-select', section);
+ table = $('#archive-table', section);
+ updateArchives = $('#update-archives', section);
+ $.on(boardSelect, 'change', function() {
+ $('tbody > :not([hidden])', table).hidden = true;
+ return $("tbody > ." + this.value, table).hidden = false;
+ });
+ return $.on(updateArchives, 'click', function() {
+ return Redirect.update(function() {
+ return Settings.addArchiveTable(section);
+ });
+ });
+ },
+ addArchiveTable: function(section) {
+ var archBoards, archive, boardID, boardOptions, boardSelect, boards, data, files, id, item, j, k, l, len, len1, len2, len3, m, name, o, ref, ref1, ref2, ref3, ref4, row, rows, select, software, table, tbody, type, uid;
+ $('#lastarchivecheck', section).textContent = Conf['lastarchivecheck'] === 0 ? 'never' : new Date(Conf['lastarchivecheck']).toLocaleString();
+ boardSelect = $('#archive-board-select', section);
+ table = $('#archive-table', section);
+ tbody = $('tbody', section);
+ $.rmAll(boardSelect);
+ $.rmAll(tbody);
+ archBoards = $.dict();
+ ref = Conf['archives'];
+ for (j = 0, len = ref.length; j < len; j++) {
+ ref1 = ref[j], uid = ref1.uid, name = ref1.name, boards = ref1.boards, files = ref1.files, software = ref1.software;
+ if (software !== 'fuuka' && software !== 'foolfuuka') {
+ continue;
+ }
+ for (k = 0, len1 = boards.length; k < len1; k++) {
+ boardID = boards[k];
+ o = archBoards[boardID] || (archBoards[boardID] = {
+ thread: [],
+ post: [],
+ file: []
+ });
+ archive = [uid != null ? uid : name, name];
+ o.thread.push(archive);
+ if (software === 'foolfuuka') {
+ o.post.push(archive);
+ }
+ if (indexOf.call(files, boardID) >= 0) {
+ o.file.push(archive);
+ }
+ }
+ }
+ rows = [];
+ boardOptions = [];
+ ref2 = Object.keys(archBoards).sort();
+ for (l = 0, len2 = ref2.length; l < len2; l++) {
+ boardID = ref2[l];
+ row = $.el('tr', {
+ className: "board-" + boardID
+ });
+ row.hidden = boardID !== g.BOARD.ID;
+ boardOptions.push($.el('option', {
+ textContent: "/" + boardID + "/",
+ value: "board-" + boardID,
+ selected: boardID === g.BOARD.ID
+ }));
+ o = archBoards[boardID];
+ ref3 = ['thread', 'post', 'file'];
+ for (m = 0, len3 = ref3.length; m < len3; m++) {
+ item = ref3[m];
+ $.add(row, Settings.addArchiveCell(boardID, o, item));
+ }
+ rows.push(row);
+ }
+ if (rows.length === 0) {
+ boardSelect.hidden = table.hidden = true;
+ return;
+ }
+ boardSelect.hidden = table.hidden = false;
+ if (!(g.BOARD.ID in archBoards)) {
+ rows[0].hidden = false;
+ }
+ $.add(boardSelect, boardOptions);
+ $.add(tbody, rows);
+ ref4 = Conf['selectedArchives'];
+ for (boardID in ref4) {
+ data = ref4[boardID];
+ for (type in data) {
+ id = data[type];
+ if ((select = $("select[data-boardid='" + boardID + "'][data-type='" + type + "']", tbody))) {
+ select.value = JSON.stringify(id);
+ if (!select.value) {
+ select.value = select.firstChild.value;
+ }
+ }
+ }
+ }
+ },
+ addArchiveCell: function(boardID, data, type) {
+ var archive, i, length, options, select, td;
+ length = data[type].length;
+ td = $.el('td', {
+ className: 'archive-cell'
+ });
+ if (!length) {
+ td.textContent = '--';
+ return td;
+ }
+ options = [];
+ i = 0;
+ while (i < length) {
+ archive = data[type][i++];
+ options.push($.el('option', {
+ value: JSON.stringify(archive[0]),
+ textContent: archive[1]
+ }));
+ }
+ $.extend(td, {innerHTML: "
"});
+ select = td.firstElementChild;
+ if (!(select.disabled = length === 1)) {
+ select.setAttribute('data-boardid', boardID);
+ select.setAttribute('data-type', type);
+ $.on(select, 'change', Settings.saveSelectedArchive);
+ }
+ $.add(select, options);
+ return td;
+ },
+ saveSelectedArchive: function() {
+ return $.get('selectedArchives', Conf['selectedArchives'], (function(_this) {
+ return function(arg) {
+ var name1, selectedArchives;
+ selectedArchives = arg.selectedArchives;
+ (selectedArchives[name1 = _this.dataset.boardid] || (selectedArchives[name1] = $.dict()))[_this.dataset.type] = JSON.parse(_this.value);
+ $.set('selectedArchives', selectedArchives);
+ Conf['selectedArchives'] = selectedArchives;
+ return Redirect.selectArchives();
+ };
+ })(this));
+ },
+ boardnav: function() {
+ return Header.generateBoardList(this.value);
+ },
+ time: function() {
+ return this.nextElementSibling.textContent = Time.format(this.value, new Date());
+ },
+ timeLocale: function() {
+ return Settings.time.call($('[name=time]', Settings.dialog));
+ },
+ backlink: function() {
+ return this.nextElementSibling.textContent = this.value.replace(/%(?:id|%)/g, function(x) {
+ return {
+ '%id': '123456789',
+ '%%': '%'
+ }[x];
+ });
+ },
+ fileInfo: function() {
+ var data;
+ data = {
+ isReply: true,
+ file: {
+ url: "//" + (ImageHost.host()) + "/g/1334437723720.jpg",
+ name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg',
+ size: '276 KB',
+ sizeInBytes: 276 * 1024,
+ dimensions: '1280x720',
+ isImage: true,
+ isVideo: false,
+ isSpoiler: true,
+ tag: 'Loop'
+ }
+ };
+ return FileInfo.format(this.value, data, this.nextElementSibling);
+ },
+ favicon: function() {
+ var f, i, icon, img, j, len, ref;
+ Favicon["switch"]();
+ if (g.VIEW === 'thread' && Conf['Unread Favicon']) {
+ Unread.update();
+ }
+ img = this.nextElementSibling.children;
+ f = Favicon;
+ ref = [f.SFW, f.unreadSFW, f.unreadSFWY, f.NSFW, f.unreadNSFW, f.unreadNSFWY, f.dead, f.unreadDead, f.unreadDeadY];
+ for (i = j = 0, len = ref.length; j < len; i = ++j) {
+ icon = ref[i];
+ if (!img[i]) {
+ $.add(this.nextElementSibling, $.el('img'));
+ }
+ img[i].src = icon;
+ }
+ },
+ togglecss: function() {
+ if ($('textarea[name=usercss]', $.x('ancestor::fieldset[1]', this)).disabled = $.id('apply-css').disabled = !this.checked) {
+ CustomCSS.rmStyle();
+ } else {
+ CustomCSS.addStyle();
+ }
+ return $.cb.checked.call(this);
+ },
+ keybinds: function(section) {
+ var arr, input, inputs, items, key, ref, tbody, tr;
+ $.extend(section, {innerHTML: "
Keybinds
are disabled.
Allowed keys: a-z , 0-9 , Ctrl , Shift , Alt , Meta , Enter , Esc , Up , Down , Right , Left .
Press Backspace to disable a keybind.
"});
+ $('.warning', section).hidden = Conf['Keybinds'];
+ tbody = $('tbody', section);
+ items = $.dict();
+ inputs = $.dict();
+ ref = Config.hotkeys;
+ for (key in ref) {
+ arr = ref[key];
+ tr = $.el('tr', {innerHTML: "
" + E(arr[1]) + " "});
+ input = $('input', tr);
+ input.name = key;
+ input.spellcheck = false;
+ items[key] = Conf[key];
+ inputs[key] = input;
+ $.on(input, 'keydown', Settings.keybind);
+ $.add(tbody, tr);
+ }
+ return $.get(items, function(items) {
+ var val;
+ for (key in items) {
+ val = items[key];
+ inputs[key].value = val;
+ }
+ });
+ },
+ keybind: function(e) {
+ var key;
+ if (e.keyCode === 9) {
+ return;
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ if ((key = Keybinds.keyCode(e)) == null) {
+ return;
+ }
+ this.value = key;
+ return $.cb.value.call(this);
+ }
+ };
+
+ return Settings;
+
+}).call(this);
+
+Test = (function() {
+ return Test;
+
+}).call(this);
+
+UI = (function() {
+ var Menu, UI, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ slice = [].slice;
+
+ dialog = function(id, properties) {
+ var child, el, i, len, move, ref;
+ el = $.el('div', {
+ className: 'dialog',
+ id: id
+ });
+ $.extend(el, properties);
+ el.style.cssText = Conf[id + ".position"];
+ move = $('.move', el);
+ $.on(move, 'touchstart mousedown', dragstart);
+ ref = move.children;
+ for (i = 0, len = ref.length; i < len; i++) {
+ child = ref[i];
+ if (!child.tagName) {
+ continue;
+ }
+ $.on(child, 'touchstart mousedown', function(e) {
+ return e.stopPropagation();
+ });
+ }
+ return el;
+ };
+
+ Menu = (function() {
+ var currentMenu, lastToggledButton;
+
+ currentMenu = null;
+
+ lastToggledButton = null;
+
+ function Menu(type) {
+ this.type = type;
+ this.addEntry = bind(this.addEntry, this);
+ this.onFocus = bind(this.onFocus, this);
+ this.keybinds = bind(this.keybinds, this);
+ this.close = bind(this.close, this);
+ this.setPosition = bind(this.setPosition, this);
+ $.on(d, 'AddMenuEntry', (function(_this) {
+ return function(arg) {
+ var detail;
+ detail = arg.detail;
+ if (detail.type !== _this.type) {
+ return;
+ }
+ delete detail.open;
+ return _this.addEntry(detail);
+ };
+ })(this));
+ this.entries = [];
+ }
+
+ Menu.prototype.makeMenu = function() {
+ var menu;
+ menu = $.el('div', {
+ className: 'dialog',
+ id: 'menu',
+ tabIndex: 0
+ });
+ menu.dataset.type = this.type;
+ $.on(menu, 'click', function(e) {
+ return e.stopPropagation();
+ });
+ $.on(menu, 'keydown', this.keybinds);
+ return menu;
+ };
+
+ Menu.prototype.toggle = function(e, button, data) {
+ var previousButton;
+ e.preventDefault();
+ e.stopPropagation();
+ if (currentMenu) {
+ previousButton = lastToggledButton;
+ currentMenu.close();
+ if (previousButton === button) {
+ return;
+ }
+ }
+ if (!this.entries.length) {
+ return;
+ }
+ return this.open(button, data);
+ };
+
+ Menu.prototype.open = function(button, data) {
+ var entry, i, len, menu, ref;
+ menu = this.menu = this.makeMenu();
+ currentMenu = this;
+ lastToggledButton = button;
+ this.entries.sort(function(first, second) {
+ return first.order - second.order;
+ });
+ ref = this.entries;
+ for (i = 0, len = ref.length; i < len; i++) {
+ entry = ref[i];
+ this.insertEntry(entry, menu, data);
+ }
+ $.addClass(lastToggledButton, 'active');
+ $.on(d, 'click CloseMenu', this.close);
+ $.on(d, 'scroll', this.setPosition);
+ $.on(window, 'resize', this.setPosition);
+ $.after(button, menu);
+ this.setPosition();
+ entry = $('.entry', menu);
+ this.focus(entry);
+ return menu.focus();
+ };
+
+ Menu.prototype.setPosition = function() {
+ var bLeft, bRect, bTop, bottom, cHeight, cWidth, left, mRect, ref, ref1, right, top;
+ mRect = this.menu.getBoundingClientRect();
+ bRect = lastToggledButton.getBoundingClientRect();
+ bTop = window.scrollY + bRect.top;
+ bLeft = window.scrollX + bRect.left;
+ cHeight = doc.clientHeight;
+ cWidth = doc.clientWidth;
+ ref = bRect.top + bRect.height + mRect.height < cHeight ? [bRect.bottom + "px", ''] : ['', (cHeight - bRect.top) + "px"], top = ref[0], bottom = ref[1];
+ ref1 = bRect.left + mRect.width < cWidth ? [bRect.left + "px", ''] : ['', (cWidth - bRect.right) + "px"], left = ref1[0], right = ref1[1];
+ $.extend(this.menu.style, {
+ top: top,
+ right: right,
+ bottom: bottom,
+ left: left
+ });
+ return this.menu.classList.toggle('left', right);
+ };
+
+ Menu.prototype.insertEntry = function(entry, parent, data) {
+ var err, i, len, ref, subEntry, submenu;
+ if (typeof entry.open === 'function') {
+ try {
+ if (!entry.open(data)) {
+ return;
+ }
+ } catch (error) {
+ err = error;
+ Main.handleErrors({
+ message: "Error in building the " + this.type + " menu.",
+ error: err
+ });
+ return;
+ }
+ }
+ $.add(parent, entry.el);
+ if (!entry.subEntries) {
+ return;
+ }
+ if (submenu = $('.submenu', entry.el)) {
+ $.rm(submenu);
+ }
+ submenu = $.el('div', {
+ className: 'dialog submenu'
+ });
+ ref = entry.subEntries;
+ for (i = 0, len = ref.length; i < len; i++) {
+ subEntry = ref[i];
+ this.insertEntry(subEntry, submenu, data);
+ }
+ $.add(entry.el, submenu);
+ };
+
+ Menu.prototype.close = function() {
+ $.rm(this.menu);
+ delete this.menu;
+ $.rmClass(lastToggledButton, 'active');
+ currentMenu = null;
+ lastToggledButton = null;
+ $.off(d, 'click scroll CloseMenu', this.close);
+ $.off(d, 'scroll', this.setPosition);
+ return $.off(window, 'resize', this.setPosition);
+ };
+
+ Menu.prototype.findNextEntry = function(entry, direction) {
+ var entries;
+ entries = slice.call(entry.parentNode.children);
+ entries.sort(function(first, second) {
+ return first.style.order - second.style.order;
+ });
+ return entries[entries.indexOf(entry) + direction];
+ };
+
+ Menu.prototype.keybinds = function(e) {
+ var entry, next, nextPrev, subEntry, submenu;
+ entry = $('.focused', this.menu);
+ while (subEntry = $('.focused', entry)) {
+ entry = subEntry;
+ }
+ switch (e.keyCode) {
+ case 27:
+ lastToggledButton.focus();
+ this.close();
+ break;
+ case 13:
+ case 32:
+ entry.click();
+ break;
+ case 38:
+ if (next = this.findNextEntry(entry, -1)) {
+ this.focus(next);
+ }
+ break;
+ case 40:
+ if (next = this.findNextEntry(entry, +1)) {
+ this.focus(next);
+ }
+ break;
+ case 39:
+ if ((submenu = $('.submenu', entry)) && (next = submenu.firstElementChild)) {
+ while (nextPrev = this.findNextEntry(next, -1)) {
+ next = nextPrev;
+ }
+ this.focus(next);
+ }
+ break;
+ case 37:
+ if (next = $.x('parent::*[contains(@class,"submenu")]/parent::*', entry)) {
+ this.focus(next);
+ }
+ break;
+ default:
+ return;
+ }
+ e.preventDefault();
+ return e.stopPropagation();
+ };
+
+ Menu.prototype.onFocus = function(e) {
+ e.stopPropagation();
+ return this.focus(e.target);
+ };
+
+ Menu.prototype.focus = function(entry) {
+ var bottom, cHeight, cWidth, eRect, focused, i, left, len, ref, ref1, ref2, right, sRect, style, submenu, top;
+ while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) {
+ $.rmClass(focused, 'focused');
+ }
+ ref = $$('.focused', entry);
+ for (i = 0, len = ref.length; i < len; i++) {
+ focused = ref[i];
+ $.rmClass(focused, 'focused');
+ }
+ $.addClass(entry, 'focused');
+ if (!(submenu = $('.submenu', entry))) {
+ return;
+ }
+ sRect = submenu.getBoundingClientRect();
+ eRect = entry.getBoundingClientRect();
+ cHeight = doc.clientHeight;
+ cWidth = doc.clientWidth;
+ ref1 = eRect.top + sRect.height < cHeight ? ['0px', 'auto'] : ['auto', '0px'], top = ref1[0], bottom = ref1[1];
+ ref2 = eRect.right + sRect.width < cWidth - 150 ? ['100%', 'auto'] : ['auto', '100%'], left = ref2[0], right = ref2[1];
+ style = submenu.style;
+ style.top = top;
+ style.bottom = bottom;
+ style.left = left;
+ return style.right = right;
+ };
+
+ Menu.prototype.addEntry = function(entry) {
+ this.parseEntry(entry);
+ return this.entries.push(entry);
+ };
+
+ Menu.prototype.parseEntry = function(entry) {
+ var el, i, len, subEntries, subEntry;
+ el = entry.el, subEntries = entry.subEntries;
+ $.addClass(el, 'entry');
+ $.on(el, 'focus mouseover', this.onFocus);
+ el.style.order = entry.order || 100;
+ if (!subEntries) {
+ return;
+ }
+ $.addClass(el, 'has-submenu');
+ for (i = 0, len = subEntries.length; i < len; i++) {
+ subEntry = subEntries[i];
+ this.parseEntry(subEntry);
+ }
+ };
+
+ return Menu;
+
+ })();
+
+ dragstart = function(e) {
+ var el, isTouching, o, rect, ref, screenHeight, screenWidth;
+ if (e.type === 'mousedown' && e.button !== 0) {
+ return;
+ }
+ e.preventDefault();
+ if (isTouching = e.type === 'touchstart') {
+ e = e.changedTouches[e.changedTouches.length - 1];
+ }
+ el = $.x('ancestor::div[contains(@class,"dialog")][1]', this);
+ rect = el.getBoundingClientRect();
+ screenHeight = doc.clientHeight;
+ screenWidth = doc.clientWidth;
+ o = {
+ id: el.id,
+ style: el.style,
+ dx: e.clientX - rect.left,
+ dy: e.clientY - rect.top,
+ height: screenHeight - rect.height,
+ width: screenWidth - rect.width,
+ screenHeight: screenHeight,
+ screenWidth: screenWidth,
+ isTouching: isTouching
+ };
+ ref = Conf['Header auto-hide'] || !Conf['Fixed Header'] ? [0, 0] : Conf['Bottom Header'] ? [0, Header.bar.getBoundingClientRect().height] : [Header.bar.getBoundingClientRect().height, 0], o.topBorder = ref[0], o.bottomBorder = ref[1];
+ if (isTouching) {
+ o.identifier = e.identifier;
+ o.move = touchmove.bind(o);
+ o.up = touchend.bind(o);
+ $.on(d, 'touchmove', o.move);
+ return $.on(d, 'touchend touchcancel', o.up);
+ } else {
+ o.move = drag.bind(o);
+ o.up = dragend.bind(o);
+ $.on(d, 'mousemove', o.move);
+ return $.on(d, 'mouseup', o.up);
+ }
+ };
+
+ touchmove = function(e) {
+ var i, len, ref, touch;
+ ref = e.changedTouches;
+ for (i = 0, len = ref.length; i < len; i++) {
+ touch = ref[i];
+ if (touch.identifier === this.identifier) {
+ drag.call(this, touch);
+ return;
+ }
+ }
+ };
+
+ drag = function(e) {
+ var bottom, clientX, clientY, left, right, style, top;
+ clientX = e.clientX, clientY = e.clientY;
+ left = clientX - this.dx;
+ left = left < 10 ? 0 : this.width - left < 10 ? '' : left / this.screenWidth * 100 + '%';
+ top = clientY - this.dy;
+ top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? '' : top / this.screenHeight * 100 + '%';
+ right = left === '' ? 0 : '';
+ bottom = top === '' ? this.bottomBorder + 'px' : '';
+ style = this.style;
+ style.left = left;
+ style.right = right;
+ style.top = top;
+ return style.bottom = bottom;
+ };
+
+ touchend = function(e) {
+ var i, len, ref, touch;
+ ref = e.changedTouches;
+ for (i = 0, len = ref.length; i < len; i++) {
+ touch = ref[i];
+ if (touch.identifier === this.identifier) {
+ dragend.call(this);
+ return;
+ }
+ }
+ };
+
+ dragend = function() {
+ if (this.isTouching) {
+ $.off(d, 'touchmove', this.move);
+ $.off(d, 'touchend touchcancel', this.up);
+ } else {
+ $.off(d, 'mousemove', this.move);
+ $.off(d, 'mouseup', this.up);
+ }
+ return $.set(this.id + ".position", this.style.cssText);
+ };
+
+ hoverstart = function(arg) {
+ var cb, el, endEvents, height, latestEvent, noRemove, o, rect, ref, root, width;
+ root = arg.root, el = arg.el, latestEvent = arg.latestEvent, endEvents = arg.endEvents, height = arg.height, width = arg.width, cb = arg.cb, noRemove = arg.noRemove;
+ rect = root.getBoundingClientRect();
+ o = {
+ root: root,
+ el: el,
+ style: el.style,
+ isImage: (ref = el.nodeName) === 'IMG' || ref === 'VIDEO',
+ cb: cb,
+ endEvents: endEvents,
+ latestEvent: latestEvent,
+ clientHeight: doc.clientHeight,
+ clientWidth: doc.clientWidth,
+ height: height,
+ width: width,
+ noRemove: noRemove,
+ clientX: (rect.left + rect.right) / 2,
+ clientY: (rect.top + rect.bottom) / 2
+ };
+ o.hover = hover.bind(o);
+ o.hoverend = hoverend.bind(o);
+ o.hover(o.latestEvent);
+ new MutationObserver(function() {
+ if (el.parentNode) {
+ return o.hover(o.latestEvent);
+ }
+ }).observe(el, {
+ childList: true
+ });
+ $.on(root, endEvents, o.hoverend);
+ if ($.x('ancestor::div[contains(@class,"inline")][1]', root)) {
+ $.on(d, 'keydown', o.hoverend);
+ }
+ $.on(root, 'mousemove', o.hover);
+ o.workaround = function(e) {
+ if (!root.contains(e.target)) {
+ return o.hoverend(e);
+ }
+ };
+ return $.on(doc, 'mousemove', o.workaround);
+ };
+
+ hoverstart.padding = 25;
+
+ hover = function(e) {
+ var clientX, clientY, height, left, marginX, ref, ref1, right, style, threshold, top, width;
+ this.latestEvent = e;
+ height = (this.height || this.el.offsetHeight) + hoverstart.padding;
+ width = this.width || this.el.offsetWidth;
+ ref = Conf['Follow Cursor'] ? e : this, clientX = ref.clientX, clientY = ref.clientY;
+ top = this.isImage ? Math.max(0, clientY * (this.clientHeight - height) / this.clientHeight) : Math.max(0, Math.min(this.clientHeight - height, clientY - 120));
+ threshold = this.clientWidth / 2;
+ if (!this.isImage) {
+ threshold = Math.max(threshold, this.clientWidth - 400);
+ }
+ marginX = (clientX <= threshold ? clientX : this.clientWidth - clientX) + 45;
+ if (this.isImage) {
+ marginX = Math.min(marginX, this.clientWidth - width);
+ }
+ marginX += 'px';
+ ref1 = clientX <= threshold ? [marginX, ''] : ['', marginX], left = ref1[0], right = ref1[1];
+ style = this.style;
+ style.top = top + 'px';
+ style.left = left;
+ return style.right = right;
+ };
+
+ hoverend = function(e) {
+ if (e.type === 'keydown' && e.keyCode !== 13 || e.target.nodeName === "TEXTAREA") {
+ return;
+ }
+ if (!this.noRemove) {
+ $.rm(this.el);
+ }
+ $.off(this.root, this.endEvents, this.hoverend);
+ $.off(d, 'keydown', this.hoverend);
+ $.off(this.root, 'mousemove', this.hover);
+ $.off(doc, 'mousemove', this.workaround);
+ if (this.cb) {
+ return this.cb.call(this);
+ }
+ };
+
+ checkbox = function(name, text, checked) {
+ var input, label;
+ if (checked == null) {
+ checked = Conf[name];
+ }
+ label = $.el('label');
+ input = $.el('input', {
+ type: 'checkbox',
+ name: name,
+ checked: checked
+ });
+ $.add(label, [input, $.tn(" " + text)]);
+ return label;
+ };
+
+ UI = {
+ dialog: dialog,
+ Menu: Menu,
+ hover: hoverstart,
+ checkbox: checkbox
+ };
+
+ return UI;
+
+}).call(this);
+
+FappeTyme = (function() {
+ var FappeTyme;
+
+ FappeTyme = {
+ init: function() {
+ var el, i, indicator, lc, len, ref, ref1, type;
+ if (!((Conf['Fappe Tyme'] || Conf['Werk Tyme']) && ((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive'))) {
+ return;
+ }
+ this.nodes = {};
+ this.enabled = {
+ fappe: false,
+ werk: Conf['werk']
+ };
+ ref1 = ["Fappe", "Werk"];
+ for (i = 0, len = ref1.length; i < len; i++) {
+ type = ref1[i];
+ if (!Conf[type + " Tyme"]) {
+ continue;
+ }
+ lc = type.toLowerCase();
+ el = UI.checkbox(lc, type + " Tyme", false);
+ el.title = type + " Tyme";
+ this.nodes[lc] = el.firstElementChild;
+ if (Conf[lc]) {
+ this.set(lc, true);
+ }
+ $.on(this.nodes[lc], 'change', this.toggle.bind(this, lc));
+ Header.menu.addEntry({
+ el: el,
+ order: 97
+ });
+ indicator = $.el('span', {
+ className: 'indicator',
+ textContent: type[0],
+ title: type + " Tyme active"
+ });
+ $.on(indicator, 'click', function() {
+ var check;
+ check = $.getOwn(FappeTyme.nodes, this.parentNode.id.replace('shortcut-', ''));
+ check.checked = !check.checked;
+ return $.event('change', null, check);
+ });
+ Header.addShortcut(lc, indicator, 410);
+ }
+ if (Conf['Werk Tyme']) {
+ $.sync('werk', this.set.bind(this, 'werk'));
+ }
+ Callbacks.Post.push({
+ name: 'Fappe Tyme',
+ cb: this.node
+ });
+ return Callbacks.CatalogThread.push({
+ name: 'Werk Tyme',
+ cb: this.catalogNode
+ });
+ },
+ node: function() {
+ return this.nodes.root.classList.toggle('noFile', !this.files.length);
+ },
+ catalogNode: function() {
+ var file, filename;
+ file = this.thread.OP.files[0];
+ if (!file) {
+ return;
+ }
+ filename = $.el('div', {
+ textContent: file.name,
+ className: 'werkTyme-filename'
+ });
+ return $.add(this.nodes.thumb.parentNode, filename);
+ },
+ set: function(type, enabled) {
+ this.enabled[type] = this.nodes[type].checked = enabled;
+ return $[(enabled ? 'add' : 'rm') + "Class"](doc, type + "Tyme");
+ },
+ toggle: function(type) {
+ this.set(type, !this.enabled[type]);
+ if (type === 'werk') {
+ return $.cb.checked.call(this.nodes[type]);
+ }
+ }
+ };
+
+ return FappeTyme;
+
+}).call(this);
+
+Gallery = (function() {
+ var Gallery;
+
+ Gallery = {
+ init: function() {
+ var el, ref;
+ if (!(this.enabled = Conf['Gallery'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) {
+ return;
+ }
+ this.delay = Conf['Slide Delay'];
+ el = $.el('a', {
+ href: 'javascript:;',
+ title: 'Gallery',
+ className: 'fa fa-picture-o',
+ textContent: 'Gallery'
+ });
+ $.on(el, 'click', this.cb.toggle);
+ Header.addShortcut('gallery', el, 530);
+ return Callbacks.Post.push({
+ name: 'Gallery',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var file, i, len, ref, results;
+ ref = this.files;
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ file = ref[i];
+ if (!file.thumb) {
+ continue;
+ }
+ if (Gallery.nodes) {
+ Gallery.generateThumb(this, file);
+ Gallery.nodes.total.textContent = Gallery.images.length;
+ }
+ if (!(Conf['Image Expansion'] || (g.SITE.software === 'tinyboard' && Main.jsEnabled))) {
+ results.push($.on(file.thumbLink, 'click', Gallery.cb.image));
+ } else {
+ results.push(void 0);
+ }
+ }
+ return results;
+ },
+ build: function(image) {
+ var candidate, cb, dialog, entry, file, i, j, k, key, len, len1, len2, menuButton, nodes, post, postThumb, ref, ref1, ref2, ref3, thumb, value;
+ cb = Gallery.cb;
+ if (Conf['Fullscreen Gallery']) {
+ $.one(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', function() {
+ return $.on(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', cb.close);
+ });
+ if (typeof doc.mozRequestFullScreen === "function") {
+ doc.mozRequestFullScreen();
+ }
+ if (typeof doc.webkitRequestFullScreen === "function") {
+ doc.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
+ }
+ }
+ Gallery.images = [];
+ nodes = Gallery.nodes = {};
+ Gallery.fileIDs = $.dict();
+ Gallery.slideshow = false;
+ nodes.el = dialog = $.el('div', {
+ id: 'a-gallery'
+ });
+ $.extend(dialog, {innerHTML: "
"});
+ ref = {
+ buttons: '.gal-buttons',
+ frame: '.gal-image',
+ name: '.gal-name',
+ count: '.count',
+ total: '.total',
+ sauce: '.gal-sauce',
+ thumbs: '.gal-thumbnails',
+ next: '.gal-image a',
+ current: '.gal-image img'
+ };
+ for (key in ref) {
+ value = ref[key];
+ nodes[key] = $(value, dialog);
+ }
+ menuButton = $('.menu-button', dialog);
+ nodes.menu = new UI.Menu('gallery');
+ $.on(nodes.frame, 'click', cb.blank);
+ if (Conf['Mouse Wheel Volume']) {
+ $.on(nodes.frame, 'wheel', Volume.wheel);
+ }
+ $.on(nodes.next, 'click', cb.click);
+ $.on(nodes.name, 'click', ImageCommon.download);
+ $.on($('.gal-prev', dialog), 'click', cb.prev);
+ $.on($('.gal-next', dialog), 'click', cb.next);
+ $.on($('.gal-start', dialog), 'click', cb.start);
+ $.on($('.gal-stop', dialog), 'click', cb.stop);
+ $.on($('.gal-close', dialog), 'click', cb.close);
+ $.on(menuButton, 'click', function(e) {
+ return nodes.menu.toggle(e, this, g);
+ });
+ ref1 = Gallery.menu.createSubEntries();
+ for (i = 0, len = ref1.length; i < len; i++) {
+ entry = ref1[i];
+ entry.order = 0;
+ nodes.menu.addEntry(entry);
+ }
+ $.on(d, 'keydown', cb.keybinds);
+ if (Conf['Keybinds']) {
+ $.off(d, 'keydown', Keybinds.keydown);
+ }
+ $.on(window, 'resize', Gallery.cb.setHeight);
+ ref2 = $$(g.SITE.selectors.file.thumb);
+ for (j = 0, len1 = ref2.length; j < len1; j++) {
+ postThumb = ref2[j];
+ if (!(post = Get.postFromNode(postThumb))) {
+ continue;
+ }
+ ref3 = post.files;
+ for (k = 0, len2 = ref3.length; k < len2; k++) {
+ file = ref3[k];
+ if (!file.thumb) {
+ continue;
+ }
+ Gallery.generateThumb(post, file);
+ if (!image && Gallery.fileIDs[post.fullID + "." + file.index]) {
+ candidate = file.thumbLink;
+ if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) {
+ image = candidate;
+ }
+ }
+ }
+ }
+ $.addClass(doc, 'gallery-open');
+ $.add(d.body, dialog);
+ nodes.thumbs.scrollTop = 0;
+ nodes.current.parentElement.scrollTop = 0;
+ if (image) {
+ thumb = $("[href='" + image.href + "']", nodes.thumbs);
+ }
+ thumb || (thumb = Gallery.images[Gallery.images.length - 1]);
+ if (thumb) {
+ Gallery.open(thumb);
+ }
+ doc.style.overflow = 'hidden';
+ return nodes.total.textContent = Gallery.images.length;
+ },
+ generateThumb: function(post, file) {
+ var thumb, thumbImg;
+ if (post.isClone || post.isHidden) {
+ return;
+ }
+ if (!(file && file.thumb && (file.isImage || file.isVideo || Conf['PDF in Gallery']))) {
+ return;
+ }
+ if (Gallery.fileIDs[post.fullID + "." + file.index]) {
+ return;
+ }
+ Gallery.fileIDs[post.fullID + "." + file.index] = true;
+ thumb = $.el('a', {
+ className: 'gal-thumb',
+ href: file.url,
+ target: '_blank',
+ title: file.name
+ });
+ thumb.dataset.id = Gallery.images.length;
+ thumb.dataset.post = post.fullID;
+ thumb.dataset.file = file.index;
+ thumbImg = file.thumb.cloneNode(false);
+ thumbImg.style.cssText = '';
+ $.add(thumb, thumbImg);
+ $.on(thumb, 'click', Gallery.cb.open);
+ Gallery.images.push(thumb);
+ return $.add(Gallery.nodes.thumbs, thumb);
+ },
+ load: function(thumb, errorCB) {
+ var elType, ext, file;
+ ext = thumb.href.match(/\w*$/);
+ elType = $.getOwn({
+ 'webm': 'video',
+ 'mp4': 'video',
+ 'ogv': 'video',
+ 'pdf': 'iframe'
+ }, ext) || 'img';
+ file = $.el(elType);
+ $.extend(file.dataset, thumb.dataset);
+ $.on(file, 'error', errorCB);
+ file.src = thumb.href;
+ return file;
+ },
+ open: function(thumb) {
+ var el, file, i, len, link, newID, node, nodes, oldID, post, ref, ref1, sauces;
+ nodes = Gallery.nodes;
+ oldID = +nodes.current.dataset.id;
+ newID = +thumb.dataset.id;
+ if (el = Gallery.images[oldID]) {
+ $.rmClass(el, 'gal-highlight');
+ }
+ $.addClass(thumb, 'gal-highlight');
+ nodes.thumbs.scrollTop = thumb.offsetTop + thumb.offsetHeight / 2 - nodes.thumbs.clientHeight / 2;
+ if (((ref = Gallery.cache) != null ? ref.dataset.id : void 0) === '' + newID) {
+ file = Gallery.cache;
+ $.off(file, 'error', Gallery.cacheError);
+ $.on(file, 'error', Gallery.error);
+ } else {
+ file = Gallery.load(thumb, Gallery.error);
+ }
+ $.off(nodes.current, 'error', Gallery.error);
+ ImageCommon.pause(nodes.current);
+ $.replace(nodes.current, file);
+ nodes.current = file;
+ if (file.nodeName === 'VIDEO') {
+ file.loop = true;
+ Volume.setup(file);
+ if (Conf['Autoplay']) {
+ file.play();
+ }
+ if (Conf['Show Controls']) {
+ ImageCommon.addControls(file);
+ }
+ }
+ doc.classList.toggle('gal-pdf', file.nodeName === 'IFRAME');
+ Gallery.cb.setHeight();
+ nodes.count.textContent = +thumb.dataset.id + 1;
+ nodes.name.download = nodes.name.textContent = thumb.title;
+ nodes.name.href = thumb.href;
+ nodes.frame.scrollTop = 0;
+ nodes.next.focus();
+ $.rmAll(nodes.sauce);
+ if (Conf['Sauce'] && Sauce.links && (post = g.posts.get(file.dataset.post))) {
+ sauces = [];
+ ref1 = Sauce.links;
+ for (i = 0, len = ref1.length; i < len; i++) {
+ link = ref1[i];
+ if ((node = Sauce.createSauceLink(link, post, post.files[+file.dataset.file]))) {
+ sauces.push($.tn(' '), node);
+ }
+ }
+ $.add(nodes.sauce, sauces);
+ }
+ if (Gallery.slideshow && (newID > oldID || (oldID === Gallery.images.length - 1 && newID === 0))) {
+ Gallery.setupTimer();
+ } else {
+ Gallery.cb.stop();
+ }
+ if (Conf['Scroll to Post'] && (post = g.posts.get(file.dataset.post))) {
+ Header.scrollTo(post.nodes.root);
+ }
+ if (isNaN(oldID) || newID === (oldID + 1) % Gallery.images.length) {
+ return Gallery.cache = Gallery.load(Gallery.images[(newID + 1) % Gallery.images.length], Gallery.cacheError);
+ }
+ },
+ error: function() {
+ var file, post, ref;
+ if (((ref = this.error) != null ? ref.code : void 0) === MediaError.MEDIA_ERR_DECODE) {
+ return new Notice('error', 'Corrupt or unplayable video', 30);
+ }
+ if (ImageCommon.isFromArchive(this)) {
+ return;
+ }
+ post = g.posts.get(this.dataset.post);
+ file = post.files[+this.dataset.file];
+ return ImageCommon.error(this, post, file, null, (function(_this) {
+ return function(url) {
+ if (!url) {
+ return;
+ }
+ Gallery.images[+_this.dataset.id].href = url;
+ if (Gallery.nodes.current === _this) {
+ return _this.src = url;
+ }
+ };
+ })(this));
+ },
+ cacheError: function() {
+ return delete Gallery.cache;
+ },
+ cleanupTimer: function() {
+ var current;
+ clearTimeout(Gallery.timeoutID);
+ current = Gallery.nodes.current;
+ $.off(current, 'canplaythrough load', Gallery.startTimer);
+ return $.off(current, 'ended', Gallery.cb.next);
+ },
+ startTimer: function() {
+ return Gallery.timeoutID = setTimeout(Gallery.checkTimer, Gallery.delay * $.SECOND);
+ },
+ setupTimer: function() {
+ var current, isVideo;
+ Gallery.cleanupTimer();
+ current = Gallery.nodes.current;
+ isVideo = current.nodeName === 'VIDEO';
+ if (isVideo) {
+ current.play();
+ }
+ if ((isVideo ? current.readyState >= 4 : current.complete) || current.nodeName === 'IFRAME') {
+ return Gallery.startTimer();
+ } else {
+ return $.on(current, (isVideo ? 'canplaythrough' : 'load'), Gallery.startTimer);
+ }
+ },
+ checkTimer: function() {
+ var current;
+ current = Gallery.nodes.current;
+ if (current.nodeName === 'VIDEO' && !current.paused) {
+ $.on(current, 'ended', Gallery.cb.next);
+ return current.loop = false;
+ } else {
+ return Gallery.cb.next();
+ }
+ },
+ cb: {
+ keybinds: function(e) {
+ var cb, key;
+ if (!(key = Keybinds.keyCode(e))) {
+ return;
+ }
+ cb = (function() {
+ switch (key) {
+ case Conf['Close']:
+ case Conf['Open Gallery']:
+ return Gallery.cb.close;
+ case Conf['Next Gallery Image']:
+ return Gallery.cb.next;
+ case Conf['Advance Gallery']:
+ return Gallery.cb.advance;
+ case Conf['Previous Gallery Image']:
+ return Gallery.cb.prev;
+ case Conf['Pause']:
+ return Gallery.cb.pause;
+ case Conf['Slideshow']:
+ return Gallery.cb.toggleSlideshow;
+ case Conf['Rotate image anticlockwise']:
+ return Gallery.cb.rotateLeft;
+ case Conf['Rotate image clockwise']:
+ return Gallery.cb.rotateRight;
+ case Conf['Download Gallery Image']:
+ return Gallery.cb.download;
+ }
+ })();
+ if (!cb) {
+ return;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ return cb();
+ },
+ open: function(e) {
+ if (e) {
+ e.preventDefault();
+ }
+ if (this) {
+ return Gallery.open(this);
+ }
+ },
+ image: function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ return Gallery.build(this);
+ },
+ prev: function() {
+ return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id - 1] || Gallery.images[Gallery.images.length - 1]);
+ },
+ next: function() {
+ return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id + 1] || Gallery.images[0]);
+ },
+ click: function(e) {
+ if (ImageCommon.onControls(e)) {
+ return;
+ }
+ e.preventDefault();
+ return Gallery.cb.advance();
+ },
+ advance: function() {
+ if (!Conf['Autoplay'] && Gallery.nodes.current.paused) {
+ return Gallery.nodes.current.play();
+ } else {
+ return Gallery.cb.next();
+ }
+ },
+ toggle: function() {
+ return (Gallery.nodes ? Gallery.cb.close : Gallery.build)();
+ },
+ blank: function(e) {
+ if (e.target === this) {
+ return Gallery.cb.close();
+ }
+ },
+ toggleSlideshow: function() {
+ return Gallery.cb[Gallery.slideshow ? 'stop' : 'start']();
+ },
+ download: function() {
+ var name;
+ name = $('.gal-name');
+ return name.click();
+ },
+ pause: function() {
+ var current;
+ Gallery.cb.stop();
+ current = Gallery.nodes.current;
+ if (current.nodeName === 'VIDEO') {
+ return current[current.paused ? 'play' : 'pause']();
+ }
+ },
+ start: function() {
+ $.addClass(Gallery.nodes.buttons, 'gal-playing');
+ Gallery.slideshow = true;
+ return Gallery.setupTimer();
+ },
+ stop: function() {
+ var current;
+ if (!Gallery.slideshow) {
+ return;
+ }
+ Gallery.cleanupTimer();
+ current = Gallery.nodes.current;
+ if (current.nodeName === 'VIDEO') {
+ current.loop = true;
+ }
+ $.rmClass(Gallery.nodes.buttons, 'gal-playing');
+ return Gallery.slideshow = false;
+ },
+ rotateLeft: function() {
+ return Gallery.cb.rotate(270);
+ },
+ rotateRight: function() {
+ return Gallery.cb.rotate(90);
+ },
+ rotate: $.debounce(100, function(delta) {
+ var current;
+ current = Gallery.nodes.current;
+ if (current.nodeName === 'IFRAME') {
+ return;
+ }
+ current.dataRotate = ((current.dataRotate || 0) + delta) % 360;
+ current.style.transform = "rotate(" + current.dataRotate + "deg)";
+ return Gallery.cb.setHeight();
+ }),
+ close: function() {
+ $.off(Gallery.nodes.current, 'error', Gallery.error);
+ ImageCommon.pause(Gallery.nodes.current);
+ $.rm(Gallery.nodes.el);
+ $.rmClass(doc, 'gallery-open');
+ if (Conf['Fullscreen Gallery']) {
+ $.off(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', Gallery.cb.close);
+ if (typeof d.mozCancelFullScreen === "function") {
+ d.mozCancelFullScreen();
+ }
+ if (typeof d.webkitExitFullscreen === "function") {
+ d.webkitExitFullscreen();
+ }
+ }
+ delete Gallery.nodes;
+ delete Gallery.fileIDs;
+ doc.style.overflow = '';
+ $.off(d, 'keydown', Gallery.cb.keybinds);
+ if (Conf['Keybinds']) {
+ $.on(d, 'keydown', Keybinds.keydown);
+ }
+ $.off(window, 'resize', Gallery.cb.setHeight);
+ return clearTimeout(Gallery.timeoutID);
+ },
+ setFitness: function() {
+ return (this.checked ? $.addClass : $.rmClass)(doc, "gal-" + (this.name.toLowerCase().replace(/\s+/g, '-')));
+ },
+ setHeight: $.debounce(100, function() {
+ var containerHeight, containerWidth, current, dim, frame, height, margin, minHeight, ref, ref1, ref2, ref3, style, width;
+ ref = Gallery.nodes, current = ref.current, frame = ref.frame;
+ style = current.style;
+ if (Conf['Stretch to Fit'] && (dim = (ref1 = g.posts.get(current.dataset.post)) != null ? ref1.files[+current.dataset.file].dimensions : void 0)) {
+ ref2 = dim.split('x'), width = ref2[0], height = ref2[1];
+ containerWidth = frame.clientWidth;
+ containerHeight = doc.clientHeight - 25;
+ if ((current.dataRotate || 0) % 180 === 90) {
+ ref3 = [containerHeight, containerWidth], containerWidth = ref3[0], containerHeight = ref3[1];
+ }
+ minHeight = Math.min(containerHeight, height / width * containerWidth);
+ style.minHeight = minHeight + 'px';
+ style.minWidth = (width / height * minHeight) + 'px';
+ } else {
+ style.minHeight = style.minWidth = '';
+ }
+ if ((current.dataRotate || 0) % 180 === 90) {
+ style.maxWidth = Conf['Fit Height'] ? (doc.clientHeight - 25) + "px" : 'none';
+ style.maxHeight = Conf['Fit Width'] ? frame.clientWidth + "px" : 'none';
+ margin = (current.clientWidth - current.clientHeight) / 2;
+ return style.margin = margin + "px " + (-margin) + "px";
+ } else {
+ return style.maxWidth = style.maxHeight = style.margin = '';
+ }
+ }),
+ setDelay: function() {
+ return Gallery.delay = +this.value;
+ }
+ },
+ menu: {
+ init: function() {
+ var el;
+ if (!Gallery.enabled) {
+ return;
+ }
+ el = $.el('span', {
+ textContent: 'Gallery',
+ className: 'gallery-link'
+ });
+ return Header.menu.addEntry({
+ el: el,
+ order: 105,
+ subEntries: Gallery.menu.createSubEntries()
+ });
+ },
+ createSubEntry: function(name) {
+ var input, label;
+ label = UI.checkbox(name, name);
+ input = label.firstElementChild;
+ if (name === 'Hide Thumbnails' || name === 'Fit Width' || name === 'Fit Height') {
+ $.on(input, 'change', Gallery.cb.setFitness);
+ }
+ $.event('change', null, input);
+ $.on(input, 'change', $.cb.checked);
+ if (name === 'Hide Thumbnails' || name === 'Fit Width' || name === 'Fit Height' || name === 'Stretch to Fit') {
+ $.on(input, 'change', Gallery.cb.setHeight);
+ }
+ return {
+ el: label
+ };
+ },
+ createSubEntries: function() {
+ var delayInput, delayLabel, item, subEntries;
+ subEntries = (function() {
+ var i, len, ref, results;
+ ref = ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Stretch to Fit', 'Scroll to Post'];
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ results.push(Gallery.menu.createSubEntry(item));
+ }
+ return results;
+ })();
+ delayLabel = $.el('label', {innerHTML: "Slide Delay:
"});
+ delayInput = delayLabel.firstElementChild;
+ delayInput.value = Gallery.delay;
+ $.on(delayInput, 'change', Gallery.cb.setDelay);
+ $.on(delayInput, 'change', $.cb.value);
+ subEntries.push({
+ el: delayLabel
+ });
+ return subEntries;
+ }
+ }
+ };
+
+ return Gallery;
+
+}).call(this);
+
+ImageCommon = (function() {
+ var ImageCommon,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ ImageCommon = {
+ pause: function(video) {
+ if (video.nodeName !== 'VIDEO') {
+ return;
+ }
+ video.pause();
+ $.off(video, 'volumechange', Volume.change);
+ return video.muted = true;
+ },
+ rewind: function(el) {
+ if (el.nodeName === 'VIDEO') {
+ if (el.readyState >= el.HAVE_METADATA) {
+ return el.currentTime = 0;
+ }
+ } else if (/\.gif$/.test(el.src)) {
+ return $.queueTask(function() {
+ return el.src = el.src;
+ });
+ }
+ },
+ pushCache: function(el) {
+ ImageCommon.cache = el;
+ return $.on(el, 'error', ImageCommon.cacheError);
+ },
+ popCache: function() {
+ var el;
+ el = ImageCommon.cache;
+ $.off(el, 'error', ImageCommon.cacheError);
+ delete ImageCommon.cache;
+ return el;
+ },
+ cacheError: function() {
+ if (ImageCommon.cache === this) {
+ return delete ImageCommon.cache;
+ }
+ },
+ decodeError: function(file, fileObj) {
+ var message, ref;
+ if (((ref = file.error) != null ? ref.code : void 0) !== MediaError.MEDIA_ERR_DECODE) {
+ return false;
+ }
+ if (!(message = $('.warning', fileObj.thumb.parentNode))) {
+ message = $.el('div', {
+ className: 'warning'
+ });
+ $.after(fileObj.thumb, message);
+ }
+ message.textContent = 'Error: Corrupt or unplayable video';
+ return true;
+ },
+ isFromArchive: function(file) {
+ return g.SITE.software === 'yotsuba' && !ImageHost.test(file.src.split('/')[2]);
+ },
+ error: function(file, post, fileObj, delay, cb) {
+ var base, parseJSON, redirect, src, threadJSON, timeoutID, url;
+ src = fileObj.url.split('/');
+ url = null;
+ if (g.SITE.software === 'yotsuba' && Conf['404 Redirect']) {
+ url = Redirect.to('file', {
+ boardID: post.board.ID,
+ filename: src[src.length - 1]
+ });
+ }
+ if (!(url && Redirect.securityCheck(url))) {
+ url = null;
+ }
+ if ((post.isDead || fileObj.isDead) && !ImageCommon.isFromArchive(file)) {
+ return cb(url);
+ }
+ if (delay != null) {
+ timeoutID = setTimeout((function() {
+ return cb(url);
+ }), delay);
+ }
+ if (post.isDead || fileObj.isDead) {
+ return;
+ }
+ redirect = function() {
+ if (!ImageCommon.isFromArchive(file)) {
+ if (delay != null) {
+ clearTimeout(timeoutID);
+ }
+ return cb(url);
+ }
+ };
+ threadJSON = typeof (base = g.SITE.urls).threadJSON === "function" ? base.threadJSON(post) : void 0;
+ if (!threadJSON) {
+ return;
+ }
+ parseJSON = function(isArchiveURL) {
+ var archivedThreadJSON, base1, i, len, postObj, ref, ref1;
+ if (this.status === 404) {
+ if (!isArchiveURL && (archivedThreadJSON = typeof (base1 = g.SITE.urls).archivedThreadJSON === "function" ? base1.archivedThreadJSON(post) : void 0)) {
+ $.ajax(archivedThreadJSON, {
+ onloadend: function() {
+ return parseJSON.call(this, true);
+ }
+ });
+ } else {
+ post.kill(!post.isClone, fileObj.index);
+ }
+ }
+ if (this.status !== 200) {
+ return redirect();
+ }
+ ref = this.response.posts;
+ for (i = 0, len = ref.length; i < len; i++) {
+ postObj = ref[i];
+ if (postObj.no === post.ID) {
+ break;
+ }
+ }
+ if (postObj.no !== post.ID) {
+ post.kill();
+ return redirect();
+ } else if (ref1 = fileObj.docIndex, indexOf.call(g.SITE.Build.parseJSON(postObj, post.board).filesDeleted, ref1) >= 0) {
+ post.kill(true);
+ return redirect();
+ } else {
+ return url = fileObj.url;
+ }
+ };
+ return $.ajax(threadJSON, {
+ onloadend: function() {
+ return parseJSON.call(this);
+ }
+ });
+ },
+ addControls: function(video) {
+ var handler;
+ handler = function() {
+ var t;
+ $.off(video, 'mouseover', handler);
+ t = new Date().getTime();
+ return $.asap((function() {
+ return $.engine !== 'gecko' || (video.readyState >= 3 && video.currentTime <= Math.max(0.1, video.duration - 0.5)) || new Date().getTime() >= t + 1000;
+ }), function() {
+ return video.controls = true;
+ });
+ };
+ return $.on(video, 'mouseover', handler);
+ },
+ onControls: function(e) {
+ return (Conf['Show Controls'] && Conf['Click Passthrough'] && e.target.nodeName === 'VIDEO') || (e.target.controls && e.target.getBoundingClientRect().bottom - e.clientY < 35);
+ },
+ download: function(e) {
+ var download, href, ref;
+ if (this.protocol === 'blob:') {
+ return true;
+ }
+ e.preventDefault();
+ ref = this, href = ref.href, download = ref.download;
+ return CrossOrigin.file(href, function(blob) {
+ var a;
+ if (blob) {
+ a = $.el('a', {
+ href: URL.createObjectURL(blob),
+ download: download,
+ hidden: true
+ });
+ $.add(d.body, a);
+ a.click();
+ return $.rm(a);
+ } else {
+ return new Notice('warning', "Could not download " + href, 20);
+ }
+ });
+ }
+ };
+
+ return ImageCommon;
+
+}).call(this);
+
+ImageExpand = (function() {
+ var ImageExpand,
+ slice = [].slice;
+
+ ImageExpand = {
+ init: function() {
+ var ref;
+ if (!(this.enabled = Conf['Image Expansion'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) {
+ return;
+ }
+ this.EAI = $.el('a', {
+ className: 'expand-all-shortcut fa fa-expand',
+ textContent: 'EAI',
+ title: 'Expand All Images',
+ href: 'javascript:;'
+ });
+ $.on(this.EAI, 'click', this.cb.toggleAll);
+ Header.addShortcut('expand-all', this.EAI, 520);
+ $.on(d, 'scroll visibilitychange', this.cb.playVideos);
+ this.videoControls = $.el('span', {
+ className: 'video-controls'
+ });
+ $.extend(this.videoControls, {innerHTML: "
contract "});
+ return Callbacks.Post.push({
+ name: 'Image Expansion',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var ref;
+ if (!(this.file && (this.file.isImage || this.file.isVideo))) {
+ return;
+ }
+ $.on(this.file.thumbLink, 'click', ImageExpand.cb.toggle);
+ if (this.isClone) {
+ if (this.file.isExpanding) {
+ ImageExpand.contract(this);
+ return ImageExpand.expand(this);
+ } else if (this.file.isExpanded && this.file.isVideo) {
+ Volume.setup(this.file.fullImage);
+ ImageExpand.setupVideoCB(this);
+ return ImageExpand.setupVideo(this, !((ref = this.origin.file.fullImage) != null ? ref.paused : void 0) || this.origin.file.wasPlaying, this.file.fullImage.controls);
+ }
+ } else if (ImageExpand.on && !this.isHidden && !this.isFetchedQuote && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) {
+ return ImageExpand.expand(this);
+ }
+ },
+ cb: {
+ toggle: function(e) {
+ var file, post, ref;
+ if ($.modifiedClick(e)) {
+ return;
+ }
+ post = Get.postFromNode(this);
+ file = post.file;
+ if (file.isExpanded && ImageCommon.onControls(e)) {
+ return;
+ }
+ e.preventDefault();
+ if (!Conf['Autoplay'] && ((ref = file.fullImage) != null ? ref.paused : void 0)) {
+ return file.fullImage.play();
+ } else {
+ return ImageExpand.toggle(post);
+ }
+ },
+ toggleAll: function() {
+ var func, threadRoot, toggle;
+ $.event('CloseMenu');
+ threadRoot = Nav.getThread();
+ toggle = function(post) {
+ var file;
+ file = post.file;
+ if (!(file && (file.isImage || file.isVideo) && doc.contains(post.nodes.root))) {
+ return;
+ }
+ if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || !Conf['Expand videos'] && file.isVideo || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0 || Conf['Expand thread only'] && g.VIEW === 'index' && !(threadRoot != null ? threadRoot.contains(file.thumb) : void 0))) {
+ return;
+ }
+ return $.queueTask(func, post);
+ };
+ if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) {
+ ImageExpand.EAI.className = 'contract-all-shortcut fa fa-compress';
+ ImageExpand.EAI.title = 'Contract All Images';
+ func = ImageExpand.expand;
+ } else {
+ ImageExpand.EAI.className = 'expand-all-shortcut fa fa-expand';
+ ImageExpand.EAI.title = 'Expand All Images';
+ func = ImageExpand.contract;
+ }
+ return g.posts.forEach(function(post) {
+ var i, len, ref;
+ ref = [post].concat(slice.call(post.clones));
+ for (i = 0, len = ref.length; i < len; i++) {
+ post = ref[i];
+ toggle(post);
+ }
+ });
+ },
+ playVideos: function() {
+ return g.posts.forEach(function(post) {
+ var file, i, len, ref, video, visible;
+ ref = [post].concat(slice.call(post.clones));
+ for (i = 0, len = ref.length; i < len; i++) {
+ post = ref[i];
+ file = post.file;
+ if (!(file && file.isVideo && file.isExpanded)) {
+ continue;
+ }
+ video = file.fullImage;
+ visible = ($.hasAudio(video) && !video.muted) || Header.isNodeVisible(video);
+ if (visible && file.wasPlaying) {
+ delete file.wasPlaying;
+ video.play();
+ } else if (!visible && !video.paused) {
+ file.wasPlaying = true;
+ video.pause();
+ }
+ }
+ });
+ },
+ setFitness: function() {
+ return $[this.checked ? 'addClass' : 'rmClass'](doc, this.name.toLowerCase().replace(/\s+/g, '-'));
+ }
+ },
+ toggle: function(post) {
+ var next;
+ if (!(post.file.isExpanding || post.file.isExpanded)) {
+ post.file.scrollIntoView = Conf['Scroll into view'];
+ ImageExpand.expand(post);
+ return;
+ }
+ ImageExpand.contract(post);
+ if (Conf['Advance on contract']) {
+ next = post.nodes.root;
+ while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) {
+ if (!($('.stub', next) || next.offsetHeight === 0)) {
+ break;
+ }
+ }
+ if (next) {
+ return Header.scrollTo(next);
+ }
+ }
+ },
+ contract: function(post) {
+ var bottom, cb, el, eventName, file, i, len, oldHeight, ref, ref1, scrollY, top, x;
+ file = post.file;
+ if (el = file.fullImage) {
+ top = Header.getTopOf(el);
+ bottom = top + el.getBoundingClientRect().height;
+ oldHeight = d.body.clientHeight;
+ scrollY = window.scrollY;
+ }
+ $.rmClass(post.nodes.root, 'expanded-image');
+ $.rmClass(file.thumb, 'expanding');
+ $.rm(file.videoControls);
+ file.thumbLink.href = file.url;
+ file.thumbLink.target = '_blank';
+ ref = ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView'];
+ for (i = 0, len = ref.length; i < len; i++) {
+ x = ref[i];
+ delete file[x];
+ }
+ if (!el) {
+ return;
+ }
+ if (doc.contains(el)) {
+ if (bottom <= 0) {
+ window.scrollBy(0, scrollY - window.scrollY + d.body.clientHeight - oldHeight);
+ } else {
+ Header.scrollToIfNeeded(post.nodes.root);
+ }
+ if (window.scrollX > 0) {
+ window.scrollBy(-window.scrollX, 0);
+ }
+ }
+ $.off(el, 'error', ImageExpand.error);
+ ImageCommon.pushCache(el);
+ if (file.isVideo) {
+ ImageCommon.pause(el);
+ ref1 = ImageExpand.videoCB;
+ for (eventName in ref1) {
+ cb = ref1[eventName];
+ $.off(el, eventName, cb);
+ }
+ }
+ if (Conf['Restart when Opened']) {
+ ImageCommon.rewind(file.thumb);
+ }
+ delete file.fullImage;
+ return $.queueTask(function() {
+ if (file.isExpanding || file.isExpanded) {
+ return;
+ }
+ $.rmClass(el, 'full-image');
+ if (el.id) {
+ return;
+ }
+ return $.rm(el);
+ });
+ },
+ expand: function(post, src) {
+ var el, file, isVideo, ref, thumb, thumbLink;
+ file = post.file;
+ thumb = file.thumb, thumbLink = file.thumbLink, isVideo = file.isVideo;
+ if (post.isHidden || file.isExpanding || file.isExpanded) {
+ return;
+ }
+ $.addClass(thumb, 'expanding');
+ file.isExpanding = true;
+ if (file.fullImage) {
+ el = file.fullImage;
+ } else if (((ref = ImageCommon.cache) != null ? ref.dataset.fileID : void 0) === (post.fullID + "." + file.index)) {
+ el = file.fullImage = ImageCommon.popCache();
+ $.on(el, 'error', ImageExpand.error);
+ if (Conf['Restart when Opened'] && el.id !== 'ihover') {
+ ImageCommon.rewind(el);
+ }
+ el.removeAttribute('id');
+ } else {
+ el = file.fullImage = $.el((isVideo ? 'video' : 'img'));
+ el.dataset.fileID = post.fullID + "." + file.index;
+ $.on(el, 'error', ImageExpand.error);
+ el.src = src || file.url;
+ }
+ el.className = 'full-image';
+ $.after(thumb, el);
+ if (isVideo) {
+ if (!file.videoControls) {
+ file.videoControls = ImageExpand.videoControls.cloneNode(true);
+ $.add(file.text, file.videoControls);
+ }
+ thumbLink.removeAttribute('href');
+ thumbLink.removeAttribute('target');
+ el.loop = true;
+ Volume.setup(el);
+ ImageExpand.setupVideoCB(post);
+ }
+ if (!isVideo) {
+ return $.asap((function() {
+ return el.naturalHeight;
+ }), function() {
+ return ImageExpand.completeExpand(post);
+ });
+ } else if (el.readyState >= el.HAVE_METADATA) {
+ return ImageExpand.completeExpand(post);
+ } else {
+ return $.on(el, 'loadedmetadata', function() {
+ return ImageExpand.completeExpand(post);
+ });
+ }
+ },
+ completeExpand: function(post) {
+ var bottom, file, imageBottom, oldHeight, scrollY;
+ file = post.file;
+ if (!file.isExpanding) {
+ return;
+ }
+ bottom = Header.getTopOf(file.thumb) + file.thumb.getBoundingClientRect().height;
+ oldHeight = d.body.clientHeight;
+ scrollY = window.scrollY;
+ $.addClass(post.nodes.root, 'expanded-image');
+ $.rmClass(file.thumb, 'expanding');
+ file.isExpanded = true;
+ delete file.isExpanding;
+ if (doc.contains(post.nodes.root) && bottom <= 0) {
+ window.scrollBy(0, scrollY - window.scrollY + d.body.clientHeight - oldHeight);
+ }
+ if (file.scrollIntoView) {
+ delete file.scrollIntoView;
+ imageBottom = Math.min(doc.clientHeight - file.fullImage.getBoundingClientRect().bottom - 25, Header.getBottomOf(file.fullImage));
+ if (imageBottom < 0) {
+ window.scrollBy(0, Math.min(-imageBottom, Header.getTopOf(file.fullImage)));
+ }
+ }
+ if (file.isVideo) {
+ return ImageExpand.setupVideo(post, Conf['Autoplay'], Conf['Show Controls']);
+ }
+ },
+ setupVideo: function(post, playing, controls) {
+ var fullImage;
+ fullImage = post.file.fullImage;
+ if (!playing) {
+ fullImage.controls = controls;
+ return;
+ }
+ fullImage.controls = false;
+ $.asap((function() {
+ return doc.contains(fullImage);
+ }), function() {
+ if (!d.hidden && Header.isNodeVisible(fullImage)) {
+ return fullImage.play();
+ } else {
+ return post.file.wasPlaying = true;
+ }
+ });
+ if (controls) {
+ return ImageCommon.addControls(fullImage);
+ }
+ },
+ videoCB: (function() {
+ var mousedown;
+ mousedown = false;
+ return {
+ mouseover: function() {
+ return mousedown = false;
+ },
+ mousedown: function(e) {
+ if (e.button === 0) {
+ return mousedown = true;
+ }
+ },
+ mouseup: function(e) {
+ if (e.button === 0) {
+ return mousedown = false;
+ }
+ },
+ mouseout: function(e) {
+ if (((e.buttons & 1) || mousedown) && e.clientX <= this.getBoundingClientRect().left) {
+ return ImageExpand.toggle(Get.postFromNode(this));
+ }
+ }
+ };
+ })(),
+ setupVideoCB: function(post) {
+ var cb, eventName, ref;
+ ref = ImageExpand.videoCB;
+ for (eventName in ref) {
+ cb = ref[eventName];
+ $.on(post.file.fullImage, eventName, cb);
+ }
+ if (post.file.videoControls) {
+ return $.on(post.file.videoControls.firstElementChild, 'click', function() {
+ return ImageExpand.toggle(post);
+ });
+ }
+ },
+ error: function() {
+ var post;
+ post = Get.postFromNode(this);
+ $.rm(this);
+ delete post.file.fullImage;
+ if (!(post.file.isExpanding || post.file.isExpanded)) {
+ return;
+ }
+ if (ImageCommon.decodeError(this, post.file)) {
+ return ImageExpand.contract(post);
+ }
+ if (ImageCommon.isFromArchive(this)) {
+ return ImageExpand.contract(post);
+ }
+ return ImageCommon.error(this, post, post.file, 10 * $.SECOND, function(URL) {
+ if (post.file.isExpanding || post.file.isExpanded) {
+ ImageExpand.contract(post);
+ if (URL) {
+ return ImageExpand.expand(post, URL);
+ }
+ }
+ });
+ },
+ menu: {
+ init: function() {
+ var conf, createSubEntry, el, name, ref, subEntries;
+ if (!ImageExpand.enabled) {
+ return;
+ }
+ el = $.el('span', {
+ textContent: 'Image Expansion',
+ className: 'image-expansion-link'
+ });
+ createSubEntry = ImageExpand.menu.createSubEntry;
+ subEntries = [];
+ ref = Config.imageExpansion;
+ for (name in ref) {
+ conf = ref[name];
+ subEntries.push(createSubEntry(name, conf[1]));
+ }
+ return Header.menu.addEntry({
+ el: el,
+ order: 105,
+ subEntries: subEntries
+ });
+ },
+ createSubEntry: function(name, desc) {
+ var input, label;
+ label = UI.checkbox(name, name);
+ label.title = desc;
+ input = label.firstElementChild;
+ if (name === 'Fit width' || name === 'Fit height') {
+ $.on(input, 'change', ImageExpand.cb.setFitness);
+ }
+ $.event('change', null, input);
+ $.on(input, 'change', $.cb.checked);
+ return {
+ el: label
+ };
+ }
+ }
+ };
+
+ return ImageExpand;
+
+}).call(this);
+
+ImageHost = (function() {
+ var ImageHost;
+
+ ImageHost = {
+ init: function() {
+ var ref;
+ if (!((this.useFaster = /\S/.test(Conf['fourchanImageHost'])) && g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread'))) {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'Image Host Rewriting',
+ cb: this.node
+ });
+ },
+ suggestions: ['i.4cdn.org', 'is2.4chan.org'],
+ host: function() {
+ return Conf['fourchanImageHost'].trim() || 'i.4cdn.org';
+ },
+ flashHost: function() {
+ return 'i.4cdn.org';
+ },
+ thumbHost: function() {
+ return 'i.4cdn.org';
+ },
+ test: function(hostname) {
+ return hostname === 'i.4cdn.org' || ImageHost.regex.test(hostname);
+ },
+ regex: /^is\d*\.4chan(?:nel)?\.org$/,
+ node: function() {
+ var host;
+ if (this.isClone) {
+ return;
+ }
+ host = ImageHost.host();
+ if (this.file && ImageHost.test(this.file.url.split('/')[2]) && !/\.swf$/.test(this.file.url)) {
+ this.file.link.hostname = host;
+ if (this.file.thumbLink) {
+ this.file.thumbLink.hostname = host;
+ }
+ this.file.url = this.file.link.href;
+ }
+ return ImageHost.fixLinks($$('a', this.nodes.comment));
+ },
+ fixLinks: function(links) {
+ var host, i, len, link;
+ for (i = 0, len = links.length; i < len; i++) {
+ link = links[i];
+ if (!(ImageHost.test(link.hostname) && !/\.swf$/.test(link.pathname))) {
+ continue;
+ }
+ host = ImageHost.host();
+ if (link.hostname !== host) {
+ link.hostname = host;
+ }
+ }
+ }
+ };
+
+ return ImageHost;
+
+}).call(this);
+
+ImageHover = (function() {
+ var ImageHover;
+
+ ImageHover = {
+ init: function() {
+ var ref;
+ if ((ref = g.VIEW) !== 'index' && ref !== 'thread') {
+ return;
+ }
+ if (Conf['Image Hover']) {
+ Callbacks.Post.push({
+ name: 'Image Hover',
+ cb: this.node
+ });
+ }
+ if (Conf['Image Hover in Catalog']) {
+ return Callbacks.CatalogThread.push({
+ name: 'Image Hover',
+ cb: this.catalogNode
+ });
+ }
+ },
+ node: function() {
+ var file, i, len, ref, results;
+ ref = this.files;
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ file = ref[i];
+ if ((file.isImage || file.isVideo) && file.thumb) {
+ results.push($.on(file.thumb, 'mouseover', ImageHover.mouseover(this, file)));
+ }
+ }
+ return results;
+ },
+ catalogNode: function() {
+ var file;
+ file = this.thread.OP.files[0];
+ if (!(file && (file.isImage || file.isVideo))) {
+ return;
+ }
+ return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover(this.thread.OP, file));
+ },
+ mouseover: function(post, file) {
+ return function(e) {
+ var base, el, error, height, isVideo, maxHeight, maxWidth, ref, ref1, scale, width, x;
+ if (!doc.contains(this)) {
+ return;
+ }
+ isVideo = file.isVideo;
+ if (file.isExpanding || file.isExpanded || (typeof (base = g.SITE).isThumbExpanded === "function" ? base.isThumbExpanded(file) : void 0)) {
+ return;
+ }
+ error = ImageHover.error(post, file);
+ if (((ref = ImageCommon.cache) != null ? ref.dataset.fileID : void 0) === (post.fullID + "." + file.index)) {
+ el = ImageCommon.popCache();
+ $.on(el, 'error', error);
+ } else {
+ el = $.el((isVideo ? 'video' : 'img'));
+ el.dataset.fileID = post.fullID + "." + file.index;
+ $.on(el, 'error', error);
+ el.src = file.url;
+ }
+ if (Conf['Restart when Opened']) {
+ ImageCommon.rewind(el);
+ ImageCommon.rewind(this);
+ }
+ el.id = 'ihover';
+ $.add(Header.hover, el);
+ if (isVideo) {
+ el.loop = true;
+ el.controls = false;
+ Volume.setup(el);
+ if (Conf['Autoplay']) {
+ el.play();
+ if (this.nodeName === 'VIDEO') {
+ this.currentTime = el.currentTime;
+ }
+ }
+ }
+ if (file.dimensions) {
+ ref1 = (function() {
+ var i, len, ref1, results;
+ ref1 = file.dimensions.split('x');
+ results = [];
+ for (i = 0, len = ref1.length; i < len; i++) {
+ x = ref1[i];
+ results.push(+x);
+ }
+ return results;
+ })(), width = ref1[0], height = ref1[1];
+ maxWidth = doc.clientWidth;
+ maxHeight = doc.clientHeight - UI.hover.padding;
+ scale = Math.min(1, maxWidth / width, maxHeight / height);
+ width *= scale;
+ height *= scale;
+ el.style.maxWidth = width + "px";
+ el.style.maxHeight = height + "px";
+ }
+ return UI.hover({
+ root: this,
+ el: el,
+ latestEvent: e,
+ endEvents: 'mouseout click',
+ height: height,
+ width: width,
+ noRemove: true,
+ cb: function() {
+ $.off(el, 'error', error);
+ ImageCommon.pushCache(el);
+ ImageCommon.pause(el);
+ $.rm(el);
+ return el.removeAttribute('style');
+ }
+ });
+ };
+ },
+ error: function(post, file) {
+ return function() {
+ if (ImageCommon.decodeError(this, file)) {
+ return;
+ }
+ return ImageCommon.error(this, post, file, 3 * $.SECOND, (function(_this) {
+ return function(URL) {
+ if (URL) {
+ return _this.src = URL + (_this.src === URL ? '?' + Date.now() : '');
+ } else {
+ return $.rm(_this);
+ }
+ };
+ })(this));
+ };
+ }
+ };
+
+ return ImageHover;
+
+}).call(this);
+
+ImageLoader = (function() {
+ var ImageLoader,
+ slice = [].slice;
+
+ ImageLoader = {
+ init: function() {
+ var el, ref, ref1, replace;
+ if ((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') {
+ return;
+ }
+ replace = Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM'];
+ if (!(Conf['Image Prefetching'] || replace)) {
+ return;
+ }
+ Callbacks.Post.push({
+ name: 'Image Replace',
+ cb: this.node
+ });
+ $.on(d, 'PostsInserted', function() {
+ if (ImageLoader.prefetchEnabled || replace) {
+ return g.posts.forEach(ImageLoader.prefetchAll);
+ }
+ });
+ if (Conf['Replace WEBM']) {
+ $.on(d, 'scroll visibilitychange 4chanXInitFinished PostsInserted', this.playVideos);
+ }
+ if (!(Conf['Image Prefetching'] && ((ref1 = g.VIEW) === 'index' || ref1 === 'thread'))) {
+ return;
+ }
+ el = $.el('a', {
+ href: 'javascript:;',
+ title: 'Prefetch Images',
+ className: 'fa fa-bolt disabled',
+ textContent: 'Prefetch'
+ });
+ $.on(el, 'click', this.toggle);
+ return Header.addShortcut('prefetch', el, 525);
+ },
+ node: function() {
+ var file, i, len, ref;
+ if (this.isClone) {
+ return;
+ }
+ ref = this.files;
+ for (i = 0, len = ref.length; i < len; i++) {
+ file = ref[i];
+ if (Conf['Replace WEBM'] && file.isVideo) {
+ ImageLoader.replaceVideo(this, file);
+ }
+ ImageLoader.prefetch(this, file);
+ }
+ },
+ replaceVideo: function(post, file) {
+ var attr, i, len, ref, thumb, video;
+ thumb = file.thumb;
+ video = $.el('video', {
+ preload: 'none',
+ loop: true,
+ muted: true,
+ poster: thumb.src || thumb.dataset.src,
+ textContent: thumb.alt,
+ className: thumb.className
+ });
+ video.setAttribute('muted', 'muted');
+ video.dataset.md5 = thumb.dataset.md5;
+ ref = ['height', 'width', 'maxHeight', 'maxWidth'];
+ for (i = 0, len = ref.length; i < len; i++) {
+ attr = ref[i];
+ video.style[attr] = thumb.style[attr];
+ }
+ video.src = file.url;
+ $.replace(thumb, video);
+ file.thumb = video;
+ return file.videoThumb = true;
+ },
+ prefetch: function(post, file) {
+ var clone, el, i, isImage, isVideo, len, ref, ref1, replace, thumb, type, url;
+ isImage = file.isImage, isVideo = file.isVideo, thumb = file.thumb, url = file.url;
+ if (file.isPrefetched || !(isImage || isVideo) || post.isHidden || post.thread.isHidden) {
+ return;
+ }
+ if (isVideo) {
+ type = 'WEBM';
+ } else {
+ type = (ref = url.match(/\.([^.]+)$/)) != null ? ref[1].toUpperCase() : void 0;
+ if (type === 'JPEG') {
+ type = 'JPG';
+ }
+ }
+ replace = Conf["Replace " + type] && !/spoiler/.test(thumb.src || thumb.dataset.src);
+ if (!(replace || ImageLoader.prefetchEnabled)) {
+ return;
+ }
+ if ($.hasClass(doc, 'catalog-mode')) {
+ return;
+ }
+ if (![post].concat(slice.call(post.clones)).some(function(clone) {
+ return doc.contains(clone.nodes.root);
+ })) {
+ return;
+ }
+ file.isPrefetched = true;
+ if (file.videoThumb) {
+ ref1 = post.clones;
+ for (i = 0, len = ref1.length; i < len; i++) {
+ clone = ref1[i];
+ clone.file.thumb.preload = 'auto';
+ }
+ thumb.preload = 'auto';
+ if ($.engine === 'gecko') {
+ $.on(thumb, 'loadeddata', function() {
+ return this.removeAttribute('poster');
+ });
+ }
+ return;
+ }
+ el = $.el(isImage ? 'img' : 'video');
+ if (isVideo) {
+ el.preload = 'auto';
+ }
+ if (replace && isImage) {
+ $.on(el, 'load', function() {
+ var j, len1, ref2;
+ ref2 = post.clones;
+ for (j = 0, len1 = ref2.length; j < len1; j++) {
+ clone = ref2[j];
+ clone.file.thumb.src = url;
+ }
+ return thumb.src = url;
+ });
+ }
+ return el.src = url;
+ },
+ prefetchAll: function(post) {
+ var file, i, len, ref;
+ ref = post.files;
+ for (i = 0, len = ref.length; i < len; i++) {
+ file = ref[i];
+ ImageLoader.prefetch(post, file);
+ }
+ },
+ toggle: function() {
+ ImageLoader.prefetchEnabled = !ImageLoader.prefetchEnabled;
+ this.classList.toggle('disabled', !ImageLoader.prefetchEnabled);
+ if (ImageLoader.prefetchEnabled) {
+ g.posts.forEach(ImageLoader.prefetchAll);
+ }
+ },
+ playVideos: function() {
+ var qpClone, ref;
+ qpClone = (ref = $.id('qp')) != null ? ref.firstElementChild : void 0;
+ return g.posts.forEach(function(post) {
+ var file, i, j, len, len1, ref1, ref2, thumb;
+ ref1 = [post].concat(slice.call(post.clones));
+ for (i = 0, len = ref1.length; i < len; i++) {
+ post = ref1[i];
+ ref2 = post.files;
+ for (j = 0, len1 = ref2.length; j < len1; j++) {
+ file = ref2[j];
+ if (!file.videoThumb) {
+ continue;
+ }
+ thumb = file.thumb;
+ if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) {
+ thumb.play();
+ } else {
+ thumb.pause();
+ }
+ }
+ }
+ });
+ }
+ };
+
+ return ImageLoader;
+
+}).call(this);
+
+Metadata = (function() {
+ var Metadata;
+
+ Metadata = {
+ init: function() {
+ var ref;
+ if (!(Conf['WEBM Metadata'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'WEBM Metadata',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var el, file, i, j, len1, ref;
+ ref = this.files;
+ for (i = j = 0, len1 = ref.length; j < len1; i = ++j) {
+ file = ref[i];
+ if (!(/webm$/i.test(file.url))) {
+ continue;
+ }
+ if (this.isClone) {
+ el = $('.webm-title', file.text);
+ } else {
+ el = $.el('span', {
+ className: 'webm-title'
+ });
+ el.dataset.index = i;
+ $.extend(el, {innerHTML: "
"});
+ $.add(file.text, [$.tn(' '), el]);
+ }
+ if (el.children.length === 1) {
+ $.one(el.lastElementChild, 'mouseover focus', Metadata.load);
+ }
+ }
+ },
+ load: function() {
+ var index;
+ $.rmClass(this.parentNode, 'error');
+ $.addClass(this.parentNode, 'loading');
+ index = this.parentNode.dataset.index;
+ return CrossOrigin.binary(Get.postFromNode(this).files[+index].url, (function(_this) {
+ return function(data) {
+ var output, title;
+ $.rmClass(_this.parentNode, 'loading');
+ if (data != null) {
+ title = Metadata.parse(data);
+ output = $.el('span', {
+ textContent: title || ''
+ });
+ if (title == null) {
+ $.addClass(_this.parentNode, 'not-found');
+ }
+ $.before(_this, output);
+ _this.parentNode.tabIndex = 0;
+ if (d.activeElement === _this) {
+ _this.parentNode.focus();
+ }
+ return _this.tabIndex = -1;
+ } else {
+ $.addClass(_this.parentNode, 'error');
+ return $.one(_this, 'click', Metadata.load);
+ }
+ };
+ })(this), {
+ Range: 'bytes=0-9999'
+ });
+ },
+ parse: function(data) {
+ var element, i, readInt, size, title;
+ readInt = function() {
+ var len, n;
+ n = data[i++];
+ len = 0;
+ while (n < (0x80 >> len)) {
+ len++;
+ }
+ n ^= 0x80 >> len;
+ while (len-- && i < data.length) {
+ n = (n << 8) ^ data[i++];
+ }
+ return n;
+ };
+ i = 0;
+ while (i < data.length) {
+ element = readInt();
+ size = readInt();
+ if (element === 0x3BA9) {
+ title = '';
+ while (size-- && i < data.length) {
+ title += String.fromCharCode(data[i++]);
+ }
+ return decodeURIComponent(escape(title));
+ } else if (element !== 0x8538067 && element !== 0x549A966) {
+ i += size;
+ }
+ }
+ return null;
+ }
+ };
+
+ return Metadata;
+
+}).call(this);
+
+RevealSpoilers = (function() {
+ var RevealSpoilers;
+
+ RevealSpoilers = {
+ init: function() {
+ var ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Reveal Spoiler Thumbnails'])) {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'Reveal Spoiler Thumbnails',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var file, i, len, ref, thumb;
+ if (this.isClone) {
+ return;
+ }
+ ref = this.files;
+ for (i = 0, len = ref.length; i < len; i++) {
+ file = ref[i];
+ if (!(file.thumb && file.isSpoiler)) {
+ continue;
+ }
+ thumb = file.thumb;
+ thumb.removeAttribute('style');
+ thumb.style.maxHeight = thumb.style.maxWidth = this.isReply ? '125px' : '250px';
+ if (thumb.src) {
+ thumb.src = file.thumbURL;
+ } else {
+ thumb.dataset.src = file.thumbURL;
+ }
+ }
+ }
+ };
+
+ return RevealSpoilers;
+
+}).call(this);
+
+Sauce = (function() {
+ var Sauce,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ Sauce = {
+ init: function() {
+ var j, len, link, linkData, links, ref, ref1;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Sauce'])) {
+ return;
+ }
+ $.addClass(doc, 'show-sauce');
+ links = [];
+ ref1 = Conf['sauces'].split('\n');
+ for (j = 0, len = ref1.length; j < len; j++) {
+ link = ref1[j];
+ if (link[0] !== '#' && (linkData = this.parseLink(link))) {
+ links.push(linkData);
+ }
+ }
+ if (!links.length) {
+ return;
+ }
+ this.links = links;
+ this.link = $.el('a', {
+ target: '_blank',
+ className: 'sauce'
+ });
+ return Callbacks.Post.push({
+ name: 'Sauce',
+ cb: this.node
+ });
+ },
+ parseLink: function(link) {
+ var err, i, j, len, m, part, parts, ref, ref1, regexp;
+ if (!(link = link.trim())) {
+ return null;
+ }
+ parts = $.dict();
+ ref = link.split(/;(?=(?:text|boards|types|regexp|sandbox):?)/);
+ for (i = j = 0, len = ref.length; j < len; i = ++j) {
+ part = ref[i];
+ if (i === 0) {
+ parts['url'] = part;
+ } else {
+ m = part.match(/^(\w*):?(.*)$/);
+ parts[m[1]] = m[2];
+ }
+ }
+ parts['text'] || (parts['text'] = ((ref1 = parts['url'].match(/(\w+)\.\w+\//)) != null ? ref1[1] : void 0) || '?');
+ if ('boards' in parts) {
+ parts['boards'] = Filter.parseBoards(parts['boards']);
+ }
+ if ('regexp' in parts) {
+ try {
+ if ((regexp = parts['regexp'].match(/^\/(.*)\/(\w*)$/))) {
+ parts['regexp'] = RegExp(regexp[1], regexp[2]);
+ } else {
+ parts['regexp'] = RegExp(parts['regexp']);
+ }
+ } catch (error) {
+ err = error;
+ new Notice('warning', [$.tn("Invalid regexp for Sauce link:"), $.el('br'), $.tn(link), $.el('br'), $.tn(err.message)], 60);
+ return null;
+ }
+ }
+ return parts;
+ },
+ createSauceLink: function(link, post, file) {
+ var a, base, ext, j, key, len, matches, missing, parts, ref;
+ ext = file.url.match(/[^.]*$/)[0];
+ parts = $.dict();
+ $.extend(parts, link);
+ if (!(!parts['boards'] || parts['boards'][post.siteID + "/" + post.boardID] || parts['boards'][post.siteID + "/*"])) {
+ return null;
+ }
+ if (!(!parts['types'] || indexOf.call(parts['types'].split(','), ext) >= 0)) {
+ return null;
+ }
+ if (!(!parts['regexp'] || (matches = file.name.match(parts['regexp'])))) {
+ return null;
+ }
+ missing = [];
+ ref = ['url', 'text'];
+ for (j = 0, len = ref.length; j < len; j++) {
+ key = ref[j];
+ parts[key] = parts[key].replace(/%(T?URL|IMG|[sh]?MD5|board|name|%|semi|\$\d+)/g, function(orig, parameter) {
+ var type;
+ if (parameter[0] === '$') {
+ if (!matches) {
+ return orig;
+ }
+ type = matches[parameter.slice(1)] || '';
+ } else {
+ type = Sauce.formatters[parameter](post, file, ext);
+ if (type == null) {
+ missing.push(parameter);
+ return '';
+ }
+ }
+ if (key === 'url' && (parameter !== '%' && parameter !== 'semi')) {
+ if (/^javascript:/i.test(parts['url'])) {
+ type = JSON.stringify(type);
+ }
+ type = encodeURIComponent(type);
+ }
+ return type;
+ });
+ }
+ if ((typeof (base = g.SITE).areMD5sDeferred === "function" ? base.areMD5sDeferred(post.board) : void 0) && missing.length && !missing.filter(function(x) {
+ return !/^.?MD5$/.test(x);
+ }).length) {
+ a = Sauce.link.cloneNode(false);
+ a.dataset.skip = '1';
+ return a;
+ }
+ if (missing.length) {
+ return null;
+ }
+ a = Sauce.link.cloneNode(false);
+ a.href = parts['url'];
+ a.textContent = parts['text'];
+ if (/^javascript:/i.test(parts['url'])) {
+ a.removeAttribute('target');
+ }
+ return a;
+ },
+ node: function() {
+ var file, j, len, ref;
+ if (this.isClone) {
+ return;
+ }
+ ref = this.files;
+ for (j = 0, len = ref.length; j < len; j++) {
+ file = ref[j];
+ Sauce.file(this, file);
+ }
+ },
+ file: function(post, file) {
+ var j, len, link, node, nodes, observer, ref, skipped;
+ nodes = [];
+ skipped = [];
+ ref = Sauce.links;
+ for (j = 0, len = ref.length; j < len; j++) {
+ link = ref[j];
+ if ((node = Sauce.createSauceLink(link, post, file))) {
+ nodes.push($.tn(' '), node);
+ if (node.dataset.skip) {
+ skipped.push([link, node]);
+ }
+ }
+ }
+ $.add(file.text, nodes);
+ if (skipped.length) {
+ observer = new MutationObserver(function() {
+ var k, len1, node2, ref1;
+ if (file.text.dataset.md5) {
+ for (k = 0, len1 = skipped.length; k < len1; k++) {
+ ref1 = skipped[k], link = ref1[0], node = ref1[1];
+ if ((node2 = Sauce.createSauceLink(link, post, file))) {
+ $.replace(node, node2);
+ }
+ }
+ return observer.disconnect();
+ }
+ });
+ return observer.observe(file.text, {
+ attributes: true
+ });
+ }
+ },
+ formatters: {
+ TURL: function(post, file) {
+ return file.thumbURL;
+ },
+ URL: function(post, file) {
+ return file.url;
+ },
+ IMG: function(post, file, ext) {
+ if (ext === 'gif' || ext === 'jpg' || ext === 'jpeg' || ext === 'png') {
+ return file.url;
+ } else {
+ return file.thumbURL;
+ }
+ },
+ MD5: function(post, file) {
+ return file.MD5;
+ },
+ sMD5: function(post, file) {
+ var ref;
+ return (ref = file.MD5) != null ? ref.replace(/[+\/=]/g, function(c) {
+ return {
+ '+': '-',
+ '/': '_',
+ '=': ''
+ }[c];
+ }) : void 0;
+ },
+ hMD5: function(post, file) {
+ var c;
+ if (file.MD5) {
+ return ((function() {
+ var j, len, ref, results;
+ ref = atob(file.MD5);
+ results = [];
+ for (j = 0, len = ref.length; j < len; j++) {
+ c = ref[j];
+ results.push(("0" + (c.charCodeAt(0).toString(16))).slice(-2));
+ }
+ return results;
+ })()).join('');
+ }
+ },
+ board: function(post) {
+ return post.board.ID;
+ },
+ name: function(post, file) {
+ return file.name;
+ },
+ '%': function() {
+ return '%';
+ },
+ semi: function() {
+ return ';';
+ }
+ }
+ };
+
+ return Sauce;
+
+}).call(this);
+
+Volume = (function() {
+ var Volume;
+
+ Volume = {
+ init: function() {
+ var base, ref, unmuteEntry, volumeEntry;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && (Conf['Image Expansion'] || Conf['Image Hover'] || Conf['Image Hover in Catalog'] || Conf['Gallery']))) {
+ return;
+ }
+ $.sync('Allow Sound', function(x) {
+ var ref1;
+ Conf['Allow Sound'] = x;
+ return (ref1 = Volume.inputs) != null ? ref1.unmute.checked = x : void 0;
+ });
+ $.sync('Default Volume', function(x) {
+ var ref1;
+ Conf['Default Volume'] = x;
+ return (ref1 = Volume.inputs) != null ? ref1.volume.value = x : void 0;
+ });
+ if (Conf['Mouse Wheel Volume']) {
+ Callbacks.Post.push({
+ name: 'Mouse Wheel Volume',
+ cb: this.node
+ });
+ }
+ if (typeof (base = g.SITE).noAudio === "function" ? base.noAudio(g.BOARD) : void 0) {
+ return;
+ }
+ if (Conf['Mouse Wheel Volume']) {
+ Callbacks.CatalogThread.push({
+ name: 'Mouse Wheel Volume',
+ cb: this.catalogNode
+ });
+ }
+ unmuteEntry = UI.checkbox('Allow Sound', 'Allow Sound');
+ unmuteEntry.title = Config.main['Images and Videos']['Allow Sound'][1];
+ volumeEntry = $.el('label', {
+ title: 'Default volume for videos.'
+ });
+ $.extend(volumeEntry, {innerHTML: "
Volume"});
+ this.inputs = {
+ unmute: unmuteEntry.firstElementChild,
+ volume: volumeEntry.firstElementChild
+ };
+ $.on(this.inputs.unmute, 'change', $.cb.checked);
+ $.on(this.inputs.volume, 'change', $.cb.value);
+ Header.menu.addEntry({
+ el: unmuteEntry,
+ order: 200
+ });
+ return Header.menu.addEntry({
+ el: volumeEntry,
+ order: 201
+ });
+ },
+ setup: function(video) {
+ video.muted = !Conf['Allow Sound'];
+ video.volume = Conf['Default Volume'];
+ return $.on(video, 'volumechange', Volume.change);
+ },
+ change: function() {
+ var items, key, muted, ref, val, volume;
+ ref = this, muted = ref.muted, volume = ref.volume;
+ items = {
+ 'Allow Sound': !muted,
+ 'Default Volume': volume
+ };
+ for (key in items) {
+ val = items[key];
+ if (Conf[key] === val) {
+ delete items[key];
+ }
+ }
+ $.set(items);
+ $.extend(Conf, items);
+ if (Volume.inputs) {
+ Volume.inputs.unmute.checked = !muted;
+ return Volume.inputs.volume.value = volume;
+ }
+ },
+ node: function() {
+ var base, file, i, len, ref;
+ if (typeof (base = g.SITE).noAudio === "function" ? base.noAudio(this.board) : void 0) {
+ return;
+ }
+ ref = this.files;
+ for (i = 0, len = ref.length; i < len; i++) {
+ file = ref[i];
+ if (!file.isVideo) {
+ continue;
+ }
+ if (file.thumb) {
+ $.on(file.thumb, 'wheel', Volume.wheel.bind(Header.hover));
+ }
+ $.on($('.file-info', file.text) || file.link, 'wheel', Volume.wheel.bind(file.thumbLink));
+ }
+ },
+ catalogNode: function() {
+ var file;
+ file = this.thread.OP.files[0];
+ if (!(file != null ? file.isVideo : void 0)) {
+ return;
+ }
+ return $.on(this.nodes.thumb, 'wheel', Volume.wheel.bind(Header.hover));
+ },
+ wheel: function(e) {
+ var el, volume;
+ if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
+ return;
+ }
+ if (!(el = $('video:not([data-md5])', this))) {
+ return;
+ }
+ if (el.muted || !$.hasAudio(el)) {
+ return;
+ }
+ volume = el.volume + 0.1;
+ if (e.deltaY < 0) {
+ volume *= 1.1;
+ }
+ if (e.deltaY > 0) {
+ volume /= 1.1;
+ }
+ el.volume = $.minmax(volume - 0.1, 0, 1);
+ return e.preventDefault();
+ }
+ };
+
+ return Volume;
+
+}).call(this);
+
+Embedding = (function() {
+ var Embedding,
+ slice = [].slice;
+
+ Embedding = {
+ init: function() {
+ var j, len, ref, ref1, type;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Linkify'] && (Conf['Embedding'] || Conf['Link Title'] || Conf['Cover Preview']))) {
+ return;
+ }
+ this.types = $.dict();
+ ref1 = this.ordered_types;
+ for (j = 0, len = ref1.length; j < len; j++) {
+ type = ref1[j];
+ this.types[type.key] = type;
+ }
+ if (Conf['Embedding'] && g.VIEW !== 'archive') {
+ this.dialog = UI.dialog('embedding', {innerHTML: "
"});
+ this.media = $('#media-embed', this.dialog);
+ $.one(d, '4chanXInitFinished', this.ready);
+ $.on(d, 'IndexRefreshInternal', function() {
+ return g.posts.forEach(function(post) {
+ var embed, k, l, len1, len2, ref2, ref3;
+ ref2 = [post].concat(slice.call(post.clones));
+ for (k = 0, len1 = ref2.length; k < len1; k++) {
+ post = ref2[k];
+ ref3 = post.nodes.embedlinks;
+ for (l = 0, len2 = ref3.length; l < len2; l++) {
+ embed = ref3[l];
+ Embedding.cb.catalogRemove.call(embed);
+ }
+ }
+ });
+ });
+ }
+ if (Conf['Link Title']) {
+ return $.on(d, '4chanXInitFinished PostsInserted', function() {
+ var key, ref2, ref3, service;
+ ref2 = Embedding.types;
+ for (key in ref2) {
+ service = ref2[key];
+ if ((ref3 = service.title) != null ? ref3.batchSize : void 0) {
+ Embedding.flushTitles(service.title);
+ }
+ }
+ });
+ }
+ },
+ events: function(post) {
+ var data, el, i, items;
+ if (g.VIEW === 'archive') {
+ return;
+ }
+ if (Conf['Embedding']) {
+ i = 0;
+ items = post.nodes.embedlinks = $$('.embedder', post.nodes.comment);
+ while (el = items[i++]) {
+ $.on(el, 'click', Embedding.cb.click);
+ if ($.hasClass(el, 'embedded')) {
+ Embedding.cb.toggle.call(el);
+ }
+ }
+ }
+ if (Conf['Cover Preview']) {
+ i = 0;
+ items = $$('.linkify', post.nodes.comment);
+ while (el = items[i++]) {
+ if ((data = Embedding.services(el))) {
+ Embedding.preview(data);
+ }
+ }
+ }
+ },
+ process: function(link, post) {
+ var data;
+ if (!(Conf['Embedding'] || Conf['Link Title'] || Conf['Cover Preview'])) {
+ return;
+ }
+ if ($.x('ancestor::pre', link)) {
+ return;
+ }
+ if (data = Embedding.services(link)) {
+ data.post = post;
+ if (Conf['Embedding'] && g.VIEW !== 'archive') {
+ Embedding.embed(data);
+ }
+ if (Conf['Link Title']) {
+ Embedding.title(data);
+ }
+ if (Conf['Cover Preview'] && g.VIEW !== 'archive') {
+ return Embedding.preview(data);
+ }
+ }
+ },
+ services: function(link) {
+ var href, j, len, match, ref, type;
+ href = link.href;
+ ref = Embedding.ordered_types;
+ for (j = 0, len = ref.length; j < len; j++) {
+ type = ref[j];
+ if ((match = type.regExp.exec(href))) {
+ return {
+ key: type.key,
+ uid: match[1],
+ options: match[2],
+ link: link
+ };
+ }
+ }
+ },
+ embed: function(data) {
+ var embed, href, key, link, name, options, post, ref, uid, value;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ href = link.href;
+ $.addClass(link, key.toLowerCase());
+ embed = $.el('a', {
+ className: 'embedder',
+ href: 'javascript:;'
+ }, {innerHTML: "(
un embed)"});
+ ref = {
+ key: key,
+ uid: uid,
+ options: options,
+ href: href
+ };
+ for (name in ref) {
+ value = ref[name];
+ embed.dataset[name] = value;
+ }
+ $.on(embed, 'click', Embedding.cb.click);
+ $.after(link, [$.tn(' '), embed]);
+ post.nodes.embedlinks.push(embed);
+ if (Conf['Auto-embed'] && !Conf['Floating Embeds'] && !post.isFetchedQuote) {
+ if ($.hasClass(doc, 'catalog-mode')) {
+ return $.addClass(embed, 'embed-removed');
+ } else {
+ return Embedding.cb.toggle.call(embed);
+ }
+ }
+ },
+ ready: function() {
+ if (!Main.isThisPageLegit()) {
+ return;
+ }
+ $.addClass(Embedding.dialog, 'empty');
+ $.on($('.close', Embedding.dialog), 'click', Embedding.closeFloat);
+ $.on($('.move', Embedding.dialog), 'mousedown', Embedding.dragEmbed);
+ $.on($('.jump', Embedding.dialog), 'click', function() {
+ if (doc.contains(Embedding.lastEmbed)) {
+ return Header.scrollTo(Embedding.lastEmbed);
+ }
+ });
+ return $.add(d.body, Embedding.dialog);
+ },
+ closeFloat: function() {
+ delete Embedding.lastEmbed;
+ $.addClass(Embedding.dialog, 'empty');
+ return $.replace(Embedding.media.firstChild, $.el('div'));
+ },
+ dragEmbed: function() {
+ var style;
+ style = Embedding.media.style;
+ if (Embedding.dragEmbed.mouseup) {
+ $.off(d, 'mouseup', Embedding.dragEmbed);
+ Embedding.dragEmbed.mouseup = false;
+ style.pointerEvents = '';
+ return;
+ }
+ $.on(d, 'mouseup', Embedding.dragEmbed);
+ Embedding.dragEmbed.mouseup = true;
+ return style.pointerEvents = 'none';
+ },
+ title: function(data) {
+ var key, link, options, post, service, uid;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ if (!(service = Embedding.types[key].title)) {
+ return;
+ }
+ $.addClass(link, key.toLowerCase());
+ if (service.batchSize) {
+ (service.queue || (service.queue = [])).push(data);
+ if (service.queue.length >= service.batchSize) {
+ return Embedding.flushTitles(service);
+ }
+ } else {
+ return CrossOrigin.cache(service.api(uid), (function() {
+ return Embedding.cb.title(this, data);
+ }));
+ }
+ },
+ flushTitles: function(service) {
+ var cb, data, queue;
+ queue = service.queue;
+ if (!(queue != null ? queue.length : void 0)) {
+ return;
+ }
+ service.queue = [];
+ cb = function() {
+ var data, j, len;
+ for (j = 0, len = queue.length; j < len; j++) {
+ data = queue[j];
+ Embedding.cb.title(this, data);
+ }
+ };
+ return CrossOrigin.cache(service.api((function() {
+ var j, len, results;
+ results = [];
+ for (j = 0, len = queue.length; j < len; j++) {
+ data = queue[j];
+ results.push(data.uid);
+ }
+ return results;
+ })()), cb);
+ },
+ preview: function(data) {
+ var key, link, service, uid;
+ key = data.key, uid = data.uid, link = data.link;
+ if (!(service = Embedding.types[key].preview)) {
+ return;
+ }
+ return $.on(link, 'mouseover', function(e) {
+ var el, height, src;
+ src = service.url(uid);
+ height = service.height;
+ el = $.el('img', {
+ src: src,
+ id: 'ihover'
+ });
+ $.add(Header.hover, el);
+ return UI.hover({
+ root: link,
+ el: el,
+ latestEvent: e,
+ endEvents: 'mouseout click',
+ height: height
+ });
+ });
+ },
+ cb: {
+ click: function(e) {
+ var div;
+ e.preventDefault();
+ if (!$.hasClass(this, 'embedded') && (Conf['Floating Embeds'] || $.hasClass(doc, 'catalog-mode'))) {
+ if (!(div = Embedding.media.firstChild)) {
+ return;
+ }
+ $.replace(div, Embedding.cb.embed(this));
+ Embedding.lastEmbed = Get.postFromNode(this).nodes.root;
+ return $.rmClass(Embedding.dialog, 'empty');
+ } else {
+ return Embedding.cb.toggle.call(this);
+ }
+ },
+ toggle: function() {
+ if ($.hasClass(this, "embedded")) {
+ $.rm(this.nextElementSibling);
+ } else {
+ $.after(this, Embedding.cb.embed(this));
+ }
+ return $.toggleClass(this, 'embedded');
+ },
+ embed: function(a) {
+ var container, el, type;
+ container = $.el('div', {
+ className: 'media-embed'
+ });
+ $.add(container, el = (type = Embedding.types[a.dataset.key]).el(a));
+ el.style.cssText = type.style != null ? type.style : 'border: none; width: 640px; height: 360px;';
+ return container;
+ },
+ catalogRemove: function() {
+ var isCatalog;
+ isCatalog = $.hasClass(doc, 'catalog-mode');
+ if ((isCatalog && $.hasClass(this, 'embedded')) || (!isCatalog && $.hasClass(this, 'embed-removed'))) {
+ Embedding.cb.toggle.call(this);
+ return $.toggleClass(this, 'embed-removed');
+ }
+ },
+ title: function(req, data) {
+ var base1, j, k, key, len, len1, link, link2, options, post, post2, ref, ref1, service, status, text, uid;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ service = Embedding.types[key].title;
+ status = req.status;
+ if ((status === 200 || status === 304) && service.status) {
+ status = service.status(req.response)[0];
+ }
+ if (!status) {
+ return;
+ }
+ text = "[" + key + "] " + ((function() {
+ switch (status) {
+ case 200:
+ case 304:
+ text = service.text(req.response, uid);
+ if (typeof text === 'string') {
+ return text;
+ } else {
+ return text = link.textContent;
+ }
+ break;
+ case 404:
+ return "Not Found";
+ case 403:
+ case 401:
+ return "Forbidden or Private";
+ default:
+ return status + "'d";
+ }
+ })());
+ link.dataset.original = link.textContent;
+ link.textContent = text;
+ ref = post.clones;
+ for (j = 0, len = ref.length; j < len; j++) {
+ post2 = ref[j];
+ ref1 = $$('a.linkify', post2.nodes.comment);
+ for (k = 0, len1 = ref1.length; k < len1; k++) {
+ link2 = ref1[k];
+ if (!(link2.href === link.href)) {
+ continue;
+ }
+ if ((base1 = link2.dataset).original == null) {
+ base1.original = link2.textContent;
+ }
+ link2.textContent = text;
+ }
+ }
+ }
+ },
+ ordered_types: [
+ {
+ key: 'audio',
+ regExp: /^[^?#]+\.(?:mp3|m4a|oga|wav|flac)(?:[?#]|$)/i,
+ style: '',
+ el: function(a) {
+ return $.el('audio', {
+ controls: true,
+ preload: 'auto',
+ src: a.dataset.href
+ });
+ }
+ }, {
+ key: 'image',
+ regExp: /^[^?#]+\.(?:gif|png|jpg|jpeg|bmp|webp)(?::\w+)?(?:[?#]|$)/i,
+ style: '',
+ el: function(a) {
+ return $.el('div', {innerHTML: "
"});
+ }
+ }, {
+ key: 'video',
+ regExp: /^[^?#]+\.(?:og[gv]|webm|mp4)(?:[?#]|$)/i,
+ style: 'max-width: 80vw; max-height: 80vh;',
+ el: function(a) {
+ var el;
+ el = $.el('video', {
+ hidden: true,
+ controls: true,
+ preload: 'auto',
+ src: a.dataset.href,
+ loop: ImageHost.test(a.dataset.href.split('/')[2])
+ });
+ $.on(el, 'loadedmetadata', function() {
+ if (el.videoHeight === 0 && el.parentNode) {
+ return $.replace(el, Embedding.types.audio.el(a));
+ } else {
+ return el.hidden = false;
+ }
+ });
+ return el;
+ }
+ }, {
+ key: 'PeerTube',
+ regExp: /^(\w+:\/\/[^\/]+\/videos\/watch\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12})(.*)/,
+ el: function(a) {
+ var el, options, start;
+ options = (start = a.dataset.options.match(/[?&](start=\w+)/)) ? "?" + start[1] : '';
+ el = $.el('iframe', {
+ src: a.dataset.uid.replace('/videos/watch/', '/videos/embed/') + options
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'BitChute',
+ regExp: /^\w+:\/\/(?:www\.)?bitchute\.com\/video\/([\w\-]+)/,
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ src: "https://www.bitchute.com/embed/" + a.dataset.uid + "/"
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'Clyp',
+ regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w{8})/,
+ style: 'border: 0; width: 640px; height: 160px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://clyp.it/" + a.dataset.uid + "/widget"
+ });
+ },
+ title: {
+ api: function(uid) {
+ return "https://api.clyp.it/oembed?url=https://clyp.it/" + uid;
+ },
+ text: function(_) {
+ return _.title;
+ }
+ }
+ }, {
+ key: 'Dailymotion',
+ regExp: /^\w+:\/\/(?:(?:www\.)?dailymotion\.com\/(?:embed\/)?video|dai\.ly)\/([A-Za-z0-9]+)[^?]*(.*)/,
+ el: function(a) {
+ var el, options, start;
+ options = (start = a.dataset.options.match(/[?&](start=\d+)/)) ? "?" + start[1] : '';
+ el = $.el('iframe', {
+ src: "//www.dailymotion.com/embed/video/" + a.dataset.uid + options
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ },
+ title: {
+ api: function(uid) {
+ return "https://api.dailymotion.com/video/" + uid;
+ },
+ text: function(_) {
+ return _.title;
+ }
+ },
+ preview: {
+ url: function(uid) {
+ return "https://www.dailymotion.com/thumbnail/video/" + uid;
+ },
+ height: 240
+ }
+ }, {
+ key: 'Gfycat',
+ regExp: /^\w+:\/\/(?:www\.)?gfycat\.com\/(?:iframe\/)?(\w+)/,
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ src: "//gfycat.com/ifr/" + a.dataset.uid
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'Gist',
+ regExp: /^\w+:\/\/gist\.github\.com\/[\w\-]+\/(\w+)/,
+ style: '',
+ el: (function() {
+ var counter;
+ counter = 0;
+ return function(a) {
+ var el;
+ el = $.el('pre', {
+ hidden: true,
+ id: "gist-embed-" + (counter++)
+ });
+ CrossOrigin.cache("https://api.github.com/gists/" + a.dataset.uid, function() {
+ el.textContent = Object.values(this.response.files)[0].content;
+ el.className = 'prettyprint';
+ $.global(function() {
+ return typeof window.prettyPrint === "function" ? window.prettyPrint((function() {}), document.getElementById(document.currentScript.dataset.id).parentNode) : void 0;
+ }, {
+ id: el.id
+ });
+ return el.hidden = false;
+ });
+ return el;
+ };
+ })(),
+ title: {
+ api: function(uid) {
+ return "https://api.github.com/gists/" + uid;
+ },
+ text: function(arg) {
+ var file, files;
+ files = arg.files;
+ for (file in files) {
+ if (files.hasOwnProperty(file)) {
+ return file;
+ }
+ }
+ }
+ }
+ }, {
+ key: 'InstallGentoo',
+ regExp: /^\w+:\/\/paste\.installgentoo\.com\/view\/(?:raw\/|download\/|embed\/)?(\w+)/,
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://paste.installgentoo.com/view/embed/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'LiveLeak',
+ regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*[tif]=(\w+)/,
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ src: "https://www.liveleak.com/e/" + a.dataset.uid
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'Loopvid',
+ regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|ni|wl|ko|mm|ic|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/,
+ style: 'max-width: 80vw; max-height: 80vh;',
+ el: function(a) {
+ var _, base, el, host, j, k, l, len, len1, len2, name, names, ref, ref1, type, types, url, urls;
+ el = $.el('video', {
+ controls: true,
+ preload: 'auto',
+ loop: true
+ });
+ if (/^http/.test(a.dataset.uid)) {
+ $.add(el, $.el('source', {
+ src: a.dataset.uid
+ }));
+ return el;
+ }
+ ref = a.dataset.uid.match(/(\w+)\/(.*)/), _ = ref[0], host = ref[1], names = ref[2];
+ types = (function() {
+ switch (host) {
+ case 'gd':
+ case 'wu':
+ case 'fc':
+ return [''];
+ case 'gc':
+ return ['giant', 'fat', 'zippy'];
+ default:
+ return ['.webm', '.mp4'];
+ }
+ })();
+ ref1 = names.split(',');
+ for (j = 0, len = ref1.length; j < len; j++) {
+ name = ref1[j];
+ for (k = 0, len1 = types.length; k < len1; k++) {
+ type = types[k];
+ base = "" + name + type;
+ urls = (function() {
+ switch (host) {
+ case 'pf':
+ return ["https://kastden.org/_loopvid_media/pf/" + base, "https://web.archive.org/web/2/http://a.pomf.se/" + base];
+ case 'kd':
+ return ["https://kastden.org/loopvid/" + base];
+ case 'lv':
+ return ["https://lv.kastden.org/" + base];
+ case 'gd':
+ return ["https://docs.google.com/uc?export=download&id=" + base];
+ case 'gh':
+ return ["https://googledrive.com/host/" + base];
+ case 'db':
+ return ["https://dl.dropboxusercontent.com/u/" + base];
+ case 'dx':
+ return ["https://dl.dropboxusercontent.com/" + base];
+ case 'nn':
+ return ["https://kastden.org/_loopvid_media/nn/" + base];
+ case 'cp':
+ return ["https://copy.com/" + base];
+ case 'wu':
+ return ["http://webmup.com/" + base + "/vid.webm"];
+ case 'ig':
+ return ["https://i.imgur.com/" + base];
+ case 'ky':
+ return ["https://kastden.org/_loopvid_media/ky/" + base];
+ case 'mf':
+ return ["https://kastden.org/_loopvid_media/mf/" + base, "https://web.archive.org/web/2/https://d.maxfile.ro/" + base];
+ case 'm2':
+ return ["https://kastden.org/_loopvid_media/m2/" + base];
+ case 'pc':
+ return ["https://kastden.org/_loopvid_media/pc/" + base, "https://web.archive.org/web/2/http://a.pomf.cat/" + base];
+ case '1c':
+ return ["http://b.1339.cf/" + base];
+ case 'pi':
+ return ["https://kastden.org/_loopvid_media/pi/" + base, "https://web.archive.org/web/2/https://u.pomf.is/" + base];
+ case 'ni':
+ return ["https://kastden.org/_loopvid_media/ni/" + base, "https://web.archive.org/web/2/https://u.nya.is/" + base];
+ case 'wl':
+ return ["http://webm.land/media/" + base];
+ case 'ko':
+ return ["https://kordy.kastden.org/loopvid/" + base];
+ case 'mm':
+ return ["https://kastden.org/_loopvid_media/mm/" + base, "https://web.archive.org/web/2/https://my.mixtape.moe/" + base];
+ case 'ic':
+ return ["https://media.8ch.net/file_store/" + base];
+ case 'fc':
+ return ["//" + (ImageHost.host()) + "/" + base + ".webm"];
+ case 'gc':
+ return ["https://" + type + ".gfycat.com/" + name + ".webm"];
+ }
+ })();
+ for (l = 0, len2 = urls.length; l < len2; l++) {
+ url = urls[l];
+ $.add(el, $.el('source', {
+ src: url
+ }));
+ }
+ }
+ }
+ return el;
+ }
+ }, {
+ key: 'Openings.moe',
+ regExp: /^\w+:\/\/openings.moe\/\?video=([^.&=]+)/,
+ style: 'width: 1280px; height: 720px; max-width: 80vw; max-height: 80vh;',
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ src: "https://openings.moe/?video=" + a.dataset.uid
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'Pastebin',
+ regExp: /^\w+:\/\/(?:\w+\.)?pastebin\.com\/(?!u\/)(?:[\w.]+(?:\/|\?i\=))?(\w+)/,
+ el: function(a) {
+ var div;
+ return div = $.el('iframe', {
+ src: "//pastebin.com/embed_iframe.php?i=" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'SoundCloud',
+ regExp: /^\w+:\/\/(?:www\.)?(?:soundcloud\.com\/|snd\.sc\/)([\w\-\/]+)/,
+ style: 'border: 0; width: 500px; height: 400px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid))
+ });
+ },
+ title: {
+ api: function(uid) {
+ return location.protocol + "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid));
+ },
+ text: function(_) {
+ return _.title;
+ }
+ }
+ }, {
+ key: 'StrawPoll',
+ regExp: /^\w+:\/\/(?:www\.)?strawpoll\.me\/(?:embed_\d+\/)?(\d+(?:\/r)?)/,
+ style: 'border: 0; width: 600px; height: 406px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://www.strawpoll.me/embed_1/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'Streamable',
+ regExp: /^\w+:\/\/(?:www\.)?streamable\.com\/(\w+)/,
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ src: "https://streamable.com/o/" + a.dataset.uid
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ },
+ title: {
+ api: function(uid) {
+ return "https://api.streamable.com/oembed?url=https://streamable.com/" + uid;
+ },
+ text: function(_) {
+ return _.title;
+ }
+ }
+ }, {
+ key: 'TwitchTV',
+ regExp: /^\w+:\/\/(?:www\.|secure\.|clips\.|m\.)?twitch\.tv\/(\w[^#\&\?]*)/,
+ el: function(a) {
+ var el, m, time, url;
+ m = a.dataset.href.match(/^\w+:\/\/(?:(clips\.)|\w+\.)?twitch\.tv\/(?:\w+\/)?(clip\/)?(\w[^#\&\?]*)/);
+ if (m[1] || m[2]) {
+ url = "//clips.twitch.tv/embed?clip=" + m[3] + "&parent=" + location.hostname;
+ } else {
+ m = a.dataset.uid.match(/(\w+)(?:\/(?:v\/)?(\d+))?/);
+ url = "//player.twitch.tv/?" + (m[2] ? "video=v" + m[2] : "channel=" + m[1]) + "&autoplay=false&parent=" + location.hostname;
+ if ((time = a.dataset.href.match(/\bt=(\w+)/))) {
+ url += "&time=" + time[1];
+ }
+ }
+ el = $.el('iframe', {
+ src: url
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'Twitter',
+ regExp: /^\w+:\/\/(?:www\.|mobile\.)?twitter\.com\/(\w+\/status\/\d+)/,
+ style: 'border: none; width: 550px; height: 250px; overflow: hidden; resize: both;',
+ el: function(a) {
+ var cont, el, onMessage;
+ el = $.el('iframe');
+ $.on(el, 'load', function() {
+ return this.contentWindow.postMessage({
+ element: 't',
+ query: 'height'
+ }, 'https://twitframe.com');
+ });
+ onMessage = function(e) {
+ if (e.source === el.contentWindow && e.origin === 'https://twitframe.com') {
+ $.off(window, 'message', onMessage);
+ return (cont || el).style.height = (+$.minmax(e.data.height, 250, 0.8 * doc.clientHeight)) + "px";
+ }
+ };
+ $.on(window, 'message', onMessage);
+ el.src = "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid;
+ if ($.engine === 'gecko') {
+ el.style.cssText = 'border: none; width: 100%; height: 100%;';
+ cont = $.el('div');
+ $.add(cont, el);
+ return cont;
+ } else {
+ return el;
+ }
+ }
+ }, {
+ key: 'VidLii',
+ regExp: /^\w+:\/\/(?:www\.)?vidlii\.com\/watch\?v=(\w{11})/,
+ style: 'border: none; width: 640px; height: 392px;',
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ src: "https://www.vidlii.com/embed?v=" + a.dataset.uid + "&a=0"
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'Vimeo',
+ regExp: /^\w+:\/\/(?:www\.)?vimeo\.com\/(\d+)/,
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque"
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ },
+ title: {
+ api: function(uid) {
+ return "https://vimeo.com/api/oembed.json?url=https://vimeo.com/" + uid;
+ },
+ text: function(_) {
+ return _.title;
+ }
+ }
+ }, {
+ key: 'Vine',
+ regExp: /^\w+:\/\/(?:www\.)?vine\.co\/v\/(\w+)/,
+ style: 'border: none; width: 500px; height: 500px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://vine.co/v/" + a.dataset.uid + "/card"
+ });
+ }
+ }, {
+ key: 'Vocaroo',
+ regExp: /^\w+:\/\/(?:(?:www\.|old\.)?vocaroo\.com|voca\.ro)\/((?:i\/)?\w+)/,
+ style: '',
+ el: function(a) {
+ var el;
+ el = $.el('iframe');
+ el.width = 300;
+ el.height = 60;
+ el.setAttribute('frameborder', 0);
+ el.src = "https://vocaroo.com/embed/" + (a.dataset.uid.replace(/^i\//, '')) + "?autoplay=0";
+ return el;
+ }
+ }, {
+ key: 'YouTube',
+ regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/))([\w\-]{11})(.*)/,
+ el: function(a) {
+ var el, start;
+ start = a.dataset.options.match(/\b(?:star)?t\=(\w+)/);
+ if (start) {
+ start = start[1];
+ }
+ if (start && !/^\d+$/.test(start)) {
+ start += ' 0h0m0s';
+ start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1];
+ }
+ el = $.el('iframe', {
+ src: "//www.youtube.com/embed/" + a.dataset.uid + "?rel=0&wmode=opaque" + (start ? '&start=' + start : '')
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ },
+ title: {
+ api: function(uid) {
+ return "https://www.youtube.com/oembed?url=https%3A//www.youtube.com/watch%3Fv%3D" + uid + "&format=json";
+ },
+ text: function(_) {
+ return _.title;
+ },
+ status: function(_) {
+ var m;
+ if (_.error) {
+ m = _.error.match(/^(\d*)\s*(.*)/);
+ return [+m[1], m[2]];
+ } else {
+ return [200, 'OK'];
+ }
+ }
+ },
+ preview: {
+ url: function(uid) {
+ return "https://img.youtube.com/vi/" + uid + "/0.jpg";
+ },
+ height: 360
+ }
+ }
+ ]
+ };
+
+ return Embedding;
+
+}).call(this);
+
+Linkify = (function() {
+ var Linkify;
+
+ Linkify = {
+ init: function() {
+ var ref;
+ if (((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') || !Conf['Linkify']) {
+ return;
+ }
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ Callbacks.Post.push({
+ name: 'Linkify',
+ cb: this.node
+ });
+ return Embedding.init();
+ },
+ node: function() {
+ var base, j, k, len, len1, link, links, ref;
+ if (this.isClone) {
+ return Embedding.events(this);
+ }
+ if (!Linkify.regString.test(this.info.comment)) {
+ return;
+ }
+ ref = $$('a', this.nodes.comment);
+ for (j = 0, len = ref.length; j < len; j++) {
+ link = ref[j];
+ if (!(typeof (base = g.SITE).isLinkified === "function" ? base.isLinkified(link) : void 0)) {
+ continue;
+ }
+ $.addClass(link, 'linkify');
+ if (ImageHost.useFaster) {
+ ImageHost.fixLinks([link]);
+ }
+ Embedding.process(link, this);
+ }
+ links = Linkify.process(this.nodes.comment);
+ if (ImageHost.useFaster) {
+ ImageHost.fixLinks(links);
+ }
+ for (k = 0, len1 = links.length; k < len1; k++) {
+ link = links[k];
+ Embedding.process(link, this);
+ }
+ },
+ process: function(node) {
+ var data, end, endNode, i, index, length, links, part1, part2, ref, ref1, result, saved, snapshot, space, test, word;
+ test = /[^\s"]+/g;
+ space = /[\s"]/;
+ snapshot = $.X('.//br|.//text()', node);
+ i = 0;
+ links = [];
+ while (node = snapshot.snapshotItem(i++)) {
+ data = node.data;
+ if (!data || node.parentElement.nodeName === "A") {
+ continue;
+ }
+ while (result = test.exec(data)) {
+ index = result.index;
+ endNode = node;
+ word = result[0];
+ if ((length = index + word.length) === data.length) {
+ test.lastIndex = 0;
+ while ((saved = snapshot.snapshotItem(i++))) {
+ if (saved.nodeName === 'BR' || (saved.parentElement.nodeName === 'P' && !saved.previousSibling)) {
+ if ((part1 = word.match(/(https?:\/\/)?([a-z\d-]+\.)*[a-z\d-]+$/i)) && (part2 = (ref = snapshot.snapshotItem(i)) != null ? (ref1 = ref.data) != null ? ref1.match(/^(\.[a-z\d-]+)*\//i) : void 0 : void 0) && (part1[0] + part2[0]).search(Linkify.regString) === 0) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ if (saved.parentElement.nodeName === "A" && !Linkify.regString.test(word)) {
+ break;
+ }
+ endNode = saved;
+ data = saved.data;
+ if (end = space.exec(data)) {
+ word += data.slice(0, end.index);
+ test.lastIndex = length = end.index;
+ i--;
+ break;
+ } else {
+ length = data.length;
+ word += data;
+ }
+ }
+ }
+ if (Linkify.regString.test(word)) {
+ links.push(Linkify.makeRange(node, endNode, index, length));
+ }
+ if (!(test.lastIndex && node === endNode)) {
+ break;
+ }
+ }
+ }
+ i = links.length;
+ while (i--) {
+ links[i] = Linkify.makeLink(links[i]);
+ }
+ return links;
+ },
+ regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/?])|([-a-z\d]+[.])+(aero|asia|biz|cat|com|coop|dance|info|int|jobs|mobi|moe|museum|name|net|org|post|pro|tel|travel|xxx|xyz|edu|gov|mil|[a-z]{2})([:\/]|(?![^\s"]))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i,
+ makeRange: function(startNode, endNode, startOffset, endOffset) {
+ var range;
+ range = document.createRange();
+ range.setStart(startNode, startOffset);
+ range.setEnd(endNode, endOffset);
+ return range;
+ },
+ makeLink: function(range) {
+ var a, encodedDomain, i, t, text;
+ text = range.toString();
+ i = text.search(Linkify.regString);
+ if (i > 0) {
+ text = text.slice(i);
+ while (range.startOffset + i >= range.startContainer.data.length) {
+ i--;
+ }
+ if (i) {
+ range.setStart(range.startContainer, range.startOffset + i);
+ }
+ }
+ i = 0;
+ while (/[)\]}>.,]/.test(t = text.charAt(text.length - (1 + i)))) {
+ if (!(/[.,]/.test(t) || (text.match(/[()\[\]{}<>]/g)).length % 2)) {
+ break;
+ }
+ i++;
+ }
+ if (i) {
+ text = text.slice(0, -i);
+ while (range.endOffset - i < 0) {
+ i--;
+ }
+ if (i) {
+ range.setEnd(range.endContainer, range.endOffset - i);
+ }
+ }
+ if (!/((mailto|magnet):|.+:\/\/)/.test(text)) {
+ text = (/@/.test(text) ? 'mailto:' : 'http://') + text;
+ }
+ if (encodedDomain = text.match(/^(https?:\/\/[^\/]*%[0-9a-f]{2})(.*)$/i)) {
+ text = encodedDomain[1].replace(/%([0-9a-f]{2})/ig, function(x, y) {
+ if (y === '25') {
+ return x;
+ } else {
+ return String.fromCharCode(parseInt(y, 16));
+ }
+ }) + encodedDomain[2];
+ }
+ a = $.el('a', {
+ className: 'linkify',
+ rel: 'noreferrer noopener',
+ target: '_blank',
+ href: text
+ });
+ $.add(a, range.extractContents());
+ range.insertNode(a);
+ return a;
+ }
+ };
+
+ return Linkify;
+
+}).call(this);
+
+ArchiveLink = (function() {
+ var ArchiveLink;
+
+ ArchiveLink = {
+ init: function() {
+ var div, entry, i, len, ref, ref1, type;
+ if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Archive Link'])) {
+ return;
+ }
+ div = $.el('div', {
+ textContent: 'Archive'
+ });
+ entry = {
+ el: div,
+ order: 60,
+ open: function(arg) {
+ var ID, board, thread;
+ ID = arg.ID, thread = arg.thread, board = arg.board;
+ return !!Redirect.to('thread', {
+ postID: ID,
+ threadID: thread.ID,
+ boardID: board.ID
+ });
+ },
+ subEntries: []
+ };
+ ref1 = [['Post', 'post'], ['Name', 'name'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Subject', 'subject'], ['Flag', 'country'], ['Filename', 'filename'], ['Image MD5', 'MD5']];
+ for (i = 0, len = ref1.length; i < len; i++) {
+ type = ref1[i];
+ entry.subEntries.push(this.createSubEntry(type[0], type[1]));
+ }
+ return Menu.menu.addEntry(entry);
+ },
+ createSubEntry: function(text, type) {
+ var el, open;
+ el = $.el('a', {
+ textContent: text,
+ target: '_blank'
+ });
+ open = type === 'post' ? function(arg) {
+ var ID, board, thread;
+ ID = arg.ID, thread = arg.thread, board = arg.board;
+ el.href = Redirect.to('thread', {
+ postID: ID,
+ threadID: thread.ID,
+ boardID: board.ID
+ });
+ return true;
+ } : function(post) {
+ var ref, typeParam, value;
+ typeParam = type === 'country' && post.info.flagCodeTroll ? 'troll_country' : type;
+ value = type === 'country' ? post.info.flagCode || ((ref = post.info.flagCodeTroll) != null ? ref.toLowerCase() : void 0) : Filter.values(type, post)[0];
+ if (!value) {
+ return false;
+ }
+ el.href = Redirect.to('search', {
+ boardID: post.board.ID,
+ type: typeParam,
+ value: value,
+ isSearch: true
+ });
+ return true;
+ };
+ return {
+ el: el,
+ open: open
+ };
+ }
+ };
+
+ return ArchiveLink;
+
+}).call(this);
+
+CopyTextLink = (function() {
+ var CopyTextLink;
+
+ CopyTextLink = {
+ init: function() {
+ var a, ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Copy Text Link'])) {
+ return;
+ }
+ a = $.el('a', {
+ className: 'copy-text-link',
+ href: 'javascript:;',
+ textContent: 'Copy Text'
+ });
+ $.on(a, 'click', CopyTextLink.copy);
+ return Menu.menu.addEntry({
+ el: a,
+ order: 12,
+ open: function(post) {
+ CopyTextLink.text = (post.origin || post).commentOrig();
+ return true;
+ }
+ });
+ },
+ copy: function() {
+ var el;
+ el = $.el('textarea', {
+ className: 'copy-text-element',
+ value: CopyTextLink.text
+ });
+ $.add(d.body, el);
+ el.select();
+ try {
+ d.execCommand('copy');
+ } catch (error) {}
+ return $.rm(el);
+ }
+ };
+
+ return CopyTextLink;
+
+}).call(this);
+
+DeleteLink = (function() {
+ var DeleteLink;
+
+ DeleteLink = {
+ auto: [$.dict(), $.dict()],
+ init: function() {
+ var div, fileEl, fileEntry, postEl, postEntry, ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Delete Link'])) {
+ return;
+ }
+ div = $.el('div', {
+ className: 'delete-link',
+ textContent: 'Delete'
+ });
+ postEl = $.el('a', {
+ className: 'delete-post',
+ href: 'javascript:;'
+ });
+ fileEl = $.el('a', {
+ className: 'delete-file',
+ href: 'javascript:;'
+ });
+ this.nodes = {
+ menu: div.firstChild,
+ links: [postEl, fileEl]
+ };
+ postEntry = {
+ el: postEl,
+ open: function() {
+ postEl.textContent = DeleteLink.linkText(false);
+ $.on(postEl, 'click', DeleteLink.toggle);
+ return true;
+ }
+ };
+ fileEntry = {
+ el: fileEl,
+ open: function(arg) {
+ var file;
+ file = arg.file;
+ if (!file || file.isDead) {
+ return false;
+ }
+ fileEl.textContent = DeleteLink.linkText(true);
+ $.on(fileEl, 'click', DeleteLink.toggle);
+ return true;
+ }
+ };
+ return Menu.menu.addEntry({
+ el: div,
+ order: 40,
+ open: function(post) {
+ if (post.isDead) {
+ return false;
+ }
+ DeleteLink.post = post;
+ DeleteLink.nodes.menu.textContent = DeleteLink.menuText();
+ DeleteLink.cooldown.start(post);
+ return true;
+ },
+ subEntries: [postEntry, fileEntry]
+ });
+ },
+ menuText: function() {
+ var seconds;
+ if (seconds = DeleteLink.cooldown.seconds[DeleteLink.post.fullID]) {
+ return "Delete (" + seconds + ")";
+ } else {
+ return 'Delete';
+ }
+ },
+ linkText: function(fileOnly) {
+ var text;
+ text = fileOnly ? 'File' : 'Post';
+ if (DeleteLink.auto[+fileOnly][DeleteLink.post.fullID]) {
+ text = "Deleting " + (text.toLowerCase()) + "...";
+ }
+ return text;
+ },
+ toggle: function() {
+ var auto, fileOnly, post;
+ post = DeleteLink.post;
+ fileOnly = $.hasClass(this, 'delete-file');
+ auto = DeleteLink.auto[+fileOnly];
+ if (auto[post.fullID]) {
+ delete auto[post.fullID];
+ } else {
+ auto[post.fullID] = true;
+ }
+ this.textContent = DeleteLink.linkText(fileOnly);
+ if (!DeleteLink.cooldown.seconds[post.fullID]) {
+ return DeleteLink["delete"](post, fileOnly);
+ }
+ },
+ "delete": function(post, fileOnly) {
+ var form, link;
+ link = DeleteLink.nodes.links[+fileOnly];
+ delete DeleteLink.auto[+fileOnly][post.fullID];
+ if (post.fullID === DeleteLink.post.fullID) {
+ $.off(link, 'click', DeleteLink.toggle);
+ }
+ form = {
+ mode: 'usrdel',
+ onlyimgdel: fileOnly,
+ pwd: QR.persona.getPassword()
+ };
+ form[+post.ID] = 'delete';
+ return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + post.board + "/"), {
+ responseType: 'document',
+ withCredentials: true,
+ onloadend: function() {
+ return DeleteLink.load(link, post, fileOnly, this.response);
+ },
+ form: $.formData(form)
+ });
+ },
+ load: function(link, post, fileOnly, resDoc) {
+ var el, msg;
+ if (!resDoc) {
+ new Notice('warning', 'Connection error, please retry.', 20);
+ if (post.fullID === DeleteLink.post.fullID) {
+ $.on(link, 'click', DeleteLink.toggle);
+ }
+ return;
+ }
+ link.textContent = DeleteLink.linkText(fileOnly);
+ if (resDoc.title === '4chan - Banned') {
+ el = $.el('span', {innerHTML: "You can't delete posts because you are
banned ."});
+ return new Notice('warning', el, 20);
+ } else if (msg = resDoc.getElementById('errmsg')) {
+ new Notice('warning', msg.textContent, 20);
+ if (post.fullID === DeleteLink.post.fullID) {
+ $.on(link, 'click', DeleteLink.toggle);
+ }
+ if (QR.cooldown.data && Conf['Cooldown'] && /\bwait\b/i.test(msg.textContent)) {
+ DeleteLink.cooldown.start(post, 5);
+ DeleteLink.auto[+fileOnly][post.fullID] = true;
+ return DeleteLink.nodes.links[+fileOnly].textContent = DeleteLink.linkText(fileOnly);
+ }
+ } else {
+ if (!fileOnly) {
+ QR.cooldown["delete"](post);
+ }
+ if (resDoc.title === 'Updating index...') {
+ (post.origin || post).kill(fileOnly);
+ }
+ if (post.fullID === DeleteLink.post.fullID) {
+ return link.textContent = 'Deleted';
+ }
+ }
+ },
+ cooldown: {
+ seconds: $.dict(),
+ start: function(post, seconds) {
+ if (DeleteLink.cooldown.seconds[post.fullID] != null) {
+ return;
+ }
+ if (seconds == null) {
+ seconds = QR.cooldown.secondsDeletion(post);
+ }
+ if (seconds > 0) {
+ DeleteLink.cooldown.seconds[post.fullID] = seconds;
+ return DeleteLink.cooldown.count(post);
+ }
+ },
+ count: function(post) {
+ var fileOnly, i, len, ref;
+ if (post.fullID === DeleteLink.post.fullID) {
+ DeleteLink.nodes.menu.textContent = DeleteLink.menuText();
+ }
+ if (DeleteLink.cooldown.seconds[post.fullID] > 0 && Conf['Cooldown']) {
+ DeleteLink.cooldown.seconds[post.fullID]--;
+ setTimeout(DeleteLink.cooldown.count, 1000, post);
+ } else {
+ delete DeleteLink.cooldown.seconds[post.fullID];
+ ref = [false, true];
+ for (i = 0, len = ref.length; i < len; i++) {
+ fileOnly = ref[i];
+ if (DeleteLink.auto[+fileOnly][post.fullID]) {
+ DeleteLink["delete"](post, fileOnly);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ return DeleteLink;
+
+}).call(this);
+
+DownloadLink = (function() {
+ var DownloadLink;
+
+ DownloadLink = {
+ init: function() {
+ var a, ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Download Link'])) {
+ return;
+ }
+ a = $.el('a', {
+ className: 'download-link',
+ textContent: 'Download file'
+ });
+ $.on(a, 'click', ImageCommon.download);
+ return Menu.menu.addEntry({
+ el: a,
+ order: 100,
+ open: function(arg) {
+ var file;
+ file = arg.file;
+ if (!file) {
+ return false;
+ }
+ a.href = file.url;
+ a.download = file.name;
+ return true;
+ }
+ });
+ }
+ };
+
+ return DownloadLink;
+
+}).call(this);
+
+Menu = (function() {
+ var Menu;
+
+ Menu = {
+ init: function() {
+ var ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'])) {
+ return;
+ }
+ this.button = $.el('a', {
+ className: 'menu-button',
+ href: 'javascript:;'
+ });
+ $.extend(this.button, {innerHTML: "
"});
+ this.menu = new UI.Menu('post');
+ Callbacks.Post.push({
+ name: 'Menu',
+ cb: this.node
+ });
+ return Callbacks.CatalogThread.push({
+ name: 'Menu',
+ cb: this.catalogNode
+ });
+ },
+ node: function() {
+ var button;
+ if (this.isClone) {
+ button = $('.menu-button', this.nodes.info);
+ $.rmClass(button, 'active');
+ $.rm($('.dialog', this.nodes.info));
+ Menu.makeButton(this, button);
+ return;
+ }
+ return $.add(this.nodes.info, Menu.makeButton(this));
+ },
+ catalogNode: function() {
+ return $.after(this.nodes.icons, Menu.makeButton(this.thread.OP));
+ },
+ makeButton: function(post, button) {
+ button || (button = Menu.button.cloneNode(true));
+ $.on(button, 'click', function(e) {
+ return Menu.menu.toggle(e, this, post);
+ });
+ return button;
+ }
+ };
+
+ return Menu;
+
+}).call(this);
+
+ReportLink = (function() {
+ var ReportLink;
+
+ ReportLink = {
+ init: function() {
+ var a, ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Report Link'])) {
+ return;
+ }
+ a = $.el('a', {
+ className: 'report-link',
+ href: 'javascript:;',
+ textContent: 'Report'
+ });
+ $.on(a, 'click', ReportLink.report);
+ return Menu.menu.addEntry({
+ el: a,
+ order: 10,
+ open: function(post) {
+ ReportLink.url = "//sys." + (location.hostname.split('.')[1]) + ".org/" + post.board + "/imgboard.php?mode=report&no=" + post;
+ if (d.cookie.indexOf('pass_enabled=1') >= 0) {
+ ReportLink.dims = 'width=350,height=275';
+ } else {
+ ReportLink.dims = 'width=400,height=550';
+ }
+ return true;
+ }
+ });
+ },
+ report: function() {
+ var dims, id, set, url;
+ url = ReportLink.url, dims = ReportLink.dims;
+ id = Date.now();
+ set = "toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1," + dims;
+ return window.open(url, id, set);
+ }
+ };
+
+ return ReportLink;
+
+}).call(this);
+
+AntiAutoplay = (function() {
+ var AntiAutoplay;
+
+ AntiAutoplay = {
+ init: function() {
+ var audio, i, len, ref;
+ if (!Conf['Disable Autoplaying Sounds']) {
+ return;
+ }
+ $.addClass(doc, 'anti-autoplay');
+ ref = $$('audio[autoplay]', doc);
+ for (i = 0, len = ref.length; i < len; i++) {
+ audio = ref[i];
+ this.stop(audio);
+ }
+ window.addEventListener('loadstart', ((function(_this) {
+ return function(e) {
+ return _this.stop(e.target);
+ };
+ })(this)), true);
+ Callbacks.Post.push({
+ name: 'Disable Autoplaying Sounds',
+ cb: this.node
+ });
+ return $.ready((function(_this) {
+ return function() {
+ return _this.process(d.body);
+ };
+ })(this));
+ },
+ stop: function(audio) {
+ if (!audio.autoplay) {
+ return;
+ }
+ audio.pause();
+ audio.autoplay = false;
+ if (audio.controls) {
+ return;
+ }
+ audio.controls = true;
+ return $.addClass(audio, 'controls-added');
+ },
+ node: function() {
+ return AntiAutoplay.process(this.nodes.comment);
+ },
+ process: function(root) {
+ var i, iframe, j, len, len1, object, ref, ref1;
+ ref = $$('iframe[src*="youtube"][src*="autoplay=1"]', root);
+ for (i = 0, len = ref.length; i < len; i++) {
+ iframe = ref[i];
+ AntiAutoplay.processVideo(iframe, 'src');
+ }
+ ref1 = $$('object[data*="youtube"][data*="autoplay=1"]', root);
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ object = ref1[j];
+ AntiAutoplay.processVideo(object, 'data');
+ }
+ },
+ processVideo: function(el, attr) {
+ el[attr] = el[attr].replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', '');
+ if (window.getComputedStyle(el).display === 'none') {
+ el.style.display = 'block';
+ }
+ return $.addClass(el, 'autoplay-removed');
+ }
+ };
+
+ return AntiAutoplay;
+
+}).call(this);
+
+Banner = (function() {
+ var Banner,
+ slice = [].slice;
+
+ Banner = {
+ init: function() {
+ if (Conf['Custom Board Titles']) {
+ this.db = new DataBoard('customTitles', null, true);
+ }
+ $.asap((function() {
+ return d.body;
+ }), function() {
+ return $.asap((function() {
+ return $('hr');
+ }), Banner.ready);
+ });
+ if (g.BOARD.ID !== 'f') {
+ return Main.ready(function() {
+ return $.queueTask(Banner.load);
+ });
+ }
+ },
+ ready: function() {
+ var banner, children;
+ banner = $(".boardBanner");
+ children = banner.children;
+ if (g.VIEW === 'thread' && Conf['Remove Thread Excerpt']) {
+ Banner.setTitle(children[1].textContent);
+ }
+ children[0].title = "Click to change";
+ $.on(children[0], 'click', Banner.cb.toggle);
+ if (Conf['Custom Board Titles']) {
+ Banner.custom(children[1]);
+ if (children[2]) {
+ return Banner.custom(children[2]);
+ }
+ }
+ },
+ load: function() {
+ var bannerCnt, img;
+ bannerCnt = $.id('bannerCnt');
+ if (!bannerCnt.firstChild) {
+ img = $.el('img', {
+ alt: '4chan',
+ src: '//s.4cdn.org/image/title/' + bannerCnt.dataset.src
+ });
+ return $.add(bannerCnt, img);
+ }
+ },
+ setTitle: function(title) {
+ if (Unread.title != null) {
+ Unread.title = title;
+ return Unread.update();
+ } else {
+ return d.title = title;
+ }
+ },
+ cb: {
+ toggle: function() {
+ var banner, i, ref;
+ if (!((ref = Banner.choices) != null ? ref.length : void 0)) {
+ Banner.choices = Conf['knownBanners'].split(',').slice();
+ }
+ i = Math.floor(Banner.choices.length * Math.random());
+ banner = Banner.choices.splice(i, 1);
+ return $('img', this.parentNode).src = "//s.4cdn.org/image/title/" + banner;
+ },
+ click: function(e) {
+ var base, br, j, len, name, ref;
+ if (!(e.ctrlKey || e.metaKey)) {
+ return;
+ }
+ if ((base = Banner.original)[name = this.className] == null) {
+ base[name] = this.cloneNode(true);
+ }
+ this.contentEditable = true;
+ ref = $$('br', this);
+ for (j = 0, len = ref.length; j < len; j++) {
+ br = ref[j];
+ $.replace(br, $.tn('\n'));
+ }
+ return this.focus();
+ },
+ keydown: function(e) {
+ e.stopPropagation();
+ if (!e.shiftKey && e.keyCode === 13) {
+ return this.blur();
+ }
+ },
+ blur: function() {
+ var br, j, len, ref;
+ ref = $$('br', this);
+ for (j = 0, len = ref.length; j < len; j++) {
+ br = ref[j];
+ $.replace(br, $.tn('\n'));
+ }
+ if (this.textContent = this.textContent.replace(/\n*$/, '')) {
+ this.contentEditable = false;
+ return Banner.db.set({
+ boardID: g.BOARD.ID,
+ threadID: this.className,
+ val: {
+ title: this.textContent,
+ orig: Banner.original[this.className].textContent
+ }
+ });
+ } else {
+ $.rmAll(this);
+ $.add(this, slice.call(Banner.original[this.className].cloneNode(true).childNodes));
+ return Banner.db["delete"]({
+ boardID: g.BOARD.ID,
+ threadID: this.className
+ });
+ }
+ }
+ },
+ original: $.dict(),
+ custom: function(child) {
+ var className, data, event, j, len, ref;
+ className = child.className;
+ child.title = "Ctrl/\u2318+click to edit board " + (className.slice(5).toLowerCase());
+ child.spellcheck = false;
+ ref = ['click', 'keydown', 'blur'];
+ for (j = 0, len = ref.length; j < len; j++) {
+ event = ref[j];
+ $.on(child, event, Banner.cb[event]);
+ }
+ if (data = Banner.db.get({
+ boardID: g.BOARD.ID,
+ threadID: className
+ })) {
+ if (Conf['Persistent Custom Board Titles'] || data.orig === child.textContent) {
+ Banner.original[className] = child.cloneNode(true);
+ return child.textContent = data.title;
+ } else {
+ return Banner.db["delete"]({
+ boardID: g.BOARD.ID,
+ threadID: className
+ });
+ }
+ }
+ }
+ };
+
+ return Banner;
+
+}).call(this);
+
+CatalogLinks = (function() {
+ var CatalogLinks;
+
+ CatalogLinks = {
+ init: function() {
+ var el, input, selector;
+ if (g.SITE.software === 'yotsuba' && (Conf['External Catalog'] || Conf['JSON Index']) && !(Conf['JSON Index'] && g.VIEW === 'index')) {
+ selector = (function() {
+ switch (g.VIEW) {
+ case 'thread':
+ case 'archive':
+ return '.navLinks.desktop > a';
+ case 'catalog':
+ return '.navLinks > :first-child > a';
+ case 'index':
+ return '#ctrl-top > a, .cataloglink > a';
+ }
+ })();
+ $.ready(function() {
+ var base, catalogLink, catalogURL, i, len, link, link2, ref;
+ ref = $$(selector);
+ for (i = 0, len = ref.length; i < len; i++) {
+ link = ref[i];
+ switch (link.pathname.replace(/\/+/g, '/')) {
+ case "/" + g.BOARD + "/":
+ if (Conf['JSON Index']) {
+ link.textContent = 'Index';
+ }
+ link.href = CatalogLinks.index();
+ break;
+ case "/" + g.BOARD + "/catalog":
+ link.href = CatalogLinks.catalog();
+ }
+ if (g.VIEW === 'catalog' && (catalogURL = CatalogLinks.catalog()) !== (typeof (base = g.SITE.urls).catalog === "function" ? base.catalog(g.BOARD) : void 0)) {
+ catalogLink = link.parentNode.cloneNode(true);
+ link2 = catalogLink.firstElementChild;
+ link2.href = catalogURL;
+ link2.textContent = link2.hostname === location.hostname ? '4chan X Catalog' : 'External Catalog';
+ $.after(link.parentNode, [$.tn(' '), catalogLink]);
+ }
+ }
+ });
+ }
+ if (g.SITE.software === 'yotsuba' && Conf['JSON Index'] && Conf['Use 4chan X Catalog']) {
+ Callbacks.Post.push({
+ name: 'Catalog Link Rewrite',
+ cb: this.node
+ });
+ }
+ if ((this.enabled = Conf['Catalog Links'])) {
+ CatalogLinks.el = el = UI.checkbox('Header catalog links', 'Catalog Links');
+ el.id = 'toggleCatalog';
+ input = $('input', el);
+ $.on(input, 'change', this.toggle);
+ $.sync('Header catalog links', CatalogLinks.set);
+ return Header.menu.addEntry({
+ el: el,
+ order: 95
+ });
+ }
+ },
+ node: function() {
+ var a, i, len, m, ref;
+ ref = $$('a', this.nodes.comment);
+ for (i = 0, len = ref.length; i < len; i++) {
+ a = ref[i];
+ if (m = a.href.match(/^https?:\/\/(boards\.4chan(?:nel)?\.org\/[^\/]+)\/catalog(#s=.*)?/)) {
+ a.href = "//" + m[1] + "/" + (m[2] || '#catalog');
+ }
+ }
+ },
+ toggle: function() {
+ $.event('CloseMenu');
+ $.set('Header catalog links', this.checked);
+ return CatalogLinks.set(this.checked);
+ },
+ set: function(useCatalog) {
+ Conf['Header catalog links'] = useCatalog;
+ CatalogLinks.setLinks(Header.boardList);
+ CatalogLinks.setLinks(Header.bottomBoardList);
+ CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + ".";
+ return $('input', CatalogLinks.el).checked = useCatalog;
+ },
+ setLinks: function(list) {
+ var VIEW, a, board, boardID, i, len, ref, ref1, ref2, ref3, siteID, tail, url;
+ if (!(((ref = CatalogLinks.enabled) != null ? ref : Conf['Catalog Links']) && list)) {
+ return;
+ }
+ tail = /(?:index)?(?:\.\w+)?$/;
+ ref1 = $$('a:not([data-only])', list);
+ for (i = 0, len = ref1.length; i < len; i++) {
+ a = ref1[i];
+ ref2 = a.dataset, siteID = ref2.siteID, boardID = ref2.boardID;
+ if (!(siteID && boardID)) {
+ ref3 = Site.parseURL(a), siteID = ref3.siteID, boardID = ref3.boardID, VIEW = ref3.VIEW;
+ if (!(siteID && boardID && (VIEW === 'index' || VIEW === 'catalog') && (a.dataset.indexOptions || a.href.replace(tail, '') === (Get.url(VIEW, {
+ siteID: siteID,
+ boardID: boardID
+ }) || '').replace(tail, '')))) {
+ continue;
+ }
+ $.extend(a.dataset, {
+ siteID: siteID,
+ boardID: boardID
+ });
+ }
+ board = {
+ siteID: siteID,
+ boardID: boardID
+ };
+ url = Conf['Header catalog links'] ? CatalogLinks.catalog(board) : Get.url('index', board);
+ if (url) {
+ a.href = url;
+ if (a.dataset.indexOptions && url.split('#')[0] === Get.url('index', board)) {
+ a.href += (a.hash ? '/' : '#') + a.dataset.indexOptions;
+ }
+ }
+ }
+ },
+ externalParse: function() {
+ var board, boards, excludes, i, len, line, ref, ref1, ref2, url;
+ CatalogLinks.externalList = $.dict();
+ ref = Conf['externalCatalogURLs'].split('\n');
+ for (i = 0, len = ref.length; i < len; i++) {
+ line = ref[i];
+ if (line[0] === '#') {
+ continue;
+ }
+ url = line.split(';')[0];
+ boards = Filter.parseBoards(((ref1 = line.match(/;boards:([^;]+)/)) != null ? ref1[1] : void 0) || '*');
+ excludes = Filter.parseBoards((ref2 = line.match(/;exclude:([^;]+)/)) != null ? ref2[1] : void 0) || $.dict();
+ for (board in boards) {
+ if (!(excludes[board] || excludes[board.split('/')[0] + '/*'])) {
+ CatalogLinks.externalList[board] = url;
+ }
+ }
+ }
+ },
+ external: function(arg) {
+ var boardID, external, siteID;
+ siteID = arg.siteID, boardID = arg.boardID;
+ if (!CatalogLinks.externalList) {
+ CatalogLinks.externalParse();
+ }
+ external = CatalogLinks.externalList[siteID + "/" + boardID] || CatalogLinks.externalList[siteID + "/*"];
+ if (external) {
+ return external.replace(/%board/g, boardID);
+ } else {
+ return void 0;
+ }
+ },
+ jsonIndex: function(board, hash) {
+ if (g.SITE.ID === board.siteID && g.BOARD.ID === board.boardID && g.VIEW === 'index') {
+ return hash;
+ } else {
+ return Get.url('index', board) + hash;
+ }
+ },
+ catalog: function(board) {
+ var external, nativeCatalog;
+ if (board == null) {
+ board = g.BOARD;
+ }
+ if (Conf['External Catalog'] && (external = CatalogLinks.external(board))) {
+ return external;
+ } else if (Index.enabledOn(board) && Conf['Use 4chan X Catalog']) {
+ return CatalogLinks.jsonIndex(board, '#catalog');
+ } else if ((nativeCatalog = Get.url('catalog', board))) {
+ return nativeCatalog;
+ } else {
+ return CatalogLinks.external(board);
+ }
+ },
+ index: function(board) {
+ if (board == null) {
+ board = g.BOARD;
+ }
+ if (Index.enabledOn(board)) {
+ return CatalogLinks.jsonIndex(board, '#index');
+ } else {
+ return Get.url('index', board);
+ }
+ }
+ };
+
+ return CatalogLinks;
+
+}).call(this);
+
+CustomCSS = (function() {
+ var CustomCSS;
+
+ CustomCSS = {
+ init: function() {
+ if (!Conf['Custom CSS']) {
+ return;
+ }
+ return this.addStyle();
+ },
+ addStyle: function() {
+ return this.style = $.addStyle(CSS.sub(Conf['usercss']), 'custom-css', '#fourchanx-css');
+ },
+ rmStyle: function() {
+ if (this.style) {
+ $.rm(this.style);
+ return delete this.style;
+ }
+ },
+ update: function() {
+ if (!this.style) {
+ return this.addStyle();
+ }
+ return this.style.textContent = CSS.sub(Conf['usercss']);
+ }
+ };
+
+ return CustomCSS;
+
+}).call(this);
+
+ExpandComment = (function() {
+ var ExpandComment;
+
+ ExpandComment = {
+ init: function() {
+ if (g.VIEW !== 'index' || !Conf['Comment Expansion'] || Conf['JSON Index']) {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'Comment Expansion',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var a;
+ if (a = $('.abbr > a:not([onclick])', this.nodes.comment)) {
+ return $.on(a, 'click', ExpandComment.cb);
+ }
+ },
+ callbacks: [],
+ cb: function(e) {
+ e.preventDefault();
+ return ExpandComment.expand(Get.postFromNode(this));
+ },
+ expand: function(post) {
+ var a;
+ if (post.nodes.longComment && !post.nodes.longComment.parentNode) {
+ $.replace(post.nodes.shortComment, post.nodes.longComment);
+ post.nodes.comment = post.nodes.longComment;
+ return;
+ }
+ if (!(a = $('.abbr > a', post.nodes.comment))) {
+ return;
+ }
+ a.textContent = "Post No." + post + " Loading...";
+ return $.cache(g.SITE.urls.threadJSON({
+ boardID: post.boardID,
+ threadID: post.threadID
+ }), function() {
+ return ExpandComment.parse(this, a, post);
+ });
+ },
+ contract: function(post) {
+ var a;
+ if (!post.nodes.shortComment) {
+ return;
+ }
+ a = $('.abbr > a', post.nodes.shortComment);
+ a.textContent = 'here';
+ $.replace(post.nodes.longComment, post.nodes.shortComment);
+ return post.nodes.comment = post.nodes.shortComment;
+ },
+ parse: function(req, a, post) {
+ var callback, clone, comment, href, i, j, k, len, len1, len2, postObj, posts, quote, ref, ref1, spoilerRange, status;
+ status = req.status;
+ if (status !== 200 && status !== 304) {
+ a.textContent = status ? "Error " + req.statusText + " (" + status + ")" : 'Connection Error';
+ return;
+ }
+ posts = req.response.posts;
+ if (spoilerRange = posts[0].custom_spoiler) {
+ g.SITE.Build.spoilerRange[g.BOARD] = spoilerRange;
+ }
+ for (i = 0, len = posts.length; i < len; i++) {
+ postObj = posts[i];
+ if (postObj.no === post.ID) {
+ break;
+ }
+ }
+ if (postObj.no !== post.ID) {
+ a.textContent = "Post No." + post + " not found.";
+ return;
+ }
+ comment = post.nodes.comment;
+ clone = comment.cloneNode(false);
+ clone.innerHTML = postObj.com;
+ ref = $$('.quotelink', clone);
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ quote = ref[j];
+ href = quote.getAttribute('href');
+ if (href[0] === '/') {
+ continue;
+ }
+ if (href[0] === '#') {
+ quote.href = "" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + href;
+ } else {
+ quote.href = (a.pathname.split(/\/+/).splice(0, 3).join('/')) + "/" + href;
+ }
+ }
+ post.nodes.shortComment = comment;
+ $.replace(comment, clone);
+ post.nodes.comment = post.nodes.longComment = clone;
+ post.parseComment();
+ post.parseQuotes();
+ ref1 = ExpandComment.callbacks;
+ for (k = 0, len2 = ref1.length; k < len2; k++) {
+ callback = ref1[k];
+ callback.call(post);
+ }
+ }
+ };
+
+ return ExpandComment;
+
+}).call(this);
+
+ExpandThread = (function() {
+ var ExpandThread,
+ slice = [].slice;
+
+ ExpandThread = {
+ statuses: $.dict(),
+ init: function() {
+ if (!(g.VIEW === 'index' && Conf['Thread Expansion'])) {
+ return;
+ }
+ if (Conf['JSON Index']) {
+ return $.on(d, 'IndexRefreshInternal', this.onIndexRefresh);
+ } else {
+ return Callbacks.Thread.push({
+ name: 'Expand Thread',
+ cb: function() {
+ return ExpandThread.setButton(this);
+ }
+ });
+ }
+ },
+ setButton: function(thread) {
+ var a, ref;
+ if (!(thread.nodes.root && (a = $('.summary', thread.nodes.root)))) {
+ return;
+ }
+ a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['+'].concat(slice.call(a.textContent.match(/\d+/g))));
+ a.style.cursor = 'pointer';
+ return $.on(a, 'click', ExpandThread.cbToggle);
+ },
+ disconnect: function(refresh) {
+ var oldReq, ref, status, threadID;
+ if (g.VIEW === 'thread' || !Conf['Thread Expansion']) {
+ return;
+ }
+ ref = ExpandThread.statuses;
+ for (threadID in ref) {
+ status = ref[threadID];
+ if ((oldReq = status.req)) {
+ delete status.req;
+ oldReq.abort();
+ }
+ delete ExpandThread.statuses[threadID];
+ }
+ if (!refresh) {
+ return $.off(d, 'IndexRefreshInternal', this.onIndexRefresh);
+ }
+ },
+ onIndexRefresh: function() {
+ ExpandThread.disconnect(true);
+ return g.BOARD.threads.forEach(function(thread) {
+ return ExpandThread.setButton(thread);
+ });
+ },
+ cbToggle: function(e) {
+ if ($.modifiedClick(e)) {
+ return;
+ }
+ e.preventDefault();
+ return ExpandThread.toggle(Get.threadFromNode(this));
+ },
+ cbToggleBottom: function(e) {
+ var bottom, thread;
+ if ($.modifiedClick(e)) {
+ return;
+ }
+ e.preventDefault();
+ thread = Get.threadFromNode(this);
+ $.rm(this);
+ bottom = thread.nodes.root.getBoundingClientRect().bottom;
+ ExpandThread.toggle(thread);
+ return window.scrollBy(0, thread.nodes.root.getBoundingClientRect().bottom - bottom);
+ },
+ toggle: function(thread) {
+ var a;
+ if (!(thread.nodes.root && (a = $('.summary', thread.nodes.root)))) {
+ return;
+ }
+ if (thread.ID in ExpandThread.statuses) {
+ return ExpandThread.contract(thread, a, thread.nodes.root);
+ } else {
+ return ExpandThread.expand(thread, a);
+ }
+ },
+ expand: function(thread, a) {
+ var ref, status;
+ ExpandThread.statuses[thread] = status = {};
+ a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['...'].concat(slice.call(a.textContent.match(/\d+/g))));
+ status.req = $.cache(g.SITE.urls.threadJSON({
+ boardID: thread.board.ID,
+ threadID: thread.ID
+ }), function() {
+ if (this !== status.req) {
+ return;
+ }
+ delete status.req;
+ return ExpandThread.parse(this, thread, a);
+ });
+ return status.numReplies = $$(g.SITE.selectors.replyOriginal, thread.nodes.root).length;
+ },
+ contract: function(thread, a, threadRoot) {
+ var filesCount, i, inlined, len, oldReq, postsCount, ref, replies, reply, status;
+ status = ExpandThread.statuses[thread];
+ delete ExpandThread.statuses[thread];
+ if ((oldReq = status.req)) {
+ delete status.req;
+ oldReq.abort();
+ if (a) {
+ a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['+'].concat(slice.call(a.textContent.match(/\d+/g))));
+ }
+ return;
+ }
+ replies = $$('.thread > .replyContainer', threadRoot);
+ if (status.numReplies) {
+ replies = replies.slice(0, -status.numReplies);
+ }
+ postsCount = 0;
+ filesCount = 0;
+ for (i = 0, len = replies.length; i < len; i++) {
+ reply = replies[i];
+ if (Conf['Quote Inlining']) {
+ while (inlined = $('.inlined', reply)) {
+ inlined.click();
+ }
+ }
+ postsCount++;
+ if ('file' in Get.postFromRoot(reply)) {
+ filesCount++;
+ }
+ $.rm(reply);
+ }
+ if (Index.enabled) {
+ $.event('PostsRemoved', null, a.parentNode);
+ }
+ a.textContent = g.SITE.Build.summaryText('+', postsCount, filesCount);
+ return $.rm($('.summary-bottom', threadRoot));
+ },
+ parse: function(req, thread, a) {
+ var a2, filesCount, i, len, post, postData, posts, postsCount, postsRoot, ref, ref1, root;
+ if ((ref = req.status) !== 200 && ref !== 304) {
+ a.textContent = req.status ? "Error " + req.statusText + " (" + req.status + ")" : 'Connection Error';
+ return;
+ }
+ g.SITE.Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler;
+ posts = [];
+ postsRoot = [];
+ filesCount = 0;
+ ref1 = req.response.posts;
+ for (i = 0, len = ref1.length; i < len; i++) {
+ postData = ref1[i];
+ if (postData.no === thread.ID) {
+ continue;
+ }
+ if ((post = thread.posts.get(postData.no)) && !post.isFetchedQuote) {
+ if ('file' in post) {
+ filesCount++;
+ }
+ root = post.nodes.root;
+ postsRoot.push(root);
+ continue;
+ }
+ root = g.SITE.Build.postFromObject(postData, thread.board.ID);
+ post = new Post(root, thread, thread.board);
+ if ('file' in post) {
+ filesCount++;
+ }
+ posts.push(post);
+ postsRoot.push(root);
+ }
+ Main.callbackNodes('Post', posts);
+ $.after(a, postsRoot);
+ $.event('PostsInserted', null, a.parentNode);
+ postsCount = postsRoot.length;
+ a.textContent = g.SITE.Build.summaryText('-', postsCount, filesCount);
+ if (root) {
+ a2 = a.cloneNode(true);
+ a2.classList.add('summary-bottom');
+ $.on(a2, 'click', ExpandThread.cbToggleBottom);
+ return $.after(root, a2);
+ }
+ }
+ };
+
+ return ExpandThread;
+
+}).call(this);
+
+FileInfo = (function() {
+ var FileInfo;
+
+ FileInfo = {
+ init: function() {
+ var ref;
+ if (((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') || !Conf['File Info Formatting']) {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'File Info Formatting',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var a, i, info, j, len, len1, oldInfo, ref, ref1;
+ if (!this.file) {
+ return;
+ }
+ if (this.isClone) {
+ ref = $$('.file-info .download-button', this.file.text);
+ for (i = 0, len = ref.length; i < len; i++) {
+ a = ref[i];
+ $.on(a, 'click', ImageCommon.download);
+ }
+ ref1 = $$('.file-info .quick-filter-md5', this.file.text);
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ a = ref1[j];
+ $.on(a, 'click', Filter.quickFilterMD5);
+ }
+ return;
+ }
+ oldInfo = $.el('span', {
+ className: 'fileText-original'
+ });
+ $.prepend(this.file.link.parentNode, oldInfo);
+ $.add(oldInfo, [this.file.link.previousSibling, this.file.link, this.file.link.nextSibling]);
+ info = $.el('span', {
+ className: 'file-info'
+ });
+ FileInfo.format(Conf['fileInfo'], this, info);
+ return $.prepend(this.file.text, info);
+ },
+ format: function(formatString, post, outputNode) {
+ var a, i, j, len, len1, output, ref, ref1;
+ output = [];
+ formatString.replace(/%(.)|[^%]+/g, function(s, c) {
+ output.push($.hasOwn(FileInfo.formatters, c) ? FileInfo.formatters[c].call(post) : {innerHTML: E(s)});
+ return '';
+ });
+ $.extend(outputNode, {innerHTML: E.cat(output)});
+ ref = $$('.download-button', outputNode);
+ for (i = 0, len = ref.length; i < len; i++) {
+ a = ref[i];
+ $.on(a, 'click', ImageCommon.download);
+ }
+ ref1 = $$('.quick-filter-md5', outputNode);
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ a = ref1[j];
+ $.on(a, 'click', Filter.quickFilterMD5);
+ }
+ },
+ formatters: {
+ t: function() {
+ return {innerHTML: E(this.file.url.match(/[^/]*$/)[0])};
+ },
+ T: function() {
+ return {innerHTML: "
" + (FileInfo.formatters.t.call(this)).innerHTML + " "};
+ },
+ l: function() {
+ return {innerHTML: "
" + (FileInfo.formatters.n.call(this)).innerHTML + " "};
+ },
+ L: function() {
+ return {innerHTML: "
" + (FileInfo.formatters.N.call(this)).innerHTML + " "};
+ },
+ n: function() {
+ var fullname, shortname;
+ fullname = this.file.name;
+ shortname = SW.yotsuba.Build.shortFilename(this.file.name, this.isReply);
+ if (fullname === shortname) {
+ return {innerHTML: E(fullname)};
+ } else {
+ return {innerHTML: "
" + E(shortname) + " " + E(fullname) + " "};
+ }
+ },
+ N: function() {
+ return {innerHTML: E(this.file.name)};
+ },
+ d: function() {
+ return {innerHTML: "
"};
+ },
+ f: function() {
+ return {innerHTML: "
"};
+ },
+ p: function() {
+ return {innerHTML: ((this.file.isSpoiler) ? "Spoiler, " : "")};
+ },
+ s: function() {
+ return {innerHTML: E(this.file.size)};
+ },
+ B: function() {
+ return {innerHTML: E(Math.round(this.file.sizeInBytes)) + " Bytes"};
+ },
+ K: function() {
+ return {innerHTML: E(Math.round(this.file.sizeInBytes/1024)) + " KB"};
+ },
+ M: function() {
+ return {innerHTML: E(Math.round(this.file.sizeInBytes/1048576*100)/100) + " MB"};
+ },
+ r: function() {
+ return {innerHTML: E(this.file.dimensions || "PDF")};
+ },
+ g: function() {
+ return {innerHTML: ((this.file.tag) ? ", " + E(this.file.tag) : "")};
+ },
+ '%': function() {
+ return {innerHTML: "%"};
+ }
+ }
+ };
+
+ return FileInfo;
+
+}).call(this);
+
+Flash = (function() {
+ var Flash;
+
+ Flash = {
+ init: function() {
+ if (g.BOARD.ID === 'f' && Conf['Enable Native Flash Embedding']) {
+ return $.ready(Flash.initReady);
+ }
+ },
+ initReady: function() {
+ if ($.hasStorage) {
+ return $.global(function() {
+ if (JSON.parse(localStorage['4chan-settings'] || '{}').disableAll) {
+ return window.SWFEmbed.init();
+ }
+ });
+ } else {
+ if (g.VIEW === 'thread') {
+ $.global(function() {
+ return window.Main.tid = location.pathname.split(/\/+/)[3];
+ });
+ }
+ return $.global(function() {
+ return window.SWFEmbed.init();
+ });
+ }
+ }
+ };
+
+ return Flash;
+
+}).call(this);
+
+Fourchan = (function() {
+ var Fourchan;
+
+ Fourchan = {
+ init: function() {
+ var ref;
+ if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive'))) {
+ return;
+ }
+ BoardConfig.ready(this.initBoard);
+ return Main.ready(this.initReady);
+ },
+ initBoard: function() {
+ if (g.BOARD.config.code_tags) {
+ $.on(window, 'prettyprint:cb', function(e) {
+ var post, pre;
+ if (!(post = g.posts.get(e.detail.ID))) {
+ return;
+ }
+ if (!(pre = $$('.prettyprint', post.nodes.comment)[+e.detail.i])) {
+ return;
+ }
+ if (!$.hasClass(pre, 'prettyprinted')) {
+ pre.innerHTML = e.detail.html;
+ return $.addClass(pre, 'prettyprinted');
+ }
+ });
+ $.global(function() {
+ return window.addEventListener('prettyprint', function(e) {
+ return window.dispatchEvent(new CustomEvent('prettyprint:cb', {
+ detail: {
+ ID: e.detail.ID,
+ i: e.detail.i,
+ html: window.prettyPrintOne(e.detail.html)
+ }
+ }));
+ }, false);
+ });
+ Callbacks.Post.push({
+ name: 'Parse [code] tags',
+ cb: Fourchan.code
+ });
+ g.posts.forEach(function(post) {
+ if (post.callbacksExecuted) {
+ return Callbacks.Post.execute(post, ['Parse [code] tags'], true);
+ }
+ });
+ ExpandComment.callbacks.push(Fourchan.code);
+ }
+ if (g.BOARD.config.math_tags) {
+ $.global(function() {
+ return window.addEventListener('mathjax', function(e) {
+ if (window.MathJax) {
+ return window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, e.target]);
+ } else {
+ if (!document.querySelector('script[src^="//cdn.mathjax.org/"]')) {
+ window.loadMathJax();
+ window.loadMathJax = function() {};
+ }
+ if (!e.target.classList.contains('postMessage')) {
+ return document.querySelector('script[src^="//cdn.mathjax.org/"]').addEventListener('load', function() {
+ return window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, e.target]);
+ }, false);
+ }
+ }
+ }, false);
+ });
+ Callbacks.Post.push({
+ name: 'Parse [math] tags',
+ cb: Fourchan.math
+ });
+ g.posts.forEach(function(post) {
+ if (post.callbacksExecuted) {
+ return Callbacks.Post.execute(post, ['Parse [math] tags'], true);
+ }
+ });
+ return ExpandComment.callbacks.push(Fourchan.math);
+ }
+ },
+ initReady: function() {
+ return $.global(function() {
+ var j, len, node, ref;
+ window.clickable_ids = false;
+ ref = document.querySelectorAll('.posteruid, .capcode');
+ for (j = 0, len = ref.length; j < len; j++) {
+ node = ref[j];
+ node.removeEventListener('click', window.idClick, false);
+ }
+ });
+ },
+ code: function() {
+ if (this.isClone) {
+ return;
+ }
+ return $.ready((function(_this) {
+ return function() {
+ var i, j, len, pre, ref;
+ ref = $$('.prettyprint', _this.nodes.comment);
+ for (i = j = 0, len = ref.length; j < len; i = ++j) {
+ pre = ref[i];
+ if (!$.hasClass(pre, 'prettyprinted')) {
+ $.event('prettyprint', {
+ ID: _this.fullID,
+ i: i,
+ html: pre.innerHTML
+ }, window);
+ }
+ }
+ };
+ })(this));
+ },
+ math: function() {
+ var cb, j, len, wbr, wbrs;
+ if (!/\[(math|eqn)\]/.test(this.nodes.comment.textContent)) {
+ return;
+ }
+ if ((wbrs = $$('wbr', this.nodes.comment)).length) {
+ for (j = 0, len = wbrs.length; j < len; j++) {
+ wbr = wbrs[j];
+ $.rm(wbr);
+ }
+ this.nodes.comment.normalize();
+ }
+ cb = (function(_this) {
+ return function() {
+ if (!doc.contains(_this.nodes.comment)) {
+ return;
+ }
+ $.off(d, 'PostsInserted', cb);
+ return $.event('mathjax', null, _this.nodes.comment);
+ };
+ })(this);
+ $.on(d, 'PostsInserted', cb);
+ return cb();
+ }
+ };
+
+ return Fourchan;
+
+}).call(this);
+
+IDColor = (function() {
+ var IDColor;
+
+ IDColor = {
+ init: function() {
+ var ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Color User IDs'])) {
+ return;
+ }
+ this.ids = $.dict();
+ this.ids['Heaven'] = [0, 0, 0, '#fff'];
+ return Callbacks.Post.push({
+ name: 'Color User IDs',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var rgb, span, style, uid;
+ if (this.isClone || !((uid = this.info.uniqueID) && (span = this.nodes.uniqueID))) {
+ return;
+ }
+ rgb = IDColor.ids[uid] || IDColor.compute(uid);
+ style = span.style;
+ style.color = rgb[3];
+ style.backgroundColor = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
+ return $.addClass(span, 'painted');
+ },
+ compute: function(uid) {
+ var hash, rgb;
+ hash = g.SITE.uidColor ? g.SITE.uidColor(uid) : parseInt(uid, 16);
+ rgb = [(hash >> 16) & 0xFF, (hash >> 8) & 0xFF, hash & 0xFF];
+ rgb.push($.luma(rgb) > 125 ? '#000' : '#fff');
+ return this.ids[uid] = rgb;
+ }
+ };
+
+ return IDColor;
+
+}).call(this);
+
+IDHighlight = (function() {
+ var IDHighlight;
+
+ IDHighlight = {
+ init: function() {
+ var ref;
+ if ((ref = g.VIEW) !== 'index' && ref !== 'thread') {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'Highlight by User ID',
+ cb: this.node
+ });
+ },
+ uniqueID: null,
+ node: function() {
+ if (this.nodes.uniqueIDRoot) {
+ $.on(this.nodes.uniqueIDRoot, 'click', IDHighlight.click(this));
+ }
+ if (this.nodes.capcode) {
+ $.on(this.nodes.capcode, 'click', IDHighlight.click(this));
+ }
+ if (!this.isClone) {
+ return IDHighlight.set(this);
+ }
+ },
+ set: function(post) {
+ var match;
+ match = (post.info.uniqueID || post.info.capcode) === IDHighlight.uniqueID;
+ return $[match ? 'addClass' : 'rmClass'](post.nodes.post, 'highlight');
+ },
+ click: function(post) {
+ return function() {
+ var uniqueID;
+ uniqueID = post.info.uniqueID || post.info.capcode;
+ IDHighlight.uniqueID = IDHighlight.uniqueID === uniqueID ? null : uniqueID;
+ return g.posts.forEach(IDHighlight.set);
+ };
+ }
+ };
+
+ return IDHighlight;
+
+}).call(this);
+
+IDPostCount = (function() {
+ var IDPostCount;
+
+ IDPostCount = {
+ init: function() {
+ if (!(g.VIEW === 'thread' && Conf['Count Posts by ID'])) {
+ return;
+ }
+ Callbacks.Thread.push({
+ name: 'Count Posts by ID',
+ cb: function() {
+ return IDPostCount.thread = this;
+ }
+ });
+ return Callbacks.Post.push({
+ name: 'Count Posts by ID',
+ cb: this.node
+ });
+ },
+ node: function() {
+ if (this.nodes.uniqueID && this.thread === IDPostCount.thread) {
+ return $.on(this.nodes.uniqueID, 'mouseover', IDPostCount.count);
+ }
+ },
+ count: function() {
+ var n, uniqueID;
+ uniqueID = Get.postFromNode(this).info.uniqueID;
+ n = 0;
+ IDPostCount.thread.posts.forEach(function(post) {
+ if (post.info.uniqueID === uniqueID) {
+ return n++;
+ }
+ });
+ return this.title = n + " post" + (n === 1 ? '' : 's') + " by this ID";
+ }
+ };
+
+ return IDPostCount;
+
+}).call(this);
+
+Keybinds = (function() {
+ var Keybinds;
+
+ Keybinds = {
+ init: function() {
+ var hotkey, init;
+ if (!Conf['Keybinds']) {
+ return;
+ }
+ for (hotkey in Config.hotkeys) {
+ $.sync(hotkey, Keybinds.sync);
+ }
+ init = function() {
+ var i, len, node, ref;
+ $.off(d, '4chanXInitFinished', init);
+ $.on(d, 'keydown', Keybinds.keydown);
+ ref = $$('[accesskey]');
+ for (i = 0, len = ref.length; i < len; i++) {
+ node = ref[i];
+ node.removeAttribute('accesskey');
+ }
+ };
+ return $.on(d, '4chanXInitFinished', init);
+ },
+ sync: function(key, hotkey) {
+ return Conf[hotkey] = key;
+ },
+ keydown: function(e) {
+ var base, base1, catalog, i, key, len, notification, notifications, post, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, searchInput, target, thread, threadRoot;
+ if (!(key = Keybinds.keyCode(e))) {
+ return;
+ }
+ target = e.target;
+ if ((ref = target.nodeName) === 'INPUT' || ref === 'TEXTAREA') {
+ if (!(/(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key) && !/^Alt\+(\d|Up|Down|Left|Right)$/.test(key))) {
+ return;
+ }
+ }
+ if ((ref1 = g.VIEW) === 'index' || ref1 === 'thread') {
+ threadRoot = Nav.getThread();
+ thread = Get.threadFromRoot(threadRoot);
+ }
+ switch (key) {
+ case Conf['Toggle board list']:
+ if (!Conf['Custom Board Navigation']) {
+ return;
+ }
+ Header.toggleBoardList();
+ break;
+ case Conf['Toggle header']:
+ Header.toggleBarVisibility();
+ break;
+ case Conf['Open empty QR']:
+ if (!QR.postingIsEnabled) {
+ return;
+ }
+ Keybinds.qr();
+ break;
+ case Conf['Open QR']:
+ if (!(QR.postingIsEnabled && threadRoot)) {
+ return;
+ }
+ Keybinds.qr(threadRoot);
+ break;
+ case Conf['Open settings']:
+ Settings.open();
+ break;
+ case Conf['Close']:
+ if (Settings.dialog) {
+ Settings.close();
+ } else if ((notifications = $$('.notification')).length) {
+ for (i = 0, len = notifications.length; i < len; i++) {
+ notification = notifications[i];
+ $('.close', notification).click();
+ }
+ } else if (QR.nodes && !(QR.nodes.el.hidden || window.getComputedStyle(QR.nodes.form).display === 'none')) {
+ if (Conf['Persistent QR']) {
+ QR.hide();
+ } else {
+ QR.close();
+ }
+ } else if (Embedding.lastEmbed) {
+ Embedding.closeFloat();
+ } else {
+ return;
+ }
+ break;
+ case Conf['Spoiler tags']:
+ if (target.nodeName !== 'TEXTAREA') {
+ return;
+ }
+ Keybinds.tags('spoiler', target);
+ break;
+ case Conf['Code tags']:
+ if (target.nodeName !== 'TEXTAREA') {
+ return;
+ }
+ Keybinds.tags('code', target);
+ break;
+ case Conf['Eqn tags']:
+ if (target.nodeName !== 'TEXTAREA') {
+ return;
+ }
+ Keybinds.tags('eqn', target);
+ break;
+ case Conf['Math tags']:
+ if (target.nodeName !== 'TEXTAREA') {
+ return;
+ }
+ Keybinds.tags('math', target);
+ break;
+ case Conf['SJIS tags']:
+ if (target.nodeName !== 'TEXTAREA') {
+ return;
+ }
+ Keybinds.tags('sjis', target);
+ break;
+ case Conf['Toggle sage']:
+ if (!(QR.nodes && !QR.nodes.el.hidden)) {
+ return;
+ }
+ Keybinds.sage();
+ break;
+ case Conf['Toggle Cooldown']:
+ if (!(QR.nodes && !QR.nodes.el.hidden && $.hasClass(QR.nodes.fileSubmit, 'custom-cooldown'))) {
+ return;
+ }
+ QR.toggleCustomCooldown();
+ break;
+ case Conf['Post from URL']:
+ if (!QR.postingIsEnabled) {
+ return;
+ }
+ QR.handleUrl('');
+ break;
+ case Conf['Add new post']:
+ if (!QR.postingIsEnabled) {
+ return;
+ }
+ QR.addPost();
+ break;
+ case Conf['Submit QR']:
+ if (!(QR.nodes && !QR.nodes.el.hidden)) {
+ return;
+ }
+ if (!QR.status()) {
+ QR.submit();
+ }
+ break;
+ case Conf['Update']:
+ switch (g.VIEW) {
+ case 'thread':
+ if (!ThreadUpdater.enabled) {
+ return;
+ }
+ ThreadUpdater.update();
+ break;
+ case 'index':
+ if (!Index.enabled) {
+ return;
+ }
+ Index.update();
+ break;
+ default:
+ return;
+ }
+ break;
+ case Conf['Watch']:
+ if (!(ThreadWatcher.enabled && thread)) {
+ return;
+ }
+ ThreadWatcher.toggle(thread);
+ break;
+ case Conf['Update thread watcher']:
+ if (!ThreadWatcher.enabled) {
+ return;
+ }
+ ThreadWatcher.buttonFetchAll();
+ break;
+ case Conf['Toggle thread watcher']:
+ if (!ThreadWatcher.enabled) {
+ return;
+ }
+ ThreadWatcher.toggleWatcher();
+ break;
+ case Conf['Toggle threading']:
+ if (!QuoteThreading.ready) {
+ return;
+ }
+ QuoteThreading.toggleThreading();
+ break;
+ case Conf['Mark thread read']:
+ if (!(g.VIEW === 'index' && thread && UnreadIndex.enabled)) {
+ return;
+ }
+ UnreadIndex.markRead.call(threadRoot);
+ break;
+ case Conf['Expand image']:
+ if (!(ImageExpand.enabled && threadRoot)) {
+ return;
+ }
+ post = Get.postFromNode(Keybinds.post(threadRoot));
+ if (post.file) {
+ ImageExpand.toggle(post);
+ }
+ break;
+ case Conf['Expand images']:
+ if (!ImageExpand.enabled) {
+ return;
+ }
+ ImageExpand.cb.toggleAll();
+ break;
+ case Conf['Open Gallery']:
+ if (!Gallery.enabled) {
+ return;
+ }
+ Gallery.cb.toggle();
+ break;
+ case Conf['fappeTyme']:
+ if (!((ref2 = FappeTyme.nodes) != null ? ref2.fappe : void 0)) {
+ return;
+ }
+ FappeTyme.toggle('fappe');
+ break;
+ case Conf['werkTyme']:
+ if (!((ref3 = FappeTyme.nodes) != null ? ref3.werk : void 0)) {
+ return;
+ }
+ FappeTyme.toggle('werk');
+ break;
+ case Conf['Front page']:
+ if (Index.enabled) {
+ Index.userPageNav(1);
+ } else {
+ location.href = "/" + g.BOARD + "/";
+ }
+ break;
+ case Conf['Open front page']:
+ $.open(location.origin + "/" + g.BOARD + "/");
+ break;
+ case Conf['Next page']:
+ if (!(g.VIEW === 'index' && !(typeof (base = g.SITE).isOnePage === "function" ? base.isOnePage(g.BOARD) : void 0))) {
+ return;
+ }
+ if (Index.enabled) {
+ if ((ref4 = Conf['Index Mode']) !== 'paged' && ref4 !== 'infinite') {
+ return;
+ }
+ $('.next button', Index.pagelist).click();
+ } else {
+ if ((ref5 = $(g.SITE.selectors.nav.next)) != null) {
+ ref5.click();
+ }
+ }
+ break;
+ case Conf['Previous page']:
+ if (!(g.VIEW === 'index' && !(typeof (base1 = g.SITE).isOnePage === "function" ? base1.isOnePage(g.BOARD) : void 0))) {
+ return;
+ }
+ if (Index.enabled) {
+ if ((ref6 = Conf['Index Mode']) !== 'paged' && ref6 !== 'infinite') {
+ return;
+ }
+ $('.prev button', Index.pagelist).click();
+ } else {
+ if ((ref7 = $(g.SITE.selectors.nav.prev)) != null) {
+ ref7.click();
+ }
+ }
+ break;
+ case Conf['Search form']:
+ if (g.VIEW !== 'index') {
+ return;
+ }
+ searchInput = Index.enabled ? Index.searchInput : g.SITE.selectors.searchBox ? $(g.SITE.selectors.searchBox) : void 0;
+ if (!searchInput) {
+ return;
+ }
+ Header.scrollToIfNeeded(searchInput);
+ searchInput.focus();
+ break;
+ case Conf['Paged mode']:
+ if (!Index.enabledOn(g.BOARD)) {
+ return;
+ }
+ location.href = g.VIEW === 'index' ? '#paged' : "/" + g.BOARD + "/#paged";
+ break;
+ case Conf['Infinite scrolling mode']:
+ if (!Index.enabledOn(g.BOARD)) {
+ return;
+ }
+ location.href = g.VIEW === 'index' ? '#infinite' : "/" + g.BOARD + "/#infinite";
+ break;
+ case Conf['All pages mode']:
+ if (!Index.enabledOn(g.BOARD)) {
+ return;
+ }
+ location.href = g.VIEW === 'index' ? '#all-pages' : "/" + g.BOARD + "/#all-pages";
+ break;
+ case Conf['Open catalog']:
+ if (!(catalog = CatalogLinks.catalog())) {
+ return;
+ }
+ location.href = catalog;
+ break;
+ case Conf['Cycle sort type']:
+ if (!Index.enabled) {
+ return;
+ }
+ Index.cycleSortType();
+ break;
+ case Conf['Next thread']:
+ if (!(g.VIEW === 'index' && threadRoot)) {
+ return;
+ }
+ Nav.scroll(+1);
+ break;
+ case Conf['Previous thread']:
+ if (!(g.VIEW === 'index' && threadRoot)) {
+ return;
+ }
+ Nav.scroll(-1);
+ break;
+ case Conf['Expand thread']:
+ if (!(g.VIEW === 'index' && threadRoot)) {
+ return;
+ }
+ ExpandThread.toggle(thread);
+ Header.scrollTo(threadRoot);
+ break;
+ case Conf['Open thread']:
+ if (!(g.VIEW === 'index' && threadRoot)) {
+ return;
+ }
+ Keybinds.open(thread);
+ break;
+ case Conf['Open thread tab']:
+ if (!(g.VIEW === 'index' && threadRoot)) {
+ return;
+ }
+ Keybinds.open(thread, true);
+ break;
+ case Conf['Next reply']:
+ if (!threadRoot) {
+ return;
+ }
+ Keybinds.hl(+1, threadRoot);
+ break;
+ case Conf['Previous reply']:
+ if (!threadRoot) {
+ return;
+ }
+ Keybinds.hl(-1, threadRoot);
+ break;
+ case Conf['Deselect reply']:
+ if (!threadRoot) {
+ return;
+ }
+ Keybinds.hl(0, threadRoot);
+ break;
+ case Conf['Hide']:
+ if (!(thread && ThreadHiding.db)) {
+ return;
+ }
+ Header.scrollTo(threadRoot);
+ ThreadHiding.toggle(thread);
+ break;
+ case Conf['Quick Filter MD5']:
+ if (!threadRoot) {
+ return;
+ }
+ post = Keybinds.post(threadRoot);
+ Keybinds.hl(+1, threadRoot);
+ Filter.quickFilterMD5.call(post, e);
+ break;
+ case Conf['Previous Post Quoting You']:
+ if (!(threadRoot && QuoteYou.db)) {
+ return;
+ }
+ QuoteYou.cb.seek('preceding');
+ break;
+ case Conf['Next Post Quoting You']:
+ if (!(threadRoot && QuoteYou.db)) {
+ return;
+ }
+ QuoteYou.cb.seek('following');
+ break;
+ default:
+ return;
+ }
+ e.preventDefault();
+ return e.stopPropagation();
+ },
+ keyCode: function(e) {
+ var kc, key;
+ key = (function() {
+ switch (kc = e.keyCode) {
+ case 8:
+ return '';
+ case 13:
+ return 'Enter';
+ case 27:
+ return 'Esc';
+ case 32:
+ return 'Space';
+ case 37:
+ return 'Left';
+ case 38:
+ return 'Up';
+ case 39:
+ return 'Right';
+ case 40:
+ return 'Down';
+ case 188:
+ return 'Comma';
+ case 190:
+ return 'Period';
+ case 191:
+ return 'Slash';
+ case 59:
+ case 186:
+ return 'Semicolon';
+ default:
+ if ((48 <= kc && kc <= 57) || (65 <= kc && kc <= 90)) {
+ return String.fromCharCode(kc).toLowerCase();
+ } else if ((96 <= kc && kc <= 105)) {
+ return String.fromCharCode(kc - 48).toLowerCase();
+ } else {
+ return null;
+ }
+ }
+ })();
+ if (key) {
+ if (e.altKey) {
+ key = 'Alt+' + key;
+ }
+ if (e.ctrlKey) {
+ key = 'Ctrl+' + key;
+ }
+ if (e.metaKey) {
+ key = 'Meta+' + key;
+ }
+ if (e.shiftKey) {
+ key = 'Shift+' + key;
+ }
+ }
+ return key;
+ },
+ post: function(thread) {
+ var s;
+ s = g.SITE.selectors;
+ return $("" + s.postContainer + s.highlightable.reply + "." + g.SITE.classes.highlight, thread) || $("" + (g.SITE.isOPContainerThread ? s.thread : s.postContainer) + s.highlightable.op, thread);
+ },
+ qr: function(thread) {
+ QR.open();
+ if (thread != null) {
+ QR.quote.call(Keybinds.post(thread));
+ }
+ return QR.nodes.com.focus();
+ },
+ tags: function(tag, ta) {
+ var range, selEnd, selStart, value;
+ BoardConfig.ready(function() {
+ var config, supported;
+ config = g.BOARD.config;
+ supported = (function() {
+ switch (tag) {
+ case 'spoiler':
+ return !!config.spoilers;
+ case 'code':
+ return !!config.code_tags;
+ case 'math':
+ case 'eqn':
+ return !!config.math_tags;
+ case 'sjis':
+ return !!config.sjis_tags;
+ }
+ })();
+ if (!supported) {
+ return new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20);
+ }
+ });
+ value = ta.value;
+ selStart = ta.selectionStart;
+ selEnd = ta.selectionEnd;
+ ta.value = value.slice(0, selStart) + ("[" + tag + "]") + value.slice(selStart, selEnd) + ("[/" + tag + "]") + value.slice(selEnd);
+ range = ("[" + tag + "]").length + selEnd;
+ ta.setSelectionRange(range, range);
+ return $.event('input', null, ta);
+ },
+ sage: function() {
+ var isSage;
+ isSage = /sage/i.test(QR.nodes.email.value);
+ return QR.nodes.email.value = isSage ? "" : "sage";
+ },
+ open: function(thread, tab) {
+ var url;
+ if (g.VIEW !== 'index') {
+ return;
+ }
+ url = Get.url('thread', thread);
+ if (tab) {
+ return $.open(url);
+ } else {
+ return location.href = url;
+ }
+ },
+ hl: function(delta, thread) {
+ var axis, height, highlight, i, len, next, postEl, replies, reply, replySelector, root;
+ replySelector = "" + g.SITE.selectors.postContainer + g.SITE.selectors.highlightable.reply;
+ highlight = g.SITE.classes.highlight;
+ postEl = $(replySelector + "." + highlight, thread);
+ if (!delta) {
+ if (postEl) {
+ $.rmClass(postEl, highlight);
+ }
+ return;
+ }
+ if (postEl) {
+ height = postEl.getBoundingClientRect().height;
+ if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) {
+ root = Get.postFromNode(postEl).nodes.root;
+ axis = delta === +1 ? 'following' : 'preceding';
+ if (!(next = $.x(axis + "-sibling::" + g.SITE.xpath.replyContainer + "[not(@hidden) and not(child::div[@class='stub'])][1]", root))) {
+ return;
+ }
+ if (!next.matches(replySelector)) {
+ next = $(replySelector, next);
+ }
+ Header.scrollToIfNeeded(next, delta === +1);
+ $.addClass(next, highlight);
+ $.rmClass(postEl, highlight);
+ return;
+ }
+ $.rmClass(postEl, highlight);
+ }
+ replies = $$(replySelector, thread);
+ if (delta === -1) {
+ replies.reverse();
+ }
+ for (i = 0, len = replies.length; i < len; i++) {
+ reply = replies[i];
+ if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) {
+ $.addClass(reply, highlight);
+ return;
+ }
+ }
+ }
+ };
+
+ return Keybinds;
+
+}).call(this);
+
+ModContact = (function() {
+ var ModContact;
+
+ ModContact = {
+ init: function() {
+ var ref;
+ if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread'))) {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'Mod Contact Links',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var links, moveNote, moved;
+ if (this.isClone || !$.hasOwn(ModContact.specific, this.info.capcode)) {
+ return;
+ }
+ links = $.el('span', {
+ className: 'contact-links brackets-wrap'
+ });
+ $.extend(links, ModContact.template(this.info.capcode));
+ $.after(this.nodes.capcode, links);
+ if ((moved = this.info.comment.match(/This thread was moved to >>>\/(\w+)\//)) && $.hasOwn(ModContact.moveNote, moved[1])) {
+ moveNote = $.el('div', {
+ className: 'move-note'
+ });
+ $.extend(moveNote, ModContact.moveNote[moved[1]]);
+ return $.add(this.nodes.post, moveNote);
+ }
+ },
+ template: function(capcode) {
+ return {innerHTML: "
feedback " + (ModContact.specific[capcode]()).innerHTML};
+ },
+ specific: {
+ Mod: function() {
+ return {innerHTML: "
IRC "};
+ },
+ Manager: function() {
+ return ModContact.specific.Mod();
+ },
+ Developer: function() {
+ return {innerHTML: "
github "};
+ },
+ Admin: function() {
+ return {innerHTML: "
twitter "};
+ }
+ },
+ moveNote: {
+ qa: {innerHTML: "Moving a thread to /qa/ does not imply mods will read it. If you wish to contact mods, use
feedback (https://www.4chan.org/feedback) or
IRC (https://www.4chan-x.net/4chan-irc.html) ."}
+ }
+ };
+
+ return ModContact;
+
+}).call(this);
+
+Nav = (function() {
+ var Nav;
+
+ Nav = {
+ init: function() {
+ var append, next, prev, span;
+ switch (g.VIEW) {
+ case 'index':
+ if (!Conf['Index Navigation']) {
+ return;
+ }
+ break;
+ case 'thread':
+ if (!Conf['Reply Navigation']) {
+ return;
+ }
+ break;
+ default:
+ return;
+ }
+ span = $.el('span', {
+ id: 'navlinks'
+ });
+ prev = $.el('a', {
+ textContent: '▲',
+ href: 'javascript:;'
+ });
+ next = $.el('a', {
+ textContent: '▼',
+ href: 'javascript:;'
+ });
+ $.on(prev, 'click', this.prev);
+ $.on(next, 'click', this.next);
+ $.add(span, [prev, $.tn(' '), next]);
+ append = function() {
+ $.off(d, '4chanXInitFinished', append);
+ return $.add(d.body, span);
+ };
+ return $.on(d, '4chanXInitFinished', append);
+ },
+ prev: function() {
+ if (g.VIEW === 'thread') {
+ return window.scrollTo(0, 0);
+ } else {
+ return Nav.scroll(-1);
+ }
+ },
+ next: function() {
+ if (g.VIEW === 'thread') {
+ return window.scrollTo(0, d.body.scrollHeight);
+ } else {
+ return Nav.scroll(+1);
+ }
+ },
+ getThread: function() {
+ var i, len, ref, thread, threadRoot;
+ if (g.VIEW === 'thread') {
+ return g.threads.get(g.BOARD + "." + g.THREADID).nodes.root;
+ }
+ if ($.hasClass(doc, 'catalog-mode')) {
+ return;
+ }
+ ref = $$(g.SITE.selectors.thread);
+ for (i = 0, len = ref.length; i < len; i++) {
+ threadRoot = ref[i];
+ thread = Get.threadFromRoot(threadRoot);
+ if (thread.isHidden && !thread.stub) {
+ continue;
+ }
+ if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) {
+ return threadRoot;
+ }
+ }
+ },
+ scroll: function(delta) {
+ var axis, extra, next, ref, thread, top;
+ if ((ref = d.activeElement) != null) {
+ ref.blur();
+ }
+ thread = Nav.getThread();
+ if (!thread) {
+ return;
+ }
+ axis = delta === +1 ? 'following' : 'preceding';
+ if (next = $.x(axis + "-sibling::" + g.SITE.xpath.thread + "[not(@hidden)][1]", thread)) {
+ top = Header.getTopOf(thread);
+ if (delta === +1 && top < 5 || delta === -1 && top > -5) {
+ thread = next;
+ }
+ }
+ extra = Header.getTopOf(thread) + doc.clientHeight - d.body.getBoundingClientRect().bottom;
+ if (extra > 0) {
+ d.body.style.marginBottom = extra + "px";
+ }
+ Header.scrollTo(thread);
+ if (extra > 0 && !Nav.haveExtra) {
+ Nav.haveExtra = true;
+ return $.on(d, 'scroll', Nav.removeExtra);
+ }
+ },
+ removeExtra: function() {
+ var extra;
+ extra = doc.clientHeight - d.body.getBoundingClientRect().bottom;
+ if (extra > 0) {
+ return d.body.style.marginBottom = extra + "px";
+ } else {
+ d.body.style.marginBottom = '';
+ delete Nav.haveExtra;
+ return $.off(d, 'scroll', Nav.removeExtra);
+ }
+ }
+ };
+
+ return Nav;
+
+}).call(this);
+
+NormalizeURL = (function() {
+ var NormalizeURL;
+
+ NormalizeURL = {
+ init: function() {
+ var pathname;
+ if (!Conf['Normalize URL']) {
+ return;
+ }
+ pathname = location.pathname.split(/\/+/);
+ if (g.SITE.software === 'yotsuba') {
+ switch (g.VIEW) {
+ case 'thread':
+ pathname[2] = 'thread';
+ pathname = pathname.slice(0, 4);
+ break;
+ case 'index':
+ pathname = pathname.slice(0, 3);
+ }
+ }
+ pathname = pathname.join('/');
+ if (location.pathname !== pathname) {
+ return history.replaceState(history.state, '', location.protocol + "//" + location.host + pathname + location.hash);
+ }
+ }
+ };
+
+ return NormalizeURL;
+
+}).call(this);
+
+PSA = (function() {
+ var PSA,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ PSA = {
+ init: function() {
+ var announcement, el;
+ if (g.SITE.software === 'yotsuba' && g.BOARD.ID === 'qa') {
+ announcement = {
+ innerHTML: "Stay in touch with your
/qa/ friends !"
+ };
+ el = $.el('div', {
+ className: 'fcx-announcement'
+ }, announcement);
+ $.onExists(doc, '.boardBanner', function(banner) {
+ return $.after(banner, el);
+ });
+ }
+ if ('samachan.org' in Conf['siteProperties'] && indexOf.call(Conf['PSAseen'], 'samachan') < 0) {
+ el = $.el('span', {
+ innerHTML: "
Looking for a new home? Some former Samachan users are regrouping on SushiChan. (a message from 4chan X)"
+ });
+ return Main.ready(function() {
+ new Notice('info', el);
+ Conf['PSAseen'].push('samachan');
+ return $.set('PSAseen', Conf['PSAseen']);
+ });
+ }
+ }
+ };
+
+ return PSA;
+
+}).call(this);
+
+PSAHiding = (function() {
+ var PSAHiding,
+ slice = [].slice;
+
+ PSAHiding = {
+ init: function() {
+ if (!(Conf['Announcement Hiding'] && g.SITE.selectors.psa)) {
+ return;
+ }
+ $.addClass(doc, 'hide-announcement');
+ $.onExists(doc, g.SITE.selectors.psa, this.setup);
+ return $.ready(function() {
+ if (!$(g.SITE.selectors.psa)) {
+ return $.rmClass(doc, 'hide-announcement');
+ }
+ });
+ },
+ setup: function(psa) {
+ var btn, entry, hr, ref, ref1, ref2;
+ PSAHiding.psa = psa;
+ PSAHiding.text = (ref = psa.dataset.utc) != null ? ref : psa.innerHTML;
+ if (g.SITE.selectors.psaTop && (hr = (ref1 = $(g.SITE.selectors.psaTop)) != null ? ref1.previousElementSibling : void 0) && hr.nodeName === 'HR') {
+ PSAHiding.hr = hr;
+ }
+ PSAHiding.content = $.el('div');
+ entry = {
+ el: $.el('a', {
+ textContent: 'Show announcement',
+ className: 'show-announcement',
+ href: 'javascript:;'
+ }),
+ order: 50,
+ open: function() {
+ return psa.hidden;
+ }
+ };
+ Header.menu.addEntry(entry);
+ $.on(entry.el, 'click', PSAHiding.toggle);
+ PSAHiding.btn = btn = $.el('a', {
+ title: 'Mark announcement as read and hide.',
+ className: 'hide-announcement-button fa fa-minus-square',
+ href: 'javascript:;'
+ });
+ $.on(btn, 'click', PSAHiding.toggle);
+ if (((ref2 = psa.firstChild) != null ? ref2.tagName : void 0) === 'HR') {
+ $.after(psa.firstChild, btn);
+ } else {
+ $.prepend(psa, btn);
+ }
+ PSAHiding.sync(Conf['hiddenPSAList']);
+ $.rmClass(doc, 'hide-announcement');
+ return $.sync('hiddenPSAList', PSAHiding.sync);
+ },
+ toggle: function() {
+ var hide, set;
+ hide = $.hasClass(this, 'hide-announcement-button');
+ set = function(hiddenPSAList) {
+ if (hide) {
+ return hiddenPSAList[g.SITE.ID] = PSAHiding.text;
+ } else {
+ return delete hiddenPSAList[g.SITE.ID];
+ }
+ };
+ set(Conf['hiddenPSAList']);
+ PSAHiding.sync(Conf['hiddenPSAList']);
+ return $.get('hiddenPSAList', Conf['hiddenPSAList'], function(arg) {
+ var hiddenPSAList;
+ hiddenPSAList = arg.hiddenPSAList;
+ set(hiddenPSAList);
+ return $.set('hiddenPSAList', hiddenPSAList);
+ });
+ },
+ sync: function(hiddenPSAList) {
+ var content, psa, ref;
+ psa = PSAHiding.psa, content = PSAHiding.content;
+ psa.hidden = hiddenPSAList[g.SITE.ID] === PSAHiding.text;
+ if (psa.hidden) {
+ $.add(content, slice.call(psa.childNodes));
+ } else {
+ $.add(psa, slice.call(content.childNodes));
+ }
+ return (ref = PSAHiding.hr) != null ? ref.hidden = psa.hidden : void 0;
+ }
+ };
+
+ return PSAHiding;
+
+}).call(this);
+
+PassMessage = (function() {
+ var PassMessage;
+
+ PassMessage = {
+ init: function() {
+ var close, msg;
+ if (Conf['passMessageClosed']) {
+ return;
+ }
+ msg = $.el('div', {
+ className: 'box-outer top-box'
+ }, {innerHTML: "
"});
+ msg.style.cssText = 'padding-bottom: 0;';
+ close = $('a', msg);
+ $.on(close, 'click', function() {
+ $.rm(msg);
+ return $.set('passMessageClosed', true);
+ });
+ return $.ready(function() {
+ var hd;
+ if ((hd = $.id('hd'))) {
+ return $.after(hd, msg);
+ } else {
+ return $.prepend(d.body, msg);
+ }
+ });
+ }
+ };
+
+ return PassMessage;
+
+}).call(this);
+
+PostJumper = (function() {
+ var PostJumper;
+
+ PostJumper = {
+ init: function() {
+ var ref;
+ if (!(Conf['Unique ID and Capcode Navigation'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) {
+ return;
+ }
+ this.buttons = this.makeButtons();
+ return Callbacks.Post.push({
+ name: 'Post Jumper',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var buttons, i, len, ref;
+ if (this.isClone) {
+ ref = $$('.postJumper', this.nodes.info);
+ for (i = 0, len = ref.length; i < len; i++) {
+ buttons = ref[i];
+ PostJumper.addListeners(buttons);
+ }
+ return;
+ }
+ if (this.nodes.uniqueIDRoot) {
+ PostJumper.addButtons(this, 'uniqueID');
+ }
+ if (this.nodes.capcode) {
+ return PostJumper.addButtons(this, 'capcode');
+ }
+ },
+ addButtons: function(post, type) {
+ var buttons, value;
+ value = post.info[type];
+ buttons = PostJumper.buttons.cloneNode(true);
+ $.extend(buttons.dataset, {
+ type: type,
+ value: value
+ });
+ $.after(post.nodes[type + (type === 'capcode' ? '' : 'Root')], buttons);
+ return PostJumper.addListeners(buttons);
+ },
+ addListeners: function(buttons) {
+ $.on(buttons.firstChild, 'click', PostJumper.buttonClick);
+ return $.on(buttons.lastChild, 'click', PostJumper.buttonClick);
+ },
+ buttonClick: function() {
+ var dir, toJumper;
+ dir = $.hasClass(this, 'prev') ? -1 : 1;
+ if ((toJumper = PostJumper.find(this.parentNode, dir))) {
+ return PostJumper.scroll(this.parentNode, toJumper);
+ }
+ },
+ find: function(jumper, dir) {
+ var axis, jumper2, ref, type, value, xpath;
+ ref = jumper.dataset, type = ref.type, value = ref.value;
+ xpath = "span[contains(@class,\"postJumper\") and @data-value=\"" + value + "\" and @data-type=\"" + type + "\"]";
+ axis = dir < 0 ? 'preceding' : 'following';
+ jumper2 = jumper;
+ while ((jumper2 = $.x(axis + "::" + xpath, jumper2))) {
+ if (jumper2.getBoundingClientRect().height) {
+ return jumper2;
+ }
+ }
+ if ((jumper2 = $.x("(//" + xpath + ")[" + (dir < 0 ? 'last()' : '1') + "]"))) {
+ if (jumper2.getBoundingClientRect().height) {
+ return jumper2;
+ }
+ }
+ while ((jumper2 = $.x(axis + "::" + xpath, jumper2)) && jumper2 !== jumper) {
+ if (jumper2.getBoundingClientRect().height) {
+ return jumper2;
+ }
+ }
+ return null;
+ },
+ makeButtons: function() {
+ var charNext, charPrev, classNext, classPrev, span;
+ charPrev = '\u23EB';
+ charNext = '\u23EC';
+ classPrev = 'prev';
+ classNext = 'next';
+ span = $.el('span', {
+ className: 'postJumper'
+ });
+ $.extend(span, {innerHTML: "
" + E(charPrev) + " " + E(charNext) + " "});
+ return span;
+ },
+ scroll: function(fromJumper, toJumper) {
+ var destPos, prevPos;
+ prevPos = fromJumper.getBoundingClientRect().top;
+ destPos = toJumper.getBoundingClientRect().top;
+ return window.scrollBy(0, destPos - prevPos);
+ }
+ };
+
+ return PostJumper;
+
+}).call(this);
+
+RelativeDates = (function() {
+ var RelativeDates,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ RelativeDates = {
+ INTERVAL: $.MINUTE / 2,
+ init: function() {
+ var ref;
+ if (((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || Index.enabled) {
+ this.flush();
+ $.on(d, 'visibilitychange PostsInserted', this.flush);
+ }
+ if (Conf['Relative Post Dates']) {
+ return Callbacks.Post.push({
+ name: 'Relative Post Dates',
+ cb: this.node
+ });
+ }
+ },
+ node: function() {
+ var dateEl;
+ if (!this.info.date) {
+ return;
+ }
+ dateEl = this.nodes.date;
+ if (Conf['Relative Date Title']) {
+ $.on(dateEl, 'mouseover', (function(_this) {
+ return function() {
+ return RelativeDates.hover(_this);
+ };
+ })(this));
+ return;
+ }
+ if (this.isClone) {
+ return;
+ }
+ dateEl.title = dateEl.textContent;
+ return RelativeDates.update(this);
+ },
+ relative: function(diff, now, date, abbrev) {
+ var days, months, number, rounded, unit, years;
+ unit = (number = diff / $.DAY) >= 1 ? (years = now.getFullYear() - date.getFullYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second');
+ rounded = Math.round(number);
+ if (abbrev) {
+ unit = unit === 'month' ? 'mo' : unit[0];
+ } else {
+ if (rounded !== 1) {
+ unit += 's';
+ }
+ }
+ if (abbrev) {
+ return "" + rounded + unit;
+ } else {
+ return rounded + " " + unit + " ago";
+ }
+ },
+ stale: [],
+ flush: function() {
+ var data, i, len, now, ref;
+ if (d.hidden) {
+ return;
+ }
+ now = new Date();
+ ref = RelativeDates.stale;
+ for (i = 0, len = ref.length; i < len; i++) {
+ data = ref[i];
+ RelativeDates.update(data, now);
+ }
+ RelativeDates.stale = [];
+ clearTimeout(RelativeDates.timeout);
+ return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL);
+ },
+ hover: function(post) {
+ var date, diff, now;
+ date = post.info.date;
+ now = new Date();
+ diff = now - date;
+ return post.nodes.date.title = RelativeDates.relative(diff, now, date);
+ },
+ update: function(data, now) {
+ var abbrev, date, diff, i, isPost, len, ref, relative, singlePost;
+ isPost = data instanceof Post;
+ if (isPost) {
+ date = data.info.date;
+ abbrev = false;
+ } else {
+ date = new Date(+data.dataset.utc);
+ abbrev = !!data.dataset.abbrev;
+ }
+ now || (now = new Date());
+ diff = now - date;
+ relative = RelativeDates.relative(diff, now, date, abbrev);
+ if (isPost) {
+ ref = [data].concat(data.clones);
+ for (i = 0, len = ref.length; i < len; i++) {
+ singlePost = ref[i];
+ singlePost.nodes.date.firstChild.textContent = relative;
+ }
+ } else {
+ data.firstChild.textContent = relative;
+ }
+ return RelativeDates.setOwnTimeout(diff, data);
+ },
+ setOwnTimeout: function(diff, data) {
+ var delay;
+ delay = diff < $.MINUTE ? $.SECOND - (diff + $.SECOND / 2) % $.SECOND : diff < $.HOUR ? $.MINUTE - (diff + $.MINUTE / 2) % $.MINUTE : diff < $.DAY ? $.HOUR - (diff + $.HOUR / 2) % $.HOUR : $.DAY - (diff + $.DAY / 2) % $.DAY;
+ return setTimeout(RelativeDates.markStale, delay, data);
+ },
+ markStale: function(data) {
+ if (indexOf.call(RelativeDates.stale, data) >= 0) {
+ return;
+ }
+ if (data instanceof Post && !g.posts.get(data.fullID)) {
+ return;
+ }
+ if (data instanceof Element && !doc.contains(data)) {
+ return;
+ }
+ return RelativeDates.stale.push(data);
+ }
+ };
+
+ return RelativeDates;
+
+}).call(this);
+
+RemoveSpoilers = (function() {
+ var RemoveSpoilers,
+ slice = [].slice;
+
+ RemoveSpoilers = {
+ init: function() {
+ if (Conf['Reveal Spoilers']) {
+ $.addClass(doc, 'reveal-spoilers');
+ }
+ if (!Conf['Remove Spoilers']) {
+ return;
+ }
+ Callbacks.Post.push({
+ name: 'Reveal Spoilers',
+ cb: this.node
+ });
+ if (g.VIEW === 'archive') {
+ return $.ready(function() {
+ return RemoveSpoilers.unspoiler($.id('arc-list'));
+ });
+ }
+ },
+ node: function() {
+ return RemoveSpoilers.unspoiler(this.nodes.comment);
+ },
+ unspoiler: function(el) {
+ var i, len, span, spoiler, spoilers;
+ spoilers = $$(g.SITE.selectors.spoiler, el);
+ for (i = 0, len = spoilers.length; i < len; i++) {
+ spoiler = spoilers[i];
+ span = $.el('span', {
+ className: 'removed-spoiler'
+ });
+ $.replace(spoiler, span);
+ $.add(span, slice.call(spoiler.childNodes));
+ }
+ }
+ };
+
+ return RemoveSpoilers;
+
+}).call(this);
+
+Report = (function() {
+ var Report;
+
+ Report = {
+ init: function() {
+ var match;
+ if (!(match = location.search.match(/\bno=(\d+)/))) {
+ return;
+ }
+ Captcha.replace.init();
+ this.postID = +match[1];
+ return $.ready(this.ready);
+ },
+ ready: function() {
+ $.addStyle(CSS.report);
+ if (Conf['Archive Report']) {
+ Report.archive();
+ }
+ new MutationObserver(function() {
+ Report.fit('iframe[src^="https://www.google.com/recaptcha/api2/frame"]');
+ return Report.fit('body');
+ }).observe(d.body, {
+ childList: true,
+ attributes: true,
+ subtree: true
+ });
+ return Report.fit('body');
+ },
+ fit: function(selector) {
+ var dy, el;
+ if (!((el = $(selector, doc)) && getComputedStyle(el).visibility !== 'hidden')) {
+ return;
+ }
+ dy = el.getBoundingClientRect().bottom - doc.clientHeight + 8;
+ if (dy > 0) {
+ return window.resizeBy(0, dy);
+ }
+ },
+ archive: function() {
+ var enabled, fieldset, form, match, message, reason, submit, types, urls;
+ if (!(urls = Redirect.report(g.BOARD.ID)).length) {
+ return;
+ }
+ form = $('form');
+ types = $.id('reportTypes');
+ message = $('h3');
+ fieldset = $.el('fieldset', {
+ id: 'archive-report',
+ hidden: true
+ }, {innerHTML: "
Report illegal content to archivesDetails Submit "});
+ enabled = $('#archive-report-enabled', fieldset);
+ reason = $('#archive-report-reason', fieldset);
+ submit = $('#archive-report-submit', fieldset);
+ $.on(enabled, 'change', function() {
+ return reason.disabled = !this.checked;
+ });
+ if (form && types) {
+ fieldset.hidden = !$('[value="31"]', types).checked;
+ $.on(types, 'change', function(e) {
+ fieldset.hidden = e.target.value !== '31';
+ return Report.fit('body');
+ });
+ $.after(types, fieldset);
+ Report.fit('body');
+ $.one(form, 'submit', function(e) {
+ if (!fieldset.hidden && enabled.checked) {
+ e.preventDefault();
+ return Report.archiveSubmit(urls, reason.value, (function(_this) {
+ return function(results) {
+ _this.action = '#archiveresults=' + encodeURIComponent(JSON.stringify(results));
+ return _this.submit();
+ };
+ })(this));
+ }
+ });
+ } else if (message) {
+ fieldset.hidden = /Report submitted!/.test(message.textContent);
+ $.on(enabled, 'change', function() {
+ return submit.hidden = !this.checked;
+ });
+ $.after(message, fieldset);
+ $.on(submit, 'click', function() {
+ return Report.archiveSubmit(urls, reason.value, Report.archiveResults);
+ });
+ }
+ if ((match = location.hash.match(/^#archiveresults=(.*)$/))) {
+ try {
+ return Report.archiveResults(JSON.parse(decodeURIComponent(match[1])));
+ } catch (error) {}
+ }
+ },
+ archiveSubmit: function(urls, reason, cb) {
+ var fn, form, i, len, name, ref, results, url;
+ form = $.formData({
+ board: g.BOARD.ID,
+ num: Report.postID,
+ reason: reason
+ });
+ results = [];
+ fn = function(name, url) {
+ return $.ajax(url, {
+ onloadend: function() {
+ results.push([
+ name, this.response || {
+ error: ''
+ }
+ ]);
+ if (results.length === urls.length) {
+ return cb(results);
+ }
+ },
+ form: form
+ });
+ };
+ for (i = 0, len = urls.length; i < len; i++) {
+ ref = urls[i], name = ref[0], url = ref[1];
+ fn(name, url);
+ }
+ },
+ archiveResults: function(results) {
+ var fieldset, i, len, line, name, ref, response;
+ fieldset = $.id('archive-report');
+ for (i = 0, len = results.length; i < len; i++) {
+ ref = results[i], name = ref[0], response = ref[1];
+ line = $.el('h3', {
+ className: 'archive-report-response'
+ });
+ if ('success' in response) {
+ $.addClass(line, 'archive-report-success');
+ line.textContent = name + ": " + response.success;
+ } else {
+ $.addClass(line, 'archive-report-error');
+ line.textContent = name + ": " + (response.error || 'Error reporting post.');
+ }
+ if (fieldset) {
+ $.before(fieldset, line);
+ } else {
+ $.add(d.body, line);
+ }
+ }
+ }
+ };
+
+ return Report;
+
+}).call(this);
+
+ThreadLinks = (function() {
+ var ThreadLinks;
+
+ ThreadLinks = {
+ init: function() {
+ if (!(g.VIEW === 'index' && Conf['Open Threads in New Tab'])) {
+ return;
+ }
+ Callbacks.Post.push({
+ name: 'Thread Links',
+ cb: this.node
+ });
+ return Callbacks.CatalogThread.push({
+ name: 'Thread Links',
+ cb: this.catalogNode
+ });
+ },
+ node: function() {
+ if (this.isReply || this.isClone) {
+ return;
+ }
+ return ThreadLinks.process(this.nodes.reply);
+ },
+ catalogNode: function() {
+ return ThreadLinks.process(this.nodes.thumb.parentNode);
+ },
+ process: function(link) {
+ return link.target = '_blank';
+ }
+ };
+
+ return ThreadLinks;
+
+}).call(this);
+
+Time = (function() {
+ var Time;
+
+ Time = {
+ init: function() {
+ var ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Time Formatting'])) {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'Time Formatting',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var textContent;
+ if (!this.info.date || this.isClone) {
+ return;
+ }
+ textContent = this.nodes.date.textContent;
+ return this.nodes.date.textContent = textContent.match(/^\s*/)[0] + Time.format(Conf['time'], this.info.date) + textContent.match(/\s*$/)[0];
+ },
+ format: function(formatString, date) {
+ return formatString.replace(/%(.)/g, function(s, c) {
+ if ($.hasOwn(Time.formatters, c)) {
+ return Time.formatters[c].call(date);
+ } else {
+ return s;
+ }
+ });
+ },
+ day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ localeFormat: function(date, options, defaultValue) {
+ if (Conf['timeLocale']) {
+ try {
+ return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date);
+ } catch (error) {}
+ }
+ return defaultValue;
+ },
+ localeFormatPart: function(date, options, part, defaultValue) {
+ var parts;
+ if (Conf['timeLocale']) {
+ try {
+ parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date);
+ return parts.map(function(x) {
+ if (x.type === part) {
+ return x.value;
+ } else {
+ return '';
+ }
+ }).join('');
+ } catch (error) {}
+ }
+ return defaultValue;
+ },
+ zeroPad: function(n) {
+ if (n < 10) {
+ return "0" + n;
+ } else {
+ return n;
+ }
+ },
+ formatters: {
+ a: function() {
+ return Time.localeFormat(this, {
+ weekday: 'short'
+ }, Time.day[this.getDay()].slice(0, 3));
+ },
+ A: function() {
+ return Time.localeFormat(this, {
+ weekday: 'long'
+ }, Time.day[this.getDay()]);
+ },
+ b: function() {
+ return Time.localeFormat(this, {
+ month: 'short'
+ }, Time.month[this.getMonth()].slice(0, 3));
+ },
+ B: function() {
+ return Time.localeFormat(this, {
+ month: 'long'
+ }, Time.month[this.getMonth()]);
+ },
+ d: function() {
+ return Time.zeroPad(this.getDate());
+ },
+ e: function() {
+ return this.getDate();
+ },
+ H: function() {
+ return Time.zeroPad(this.getHours());
+ },
+ I: function() {
+ return Time.zeroPad(this.getHours() % 12 || 12);
+ },
+ k: function() {
+ return this.getHours();
+ },
+ l: function() {
+ return this.getHours() % 12 || 12;
+ },
+ m: function() {
+ return Time.zeroPad(this.getMonth() + 1);
+ },
+ M: function() {
+ return Time.zeroPad(this.getMinutes());
+ },
+ p: function() {
+ return Time.localeFormatPart(this, {
+ hour: 'numeric',
+ hour12: true
+ }, 'dayperiod', (this.getHours() < 12 ? 'AM' : 'PM'));
+ },
+ P: function() {
+ return Time.formatters.p.call(this).toLowerCase();
+ },
+ S: function() {
+ return Time.zeroPad(this.getSeconds());
+ },
+ y: function() {
+ return this.getFullYear().toString().slice(2);
+ },
+ Y: function() {
+ return this.getFullYear();
+ },
+ '%': function() {
+ return '%';
+ }
+ }
+ };
+
+ return Time;
+
+}).call(this);
+
+Tinyboard = (function() {
+ var Tinyboard;
+
+ Tinyboard = {
+ init: function() {
+ if (g.SITE.software !== 'tinyboard') {
+ return;
+ }
+ if (g.VIEW === 'thread') {
+ return Main.ready(function() {
+ return $.global(function() {
+ var base, boardID, form, originalNoko, ref, ref1, ref2, threadID;
+ ref = document.currentScript.dataset, boardID = ref.boardID, threadID = ref.threadID;
+ threadID = +threadID;
+ form = document.querySelector('form[name="post"]');
+ window.$(document).ajaxComplete(function(event, request, settings) {
+ var detail, noko, postID, redirect, ref1, ref2;
+ if (settings.url !== form.action) {
+ return;
+ }
+ if (!(postID = +((ref1 = request.responseJSON) != null ? ref1.id : void 0))) {
+ return;
+ }
+ detail = {
+ boardID: boardID,
+ threadID: threadID,
+ postID: postID
+ };
+ try {
+ ref2 = request.responseJSON, redirect = ref2.redirect, noko = ref2.noko;
+ if (redirect && (typeof originalNoko !== "undefined" && originalNoko !== null) && !originalNoko && !noko) {
+ detail.redirect = redirect;
+ }
+ } catch (error) {}
+ event = new CustomEvent('QRPostSuccessful', {
+ bubbles: true,
+ detail: detail
+ });
+ return document.dispatchEvent(event);
+ });
+ originalNoko = (ref1 = window.tb_settings) != null ? (ref2 = ref1.ajax) != null ? ref2.always_noko_replies : void 0 : void 0;
+ return ((base = (window.tb_settings || (window.tb_settings = {}))).ajax || (base.ajax = {})).always_noko_replies = true;
+ }, {
+ boardID: g.BOARD.ID,
+ threadID: g.THREADID
+ });
+ });
+ }
+ }
+ };
+
+ return Tinyboard;
+
+}).call(this);
+
+Favicon = (function() {
+ var Favicon;
+
+ Favicon = {
+ init: function() {
+ return $.asap((function() {
+ return d.head && (Favicon.el = $('link[rel="shortcut icon"]', d.head));
+ }), Favicon.initAsap);
+ },
+ set: function(status) {
+ Favicon.status = status;
+ if (Favicon.el) {
+ Favicon.el.href = Favicon[status];
+ return $.add(d.head, Favicon.el);
+ }
+ },
+ initAsap: function() {
+ var href;
+ Favicon.el.type = 'image/x-icon';
+ href = Favicon.el.href;
+ Favicon.isSFW = /ws\.ico$/.test(href);
+ Favicon["default"] = href;
+ Favicon["switch"]();
+ if (Favicon.status) {
+ return Favicon.set(Favicon.status);
+ }
+ },
+ "switch": function() {
+ var f, i, items, t;
+ items = {
+ ferongr: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///9zBQC/AADpDAP/gID/q6voCwJJTwpOAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42q1TOwrCQBB9s0FRtJI0WoqFtSLYegoP4gVSeJsUHsHSI3iFeIqRXXgwrhlXwYHHhLwPTB7B36abBCV+0pA4DUBQUNZYQptGtW3jtoKyxgoe0yrBCoyZfL/5ioQ3URZOXW9I341l3oo+NXEZiW4CEuIzvPECopED4OaZ3RNmeAm4u+a8Jr5f17VyVoL8fr8qcltzwlyyj2iqcgPOQ9ExkHAITgD75bYBe0A5S4H/P9htuWMF3QXoQpwaKeT+lnsC6JE5I6aq6fEAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8AcH4AtswA2PJ55fKi6fIA1/FtpPADAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4y2NgoBq4/vE/HJOsBiRQUIfA2AzBqQYqUfn00/9FLz+BaQxDCKqBmX7jExijKEDSDJPHrnnbGQhGV4RmOFwdVkNwhQMheYwQxhaIi7b9Z9A3gWAQm2BUoQOgRhgA8o7j1ozLC4LCyAZcx6kZI5qg4kLKqggDFFWxJySsUQVzlb4pwgAJaTRvokcVNgOqOv8zcHBCsL07DgNg8YsczzA5MxtUL+DMD8g0slxI/H8GQ/P/DJKyeKIRpglXZsIiBwBhP5O+VbI/JgAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8oeQBJ3ABV/wHM/7Lu/+ZU/gAqUP3dAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4y2NgoBYI+cfwH4ZJVgMS0KhEYGyG4FQDkzjzf9P/d/+fgWl0QwiqgSkI/c8IxsgKkDXD5LFq9rwDweiK0A2HqcNqCK5wICSPEcLYAtH+AMN/IXMIBrEJRie6OEgjDAC5x3FqxuUFNiEUA67j1IweTTBxBQ1puAG86jgSEraogskJWSBcwCGF5k30qMJmgMFEhv/MXBAs5oLDAFj8IsczTE7UEeECbhU8+QGZRpaTi2b4L2zF8J9TGk80wjThykzY5AAW/2O1C2mIbgAAAABJRU5ErkJggg=='],
+ 'xat-': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEX+AACLkZFub2yfaF3zZGIAAAD/AAD/iYr/zs8IPcF6AAAABXRSTlMAeprJ7xzg6IEAAABZSURBVAjXY2DABKGBSkqioQwMrGmpxsZhaQEMDGFpIa5pqSCRtPDSNJBIaGh5eShQDYOye0V7iREKAyQFYoiCFAcyILQDGcGmEEZYkGoqiMHKysAQEICwGwAAjBmBqhYlagAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAPFBMVEUAAACEgoBva2ilamDxcG7IaWYgFBNOSEf//f0PDQwBAAA7LCwAAAD/AAD+hIX+m5z+zc5HAADPAAAGAADl032uAAAADHRSTlMAzNv0/vz+6v3+7ALrmfyXAAAAaUlEQVQY042PyxKAIAhFAc1eV7T6/3/N8VXOtAgWwBm4ANEPA8AswpySXHvvYZLlpBNrh9pDtcSqAQ1BUTVIjNUQY5icmwfglmXNgE0d6QBF9GigrU0A9LoM53U1kFzk6SBQuWfD/vHqDUCpBmVKTTM4AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIVBMVEUAAACRjop4dXVpZ2tdcI9dfKdisfMAAAAumMN9xv+s2/+PADT2AAAAB3RSTlMAepGdv83v3HIc4QAAAFxJREFUCNdjYMAE5YXKRuLlDAzsHe2uIRUdBQwMFR1l6R3tIJGOyukdIJHy8lkry4FqGEwzV62aFozMUAFJOQEZ4iDFhQwI7UBGaTiEUVFs3g5isLMzMBQUIOwGAJRlIu9hk08QAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAACAgYVlc4ljsu4AAAAAAAAAAAAumMODyP6b1P6e1f/g8v89msgSIiwNFxwbPU3tQYj5AAAABnRSTlMAxej+9VTmD9ciAAAAZElEQVQI12NgwARpiUKKYmkMDGzlZUpK6eUJDAzp5clm5WUgkfKMtnKQSFpa54o0oBoGJYvZO88+gjJu7wMyhIBS2SCGGFDxaxADpP32NjAjSe0bSFd6epIaWISNjYEhJRVhNwAGlyJpYtcvcAAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUfJSCRi5Frbm9dn19082KR/30AAABmzDOq/5vZ/9Gt/vt2AAAABnRSTlMAe5rJ7/4vxEp4AAAAWUlEQVQI12NgwARpiUpKYmkMDGzlZcbG6eUJDAzp5Slu5WUgkfLUsHKQSFpaRGsaUA2DsmvnjBAjFAZICsQQAylOZEBoBzKSzSCM9CS1MhCDjY2BISEBYTcAtgAcKSK2vuIAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAM1BMVEUAAACBj39tfm1qj2RepFlu2VQAAQAAAAAAAABmyzOX/oSr/pus/pzk/98PGgtatC4CBAI1ENblAAAACHRSTlMA09/p9v77ig0SBcQAAABnSURBVBjTjY9LDsAgCEQRsR2xWu9/2hK/adJFYQG8wABEPwyAYzNnSatjjPAiviWLhPCqI1R7HBrQdCmGBrEETTmnUAq/QMm5dODHyAQOXXR1zLUGsIEI7lonMGfeHQTq9xw4P159AIxSBSC53km7AAAAAElFTkSuQmCC'],
+ Mayhem: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABFklEQVR4AZ2R4WqEMBCEFy1yiJQQ14gcIhIuFBFR+qPQ93+v66QMksrlTwMfkZ2ZZbMKTgVqYIDl3YAbeCM31lJP/Zul4MAEPJjBQGNDLGsz8PQ6aqLAP5PTdd1WlmU09mSKtdTDRgrkzspJPKq6RxMahfj9yhOzQEZwZAwfzrk1ox3MXibIN8hO4MAjeV72CemJGWblnRsOYOdoGw0jebB20BPAwKzUQPlrFhrXFw1Wagu9yuzZwINzVAZCURRL+gRr7Wd8Vtqg4Th/lsUmewyk9WQ/A7NiwJz5VV/GmO+MNjMrFvh/NPDMigHTaeJN09a27ZHRJmalBg54CgfvAGYSLpoHjlmpuAwFdzDy7oGS/qIpM9UPFGg1b1kUlssAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABR0lEQVR4AYWSQWq0QBCFCw0SRIK0PQ4hiIhEZBhEySLyewUPEMgqR/JIXiDhzz7kKKYePIZajEzDRxfV9dWU3SO6IiVWUsVxT5R75Y4gTmwNnUh4kCulUiuV8sjChDjmKtaUcHgmHsnNrMPh0IVhiMIjKZGzNXDoyhMzF7C89z2KtFGD+FoNXEUKZdgpaPM8P++cDXTtBDca7EyQK8+bXTufYBccuvLAG26UnqN1LCgI4g/lm7zTgSux4vk0J8rnKw3+m1//pBPbBrVyGZVNmiAITviEtm3t+D+2QcJx7GUxlN4594K4ZY75Xzh0JVWqnad6TdP0H+LRNBjHcYNDV5xS32qwaC4my7Lwn6guu5QoomgbdFmWDYhnM8E8zxscuhLzPWtKA/dGqUizrityX9M0YX+DQ1ciXobnP6vgfmTOM7Znnk70B58pPaEvx+epAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/ElEQVR4AZ3RUWqEMBSF4ftQZAhSREQJIiIXpQwi+tSldkFdWPsLhyEE0ocKH2Fyzg1mNJ4KAQ1arTUeeJMH6qwTUJmCHjMcC6KKtbSIylzdXpl18J/k4fdTpUFmPLOOa9bGe+P4+n5RYYfLXuiMsAlXofBxK2QXpvwN/jqg+AY91vR+pStk+apZe0fEhhMXDhUmWXEoO9WNmrWAzvRPq7jnB2jvUGfWTEgPcJzZFTbZk/0Tnh5QI+af6lVGvq/Do2atwVL4VJ+3QrZo1lr4Pw5wzVqDWaV7SUvHrZDNmrWAHq7g0rphkS3LXDMBVqFGhxGT1gGdDFnWaab6BRmXRvbxDmYiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABQElEQVR4AY2SQUrEQBBFS9CMNFEkhAQdYmiCIUgcZlYGc4VsBcGVF/AuWXme4F7RtXiVWF9+Y9MYtOHRTdX/NZWaEj2RYpQTJeEdK4fKPuA7DjSGXiQkU0qlUqxySmFMEsYsNSU8zEmK4OwdEbmkKCclYoGmolfWCGyenh1O0EJE2gXNWpFC2S0IGrCQ29EbdPCPAmEHmXIxByf8hDAPD71yzAnXypatbSgoAN8Pyju5h4deMUrqJk1z+0uBN+/XX+gxfoFK2QafUJO2aRq//Q+/QIx2wr+Kwq0rusrP/QKf9MTCtbQLf9U1wNvYnz3qug45S68kSvVXgbPbx3nvYPXNOI7cRPWySukK+DcGCvA+urqZ3RmGAbmSXjFK5rpwW8nhWVJP04TYa9/3uO/goVciDiPlZhW8c8ZAHuRSeqIv32FK/GYGL8YAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/ElEQVR4AZ3RUWqEMBSF4ftQZAihDCKKiAQJShERQx+6o662e2p/4TCEQF468BEm95yLovFr4PBEq9PjgTd5wBcZp6559AiIWDAq6KXV3aJMUMfDOsTf7Mf/XaFBAvYiE9W16b74/vl8UeBAlKOSmWAzUiXwcavMkrrFE9QXVJ+gx5q9XvUVivmqrr1jxIYLCacCs6y6S8psGNU1hw4Bu4JHuUB3pzJBHZcviLiKV9jkyO4vxHyBx1h+qlcY5b2Wj+raE0vlU33dKrNFXWsR/7EgqmtPBIXuIw+dt8osqGsOPaIGSeeGRbZiFtVxsAYeHSbMOgd0MhSzTp3mD4RaQX4aW3NMAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABP0lEQVR4AYWS0UqFQBCGhziImNRBRImDmUgiIaF0kWSP4AMEXXXTE/QiPpL3UdR19Crb/PAvLEtyFj5mmfn/cdxd0RUokbJXEsZYCZUd4D72NBG8wkKmlEqtVMoFhTFJmKuoKelBTVIkjbNE5IainJTIeZqaXjkg8fp+Z7GCjiLQbWgOihTKsCFowUZtoNef4HgDf4JMuTbe8n/Br8NDr5zxhBul52i3FBQE+xflmzzTA69ESmpPmubunwZfztc/6IncBrXSe7/QkK5tW3f8H7dBjHH8q6Kwt033V6Hb4JeeWPgsq42rugfYZ92psWscRwMPvZIo9bEGD2+F2YUnBizLwpeoXnYpbQM34kAB9peP58aueZ4NPPRKxPusaRoYG6UizbquyH1O04T4RA+8EvAwUr6sgjFnDuReLaUn+ANygUa7+9SCWgAAAABJRU5ErkJggg=='],
+ '4chanJS': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAAD/AABmZmYA/wBD99DBAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAAAul8NnZ2f/AAD7B+mqAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAABmzDNmZmb/AAC8/wCMAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII='],
+ Original: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX/////AAD///8AAABBZmS3AAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhElEQVR42q1RwQnAMAjMu5M4guAKXa4j5dUROo5tipSDcrFChUONd0di2m/hEGVOHDyIPufgwAFASDkpoSzmBrkJ2UMyR9LsJ3rvrqo3Rt1YMIMhhNnOxLMnoMFBxHyJAr2IOBFzA8U+6pLBdmEJTA0aMVjpDd6Loks0s5HZNwYx8tfZCZ0kll7ORffZAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///8ul8P///8AAACaqgkzAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OzvBwcH///8uS/CdAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eILZO5/XI0UAgm7H9tOsu0yGWAQSOoFijHOxOANGqm/LczpOaXs4gISrPZ+gc2+hO5w2xdwgOjBFUIF+sEJrhUl9JFr+badFwR+BfqlmGUJAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///9mzDP///8AAACT0n1lAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztmzDPBwcH///+rsf3XAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eIDhbn/cTVSCCTsfmw7ybbLZIBBIKkXKKU0E4M3aKT+tjCn5xiziwuIsNr7BTb7ErrDZV/AAaIHdwgV6AcnuFaU0Eeu5dt2XiUyBjCQ2bIrAAAAAElFTkSuQmCC'],
+ 'Metro': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAC/AAD///8dAAApAABsAAAHAAA4AACQAAAsAABMCpCvAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAA1/H///8AISUALzQAeokACAkAQEcAorYAMTcE9WFNAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAABV/wH///8NKAASOAAwkQADCgAZTABAwQATOwC5e3VGAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=']
+ };
+ items = $.getOwn(items, Conf['favicon']);
+ f = Favicon;
+ t = 'data:image/png;base64,';
+ i = 0;
+ while (items[i]) {
+ items[i] = t + items[i++];
+ }
+ f.unreadDead = items[0], f.unreadDeadY = items[1], f.unreadSFW = items[2], f.unreadSFWY = items[3], f.unreadNSFW = items[4], f.unreadNSFWY = items[5];
+ return f.update();
+ },
+ update: function() {
+ if (this.isSFW) {
+ this.unread = this.unreadSFW;
+ return this.unreadY = this.unreadSFWY;
+ } else {
+ this.unread = this.unreadNSFW;
+ return this.unreadY = this.unreadNSFWY;
+ }
+ },
+ SFW: '//s.4cdn.org/image/favicon-ws.ico',
+ NSFW: '//s.4cdn.org/image/favicon.ico',
+ dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
+ logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAGlJREFUWMPtlkEKADEIA/tJP9lXLttQto2yHxgDHozTi0ToGK2WKZZ+HAQQMZc+xBwI4EZ+wAC2IfPuSIDOZJrSZQEAX9eVJhhwIuUYAnQe8rhAEMAZlTI2MID9f5Clyh0JeE1V1ZEAvB4qDfwuJTSGRAAAAABJRU5ErkJggg=='
+ };
+
+ return Favicon;
+
+}).call(this);
+
+MarkNewIPs = (function() {
+ var MarkNewIPs;
+
+ MarkNewIPs = {
+ init: function() {
+ if (!(g.SITE.software === 'yotsuba' && g.VIEW === 'thread' && Conf['Mark New IPs'])) {
+ return;
+ }
+ return Callbacks.Thread.push({
+ name: 'Mark New IPs',
+ cb: this.node
+ });
+ },
+ node: function() {
+ MarkNewIPs.ipCount = this.ipCount;
+ MarkNewIPs.postCount = this.posts.keys.length;
+ return $.on(d, 'ThreadUpdate', MarkNewIPs.onUpdate);
+ },
+ onUpdate: function(e) {
+ var deletedPosts, fullID, i, ipCount, j, k, len, len1, newPosts, postCount, ref;
+ ref = e.detail, ipCount = ref.ipCount, postCount = ref.postCount, newPosts = ref.newPosts, deletedPosts = ref.deletedPosts;
+ if (ipCount == null) {
+ return;
+ }
+ switch (ipCount - MarkNewIPs.ipCount) {
+ case postCount - MarkNewIPs.postCount + deletedPosts.length:
+ i = MarkNewIPs.ipCount;
+ for (j = 0, len = newPosts.length; j < len; j++) {
+ fullID = newPosts[j];
+ MarkNewIPs.markNew(g.posts.get(fullID), ++i);
+ }
+ break;
+ case -deletedPosts.length:
+ for (k = 0, len1 = newPosts.length; k < len1; k++) {
+ fullID = newPosts[k];
+ MarkNewIPs.markOld(g.posts.get(fullID));
+ }
+ }
+ MarkNewIPs.ipCount = ipCount;
+ return MarkNewIPs.postCount = postCount;
+ },
+ markNew: function(post, ipCount) {
+ var counter, suffix;
+ suffix = (Math.floor(ipCount / 10)) % 10 === 1 ? 'th' : ['st', 'nd', 'rd'][ipCount % 10 - 1] || 'th';
+ counter = $.el('span', {
+ className: 'ip-counter',
+ textContent: "(" + ipCount + ")"
+ });
+ post.nodes.nameBlock.title = "This is the " + ipCount + suffix + " IP in the thread.";
+ $.add(post.nodes.nameBlock, [$.tn(' '), counter]);
+ return $.addClass(post.nodes.root, 'new-ip');
+ },
+ markOld: function(post) {
+ post.nodes.nameBlock.title = 'Not the first post from this IP.';
+ return $.addClass(post.nodes.root, 'old-ip');
+ }
+ };
+
+ return MarkNewIPs;
+
+}).call(this);
+
+ReplyPruning = (function() {
+ var ReplyPruning;
+
+ ReplyPruning = {
+ init: function() {
+ var el, label;
+ if (!(g.VIEW === 'thread' && Conf['Reply Pruning'])) {
+ return;
+ }
+ this.container = $.frag();
+ this.summary = $.el('span', {
+ hidden: true,
+ className: 'summary'
+ });
+ this.summary.style.cursor = 'pointer';
+ $.on(this.summary, 'click', (function(_this) {
+ return function() {
+ _this.inputs.enabled.checked = !_this.inputs.enabled.checked;
+ return $.event('change', null, _this.inputs.enabled);
+ };
+ })(this));
+ label = UI.checkbox('Prune Replies', 'Show Last', Conf['Prune All Threads']);
+ el = $.el('span', {
+ title: 'Maximum number of replies to show.'
+ }, {innerHTML: "
"});
+ $.prepend(el, label);
+ this.inputs = {
+ enabled: label.firstElementChild,
+ replies: el.lastElementChild
+ };
+ this.setEnabled.call(this.inputs.enabled);
+ $.on(this.inputs.enabled, 'change', this.setEnabled);
+ $.on(this.inputs.replies, 'change', $.cb.value);
+ Header.menu.addEntry({
+ el: el,
+ order: 190
+ });
+ return Callbacks.Thread.push({
+ name: 'Reply Pruning',
+ cb: this.node
+ });
+ },
+ position: 0,
+ hidden: 0,
+ hiddenFiles: 0,
+ total: 0,
+ totalFiles: 0,
+ setEnabled: function() {
+ var other;
+ other = QuoteThreading.input;
+ if (this.checked && (other != null ? other.checked : void 0)) {
+ other.checked = false;
+ $.event('change', null, other);
+ }
+ return ReplyPruning.active = this.checked;
+ },
+ showIfHidden: function(id) {
+ if (ReplyPruning.container && $("#" + id, ReplyPruning.container)) {
+ ReplyPruning.inputs.enabled.checked = false;
+ return $.event('change', null, ReplyPruning.inputs.enabled);
+ }
+ },
+ node: function() {
+ var ref;
+ ReplyPruning.thread = this;
+ if (this.isSticky) {
+ ReplyPruning.active = ReplyPruning.inputs.enabled.checked = true;
+ if (QuoteThreading.input) {
+ Conf['Thread Quotes'] = QuoteThreading.input.checked = false;
+ }
+ }
+ this.posts.forEach(function(post) {
+ if (post.isReply) {
+ ReplyPruning.total++;
+ if (post.file) {
+ return ReplyPruning.totalFiles++;
+ }
+ }
+ });
+ if (ReplyPruning.active && /^#p\d+$/.test(location.hash) && (1 <= (ref = this.posts.keys.indexOf(location.hash.slice(2))) && ref < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0))) {
+ ReplyPruning.active = ReplyPruning.inputs.enabled.checked = false;
+ }
+ $.after(this.OP.nodes.root, ReplyPruning.summary);
+ $.on(ReplyPruning.inputs.enabled, 'change', ReplyPruning.update);
+ $.on(ReplyPruning.inputs.replies, 'change', ReplyPruning.update);
+ $.on(d, 'ThreadUpdate', ReplyPruning.updateCount);
+ $.on(d, 'ThreadUpdate', ReplyPruning.update);
+ return ReplyPruning.update();
+ },
+ updateCount: function(e) {
+ var fullID, i, len, ref;
+ if (e.detail[404]) {
+ return;
+ }
+ ref = e.detail.newPosts;
+ for (i = 0, len = ref.length; i < len; i++) {
+ fullID = ref[i];
+ ReplyPruning.total++;
+ if (g.posts.get(fullID).file) {
+ ReplyPruning.totalFiles++;
+ }
+ }
+ },
+ update: function() {
+ var boardTop, frag, hidden1, hidden2, node, oldPos, post, posts;
+ hidden1 = ReplyPruning.hidden;
+ hidden2 = ReplyPruning.active ? Math.max(ReplyPruning.total - +Conf["Max Replies"], 0) : 0;
+ oldPos = d.body.clientHeight - window.scrollY;
+ posts = ReplyPruning.thread.posts;
+ if (ReplyPruning.hidden < hidden2) {
+ while (ReplyPruning.hidden < hidden2 && ReplyPruning.position < posts.keys.length) {
+ post = posts.get(posts.keys[ReplyPruning.position++]);
+ if (post.isReply && !post.isFetchedQuote) {
+ while ((node = ReplyPruning.summary.nextSibling) && node !== post.nodes.root) {
+ $.add(ReplyPruning.container, node);
+ }
+ $.add(ReplyPruning.container, post.nodes.root);
+ ReplyPruning.hidden++;
+ if (post.file) {
+ ReplyPruning.hiddenFiles++;
+ }
+ }
+ }
+ } else if (ReplyPruning.hidden > hidden2) {
+ frag = $.frag();
+ while (ReplyPruning.hidden > hidden2 && ReplyPruning.position > 0) {
+ post = posts.get(posts.keys[--ReplyPruning.position]);
+ if (post.isReply && !post.isFetchedQuote) {
+ while ((node = ReplyPruning.container.lastChild) && node !== post.nodes.root) {
+ $.prepend(frag, node);
+ }
+ $.prepend(frag, post.nodes.root);
+ ReplyPruning.hidden--;
+ if (post.file) {
+ ReplyPruning.hiddenFiles--;
+ }
+ }
+ }
+ $.after(ReplyPruning.summary, frag);
+ $.event('PostsInserted', null, ReplyPruning.summary.parentNode);
+ }
+ ReplyPruning.summary.textContent = ReplyPruning.active ? g.SITE.Build.summaryText('+', ReplyPruning.hidden, ReplyPruning.hiddenFiles) : g.SITE.Build.summaryText('-', ReplyPruning.total, ReplyPruning.totalFiles);
+ ReplyPruning.summary.hidden = ReplyPruning.total <= +Conf["Max Replies"];
+ if (hidden1 !== hidden2 && (boardTop = Header.getTopOf($('.board'))) < 0) {
+ return window.scrollBy(0, Math.max(d.body.clientHeight - oldPos, window.scrollY + boardTop) - window.scrollY);
+ }
+ }
+ };
+
+ return ReplyPruning;
+
+}).call(this);
+
+ThreadStats = (function() {
+ var ThreadStats;
+
+ ThreadStats = {
+ postCount: 0,
+ fileCount: 0,
+ postIndex: 0,
+ init: function() {
+ var base, sc, statsHTML, statsTitle;
+ if (g.VIEW !== 'thread' || !Conf['Thread Stats']) {
+ return;
+ }
+ if (Conf['Page Count in Stats']) {
+ this[(typeof (base = g.SITE).isPrunedByAge === "function" ? base.isPrunedByAge(g.BOARD) : void 0) ? 'showPurgePos' : 'showPage'] = true;
+ }
+ statsHTML = {innerHTML: "
? /
? " + ((Conf["IP Count in Stats"] && g.SITE.hasIPCount) ? " /
? " : "") + ((Conf["Page Count in Stats"]) ? " /
? " : "")};
+ statsTitle = 'Posts / Files';
+ if (Conf['IP Count in Stats'] && g.SITE.hasIPCount) {
+ statsTitle += ' / IPs';
+ }
+ if (Conf['Page Count in Stats']) {
+ statsTitle += (this.showPurgePos ? ' / Purge Position' : ' / Page');
+ }
+ if (Conf['Updater and Stats in Header']) {
+ this.dialog = sc = $.el('span', {
+ id: 'thread-stats',
+ title: statsTitle
+ });
+ $.extend(sc, statsHTML);
+ Header.addShortcut('stats', sc, 200);
+ } else {
+ this.dialog = sc = UI.dialog('thread-stats', {innerHTML: "
" + (statsHTML).innerHTML + "
"});
+ $.addClass(doc, 'float');
+ $.ready(function() {
+ return $.add(d.body, sc);
+ });
+ }
+ this.postCountEl = $('#post-count', sc);
+ this.fileCountEl = $('#file-count', sc);
+ this.ipCountEl = $('#ip-count', sc);
+ this.pageCountEl = $('#page-count', sc);
+ if (this.pageCountEl) {
+ $.on(this.pageCountEl, 'click', ThreadStats.fetchPage);
+ }
+ return Callbacks.Thread.push({
+ name: 'Thread Stats',
+ cb: this.node
+ });
+ },
+ node: function() {
+ ThreadStats.thread = this;
+ ThreadStats.count();
+ ThreadStats.update();
+ ThreadStats.fetchPage();
+ $.on(d, 'PostsInserted', function() {
+ return $.queueTask(ThreadStats.onPostsInserted);
+ });
+ return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate);
+ },
+ count: function() {
+ var i, j, n, post, posts, ref, ref1;
+ posts = ThreadStats.thread.posts;
+ n = posts.keys.length;
+ for (i = j = ref = ThreadStats.postIndex, ref1 = n; j < ref1; i = j += 1) {
+ post = posts.get(posts.keys[i]);
+ if (!post.isFetchedQuote) {
+ ThreadStats.postCount++;
+ ThreadStats.fileCount += post.files.length;
+ }
+ }
+ return ThreadStats.postIndex = n;
+ },
+ onUpdate: function(e) {
+ var fileCount, postCount, ref;
+ if (e.detail[404]) {
+ return;
+ }
+ ref = e.detail, postCount = ref.postCount, fileCount = ref.fileCount;
+ $.extend(ThreadStats, {
+ postCount: postCount,
+ fileCount: fileCount
+ });
+ ThreadStats.postIndex = ThreadStats.thread.posts.keys.length;
+ ThreadStats.update();
+ if (ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1') {
+ return ThreadStats.fetchPage();
+ }
+ },
+ onPostsInserted: function() {
+ if (!(ThreadStats.thread.posts.keys.length > ThreadStats.postIndex)) {
+ return;
+ }
+ ThreadStats.count();
+ ThreadStats.update();
+ if (ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1') {
+ return ThreadStats.fetchPage();
+ }
+ },
+ update: function() {
+ var fileCountEl, ipCountEl, postCountEl, ref, thread;
+ thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl, ipCountEl = ThreadStats.ipCountEl;
+ postCountEl.textContent = ThreadStats.postCount;
+ fileCountEl.textContent = ThreadStats.fileCount;
+ if (ipCountEl != null) {
+ ipCountEl.textContent = (ref = thread.ipCount) != null ? ref : '?';
+ }
+ postCountEl.classList.toggle('warning', thread.postLimit && !thread.isSticky);
+ return fileCountEl.classList.toggle('warning', thread.fileLimit && !thread.isSticky);
+ },
+ fetchPage: function() {
+ if (!ThreadStats.pageCountEl) {
+ return;
+ }
+ clearTimeout(ThreadStats.timeout);
+ if (ThreadStats.thread.isDead) {
+ ThreadStats.pageCountEl.textContent = 'Dead';
+ $.addClass(ThreadStats.pageCountEl, 'warning');
+ return;
+ }
+ ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE);
+ return $.whenModified(g.SITE.urls.threadsListJSON(ThreadStats.thread), 'ThreadStats', ThreadStats.onThreadsLoad);
+ },
+ onThreadsLoad: function() {
+ var i, j, k, l, len, len1, len2, len3, len4, m, nThreads, o, page, pageNum, purgePos, ref, ref1, ref2, ref3, ref4, thread;
+ if (this.status === 200) {
+ if (ThreadStats.showPurgePos) {
+ purgePos = 1;
+ ref = this.response;
+ for (j = 0, len = ref.length; j < len; j++) {
+ page = ref[j];
+ ref1 = page.threads;
+ for (k = 0, len1 = ref1.length; k < len1; k++) {
+ thread = ref1[k];
+ if (thread.no < ThreadStats.thread.ID) {
+ purgePos++;
+ }
+ }
+ }
+ ThreadStats.pageCountEl.textContent = purgePos;
+ return ThreadStats.pageCountEl.classList.toggle('warning', purgePos === 1);
+ } else {
+ i = nThreads = 0;
+ ref2 = this.response;
+ for (l = 0, len2 = ref2.length; l < len2; l++) {
+ page = ref2[l];
+ nThreads += page.threads.length;
+ }
+ ref3 = this.response;
+ for (pageNum = m = 0, len3 = ref3.length; m < len3; pageNum = ++m) {
+ page = ref3[pageNum];
+ ref4 = page.threads;
+ for (o = 0, len4 = ref4.length; o < len4; o++) {
+ thread = ref4[o];
+ if (thread.no === ThreadStats.thread.ID) {
+ ThreadStats.pageCountEl.textContent = pageNum + 1;
+ ThreadStats.pageCountEl.classList.toggle('warning', i >= nThreads - this.response[0].threads.length);
+ ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND);
+ ThreadStats.retry();
+ return;
+ }
+ i++;
+ }
+ }
+ }
+ } else if (this.status === 304) {
+ return ThreadStats.retry();
+ }
+ },
+ retry: function() {
+ if (!(ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1' && !g.SITE.threadModTimeIgnoresSage && ThreadStats.thread.posts.get(ThreadStats.thread.lastPost).info.date > ThreadStats.lastPageUpdate)) {
+ return;
+ }
+ clearTimeout(ThreadStats.timeout);
+ return ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 5 * $.SECOND);
+ }
+ };
+
+ return ThreadStats;
+
+}).call(this);
+
+ThreadUpdater = (function() {
+ var ThreadUpdater,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ ThreadUpdater = {
+ init: function() {
+ var conf, el, input, name, ref, sc, subEntries, updateLink;
+ if (g.VIEW !== 'thread' || !Conf['Thread Updater']) {
+ return;
+ }
+ this.enabled = true;
+ this.audio = $.el('audio');
+ if ($.engine !== 'gecko') {
+ this.audio.src = this.beep;
+ }
+ if (Conf['Updater and Stats in Header']) {
+ this.dialog = sc = $.el('span', {
+ id: 'updater'
+ });
+ $.extend(sc, {innerHTML: "
"});
+ Header.addShortcut('updater', sc, 100);
+ } else {
+ this.dialog = sc = UI.dialog('updater', {innerHTML: "
"});
+ $.addClass(doc, 'float');
+ $.ready(function() {
+ return $.add(d.body, sc);
+ });
+ }
+ this.checkPostCount = 0;
+ this.timer = $('#update-timer', sc);
+ this.status = $('#update-status', sc);
+ $.on(this.timer, 'click', this.update);
+ $.on(this.status, 'click', this.update);
+ updateLink = $.el('span', {
+ className: 'brackets-wrap updatelink'
+ });
+ $.extend(updateLink, {innerHTML: "
Update "});
+ Main.ready(function() {
+ var navLinksBot;
+ if ((navLinksBot = $('.navLinksBot'))) {
+ return $.add(navLinksBot, [$.tn(' '), updateLink]);
+ }
+ });
+ $.on(updateLink.firstElementChild, 'click', this.update);
+ subEntries = [];
+ ref = Config.updater.checkbox;
+ for (name in ref) {
+ conf = ref[name];
+ el = UI.checkbox(name, name);
+ el.title = conf[1];
+ input = el.firstElementChild;
+ $.on(input, 'change', $.cb.checked);
+ if (input.name === 'Scroll BG') {
+ $.on(input, 'change', this.cb.scrollBG);
+ this.cb.scrollBG();
+ } else if (input.name === 'Auto Update') {
+ $.on(input, 'change', this.setInterval);
+ }
+ subEntries.push({
+ el: el
+ });
+ }
+ this.settings = $.el('span', {innerHTML: "
Interval "});
+ $.on(this.settings, 'click', this.intervalShortcut);
+ subEntries.push({
+ el: this.settings
+ });
+ Header.menu.addEntry(this.entry = {
+ el: $.el('span', {
+ textContent: 'Updater'
+ }),
+ order: 110,
+ subEntries: subEntries
+ });
+ return Callbacks.Thread.push({
+ name: 'Thread Updater',
+ cb: this.node
+ });
+ },
+ node: function() {
+ ThreadUpdater.thread = this;
+ ThreadUpdater.root = this.nodes.root;
+ ThreadUpdater.outdateCount = 0;
+ ThreadUpdater.postIDs = [];
+ ThreadUpdater.fileIDs = [];
+ this.posts.forEach(function(post) {
+ ThreadUpdater.postIDs.push(post.ID);
+ if (post.file) {
+ return ThreadUpdater.fileIDs.push(post.ID);
+ }
+ });
+ ThreadUpdater.cb.interval.call($.el('input', {
+ value: Conf['Interval']
+ }));
+ $.on(d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost);
+ $.on(d, 'visibilitychange', ThreadUpdater.cb.visibility);
+ return ThreadUpdater.setInterval();
+ },
+
+ /*
+ http://freesound.org/people/pierrecartoons1979/sounds/90112/
+ cc-by-nc-3.0
+ */
+ beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA',
+ playBeep: function() {
+ var audio;
+ audio = ThreadUpdater.audio;
+ audio.src || (audio.src = ThreadUpdater.beep);
+ if (audio.paused) {
+ return audio.play();
+ } else {
+ return $.one(audio, 'ended', ThreadUpdater.playBeep);
+ }
+ },
+ cb: {
+ checkpost: function(e) {
+ if (e.detail.threadID !== ThreadUpdater.thread.ID) {
+ return;
+ }
+ ThreadUpdater.postID = e.detail.postID;
+ ThreadUpdater.checkPostCount = 0;
+ ThreadUpdater.outdateCount = 0;
+ return ThreadUpdater.setInterval();
+ },
+ visibility: function() {
+ if (d.hidden) {
+ return;
+ }
+ ThreadUpdater.outdateCount = 0;
+ if (ThreadUpdater.seconds > ThreadUpdater.interval) {
+ return ThreadUpdater.setInterval();
+ }
+ },
+ scrollBG: function() {
+ return ThreadUpdater.scrollBG = Conf['Scroll BG'] ? function() {
+ return true;
+ } : function() {
+ return !d.hidden;
+ };
+ },
+ interval: function(e) {
+ var val;
+ val = parseInt(this.value, 10);
+ if (val < 1) {
+ val = 1;
+ }
+ ThreadUpdater.interval = this.value = val;
+ if (e) {
+ return $.cb.value.call(this);
+ }
+ },
+ load: function() {
+ if (this !== ThreadUpdater.req) {
+ return;
+ }
+ switch (this.status) {
+ case 200:
+ ThreadUpdater.parse(this);
+ if (ThreadUpdater.thread.isArchived) {
+ return ThreadUpdater.kill();
+ } else {
+ return ThreadUpdater.setInterval();
+ }
+ break;
+ case 404:
+ return $.ajax(g.SITE.urls.catalogJSON({
+ boardID: ThreadUpdater.thread.board.ID
+ }), {
+ onloadend: function() {
+ var confirmed, i, k, len, len1, page, ref, ref1, thread;
+ if (this.status === 200) {
+ confirmed = true;
+ ref = this.response;
+ for (i = 0, len = ref.length; i < len; i++) {
+ page = ref[i];
+ ref1 = page.threads;
+ for (k = 0, len1 = ref1.length; k < len1; k++) {
+ thread = ref1[k];
+ if (thread.no === ThreadUpdater.thread.ID) {
+ confirmed = false;
+ break;
+ }
+ }
+ }
+ } else {
+ confirmed = false;
+ }
+ if (confirmed) {
+ return ThreadUpdater.kill();
+ } else {
+ return ThreadUpdater.error(this);
+ }
+ }
+ });
+ default:
+ return ThreadUpdater.error(this);
+ }
+ }
+ },
+ kill: function() {
+ ThreadUpdater.thread.kill();
+ ThreadUpdater.setInterval();
+ return $.event('ThreadUpdate', {
+ 404: true,
+ threadID: ThreadUpdater.thread.fullID
+ });
+ },
+ error: function(req) {
+ if (req.status === 304) {
+ ThreadUpdater.set('status', '');
+ }
+ ThreadUpdater.setInterval();
+ if (!req.status) {
+ return ThreadUpdater.set('status', 'Connection Error', 'warning');
+ } else if (req.status !== 304) {
+ return ThreadUpdater.set('status', req.statusText + " (" + req.status + ")", 'warning');
+ }
+ },
+ setInterval: function() {
+ var cur, interval, j, limit;
+ clearTimeout(ThreadUpdater.timeoutID);
+ if (ThreadUpdater.thread.isDead) {
+ ThreadUpdater.set('status', (ThreadUpdater.thread.isArchived ? 'Archived' : '404'), 'warning');
+ ThreadUpdater.set('timer', '');
+ return;
+ }
+ if (ThreadUpdater.postID && ThreadUpdater.checkPostCount < 5) {
+ ThreadUpdater.set('timer', '...', 'loading');
+ ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.update, ++ThreadUpdater.checkPostCount * $.SECOND);
+ return;
+ }
+ if (!Conf['Auto Update']) {
+ ThreadUpdater.set('timer', 'Update');
+ return;
+ }
+ interval = ThreadUpdater.interval;
+ if (Conf['Optional Increase']) {
+ limit = d.hidden ? 10 : 5;
+ j = Math.min(ThreadUpdater.outdateCount, limit);
+ cur = (Math.floor(interval * 0.1) || 1) * j * j;
+ ThreadUpdater.seconds = $.minmax(cur, interval, 300);
+ } else {
+ ThreadUpdater.seconds = interval;
+ }
+ return ThreadUpdater.timeout();
+ },
+ intervalShortcut: function() {
+ var settings;
+ Settings.open('Advanced');
+ settings = $.id('fourchanx-settings');
+ return $('input[name=Interval]', settings).focus();
+ },
+ set: function(name, text, klass) {
+ var el, node;
+ el = ThreadUpdater[name];
+ if (node = el.firstChild) {
+ node.data = text;
+ } else {
+ el.textContent = text;
+ }
+ return el.className = klass != null ? klass : (text === '' ? 'empty' : '');
+ },
+ timeout: function() {
+ if (ThreadUpdater.seconds) {
+ ThreadUpdater.set('timer', ThreadUpdater.seconds);
+ ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.timeout, 1000);
+ } else {
+ ThreadUpdater.outdateCount++;
+ ThreadUpdater.update();
+ }
+ return ThreadUpdater.seconds--;
+ },
+ update: function() {
+ var oldReq;
+ clearTimeout(ThreadUpdater.timeoutID);
+ ThreadUpdater.set('timer', '...', 'loading');
+ if ((oldReq = ThreadUpdater.req)) {
+ delete ThreadUpdater.req;
+ oldReq.abort();
+ }
+ return ThreadUpdater.req = $.whenModified(g.SITE.urls.threadJSON({
+ boardID: ThreadUpdater.thread.board.ID,
+ threadID: ThreadUpdater.thread.ID
+ }), 'ThreadUpdater', ThreadUpdater.cb.load, {
+ timeout: $.MINUTE
+ });
+ },
+ updateThreadStatus: function(type, status) {
+ var change, hasChanged;
+ if (!(hasChanged = ThreadUpdater.thread["is" + type] !== status)) {
+ return;
+ }
+ ThreadUpdater.thread.setStatus(type, status);
+ if (type === 'Closed' && ThreadUpdater.thread.isArchived) {
+ return;
+ }
+ change = type === 'Sticky' ? status ? 'now a sticky' : 'not a sticky anymore' : status ? 'now closed' : 'not closed anymore';
+ return new Notice('info', "The thread is " + change + ".", 30);
+ },
+ parse: function(req) {
+ var ID, OP, board, deletedFiles, deletedPosts, files, firstPost, i, index, ipCountEl, k, l, lastPost, len, len1, len2, len3, m, newPosts, node, post, postObject, postObjects, posts, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, scroll, thread, unreadCount, unreadQYCount;
+ postObjects = req.response.posts;
+ OP = postObjects[0];
+ thread = ThreadUpdater.thread;
+ board = thread.board;
+ ref = ThreadUpdater.postIDs, lastPost = ref[ref.length - 1];
+ if (postObjects[postObjects.length - 1].no < lastPost && new Date(req.getResponseHeader('Last-Modified')) - thread.posts.get(lastPost).info.date < 30 * $.SECOND) {
+ return;
+ }
+ g.SITE.Build.spoilerRange[board] = OP.custom_spoiler;
+ thread.setStatus('Archived', !!OP.archived);
+ ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky);
+ ThreadUpdater.updateThreadStatus('Closed', !!OP.closed);
+ thread.postLimit = !!OP.bumplimit;
+ thread.fileLimit = !!OP.imagelimit;
+ if (OP.unique_ips != null) {
+ thread.ipCount = OP.unique_ips;
+ }
+ posts = [];
+ index = [];
+ files = [];
+ newPosts = [];
+ for (i = 0, len = postObjects.length; i < len; i++) {
+ postObject = postObjects[i];
+ ID = postObject.no;
+ index.push(ID);
+ if (postObject.fsize) {
+ files.push(ID);
+ }
+ if (ID <= lastPost) {
+ continue;
+ }
+ if ((post = thread.posts.get(ID)) && !post.isFetchedQuote) {
+ post.resurrect();
+ continue;
+ }
+ newPosts.push(board + "." + ID);
+ node = g.SITE.Build.postFromObject(postObject, board.ID);
+ posts.push(new Post(node, thread, board));
+ if (ThreadUpdater.postID === ID) {
+ delete ThreadUpdater.postID;
+ }
+ }
+ deletedPosts = [];
+ ref1 = ThreadUpdater.postIDs;
+ for (k = 0, len1 = ref1.length; k < len1; k++) {
+ ID = ref1[k];
+ if (!(indexOf.call(index, ID) < 0)) {
+ continue;
+ }
+ thread.posts.get(ID).kill();
+ deletedPosts.push(board + "." + ID);
+ }
+ ThreadUpdater.postIDs = index;
+ deletedFiles = [];
+ ref2 = ThreadUpdater.fileIDs;
+ for (l = 0, len2 = ref2.length; l < len2; l++) {
+ ID = ref2[l];
+ if (!(!(indexOf.call(files, ID) >= 0 || (ref3 = board + "." + ID, indexOf.call(deletedPosts, ref3) >= 0)))) {
+ continue;
+ }
+ thread.posts.get(ID).kill(true);
+ deletedFiles.push(board + "." + ID);
+ }
+ ThreadUpdater.fileIDs = files;
+ if (!posts.length) {
+ ThreadUpdater.set('status', '');
+ } else {
+ ThreadUpdater.set('status', "+" + posts.length, 'new');
+ ThreadUpdater.outdateCount = 0;
+ unreadCount = (ref4 = Unread.posts) != null ? ref4.size : void 0;
+ unreadQYCount = (ref5 = Unread.postsQuotingYou) != null ? ref5.size : void 0;
+ Main.callbackNodes('Post', posts);
+ if (d.hidden || !d.hasFocus()) {
+ if (Conf['Beep Quoting You'] && ((ref6 = Unread.postsQuotingYou) != null ? ref6.size : void 0) > unreadQYCount) {
+ ThreadUpdater.playBeep();
+ if (Conf['Beep']) {
+ ThreadUpdater.playBeep();
+ }
+ } else if (Conf['Beep'] && ((ref7 = Unread.posts) != null ? ref7.size : void 0) > 0 && unreadCount === 0) {
+ ThreadUpdater.playBeep();
+ }
+ }
+ scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25;
+ firstPost = null;
+ for (m = 0, len3 = posts.length; m < len3; m++) {
+ post = posts[m];
+ if (!QuoteThreading.insert(post)) {
+ firstPost || (firstPost = post.nodes.root);
+ $.add(ThreadUpdater.root, post.nodes.root);
+ }
+ }
+ $.event('PostsInserted', null, ThreadUpdater.root);
+ if (scroll) {
+ if (Conf['Bottom Scroll']) {
+ window.scrollTo(0, d.body.clientHeight);
+ } else {
+ if (firstPost) {
+ Header.scrollTo(firstPost);
+ }
+ }
+ }
+ }
+ if ((OP.unique_ips != null) && (ipCountEl = $.id('unique-ips'))) {
+ ipCountEl.textContent = OP.unique_ips;
+ ipCountEl.previousSibling.textContent = ipCountEl.previousSibling.textContent.replace(/\b(?:is|are)\b/, OP.unique_ips === 1 ? 'is' : 'are');
+ ipCountEl.nextSibling.textContent = ipCountEl.nextSibling.textContent.replace(/\bposters?\b/, OP.unique_ips === 1 ? 'poster' : 'posters');
+ }
+ return $.event('ThreadUpdate', {
+ 404: false,
+ threadID: thread.fullID,
+ newPosts: newPosts,
+ deletedPosts: deletedPosts,
+ deletedFiles: deletedFiles,
+ postCount: OP.replies + 1,
+ fileCount: OP.images + !!OP.fsize,
+ ipCount: OP.unique_ips
+ });
+ }
+ };
+
+ return ThreadUpdater;
+
+}).call(this);
+
+ThreadWatcher = (function() {
+ var ThreadWatcher,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+ slice = [].slice;
+
+ ThreadWatcher = {
+ init: function() {
+ var ref, sc;
+ if (!(this.enabled = Conf['Thread Watcher'])) {
+ return;
+ }
+ this.shortcut = sc = $.el('a', {
+ id: 'watcher-link',
+ textContent: 'Watcher',
+ title: 'Thread Watcher',
+ href: 'javascript:;',
+ className: 'fa fa-eye'
+ });
+ this.db = new DataBoard('watchedThreads', this.refresh, true);
+ this.dbLM = new DataBoard('watcherLastModified', null, true);
+ this.dialog = UI.dialog('thread-watcher', {innerHTML: "
"});
+ this.status = $('#watcher-status', this.dialog);
+ this.list = this.dialog.lastElementChild;
+ this.refreshButton = $('.refresh', this.dialog);
+ this.closeButton = $('.move > .close', this.dialog);
+ this.unreaddb = Unread.db || UnreadIndex.db || new DataBoard('lastReadPosts');
+ this.unreadEnabled = Conf['Remember Last Read Post'];
+ $.on(d, 'QRPostSuccessful', this.cb.post);
+ $.on(sc, 'click', this.toggleWatcher);
+ $.on(this.refreshButton, 'click', this.buttonFetchAll);
+ $.on(this.closeButton, 'click', this.toggleWatcher);
+ this.menu.addHeaderMenuEntry();
+ $.onExists(doc, 'body', this.addDialog);
+ switch (g.VIEW) {
+ case 'index':
+ $.on(d, 'IndexUpdate', this.cb.onIndexUpdate);
+ break;
+ case 'thread':
+ $.on(d, 'ThreadUpdate', this.cb.onThreadRefresh);
+ }
+ if (Conf['Fixed Thread Watcher']) {
+ $.addClass(doc, 'fixed-watcher');
+ }
+ if (!Conf['Persistent Thread Watcher']) {
+ $.addClass(ThreadWatcher.shortcut, 'disabled');
+ this.dialog.hidden = true;
+ }
+ Header.addShortcut('watcher', sc, 510);
+ ThreadWatcher.initLastModified();
+ ThreadWatcher.fetchAuto();
+ $.on(window, 'visibilitychange focus', function() {
+ return $.queueTask(ThreadWatcher.fetchAuto);
+ });
+ if (Conf['Menu'] && Index.enabled) {
+ Menu.menu.addEntry({
+ el: $.el('a', {
+ href: 'javascript:;',
+ className: 'has-shortcut-text'
+ }, {innerHTML: "
Alt+click "}),
+ order: 6,
+ open: function(arg) {
+ var thread;
+ thread = arg.thread;
+ if (Conf['Index Mode'] !== 'catalog') {
+ return false;
+ }
+ this.el.firstElementChild.textContent = ThreadWatcher.isWatched(thread) ? 'Unwatch' : 'Watch';
+ if (this.cb) {
+ $.off(this.el, 'click', this.cb);
+ }
+ this.cb = function() {
+ $.event('CloseMenu');
+ return ThreadWatcher.toggle(thread);
+ };
+ $.on(this.el, 'click', this.cb);
+ return true;
+ }
+ });
+ }
+ if ((ref = g.VIEW) !== 'index' && ref !== 'thread') {
+ return;
+ }
+ Callbacks.Post.push({
+ name: 'Thread Watcher',
+ cb: this.node
+ });
+ return Callbacks.CatalogThread.push({
+ name: 'Thread Watcher',
+ cb: this.catalogNode
+ });
+ },
+ isWatched: function(thread) {
+ var ref;
+ return !!((ref = ThreadWatcher.db) != null ? ref.get({
+ boardID: thread.board.ID,
+ threadID: thread.ID
+ }) : void 0);
+ },
+ isWatchedRaw: function(boardID, threadID) {
+ var ref;
+ return !!((ref = ThreadWatcher.db) != null ? ref.get({
+ boardID: boardID,
+ threadID: threadID
+ }) : void 0);
+ },
+ setToggler: function(toggler, isWatched) {
+ toggler.classList.toggle('watched', isWatched);
+ return toggler.title = (isWatched ? 'Unwatch' : 'Watch') + " Thread";
+ },
+ node: function() {
+ var boardID, data, siteID, threadID, toggler;
+ if (this.isReply) {
+ return;
+ }
+ if (this.isClone) {
+ toggler = $('.watch-thread-link', this.nodes.info);
+ } else {
+ toggler = $.el('a', {
+ href: 'javascript:;',
+ className: 'watch-thread-link'
+ });
+ $.before($('input', this.nodes.info), toggler);
+ }
+ siteID = g.SITE.ID;
+ boardID = this.board.ID;
+ threadID = this.thread.ID;
+ data = ThreadWatcher.db.get({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID
+ });
+ ThreadWatcher.setToggler(toggler, !!data);
+ $.on(toggler, 'click', ThreadWatcher.cb.toggle);
+ if (data && (data.excerpt == null)) {
+ return $.queueTask((function(_this) {
+ return function() {
+ return ThreadWatcher.update(siteID, boardID, threadID, {
+ excerpt: Get.threadExcerpt(_this.thread)
+ });
+ };
+ })(this));
+ }
+ },
+ catalogNode: function() {
+ if (ThreadWatcher.isWatched(this.thread)) {
+ $.addClass(this.nodes.root, 'watched');
+ }
+ return $.on(this.nodes.root, 'mousedown click', (function(_this) {
+ return function(e) {
+ if (!(e.button === 0 && e.altKey)) {
+ return;
+ }
+ if (e.type === 'click') {
+ ThreadWatcher.toggle(_this.thread);
+ }
+ return e.preventDefault();
+ };
+ })(this));
+ },
+ addDialog: function() {
+ if (!Main.isThisPageLegit()) {
+ return;
+ }
+ ThreadWatcher.build();
+ return $.prepend(d.body, ThreadWatcher.dialog);
+ },
+ toggleWatcher: function() {
+ $.toggleClass(ThreadWatcher.shortcut, 'disabled');
+ return ThreadWatcher.dialog.hidden = !ThreadWatcher.dialog.hidden;
+ },
+ cb: {
+ openAll: function() {
+ var a, j, len1, ref;
+ if ($.hasClass(this, 'disabled')) {
+ return;
+ }
+ ref = $$('a.watcher-link', ThreadWatcher.list);
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ a = ref[j];
+ $.open(a.href);
+ }
+ return $.event('CloseMenu');
+ },
+ openUnread: function() {
+ var a, j, len1, ref;
+ if ($.hasClass(this, 'disabled')) {
+ return;
+ }
+ ref = $$('.replies-unread > a.watcher-link', ThreadWatcher.list);
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ a = ref[j];
+ $.open(a.href);
+ }
+ return $.event('CloseMenu');
+ },
+ openDeads: function() {
+ var a, j, len1, ref;
+ if ($.hasClass(this, 'disabled')) {
+ return;
+ }
+ ref = $$('.dead-thread > a.watcher-link', ThreadWatcher.list);
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ a = ref[j];
+ $.open(a.href);
+ }
+ return $.event('CloseMenu');
+ },
+ pruneDeads: function() {
+ var boardID, data, j, len1, ref, ref1, siteID, threadID;
+ if ($.hasClass(this, 'disabled')) {
+ return;
+ }
+ ref = ThreadWatcher.getAll();
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data;
+ if (data.isDead) {
+ ThreadWatcher.db["delete"]({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID
+ });
+ }
+ }
+ ThreadWatcher.refresh();
+ return $.event('CloseMenu');
+ },
+ dismiss: function() {
+ var boardID, data, j, len1, ref, ref1, siteID, threadID;
+ ref = ThreadWatcher.getAll();
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data;
+ if (data.quotingYou) {
+ ThreadWatcher.update(siteID, boardID, threadID, {
+ dismiss: data.quotingYou || 0
+ });
+ }
+ }
+ return $.event('CloseMenu');
+ },
+ toggle: function() {
+ var thread;
+ thread = Get.postFromNode(this).thread;
+ return ThreadWatcher.toggle(thread);
+ },
+ rm: function() {
+ var boardID, ref, siteID, threadID;
+ siteID = this.parentNode.dataset.siteID;
+ ref = this.parentNode.dataset.fullID.split('.'), boardID = ref[0], threadID = ref[1];
+ return ThreadWatcher.rm(siteID, boardID, +threadID);
+ },
+ post: function(e) {
+ var boardID, cb, postID, ref, threadID;
+ ref = e.detail, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID;
+ cb = PostRedirect.delay();
+ if (postID === threadID) {
+ if (Conf['Auto Watch']) {
+ return ThreadWatcher.addRaw(boardID, threadID, {}, cb);
+ }
+ } else if (Conf['Auto Watch Reply']) {
+ return ThreadWatcher.add(g.threads.get(boardID + '.' + threadID) || new Thread(threadID, g.boards[boardID] || new Board(boardID)), cb);
+ }
+ },
+ onIndexUpdate: function(e) {
+ var boardID, data, db, nKilled, ref, ref1, siteID, threadID;
+ db = ThreadWatcher.db;
+ siteID = g.SITE.ID;
+ boardID = g.BOARD.ID;
+ nKilled = 0;
+ ref = db.data[siteID].boards[boardID];
+ for (threadID in ref) {
+ data = ref[threadID];
+ if (!(!(data != null ? data.isDead : void 0) && (ref1 = boardID + "." + threadID, indexOf.call(e.detail.threads, ref1) < 0))) {
+ continue;
+ }
+ if (!e.detail.threads.some(function(fullID) {
+ return +fullID.split('.')[1] > threadID;
+ })) {
+ continue;
+ }
+ if (Conf['Auto Prune'] || !(data && typeof data === 'object')) {
+ db["delete"]({
+ boardID: boardID,
+ threadID: threadID
+ });
+ nKilled++;
+ } else {
+ ThreadWatcher.fetchStatus({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID,
+ data: data
+ });
+ }
+ }
+ if (nKilled) {
+ return ThreadWatcher.refresh();
+ }
+ },
+ onThreadRefresh: function(e) {
+ var thread;
+ thread = g.threads.get(e.detail.threadID);
+ if (!(e.detail[404] && ThreadWatcher.isWatched(thread))) {
+ return;
+ }
+ return ThreadWatcher.add(thread);
+ }
+ },
+ requests: [],
+ fetched: 0,
+ fetch: function(url, arg, args, cb) {
+ var ajax, force, onloadend, ref, req, siteID;
+ siteID = arg.siteID, force = arg.force;
+ if (ThreadWatcher.requests.length === 0) {
+ ThreadWatcher.status.textContent = '...';
+ $.addClass(ThreadWatcher.refreshButton, 'fa-spin');
+ }
+ onloadend = function() {
+ if (this.finished) {
+ return;
+ }
+ this.finished = true;
+ ThreadWatcher.fetched++;
+ if (ThreadWatcher.fetched === ThreadWatcher.requests.length) {
+ ThreadWatcher.clearRequests();
+ } else {
+ ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%";
+ }
+ return cb.apply(this, args);
+ };
+ ajax = siteID === g.SITE.ID ? $.ajax : CrossOrigin.ajax;
+ if (force) {
+ if ((ref = $.lastModified.ThreadWatcher) != null) {
+ delete ref[url];
+ }
+ }
+ req = $.whenModified(url, 'ThreadWatcher', onloadend, {
+ timeout: $.MINUTE,
+ ajax: ajax
+ });
+ return ThreadWatcher.requests.push(req);
+ },
+ clearRequests: function() {
+ ThreadWatcher.requests = [];
+ ThreadWatcher.fetched = 0;
+ ThreadWatcher.status.textContent = '';
+ return $.rmClass(ThreadWatcher.refreshButton, 'fa-spin');
+ },
+ abort: function() {
+ var j, len1, ref, req;
+ delete ThreadWatcher.syncing;
+ ref = ThreadWatcher.requests;
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ req = ref[j];
+ if (!(!req.finished)) {
+ continue;
+ }
+ req.finished = true;
+ req.abort();
+ }
+ return ThreadWatcher.clearRequests();
+ },
+ initLastModified: function() {
+ var base, boardID, boards, data, date, lm, ref, ref1, siteID, url;
+ lm = ((base = $.lastModified)['ThreadWatcher'] || (base['ThreadWatcher'] = $.dict()));
+ ref = ThreadWatcher.dbLM.data;
+ for (siteID in ref) {
+ boards = ref[siteID];
+ ref1 = boards.boards;
+ for (boardID in ref1) {
+ data = ref1[boardID];
+ if (ThreadWatcher.db.get({
+ siteID: siteID,
+ boardID: boardID
+ })) {
+ for (url in data) {
+ date = data[url];
+ lm[url] = date;
+ }
+ } else {
+ ThreadWatcher.dbLM["delete"]({
+ siteID: siteID,
+ boardID: boardID
+ });
+ }
+ }
+ }
+ },
+ fetchAuto: function() {
+ var db, interval, now, ref;
+ clearTimeout(ThreadWatcher.timeout);
+ if (!Conf['Auto Update Thread Watcher']) {
+ return;
+ }
+ db = ThreadWatcher.db;
+ interval = Conf['Show Page'] || (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) ? 5 * $.MINUTE : 2 * $.HOUR;
+ now = Date.now();
+ if (!((now - interval < (ref = db.data.lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) {
+ ThreadWatcher.fetchAllStatus(interval);
+ }
+ return ThreadWatcher.timeout = setTimeout(ThreadWatcher.fetchAuto, interval);
+ },
+ buttonFetchAll: function() {
+ if (ThreadWatcher.syncing || ThreadWatcher.requests.length) {
+ return ThreadWatcher.abort();
+ } else {
+ return ThreadWatcher.fetchAllStatus();
+ }
+ },
+ fetchAllStatus: function(interval) {
+ var dbi, dbs, j, len1, n, results;
+ if (interval == null) {
+ interval = 0;
+ }
+ ThreadWatcher.status.textContent = '...';
+ $.addClass(ThreadWatcher.refreshButton, 'fa-spin');
+ ThreadWatcher.syncing = true;
+ dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter(function(x) {
+ return x;
+ });
+ n = 0;
+ results = [];
+ for (j = 0, len1 = dbs.length; j < len1; j++) {
+ dbi = dbs[j];
+ results.push(dbi.forceSync(function() {
+ var board, boards, db, deep, k, len2, now, ref, ref1;
+ if ((++n) === dbs.length) {
+ if (!ThreadWatcher.syncing) {
+ return;
+ }
+ delete ThreadWatcher.syncing;
+ if (!((0 <= (ref = Date.now() - (ThreadWatcher.db.data.lastChecked || 0)) && ref < interval))) {
+ db = ThreadWatcher.db;
+ now = Date.now();
+ deep = !((now - 2 * $.HOUR < (ref1 = db.data.lastChecked2 || 0) && ref1 <= now));
+ boards = ThreadWatcher.getAll(true);
+ for (k = 0, len2 = boards.length; k < len2; k++) {
+ board = boards[k];
+ ThreadWatcher.fetchBoard(board, deep);
+ }
+ db.setLastChecked();
+ if (deep) {
+ db.setLastChecked('lastChecked2');
+ }
+ }
+ if (ThreadWatcher.fetched === ThreadWatcher.requests.length) {
+ return ThreadWatcher.clearRequests();
+ }
+ }
+ }));
+ }
+ return results;
+ },
+ fetchBoard: function(board, deep) {
+ var base, boardID, data, force, j, len1, ref, site, siteID, thread, url, urlF;
+ if (!board.some(function(thread) {
+ return !thread.data.isDead;
+ })) {
+ return;
+ }
+ force = false;
+ for (j = 0, len1 = board.length; j < len1; j++) {
+ thread = board[j];
+ data = thread.data;
+ if (!data.isDead && data.last !== -1) {
+ if (Conf['Show Page'] && (data.page == null)) {
+ force = true;
+ }
+ if (data.modified == null) {
+ force = thread.force = true;
+ }
+ }
+ }
+ ref = board[0], siteID = ref.siteID, boardID = ref.boardID;
+ site = g.sites[siteID];
+ if (!site) {
+ return;
+ }
+ urlF = deep && site.threadModTimeIgnoresSage ? 'catalogJSON' : 'threadsListJSON';
+ url = typeof (base = site.urls)[urlF] === "function" ? base[urlF]({
+ siteID: siteID,
+ boardID: boardID
+ }) : void 0;
+ if (!url) {
+ return;
+ }
+ return ThreadWatcher.fetch(url, {
+ siteID: siteID,
+ force: force
+ }, [board, url], ThreadWatcher.parseBoard);
+ },
+ parseBoard: function(board, url) {
+ var base, boardID, data, i, index, item, j, k, l, lastPage, len1, len2, len3, len4, lmDate, m, modified, nThreads, oldest, page, pageLength, ref, ref1, ref2, ref3, ref4, replies, siteID, thread, threadID, threads;
+ if (this.status !== 200) {
+ return;
+ }
+ ref = board[0], siteID = ref.siteID, boardID = ref.boardID;
+ lmDate = this.getResponseHeader('Last-Modified');
+ ThreadWatcher.dbLM.extend({
+ siteID: siteID,
+ boardID: boardID,
+ val: $.item(url, lmDate)
+ });
+ threads = $.dict();
+ pageLength = 0;
+ nThreads = 0;
+ oldest = null;
+ try {
+ pageLength = ((ref1 = this.response[0]) != null ? ref1.threads.length : void 0) || 0;
+ ref2 = this.response;
+ for (i = j = 0, len1 = ref2.length; j < len1; i = ++j) {
+ page = ref2[i];
+ ref3 = page.threads;
+ for (k = 0, len2 = ref3.length; k < len2; k++) {
+ item = ref3[k];
+ threads[item.no] = {
+ page: i + 1,
+ index: nThreads,
+ modified: item.last_modified,
+ replies: item.replies
+ };
+ nThreads++;
+ if ((oldest == null) || item.no < oldest) {
+ oldest = item.no;
+ }
+ }
+ }
+ } catch (error) {
+ for (l = 0, len3 = board.length; l < len3; l++) {
+ thread = board[l];
+ ThreadWatcher.fetchStatus(thread);
+ }
+ }
+ for (m = 0, len4 = board.length; m < len4; m++) {
+ thread = board[m];
+ threadID = thread.threadID, data = thread.data;
+ if (threads[threadID]) {
+ ref4 = threads[threadID], page = ref4.page, index = ref4.index, modified = ref4.modified, replies = ref4.replies;
+ if (Conf['Show Page']) {
+ lastPage = (typeof (base = g.sites[siteID]).isPrunedByAge === "function" ? base.isPrunedByAge({
+ siteID: siteID,
+ boardID: boardID
+ }) : void 0) ? threadID === oldest : index >= nThreads - pageLength;
+ ThreadWatcher.update(siteID, boardID, threadID, {
+ page: page,
+ lastPage: lastPage
+ });
+ }
+ if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) {
+ if (modified !== data.modified || ((replies != null) && replies !== data.replies)) {
+ (thread.newData || (thread.newData = {})).modified = modified;
+ ThreadWatcher.fetchStatus(thread);
+ }
+ }
+ } else {
+ ThreadWatcher.fetchStatus(thread);
+ }
+ }
+ },
+ fetchStatus: function(thread) {
+ var base, boardID, data, force, ref, siteID, threadID, url;
+ siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data, force = thread.force;
+ url = (ref = g.sites[siteID]) != null ? typeof (base = ref.urls).threadJSON === "function" ? base.threadJSON({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID
+ }) : void 0 : void 0;
+ if (!url) {
+ return;
+ }
+ if (data.isDead && !force) {
+ return;
+ }
+ if (data.last === -1) {
+ return;
+ }
+ return ThreadWatcher.fetch(url, {
+ siteID: siteID,
+ force: force
+ }, [thread], ThreadWatcher.parseStatus);
+ },
+ parseStatus: function(thread, isArchiveURL) {
+ var archiveURL, base, boardID, data, force, isArchived, isDead, j, last, lastReadPost, len1, match, newData, postObj, quotesYou, quotingYou, ref, ref1, ref2, ref3, regexp, replies, site, siteID, threadID, unread, youOP;
+ siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data, newData = thread.newData, force = thread.force;
+ site = g.sites[siteID];
+ if (this.status === 200 && this.response) {
+ last = this.response.posts[this.response.posts.length - 1].no;
+ replies = this.response.posts.length - 1;
+ isDead = isArchived = !!(this.response.posts[0].archived || isArchiveURL);
+ if (isDead && Conf['Auto Prune']) {
+ ThreadWatcher.rm(siteID, boardID, threadID);
+ return;
+ }
+ if (last === data.last && isDead === data.isDead && isArchived === data.isArchived) {
+ return;
+ }
+ lastReadPost = ThreadWatcher.unreaddb.get({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID,
+ defaultValue: 0
+ });
+ unread = data.unread || 0;
+ quotingYou = data.quotingYou || 0;
+ youOP = !!((ref = QuoteYou.db) != null ? ref.get({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID,
+ postID: threadID
+ }) : void 0);
+ ref1 = this.response.posts;
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ postObj = ref1[j];
+ if (!(postObj.no > (data.last || 0) && postObj.no > lastReadPost)) {
+ continue;
+ }
+ if ((ref2 = QuoteYou.db) != null ? ref2.get({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID,
+ postID: postObj.no
+ }) : void 0) {
+ continue;
+ }
+ quotesYou = false;
+ if (!Conf['Require OP Quote Link'] && youOP) {
+ quotesYou = true;
+ } else if (QuoteYou.db && postObj.com) {
+ regexp = site.regexp.quotelinkHTML;
+ regexp.lastIndex = 0;
+ while ((match = regexp.exec(postObj.com))) {
+ if (QuoteYou.db.get({
+ siteID: siteID,
+ boardID: match[1] ? encodeURIComponent(match[1]) : boardID,
+ threadID: match[2] || threadID,
+ postID: match[3] || match[2] || threadID
+ })) {
+ quotesYou = true;
+ break;
+ }
+ }
+ }
+ if (!unread || (!quotingYou && quotesYou)) {
+ if (Filter.isHidden(site.Build.parseJSON(postObj, {
+ siteID: siteID,
+ boardID: boardID
+ }))) {
+ continue;
+ }
+ }
+ unread++;
+ if (quotesYou) {
+ quotingYou = postObj.no;
+ }
+ }
+ newData || (newData = {});
+ $.extend(newData, {
+ last: last,
+ replies: replies,
+ isDead: isDead,
+ isArchived: isArchived,
+ unread: unread,
+ quotingYou: quotingYou
+ });
+ return ThreadWatcher.update(siteID, boardID, threadID, newData);
+ } else if (this.status === 404) {
+ archiveURL = (ref3 = g.sites[siteID]) != null ? typeof (base = ref3.urls).archivedThreadJSON === "function" ? base.archivedThreadJSON({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID
+ }) : void 0 : void 0;
+ if (!isArchiveURL && archiveURL) {
+ return ThreadWatcher.fetch(archiveURL, {
+ siteID: siteID,
+ force: force
+ }, [thread, true], ThreadWatcher.parseStatus);
+ } else if (site.mayLackJSON && (data.last == null)) {
+ return ThreadWatcher.update(siteID, boardID, threadID, {
+ last: -1
+ });
+ } else {
+ return ThreadWatcher.update(siteID, boardID, threadID, {
+ isDead: true
+ });
+ }
+ }
+ },
+ getAll: function(groupByBoard) {
+ var all, boardID, boards, cont, data, ref, ref1, siteID, threadID, threads;
+ all = [];
+ ref = ThreadWatcher.db.data;
+ for (siteID in ref) {
+ boards = ref[siteID];
+ ref1 = boards.boards;
+ for (boardID in ref1) {
+ threads = ref1[boardID];
+ if (Conf['Current Board'] && (siteID !== g.SITE.ID || boardID !== g.BOARD.ID)) {
+ continue;
+ }
+ if (groupByBoard) {
+ all.push((cont = []));
+ }
+ for (threadID in threads) {
+ data = threads[threadID];
+ if (data && typeof data === 'object') {
+ (groupByBoard ? cont : all).push({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID,
+ data: data
+ });
+ }
+ }
+ }
+ }
+ return all;
+ },
+ makeLine: function(siteID, boardID, threadID, data) {
+ var count, div, excerpt, fullID, isArchived, link, page, ref, title, x;
+ x = $.el('a', {
+ className: 'fa fa-times',
+ href: 'javascript:;'
+ });
+ $.on(x, 'click', ThreadWatcher.cb.rm);
+ excerpt = data.excerpt, isArchived = data.isArchived;
+ excerpt || (excerpt = "/" + boardID + "/ - No." + threadID);
+ if (Conf['Show Site Prefix']) {
+ excerpt = ThreadWatcher.prefixes[siteID] + excerpt;
+ }
+ link = $.el('a', {
+ href: ((ref = g.sites[siteID]) != null ? ref.urls.thread({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID
+ }, isArchived) : void 0) || '',
+ title: excerpt,
+ className: 'watcher-link'
+ });
+ if (Conf['Show Page'] && (data.page != null)) {
+ page = $.el('span', {
+ textContent: "[" + data.page + "]",
+ className: 'watcher-page'
+ });
+ $.add(link, page);
+ }
+ if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && (data.unread != null)) {
+ count = $.el('span', {
+ textContent: "(" + data.unread + ")",
+ className: 'watcher-unread'
+ });
+ $.add(link, count);
+ }
+ title = $.el('span', {
+ textContent: excerpt,
+ className: 'watcher-title'
+ });
+ $.add(link, title);
+ div = $.el('div');
+ fullID = boardID + "." + threadID;
+ div.dataset.fullID = fullID;
+ div.dataset.siteID = siteID;
+ if (g.VIEW === 'thread' && fullID === (g.BOARD + "." + g.THREADID)) {
+ $.addClass(div, 'current');
+ }
+ if (data.isDead) {
+ $.addClass(div, 'dead-thread');
+ }
+ if (Conf['Show Page']) {
+ if (data.lastPage) {
+ $.addClass(div, 'last-page');
+ }
+ if (data.page != null) {
+ div.dataset.page = data.page;
+ }
+ }
+ if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) {
+ if (data.unread === 0) {
+ $.addClass(div, 'replies-read');
+ }
+ if (data.unread) {
+ $.addClass(div, 'replies-unread');
+ }
+ if ((data.quotingYou || 0) > (data.dismiss || 0)) {
+ $.addClass(div, 'replies-quoting-you');
+ }
+ }
+ $.add(div, [x, $.tn(' '), link]);
+ return div;
+ },
+ setPrefixes: function(threads) {
+ var conflicts, conflicts2, j, k, len, len1, len2, prefix, prefixes, siteID, siteID2;
+ prefixes = $.dict();
+ for (j = 0, len1 = threads.length; j < len1; j++) {
+ siteID = threads[j].siteID;
+ if (siteID in prefixes) {
+ continue;
+ }
+ len = 0;
+ prefix = '';
+ conflicts = Object.keys(prefixes);
+ while (conflicts.length > 0) {
+ len++;
+ prefix = siteID.slice(0, len);
+ conflicts2 = [];
+ for (k = 0, len2 = conflicts.length; k < len2; k++) {
+ siteID2 = conflicts[k];
+ if (siteID2.slice(0, len) === prefix) {
+ conflicts2.push(siteID2);
+ } else if (prefixes[siteID2].length < len) {
+ prefixes[siteID2] = siteID2.slice(0, len);
+ }
+ }
+ conflicts = conflicts2;
+ }
+ prefixes[siteID] = prefix;
+ }
+ return ThreadWatcher.prefixes = prefixes;
+ },
+ build: function() {
+ var boardID, data, j, len1, list, nodes, ref, siteID, thread, threadID, threads;
+ nodes = [];
+ threads = ThreadWatcher.getAll();
+ ThreadWatcher.setPrefixes(threads);
+ for (j = 0, len1 = threads.length; j < len1; j++) {
+ ref = threads[j], siteID = ref.siteID, boardID = ref.boardID, threadID = ref.threadID, data = ref.data;
+ if ((data.excerpt == null) && siteID === g.SITE.ID && (thread = g.threads.get(boardID + "." + threadID)) && thread.OP) {
+ ThreadWatcher.db.extend({
+ boardID: boardID,
+ threadID: threadID,
+ val: {
+ excerpt: Get.threadExcerpt(thread)
+ }
+ });
+ }
+ nodes.push(ThreadWatcher.makeLine(siteID, boardID, threadID, data));
+ }
+ list = ThreadWatcher.list;
+ $.rmAll(list);
+ $.add(list, nodes);
+ return ThreadWatcher.refreshIcon();
+ },
+ refresh: function() {
+ ThreadWatcher.build();
+ g.threads.forEach(function(thread) {
+ var isWatched, j, len1, post, ref, toggler;
+ isWatched = ThreadWatcher.isWatched(thread);
+ if (thread.OP) {
+ ref = [thread.OP].concat(slice.call(thread.OP.clones));
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ post = ref[j];
+ if ((toggler = $('.watch-thread-link', post.nodes.info))) {
+ ThreadWatcher.setToggler(toggler, isWatched);
+ }
+ }
+ }
+ if (thread.catalogView) {
+ return thread.catalogView.nodes.root.classList.toggle('watched', isWatched);
+ }
+ });
+ if (Conf['Pin Watched Threads']) {
+ return $.event('SortIndex', {
+ deferred: Conf['Index Mode'] !== 'catalog'
+ });
+ }
+ },
+ refreshIcon: function() {
+ var className, j, len1, ref;
+ ref = ['replies-unread', 'replies-quoting-you'];
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ className = ref[j];
+ ThreadWatcher.shortcut.classList.toggle(className, !!$("." + className, ThreadWatcher.dialog));
+ }
+ },
+ update: function(siteID, boardID, threadID, newData) {
+ var data, j, key, len1, line, n, newLine, ref, ref1, val;
+ if (!(data = (ref = ThreadWatcher.db) != null ? ref.get({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID
+ }) : void 0)) {
+ return;
+ }
+ if (newData.isDead && Conf['Auto Prune']) {
+ ThreadWatcher.rm(siteID, boardID, threadID);
+ return;
+ }
+ if (newData.isDead || newData.last === -1) {
+ ref1 = ['isArchived', 'page', 'lastPage', 'unread', 'quotingyou'];
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ key = ref1[j];
+ if (!(key in newData)) {
+ newData[key] = void 0;
+ }
+ }
+ }
+ if ((newData.last != null) && newData.last < data.last) {
+ newData.modified = void 0;
+ }
+ n = 0;
+ for (key in newData) {
+ val = newData[key];
+ if (data[key] !== val) {
+ n++;
+ }
+ }
+ if (!n) {
+ return;
+ }
+ ThreadWatcher.db.extend({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID,
+ val: newData
+ });
+ if ((line = $("#watched-threads > [data-site-i-d='" + siteID + "'][data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog))) {
+ newLine = ThreadWatcher.makeLine(siteID, boardID, threadID, data);
+ $.replace(line, newLine);
+ return ThreadWatcher.refreshIcon();
+ } else {
+ return ThreadWatcher.refresh();
+ }
+ },
+ set404: function(boardID, threadID, cb) {
+ var data, ref;
+ if (!(data = (ref = ThreadWatcher.db) != null ? ref.get({
+ boardID: boardID,
+ threadID: threadID
+ }) : void 0)) {
+ return cb();
+ }
+ if (Conf['Auto Prune']) {
+ ThreadWatcher.db["delete"]({
+ boardID: boardID,
+ threadID: threadID
+ });
+ return cb();
+ }
+ if (data.isDead && !((data.isArchived != null) || (data.page != null) || (data.lastPage != null) || (data.unread != null) || (data.quotingYou != null))) {
+ return cb();
+ }
+ return ThreadWatcher.db.extend({
+ boardID: boardID,
+ threadID: threadID,
+ val: {
+ isDead: true,
+ isArchived: void 0,
+ page: void 0,
+ lastPage: void 0,
+ unread: void 0,
+ quotingYou: void 0
+ }
+ }, cb);
+ },
+ toggle: function(thread) {
+ var boardID, siteID, threadID;
+ siteID = g.SITE.ID;
+ boardID = thread.board.ID;
+ threadID = thread.ID;
+ if (ThreadWatcher.db.get({
+ boardID: boardID,
+ threadID: threadID
+ })) {
+ return ThreadWatcher.rm(siteID, boardID, threadID);
+ } else {
+ return ThreadWatcher.add(thread);
+ }
+ },
+ add: function(thread, cb) {
+ var boardID, data, siteID, threadID;
+ data = {};
+ siteID = g.SITE.ID;
+ boardID = thread.board.ID;
+ threadID = thread.ID;
+ if (thread.isDead) {
+ if (Conf['Auto Prune'] && ThreadWatcher.db.get({
+ boardID: boardID,
+ threadID: threadID
+ })) {
+ ThreadWatcher.rm(siteID, boardID, threadID, cb);
+ return;
+ }
+ data.isDead = true;
+ }
+ if (thread.OP) {
+ data.excerpt = Get.threadExcerpt(thread);
+ }
+ return ThreadWatcher.addRaw(boardID, threadID, data, cb);
+ },
+ addRaw: function(boardID, threadID, data, cb) {
+ var oldData, thread;
+ oldData = ThreadWatcher.db.get({
+ boardID: boardID,
+ threadID: threadID,
+ defaultValue: $.dict()
+ });
+ delete oldData.last;
+ delete oldData.modified;
+ $.extend(oldData, data);
+ ThreadWatcher.db.set({
+ boardID: boardID,
+ threadID: threadID,
+ val: oldData
+ }, cb);
+ ThreadWatcher.refresh();
+ thread = {
+ siteID: g.SITE.ID,
+ boardID: boardID,
+ threadID: threadID,
+ data: data,
+ force: true
+ };
+ if (Conf['Show Page'] && !data.isDead) {
+ return ThreadWatcher.fetchBoard([thread]);
+ } else if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) {
+ return ThreadWatcher.fetchStatus(thread);
+ }
+ },
+ rm: function(siteID, boardID, threadID, cb) {
+ ThreadWatcher.db["delete"]({
+ siteID: siteID,
+ boardID: boardID,
+ threadID: threadID
+ }, cb);
+ return ThreadWatcher.refresh();
+ },
+ menu: {
+ init: function() {
+ var menu;
+ if (!Conf['Thread Watcher']) {
+ return;
+ }
+ menu = this.menu = new UI.Menu('thread watcher');
+ $.on($('.menu-button', ThreadWatcher.dialog), 'click', function(e) {
+ return menu.toggle(e, this, ThreadWatcher);
+ });
+ return this.addMenuEntries();
+ },
+ addHeaderMenuEntry: function() {
+ var entryEl;
+ if (g.VIEW !== 'thread') {
+ return;
+ }
+ entryEl = $.el('a', {
+ href: 'javascript:;'
+ });
+ Header.menu.addEntry({
+ el: entryEl,
+ order: 60,
+ open: function() {
+ var addClass, ref, rmClass, text;
+ ref = !!ThreadWatcher.db.get({
+ boardID: g.BOARD.ID,
+ threadID: g.THREADID
+ }) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = ref[0], rmClass = ref[1], text = ref[2];
+ $.addClass(entryEl, addClass);
+ $.rmClass(entryEl, rmClass);
+ entryEl.textContent = text;
+ return true;
+ }
+ });
+ return $.on(entryEl, 'click', function() {
+ return ThreadWatcher.toggle(g.threads.get(g.BOARD + "." + g.THREADID));
+ });
+ },
+ addMenuEntries: function() {
+ var cb, conf, entries, entry, j, len1, name, open, ref, ref1, text, title;
+ entries = [];
+ entries.push({
+ text: 'Open all threads',
+ cb: ThreadWatcher.cb.openAll,
+ open: function() {
+ this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild);
+ return true;
+ }
+ });
+ entries.push({
+ text: 'Open unread threads',
+ cb: ThreadWatcher.cb.openUnread,
+ open: function() {
+ this.el.classList.toggle('disabled', !$('.replies-unread', ThreadWatcher.list));
+ return true;
+ }
+ });
+ entries.push({
+ text: 'Open dead threads',
+ cb: ThreadWatcher.cb.openDeads,
+ open: function() {
+ this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list));
+ return true;
+ }
+ });
+ entries.push({
+ text: 'Prune dead threads',
+ cb: ThreadWatcher.cb.pruneDeads,
+ open: function() {
+ this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list));
+ return true;
+ }
+ });
+ entries.push({
+ text: 'Dismiss posts quoting you',
+ title: 'Unhighlight the thread watcher icon and threads until there are new replies quoting you.',
+ cb: ThreadWatcher.cb.dismiss,
+ open: function() {
+ this.el.classList.toggle('disabled', !$.hasClass(ThreadWatcher.shortcut, 'replies-quoting-you'));
+ return true;
+ }
+ });
+ for (j = 0, len1 = entries.length; j < len1; j++) {
+ ref = entries[j], text = ref.text, title = ref.title, cb = ref.cb, open = ref.open;
+ entry = {
+ el: $.el('a', {
+ textContent: text,
+ href: 'javascript:;'
+ })
+ };
+ if (title) {
+ entry.el.title = title;
+ }
+ $.on(entry.el, 'click', cb);
+ entry.open = open.bind(entry);
+ this.menu.addEntry(entry);
+ }
+ ref1 = Config.threadWatcher;
+ for (name in ref1) {
+ conf = ref1[name];
+ this.addCheckbox(name, conf[1]);
+ }
+ },
+ addCheckbox: function(name, desc) {
+ var entry, input;
+ entry = {
+ type: 'thread watcher',
+ el: UI.checkbox(name, name.replace(' Thread Watcher', ''))
+ };
+ entry.el.title = desc;
+ input = entry.el.firstElementChild;
+ if (name === 'Show Unread Count' && !ThreadWatcher.unreadEnabled) {
+ input.disabled = true;
+ $.addClass(entry.el, 'disabled');
+ entry.el.title += '\n[Remember Last Read Post is disabled.]';
+ }
+ $.on(input, 'change', $.cb.checked);
+ if (name === 'Current Board' || name === 'Show Page' || name === 'Show Unread Count' || name === 'Show Site Prefix') {
+ $.on(input, 'change', ThreadWatcher.refresh);
+ }
+ if (name === 'Show Page' || name === 'Show Unread Count' || name === 'Auto Update Thread Watcher') {
+ $.on(input, 'change', ThreadWatcher.fetchAuto);
+ }
+ return this.menu.addEntry(entry);
+ }
+ }
+ };
+
+ return ThreadWatcher;
+
+}).call(this);
+
+Unread = (function() {
+ var Unread;
+
+ Unread = {
+ init: function() {
+ if (!(g.VIEW === 'thread' && (Conf['Unread Count'] || Conf['Unread Favicon'] || Conf['Unread Line'] || Conf['Remember Last Read Post'] || Conf['Desktop Notifications'] || Conf['Quote Threading']))) {
+ return;
+ }
+ if (Conf['Remember Last Read Post']) {
+ $.sync('Remember Last Read Post', function(enabled) {
+ return Conf['Remember Last Read Post'] = enabled;
+ });
+ this.db = new DataBoard('lastReadPosts', this.sync);
+ }
+ this.hr = $.el('hr', {
+ id: 'unread-line',
+ className: 'unread-line'
+ });
+ this.posts = new Set();
+ this.postsQuotingYou = new Set();
+ this.order = new RandomAccessList();
+ this.position = null;
+ Callbacks.Thread.push({
+ name: 'Unread',
+ cb: this.node
+ });
+ return Callbacks.Post.push({
+ name: 'Unread',
+ cb: this.addPost
+ });
+ },
+ node: function() {
+ var ID, j, len, ref, ref1, resetLink;
+ Unread.thread = this;
+ Unread.title = d.title;
+ Unread.lastReadPost = ((ref = Unread.db) != null ? ref.get({
+ boardID: this.board.ID,
+ threadID: this.ID
+ }) : void 0) || 0;
+ Unread.readCount = 0;
+ ref1 = this.posts.keys;
+ for (j = 0, len = ref1.length; j < len; j++) {
+ ID = ref1[j];
+ if (+ID <= Unread.lastReadPost) {
+ Unread.readCount++;
+ }
+ }
+ $.one(d, '4chanXInitFinished', Unread.ready);
+ $.on(d, 'PostsInserted', Unread.onUpdate);
+ $.on(d, 'ThreadUpdate', function(e) {
+ if (e.detail[404]) {
+ return Unread.update();
+ }
+ });
+ resetLink = $.el('a', {
+ href: 'javascript:;',
+ className: 'unread-reset',
+ textContent: 'Mark all unread'
+ });
+ $.on(resetLink, 'click', Unread.reset);
+ return Header.menu.addEntry({
+ el: resetLink,
+ order: 70
+ });
+ },
+ ready: function() {
+ if (Conf['Remember Last Read Post'] && Conf['Scroll to Last Read Post']) {
+ Unread.scroll();
+ }
+ Unread.setLine(true);
+ Unread.read();
+ Unread.update();
+ $.on(d, 'scroll visibilitychange', Unread.read);
+ if (Conf['Unread Line']) {
+ return $.on(d, 'visibilitychange', Unread.setLine);
+ }
+ },
+ positionPrev: function() {
+ if (Unread.position) {
+ return Unread.position.prev;
+ } else {
+ return Unread.order.last;
+ }
+ },
+ scroll: function() {
+ var bottom, hash, position;
+ if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
+ return;
+ }
+ position = Unread.positionPrev();
+ while (position) {
+ bottom = position.data.nodes.bottom;
+ if (!bottom.getBoundingClientRect().height) {
+ position = position.prev;
+ } else {
+ Header.scrollToIfNeeded(bottom, true);
+ break;
+ }
+ }
+ },
+ reset: function() {
+ if (Unread.lastReadPost == null) {
+ return;
+ }
+ Unread.posts = new Set();
+ Unread.postsQuotingYou = new Set();
+ Unread.order = new RandomAccessList();
+ Unread.position = null;
+ Unread.lastReadPost = 0;
+ Unread.readCount = 0;
+ Unread.thread.posts.forEach(function(post) {
+ return Unread.addPost.call(post);
+ });
+ $.forceSync('Remember Last Read Post');
+ if (Conf['Remember Last Read Post'] && (!Unread.thread.isDead || Unread.thread.isArchived)) {
+ Unread.db.set({
+ boardID: Unread.thread.board.ID,
+ threadID: Unread.thread.ID,
+ val: 0
+ });
+ }
+ Unread.updatePosition();
+ Unread.setLine();
+ return Unread.update();
+ },
+ sync: function() {
+ var ID, i, j, lastReadPost, postIDs, ref, ref1;
+ if (Unread.lastReadPost == null) {
+ return;
+ }
+ lastReadPost = Unread.db.get({
+ boardID: Unread.thread.board.ID,
+ threadID: Unread.thread.ID,
+ defaultValue: 0
+ });
+ if (!(Unread.lastReadPost < lastReadPost)) {
+ return;
+ }
+ Unread.lastReadPost = lastReadPost;
+ postIDs = Unread.thread.posts.keys;
+ for (i = j = ref = Unread.readCount, ref1 = postIDs.length; j < ref1; i = j += 1) {
+ ID = +postIDs[i];
+ if (!Unread.thread.posts.get(ID).isFetchedQuote) {
+ if (ID > Unread.lastReadPost) {
+ break;
+ }
+ Unread.posts["delete"](ID);
+ Unread.postsQuotingYou["delete"](ID);
+ }
+ Unread.readCount++;
+ }
+ Unread.updatePosition();
+ Unread.setLine();
+ return Unread.update();
+ },
+ addPost: function() {
+ if (this.isFetchedQuote || this.isClone) {
+ return;
+ }
+ Unread.order.push(this);
+ if (this.ID <= Unread.lastReadPost || this.isHidden || QuoteYou.isYou(this)) {
+ return;
+ }
+ Unread.posts.add((Unread.posts.last = this.ID));
+ Unread.addPostQuotingYou(this);
+ return Unread.position != null ? Unread.position : Unread.position = Unread.order[this.ID];
+ },
+ addPostQuotingYou: function(post) {
+ var j, len, quotelink, ref, ref1;
+ ref = post.nodes.quotelinks;
+ for (j = 0, len = ref.length; j < len; j++) {
+ quotelink = ref[j];
+ if (!((ref1 = QuoteYou.db) != null ? ref1.get(Get.postDataFromLink(quotelink)) : void 0)) {
+ continue;
+ }
+ Unread.postsQuotingYou.add((Unread.postsQuotingYou.last = post.ID));
+ Unread.openNotification(post);
+ return;
+ }
+ },
+ openNotification: function(post, predicate) {
+ var notif;
+ if (predicate == null) {
+ predicate = ' replied to you';
+ }
+ if (!Header.areNotificationsEnabled) {
+ return;
+ }
+ notif = new Notification("" + post.info.nameBlock + predicate, {
+ body: post.commentDisplay(),
+ icon: Favicon.logo
+ });
+ notif.onclick = function() {
+ Header.scrollToIfNeeded(post.nodes.bottom, true);
+ return window.focus();
+ };
+ return notif.onshow = function() {
+ return setTimeout(function() {
+ return notif.close();
+ }, 7 * $.SECOND);
+ };
+ },
+ onUpdate: function() {
+ return $.queueTask(function() {
+ Unread.setLine();
+ Unread.read();
+ return Unread.update();
+ });
+ },
+ readSinglePost: function(post) {
+ var ID;
+ ID = post.ID;
+ if (!Unread.posts.has(ID)) {
+ return;
+ }
+ Unread.posts["delete"](ID);
+ Unread.postsQuotingYou["delete"](ID);
+ Unread.updatePosition();
+ Unread.saveLastReadPost();
+ return Unread.update();
+ },
+ read: $.debounce(100, function(e) {
+ var ID, bottom, count, data, ref;
+ if (!Unread.posts.size && Unread.readCount !== Unread.thread.posts.keys.length) {
+ Unread.saveLastReadPost();
+ }
+ if (d.hidden || !Unread.posts.size) {
+ return;
+ }
+ count = 0;
+ while (Unread.position) {
+ ref = Unread.position, ID = ref.ID, data = ref.data;
+ bottom = data.nodes.bottom;
+ if (!(!bottom.getBoundingClientRect().height || Header.getBottomOf(bottom) > -1)) {
+ break;
+ }
+ count++;
+ Unread.posts["delete"](ID);
+ Unread.postsQuotingYou["delete"](ID);
+ Unread.position = Unread.position.next;
+ }
+ if (!count) {
+ return;
+ }
+ Unread.updatePosition();
+ Unread.saveLastReadPost();
+ if (e) {
+ return Unread.update();
+ }
+ }),
+ updatePosition: function() {
+ while (Unread.position && !Unread.posts.has(Unread.position.ID)) {
+ Unread.position = Unread.position.next;
+ }
+ },
+ saveLastReadPost: $.debounce(2 * $.SECOND, function() {
+ var ID, i, j, postIDs, ref, ref1;
+ $.forceSync('Remember Last Read Post');
+ if (!(Conf['Remember Last Read Post'] && Unread.db)) {
+ return;
+ }
+ postIDs = Unread.thread.posts.keys;
+ for (i = j = ref = Unread.readCount, ref1 = postIDs.length; j < ref1; i = j += 1) {
+ ID = +postIDs[i];
+ if (!Unread.thread.posts.get(ID).isFetchedQuote) {
+ if (Unread.posts.has(ID)) {
+ break;
+ }
+ Unread.lastReadPost = ID;
+ }
+ Unread.readCount++;
+ }
+ if (Unread.thread.isDead && !Unread.thread.isArchived) {
+ return;
+ }
+ return Unread.db.set({
+ boardID: Unread.thread.board.ID,
+ threadID: Unread.thread.ID,
+ val: Unread.lastReadPost
+ });
+ }),
+ setLine: function(force) {
+ var node, oldPosition, ref;
+ if (!Conf['Unread Line']) {
+ return;
+ }
+ if (Unread.hr.hidden || d.hidden || (force === true)) {
+ oldPosition = Unread.linePosition;
+ if ((Unread.linePosition = Unread.positionPrev())) {
+ if (Unread.linePosition !== oldPosition) {
+ node = Unread.linePosition.data.nodes.bottom;
+ if (((ref = node.nextSibling) != null ? ref.tagName : void 0) === 'BR') {
+ node = node.nextSibling;
+ }
+ $.after(node, Unread.hr);
+ }
+ } else {
+ $.rm(Unread.hr);
+ }
+ }
+ return Unread.hr.hidden = Unread.linePosition === Unread.order.last;
+ },
+ update: function() {
+ var count, countQuotingYou, isDead, titleCount, titleDead, titleQuotingYou;
+ count = Unread.posts.size;
+ countQuotingYou = Unread.postsQuotingYou.size;
+ if (Conf['Unread Count']) {
+ titleQuotingYou = Conf['Quoted Title'] && countQuotingYou ? '(!) ' : '';
+ titleCount = count || !Conf['Hide Unread Count at (0)'] ? "(" + count + ") " : '';
+ titleDead = Unread.thread.isDead ? Unread.title.replace('-', (Unread.thread.isArchived ? '- Archived -' : '- 404 -')) : Unread.title;
+ d.title = "" + titleQuotingYou + titleCount + titleDead;
+ }
+ Unread.saveThreadWatcherCount();
+ if (Conf['Unread Favicon'] && g.SITE.software === 'yotsuba') {
+ isDead = Unread.thread.isDead;
+ return Favicon.set((countQuotingYou ? (isDead ? 'unreadDeadY' : 'unreadY') : count ? (isDead ? 'unreadDead' : 'unread') : (isDead ? 'dead' : 'default')));
+ }
+ },
+ saveThreadWatcherCount: $.debounce(2 * $.SECOND, function() {
+ var i, j, posts, quotingYou, ref;
+ $.forceSync('Remember Last Read Post');
+ if (Conf['Remember Last Read Post'] && (!Unread.thread.isDead || Unread.thread.isArchived)) {
+ quotingYou = !Conf['Require OP Quote Link'] && QuoteYou.isYou(Unread.thread.OP) ? Unread.posts : Unread.postsQuotingYou;
+ if (!quotingYou.size) {
+ quotingYou.last = 0;
+ } else if (!quotingYou.has(quotingYou.last)) {
+ quotingYou.last = 0;
+ posts = Unread.thread.posts.keys;
+ for (i = j = ref = posts.length - 1; j >= 0; i = j += -1) {
+ if (quotingYou.has(+posts[i])) {
+ quotingYou.last = posts[i];
+ break;
+ }
+ }
+ }
+ return ThreadWatcher.update(g.SITE.ID, Unread.thread.board.ID, Unread.thread.ID, {
+ last: Unread.thread.lastPost,
+ isDead: Unread.thread.isDead,
+ isArchived: Unread.thread.isArchived,
+ unread: Unread.posts.size,
+ quotingYou: quotingYou.last || 0
+ });
+ }
+ })
+ };
+
+ return Unread;
+
+}).call(this);
+
+UnreadIndex = (function() {
+ var UnreadIndex;
+
+ UnreadIndex = {
+ lastReadPost: $.dict(),
+ hr: $.dict(),
+ markReadLink: $.dict(),
+ init: function() {
+ if (!(g.VIEW === 'index' && Conf['Remember Last Read Post'] && Conf['Unread Line in Index'])) {
+ return;
+ }
+ this.enabled = true;
+ this.db = new DataBoard('lastReadPosts', this.sync);
+ Callbacks.Thread.push({
+ name: 'Unread Line in Index',
+ cb: this.node
+ });
+ $.on(d, 'IndexRefreshInternal', this.onIndexRefresh);
+ return $.on(d, 'PostsInserted PostsRemoved', this.onPostsInserted);
+ },
+ node: function() {
+ UnreadIndex.lastReadPost[this.fullID] = UnreadIndex.db.get({
+ boardID: this.board.ID,
+ threadID: this.ID
+ }) || 0;
+ if (!Index.enabled) {
+ return UnreadIndex.update(this);
+ }
+ },
+ onIndexRefresh: function(e) {
+ var i, len, ref, results, thread, threadID;
+ if (e.detail.isCatalog) {
+ return;
+ }
+ ref = e.detail.threadIDs;
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ threadID = ref[i];
+ thread = g.threads.get(threadID);
+ results.push(UnreadIndex.update(thread));
+ }
+ return results;
+ },
+ onPostsInserted: function(e) {
+ var ref, ref1, thread, wasVisible;
+ if (e.target === Index.root) {
+ return;
+ }
+ thread = Get.threadFromNode(e.target);
+ if (!thread || thread.nodes.root !== e.target) {
+ return;
+ }
+ wasVisible = !!((ref = UnreadIndex.hr[thread.fullID]) != null ? ref.parentNode : void 0);
+ UnreadIndex.update(thread);
+ if (Conf['Scroll to Last Read Post'] && e.type === 'PostsInserted' && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) {
+ return Header.scrollToIfNeeded(UnreadIndex.hr[thread.fullID], true);
+ }
+ },
+ sync: function() {
+ return g.threads.forEach(function(thread) {
+ var lastReadPost, ref;
+ lastReadPost = UnreadIndex.db.get({
+ boardID: thread.board.ID,
+ threadID: thread.ID
+ }) || 0;
+ if (lastReadPost !== UnreadIndex.lastReadPost[thread.fullID]) {
+ UnreadIndex.lastReadPost[thread.fullID] = lastReadPost;
+ if ((ref = thread.nodes.root) != null ? ref.parentNode : void 0) {
+ return UnreadIndex.update(thread);
+ }
+ }
+ });
+ },
+ update: function(thread) {
+ var divider, firstUnread, hasUnread, hr, lastReadPost, link, repliesRead, repliesShown;
+ lastReadPost = UnreadIndex.lastReadPost[thread.fullID];
+ repliesShown = 0;
+ repliesRead = 0;
+ firstUnread = null;
+ thread.posts.forEach(function(post) {
+ if (post.isReply && thread.nodes.root.contains(post.nodes.root)) {
+ repliesShown++;
+ if (post.ID <= lastReadPost) {
+ return repliesRead++;
+ } else if ((!firstUnread || post.ID < firstUnread.ID) && !post.isHidden && !QuoteYou.isYou(post)) {
+ return firstUnread = post;
+ }
+ }
+ });
+ hr = UnreadIndex.hr[thread.fullID];
+ if (firstUnread && (repliesRead || (lastReadPost === thread.OP.ID && (!$(g.SITE.selectors.summary, thread.nodes.root) || thread.ID in ExpandThread.statuses)))) {
+ if (!hr) {
+ hr = UnreadIndex.hr[thread.fullID] = $.el('hr', {
+ className: 'unread-line'
+ });
+ }
+ $.before(firstUnread.nodes.root, hr);
+ } else {
+ $.rm(hr);
+ }
+ hasUnread = repliesShown ? firstUnread || !repliesRead : Index.enabled ? thread.lastPost > lastReadPost : thread.OP.ID > lastReadPost;
+ thread.nodes.root.classList.toggle('unread-thread', hasUnread);
+ link = UnreadIndex.markReadLink[thread.fullID];
+ if (!link) {
+ link = UnreadIndex.markReadLink[thread.fullID] = $.el('a', {
+ className: 'unread-mark-read brackets-wrap',
+ href: 'javascript:;',
+ textContent: 'Mark Read'
+ });
+ $.on(link, 'click', UnreadIndex.markRead);
+ }
+ if ((divider = $(g.SITE.selectors.threadDivider, thread.nodes.root))) {
+ return $.before(divider, link);
+ } else {
+ return $.add(thread.nodes.root, link);
+ }
+ },
+ markRead: function() {
+ var thread;
+ thread = Get.threadFromNode(this);
+ UnreadIndex.lastReadPost[thread.fullID] = thread.lastPost;
+ UnreadIndex.db.set({
+ boardID: thread.board.ID,
+ threadID: thread.ID,
+ val: thread.lastPost
+ });
+ $.rm(UnreadIndex.hr[thread.fullID]);
+ thread.nodes.root.classList.remove('unread-thread');
+ return ThreadWatcher.update(g.SITE.ID, thread.board.ID, thread.ID, {
+ last: thread.lastPost,
+ unread: 0,
+ quotingYou: 0
+ });
+ }
+ };
+
+ return UnreadIndex;
+
+}).call(this);
+
+Captcha = {};
+
+(function() {
+ Captcha.cache = {
+ init: function() {
+ $.on(d, 'SaveCaptcha', (function(_this) {
+ return function(e) {
+ return _this.saveAPI(e.detail);
+ };
+ })(this));
+ return $.on(d, 'NoCaptcha', (function(_this) {
+ return function(e) {
+ return _this.noCaptcha(e.detail);
+ };
+ })(this));
+ },
+ captchas: [],
+ getCount: function() {
+ return this.captchas.length;
+ },
+ neededRaw: function() {
+ return !(this.haveCookie() || this.captchas.length || QR.req || this.submitCB) && (QR.posts.length > 1 || Conf['Auto-load captcha'] || !QR.posts[0].isOnlyQuotes() || QR.posts[0].file);
+ },
+ needed: function() {
+ return this.neededRaw() && $.event('LoadCaptcha');
+ },
+ prerequest: function() {
+ if (!Conf['Prerequest Captcha']) {
+ return;
+ }
+ return $.queueTask((function(_this) {
+ return function() {
+ var isReply;
+ if (!_this.prerequested && _this.neededRaw() && !$.event('LoadCaptcha') && !QR.captcha.occupied() && QR.cooldown.seconds <= 60 && QR.selected === QR.posts[QR.posts.length - 1] && !QR.selected.isOnlyQuotes()) {
+ isReply = QR.selected.thread !== 'new';
+ if (!$.event('RequestCaptcha', {
+ isReply: isReply
+ })) {
+ _this.prerequested = true;
+ _this.submitCB = function(captcha) {
+ if (captcha) {
+ return _this.save(captcha);
+ }
+ };
+ return _this.updateCount();
+ }
+ }
+ };
+ })(this));
+ },
+ haveCookie: function() {
+ return /\b_ct=/.test(d.cookie) && QR.posts[0].thread !== 'new';
+ },
+ getOne: function() {
+ var captcha;
+ delete this.prerequested;
+ this.clear();
+ if ((captcha = this.captchas.shift())) {
+ this.count();
+ return captcha;
+ } else {
+ return null;
+ }
+ },
+ request: function(isReply) {
+ if (!this.submitCB) {
+ if ($.event('RequestCaptcha', {
+ isReply: isReply
+ })) {
+ return;
+ }
+ }
+ return (function(_this) {
+ return function(cb) {
+ _this.submitCB = cb;
+ return _this.updateCount();
+ };
+ })(this);
+ },
+ abort: function() {
+ if (this.submitCB) {
+ delete this.submitCB;
+ $.event('AbortCaptcha');
+ return this.updateCount();
+ }
+ },
+ saveAPI: function(captcha) {
+ var cb;
+ if ((cb = this.submitCB)) {
+ delete this.submitCB;
+ cb(captcha);
+ return this.updateCount();
+ } else {
+ return this.save(captcha);
+ }
+ },
+ noCaptcha: function(detail) {
+ var cb;
+ if ((cb = this.submitCB)) {
+ if (!this.haveCookie() || (detail != null ? detail.error : void 0)) {
+ QR.error((detail != null ? detail.error : void 0) || 'Failed to retrieve captcha.');
+ QR.captcha.setup(d.activeElement === QR.nodes.status);
+ }
+ delete this.submitCB;
+ cb();
+ return this.updateCount();
+ }
+ },
+ save: function(captcha) {
+ var cb;
+ if ((cb = this.submitCB)) {
+ this.abort();
+ cb(captcha);
+ return;
+ }
+ this.captchas.push(captcha);
+ this.captchas.sort(function(a, b) {
+ return a.timeout - b.timeout;
+ });
+ return this.count();
+ },
+ clear: function() {
+ var captcha, i, j, len, now, ref;
+ if (this.captchas.length) {
+ now = Date.now();
+ ref = this.captchas;
+ for (i = j = 0, len = ref.length; j < len; i = ++j) {
+ captcha = ref[i];
+ if (captcha.timeout > now) {
+ break;
+ }
+ }
+ if (i) {
+ this.captchas = this.captchas.slice(i);
+ return this.count();
+ }
+ }
+ },
+ count: function() {
+ clearTimeout(this.timer);
+ if (this.captchas.length) {
+ this.timer = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now());
+ }
+ return this.updateCount();
+ },
+ updateCount: function() {
+ return $.event('CaptchaCount', this.captchas.length);
+ }
+ };
+
+}).call(this);
+
+(function() {
+ Captcha.replace = {
+ init: function() {
+ var ref;
+ if (!(g.SITE.software === 'yotsuba' && d.cookie.indexOf('pass_enabled=1') < 0)) {
+ return;
+ }
+ if (Conf['Force Noscript Captcha'] && Main.jsEnabled) {
+ $.ready(Captcha.replace.noscript);
+ return;
+ }
+ if (Conf['captchaLanguage'].trim()) {
+ if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') {
+ return $.onExists(doc, '#captchaFormPart', function(node) {
+ return $.onExists(node, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe);
+ });
+ } else {
+ return $.onExists(doc, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe);
+ }
+ }
+ },
+ noscript: function() {
+ var insert, noscript, original, span, toggle;
+ if (!((original = $('#g-recaptcha')) && (noscript = $('noscript', original.parentNode)))) {
+ return;
+ }
+ span = $.el('span', {
+ id: 'captcha-forced-noscript'
+ });
+ $.replace(noscript, span);
+ $.rm(original);
+ insert = function() {
+ span.innerHTML = noscript.textContent;
+ return Captcha.replace.iframe($('iframe[src^="https://www.google.com/recaptcha/"]', span));
+ };
+ if ((toggle = $('#togglePostFormLink a, #form-link'))) {
+ return $.on(toggle, 'click', insert);
+ } else {
+ return insert();
+ }
+ },
+ iframe: function(iframe) {
+ var lang, src;
+ if ((lang = Conf['captchaLanguage'].trim())) {
+ src = /[?&]hl=/.test(iframe.src) ? iframe.src.replace(/([?&]hl=)[^&]*/, '$1' + encodeURIComponent(lang)) : iframe.src + ("&hl=" + (encodeURIComponent(lang)));
+ if (iframe.src !== src) {
+ iframe.src = src;
+ }
+ }
+ }
+ };
+
+}).call(this);
+
+(function() {
+ Captcha.t = {
+ init: function() {
+ var root;
+ if (d.cookie.indexOf('pass_enabled=1') >= 0) {
+ return;
+ }
+ if (!(this.isEnabled = !!$('#t-root') || !$.id('postForm'))) {
+ return;
+ }
+ root = $.el('div', {
+ className: 'captcha-root'
+ });
+ this.nodes = {
+ root: root
+ };
+ $.addClass(QR.nodes.el, 'has-captcha', 'captcha-t');
+ return $.after(QR.nodes.com.parentNode, root);
+ },
+ moreNeeded: function() {},
+ getThread: function() {
+ var boardID, threadID;
+ boardID = g.BOARD.ID;
+ if (QR.posts[0].thread === 'new') {
+ threadID = '0';
+ } else {
+ threadID = '' + QR.posts[0].thread;
+ }
+ return {
+ boardID: boardID,
+ threadID: threadID
+ };
+ },
+ setup: function(focus) {
+ if (!this.isEnabled) {
+ return;
+ }
+ if (!this.nodes.container) {
+ this.nodes.container = $.el('div', {
+ className: 'captcha-container'
+ });
+ $.prepend(this.nodes.root, this.nodes.container);
+ Captcha.t.currentThread = Captcha.t.getThread();
+ $.global(function() {
+ var el;
+ el = document.querySelector('#qr .captcha-container');
+ window.TCaptcha.init(el, this.boardID, +this.threadID);
+ return window.TCaptcha.setErrorCb(function(err) {
+ return window.dispatchEvent(new CustomEvent('CreateNotification', {
+ detail: {
+ type: 'warning',
+ content: '' + err
+ }
+ }));
+ });
+ }, Captcha.t.currentThread);
+ }
+ if (focus) {
+ return $('#t-resp').focus();
+ }
+ },
+ destroy: function() {
+ if (!(this.isEnabled && this.nodes.container)) {
+ return;
+ }
+ $.global(function() {
+ return window.TCaptcha.destroy();
+ });
+ $.rm(this.nodes.container);
+ return delete this.nodes.container;
+ },
+ updateThread: function() {
+ var boardID, newThread, ref, threadID;
+ if (!this.isEnabled) {
+ return;
+ }
+ ref = Captcha.t.currentThread || {}, boardID = ref.boardID, threadID = ref.threadID;
+ newThread = Captcha.t.getThread();
+ if (!(newThread.boardID === boardID && newThread.threadID === threadID)) {
+ Captcha.t.destroy();
+ return Captcha.t.setup();
+ }
+ },
+ getOne: function() {
+ var el, i, key, len, ref, response;
+ response = {};
+ if (this.nodes.container) {
+ ref = ['t-response', 't-challenge'];
+ for (i = 0, len = ref.length; i < len; i++) {
+ key = ref[i];
+ response[key] = $("[name='" + key + "']", this.nodes.container).value;
+ }
+ }
+ if (!response['t-response'] && !((el = $('#t-msg')) && /Verification not required/i.test(el.textContent))) {
+ response = null;
+ }
+ return response;
+ },
+ setUsed: function() {
+ if (!this.isEnabled) {
+ return;
+ }
+ if (this.nodes.container) {
+ return $.global(function() {
+ return window.TCaptcha.clearChallenge();
+ });
+ }
+ },
+ occupied: function() {
+ return !!this.nodes.container;
+ }
+ };
+
+}).call(this);
+
+(function() {
+ var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ Captcha.v2 = {
+ lifetime: 2 * $.MINUTE,
+ init: function() {
+ var counter, root;
+ if (d.cookie.indexOf('pass_enabled=1') >= 0) {
+ return;
+ }
+ if (!(this.isEnabled = !!$('#g-recaptcha, #captcha-forced-noscript') || !$.id('postForm'))) {
+ return;
+ }
+ if ((this.noscript = Conf['Force Noscript Captcha'] || !Main.jsEnabled)) {
+ $.addClass(QR.nodes.el, 'noscript-captcha');
+ }
+ Captcha.cache.init();
+ $.on(d, 'CaptchaCount', this.count.bind(this));
+ root = $.el('div', {
+ className: 'captcha-root'
+ });
+ $.extend(root, {innerHTML: "
"});
+ counter = $('.captcha-counter > a', root);
+ this.nodes = {
+ root: root,
+ counter: counter
+ };
+ this.count();
+ $.addClass(QR.nodes.el, 'has-captcha', 'captcha-v2');
+ $.after(QR.nodes.com.parentNode, root);
+ $.on(counter, 'click', this.toggle.bind(this));
+ $.on(counter, 'keydown', (function(_this) {
+ return function(e) {
+ if (Keybinds.keyCode(e) !== 'Space') {
+ return;
+ }
+ _this.toggle();
+ e.preventDefault();
+ return e.stopPropagation();
+ };
+ })(this));
+ return $.on(window, 'captcha:success', (function(_this) {
+ return function() {
+ return $.queueTask(function() {
+ return _this.save(false);
+ });
+ };
+ })(this));
+ },
+ timeouts: {},
+ prevNeeded: 0,
+ noscriptURL: function() {
+ var lang, url;
+ url = 'https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc';
+ if ((lang = Conf['captchaLanguage'].trim())) {
+ url += "&hl=" + (encodeURIComponent(lang));
+ }
+ return url;
+ },
+ moreNeeded: function() {
+ return $.queueTask((function(_this) {
+ return function() {
+ var needed;
+ needed = Captcha.cache.needed();
+ if (needed && !_this.prevNeeded) {
+ _this.setup(QR.cooldown.auto && d.activeElement === QR.nodes.status);
+ }
+ return _this.prevNeeded = needed;
+ };
+ })(this));
+ },
+ toggle: function() {
+ if (this.nodes.container && !this.timeouts.destroy) {
+ return this.destroy();
+ } else {
+ return this.setup(true, true);
+ }
+ },
+ setup: function(focus, force) {
+ if (!(this.isEnabled && (Captcha.cache.needed() || force))) {
+ return;
+ }
+ if (focus) {
+ $.addClass(QR.nodes.el, 'focus');
+ this.nodes.counter.focus();
+ }
+ if (this.timeouts.destroy) {
+ clearTimeout(this.timeouts.destroy);
+ delete this.timeouts.destroy;
+ return this.reload();
+ }
+ if (this.nodes.container) {
+ $.queueTask((function(_this) {
+ return function() {
+ var iframe;
+ if (_this.nodes.container && d.activeElement === _this.nodes.counter && (iframe = $('iframe[src^="https://www.google.com/recaptcha/"]', _this.nodes.container))) {
+ iframe.focus();
+ return QR.focus();
+ }
+ };
+ })(this));
+ return;
+ }
+ this.nodes.container = $.el('div', {
+ className: 'captcha-container'
+ });
+ $.prepend(this.nodes.root, this.nodes.container);
+ new MutationObserver(this.afterSetup.bind(this)).observe(this.nodes.container, {
+ childList: true,
+ subtree: true
+ });
+ if (this.noscript) {
+ return this.setupNoscript();
+ } else {
+ return this.setupJS();
+ }
+ },
+ setupNoscript: function() {
+ var div, iframe, textarea;
+ iframe = $.el('iframe', {
+ id: 'qr-captcha-iframe',
+ scrolling: 'no',
+ src: this.noscriptURL()
+ });
+ div = $.el('div');
+ textarea = $.el('textarea');
+ $.add(div, textarea);
+ return $.add(this.nodes.container, [iframe, div]);
+ },
+ setupJS: function() {
+ return $.global(function() {
+ var cbNative, render, script;
+ render = function() {
+ var classList, container;
+ classList = document.documentElement.classList;
+ container = document.querySelector('#qr .captcha-container');
+ return container.dataset.widgetID = window.grecaptcha.render(container, {
+ sitekey: '6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc',
+ theme: classList.contains('tomorrow') || classList.contains('spooky') || classList.contains('dark-captcha') ? 'dark' : 'light',
+ callback: function(response) {
+ return window.dispatchEvent(new CustomEvent('captcha:success', {
+ detail: response
+ }));
+ }
+ });
+ };
+ if (window.grecaptcha) {
+ return render();
+ } else {
+ cbNative = window.onRecaptchaLoaded;
+ window.onRecaptchaLoaded = function() {
+ render();
+ return cbNative();
+ };
+ if (!document.head.querySelector('script[src^="https://www.google.com/recaptcha/api.js"]')) {
+ script = document.createElement('script');
+ script.src = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit';
+ return document.head.appendChild(script);
+ }
+ }
+ });
+ },
+ afterSetup: function(mutations) {
+ var i, iframe, j, len, len1, mutation, node, ref, textarea;
+ for (i = 0, len = mutations.length; i < len; i++) {
+ mutation = mutations[i];
+ ref = mutation.addedNodes;
+ for (j = 0, len1 = ref.length; j < len1; j++) {
+ node = ref[j];
+ if ((iframe = $.x('./descendant-or-self::iframe[starts-with(@src, "https://www.google.com/recaptcha/")]', node))) {
+ this.setupIFrame(iframe);
+ }
+ if ((textarea = $.x('./descendant-or-self::textarea', node))) {
+ this.setupTextArea(textarea);
+ }
+ }
+ }
+ },
+ setupIFrame: function(iframe) {
+ var ref, ref1;
+ if (!doc.contains(iframe)) {
+ return;
+ }
+ Captcha.replace.iframe(iframe);
+ $.addClass(QR.nodes.el, 'captcha-open');
+ this.fixQRPosition();
+ $.on(iframe, 'load', this.fixQRPosition);
+ if (d.activeElement === this.nodes.counter) {
+ iframe.focus();
+ }
+ if (((ref = $.engine) === 'blink' || ref === 'edge') && (ref1 = iframe.parentNode, indexOf.call($$('#qr .captcha-container > div > div:first-of-type'), ref1) >= 0)) {
+ return $.on(iframe.parentNode, 'scroll', function() {
+ return this.scrollTop = 0;
+ });
+ }
+ },
+ fixQRPosition: function() {
+ if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) {
+ QR.nodes.el.style.top = '';
+ return QR.nodes.el.style.bottom = '0px';
+ }
+ },
+ setupTextArea: function(textarea) {
+ return $.one(textarea, 'input', (function(_this) {
+ return function() {
+ return _this.save(true);
+ };
+ })(this));
+ },
+ destroy: function() {
+ if (!this.isEnabled) {
+ return;
+ }
+ delete this.timeouts.destroy;
+ $.rmClass(QR.nodes.el, 'captcha-open');
+ if (this.nodes.container) {
+ $.global(function() {
+ var container;
+ container = document.querySelector('#qr .captcha-container');
+ return window.grecaptcha.reset(container.dataset.widgetID);
+ });
+ $.rm(this.nodes.container);
+ return delete this.nodes.container;
+ }
+ },
+ getOne: function(isReply) {
+ return Captcha.cache.getOne(isReply);
+ },
+ save: function(pasted, token) {
+ var base, focus, ref;
+ Captcha.cache.save({
+ response: token || $('textarea', this.nodes.container).value,
+ timeout: Date.now() + this.lifetime
+ });
+ focus = ((ref = d.activeElement) != null ? ref.nodeName : void 0) === 'IFRAME' && /https?:\/\/www\.google\.com\/recaptcha\//.test(d.activeElement.src);
+ if (Captcha.cache.needed()) {
+ if (focus) {
+ if (QR.cooldown.auto || Conf['Post on Captcha Completion']) {
+ this.nodes.counter.focus();
+ } else {
+ QR.nodes.status.focus();
+ }
+ }
+ this.reload();
+ } else {
+ if (pasted) {
+ this.destroy();
+ } else {
+ if ((base = this.timeouts).destroy == null) {
+ base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND);
+ }
+ }
+ if (focus) {
+ QR.nodes.status.focus();
+ }
+ }
+ if (Conf['Post on Captcha Completion'] && !QR.cooldown.auto) {
+ return QR.submit();
+ }
+ },
+ count: function() {
+ var count, loading;
+ count = Captcha.cache.getCount();
+ loading = Captcha.cache.submitCB ? '...' : '';
+ this.nodes.counter.textContent = "Captchas: " + count + loading;
+ return this.moreNeeded();
+ },
+ reload: function() {
+ if ($('iframe[src^="https://www.google.com/recaptcha/api/fallback?"]', this.nodes.container)) {
+ this.destroy();
+ return this.setup(false, true);
+ } else {
+ return $.global(function() {
+ var container;
+ container = document.querySelector('#qr .captcha-container');
+ return window.grecaptcha.reset(container.dataset.widgetID);
+ });
+ }
+ },
+ occupied: function() {
+ return !!this.nodes.container && !this.timeouts.destroy;
+ }
+ };
+
+}).call(this);
+
+PassLink = (function() {
+ var PassLink;
+
+ PassLink = {
+ init: function() {
+ if (!(g.SITE.software === 'yotsuba' && Conf['Pass Link'])) {
+ return;
+ }
+ return Main.ready(this.ready);
+ },
+ ready: function() {
+ var passLink, styleSelector;
+ if (!(styleSelector = $.id('styleSelector'))) {
+ return;
+ }
+ passLink = $.el('span', {
+ className: 'brackets-wrap pass-link-container'
+ });
+ $.extend(passLink, {innerHTML: "
4chan Pass "});
+ $.on(passLink.firstElementChild, 'click', function() {
+ return window.open("//sys." + (location.hostname.split('.')[1]) + ".org/auth", Date.now(), 'width=500,height=280,toolbar=0');
+ });
+ return $.before(styleSelector.previousSibling, [passLink, $.tn('\u00A0\u00A0')]);
+ }
+ };
+
+ return PassLink;
+
+}).call(this);
+
+PostRedirect = (function() {
+ var PostRedirect;
+
+ PostRedirect = {
+ init: function() {
+ return $.on(d, 'QRPostSuccessful', (function(_this) {
+ return function(e) {
+ if (!e.detail.redirect) {
+ return;
+ }
+ _this.event = e;
+ _this.delays = 0;
+ return $.queueTask(function() {
+ if (e === _this.event && _this.delays === 0) {
+ return location.href = e.detail.redirect;
+ }
+ });
+ };
+ })(this));
+ },
+ delays: 0,
+ delay: function() {
+ var e;
+ if (!this.event) {
+ return null;
+ }
+ e = this.event;
+ this.delays++;
+ return (function(_this) {
+ return function() {
+ if (e !== _this.event) {
+ return;
+ }
+ _this.delays--;
+ if (_this.delays === 0) {
+ return location.href = e.detail.redirect;
+ }
+ };
+ })(this);
+ }
+ };
+
+ return PostRedirect;
+
+}).call(this);
+
+PostSuccessful = (function() {
+ var PostSuccessful;
+
+ PostSuccessful = {
+ init: function() {
+ if (!Conf['Remember Your Posts']) {
+ return;
+ }
+ return $.ready(this.ready);
+ },
+ ready: function() {
+ var _, db, postID, ref, threadID;
+ if (d.title !== 'Post successful!') {
+ return;
+ }
+ ref = $('h1').nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = ref[0], threadID = ref[1], postID = ref[2];
+ postID = +postID;
+ threadID = +threadID || postID;
+ db = new DataBoard('yourPosts');
+ return db.set({
+ boardID: g.BOARD.ID,
+ threadID: threadID,
+ postID: postID,
+ val: true
+ });
+ }
+ };
+
+ return PostSuccessful;
+
+}).call(this);
+
+QR = (function() {
+ var QR,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+ slice = [].slice;
+
+ QR = {
+ mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm'],
+ validExtension: /\.(jpe?g|png|gif|pdf|swf|webm)$/i,
+ typeFromExtension: {
+ 'jpg': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'png': 'image/png',
+ 'gif': 'image/gif',
+ 'pdf': 'application/pdf',
+ 'swf': 'application/vnd.adobe.flash.movie',
+ 'webm': 'video/webm'
+ },
+ extensionFromType: {
+ 'image/jpeg': 'jpg',
+ 'image/png': 'png',
+ 'image/gif': 'gif',
+ 'application/pdf': 'pdf',
+ 'application/vnd.adobe.flash.movie': 'swf',
+ 'application/x-shockwave-flash': 'swf',
+ 'video/webm': 'webm'
+ },
+ init: function() {
+ var sc;
+ if (!Conf['Quick Reply']) {
+ return;
+ }
+ this.posts = [];
+ $.on(d, '4chanXInitFinished', function() {
+ return BoardConfig.ready(QR.initReady);
+ });
+ Callbacks.Post.push({
+ name: 'Quick Reply',
+ cb: this.node
+ });
+ this.shortcut = sc = $.el('a', {
+ className: 'fa fa-comment-o disabled',
+ textContent: 'QR',
+ title: 'Quick Reply',
+ href: 'javascript:;'
+ });
+ $.on(sc, 'click', function() {
+ if (!QR.postingIsEnabled) {
+ return;
+ }
+ if (Conf['Persistent QR'] || !QR.nodes || QR.nodes.el.hidden) {
+ QR.open();
+ return QR.nodes.com.focus();
+ } else {
+ return QR.close();
+ }
+ });
+ return Header.addShortcut('qr', sc, 540);
+ },
+ initReady: function() {
+ var captchaVersion, config, link, linkBot, navLinksBot, origToggle, prop;
+ captchaVersion = $('#g-recaptcha, #captcha-forced-noscript') ? 'v2' : 't';
+ QR.captcha = Captcha[captchaVersion];
+ QR.postingIsEnabled = true;
+ config = g.BOARD.config;
+ prop = function(key, def) {
+ var ref;
+ return +((ref = config[key]) != null ? ref : def);
+ };
+ QR.min_width = prop('min_image_width', 1);
+ QR.min_height = prop('min_image_height', 1);
+ QR.max_width = QR.max_height = 10000;
+ QR.max_size = prop('max_filesize', 4194304);
+ QR.max_size_video = prop('max_webm_filesize', QR.max_size);
+ QR.max_comment = prop('max_comment_chars', 2000);
+ QR.max_width_video = QR.max_height_video = 2048;
+ QR.max_duration_video = prop('max_webm_duration', 120);
+ QR.forcedAnon = !!config.forced_anon;
+ QR.spoiler = !!config.spoilers;
+ if ((origToggle = $.id('togglePostFormLink'))) {
+ link = $.el('h1', {
+ className: "qr-link-container"
+ });
+ $.extend(link, {innerHTML: "
" + ((g.VIEW === "thread") ? "Reply to Thread" : "Start a Thread") + " "});
+ QR.link = link.firstElementChild;
+ $.on(link.firstChild, 'click', function() {
+ QR.open();
+ return QR.nodes.com.focus();
+ });
+ $.before(origToggle, link);
+ origToggle.firstElementChild.textContent = 'Original Form';
+ }
+ if (g.VIEW === 'thread') {
+ linkBot = $.el('div', {
+ className: "brackets-wrap qr-link-container-bottom"
+ });
+ $.extend(linkBot, {innerHTML: "
Reply to Thread "});
+ $.on(linkBot.firstElementChild, 'click', function() {
+ QR.open();
+ return QR.nodes.com.focus();
+ });
+ if ((navLinksBot = $('.navLinksBot'))) {
+ $.prepend(navLinksBot, linkBot);
+ }
+ }
+ $.on(d, 'QRGetFile', QR.getFile);
+ $.on(d, 'QRDrawFile', QR.drawFile);
+ $.on(d, 'QRSetFile', QR.setFile);
+ $.on(d, 'paste', QR.paste);
+ $.on(d, 'dragover', QR.dragOver);
+ $.on(d, 'drop', QR.dropFile);
+ $.on(d, 'dragstart dragend', QR.drag);
+ $.on(d, 'IndexRefreshInternal', QR.generatePostableThreadsList);
+ $.on(d, 'ThreadUpdate', QR.statusCheck);
+ if (!Conf['Persistent QR']) {
+ return;
+ }
+ QR.open();
+ if (Conf['Auto Hide QR']) {
+ return QR.hide();
+ }
+ },
+ statusCheck: function() {
+ var thread;
+ if (!QR.nodes) {
+ return;
+ }
+ thread = QR.posts[0].thread;
+ if (thread !== 'new' && g.threads.get(g.BOARD + "." + thread).isDead) {
+ return QR.abort();
+ } else {
+ return QR.status();
+ }
+ },
+ node: function() {
+ $.on(this.nodes.quote, 'click', QR.quote);
+ if (this.isFetchedQuote) {
+ return QR.generatePostableThreadsList();
+ }
+ },
+ open: function() {
+ var err;
+ if (QR.nodes) {
+ if (QR.nodes.el.hidden) {
+ QR.captcha.setup();
+ }
+ QR.nodes.el.hidden = false;
+ QR.unhide();
+ } else {
+ try {
+ QR.dialog();
+ } catch (error) {
+ err = error;
+ delete QR.nodes;
+ Main.handleErrors({
+ message: 'Quick Reply dialog creation crashed.',
+ error: err
+ });
+ return;
+ }
+ }
+ return $.rmClass(QR.shortcut, 'disabled');
+ },
+ close: function() {
+ var j, len, post, ref;
+ if (QR.req) {
+ QR.abort();
+ return;
+ }
+ QR.nodes.el.hidden = true;
+ QR.cleanNotifications();
+ QR.blur();
+ $.rmClass(QR.nodes.el, 'dump');
+ $.addClass(QR.shortcut, 'disabled');
+ new QR.post(true);
+ ref = QR.posts.splice(0, QR.posts.length - 1);
+ for (j = 0, len = ref.length; j < len; j++) {
+ post = ref[j];
+ post["delete"]();
+ }
+ QR.cooldown.auto = false;
+ QR.status();
+ return QR.captcha.destroy();
+ },
+ focus: function() {
+ return $.queueTask(function() {
+ if (!QR.inBubble()) {
+ QR.hasFocus = d.activeElement && QR.nodes.el.contains(d.activeElement);
+ return QR.nodes.el.classList.toggle('focus', QR.hasFocus);
+ }
+ });
+ },
+ inBubble: function() {
+ var bubbles, ref;
+ bubbles = $$('iframe[src^="https://www.google.com/recaptcha/api2/frame"]');
+ return (ref = d.activeElement, indexOf.call(bubbles, ref) >= 0) || bubbles.some(function(el) {
+ return getComputedStyle(el).visibility !== 'hidden' && el.getBoundingClientRect().bottom > 0;
+ });
+ },
+ hide: function() {
+ QR.blur();
+ $.addClass(QR.nodes.el, 'autohide');
+ return QR.nodes.autohide.checked = true;
+ },
+ unhide: function() {
+ $.rmClass(QR.nodes.el, 'autohide');
+ return QR.nodes.autohide.checked = false;
+ },
+ toggleHide: function() {
+ if (this.checked) {
+ return QR.hide();
+ } else {
+ return QR.unhide();
+ }
+ },
+ blur: function() {
+ if (QR.nodes.el.contains(d.activeElement)) {
+ return d.activeElement.blur();
+ }
+ },
+ toggleSJIS: function(e) {
+ e.preventDefault();
+ Conf['sjisPreview'] = !Conf['sjisPreview'];
+ $.set('sjisPreview', Conf['sjisPreview']);
+ return QR.nodes.el.classList.toggle('sjis-preview', Conf['sjisPreview']);
+ },
+ texPreviewShow: function() {
+ if ($.hasClass(QR.nodes.el, 'tex-preview')) {
+ return QR.texPreviewHide();
+ }
+ $.addClass(QR.nodes.el, 'tex-preview');
+ QR.nodes.texPreview.textContent = QR.nodes.com.value;
+ return $.event('mathjax', null, QR.nodes.texPreview);
+ },
+ texPreviewHide: function() {
+ return $.rmClass(QR.nodes.el, 'tex-preview');
+ },
+ addPost: function() {
+ var wasOpen;
+ wasOpen = QR.nodes && !QR.nodes.el.hidden;
+ QR.open();
+ if (wasOpen) {
+ $.addClass(QR.nodes.el, 'dump');
+ new QR.post(true);
+ }
+ return QR.nodes.com.focus();
+ },
+ setCustomCooldown: function(enabled) {
+ Conf['customCooldownEnabled'] = enabled;
+ QR.cooldown.customCooldown = enabled;
+ return QR.nodes.customCooldown.classList.toggle('disabled', !enabled);
+ },
+ toggleCustomCooldown: function() {
+ var enabled;
+ enabled = $.hasClass(QR.nodes.customCooldown, 'disabled');
+ QR.setCustomCooldown(enabled);
+ return $.set('customCooldownEnabled', enabled);
+ },
+ error: function(err, focusOverride) {
+ var el, notice, notif;
+ QR.open();
+ if (typeof err === 'string') {
+ el = $.tn(err);
+ } else {
+ el = err;
+ el.removeAttribute('style');
+ }
+ notice = new Notice('warning', el);
+ QR.notifications.push(notice);
+ if (!Header.areNotificationsEnabled) {
+ if (d.hidden && !QR.cooldown.auto) {
+ return alert(el.textContent);
+ }
+ } else if (d.hidden || !(focusOverride || d.hasFocus())) {
+ notif = new Notification(el.textContent, {
+ body: el.textContent,
+ icon: Favicon.logo
+ });
+ notif.onclick = function() {
+ return window.focus();
+ };
+ if ($.engine !== 'gecko') {
+ notif.onclose = function() {
+ return notice.close();
+ };
+ return notif.onshow = function() {
+ return setTimeout(function() {
+ notif.onclose = null;
+ return notif.close();
+ }, 7 * $.SECOND);
+ };
+ }
+ }
+ },
+ connectionError: function() {
+ return $.el('span', {innerHTML: "Connection error while posting. [
More info ]"});
+ },
+ notifications: [],
+ cleanNotifications: function() {
+ var j, len, notification, ref;
+ ref = QR.notifications;
+ for (j = 0, len = ref.length; j < len; j++) {
+ notification = ref[j];
+ notification.close();
+ }
+ return QR.notifications = [];
+ },
+ status: function() {
+ var disabled, status, thread, value;
+ if (!QR.nodes) {
+ return;
+ }
+ thread = QR.posts[0].thread;
+ if (thread !== 'new' && g.threads.get(g.BOARD + "." + thread).isDead) {
+ value = 'Dead';
+ disabled = true;
+ QR.cooldown.auto = false;
+ }
+ value = QR.req ? QR.req.progress : QR.cooldown.seconds || value;
+ status = QR.nodes.status;
+ status.value = !value ? 'Submit' : QR.cooldown.auto ? "Auto " + value : value;
+ return status.disabled = disabled || false;
+ },
+ openPost: function() {
+ var index;
+ QR.open();
+ if (QR.selected.isLocked) {
+ index = QR.posts.indexOf(QR.selected);
+ (QR.posts[index + 1] || new QR.post()).select();
+ $.addClass(QR.nodes.el, 'dump');
+ return QR.cooldown.auto = true;
+ }
+ },
+ quote: function(e) {
+ var ancestor, base, caretPos, com, frag, i, insideCode, j, k, l, len, len1, len2, len3, n, node, o, post, postRange, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, sel, text, thread, wasOnlyQuotes;
+ if (e != null) {
+ e.preventDefault();
+ }
+ if (!QR.postingIsEnabled) {
+ return;
+ }
+ sel = d.getSelection();
+ post = Get.postFromNode(this);
+ root = post.nodes.root;
+ postRange = new Range();
+ postRange.selectNode(root);
+ text = post.board.ID === g.BOARD.ID ? ">>" + post + "\n" : ">>>/" + post.board + "/" + post + "\n";
+ for (i = j = 0, ref = sel.rangeCount; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+ range = sel.getRangeAt(i);
+ if (range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0) {
+ range.setStartBefore(root);
+ }
+ if (range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0) {
+ range.setEndAfter(root);
+ }
+ if (!range.toString().trim()) {
+ continue;
+ }
+ frag = range.cloneContents();
+ ancestor = range.commonAncestorContainer;
+ if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) {
+ $.prepend(frag, $.tn('[spoiler]'));
+ $.add(frag, $.tn('[/spoiler]'));
+ }
+ if (insideCode = $.x('ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor)) {
+ $.prepend(frag, $.tn('[code]'));
+ $.add(frag, $.tn('[/code]'));
+ }
+ ref1 = $$((insideCode ? 'br' : '.prettyprint br'), frag);
+ for (k = 0, len = ref1.length; k < len; k++) {
+ node = ref1[k];
+ $.replace(node, $.tn('\n'));
+ }
+ ref2 = $$('br', frag);
+ for (l = 0, len1 = ref2.length; l < len1; l++) {
+ node = ref2[l];
+ if (node !== frag.lastChild) {
+ $.replace(node, $.tn('\n>'));
+ }
+ }
+ if (typeof (base = g.SITE).insertTags === "function") {
+ base.insertTags(frag);
+ }
+ ref3 = $$('.linkify[data-original]', frag);
+ for (n = 0, len2 = ref3.length; n < len2; n++) {
+ node = ref3[n];
+ $.replace(node, $.tn(node.dataset.original));
+ }
+ ref4 = $$('.embedder', frag);
+ for (o = 0, len3 = ref4.length; o < len3; o++) {
+ node = ref4[o];
+ if (((ref5 = node.previousSibling) != null ? ref5.nodeValue : void 0) === ' ') {
+ $.rm(node.previousSibling);
+ }
+ $.rm(node);
+ }
+ text += ">" + (frag.textContent.trim()) + "\n";
+ }
+ QR.openPost();
+ ref6 = QR.nodes, com = ref6.com, thread = ref6.thread;
+ if (!com.value) {
+ thread.value = Get.threadFromNode(this);
+ }
+ wasOnlyQuotes = QR.selected.isOnlyQuotes();
+ caretPos = com.selectionStart;
+ com.value = com.value.slice(0, caretPos) + text + com.value.slice(com.selectionEnd);
+ range = caretPos + text.length;
+ com.setSelectionRange(range, range);
+ com.focus();
+ if (wasOnlyQuotes) {
+ QR.selected.quotedText = com.value;
+ }
+ QR.selected.save(com);
+ return QR.selected.save(thread);
+ },
+ characterCount: function() {
+ var count, counter;
+ counter = QR.nodes.charCount;
+ count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length;
+ counter.textContent = count;
+ counter.hidden = count < QR.max_comment / 2;
+ return (count > QR.max_comment ? $.addClass : $.rmClass)(counter, 'warning');
+ },
+ getFile: function() {
+ var ref;
+ return $.event('QRFile', (ref = QR.selected) != null ? ref.file : void 0);
+ },
+ drawFile: function(e) {
+ var el, file, isVideo, ref;
+ file = (ref = QR.selected) != null ? ref.file : void 0;
+ if (!(file && /^(image|video)\//.test(file.type))) {
+ return;
+ }
+ isVideo = /^video\//.test(file);
+ el = $.el((isVideo ? 'video' : 'img'));
+ $.on(el, 'error', function() {
+ return QR.openError();
+ });
+ $.on(el, (isVideo ? 'loadeddata' : 'load'), function() {
+ e.target.getContext('2d').drawImage(el, 0, 0);
+ URL.revokeObjectURL(el.src);
+ return $.event('QRImageDrawn', null, e.target);
+ });
+ return el.src = URL.createObjectURL(file);
+ },
+ openError: function() {
+ var div;
+ div = $.el('div');
+ $.extend(div, {innerHTML: "Could not open file. [
More info ]"});
+ return QR.error(div);
+ },
+ setFile: function(e) {
+ var file, name, ref, source;
+ ref = e.detail, file = ref.file, name = ref.name, source = ref.source;
+ if (name != null) {
+ file.name = name;
+ }
+ if (source != null) {
+ file.source = source;
+ }
+ QR.open();
+ return QR.handleFiles([file]);
+ },
+ drag: function(e) {
+ var toggle;
+ toggle = e.type === 'dragstart' ? $.off : $.on;
+ toggle(d, 'dragover', QR.dragOver);
+ return toggle(d, 'drop', QR.dropFile);
+ },
+ dragOver: function(e) {
+ e.preventDefault();
+ return e.dataTransfer.dropEffect = 'copy';
+ },
+ dropFile: function(e) {
+ if (!e.dataTransfer.files.length) {
+ return;
+ }
+ e.preventDefault();
+ QR.open();
+ return QR.handleFiles(e.dataTransfer.files);
+ },
+ paste: function(e) {
+ var blob, file, file2, item, j, len, ref, score, score2, type;
+ if (!e.clipboardData.items) {
+ return;
+ }
+ file = null;
+ score = -1;
+ ref = e.clipboardData.items;
+ for (j = 0, len = ref.length; j < len; j++) {
+ item = ref[j];
+ if (!(item.kind === 'file' && (file2 = item.getAsFile()))) {
+ continue;
+ }
+ score2 = 2 * (file2.size <= QR.max_size) + (file2.type === 'image/png');
+ if (score2 > score) {
+ file = file2;
+ score = score2;
+ }
+ }
+ if (file) {
+ type = file.type;
+ blob = new Blob([file], {
+ type: type
+ });
+ blob.name = Conf['pastedname'] + "." + ($.getOwn(QR.extensionFromType, type) || 'jpg');
+ QR.open();
+ QR.handleFiles([blob]);
+ $.addClass(QR.nodes.el, 'dump');
+ }
+ },
+ pasteFF: function() {
+ var arr, blob, bstr, i, images, img, j, k, len, m, pasteArea, ref, src;
+ pasteArea = QR.nodes.pasteArea;
+ if (!pasteArea.childNodes.length) {
+ return;
+ }
+ images = $$('img', pasteArea);
+ $.rmAll(pasteArea);
+ for (j = 0, len = images.length; j < len; j++) {
+ img = images[j];
+ src = img.src;
+ if (m = src.match(/data:(image\/(\w+));base64,(.+)/)) {
+ bstr = atob(m[3]);
+ arr = new Uint8Array(bstr.length);
+ for (i = k = 0, ref = bstr.length; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) {
+ arr[i] = bstr.charCodeAt(i);
+ }
+ blob = new Blob([arr], {
+ type: m[1]
+ });
+ blob.name = Conf['pastedname'] + "." + m[2];
+ QR.handleFiles([blob]);
+ } else if (/^https?:\/\//.test(src)) {
+ QR.handleUrl(src);
+ }
+ }
+ },
+ handleUrl: function(urlDefault) {
+ QR.open();
+ QR.selected.preventAutoPost();
+ return CrossOrigin.permission(function() {
+ var url;
+ url = prompt('Enter a URL:', urlDefault);
+ if (url === null) {
+ return;
+ }
+ QR.nodes.fileButton.focus();
+ return CrossOrigin.file(url, function(blob) {
+ if (blob && !/^text\//.test(blob.type)) {
+ return QR.handleFiles([blob]);
+ } else {
+ return QR.error("Can't load file.");
+ }
+ });
+ });
+ },
+ handleFiles: function(files) {
+ var file, j, len;
+ if (this !== QR) {
+ files = slice.call(this.files);
+ this.value = null;
+ }
+ if (!files.length) {
+ return;
+ }
+ QR.cleanNotifications();
+ for (j = 0, len = files.length; j < len; j++) {
+ file = files[j];
+ QR.handleFile(file, files.length);
+ }
+ if (files.length !== 1) {
+ $.addClass(QR.nodes.el, 'dump');
+ }
+ if (d.activeElement === QR.nodes.fileButton && $.hasClass(QR.nodes.fileSubmit, 'has-file')) {
+ return QR.nodes.filename.focus();
+ }
+ },
+ handleFile: function(file, nfiles) {
+ var isText, post;
+ isText = /^text\//.test(file.type);
+ if (nfiles === 1) {
+ post = QR.selected;
+ } else {
+ post = QR.posts[QR.posts.length - 1];
+ if ((isText ? post.com || post.pasting : post.file)) {
+ post = new QR.post();
+ }
+ }
+ return post[isText ? 'pasteText' : 'setFile'](file);
+ },
+ openFileInput: function() {
+ if (QR.nodes.fileButton.disabled) {
+ return;
+ }
+ QR.nodes.fileInput.click();
+ return QR.nodes.fileButton.focus();
+ },
+ generatePostableThreadsList: function() {
+ var j, len, list, options, ref, thread, val;
+ if (!QR.nodes) {
+ return;
+ }
+ list = QR.nodes.thread;
+ options = [list.firstElementChild];
+ ref = g.BOARD.threads.keys;
+ for (j = 0, len = ref.length; j < len; j++) {
+ thread = ref[j];
+ options.push($.el('option', {
+ value: thread,
+ textContent: "Thread " + thread
+ }));
+ }
+ val = list.value;
+ $.rmAll(list);
+ $.add(list, options);
+ list.value = val;
+ if (list.value === val) {
+ return;
+ }
+ list.value = g.VIEW === 'thread' ? g.THREADID : 'new';
+ return (g.VIEW === 'thread' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
+ },
+ dialog: function() {
+ var classList, config, dialog, event, i, items, name, node, nodes, save, setNode;
+ QR.nodes = nodes = {
+ el: dialog = UI.dialog('qr', {innerHTML: "
"})
+ };
+ setNode = function(name, query) {
+ return nodes[name] = $(query, dialog);
+ };
+ setNode('move', '.move');
+ setNode('autohide', '#autohide');
+ setNode('close', '.close');
+ setNode('thread', 'select');
+ setNode('form', 'form');
+ setNode('sjisToggle', '#sjis-toggle');
+ setNode('texButton', '#tex-preview-button');
+ setNode('name', '[data-name=name]');
+ setNode('email', '[data-name=email]');
+ setNode('sub', '[data-name=sub]');
+ setNode('com', '[data-name=com]');
+ setNode('charCount', '#char-count');
+ setNode('texPreview', '#tex-preview');
+ setNode('dumpList', '#dump-list');
+ setNode('addPost', '#add-post');
+ setNode('oekaki', '.oekaki');
+ setNode('drawButton', '#qr-draw-button');
+ setNode('fileSubmit', '#file-n-submit');
+ setNode('fileButton', '#qr-file-button');
+ setNode('noFile', '#qr-no-file');
+ setNode('filename', '#qr-filename');
+ setNode('spoiler', '#qr-file-spoiler');
+ setNode('oekakiButton', '#qr-oekaki-button');
+ setNode('fileRM', '#qr-filerm');
+ setNode('urlButton', '#url-button');
+ setNode('pasteArea', '#paste-area');
+ setNode('customCooldown', '#custom-cooldown-button');
+ setNode('dumpButton', '#dump-button');
+ setNode('status', '[type=submit]');
+ setNode('flashTag', '[name=filetag]');
+ setNode('fileInput', '[type=file]');
+ config = g.BOARD.config;
+ classList = QR.nodes.el.classList;
+ classList.toggle('forced-anon', QR.forcedAnon);
+ classList.toggle('has-spoiler', QR.spoiler);
+ classList.toggle('has-sjis', !!config.sjis_tags);
+ classList.toggle('has-math', !!config.math_tags);
+ classList.toggle('sjis-preview', !!config.sjis_tags && Conf['sjisPreview']);
+ classList.toggle('show-new-thread-option', Conf['Show New Thread Option in Threads']);
+ if (parseInt(Conf['customCooldown'], 10) > 0) {
+ $.addClass(QR.nodes.fileSubmit, 'custom-cooldown');
+ $.get('customCooldownEnabled', Conf['customCooldownEnabled'], function(arg) {
+ var customCooldownEnabled;
+ customCooldownEnabled = arg.customCooldownEnabled;
+ QR.setCustomCooldown(customCooldownEnabled);
+ return $.sync('customCooldownEnabled', QR.setCustomCooldown);
+ });
+ }
+ QR.flagsInput();
+ $.on(nodes.autohide, 'change', QR.toggleHide);
+ $.on(nodes.close, 'click', QR.close);
+ $.on(nodes.status, 'click', QR.submit);
+ $.on(nodes.form, 'submit', QR.submit);
+ $.on(nodes.sjisToggle, 'click', QR.toggleSJIS);
+ $.on(nodes.texButton, 'mousedown', QR.texPreviewShow);
+ $.on(nodes.texButton, 'mouseup', QR.texPreviewHide);
+ $.on(nodes.addPost, 'click', function() {
+ return new QR.post(true);
+ });
+ $.on(nodes.drawButton, 'click', QR.oekaki.draw);
+ $.on(nodes.fileButton, 'click', QR.openFileInput);
+ $.on(nodes.noFile, 'click', QR.openFileInput);
+ $.on(nodes.filename, 'focus', function() {
+ return $.addClass(this.parentNode, 'focus');
+ });
+ $.on(nodes.filename, 'blur', function() {
+ return $.rmClass(this.parentNode, 'focus');
+ });
+ $.on(nodes.spoiler, 'change', function() {
+ return QR.selected.nodes.spoiler.click();
+ });
+ $.on(nodes.oekakiButton, 'click', QR.oekaki.button);
+ $.on(nodes.fileRM, 'click', function() {
+ return QR.selected.rmFile();
+ });
+ $.on(nodes.urlButton, 'click', function() {
+ return QR.handleUrl('');
+ });
+ $.on(nodes.customCooldown, 'click', QR.toggleCustomCooldown);
+ $.on(nodes.dumpButton, 'click', function() {
+ return nodes.el.classList.toggle('dump');
+ });
+ $.on(nodes.fileInput, 'change', QR.handleFiles);
+ window.addEventListener('focus', QR.focus, true);
+ window.addEventListener('blur', QR.focus, true);
+ $.on(d, 'click', QR.focus);
+ if ($.engine === 'gecko' && !window.DataTransferItemList) {
+ nodes.pasteArea.hidden = false;
+ }
+ new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, {
+ childList: true
+ });
+ items = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag'];
+ i = 0;
+ save = function() {
+ return QR.selected.save(this);
+ };
+ while (name = items[i++]) {
+ if (!(node = nodes[name])) {
+ continue;
+ }
+ event = node.nodeName === 'SELECT' ? 'change' : 'input';
+ $.on(nodes[name], event, save);
+ }
+ if ($.engine === 'gecko' && Conf['Remember QR Size']) {
+ $.get('QR Size', '', function(item) {
+ return nodes.com.style.cssText = item['QR Size'];
+ });
+ $.on(nodes.com, 'mouseup', function(e) {
+ if (e.button !== 0) {
+ return;
+ }
+ return $.set('QR Size', this.style.cssText);
+ });
+ }
+ QR.generatePostableThreadsList();
+ QR.persona.load();
+ new QR.post(true);
+ QR.status();
+ QR.cooldown.setup();
+ QR.captcha.init();
+ $.add(d.body, dialog);
+ QR.captcha.setup();
+ QR.oekaki.setup();
+ return $.event('QRDialogCreation', null, dialog);
+ },
+ flags: function() {
+ var addFlag, ref, select, textContent, value;
+ select = $.el('select', {
+ name: 'flag',
+ className: 'flagSelector'
+ });
+ addFlag = function(value, textContent) {
+ return $.add(select, $.el('option', {
+ value: value,
+ textContent: textContent
+ }));
+ };
+ addFlag('0', (g.BOARD.config.country_flags ? 'Geographic Location' : 'None'));
+ ref = g.BOARD.config.board_flags;
+ for (value in ref) {
+ textContent = ref[value];
+ addFlag(value, textContent);
+ }
+ return select;
+ },
+ flagsInput: function() {
+ var flag, nodes;
+ nodes = QR.nodes;
+ if (!nodes) {
+ return;
+ }
+ if (nodes.flag) {
+ $.rm(nodes.flag);
+ delete nodes.flag;
+ }
+ if (g.BOARD.config.board_flags) {
+ flag = QR.flags();
+ flag.dataset.name = 'flag';
+ flag.dataset["default"] = '0';
+ nodes.flag = flag;
+ return $.add(nodes.form, flag);
+ }
+ },
+ submit: function(e) {
+ var captcha, cb, err, filetag, force, formData, options, post, ref, thread, threadID;
+ if (e != null) {
+ e.preventDefault();
+ }
+ force = e != null ? e.shiftKey : void 0;
+ if (QR.req) {
+ QR.abort();
+ return;
+ }
+ $.forceSync('cooldowns');
+ if (QR.cooldown.seconds) {
+ if (force) {
+ QR.cooldown.clear();
+ } else {
+ QR.cooldown.auto = !QR.cooldown.auto;
+ QR.status();
+ return;
+ }
+ }
+ post = QR.posts[0];
+ delete post.quotedText;
+ post.forceSave();
+ threadID = post.thread;
+ thread = g.BOARD.threads.get(threadID);
+ if (g.BOARD.ID === 'f' && threadID === 'new') {
+ filetag = QR.nodes.flashTag.value;
+ }
+ if (threadID === 'new') {
+ threadID = null;
+ if (!!g.BOARD.config.require_subject && !post.sub) {
+ err = 'New threads require a subject.';
+ } else if (!(!!g.BOARD.config.text_only || post.file)) {
+ err = 'No file selected.';
+ }
+ } else if (g.BOARD.threads.get(threadID).isClosed) {
+ err = 'You can\'t reply to this thread anymore.';
+ } else if (!(post.com || post.file)) {
+ err = 'No comment or file.';
+ } else if (post.file && thread.fileLimit) {
+ err = 'Max limit of image replies has been reached.';
+ }
+ if (g.BOARD.ID === 'r9k' && !((ref = post.com) != null ? ref.match(/[a-z-]/i) : void 0)) {
+ err || (err = 'Original comment required.');
+ }
+ if (QR.captcha.isEnabled && !(QR.captcha === Captcha.v2 && /\b_ct=/.test(d.cookie) && threadID) && !(err && !force)) {
+ captcha = QR.captcha.getOne(!!threadID);
+ if (QR.captcha === Captcha.v2) {
+ captcha || (captcha = Captcha.cache.request(!!threadID));
+ }
+ if (!captcha) {
+ err = 'No valid captcha.';
+ QR.captcha.setup(!QR.cooldown.auto || d.activeElement === QR.nodes.status);
+ }
+ }
+ QR.cleanNotifications();
+ if (err && !force) {
+ QR.cooldown.auto = false;
+ QR.status();
+ QR.error(err);
+ return;
+ }
+ QR.cooldown.auto = QR.posts.length > 1;
+ post.lock();
+ formData = {
+ resto: threadID,
+ name: !QR.forcedAnon ? post.name : void 0,
+ email: post.email,
+ sub: !(QR.forcedAnon || threadID) ? post.sub : void 0,
+ com: post.com,
+ upfile: post.file,
+ filetag: filetag,
+ spoiler: post.spoiler,
+ flag: post.flag,
+ mode: 'regist',
+ pwd: QR.persona.getPassword()
+ };
+ options = {
+ responseType: 'document',
+ withCredentials: true,
+ onloadend: QR.response,
+ form: $.formData(formData)
+ };
+ if (Conf['Show Upload Progress']) {
+ options.onprogress = function(e) {
+ var ref1;
+ if (this !== ((ref1 = QR.req) != null ? ref1.upload : void 0)) {
+ return;
+ }
+ if (e.loaded < e.total) {
+ QR.req.progress = (Math.round(e.loaded / e.total * 100)) + "%";
+ } else {
+ QR.req.isUploadFinished = true;
+ QR.req.progress = '...';
+ }
+ return QR.status();
+ };
+ }
+ cb = function(response) {
+ var key, val;
+ if (response != null) {
+ QR.currentCaptcha = response;
+ if (QR.captcha === Captcha.v2) {
+ if (response.challenge != null) {
+ options.form.append('recaptcha_challenge_field', response.challenge);
+ options.form.append('recaptcha_response_field', response.response);
+ } else {
+ options.form.append('g-recaptcha-response', response.response);
+ }
+ } else {
+ for (key in response) {
+ val = response[key];
+ options.form.append(key, val);
+ }
+ }
+ }
+ QR.req = $.ajax("https://sys." + (location.hostname.split('.')[1]) + ".org/" + g.BOARD + "/post", options);
+ return QR.req.progress = '...';
+ };
+ if (typeof captcha === 'function') {
+ QR.req = {
+ progress: '...',
+ abort: function() {
+ if (QR.captcha === Captcha.v2) {
+ Captcha.cache.abort();
+ }
+ return cb = null;
+ }
+ };
+ captcha(function(response) {
+ if (QR.captcha === Captcha.v2 && Captcha.cache.haveCookie()) {
+ if (typeof cb === "function") {
+ cb();
+ }
+ if (response) {
+ return Captcha.cache.save(response);
+ }
+ } else if (response) {
+ return typeof cb === "function" ? cb(response) : void 0;
+ } else {
+ delete QR.req;
+ post.unlock();
+ QR.cooldown.auto = !!Captcha.cache.getCount();
+ return QR.status();
+ }
+ });
+ } else {
+ cb(captcha);
+ }
+ return QR.status();
+ },
+ response: function() {
+ var URL, _, base, connErr, err, h1, isReply, j, lastPostToThread, len, m, mi, open, post, postID, postsCount, ref, ref1, ref2, ref3, seconds, threadID;
+ if (this !== QR.req) {
+ return;
+ }
+ delete QR.req;
+ post = QR.posts[0];
+ post.unlock();
+ if ((err = (ref = this.response) != null ? ref.getElementById('errmsg') : void 0)) {
+ if ((ref1 = $('a', err)) != null) {
+ ref1.target = '_blank';
+ }
+ } else if ((connErr = !this.response || this.response.title !== 'Post successful!')) {
+ err = QR.connectionError();
+ if (QR.captcha === Captcha.v2 && QR.currentCaptcha) {
+ Captcha.cache.save(QR.currentCaptcha);
+ }
+ } else if (this.status !== 200) {
+ err = "Error " + this.statusText + " (" + this.status + ")";
+ }
+ if (!connErr) {
+ if (typeof (base = QR.captcha).setUsed === "function") {
+ base.setUsed();
+ }
+ }
+ delete QR.currentCaptcha;
+ if (err) {
+ QR.errorCount = (QR.errorCount || 0) + 1;
+ if (/captcha|verification/i.test(err.textContent) || connErr) {
+ if (/mistyped/i.test(err.textContent)) {
+ err = 'You mistyped the CAPTCHA, or the CAPTCHA malfunctioned.';
+ } else if (/expired/i.test(err.textContent)) {
+ err = 'This CAPTCHA is no longer valid because it has expired.';
+ }
+ if (QR.errorCount >= 5) {
+ QR.cooldown.auto = false;
+ } else {
+ QR.cooldown.auto = QR.captcha.isEnabled || connErr;
+ QR.cooldown.addDelay(post, 2);
+ }
+ } else if (err.textContent && (m = err.textContent.match(/\d+\s+(?:minute|second)/gi)) && !/duplicate|hour/i.test(err.textContent)) {
+ QR.cooldown.auto = !/have\s+been\s+muted/i.test(err.textContent);
+ seconds = 0;
+ for (j = 0, len = m.length; j < len; j++) {
+ mi = m[j];
+ seconds += (/minute/i.test(mi) ? 60 : 1) * (+mi.match(/\d+/)[0]);
+ }
+ if (/muted/i.test(err.textContent)) {
+ QR.cooldown.addMute(seconds);
+ } else {
+ QR.cooldown.addDelay(post, seconds);
+ }
+ } else {
+ QR.cooldown.auto = false;
+ }
+ QR.captcha.setup(QR.cooldown.auto && ((ref2 = d.activeElement) === QR.nodes.status || ref2 === d.body));
+ QR.status();
+ QR.error(err);
+ return;
+ }
+ delete QR.errorCount;
+ h1 = $('h1', this.response);
+ ref3 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = ref3[0], threadID = ref3[1], postID = ref3[2];
+ postID = +postID;
+ threadID = +threadID || postID;
+ isReply = threadID !== postID;
+ $.event('QRPostSuccessful', {
+ boardID: g.BOARD.ID,
+ threadID: threadID,
+ postID: postID
+ });
+ $.event('QRPostSuccessful_', {
+ boardID: g.BOARD.ID,
+ threadID: threadID,
+ postID: postID
+ });
+ postsCount = QR.posts.length - 1;
+ QR.cooldown.auto = postsCount && isReply;
+ lastPostToThread = !((function() {
+ var k, len1, p, ref4;
+ ref4 = QR.posts.slice(1);
+ for (k = 0, len1 = ref4.length; k < len1; k++) {
+ p = ref4[k];
+ if (p.thread === post.thread) {
+ return true;
+ }
+ }
+ })());
+ if (postsCount) {
+ post.rm();
+ QR.captcha.setup(d.activeElement === QR.nodes.status);
+ } else if (Conf['Persistent QR']) {
+ post.rm();
+ if (Conf['Auto Hide QR']) {
+ QR.hide();
+ } else {
+ QR.blur();
+ }
+ } else {
+ QR.close();
+ }
+ QR.cleanNotifications();
+ if (Conf['Posting Success Notifications']) {
+ QR.notifications.push(new Notice('success', h1.textContent, 5));
+ }
+ QR.cooldown.add(threadID, postID);
+ URL = threadID === postID ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID : threadID !== g.THREADID && lastPostToThread && Conf['Open Post in New Tab'] ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0;
+ if (URL) {
+ open = Conf['Open Post in New Tab'] || postsCount ? function() {
+ return $.open(URL);
+ } : function() {
+ return location.href = URL;
+ };
+ if (threadID === postID) {
+ QR.waitForThread(URL, open);
+ } else {
+ open();
+ }
+ }
+ return QR.status();
+ },
+ waitForThread: function(url, cb) {
+ var attempts, check;
+ attempts = 0;
+ check = function() {
+ return $.ajax(url, {
+ onloadend: function() {
+ attempts++;
+ if (attempts >= 6 || this.status === 200) {
+ return cb();
+ } else {
+ return setTimeout(check, attempts * $.SECOND);
+ }
+ },
+ responseType: 'text',
+ type: 'HEAD'
+ });
+ };
+ return check();
+ },
+ abort: function() {
+ var oldReq;
+ if ((oldReq = QR.req) && !QR.req.isUploadFinished) {
+ delete QR.req;
+ oldReq.abort();
+ if (QR.captcha === Captcha.v2 && QR.currentCaptcha) {
+ Captcha.cache.save(QR.currentCaptcha);
+ }
+ delete QR.currentCaptcha;
+ QR.posts[0].unlock();
+ QR.cooldown.auto = false;
+ QR.notifications.push(new Notice('info', 'QR upload aborted.', 5));
+ }
+ return QR.status();
+ }
+ };
+
+ return QR;
+
+}).call(this);
+
+(function() {
+ QR.cooldown = {
+ seconds: 0,
+ delays: {
+ deletion: 60
+ },
+ init: function() {
+ if (!Conf['Quick Reply']) {
+ return;
+ }
+ this.data = Conf['cooldowns'];
+ this.changes = $.dict();
+ return $.sync('cooldowns', this.sync);
+ },
+ setup: function() {
+ var delay, ref, type;
+ $.extend(QR.cooldown.delays, g.BOARD.cooldowns());
+ QR.cooldown.maxDelay = 0;
+ ref = QR.cooldown.delays;
+ for (type in ref) {
+ delay = ref[type];
+ if (type !== 'thread' && type !== 'thread_global') {
+ QR.cooldown.maxDelay = Math.max(QR.cooldown.maxDelay, delay);
+ }
+ }
+ QR.cooldown.isSetup = true;
+ return QR.cooldown.start();
+ },
+ start: function() {
+ var data;
+ data = QR.cooldown.data;
+ if (!(Conf['Cooldown'] && QR.cooldown.isSetup && !QR.cooldown.isCounting && Object.keys(data[g.BOARD.ID] || {}).length + Object.keys(data.global || {}).length > 0)) {
+ return;
+ }
+ QR.cooldown.isCounting = true;
+ return QR.cooldown.count();
+ },
+ sync: function(data) {
+ QR.cooldown.data = data || $.dict();
+ return QR.cooldown.start();
+ },
+ add: function(threadID, postID) {
+ var boardID, start;
+ if (!Conf['Cooldown']) {
+ return;
+ }
+ start = Date.now();
+ boardID = g.BOARD.ID;
+ QR.cooldown.set(boardID, start, {
+ threadID: threadID,
+ postID: postID
+ });
+ if (threadID === postID) {
+ QR.cooldown.set('global', start, {
+ boardID: boardID,
+ threadID: threadID,
+ postID: postID
+ });
+ }
+ QR.cooldown.save();
+ return QR.cooldown.start();
+ },
+ addDelay: function(post, delay) {
+ var cooldown;
+ if (!Conf['Cooldown']) {
+ return;
+ }
+ cooldown = QR.cooldown.categorize(post);
+ cooldown.delay = delay;
+ QR.cooldown.set(g.BOARD.ID, Date.now(), cooldown);
+ QR.cooldown.save();
+ return QR.cooldown.start();
+ },
+ addMute: function(delay) {
+ if (!Conf['Cooldown']) {
+ return;
+ }
+ QR.cooldown.set(g.BOARD.ID, Date.now(), {
+ type: 'mute',
+ delay: delay
+ });
+ QR.cooldown.save();
+ return QR.cooldown.start();
+ },
+ "delete": function(post) {
+ var base, cooldown, cooldowns, id, name;
+ if (!QR.cooldown.data) {
+ return;
+ }
+ cooldowns = ((base = QR.cooldown.data)[name = post.board.ID] || (base[name] = $.dict()));
+ for (id in cooldowns) {
+ cooldown = cooldowns[id];
+ if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) {
+ QR.cooldown.set(post.board.ID, id, null);
+ }
+ }
+ return QR.cooldown.save();
+ },
+ secondsDeletion: function(post) {
+ var cooldown, cooldowns, seconds, start;
+ if (!(QR.cooldown.data && Conf['Cooldown'])) {
+ return 0;
+ }
+ cooldowns = QR.cooldown.data[post.board.ID] || $.dict();
+ for (start in cooldowns) {
+ cooldown = cooldowns[start];
+ if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) {
+ seconds = QR.cooldown.delays.deletion - Math.floor((Date.now() - start) / $.SECOND);
+ return Math.max(seconds, 0);
+ }
+ }
+ return 0;
+ },
+ categorize: function(post) {
+ if (post.thread === 'new') {
+ return {
+ type: 'thread'
+ };
+ } else {
+ return {
+ type: !!post.file ? 'image' : 'reply',
+ threadID: +post.thread
+ };
+ }
+ },
+ mergeChange: function(data, scope, id, value) {
+ if (value) {
+ return (data[scope] || (data[scope] = $.dict()))[id] = value;
+ } else if (scope in data) {
+ delete data[scope][id];
+ if (Object.keys(data[scope]).length === 0) {
+ return delete data[scope];
+ }
+ }
+ },
+ set: function(scope, id, value) {
+ var base;
+ QR.cooldown.mergeChange(QR.cooldown.data, scope, id, value);
+ return ((base = QR.cooldown.changes)[scope] || (base[scope] = $.dict()))[id] = value;
+ },
+ save: function() {
+ var changes;
+ changes = QR.cooldown.changes;
+ if (!Object.keys(changes).length) {
+ return;
+ }
+ return $.get('cooldowns', $.dict(), function(arg) {
+ var cooldowns, id, ref, scope, value;
+ cooldowns = arg.cooldowns;
+ for (scope in QR.cooldown.changes) {
+ ref = QR.cooldown.changes[scope];
+ for (id in ref) {
+ value = ref[id];
+ QR.cooldown.mergeChange(cooldowns, scope, id, value);
+ }
+ QR.cooldown.data = cooldowns;
+ }
+ return $.set('cooldowns', cooldowns, function() {
+ return QR.cooldown.changes = $.dict();
+ });
+ });
+ },
+ clear: function() {
+ QR.cooldown.data = $.dict();
+ QR.cooldown.changes = $.dict();
+ QR.cooldown.auto = false;
+ QR.cooldown.update();
+ return $.queueTask($["delete"], 'cooldowns');
+ },
+ update: function() {
+ var base, cooldown, cooldowns, elapsed, i, len, maxDelay, nCooldowns, now, ref, ref1, save, scope, seconds, start, suffix, threadID, type, update;
+ if (!QR.cooldown.isCounting) {
+ return;
+ }
+ save = false;
+ nCooldowns = 0;
+ now = Date.now();
+ ref = QR.cooldown.categorize(QR.posts[0]), type = ref.type, threadID = ref.threadID;
+ seconds = 0;
+ if (Conf['Cooldown']) {
+ ref1 = [g.BOARD.ID, 'global'];
+ for (i = 0, len = ref1.length; i < len; i++) {
+ scope = ref1[i];
+ cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = $.dict()));
+ for (start in cooldowns) {
+ cooldown = cooldowns[start];
+ start = +start;
+ elapsed = Math.floor((now - start) / $.SECOND);
+ if (elapsed < 0) {
+ QR.cooldown.set(scope, start, null);
+ save = true;
+ continue;
+ }
+ if (cooldown.delay != null) {
+ if (cooldown.delay <= elapsed) {
+ QR.cooldown.set(scope, start, null);
+ save = true;
+ } else if ((cooldown.type === type && cooldown.threadID === threadID) || cooldown.type === 'mute') {
+ seconds = Math.max(seconds, cooldown.delay - elapsed);
+ }
+ continue;
+ }
+ maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread'];
+ if (QR.cooldown.customCooldown) {
+ maxDelay = Math.max(maxDelay, parseInt(Conf['customCooldown'], 10));
+ }
+ if (maxDelay <= elapsed) {
+ QR.cooldown.set(scope, start, null);
+ save = true;
+ continue;
+ }
+ if ((type === 'thread') === (cooldown.threadID === cooldown.postID) && cooldown.boardID !== g.BOARD.ID) {
+ suffix = scope === 'global' ? '_global' : '';
+ seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed);
+ if (QR.cooldown.customCooldown) {
+ seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed);
+ }
+ }
+ }
+ nCooldowns += Object.keys(cooldowns).length;
+ }
+ }
+ if (save) {
+ QR.cooldown.save;
+ }
+ if (nCooldowns) {
+ clearTimeout(QR.cooldown.timeout);
+ QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
+ } else {
+ delete QR.cooldown.isCounting;
+ }
+ update = seconds !== QR.cooldown.seconds;
+ QR.cooldown.seconds = seconds;
+ if (update) {
+ return QR.status();
+ }
+ },
+ count: function() {
+ QR.cooldown.update();
+ if (QR.cooldown.seconds === 0 && QR.cooldown.auto && !QR.req) {
+ return QR.submit();
+ }
+ }
+ };
+
+}).call(this);
+
+(function() {
+ QR.oekaki = {
+ menu: {
+ init: function() {
+ var a, ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Edit Link'] && Conf['Quick Reply'])) {
+ return;
+ }
+ a = $.el('a', {
+ className: 'edit-link',
+ href: 'javascript:;',
+ textContent: 'Edit image'
+ });
+ $.on(a, 'click', this.editFile);
+ return Menu.menu.addEntry({
+ el: a,
+ order: 90,
+ open: function(post) {
+ var file;
+ QR.oekaki.menu.post = post;
+ file = post.file;
+ return QR.postingIsEnabled && !!file && (file.isImage || file.isVideo);
+ }
+ });
+ },
+ editFile: function() {
+ var currentTime, isVideo, post, ref;
+ post = QR.oekaki.menu.post;
+ QR.quote.call(post.nodes.post);
+ isVideo = post.file.isVideo;
+ currentTime = ((ref = post.file.fullImage) != null ? ref.currentTime : void 0) || 0;
+ return CrossOrigin.file(post.file.url, function(blob) {
+ var video;
+ if (!blob) {
+ return QR.error("Can't load file.");
+ } else if (isVideo) {
+ video = $.el('video');
+ $.on(video, 'loadedmetadata', function() {
+ $.on(video, 'seeked', function() {
+ var canvas;
+ canvas = $.el('canvas', {
+ width: video.videoWidth,
+ height: video.videoHeight
+ });
+ canvas.getContext('2d').drawImage(video, 0, 0);
+ return canvas.toBlob(function(snapshot) {
+ snapshot.name = post.file.name.replace(/\.\w+$/, '') + '.png';
+ QR.handleFiles([snapshot]);
+ return QR.oekaki.edit();
+ });
+ });
+ return video.currentTime = currentTime;
+ });
+ $.on(video, 'error', function() {
+ return QR.openError();
+ });
+ return video.src = URL.createObjectURL(blob);
+ } else {
+ blob.name = post.file.name;
+ QR.handleFiles([blob]);
+ return QR.oekaki.edit();
+ }
+ });
+ }
+ },
+ setup: function() {
+ return $.global(function() {
+ var FCX;
+ FCX = window.FCX;
+ FCX.oekakiCB = function() {
+ return window.Tegaki.flatten().toBlob(function(file) {
+ var source;
+ source = "oekaki-" + (Date.now());
+ FCX.oekakiLatest = source;
+ return document.dispatchEvent(new CustomEvent('QRSetFile', {
+ bubbles: true,
+ detail: {
+ file: file,
+ name: FCX.oekakiName,
+ source: source
+ }
+ }));
+ });
+ };
+ if (window.Tegaki) {
+ return document.querySelector('#qr .oekaki').hidden = false;
+ }
+ });
+ },
+ load: function(cb) {
+ var n, onload, script, style;
+ if ($('script[src^="//s.4cdn.org/js/tegaki"]', d.head)) {
+ return cb();
+ } else {
+ style = $.el('link', {
+ rel: 'stylesheet',
+ href: "//s.4cdn.org/css/tegaki." + (Date.now()) + ".css"
+ });
+ script = $.el('script', {
+ src: "//s.4cdn.org/js/tegaki.min." + (Date.now()) + ".js"
+ });
+ n = 0;
+ onload = function() {
+ if (++n === 2) {
+ return cb();
+ }
+ };
+ $.on(style, 'load', onload);
+ $.on(script, 'load', onload);
+ return $.add(d.head, [style, script]);
+ }
+ },
+ draw: function() {
+ return $.global(function() {
+ var FCX, Tegaki;
+ Tegaki = window.Tegaki, FCX = window.FCX;
+ if (Tegaki.bg) {
+ Tegaki.destroy();
+ }
+ FCX.oekakiName = 'tegaki.png';
+ return Tegaki.open({
+ onDone: FCX.oekakiCB,
+ onCancel: function() {
+ return Tegaki.bgColor = '#ffffff';
+ },
+ width: +document.querySelector('#qr [name=oekaki-width]').value,
+ height: +document.querySelector('#qr [name=oekaki-height]').value,
+ bgColor: document.querySelector('#qr [name=oekaki-bg]').checked ? document.querySelector('#qr [name=oekaki-bgcolor]').value : 'transparent'
+ });
+ });
+ },
+ button: function() {
+ if (QR.selected.file) {
+ return QR.oekaki.edit();
+ } else {
+ return QR.oekaki.toggle();
+ }
+ },
+ edit: function() {
+ return QR.oekaki.load(function() {
+ return $.global(function() {
+ var FCX, Tegaki, cb, error, name, source;
+ Tegaki = window.Tegaki, FCX = window.FCX;
+ name = document.getElementById('qr-filename').value.replace(/\.\w+$/, '') + '.png';
+ source = document.getElementById('file-n-submit').dataset.source;
+ error = function(content) {
+ return document.dispatchEvent(new CustomEvent('CreateNotification', {
+ bubbles: true,
+ detail: {
+ type: 'warning',
+ content: content,
+ lifetime: 20
+ }
+ }));
+ };
+ cb = function(e) {
+ var canvas, selected;
+ if (e) {
+ this.removeEventListener('QRMetadata', cb, false);
+ }
+ selected = document.getElementById('selected');
+ if (!(selected != null ? selected.dataset.type : void 0)) {
+ return error('No file to edit.');
+ }
+ if (!/^(image|video)\//.test(selected.dataset.type)) {
+ return error('Not an image.');
+ }
+ if (!selected.dataset.height) {
+ return error('Metadata not available.');
+ }
+ if (selected.dataset.height === 'loading') {
+ selected.addEventListener('QRMetadata', cb, false);
+ return;
+ }
+ if (Tegaki.bg) {
+ Tegaki.destroy();
+ }
+ FCX.oekakiName = name;
+ Tegaki.open({
+ onDone: FCX.oekakiCB,
+ onCancel: function() {
+ return Tegaki.bgColor = '#ffffff';
+ },
+ width: +selected.dataset.width,
+ height: +selected.dataset.height,
+ bgColor: 'transparent'
+ });
+ canvas = document.createElement('canvas');
+ canvas.width = canvas.naturalWidth = +selected.dataset.width;
+ canvas.height = canvas.naturalHeight = +selected.dataset.height;
+ canvas.hidden = true;
+ document.body.appendChild(canvas);
+ canvas.addEventListener('QRImageDrawn', function() {
+ this.remove();
+ return Tegaki.onOpenImageLoaded.call(this);
+ }, false);
+ return canvas.dispatchEvent(new CustomEvent('QRDrawFile', {
+ bubbles: true
+ }));
+ };
+ if (Tegaki.bg && Tegaki.onDoneCb === FCX.oekakiCB && source === FCX.oekakiLatest) {
+ FCX.oekakiName = name;
+ return Tegaki.resume();
+ } else {
+ return cb();
+ }
+ });
+ });
+ },
+ toggle: function() {
+ return QR.oekaki.load(function() {
+ return QR.nodes.oekaki.hidden = !QR.nodes.oekaki.hidden;
+ });
+ }
+ };
+
+}).call(this);
+
+(function() {
+ var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ QR.persona = {
+ always: {},
+ types: {
+ name: [],
+ email: [],
+ sub: []
+ },
+ init: function() {
+ var i, item, len, ref;
+ if (!(Conf['Quick Reply'] || (Conf['Menu'] && Conf['Delete Link']))) {
+ return;
+ }
+ ref = Conf['QR.personas'].split('\n');
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ QR.persona.parseItem(item.trim());
+ }
+ },
+ parseItem: function(item) {
+ var boards, match, ref, ref1, ref2, type, val;
+ if (item[0] === '#') {
+ return;
+ }
+ if (!(match = item.match(/(name|options|email|subject|password):"(.*)"/i))) {
+ return;
+ }
+ ref = match, match = ref[0], type = ref[1], val = ref[2];
+ item = item.replace(match, '');
+ boards = ((ref1 = item.match(/boards:([^;]+)/i)) != null ? ref1[1].toLowerCase() : void 0) || 'global';
+ if (boards !== 'global' && (ref2 = g.BOARD.ID, indexOf.call(boards.split(','), ref2) < 0)) {
+ return;
+ }
+ if (type === 'password') {
+ QR.persona.pwd = val;
+ return;
+ }
+ if (type === 'options') {
+ type = 'email';
+ }
+ if (type === 'subject') {
+ type = 'sub';
+ }
+ if (/always/i.test(item)) {
+ QR.persona.always[type] = val;
+ }
+ if (indexOf.call(QR.persona.types[type], val) < 0) {
+ return QR.persona.types[type].push(val);
+ }
+ },
+ load: function() {
+ var arr, i, len, list, ref, type, val;
+ ref = QR.persona.types;
+ for (type in ref) {
+ arr = ref[type];
+ list = $("#list-" + type, QR.nodes.el);
+ for (i = 0, len = arr.length; i < len; i++) {
+ val = arr[i];
+ if (val) {
+ $.add(list, $.el('option', {
+ textContent: val
+ }));
+ }
+ }
+ }
+ },
+ getPassword: function() {
+ var m;
+ if (QR.persona.pwd != null) {
+ return QR.persona.pwd;
+ } else if ((m = d.cookie.match(/4chan_pass=([^;]+)/))) {
+ return decodeURIComponent(m[1]);
+ } else {
+ return '';
+ }
+ },
+ get: function(cb) {
+ return $.get('QR.persona', {}, function(arg) {
+ var persona;
+ persona = arg['QR.persona'];
+ return cb(persona);
+ });
+ },
+ set: function(post) {
+ return $.get('QR.persona', {}, function(arg) {
+ var persona;
+ persona = arg['QR.persona'];
+ persona = {
+ name: post.name,
+ flag: post.flag
+ };
+ return $.set('QR.persona', persona);
+ });
+ }
+ };
+
+}).call(this);
+
+(function() {
+ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+ slice = [].slice;
+
+ QR.post = (function() {
+ function _Class(select) {
+ this.select = bind(this.select, this);
+ var el, event, i, j, label, len, len1, prev, ref, ref1;
+ el = $.el('a', {
+ className: 'qr-preview',
+ draggable: true,
+ href: 'javascript:;'
+ });
+ $.extend(el, {innerHTML: "
Spoiler"});
+ this.nodes = {
+ el: el,
+ rm: el.firstChild,
+ spoiler: $('.qr-preview-spoiler input', el),
+ span: el.lastChild
+ };
+ $.on(el, 'click', this.select);
+ $.on(this.nodes.rm, 'click', (function(_this) {
+ return function(e) {
+ e.stopPropagation();
+ return _this.rm();
+ };
+ })(this));
+ $.on(this.nodes.spoiler, 'change', (function(_this) {
+ return function(e) {
+ _this.spoiler = e.target.checked;
+ if (_this === QR.selected) {
+ QR.nodes.spoiler.checked = _this.spoiler;
+ }
+ return _this.preventAutoPost();
+ };
+ })(this));
+ ref = $$('label', el);
+ for (i = 0, len = ref.length; i < len; i++) {
+ label = ref[i];
+ $.on(label, 'click', function(e) {
+ return e.stopPropagation();
+ });
+ }
+ $.add(QR.nodes.dumpList, el);
+ ref1 = ['dragStart', 'dragEnter', 'dragLeave', 'dragOver', 'dragEnd', 'drop'];
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ event = ref1[j];
+ $.on(el, event.toLowerCase(), this[event]);
+ }
+ this.thread = g.VIEW === 'thread' ? g.THREADID : 'new';
+ prev = QR.posts[QR.posts.length - 1];
+ QR.posts.push(this);
+ this.nodes.spoiler.checked = this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false;
+ QR.persona.get((function(_this) {
+ return function(persona) {
+ _this.name = 'name' in QR.persona.always ? QR.persona.always.name : prev ? prev.name : persona.name;
+ _this.email = 'email' in QR.persona.always ? QR.persona.always.email : '';
+ _this.sub = 'sub' in QR.persona.always ? QR.persona.always.sub : '';
+ if (QR.nodes.flag) {
+ _this.flag = prev ? prev.flag : persona.flag && persona.flag in g.BOARD.config.board_flags ? persona.flag : void 0;
+ }
+ if (QR.selected === _this) {
+ return _this.load();
+ }
+ };
+ })(this));
+ if (select) {
+ this.select();
+ }
+ this.unlock();
+ QR.captcha.moreNeeded();
+ }
+
+ _Class.prototype.rm = function() {
+ var base, index;
+ this["delete"]();
+ index = QR.posts.indexOf(this);
+ if (QR.posts.length === 1) {
+ new QR.post(true);
+ $.rmClass(QR.nodes.el, 'dump');
+ } else if (this === QR.selected) {
+ (QR.posts[index - 1] || QR.posts[index + 1]).select();
+ }
+ QR.posts.splice(index, 1);
+ QR.status();
+ return typeof (base = QR.captcha).updateThread === "function" ? base.updateThread() : void 0;
+ };
+
+ _Class.prototype["delete"] = function() {
+ $.rm(this.nodes.el);
+ URL.revokeObjectURL(this.URL);
+ return this.dismissErrors();
+ };
+
+ _Class.prototype.lock = function(lock) {
+ var i, len, name, node, ref;
+ if (lock == null) {
+ lock = true;
+ }
+ this.isLocked = lock;
+ if (this !== QR.selected) {
+ return;
+ }
+ ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag'];
+ for (i = 0, len = ref.length; i < len; i++) {
+ name = ref[i];
+ if (node = QR.nodes[name]) {
+ node.disabled = lock;
+ }
+ }
+ this.nodes.rm.style.visibility = lock ? 'hidden' : '';
+ this.nodes.spoiler.disabled = lock;
+ return this.nodes.el.draggable = !lock;
+ };
+
+ _Class.prototype.unlock = function() {
+ return this.lock(false);
+ };
+
+ _Class.prototype.select = function() {
+ var rectEl, rectList;
+ if (QR.selected) {
+ QR.selected.nodes.el.removeAttribute('id');
+ QR.selected.forceSave();
+ }
+ QR.selected = this;
+ this.lock(this.isLocked);
+ this.nodes.el.id = 'selected';
+ rectEl = this.nodes.el.getBoundingClientRect();
+ rectList = this.nodes.el.parentNode.getBoundingClientRect();
+ this.nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width / 2 - rectList.left - rectList.width / 2;
+ return this.load();
+ };
+
+ _Class.prototype.load = function() {
+ var i, len, name, node, ref;
+ ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag'];
+ for (i = 0, len = ref.length; i < len; i++) {
+ name = ref[i];
+ if (!(node = QR.nodes[name])) {
+ continue;
+ }
+ node.value = this[name] || node.dataset["default"] || '';
+ }
+ (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
+ this.showFileData();
+ return QR.characterCount();
+ };
+
+ _Class.prototype.save = function(input, forced) {
+ var base, name, prev;
+ if (input.type === 'checkbox') {
+ this.spoiler = input.checked;
+ return;
+ }
+ name = input.dataset.name;
+ if (name !== 'thread' && name !== 'name' && name !== 'email' && name !== 'sub' && name !== 'com' && name !== 'filename' && name !== 'flag') {
+ return;
+ }
+ prev = this[name] || input.dataset["default"] || null;
+ this[name] = input.value || input.dataset["default"] || null;
+ switch (name) {
+ case 'thread':
+ (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
+ QR.status();
+ if (typeof (base = QR.captcha).updateThread === "function") {
+ base.updateThread();
+ }
+ break;
+ case 'com':
+ this.updateComment();
+ break;
+ case 'filename':
+ if (!this.file) {
+ return;
+ }
+ this.saveFilename();
+ this.updateFilename();
+ break;
+ case 'name':
+ case 'flag':
+ if (this[name] !== prev) {
+ QR.persona.set(this);
+ }
+ }
+ if (!forced) {
+ return this.preventAutoPost();
+ }
+ };
+
+ _Class.prototype.forceSave = function() {
+ var i, len, name, node, ref;
+ if (this !== QR.selected) {
+ return;
+ }
+ ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag'];
+ for (i = 0, len = ref.length; i < len; i++) {
+ name = ref[i];
+ if (!(node = QR.nodes[name])) {
+ continue;
+ }
+ this.save(node, true);
+ }
+ };
+
+ _Class.prototype.preventAutoPost = function() {
+ if (QR.cooldown.auto && this === QR.posts[0]) {
+ QR.cooldown.update();
+ if (QR.cooldown.seconds <= 5) {
+ return QR.cooldown.auto = false;
+ }
+ }
+ };
+
+ _Class.prototype.setComment = function(com) {
+ this.com = com || null;
+ if (this === QR.selected) {
+ QR.nodes.com.value = this.com;
+ }
+ return this.updateComment();
+ };
+
+ _Class.prototype.updateComment = function() {
+ if (this === QR.selected) {
+ QR.characterCount();
+ }
+ this.nodes.span.textContent = this.com;
+ QR.captcha.moreNeeded();
+ if (QR.captcha === Captcha.v2) {
+ return Captcha.cache.prerequest();
+ }
+ };
+
+ _Class.prototype.isOnlyQuotes = function() {
+ return (this.com || '').trim() === (this.quotedText || '').trim();
+ };
+
+ _Class.rmErrored = function(e) {
+ var error, errors, i, j, len, post, ref;
+ e.stopPropagation();
+ ref = QR.posts;
+ for (i = ref.length - 1; i >= 0; i += -1) {
+ post = ref[i];
+ if (errors = post.errors) {
+ for (j = 0, len = errors.length; j < len; j++) {
+ error = errors[j];
+ if (!(doc.contains(error))) {
+ continue;
+ }
+ post.rm();
+ break;
+ }
+ }
+ }
+ };
+
+ _Class.prototype.error = function(className, message, link) {
+ var div, ref, rm, rmAll;
+ div = $.el('div', {
+ className: className
+ });
+ $.extend(div, {innerHTML: E(message) + ((link) ? " [
More info ]" : "") + "
[
delete post ] [
delete all ]"});
+ (this.errors || (this.errors = [])).push(div);
+ ref = $$('a', div), rm = ref[0], rmAll = ref[1];
+ $.on(div, 'click', (function(_this) {
+ return function() {
+ if (indexOf.call(QR.posts, _this) >= 0) {
+ return _this.select();
+ }
+ };
+ })(this));
+ $.on(rm, 'click', (function(_this) {
+ return function(e) {
+ e.stopPropagation();
+ if (indexOf.call(QR.posts, _this) >= 0) {
+ return _this.rm();
+ }
+ };
+ })(this));
+ $.on(rmAll, 'click', QR.post.rmErrored);
+ return QR.error(div, true);
+ };
+
+ _Class.prototype.fileError = function(message, link) {
+ return this.error('file-error', this.filename + ": " + message, link);
+ };
+
+ _Class.prototype.dismissErrors = function(test) {
+ var error, i, len, ref;
+ if (test == null) {
+ test = function() {
+ return true;
+ };
+ }
+ if (this.errors) {
+ ref = this.errors;
+ for (i = 0, len = ref.length; i < len; i++) {
+ error = ref[i];
+ if (doc.contains(error) && test(error)) {
+ error.parentNode.previousElementSibling.click();
+ }
+ }
+ }
+ };
+
+ _Class.prototype.setFile = function(file1) {
+ var ext, ref;
+ this.file = file1;
+ if (Conf['Randomize Filename'] && g.BOARD.ID !== 'f') {
+ this.filename = "" + (Date.now() - Math.floor(Math.random() * 365 * $.DAY));
+ if (ext = this.file.name.match(QR.validExtension)) {
+ this.filename += ext[0];
+ }
+ } else {
+ this.filename = this.file.name;
+ }
+ this.filesize = $.bytesToString(this.file.size);
+ this.checkSize();
+ $.addClass(this.nodes.el, 'has-file');
+ QR.captcha.moreNeeded();
+ URL.revokeObjectURL(this.URL);
+ this.saveFilename();
+ if (this === QR.selected) {
+ this.showFileData();
+ } else {
+ this.updateFilename();
+ }
+ this.rmMetadata();
+ this.nodes.el.dataset.type = this.file.type;
+ this.nodes.el.style.backgroundImage = '';
+ if (ref = this.file.type, indexOf.call(QR.mimeTypes, ref) < 0) {
+ this.fileError('Unsupported file type.');
+ } else if (/^(image|video)\//.test(this.file.type)) {
+ this.readFile();
+ }
+ return this.preventAutoPost();
+ };
+
+ _Class.prototype.checkSize = function() {
+ var max;
+ max = QR.max_size;
+ if (/^video\//.test(this.file.type)) {
+ max = Math.min(max, QR.max_size_video);
+ }
+ if (this.file.size > max) {
+ return this.fileError("File too large (file: " + this.filesize + ", max: " + ($.bytesToString(max)) + ").");
+ }
+ };
+
+ _Class.prototype.readFile = function() {
+ var el, event, isVideo, onerror, onload;
+ isVideo = /^video\//.test(this.file.type);
+ el = $.el(isVideo ? 'video' : 'img');
+ if (isVideo && !el.canPlayType(this.file.type)) {
+ return;
+ }
+ event = isVideo ? 'loadeddata' : 'load';
+ onload = (function(_this) {
+ return function() {
+ $.off(el, event, onload);
+ $.off(el, 'error', onerror);
+ _this.checkDimensions(el);
+ _this.setThumbnail(el);
+ return $.event('QRMetadata', null, _this.nodes.el);
+ };
+ })(this);
+ onerror = (function(_this) {
+ return function() {
+ $.off(el, event, onload);
+ $.off(el, 'error', onerror);
+ _this.fileError("Corrupt " + (isVideo ? 'video' : 'image') + " or error reading metadata.", 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions#error-reading-metadata');
+ URL.revokeObjectURL(el.src);
+ _this.nodes.el.removeAttribute('data-height');
+ return $.event('QRMetadata', null, _this.nodes.el);
+ };
+ })(this);
+ this.nodes.el.dataset.height = 'loading';
+ $.on(el, event, onload);
+ $.on(el, 'error', onerror);
+ return el.src = URL.createObjectURL(this.file);
+ };
+
+ _Class.prototype.checkDimensions = function(el) {
+ var duration, height, max_height, max_width, videoHeight, videoWidth, width;
+ if (el.tagName === 'IMG') {
+ height = el.height, width = el.width;
+ this.nodes.el.dataset.height = height;
+ this.nodes.el.dataset.width = width;
+ if (height > QR.max_height || width > QR.max_width) {
+ this.fileError("Image too large (image: " + height + "x" + width + "px, max: " + QR.max_height + "x" + QR.max_width + "px)");
+ }
+ if (height < QR.min_height || width < QR.min_width) {
+ return this.fileError("Image too small (image: " + height + "x" + width + "px, min: " + QR.min_height + "x" + QR.min_width + "px)");
+ }
+ } else {
+ videoHeight = el.videoHeight, videoWidth = el.videoWidth, duration = el.duration;
+ this.nodes.el.dataset.height = videoHeight;
+ this.nodes.el.dataset.width = videoWidth;
+ this.nodes.el.dataset.duration = duration;
+ max_height = Math.min(QR.max_height, QR.max_height_video);
+ max_width = Math.min(QR.max_width, QR.max_width_video);
+ if (videoHeight > max_height || videoWidth > max_width) {
+ this.fileError("Video too large (video: " + videoHeight + "x" + videoWidth + "px, max: " + max_height + "x" + max_width + "px)");
+ }
+ if (videoHeight < QR.min_height || videoWidth < QR.min_width) {
+ this.fileError("Video too small (video: " + videoHeight + "x" + videoWidth + "px, min: " + QR.min_height + "x" + QR.min_width + "px)");
+ }
+ if (!isFinite(duration)) {
+ this.fileError('Video lacks duration metadata (try remuxing)');
+ } else if (duration > QR.max_duration_video) {
+ this.fileError("Video too long (video: " + duration + "s, max: " + QR.max_duration_video + "s)");
+ }
+ if (BoardConfig.noAudio(g.BOARD.ID) && $.hasAudio(el)) {
+ return this.fileError('Audio not allowed');
+ }
+ }
+ };
+
+ _Class.prototype.setThumbnail = function(el) {
+ var cv, height, isVideo, s, width;
+ isVideo = el.tagName === 'VIDEO';
+ s = 90 * 2 * window.devicePixelRatio;
+ if (this.file.type === 'image/gif') {
+ s *= 3;
+ }
+ if (isVideo) {
+ height = el.videoHeight;
+ width = el.videoWidth;
+ } else {
+ height = el.height, width = el.width;
+ if (height < s || width < s) {
+ this.URL = el.src;
+ this.nodes.el.style.backgroundImage = "url(" + this.URL + ")";
+ return;
+ }
+ }
+ if (height <= width) {
+ width = s / height * width;
+ height = s;
+ } else {
+ height = s / width * height;
+ width = s;
+ }
+ cv = $.el('canvas');
+ cv.height = height;
+ cv.width = width;
+ cv.getContext('2d').drawImage(el, 0, 0, width, height);
+ URL.revokeObjectURL(el.src);
+ return cv.toBlob((function(_this) {
+ return function(blob) {
+ _this.URL = URL.createObjectURL(blob);
+ return _this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")";
+ };
+ })(this));
+ };
+
+ _Class.prototype.rmFile = function() {
+ if (this.isLocked) {
+ return;
+ }
+ delete this.file;
+ delete this.filename;
+ delete this.filesize;
+ this.nodes.el.removeAttribute('title');
+ QR.nodes.filename.removeAttribute('title');
+ this.rmMetadata();
+ this.nodes.el.style.backgroundImage = '';
+ $.rmClass(this.nodes.el, 'has-file');
+ this.showFileData();
+ URL.revokeObjectURL(this.URL);
+ this.dismissErrors(function(error) {
+ return $.hasClass(error, 'file-error');
+ });
+ return this.preventAutoPost();
+ };
+
+ _Class.prototype.rmMetadata = function() {
+ var attr, i, len, ref;
+ ref = ['type', 'height', 'width', 'duration'];
+ for (i = 0, len = ref.length; i < len; i++) {
+ attr = ref[i];
+ this.nodes.el.removeAttribute("data-" + attr);
+ }
+ };
+
+ _Class.prototype.saveFilename = function() {
+ this.file.newName = (this.filename || '').replace(/[\/\\]/g, '-');
+ if (!QR.validExtension.test(this.filename)) {
+ return this.file.newName += "." + ($.getOwn(QR.extensionFromType, this.file.type) || 'jpg');
+ }
+ };
+
+ _Class.prototype.updateFilename = function() {
+ var long;
+ long = this.filename + " (" + this.filesize + ")";
+ this.nodes.el.title = long;
+ if (this !== QR.selected) {
+ return;
+ }
+ return QR.nodes.filename.title = long;
+ };
+
+ _Class.prototype.showFileData = function() {
+ var ref;
+ if (this.file) {
+ this.updateFilename();
+ QR.nodes.filename.value = this.filename;
+ $.addClass(QR.nodes.oekaki, 'has-file');
+ $.addClass(QR.nodes.fileSubmit, 'has-file');
+ } else {
+ $.rmClass(QR.nodes.oekaki, 'has-file');
+ $.rmClass(QR.nodes.fileSubmit, 'has-file');
+ }
+ if (((ref = this.file) != null ? ref.source : void 0) != null) {
+ QR.nodes.fileSubmit.dataset.source = this.file.source;
+ } else {
+ QR.nodes.fileSubmit.removeAttribute('data-source');
+ }
+ return QR.nodes.spoiler.checked = this.spoiler;
+ };
+
+ _Class.prototype.pasteText = function(file) {
+ var reader;
+ this.pasting = true;
+ this.preventAutoPost();
+ reader = new FileReader();
+ reader.onload = (function(_this) {
+ return function(e) {
+ var result;
+ result = e.target.result;
+ _this.setComment((_this.com ? _this.com + "\n" + result : result));
+ return delete _this.pasting;
+ };
+ })(this);
+ return reader.readAsText(file);
+ };
+
+ _Class.prototype.dragStart = function(e) {
+ var left, ref, top;
+ ref = this.getBoundingClientRect(), left = ref.left, top = ref.top;
+ e.dataTransfer.setDragImage(this, e.clientX - left, e.clientY - top);
+ return $.addClass(this, 'drag');
+ };
+
+ _Class.prototype.dragEnd = function() {
+ return $.rmClass(this, 'drag');
+ };
+
+ _Class.prototype.dragEnter = function() {
+ return $.addClass(this, 'over');
+ };
+
+ _Class.prototype.dragLeave = function() {
+ return $.rmClass(this, 'over');
+ };
+
+ _Class.prototype.dragOver = function(e) {
+ e.preventDefault();
+ return e.dataTransfer.dropEffect = 'move';
+ };
+
+ _Class.prototype.drop = function() {
+ var base, el, index, newIndex, oldIndex, post;
+ $.rmClass(this, 'over');
+ if (!this.draggable) {
+ return;
+ }
+ el = $('.drag', this.parentNode);
+ index = function(el) {
+ return slice.call(el.parentNode.children).indexOf(el);
+ };
+ oldIndex = index(el);
+ newIndex = index(this);
+ if (QR.posts[oldIndex].isLocked || QR.posts[newIndex].isLocked) {
+ return;
+ }
+ (oldIndex < newIndex ? $.after : $.before)(this, el);
+ post = QR.posts.splice(oldIndex, 1)[0];
+ QR.posts.splice(newIndex, 0, post);
+ QR.status();
+ return typeof (base = QR.captcha).updateThread === "function" ? base.updateThread() : void 0;
+ };
+
+ return _Class;
+
+ })();
+
+}).call(this);
+
+QuoteBacklink = (function() {
+ var QuoteBacklink;
+
+ QuoteBacklink = {
+ containers: $.dict(),
+ init: function() {
+ var ref;
+ if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Quote Backlinks']) {
+ return;
+ }
+ if ((this.bottomBacklinks = Conf['Bottom Backlinks'])) {
+ $.addClass(doc, 'bottom-backlinks');
+ }
+ Callbacks.Post.push({
+ name: 'Quote Backlinking Part 1',
+ cb: this.firstNode
+ });
+ return Callbacks.Post.push({
+ name: 'Quote Backlinking Part 2',
+ cb: this.secondNode
+ });
+ },
+ firstNode: function() {
+ var a, clone, container, containers, hash, i, j, k, len, len1, len2, link, markYours, nodes, post, quote, ref, ref1;
+ if (this.isClone || !this.quotes.length || this.isRebuilt) {
+ return;
+ }
+ markYours = Conf['Mark Quotes of You'] && QuoteYou.isYou(this);
+ a = $.el('a', {
+ href: g.SITE.Build.postURL(this.board.ID, this.thread.ID, this.ID),
+ className: this.isHidden ? 'filtered backlink' : 'backlink',
+ textContent: Conf['backlink'].replace(/%(?:id|%)/g, (function(_this) {
+ return function(x) {
+ return {
+ '%id': _this.ID,
+ '%%': '%'
+ }[x];
+ };
+ })(this))
+ });
+ if (markYours) {
+ $.add(a, QuoteYou.mark.cloneNode(true));
+ }
+ ref = this.quotes;
+ for (i = 0, len = ref.length; i < len; i++) {
+ quote = ref[i];
+ containers = [QuoteBacklink.getContainer(quote)];
+ if ((post = g.posts.get(quote)) && post.nodes.backlinkContainer) {
+ ref1 = post.clones;
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ clone = ref1[j];
+ containers.push(clone.nodes.backlinkContainer);
+ }
+ }
+ for (k = 0, len2 = containers.length; k < len2; k++) {
+ container = containers[k];
+ link = a.cloneNode(true);
+ nodes = container.firstChild ? [$.tn(' '), link] : [link];
+ if (Conf['Quote Previewing']) {
+ $.on(link, 'mouseover', QuotePreview.mouseover);
+ }
+ if (Conf['Quote Inlining']) {
+ $.on(link, 'click', QuoteInline.toggle);
+ if (Conf['Quote Hash Navigation']) {
+ hash = QuoteInline.qiQuote(link, $.hasClass(link, 'filtered'));
+ nodes.push(hash);
+ }
+ }
+ $.add(container, nodes);
+ }
+ }
+ },
+ secondNode: function() {
+ var container;
+ if (this.isClone && (this.origin.isReply || Conf['OP Backlinks'])) {
+ this.nodes.backlinkContainer = $('.container', this.nodes.post);
+ return;
+ }
+ if (!(this.isReply || Conf['OP Backlinks'])) {
+ return;
+ }
+ container = QuoteBacklink.getContainer(this.fullID);
+ this.nodes.backlinkContainer = container;
+ if (QuoteBacklink.bottomBacklinks) {
+ return $.add(this.nodes.post, container);
+ } else {
+ return $.add(this.nodes.info, container);
+ }
+ },
+ getContainer: function(id) {
+ var base;
+ return (base = this.containers)[id] || (base[id] = $.el('span', {
+ className: 'container'
+ }));
+ }
+ };
+
+ return QuoteBacklink;
+
+}).call(this);
+
+QuoteCT = (function() {
+ var QuoteCT;
+
+ QuoteCT = {
+ init: function() {
+ var ref;
+ if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Mark Cross-thread Quotes']) {
+ return;
+ }
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ this.mark = $.el('span', {
+ textContent: '\u00A0(Cross-thread)',
+ className: 'qmark-ct'
+ });
+ return Callbacks.Post.push({
+ name: 'Mark Cross-thread Quotes',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var board, boardID, i, len, quotelink, ref, ref1, ref2, thread, threadID;
+ if (this.isClone && this.thread === this.context.thread) {
+ return;
+ }
+ ref = this.context, board = ref.board, thread = ref.thread;
+ ref1 = this.nodes.quotelinks;
+ for (i = 0, len = ref1.length; i < len; i++) {
+ quotelink = ref1[i];
+ ref2 = Get.postDataFromLink(quotelink), boardID = ref2.boardID, threadID = ref2.threadID;
+ if (!threadID) {
+ continue;
+ }
+ if (this.isClone) {
+ $.rm($('.qmark-ct', quotelink));
+ }
+ if (boardID === board.ID && threadID !== thread.ID) {
+ $.add(quotelink, QuoteCT.mark.cloneNode(true));
+ }
+ }
+ }
+ };
+
+ return QuoteCT;
+
+}).call(this);
+
+QuoteInline = (function() {
+ var QuoteInline,
+ slice = [].slice;
+
+ QuoteInline = {
+ init: function() {
+ var ref;
+ if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Quote Inlining']) {
+ return;
+ }
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ return Callbacks.Post.push({
+ name: 'Quote Inlining',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var i, isClone, len, link, process, ref;
+ process = QuoteInline.process;
+ isClone = this.isClone;
+ ref = this.nodes.quotelinks.concat(slice.call(this.nodes.backlinks), this.nodes.archivelinks);
+ for (i = 0, len = ref.length; i < len; i++) {
+ link = ref[i];
+ process(link, isClone);
+ }
+ },
+ process: function(link, clone) {
+ if (Conf['Quote Hash Navigation']) {
+ if (!clone) {
+ $.after(link, QuoteInline.qiQuote(link, $.hasClass(link, 'filtered')));
+ }
+ }
+ return $.on(link, 'click', QuoteInline.toggle);
+ },
+ qiQuote: function(link, hidden) {
+ var name;
+ name = "hashlink";
+ if (hidden) {
+ name += " filtered";
+ }
+ return $.el('a', {
+ className: name,
+ textContent: '#',
+ href: link.href
+ });
+ },
+ toggle: function(e) {
+ var boardID, context, postID, quoter, ref, ref1, threadID;
+ if ($.modifiedClick(e)) {
+ return;
+ }
+ ref = Get.postDataFromLink(this), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID;
+ if (Conf['Inline Cross-thread Quotes Only'] && g.VIEW === 'thread' && ((ref1 = g.posts.get(boardID + "." + postID)) != null ? ref1.nodes.root.offsetParent : void 0)) {
+ return;
+ }
+ if ($.hasClass(doc, 'catalog-mode')) {
+ return;
+ }
+ e.preventDefault();
+ quoter = Get.postFromNode(this);
+ context = quoter.context;
+ if ($.hasClass(this, 'inlined')) {
+ QuoteInline.rm(this, boardID, threadID, postID, context);
+ } else {
+ if ($.x("ancestor::div[@data-full-i-d='" + boardID + "." + postID + "']", this)) {
+ return;
+ }
+ QuoteInline.add(this, boardID, threadID, postID, context, quoter);
+ }
+ return this.classList.toggle('inlined');
+ },
+ findRoot: function(quotelink, isBacklink) {
+ if (isBacklink) {
+ return $.x('ancestor::*[parent::*[contains(@class,"post")]][1]', quotelink);
+ } else {
+ return $.x('ancestor-or-self::*[parent::blockquote][1]', quotelink);
+ }
+ },
+ add: function(quotelink, boardID, threadID, postID, context, quoter) {
+ var inline, isBacklink, post, qroot, root;
+ isBacklink = $.hasClass(quotelink, 'backlink');
+ inline = $.el('div', {
+ className: 'inline'
+ });
+ inline.dataset.fullID = boardID + "." + postID;
+ root = QuoteInline.findRoot(quotelink, isBacklink);
+ $.after(root, inline);
+ qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root);
+ $.addClass(qroot, 'hasInline');
+ new Fetcher(boardID, threadID, postID, inline, quoter);
+ if (!((post = g.posts.get(boardID + "." + postID)) && context.thread === post.thread)) {
+ return;
+ }
+ if (isBacklink && Conf['Forward Hiding']) {
+ $.addClass(post.nodes.root, 'forwarded');
+ post.forwarded++ || (post.forwarded = 1);
+ }
+ if (!Unread.posts) {
+ return;
+ }
+ return Unread.readSinglePost(post);
+ },
+ rm: function(quotelink, boardID, threadID, postID, context) {
+ var el, inlined, isBacklink, parentNode, post, qroot, ref, root;
+ isBacklink = $.hasClass(quotelink, 'backlink');
+ root = QuoteInline.findRoot(quotelink, isBacklink);
+ root = $.x("following-sibling::div[@data-full-i-d='" + boardID + "." + postID + "'][1]", root);
+ qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root);
+ parentNode = root.parentNode;
+ $.rm(root);
+ $.event('PostsRemoved', null, parentNode);
+ if (!$('.inline', qroot)) {
+ $.rmClass(qroot, 'hasInline');
+ }
+ if (!(el = root.firstElementChild)) {
+ return;
+ }
+ post = g.posts.get(boardID + "." + postID);
+ post.rmClone(el.dataset.clone);
+ if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads.get(boardID + "." + threadID) && !--post.forwarded) {
+ delete post.forwarded;
+ $.rmClass(post.nodes.root, 'forwarded');
+ }
+ while (inlined = $('.inlined', el)) {
+ ref = Get.postDataFromLink(inlined), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID;
+ QuoteInline.rm(inlined, boardID, threadID, postID, context);
+ $.rmClass(inlined, 'inlined');
+ }
+ }
+ };
+
+ return QuoteInline;
+
+}).call(this);
+
+QuoteOP = (function() {
+ var QuoteOP,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ QuoteOP = {
+ init: function() {
+ var ref;
+ if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Mark OP Quotes']) {
+ return;
+ }
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ this.mark = $.el('span', {
+ textContent: '\u00A0(OP)',
+ className: 'qmark-op'
+ });
+ return Callbacks.Post.push({
+ name: 'Mark OP Quotes',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var boardID, fullID, i, postID, quotelink, quotelinks, quotes, ref, ref1;
+ if (this.isClone && this.thread === this.context.thread) {
+ return;
+ }
+ if (!(quotes = this.quotes).length) {
+ return;
+ }
+ quotelinks = this.nodes.quotelinks;
+ if (this.isClone && (ref = this.thread.fullID, indexOf.call(quotes, ref) >= 0)) {
+ i = 0;
+ while (quotelink = quotelinks[i++]) {
+ $.rm($('.qmark-op', quotelink));
+ }
+ }
+ fullID = this.context.thread.fullID;
+ if (indexOf.call(quotes, fullID) < 0) {
+ return;
+ }
+ i = 0;
+ while (quotelink = quotelinks[i++]) {
+ ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID;
+ if ((boardID + "." + postID) === fullID) {
+ $.add(quotelink, QuoteOP.mark.cloneNode(true));
+ }
+ }
+ }
+ };
+
+ return QuoteOP;
+
+}).call(this);
+
+QuotePreview = (function() {
+ var QuotePreview,
+ slice = [].slice;
+
+ QuotePreview = {
+ init: function() {
+ var ref;
+ if (!Conf['Quote Previewing']) {
+ return;
+ }
+ if (g.VIEW === 'archive') {
+ $.on(d, 'mouseover', function(e) {
+ if (e.target.nodeName === 'A' && $.hasClass(e.target, 'quotelink')) {
+ return QuotePreview.mouseover.call(e.target, e);
+ }
+ });
+ }
+ if ((ref = g.VIEW) !== 'index' && ref !== 'thread') {
+ return;
+ }
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ return Callbacks.Post.push({
+ name: 'Quote Previewing',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var i, len, link, ref;
+ ref = this.nodes.quotelinks.concat(slice.call(this.nodes.backlinks), this.nodes.archivelinks);
+ for (i = 0, len = ref.length; i < len; i++) {
+ link = ref[i];
+ $.on(link, 'mouseover', QuotePreview.mouseover);
+ }
+ },
+ mouseover: function(e) {
+ var boardID, i, len, origin, post, postID, posts, qp, ref, threadID;
+ if (($.hasClass(this, 'inlined') && !$.hasClass(doc, 'catalog-mode')) || !d.contains(this)) {
+ return;
+ }
+ ref = Get.postDataFromLink(this), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID;
+ qp = $.el('div', {
+ id: 'qp',
+ className: 'dialog'
+ });
+ $.add(Header.hover, qp);
+ new Fetcher(boardID, threadID, postID, qp, Get.postFromNode(this));
+ UI.hover({
+ root: this,
+ el: qp,
+ latestEvent: e,
+ endEvents: 'mouseout click',
+ cb: QuotePreview.mouseout
+ });
+ if (Conf['Quote Highlighting'] && (origin = g.posts.get(boardID + "." + postID))) {
+ posts = [origin].concat(origin.clones);
+ posts.pop();
+ for (i = 0, len = posts.length; i < len; i++) {
+ post = posts[i];
+ $.addClass(post.nodes.post, 'qphl');
+ }
+ }
+ },
+ mouseout: function() {
+ var clone, i, len, post, ref, root;
+ if (!(root = this.el.firstElementChild)) {
+ return;
+ }
+ $.event('PostsRemoved', null, Header.hover);
+ clone = Get.postFromRoot(root);
+ post = clone.origin;
+ post.rmClone(root.dataset.clone);
+ if (!Conf['Quote Highlighting']) {
+ return;
+ }
+ ref = [post].concat(post.clones);
+ for (i = 0, len = ref.length; i < len; i++) {
+ post = ref[i];
+ $.rmClass(post.nodes.post, 'qphl');
+ }
+ }
+ };
+
+ return QuotePreview;
+
+}).call(this);
+
+QuoteStrikeThrough = (function() {
+ var QuoteStrikeThrough;
+
+ QuoteStrikeThrough = {
+ init: function() {
+ var ref;
+ if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && (Conf['Reply Hiding Buttons'] || (Conf['Menu'] && Conf['Reply Hiding Link']) || Conf['Filter']))) {
+ return;
+ }
+ return Callbacks.Post.push({
+ name: 'Strike-through Quotes',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var boardID, i, len, postID, quotelink, ref, ref1, ref2;
+ if (this.isClone) {
+ return;
+ }
+ ref = this.nodes.quotelinks;
+ for (i = 0, len = ref.length; i < len; i++) {
+ quotelink = ref[i];
+ ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID;
+ if ((ref2 = g.posts.get(boardID + "." + postID)) != null ? ref2.isHidden : void 0) {
+ $.addClass(quotelink, 'filtered');
+ }
+ }
+ }
+ };
+
+ return QuoteStrikeThrough;
+
+}).call(this);
+
+QuoteThreading =
+/*
+ <3 aeosynth
+ */
+
+(function() {
+ var QuoteThreading;
+
+ QuoteThreading = {
+ init: function() {
+ if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) {
+ return;
+ }
+ this.controls = $.el('label', {innerHTML: "
Threading"});
+ this.threadNewLink = $.el('span', {
+ className: 'brackets-wrap threadnewlink',
+ hidden: true
+ });
+ $.extend(this.threadNewLink, {innerHTML: "
Thread New Posts "});
+ this.input = $('input', this.controls);
+ this.input.checked = Conf['Thread Quotes'];
+ $.on(this.input, 'change', this.setEnabled);
+ $.on(this.input, 'change', this.rethread);
+ $.on(this.threadNewLink.firstElementChild, 'click', this.rethread);
+ $.on(d, '4chanXInitFinished', (function(_this) {
+ return function() {
+ return _this.ready = true;
+ };
+ })(this));
+ Header.menu.addEntry(this.entry = {
+ el: this.controls,
+ order: 99
+ });
+ Callbacks.Thread.push({
+ name: 'Quote Threading',
+ cb: this.setThread
+ });
+ return Callbacks.Post.push({
+ name: 'Quote Threading',
+ cb: this.node
+ });
+ },
+ parent: $.dict(),
+ children: $.dict(),
+ inserted: $.dict(),
+ toggleThreading: function() {
+ return this.setThreadingState(!Conf['Thread Quotes']);
+ },
+ setThreadingState: function(enabled) {
+ this.input.checked = enabled;
+ this.setEnabled.call(this.input);
+ return this.rethread.call(this.input);
+ },
+ setEnabled: function() {
+ var other, ref;
+ if (this.checked) {
+ $.set('Prune All Threads', false);
+ other = (ref = ReplyPruning.inputs) != null ? ref.enabled : void 0;
+ if (other != null ? other.checked : void 0) {
+ other.checked = false;
+ $.event('change', null, other);
+ }
+ }
+ return $.cb.checked.call(this);
+ },
+ setThread: function() {
+ QuoteThreading.thread = this;
+ return $.asap((function() {
+ return !Conf['Thread Updater'] || $('.navLinksBot > .updatelink');
+ }), function() {
+ var navLinksBot;
+ if ((navLinksBot = $('.navLinksBot'))) {
+ return $.add(navLinksBot, [$.tn(' '), QuoteThreading.threadNewLink]);
+ }
+ });
+ },
+ node: function() {
+ var ancestor, j, lastParent, len, parent, parents, quote, ref;
+ if (this.isFetchedQuote || this.isClone || !this.isReply) {
+ return;
+ }
+ parents = new Set();
+ lastParent = null;
+ ref = this.quotes;
+ for (j = 0, len = ref.length; j < len; j++) {
+ quote = ref[j];
+ if (parent = g.posts.get(quote)) {
+ if (!parent.isFetchedQuote && parent.isReply && parent.ID < this.ID) {
+ parents.add(parent.ID);
+ if (!lastParent || parent.ID > lastParent.ID) {
+ lastParent = parent;
+ }
+ }
+ }
+ }
+ if (!lastParent) {
+ return;
+ }
+ ancestor = lastParent;
+ while (ancestor = QuoteThreading.parent[ancestor.fullID]) {
+ parents["delete"](ancestor.ID);
+ }
+ if (parents.size === 1) {
+ return QuoteThreading.parent[this.fullID] = lastParent;
+ }
+ },
+ descendants: function(post) {
+ var child, children, j, len, posts;
+ posts = [post];
+ if (children = QuoteThreading.children[post.fullID]) {
+ for (j = 0, len = children.length; j < len; j++) {
+ child = children[j];
+ posts = posts.concat(QuoteThreading.descendants(child));
+ }
+ }
+ return posts;
+ },
+ insert: function(post) {
+ var base, child, children, descendants, i, j, k, l, len, name, next, nodes, order, parent, prev, prev2, threadContainer, x;
+ if (!(Conf['Thread Quotes'] && (parent = QuoteThreading.parent[post.fullID]) && !QuoteThreading.inserted[post.fullID])) {
+ return false;
+ }
+ descendants = QuoteThreading.descendants(post);
+ if (!Unread.posts.has(parent.ID)) {
+ if ((function() {
+ var j, len, x;
+ for (j = 0, len = descendants.length; j < len; j++) {
+ x = descendants[j];
+ if (Unread.posts.has(x.ID)) {
+ return true;
+ }
+ }
+ })()) {
+ QuoteThreading.threadNewLink.hidden = false;
+ return false;
+ }
+ }
+ order = Unread.order;
+ children = ((base = QuoteThreading.children)[name = parent.fullID] || (base[name] = []));
+ threadContainer = parent.nodes.threadContainer || $.el('div', {
+ className: 'threadContainer'
+ });
+ nodes = [post.nodes.root];
+ if (post.nodes.threadContainer) {
+ nodes.push(post.nodes.threadContainer);
+ }
+ i = children.length;
+ for (j = children.length - 1; j >= 0; j += -1) {
+ child = children[j];
+ if (child.ID >= post.ID) {
+ i--;
+ }
+ }
+ if (i !== children.length) {
+ next = children[i];
+ for (k = 0, len = descendants.length; k < len; k++) {
+ x = descendants[k];
+ order.before(order[next.ID], order[x.ID]);
+ }
+ children.splice(i, 0, post);
+ $.before(next.nodes.root, nodes);
+ } else {
+ prev = parent;
+ while ((prev2 = QuoteThreading.children[prev.fullID]) && prev2.length) {
+ prev = prev2[prev2.length - 1];
+ }
+ for (l = descendants.length - 1; l >= 0; l += -1) {
+ x = descendants[l];
+ order.after(order[prev.ID], order[x.ID]);
+ }
+ children.push(post);
+ $.add(threadContainer, nodes);
+ }
+ QuoteThreading.inserted[post.fullID] = true;
+ if (!parent.nodes.threadContainer) {
+ parent.nodes.threadContainer = threadContainer;
+ $.addClass(parent.nodes.root, 'threadOP');
+ $.after(parent.nodes.root, threadContainer);
+ }
+ return true;
+ },
+ rethread: function() {
+ var nodes, posts, thread;
+ if (!QuoteThreading.ready) {
+ return;
+ }
+ thread = QuoteThreading.thread;
+ posts = thread.posts;
+ QuoteThreading.threadNewLink.hidden = true;
+ if (Conf['Thread Quotes']) {
+ posts.forEach(QuoteThreading.insert);
+ } else {
+ nodes = [];
+ Unread.order = new RandomAccessList();
+ QuoteThreading.inserted = $.dict();
+ posts.forEach(function(post) {
+ if (post.isFetchedQuote) {
+ return;
+ }
+ Unread.order.push(post);
+ if (post.isReply) {
+ nodes.push(post.nodes.root);
+ }
+ if (QuoteThreading.children[post.fullID]) {
+ delete QuoteThreading.children[post.fullID];
+ $.rmClass(post.nodes.root, 'threadOP');
+ $.rm(post.nodes.threadContainer);
+ return delete post.nodes.threadContainer;
+ }
+ });
+ $.add(thread.nodes.root, nodes);
+ }
+ Unread.position = Unread.order.first;
+ Unread.updatePosition();
+ Unread.setLine(true);
+ Unread.read();
+ return Unread.update();
+ }
+ };
+
+ return QuoteThreading;
+
+}).call(this);
+
+QuoteYou = (function() {
+ var QuoteYou;
+
+ QuoteYou = {
+ init: function() {
+ var ref;
+ if (!Conf['Remember Your Posts']) {
+ return;
+ }
+ this.db = new DataBoard('yourPosts');
+ $.sync('Remember Your Posts', function(enabled) {
+ return Conf['Remember Your Posts'] = enabled;
+ });
+ $.on(d, 'QRPostSuccessful', function(e) {
+ var cb;
+ cb = PostRedirect.delay();
+ return $.get('Remember Your Posts', Conf['Remember Your Posts'], function(items) {
+ var boardID, postID, ref, threadID;
+ if (!items['Remember Your Posts']) {
+ return;
+ }
+ ref = e.detail, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID;
+ return QuoteYou.db.set({
+ boardID: boardID,
+ threadID: threadID,
+ postID: postID,
+ val: true
+ }, cb);
+ });
+ });
+ if ((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') {
+ return;
+ }
+ if (Conf['Highlight Own Posts']) {
+ $.addClass(doc, 'highlight-own');
+ }
+ if (Conf['Highlight Posts Quoting You']) {
+ $.addClass(doc, 'highlight-you');
+ }
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ this.mark = $.el('span', {
+ textContent: '\u00A0(You)',
+ className: 'qmark-you'
+ });
+ Callbacks.Post.push({
+ name: 'Mark Quotes of You',
+ cb: this.node
+ });
+ return QuoteYou.menu.init();
+ },
+ isYou: function(post) {
+ var ref;
+ return !!((ref = QuoteYou.db) != null ? ref.get({
+ boardID: post.boardID,
+ threadID: post.threadID,
+ postID: post.ID
+ }) : void 0);
+ },
+ node: function() {
+ var i, len, quotelink, ref;
+ if (this.isClone) {
+ return;
+ }
+ if (QuoteYou.isYou(this)) {
+ $.addClass(this.nodes.root, 'yourPost');
+ }
+ if (!this.quotes.length) {
+ return;
+ }
+ ref = this.nodes.quotelinks;
+ for (i = 0, len = ref.length; i < len; i++) {
+ quotelink = ref[i];
+ if (!(QuoteYou.db.get(Get.postDataFromLink(quotelink)))) {
+ continue;
+ }
+ if (Conf['Mark Quotes of You']) {
+ $.add(quotelink, QuoteYou.mark.cloneNode(true));
+ }
+ $.addClass(quotelink, 'you');
+ $.addClass(this.nodes.root, 'quotesYou');
+ }
+ },
+ menu: {
+ init: function() {
+ var input, label, ref;
+ label = $.el('label', {
+ className: 'toggle-you'
+ }, {innerHTML: "
You"});
+ input = $('input', label);
+ $.on(input, 'change', QuoteYou.menu.toggle);
+ return (ref = Menu.menu) != null ? ref.addEntry({
+ el: label,
+ order: 80,
+ open: function(post) {
+ QuoteYou.menu.post = post.origin || post;
+ input.checked = QuoteYou.isYou(post);
+ return true;
+ }
+ }) : void 0;
+ },
+ toggle: function() {
+ var clone, data, i, j, len, len1, post, quotelink, quoter, ref, ref1;
+ post = QuoteYou.menu.post;
+ data = {
+ boardID: post.board.ID,
+ threadID: post.thread.ID,
+ postID: post.ID,
+ val: true
+ };
+ if (this.checked) {
+ QuoteYou.db.set(data);
+ } else {
+ QuoteYou.db["delete"](data);
+ }
+ ref = [post].concat(post.clones);
+ for (i = 0, len = ref.length; i < len; i++) {
+ clone = ref[i];
+ clone.nodes.root.classList.toggle('yourPost', this.checked);
+ }
+ ref1 = Get.allQuotelinksLinkingTo(post);
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ quotelink = ref1[j];
+ if (this.checked) {
+ if (Conf['Mark Quotes of You']) {
+ $.add(quotelink, QuoteYou.mark.cloneNode(true));
+ }
+ } else {
+ $.rm($('.qmark-you', quotelink));
+ }
+ quotelink.classList.toggle('you', this.checked);
+ if ($.hasClass(quotelink, 'quotelink')) {
+ quoter = Get.postFromNode(quotelink).nodes.root;
+ quoter.classList.toggle('quotesYou', !!$('.quotelink.you', quoter));
+ }
+ }
+ }
+ },
+ cb: {
+ seek: function(type) {
+ var highlight, highlighted, post, posts, result, str;
+ highlight = g.SITE.classes.highlight;
+ if ((highlighted = $("." + highlight))) {
+ $.rmClass(highlighted, highlight);
+ }
+ if (!(QuoteYou.lastRead && doc.contains(QuoteYou.lastRead) && $.hasClass(QuoteYou.lastRead, 'quotesYou'))) {
+ if (!(post = QuoteYou.lastRead = $('.quotesYou'))) {
+ new Notice('warning', 'No posts are currently quoting you, loser.', 20);
+ return;
+ }
+ if (QuoteYou.cb.scroll(post)) {
+ return;
+ }
+ } else {
+ post = QuoteYou.lastRead;
+ }
+ str = type + "::div[contains(@class,'quotesYou')]";
+ while ((post = (result = $.X(str, post)).snapshotItem(type === 'preceding' ? result.snapshotLength - 1 : 0))) {
+ if (QuoteYou.cb.scroll(post)) {
+ return;
+ }
+ }
+ posts = $$('.quotesYou');
+ return QuoteYou.cb.scroll(posts[type === 'following' ? 0 : posts.length - 1]);
+ },
+ scroll: function(root) {
+ var node, post, sel;
+ post = Get.postFromRoot(root);
+ if (!post.nodes.post.getBoundingClientRect().height) {
+ return false;
+ } else {
+ QuoteYou.lastRead = root;
+ location.href = Get.url('post', post);
+ Header.scrollTo(post.nodes.post);
+ if (post.isReply) {
+ sel = "" + g.SITE.selectors.postContainer + g.SITE.selectors.highlightable.reply;
+ node = post.nodes.root;
+ if (!node.matches(sel)) {
+ node = $(sel, node);
+ }
+ $.addClass(node, g.SITE.classes.highlight);
+ }
+ return true;
+ }
+ }
+ }
+ };
+
+ return QuoteYou;
+
+}).call(this);
+
+Quotify = (function() {
+ var Quotify,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+ slice = [].slice;
+
+ Quotify = {
+ init: function() {
+ var ref;
+ if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Resurrect Quotes']) {
+ return;
+ }
+ $.addClass(doc, 'resurrect-quotes');
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ return Callbacks.Post.push({
+ name: 'Resurrect Quotes',
+ cb: this.node
+ });
+ },
+ node: function() {
+ var deadlink, i, j, len, len1, link, ref, ref1;
+ if (this.isClone) {
+ this.nodes.archivelinks = $$('a.linkify.quotelink', this.nodes.comment);
+ return;
+ }
+ ref = $$('a.linkify', this.nodes.comment);
+ for (i = 0, len = ref.length; i < len; i++) {
+ link = ref[i];
+ Quotify.parseArchivelink.call(this, link);
+ }
+ ref1 = $$('.deadlink', this.nodes.comment);
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ deadlink = ref1[j];
+ Quotify.parseDeadlink.call(this, deadlink);
+ }
+ },
+ parseArchivelink: function(link) {
+ var boardID, m, postID, ref, threadID;
+ if (!(m = link.pathname.match(/^\/([^\/]+)\/thread\/S?(\d+)\/?$/))) {
+ return;
+ }
+ if ((ref = link.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') {
+ return;
+ }
+ boardID = m[1];
+ threadID = m[2];
+ postID = link.hash.match(/^#[pq]?(\d+)$|$/)[1] || threadID;
+ if (Redirect.to('post', {
+ boardID: boardID,
+ postID: postID
+ })) {
+ $.addClass(link, 'quotelink');
+ $.extend(link.dataset, {
+ boardID: boardID,
+ threadID: threadID,
+ postID: postID
+ });
+ return this.nodes.archivelinks.push(link);
+ }
+ },
+ parseDeadlink: function(deadlink) {
+ var a, boardID, fetchable, m, post, postID, quote, quoteID, redirect, ref;
+ if ($.hasClass(deadlink.parentNode, 'prettyprint')) {
+ Quotify.fixDeadlink(deadlink);
+ return;
+ }
+ quote = deadlink.textContent;
+ if (!(postID = (ref = quote.match(/\d+$/)) != null ? ref[0] : void 0)) {
+ return;
+ }
+ if (postID[0] === '0') {
+ Quotify.fixDeadlink(deadlink);
+ return;
+ }
+ boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
+ quoteID = boardID + "." + postID;
+ if (post = g.posts.get(quoteID)) {
+ if (!post.isDead) {
+ a = $.el('a', {
+ href: g.SITE.Build.postURL(boardID, post.thread.ID, postID),
+ className: 'quotelink',
+ textContent: quote
+ });
+ } else {
+ a = $.el('a', {
+ href: g.SITE.Build.postURL(boardID, post.thread.ID, postID),
+ className: 'quotelink deadlink',
+ textContent: quote
+ });
+ $.add(a, Post.deadMark.cloneNode(true));
+ $.extend(a.dataset, {
+ boardID: boardID,
+ threadID: post.thread.ID,
+ postID: postID
+ });
+ }
+ } else {
+ redirect = Redirect.to('thread', {
+ boardID: boardID,
+ threadID: 0,
+ postID: postID
+ });
+ fetchable = Redirect.to('post', {
+ boardID: boardID,
+ postID: postID
+ });
+ if (redirect || fetchable) {
+ a = $.el('a', {
+ href: redirect || 'javascript:;',
+ className: 'deadlink',
+ textContent: quote
+ });
+ $.add(a, Post.deadMark.cloneNode(true));
+ if (fetchable) {
+ $.addClass(a, 'quotelink');
+ $.extend(a.dataset, {
+ boardID: boardID,
+ postID: postID
+ });
+ }
+ }
+ }
+ if (indexOf.call(this.quotes, quoteID) < 0) {
+ this.quotes.push(quoteID);
+ }
+ if (!a) {
+ $.add(deadlink, Post.deadMark.cloneNode(true));
+ return;
+ }
+ $.replace(deadlink, a);
+ if ($.hasClass(a, 'quotelink')) {
+ return this.nodes.quotelinks.push(a);
+ }
+ },
+ fixDeadlink: function(deadlink) {
+ var el, green;
+ if (!(el = deadlink.previousSibling) || el.nodeName === 'BR') {
+ green = $.el('span', {
+ className: 'quote'
+ });
+ $.before(deadlink, green);
+ $.add(green, deadlink);
+ }
+ return $.replace(deadlink, slice.call(deadlink.childNodes));
+ }
+ };
+
+ return Quotify;
+
+}).call(this);
+
+Main = (function() {
+ var Main,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+ Main = {
+ init: function() {
+ var db, flatten, i, items, j, k, key, len, mountedCB, ref, ref1, ref2, w;
+ try {
+ w = window;
+ if ($.platform === 'crx') {
+ w = w.wrappedJSObject || w;
+ }
+ if ('4chan X antidup' in w) {
+ return;
+ }
+ w['4chan X antidup'] = true;
+ } catch (error1) {}
+ try {
+ if (window.frameElement && ((ref = window.frameElement.src) === '' || ref === 'about:blank')) {
+ return;
+ }
+ } catch (error1) {}
+ if (doc && $.hasClass(doc, 'fourchan-x')) {
+ return;
+ }
+ $.asap(docSet, function() {
+ $.addClass(doc, 'fourchan-x', 'seaweedchan');
+ if ($.engine) {
+ return $.addClass(doc, "ua-" + $.engine);
+ }
+ });
+ $.on(d, '4chanXInitFinished', function() {
+ if (Main.expectInitFinished) {
+ return delete Main.expectInitFinished;
+ } else {
+ new Notice('error', 'Error: Multiple copies of 4chan X are enabled.');
+ return $.addClass(doc, 'tainted');
+ }
+ });
+ mountedCB = function() {
+ var cb, j, len, ref1, results;
+ d.removeEventListener('mounted', mountedCB, true);
+ Main.isMounted = true;
+ ref1 = Main.mountedCBs;
+ results = [];
+ for (j = 0, len = ref1.length; j < len; j++) {
+ cb = ref1[j];
+ try {
+ results.push(cb());
+ } catch (error1) {}
+ }
+ return results;
+ };
+ d.addEventListener('mounted', mountedCB, true);
+ flatten = function(parent, obj) {
+ var key, val;
+ if (obj instanceof Array) {
+ Conf[parent] = $.dict.clone(obj[0]);
+ } else if (typeof obj === 'object') {
+ for (key in obj) {
+ val = obj[key];
+ flatten(key, val);
+ }
+ } else {
+ Conf[parent] = obj;
+ }
+ };
+ if ((ref1 = location.hostname) === 'boards.4chan.org' || ref1 === 'boards.4channel.org') {
+ $.global(function() {
+ var fromCharCode0;
+ fromCharCode0 = String.fromCharCode;
+ return String.fromCharCode = function() {
+ if (document.body) {
+ String.fromCharCode = fromCharCode0;
+ } else if (document.currentScript && !document.currentScript.src) {
+ throw Error();
+ }
+ return fromCharCode0.apply(this, arguments);
+ };
+ });
+ $.asap(docSet, function() {
+ return $.onExists(doc, 'iframe[srcdoc]', $.rm);
+ });
+ }
+ flatten(null, Config);
+ ref2 = DataBoard.keys;
+ for (j = 0, len = ref2.length; j < len; j++) {
+ db = ref2[j];
+ Conf[db] = $.dict();
+ }
+ Conf['customTitles'] = $.dict.clone({
+ '4chan.org': {
+ boards: {
+ 'qa': {
+ 'boardTitle': {
+ orig: '/qa/ - Question & Answer',
+ title: '/qa/ - 2D/Random'
+ }
+ }
+ }
+ }
+ });
+ Conf['boardConfig'] = {
+ boards: $.dict()
+ };
+ Conf['archives'] = Redirect.archives;
+ Conf['selectedArchives'] = $.dict();
+ Conf['cooldowns'] = $.dict();
+ Conf['Index Sort'] = $.dict();
+ for (i = k = 0; k < 2; i = ++k) {
+ Conf["Last Long Reply Thresholds " + i] = $.dict();
+ }
+ Conf['siteProperties'] = $.dict();
+ Conf['Except Archives from Encryption'] = false;
+ Conf['JSON Navigation'] = true;
+ Conf['Oekaki Links'] = true;
+ Conf['Show Name and Subject'] = false;
+ Conf['QR Shortcut'] = true;
+ Conf['Bottom QR Link'] = true;
+ Conf['Toggleable Thread Watcher'] = true;
+ Conf['siteSoftware'] = '';
+ Conf['Use Faster Image Host'] = 'true';
+ Conf['Captcha Fixes'] = true;
+ Conf['captchaServiceDomain'] = '';
+ Conf['captchaServiceKey'] = $.dict();
+ if (/\.4chan(?:nel)?\.org$/.test(location.hostname) && !SW.yotsuba.regexp.pass.test(location.href) && !$$('script:not([src])', d).filter(function(s) {
+ return /this\[/.test(s.textContent);
+ }).length) {
+ ($.getSync || $.get)({
+ 'jsWhitelist': Conf['jsWhitelist']
+ }, function(arg) {
+ var jsWhitelist;
+ jsWhitelist = arg.jsWhitelist;
+ return $.addCSP("script-src " + (jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim()));
+ });
+ }
+ items = $.dict();
+ for (key in Conf) {
+ items[key] = void 0;
+ }
+ items['previousversion'] = void 0;
+ return ($.getSync || $.get)(items, function(items) {
+ var ref3;
+ if (!$.perProtocolSettings && /\.4chan(?:nel)?\.org$/.test(location.hostname) && ((ref3 = items['Redirect to HTTPS']) != null ? ref3 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') {
+ location.replace('https://' + location.host + location.pathname + location.search + location.hash);
+ return;
+ }
+ return $.asap(docSet, function() {
+ var ref4, val;
+ if ($.cantSet) {
+
+ } else if (items.previousversion == null) {
+ Main.isFirstRun = true;
+ Main.ready(function() {
+ $.set('previousversion', g.VERSION);
+ return Settings.open();
+ });
+ } else if (items.previousversion !== g.VERSION) {
+ Main.upgrade(items);
+ }
+ for (key in Conf) {
+ val = Conf[key];
+ Conf[key] = (ref4 = items[key]) != null ? ref4 : val;
+ }
+ return Site.init(Main.initFeatures);
+ });
+ });
+ },
+ upgrade: function(items) {
+ var changes, previousversion;
+ previousversion = items.previousversion;
+ changes = Settings.upgrade(items, previousversion);
+ items.previousversion = changes.previousversion = g.VERSION;
+ return $.set(changes, function() {
+ var el, ref;
+ if ((ref = items['Show Updated Notifications']) != null ? ref : true) {
+ el = $.el('span', {innerHTML: "4chan X has been updated to
version " + E(g.VERSION) + " ."});
+ return new Notice('info', el, 15);
+ }
+ });
+ },
+ parseURL: function(site, url) {
+ var pathname, r, ref;
+ if (site == null) {
+ site = g.SITE;
+ }
+ if (url == null) {
+ url = location;
+ }
+ r = {};
+ if (!site) {
+ return r;
+ }
+ r.siteID = site.ID;
+ if (typeof site.isBoardlessPage === "function" ? site.isBoardlessPage(url) : void 0) {
+ return r;
+ }
+ pathname = url.pathname.split(/\/+/);
+ r.boardID = pathname[1];
+ if (site.isFileURL(url)) {
+ r.VIEW = 'file';
+ } else if (typeof site.isAuxiliaryPage === "function" ? site.isAuxiliaryPage(url) : void 0) {
+
+ } else if ((ref = pathname[2]) === 'thread' || ref === 'res') {
+ r.VIEW = 'thread';
+ r.threadID = r.THREADID = +pathname[3].replace(/\.\w+$/, '');
+ } else if (pathname[2] === 'archive' && pathname[3] === 'res') {
+ r.VIEW = 'thread';
+ r.threadID = r.THREADID = +pathname[4].replace(/\.\w+$/, '');
+ r.threadArchived = true;
+ } else if (/^(?:catalog|archive)(?:\.\w+)?$/.test(pathname[2])) {
+ r.VIEW = pathname[2].replace(/\.\w+$/, '');
+ } else if (/^(?:index|\d*)(?:\.\w+)?$/.test(pathname[2])) {
+ r.VIEW = 'index';
+ }
+ return r;
+ },
+ initFeatures: function() {
+ var base, err, feature, j, len, name, ref, ref1;
+ $.global(function() {
+ document.documentElement.classList.add('js-enabled');
+ return window.FCX = {};
+ });
+ Main.jsEnabled = $.hasClass(doc, 'js-enabled');
+ if (typeof $.ajaxPageInit === "function") {
+ $.ajaxPageInit();
+ }
+ $.extend(g, Main.parseURL());
+ if (g.boardID) {
+ g.BOARD = new Board(g.boardID);
+ }
+ if (!g.VIEW) {
+ if (typeof (base = g.SITE).initAuxiliary === "function") {
+ base.initAuxiliary();
+ }
+ return;
+ }
+ if (g.VIEW === 'file') {
+ $.asap((function() {
+ return d.readyState !== 'loading';
+ }), function() {
+ var base1, pathname, video;
+ if (g.SITE.software === 'yotsuba' && Conf['404 Redirect'] && (typeof (base1 = g.SITE).is404 === "function" ? base1.is404() : void 0)) {
+ pathname = location.pathname.split(/\/+/);
+ return Redirect.navigate('file', {
+ boardID: g.BOARD.ID,
+ filename: pathname[pathname.length - 1]
+ });
+ } else if (video = $('video')) {
+ if (Conf['Volume in New Tab']) {
+ Volume.setup(video);
+ }
+ if (Conf['Loop in New Tab']) {
+ video.loop = true;
+ video.controls = false;
+ video.play();
+ return ImageCommon.addControls(video);
+ }
+ }
+ });
+ return;
+ }
+ g.threads = new SimpleDict();
+ g.posts = new SimpleDict();
+ $.onExists(doc, 'body', Main.initStyle);
+ ref = Main.features;
+ for (j = 0, len = ref.length; j < len; j++) {
+ ref1 = ref[j], name = ref1[0], feature = ref1[1];
+ if (g.SITE.disabledFeatures && indexOf.call(g.SITE.disabledFeatures, name) >= 0) {
+ continue;
+ }
+ try {
+ feature.init();
+ } catch (error1) {
+ err = error1;
+ Main.handleErrors({
+ message: "\"" + name + "\" initialization crashed.",
+ error: err
+ });
+ }
+ }
+ return $.ready(Main.initReady);
+ },
+ initStyle: function() {
+ var keyboard, ref;
+ if (!Main.isThisPageLegit()) {
+ return;
+ }
+ if ((ref = $('link[href*=mobile]', d.head)) != null) {
+ ref.disabled = true;
+ }
+ doc.dataset.host = location.host;
+ $.addClass(doc, "sw-" + g.SITE.software);
+ $.addClass(doc, g.VIEW === 'thread' ? 'thread-view' : g.VIEW);
+ $.onExists(doc, '.ad-cnt, .adg-rects > .desktop', function(ad) {
+ return $.onExists(ad, 'img, iframe', function() {
+ return $.addClass(doc, 'ads-loaded');
+ });
+ });
+ if (Conf['Autohiding Scrollbar']) {
+ $.addClass(doc, 'autohiding-scrollbar');
+ }
+ $.ready(function() {
+ if (d.body.clientHeight > doc.clientHeight && (window.innerWidth === doc.clientWidth) !== Conf['Autohiding Scrollbar']) {
+ Conf['Autohiding Scrollbar'] = !Conf['Autohiding Scrollbar'];
+ $.set('Autohiding Scrollbar', Conf['Autohiding Scrollbar']);
+ return $.toggleClass(doc, 'autohiding-scrollbar');
+ }
+ });
+ $.addStyle(CSS.sub(CSS.boards), 'fourchanx-css');
+ Main.bgColorStyle = $.el('style', {
+ id: 'fourchanx-bgcolor-css'
+ });
+ keyboard = false;
+ $.on(d, 'mousedown', function() {
+ return keyboard = false;
+ });
+ $.on(d, 'keydown', function(e) {
+ if (e.keyCode === 9) {
+ return keyboard = true;
+ }
+ });
+ window.addEventListener('focus', (function() {
+ return doc.classList.toggle('keyboard-focus', keyboard);
+ }), true);
+ return Main.setClass();
+ },
+ setClass: function() {
+ var j, knownStyles, len, mainStyleSheet, ref, ref1, setStyle, style, styleSheet, styleSheets;
+ knownStyles = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'photon', 'tomorrow', 'spooky'];
+ if (g.SITE.software === 'yotsuba' && g.VIEW === 'catalog') {
+ if ((mainStyleSheet = $.id('base-css'))) {
+ style = (ref = mainStyleSheet.href.match(/catalog_(\w+)/)) != null ? ref[1].replace('_new', '').replace(/_+/g, '-') : void 0;
+ if (indexOf.call(knownStyles, style) >= 0) {
+ $.addClass(doc, style);
+ return;
+ }
+ }
+ }
+ style = mainStyleSheet = styleSheets = null;
+ setStyle = function() {
+ var bgColor, css, div, j, len, rgb, s, styleSheet;
+ if (g.SITE.software === 'yotsuba') {
+ $.rmClass(doc, style);
+ style = null;
+ for (j = 0, len = styleSheets.length; j < len; j++) {
+ styleSheet = styleSheets[j];
+ if (styleSheet.href === (mainStyleSheet != null ? mainStyleSheet.href : void 0)) {
+ style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-');
+ if (style === '_special') {
+ style = styleSheet.href.match(/[a-z]*(?=[^\/]*$)/)[0];
+ }
+ if (indexOf.call(knownStyles, style) < 0) {
+ style = null;
+ }
+ break;
+ }
+ }
+ if (style) {
+ $.addClass(doc, style);
+ $.rm(Main.bgColorStyle);
+ return;
+ }
+ }
+ div = g.SITE.bgColoredEl();
+ div.style.position = 'absolute';
+ div.style.visibility = 'hidden';
+ $.add(d.body, div);
+ bgColor = window.getComputedStyle(div).backgroundColor;
+ $.rm(div);
+ rgb = bgColor.match(/[\d.]+/g);
+ if (!/^rgb\(/.test(bgColor)) {
+ s = window.getComputedStyle(d.body);
+ bgColor = s.backgroundColor + " " + s.backgroundImage + " " + s.backgroundRepeat + " " + s.backgroundPosition;
+ }
+ css = ".dialog, .suboption-list > div:last-of-type, :root.catalog-hover-expand .catalog-container:hover > .post {\n background: " + bgColor + ";\n}\n.unread-mark-read {\n background-color: rgba(" + (rgb.slice(0, 3).join(', ')) + ", " + (0.5 * (rgb[3] || 1)) + ");\n}";
+ if ($.luma(rgb) < 100) {
+ css += ".watch-thread-link {\n background-image: url(\"data:image/svg+xml,
\");\n}";
+ }
+ Main.bgColorStyle.textContent = css;
+ return $.after($.id('fourchanx-css'), Main.bgColorStyle);
+ };
+ $.onExists(d.head, g.SITE.selectors.styleSheet, function(el) {
+ mainStyleSheet = el;
+ if (g.SITE.software === 'yotsuba') {
+ styleSheets = $$('link[rel="alternate stylesheet"]', d.head);
+ }
+ new MutationObserver(setStyle).observe(mainStyleSheet, {
+ attributes: true,
+ attributeFilter: ['href']
+ });
+ $.on(mainStyleSheet, 'load', setStyle);
+ return setStyle();
+ });
+ if (!mainStyleSheet) {
+ ref1 = $$('link[rel="stylesheet"]', d.head);
+ for (j = 0, len = ref1.length; j < len; j++) {
+ styleSheet = ref1[j];
+ $.on(styleSheet, 'load', setStyle);
+ }
+ return setStyle();
+ }
+ },
+ initReady: function() {
+ var base, base1, msg;
+ if (typeof (base = g.SITE).is404 === "function" ? base.is404() : void 0) {
+ if (g.VIEW === 'thread') {
+ ThreadWatcher.set404(g.BOARD.ID, g.THREADID, function() {
+ if (Conf['404 Redirect']) {
+ return Redirect.navigate('thread', {
+ boardID: g.BOARD.ID,
+ threadID: g.THREADID,
+ postID: +location.hash.match(/\d+/)
+ }, "/" + g.BOARD + "/");
+ }
+ });
+ }
+ return;
+ }
+ if (typeof (base1 = g.SITE).isIncomplete === "function" ? base1.isIncomplete() : void 0) {
+ msg = $.el('div', {innerHTML: "The page didn't load completely.
Some features may not work unless you
reload ."});
+ $.on($('a', msg), 'click', function() {
+ return location.reload();
+ });
+ new Notice('warning', msg);
+ }
+ if (g.VIEW === 'catalog') {
+ return Main.initCatalog();
+ } else if (!Index.enabled) {
+ if (g.SITE.awaitBoard) {
+ return g.SITE.awaitBoard(Main.initThread);
+ } else {
+ return Main.initThread();
+ }
+ } else {
+ Main.expectInitFinished = true;
+ return $.event('4chanXInitFinished');
+ }
+ },
+ initThread: function() {
+ var base, base1, board, errors, posts, ref, s, threads;
+ s = g.SITE.selectors;
+ if ((board = $(((ref = s.boardFor) != null ? ref[g.VIEW] : void 0) || s.board))) {
+ threads = [];
+ posts = [];
+ errors = [];
+ try {
+ if (typeof (base = g.SITE).preParsingFixes === "function") {
+ base.preParsingFixes(board);
+ }
+ } catch (error1) {}
+ Main.addThreadsObserver = new MutationObserver(Main.addThreads);
+ Main.addPostsObserver = new MutationObserver(Main.addPosts);
+ Main.addThreadsObserver.observe(board, {
+ childList: true
+ });
+ Main.parseThreads($$(s.thread, board), threads, posts, errors);
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ if (g.VIEW === 'thread') {
+ if (g.threadArchived) {
+ threads[0].isArchived = true;
+ threads[0].kill();
+ }
+ if (typeof (base1 = g.SITE).parseThreadMetadata === "function") {
+ base1.parseThreadMetadata(threads[0]);
+ }
+ }
+ Main.callbackNodes('Thread', threads);
+ return Main.callbackNodesDB('Post', posts, function() {
+ var j, len, post;
+ for (j = 0, len = posts.length; j < len; j++) {
+ post = posts[j];
+ QuoteThreading.insert(post);
+ }
+ Main.expectInitFinished = true;
+ return $.event('4chanXInitFinished');
+ });
+ } else {
+ Main.expectInitFinished = true;
+ return $.event('4chanXInitFinished');
+ }
+ },
+ parseThreads: function(threadRoots, threads, posts, errors) {
+ var boardID, boardObj, j, len, postRoots, ref, thread, threadID, threadRoot;
+ for (j = 0, len = threadRoots.length; j < len; j++) {
+ threadRoot = threadRoots[j];
+ boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD;
+ threadID = +threadRoot.id.match(/\d*$/)[0];
+ if (!threadID || ((ref = boardObj.threads.get(threadID)) != null ? ref.nodes.root : void 0)) {
+ return;
+ }
+ thread = new Thread(threadID, boardObj);
+ thread.nodes.root = threadRoot;
+ threads.push(thread);
+ postRoots = $$(g.SITE.selectors.postContainer, threadRoot);
+ if (g.SITE.isOPContainerThread) {
+ postRoots.unshift(threadRoot);
+ }
+ Main.parsePosts(postRoots, thread, posts, errors);
+ Main.addPostsObserver.observe(threadRoot, {
+ childList: true
+ });
+ }
+ },
+ parsePosts: function(postRoots, thread, posts, errors) {
+ var err, j, len, postRoot;
+ for (j = 0, len = postRoots.length; j < len; j++) {
+ postRoot = postRoots[j];
+ if (!(postRoot.dataset.fullID && g.posts.get(postRoot.dataset.fullID)) && $(g.SITE.selectors.comment, postRoot)) {
+ try {
+ posts.push(new Post(postRoot, thread, thread.board));
+ } catch (error1) {
+ err = error1;
+ errors.push({
+ message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.",
+ error: err,
+ html: postRoot.outerHTML
+ });
+ }
+ }
+ }
+ },
+ addThreads: function(records) {
+ var errors, j, k, len, len1, node, posts, record, ref, threadRoots, threads;
+ threadRoots = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ ref = record.addedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ node = ref[k];
+ if (node.nodeType === Node.ELEMENT_NODE && node.matches(g.SITE.selectors.thread)) {
+ threadRoots.push(node);
+ }
+ }
+ }
+ if (!threadRoots.length) {
+ return;
+ }
+ threads = [];
+ posts = [];
+ errors = [];
+ Main.parseThreads(threadRoots, threads, posts, errors);
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ Main.callbackNodes('Thread', threads);
+ return Main.callbackNodesDB('Post', posts, function() {
+ return $.event('PostsInserted', null, records[0].target);
+ });
+ },
+ addPosts: function(records) {
+ var anyRemoved, el, errors, j, k, l, len, len1, len2, n, node, postRoots, posts, record, ref, ref1, ref2, thread, threads, threadsRM;
+ threads = [];
+ threadsRM = [];
+ posts = [];
+ errors = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ thread = Get.threadFromRoot(record.target);
+ postRoots = [];
+ ref = record.addedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ node = ref[k];
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ if (node.matches(g.SITE.selectors.postContainer) || (node = $(g.SITE.selectors.postContainer, node))) {
+ postRoots.push(node);
+ }
+ }
+ }
+ n = posts.length;
+ Main.parsePosts(postRoots, thread, posts, errors);
+ if (posts.length > n && indexOf.call(threads, thread) < 0) {
+ threads.push(thread);
+ }
+ anyRemoved = false;
+ ref1 = record.removedNodes;
+ for (l = 0, len2 = ref1.length; l < len2; l++) {
+ el = ref1[l];
+ if (((ref2 = Get.postFromRoot(el)) != null ? ref2.nodes.root : void 0) === el && !doc.contains(el)) {
+ anyRemoved = true;
+ break;
+ }
+ }
+ if (anyRemoved && indexOf.call(threadsRM, thread) < 0) {
+ threadsRM.push(thread);
+ }
+ }
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ return Main.callbackNodesDB('Post', posts, function() {
+ var len3, len4, m, o;
+ for (m = 0, len3 = threads.length; m < len3; m++) {
+ thread = threads[m];
+ $.event('PostsInserted', null, thread.nodes.root);
+ }
+ for (o = 0, len4 = threadsRM.length; o < len4; o++) {
+ thread = threadsRM[o];
+ $.event('PostsRemoved', null, thread.nodes.root);
+ }
+ });
+ },
+ initCatalog: function() {
+ var board, errors, s, threads;
+ s = g.SITE.selectors.catalog;
+ if (s && (board = $(s.board))) {
+ threads = [];
+ errors = [];
+ Main.addCatalogThreadsObserver = new MutationObserver(Main.addCatalogThreads);
+ Main.addCatalogThreadsObserver.observe(board, {
+ childList: true
+ });
+ Main.parseCatalogThreads($$(s.thread, board), threads, errors);
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ Main.callbackNodes('CatalogThreadNative', threads);
+ }
+ Main.expectInitFinished = true;
+ return $.event('4chanXInitFinished');
+ },
+ parseCatalogThreads: function(threadRoots, threads, errors) {
+ var err, j, len, ref, thread, threadRoot;
+ for (j = 0, len = threadRoots.length; j < len; j++) {
+ threadRoot = threadRoots[j];
+ try {
+ thread = new CatalogThreadNative(threadRoot);
+ if (((ref = thread.thread.catalogViewNative) != null ? ref.nodes.root : void 0) !== threadRoot) {
+ thread.thread.catalogViewNative = thread;
+ threads.push(thread);
+ }
+ } catch (error1) {
+ err = error1;
+ errors.push({
+ message: "Parsing of Catalog Thread No." + ((threadRoot.dataset.id || threadRoot.id).match(/\d+/)) + " failed. Thread will be skipped.",
+ error: err,
+ html: threadRoot.outerHTML
+ });
+ }
+ }
+ },
+ addCatalogThreads: function(records) {
+ var errors, j, k, len, len1, node, record, ref, threadRoots, threads;
+ threadRoots = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ ref = record.addedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ node = ref[k];
+ if (node.nodeType === Node.ELEMENT_NODE && node.matches(g.SITE.selectors.catalog.thread)) {
+ threadRoots.push(node);
+ }
+ }
+ }
+ if (!threadRoots.length) {
+ return;
+ }
+ threads = [];
+ errors = [];
+ Main.parseCatalogThreads(threadRoots, threads, errors);
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ return Main.callbackNodes('CatalogThreadNative', threads);
+ },
+ callbackNodes: function(klass, nodes) {
+ var cb, i, node;
+ i = 0;
+ cb = Callbacks[klass];
+ while (node = nodes[i++]) {
+ cb.execute(node);
+ }
+ },
+ callbackNodesDB: function(klass, nodes, cb) {
+ var cbs, fn, i, softTask;
+ i = 0;
+ cbs = Callbacks[klass];
+ fn = function() {
+ var node;
+ if (!(node = nodes[i])) {
+ return false;
+ }
+ cbs.execute(node);
+ return ++i % 25;
+ };
+ softTask = function() {
+ while (fn()) {
+ continue;
+ }
+ if (!nodes[i]) {
+ if (cb) {
+ cb();
+ }
+ return;
+ }
+ return setTimeout(softTask, 0);
+ };
+ return softTask();
+ },
+ handleErrors: function(errors) {
+ var div, enabled, error, j, len, logs, msg;
+ if (d.body && $.hasClass(d.body, 'fourchan_x') && !$.hasClass(doc, 'tainted')) {
+ new Notice('error', 'Error: Multiple copies of 4chan X are enabled.');
+ $.addClass(doc, 'tainted');
+ }
+ if (g.SITE.testNativeExtension && !$.hasClass(doc, 'tainted')) {
+ enabled = g.SITE.testNativeExtension().enabled;
+ if (enabled) {
+ $.addClass(doc, 'tainted');
+ if (Conf['Disable Native Extension'] && !Main.isFirstRun) {
+ msg = $.el('div', {innerHTML: "Failed to disable the native extension. You may need to
block it ."});
+ new Notice('error', msg);
+ }
+ }
+ }
+ if (!(errors instanceof Array)) {
+ error = errors;
+ } else if (errors.length === 1) {
+ error = errors[0];
+ }
+ if (error) {
+ new Notice('error', Main.parseError(error, Main.reportLink([error])), 15);
+ return;
+ }
+ div = $.el('div', {innerHTML: E(errors.length) + " errors occurred." + (Main.reportLink(errors)).innerHTML + " [
show ]"});
+ $.on(div.lastElementChild, 'click', function() {
+ var ref;
+ return ref = this.textContent === 'show' ? ['hide', false] : ['show', true], this.textContent = ref[0], logs.hidden = ref[1], ref;
+ });
+ logs = $.el('div', {
+ hidden: true
+ });
+ for (j = 0, len = errors.length; j < len; j++) {
+ error = errors[j];
+ $.add(logs, Main.parseError(error));
+ }
+ return new Notice('error', [div, logs], 30);
+ },
+ parseError: function(data, reportLink) {
+ var context, error, lines, message, ref, ref1;
+ c.error(data.message, data.error.stack);
+ message = $.el('div', {innerHTML: E(data.message) + ((reportLink) ? (reportLink).innerHTML : "")});
+ error = $.el('div', {
+ textContent: (data.error.name || 'Error') + ": " + (data.error.message || 'see console for details')
+ });
+ lines = ((ref = data.error.stack) != null ? (ref1 = ref.match(/\d+(?=:\d+\)?$)/mg)) != null ? ref1.join().replace(/^/, ' at ') : void 0 : void 0) || '';
+ context = $.el('div', {
+ textContent: "(4chan X ccd0 v" + g.VERSION + " " + $.platform + " on " + $.engine + lines + ")"
+ });
+ return [message, error, context];
+ },
+ reportLink: function(errors) {
+ var addDetails, data, details, info, title, url;
+ data = errors[0];
+ title = data.message;
+ if (errors.length > 1) {
+ title += " (+" + (errors.length - 1) + " other errors)";
+ }
+ details = '';
+ addDetails = function(text) {
+ if (!(encodeURIComponent(title + details + text + '\n').length > 8110)) {
+ return details += text + '\n';
+ }
+ };
+ addDetails("[Please describe the steps needed to reproduce this error.]\n\nScript: 4chan X ccd0 v" + g.VERSION + " " + $.platform + "\nURL: " + location.href + "\nUser agent: " + navigator.userAgent);
+ if ($.platform === 'userscript' && (info = typeof GM !== "undefined" && GM !== null ? GM.info : (typeof GM_info !== "undefined" && GM_info !== null ? GM_info : void 0))) {
+ addDetails("Userscript manager: " + info.scriptHandler + " " + info.version);
+ }
+ addDetails('\n' + data.error);
+ if (data.error.stack) {
+ addDetails(data.error.stack.replace(data.error.toString(), '').trim());
+ }
+ if (data.html) {
+ addDetails('\n`' + data.html + '`');
+ }
+ details = details.replace(/file:\/{3}.+\//g, '');
+ url = 'https://gitreports.com/issue/ccd0/4chan-x?issue_title=%title&details=%details'.replace('%title', encodeURIComponent(title)).replace('%details', encodeURIComponent(details));
+ return {innerHTML: "
[report ] "};
+ },
+ isThisPageLegit: function() {
+ if (!('thisPageIsLegit' in Main)) {
+ Main.thisPageIsLegit = g.SITE.isThisPageLegit ? g.SITE.isThisPageLegit() : !/^[45]\d\d\b/.test(document.title) && !/\.(?:json|rss)$/.test(location.pathname);
+ }
+ return Main.thisPageIsLegit;
+ },
+ ready: function(cb) {
+ return $.ready(function() {
+ if (Main.isThisPageLegit()) {
+ return cb();
+ }
+ });
+ },
+ mounted: function(cb) {
+ if (Main.isMounted) {
+ return cb();
+ } else {
+ return Main.mountedCBs.push(cb);
+ }
+ },
+ mountedCBs: [],
+ features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Delay Redirect on Post', PostRedirect], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Tinyboard Glue', Tinyboard], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Post Jumper', PostJumper], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Announcements', PSA], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning], ['Mod Contact Links', ModContact]]
+ };
+
+ return Main;
+
+}).call(this);
+
+Main.init();
+
+})();
diff --git a/.config/qutebrowser/plugins/redirect.py b/.config/qutebrowser/plugins/redirect.py
new file mode 100644
index 0000000..f4406fa
--- /dev/null
+++ b/.config/qutebrowser/plugins/redirect.py
@@ -0,0 +1,38 @@
+import qutebrowser.api.interceptor
+import random
+
+
+redirects = {
+ "imgur.com": [
+ "imgur.alt.tyil.nl",
+ ],
+ "youtube.com": [
+ "youtube.alt.tyil.nl",
+ ],
+ "twitter.com": [
+ "twitter.alt.tyil.nl",
+ ],
+ "reddit.com": [
+ "reddit.alt.tyil.nl",
+ ],
+ "www.reddit.com": [
+ "reddit.alt.tyil.nl",
+ ]
+}
+
+
+def redirect(request: qutebrowser.api.interceptor.Request):
+ source = request.request_url.host()
+
+ if source not in redirects:
+ return
+
+ destination = random.choice(redirects[request.request_url.host()])
+
+ print(f"Redirecting {source} to {destination}")
+
+ request.request_url.setHost(destination)
+ request.redirect(request.request_url)
+
+
+qutebrowser.api.interceptor.register(redirect)
diff --git a/.config/qutebrowser/quickmarks b/.config/qutebrowser/quickmarks
new file mode 100644
index 0000000..e9392eb
--- /dev/null
+++ b/.config/qutebrowser/quickmarks
@@ -0,0 +1 @@
+blog https://www.tyil.nl/posts/
--
cgit v1.1