diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js
index de2e61d9f..2ea12029b 100644
--- a/builds/appchan-x.user.js
+++ b/builds/appchan-x.user.js
@@ -3872,15 +3872,22 @@
};
DataBoard.prototype["delete"] = function(_arg) {
- var boardID, postID, threadID;
+ var boardID, postID, threadID, _ref;
boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID;
+ $.forceSync(this.key);
if (postID) {
+ if (!((_ref = this.data.boards[boardID]) != null ? _ref[threadID] : void 0)) {
+ return;
+ }
delete this.data.boards[boardID][threadID][postID];
this.deleteIfEmpty({
boardID: boardID,
threadID: threadID
});
} else if (threadID) {
+ if (!this.data.boards[boardID]) {
+ return;
+ }
delete this.data.boards[boardID][threadID];
this.deleteIfEmpty({
boardID: boardID
@@ -3894,6 +3901,7 @@
DataBoard.prototype.deleteIfEmpty = function(_arg) {
var boardID, threadID;
boardID = _arg.boardID, threadID = _arg.threadID;
+ $.forceSync(this.key);
if (threadID) {
if (!Object.keys(this.data.boards[boardID][threadID]).length) {
delete this.data.boards[boardID][threadID];
@@ -3909,6 +3917,7 @@
DataBoard.prototype.set = function(_arg) {
var boardID, postID, threadID, val, _base, _base1, _base2;
boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID, val = _arg.val;
+ $.forceSync(this.key);
if (postID !== void 0) {
((_base = ((_base1 = this.data.boards)[boardID] || (_base1[boardID] = {})))[threadID] || (_base[threadID] = {}))[postID] = val;
} else if (threadID !== void 0) {
@@ -3942,70 +3951,47 @@
return val || defaultValue;
};
+ DataBoard.prototype.forceSync = function() {
+ return $.forceSync(this.key);
+ };
+
DataBoard.prototype.clean = function() {
- var boardID, keys, now, _i, _len;
- now = Date.now();
- if ((this.data.lastChecked || 0) > now - 2 * $.HOUR) {
- return;
- }
- keys = Object.keys(this.data.boards);
- if (!keys.length) {
- return;
- }
- for (_i = 0, _len = keys.length; _i < _len; _i++) {
- boardID = keys[_i];
+ var boardID, now, threadID, val, _ref;
+ $.forceSync(this.key);
+ _ref = this.data.boards;
+ for (boardID in _ref) {
+ val = _ref[boardID];
this.deleteIfEmpty({
boardID: boardID
});
- if (boardID in this.data.boards) {
- this.ajaxClean(boardID);
+ }
+ now = Date.now();
+ if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) {
+ this.data.lastChecked = now;
+ for (boardID in this.data.boards) {
+ for (threadID in this.data.boards[boardID]) {
+ this.ajaxClean(boardID, threadID);
+ }
}
}
- this.data.lastChecked = now;
return this.save();
};
- DataBoard.prototype.ajaxClean = function(boardID) {
- return $.cache("//a.4cdn.org/" + boardID + "/threads.json", (function(_this) {
- return function(e) {
- var board, count, page, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
- if (e.target.status !== 200) {
+ DataBoard.prototype.ajaxClean = function(boardID, threadID) {
+ return $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", {
+ onloadend: (function(_this) {
+ return function(e) {
if (e.target.status === 404) {
- _this["delete"]({
- boardID: boardID
+ return _this["delete"]({
+ boardID: boardID,
+ threadID: threadID
});
}
- return;
- }
- board = _this.data.boards[boardID];
- threads = {};
- _ref = e.target.response;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- page = _ref[_i];
- _ref1 = page.threads;
- for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
- thread = _ref1[_j];
- if (thread.no in board) {
- threads[thread.no] = board[thread.no];
- }
- }
- }
- count = Object.keys(threads).length;
- if (count === Object.keys(board).length) {
- return;
- }
- if (count) {
- return _this.set({
- boardID: boardID,
- val: threads
- });
- } else {
- return _this["delete"]({
- boardID: boardID
- });
- }
- };
- })(this));
+ };
+ })(this)
+ }, {
+ type: 'head'
+ });
};
DataBoard.prototype.onSync = function(data) {
@@ -4053,7 +4039,7 @@
return;
}
$.off(d, 'visibilitychange', this.add);
- $.add(Header.noticesRoot, this.el);
+ $.add(doc, this.el);
this.el.clientHeight;
this.el.style.opacity = 1;
if (this.timeout) {
@@ -4319,7 +4305,7 @@
className: 'menu-button a-icon',
id: 'main-menu'
});
- box = UI.checkbox.bind(UI);
+ box = UI.checkbox;
barFixedToggler = box('Fixed Header', 'Fixed Header');
headerToggler = box('Header auto-hide', ' Auto-hide header');
scrollHeaderToggler = box('Header auto-hide on scroll', ' Auto-hide header on scroll');
@@ -6211,43 +6197,21 @@
};
/* File Info */
- if (file != null ? file.isDeleted : void 0) {
- fileCont = {
- innerHTML: "
"
- };
- } else if (file && boardID === 'f') {
- fileCont = {
- innerHTML: "
File: " + E(file.name) + "-(" + E($.bytesToString(file.size)) + ", " + E(file.width) + "x" + E(file.height) + ", " + E(file.tag) + ") "
- };
- } else if (file) {
- if (file.isSpoiler) {
- shortFilename = 'Spoiler Image';
- if (spoilerRange = Build.spoilerRange[boardID]) {
- fileThumb = "//s.4cdn.org/image/spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png";
- } else {
- fileThumb = '//s.4cdn.org/image/spoiler.png';
- }
- file.twidth = file.theight = 100;
- } else {
- shortFilename = Build.shortFilename(file.name, !isOP);
- fileThumb = file.turl;
- }
- fileSize = $.bytesToString(file.size);
- fileDims = file.url.slice(-4) === '.pdf' ? 'PDF' : "" + file.width + "x" + file.height;
- fileLink = file.isSpoiler || file.name === shortFilename ? {
- innerHTML: "" + E(shortFilename) + ""
- } : {
- innerHTML: "" + E(shortFilename) + ""
- };
- fileText = file.isSpoiler ? {
- innerHTML: "File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
"
- } : {
- innerHTML: "File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
"
- };
- ({
- innerHTML: fileText.innerHTML + "
"
- });
- }
+ fileCont = (file != null ? file.isDeleted : void 0) ? {
+ innerHTML: "
"
+ } : file && boardID === 'f' ? {
+ innerHTML: "File: " + E(file.name) + "-(" + E($.bytesToString(file.size)) + ", " + E(file.width) + "x" + E(file.height) + ", " + E(file.tag) + ") "
+ } : file ? (file.isSpoiler ? (shortFilename = 'Spoiler Image', (spoilerRange = Build.spoilerRange[boardID]) ? fileThumb = "//s.4cdn.org/image/spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png" : fileThumb = '//s.4cdn.org/image/spoiler.png', file.twidth = file.theight = 100) : (shortFilename = Build.shortFilename(file.name, !isOP), fileThumb = file.turl), fileSize = $.bytesToString(file.size), fileDims = file.url.slice(-4) === '.pdf' ? 'PDF' : "" + file.width + "x" + file.height, fileLink = file.isSpoiler || file.name === shortFilename ? {
+ innerHTML: "" + E(shortFilename) + ""
+ } : {
+ innerHTML: "" + E(shortFilename) + ""
+ }, fileText = file.isSpoiler ? {
+ innerHTML: "File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
"
+ } : {
+ innerHTML: "File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
"
+ }, {
+ innerHTML: fileText.innerHTML + "
"
+ }) : void 0;
fileBlock = file ? {
innerHTML: "" + fileCont.innerHTML + "
"
} : {
@@ -6765,20 +6729,30 @@
};
UI = (function() {
- var Menu, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove;
- dialog = function(id, position, html) {
- var el, move;
+ var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove;
+ dialog = function(id, position, properties) {
+ var child, el, move, _i, _len, _ref;
el = $.el('div', {
className: 'dialog',
- innerHTML: html,
id: id
});
+ $.extend(el, properties);
el.style.cssText = position;
$.get("" + id + ".position", position, function(item) {
return el.style.cssText = item["" + 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() {
@@ -6788,11 +6762,24 @@
lastToggledButton = null;
- function Menu() {
+ function Menu(type) {
+ this.type = type;
this.rmEntry = __bind(this.rmEntry, this);
+ this.addEntry = __bind(this.addEntry, this);
this.onFocus = __bind(this.onFocus, this);
this.keybinds = __bind(this.keybinds, this);
this.close = __bind(this.close, 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 = [];
}
@@ -6832,7 +6819,6 @@
menu = this.makeMenu();
currentMenu = menu;
lastToggledButton = button;
- $.addClass(button, 'open');
this.entries.sort(function(first, second) {
return first.order - second.order;
});
@@ -6870,12 +6856,7 @@
Menu.prototype.insertEntry = function(entry, parent, data) {
var subEntry, submenu, _i, _len, _ref;
if (typeof entry.open === 'function') {
- if (!entry.open(data, (function(_this) {
- return function(subEntry) {
- _this.parseEntry(subEntry);
- return entry.subEntries.push(subEntry);
- };
- })(this))) {
+ if (!entry.open(data)) {
return;
}
}
@@ -6899,7 +6880,7 @@
Menu.prototype.close = function() {
$.rm(currentMenu);
- $.rmClass(lastToggledButton, 'open');
+ $.rmClass(lastToggledButton, 'active');
currentMenu = null;
lastToggledButton = null;
return $.off(d, 'click CloseMenu', this.close);
@@ -7014,7 +6995,6 @@
return;
}
$.addClass(el, 'has-submenu');
- $.addClass(el, 'pfa');
for (_i = 0, _len = subEntries.length; _i < _len; _i++) {
subEntry = subEntries[_i];
this.parseEntry(subEntry);
@@ -7025,13 +7005,13 @@
})();
dragstart = function(e) {
- var el, isTouching, o, rect, screenHeight, screenWidth, _ref, _ref1;
+ var el, isTouching, o, rect, screenHeight, screenWidth, _ref;
if (e.type === 'mousedown' && e.button !== 0) {
return;
}
e.preventDefault();
if (isTouching = e.type === 'touchstart') {
- _ref = e.changedTouches, e = _ref[_ref.length - 1];
+ e = e.changedTouches[e.changedTouches.length - 1];
}
el = $.x('ancestor::div[contains(@class,"dialog")][1]', this);
rect = el.getBoundingClientRect();
@@ -7048,7 +7028,7 @@
screenWidth: screenWidth,
isTouching: isTouching
};
- _ref1 = Conf['Header auto-hide'] || !Conf['Fixed Header'] ? [0, 0] : Conf['Bottom Header'] ? [0, Header.bar.getBoundingClientRect().height] : [Header.bar.getBoundingClientRect().height, 0], o.topBorder = _ref1[0], o.bottomBorder = _ref1[1];
+ _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);
@@ -7110,33 +7090,29 @@
return $.set("" + this.id + ".position", this.style.cssText);
};
hoverstart = function(_arg) {
- var asapTest, cb, el, endEvents, latestEvent, noRemove, o, offsetX, offsetY, root;
- root = _arg.root, el = _arg.el, latestEvent = _arg.latestEvent, endEvents = _arg.endEvents, asapTest = _arg.asapTest, cb = _arg.cb, offsetX = _arg.offsetX, offsetY = _arg.offsetY, noRemove = _arg.noRemove;
+ var asapTest, cb, el, endEvents, height, latestEvent, noRemove, o, root, _ref;
+ root = _arg.root, el = _arg.el, latestEvent = _arg.latestEvent, endEvents = _arg.endEvents, asapTest = _arg.asapTest, height = _arg.height, cb = _arg.cb, noRemove = _arg.noRemove;
o = {
root: root,
el: el,
style: el.style,
+ isImage: (_ref = el.nodeName) === 'IMG' || _ref === 'VIDEO',
cb: cb,
- close: close,
endEvents: endEvents,
latestEvent: latestEvent,
clientHeight: doc.clientHeight,
clientWidth: doc.clientWidth,
- offsetX: offsetX || 45,
- offsetY: offsetY || -120,
noRemove: noRemove
};
o.hover = hover.bind(o);
o.hoverend = hoverend.bind(o);
- if (asapTest) {
- $.asap(function() {
- return !el.parentNode || asapTest();
- }, function() {
- if (el.parentNode) {
- return o.hover(o.latestEvent);
- }
- });
- }
+ $.asap(function() {
+ return !el.parentNode || asapTest();
+ }, function() {
+ if (el.parentNode) {
+ return o.hover(o.latestEvent);
+ }
+ });
$.on(root, endEvents, o.hoverend);
if ($.x('ancestor::div[contains(@class,"inline")][1]', root)) {
$.on(d, 'keydown', o.hoverend);
@@ -7150,22 +7126,28 @@
return $.on(doc, 'mousemove', o.workaround);
};
hover = function(e) {
- var clientX, clientY, height, left, right, top, _ref;
+ var clientX, clientY, height, left, right, style, threshold, top, _ref;
this.latestEvent = e;
- height = this.el.offsetHeight + 25;
+ height = this.height || this.el.offsetHeight;
clientX = e.clientX, clientY = e.clientY;
- top = clientY + this.offsetY;
- top = this.clientHeight <= height || top <= 0 ? 0 : top + height >= this.clientHeight ? this.clientHeight - height : top;
- _ref = clientX <= this.clientWidth / 2 ? [clientX + this.offsetX + 'px', null] : [null, this.clientWidth - clientX + this.offsetX + 'px'], left = _ref[0], right = _ref[1];
- this.style.top = top + 'px';
- this.style.left = left;
- return this.style.right = right;
+ 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);
+ }
+ _ref = clientX <= threshold ? [clientX + 45 + 'px', null] : [null, this.clientWidth - clientX + 45 + 'px'], left = _ref[0], right = _ref[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;
}
- $.rm(this.el);
+ if (!this.noRemove) {
+ $.rm(this.el);
+ }
$.off(this.root, this.endEvents, this.hoverend);
$.off(d, 'keydown', this.hoverend);
$.off(this.root, 'mousemove', this.hover);
@@ -7174,10 +7156,25 @@
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;
+ };
return {
dialog: dialog,
Menu: Menu,
- hover: hoverstart
+ hover: hoverstart,
+ checkbox: checkbox
};
})();
@@ -9730,11 +9727,10 @@
dialog: function() {
var dialog, event, i, items, match_max, match_min, name, node, nodes, rules, save, setNode;
QR.nodes = nodes = {
- el: dialog = UI.dialog('qr', 'top:0;right:0;')
+ el: dialog = UI.dialog('qr', 'top:0;right:0;', {
+ innerHTML: "\r\r\r\r\r"
+ })
};
- $.extend(dialog, {
- innerHTML: "\r\r\r\r\r"
- });
setNode = function(name, query) {
return nodes[name] = $(query, dialog);
};
@@ -14099,6 +14095,7 @@
this.status = $('#watcher-status', this.dialog);
this.list = this.dialog.lastElementChild;
this.refreshButton = $('.refresh', this.dialog);
+ this.unreaddb = Unread.db || new DataBoard('lastReadPosts');
$.on(d, 'QRPostSuccessful', this.cb.post);
if (g.VIEW === 'thread') {
$.on(d, 'ThreadUpdate', this.cb.threadUpdate);
diff --git a/builds/crx/script.js b/builds/crx/script.js
index 842fdf724..b2addcf5f 100644
--- a/builds/crx/script.js
+++ b/builds/crx/script.js
@@ -3899,15 +3899,22 @@
};
DataBoard.prototype["delete"] = function(_arg) {
- var boardID, postID, threadID;
+ var boardID, postID, threadID, _ref;
boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID;
+ $.forceSync(this.key);
if (postID) {
+ if (!((_ref = this.data.boards[boardID]) != null ? _ref[threadID] : void 0)) {
+ return;
+ }
delete this.data.boards[boardID][threadID][postID];
this.deleteIfEmpty({
boardID: boardID,
threadID: threadID
});
} else if (threadID) {
+ if (!this.data.boards[boardID]) {
+ return;
+ }
delete this.data.boards[boardID][threadID];
this.deleteIfEmpty({
boardID: boardID
@@ -3921,6 +3928,7 @@
DataBoard.prototype.deleteIfEmpty = function(_arg) {
var boardID, threadID;
boardID = _arg.boardID, threadID = _arg.threadID;
+ $.forceSync(this.key);
if (threadID) {
if (!Object.keys(this.data.boards[boardID][threadID]).length) {
delete this.data.boards[boardID][threadID];
@@ -3936,6 +3944,7 @@
DataBoard.prototype.set = function(_arg) {
var boardID, postID, threadID, val, _base, _base1, _base2;
boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID, val = _arg.val;
+ $.forceSync(this.key);
if (postID !== void 0) {
((_base = ((_base1 = this.data.boards)[boardID] || (_base1[boardID] = {})))[threadID] || (_base[threadID] = {}))[postID] = val;
} else if (threadID !== void 0) {
@@ -3969,70 +3978,47 @@
return val || defaultValue;
};
+ DataBoard.prototype.forceSync = function() {
+ return $.forceSync(this.key);
+ };
+
DataBoard.prototype.clean = function() {
- var boardID, keys, now, _i, _len;
- now = Date.now();
- if ((this.data.lastChecked || 0) > now - 2 * $.HOUR) {
- return;
- }
- keys = Object.keys(this.data.boards);
- if (!keys.length) {
- return;
- }
- for (_i = 0, _len = keys.length; _i < _len; _i++) {
- boardID = keys[_i];
+ var boardID, now, threadID, val, _ref;
+ $.forceSync(this.key);
+ _ref = this.data.boards;
+ for (boardID in _ref) {
+ val = _ref[boardID];
this.deleteIfEmpty({
boardID: boardID
});
- if (boardID in this.data.boards) {
- this.ajaxClean(boardID);
+ }
+ now = Date.now();
+ if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) {
+ this.data.lastChecked = now;
+ for (boardID in this.data.boards) {
+ for (threadID in this.data.boards[boardID]) {
+ this.ajaxClean(boardID, threadID);
+ }
}
}
- this.data.lastChecked = now;
return this.save();
};
- DataBoard.prototype.ajaxClean = function(boardID) {
- return $.cache("//a.4cdn.org/" + boardID + "/threads.json", (function(_this) {
- return function(e) {
- var board, count, page, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
- if (e.target.status !== 200) {
+ DataBoard.prototype.ajaxClean = function(boardID, threadID) {
+ return $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", {
+ onloadend: (function(_this) {
+ return function(e) {
if (e.target.status === 404) {
- _this["delete"]({
- boardID: boardID
+ return _this["delete"]({
+ boardID: boardID,
+ threadID: threadID
});
}
- return;
- }
- board = _this.data.boards[boardID];
- threads = {};
- _ref = e.target.response;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- page = _ref[_i];
- _ref1 = page.threads;
- for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
- thread = _ref1[_j];
- if (thread.no in board) {
- threads[thread.no] = board[thread.no];
- }
- }
- }
- count = Object.keys(threads).length;
- if (count === Object.keys(board).length) {
- return;
- }
- if (count) {
- return _this.set({
- boardID: boardID,
- val: threads
- });
- } else {
- return _this["delete"]({
- boardID: boardID
- });
- }
- };
- })(this));
+ };
+ })(this)
+ }, {
+ type: 'head'
+ });
};
DataBoard.prototype.onSync = function(data) {
@@ -4080,7 +4066,7 @@
return;
}
$.off(d, 'visibilitychange', this.add);
- $.add(Header.noticesRoot, this.el);
+ $.add(doc, this.el);
this.el.clientHeight;
this.el.style.opacity = 1;
if (this.timeout) {
@@ -4348,7 +4334,7 @@
className: 'menu-button a-icon',
id: 'main-menu'
});
- box = UI.checkbox.bind(UI);
+ box = UI.checkbox;
barFixedToggler = box('Fixed Header', 'Fixed Header');
headerToggler = box('Header auto-hide', ' Auto-hide header');
scrollHeaderToggler = box('Header auto-hide on scroll', ' Auto-hide header on scroll');
@@ -6240,43 +6226,21 @@
};
/* File Info */
- if (file != null ? file.isDeleted : void 0) {
- fileCont = {
- innerHTML: "
"
- };
- } else if (file && boardID === 'f') {
- fileCont = {
- innerHTML: "File: " + E(file.name) + "-(" + E($.bytesToString(file.size)) + ", " + E(file.width) + "x" + E(file.height) + ", " + E(file.tag) + ") "
- };
- } else if (file) {
- if (file.isSpoiler) {
- shortFilename = 'Spoiler Image';
- if (spoilerRange = Build.spoilerRange[boardID]) {
- fileThumb = "//s.4cdn.org/image/spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png";
- } else {
- fileThumb = '//s.4cdn.org/image/spoiler.png';
- }
- file.twidth = file.theight = 100;
- } else {
- shortFilename = Build.shortFilename(file.name, !isOP);
- fileThumb = file.turl;
- }
- fileSize = $.bytesToString(file.size);
- fileDims = file.url.slice(-4) === '.pdf' ? 'PDF' : "" + file.width + "x" + file.height;
- fileLink = file.isSpoiler || file.name === shortFilename ? {
- innerHTML: "" + E(shortFilename) + ""
- } : {
- innerHTML: "" + E(shortFilename) + ""
- };
- fileText = file.isSpoiler ? {
- innerHTML: "File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
"
- } : {
- innerHTML: "File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
"
- };
- ({
- innerHTML: fileText.innerHTML + "
"
- });
- }
+ fileCont = (file != null ? file.isDeleted : void 0) ? {
+ innerHTML: "
"
+ } : file && boardID === 'f' ? {
+ innerHTML: "File: " + E(file.name) + "-(" + E($.bytesToString(file.size)) + ", " + E(file.width) + "x" + E(file.height) + ", " + E(file.tag) + ") "
+ } : file ? (file.isSpoiler ? (shortFilename = 'Spoiler Image', (spoilerRange = Build.spoilerRange[boardID]) ? fileThumb = "//s.4cdn.org/image/spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png" : fileThumb = '//s.4cdn.org/image/spoiler.png', file.twidth = file.theight = 100) : (shortFilename = Build.shortFilename(file.name, !isOP), fileThumb = file.turl), fileSize = $.bytesToString(file.size), fileDims = file.url.slice(-4) === '.pdf' ? 'PDF' : "" + file.width + "x" + file.height, fileLink = file.isSpoiler || file.name === shortFilename ? {
+ innerHTML: "" + E(shortFilename) + ""
+ } : {
+ innerHTML: "" + E(shortFilename) + ""
+ }, fileText = file.isSpoiler ? {
+ innerHTML: "File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
"
+ } : {
+ innerHTML: "File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
"
+ }, {
+ innerHTML: fileText.innerHTML + "
"
+ }) : void 0;
fileBlock = file ? {
innerHTML: "" + fileCont.innerHTML + "
"
} : {
@@ -6794,20 +6758,30 @@
};
UI = (function() {
- var Menu, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove;
- dialog = function(id, position, html) {
- var el, move;
+ var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove;
+ dialog = function(id, position, properties) {
+ var child, el, move, _i, _len, _ref;
el = $.el('div', {
className: 'dialog',
- innerHTML: html,
id: id
});
+ $.extend(el, properties);
el.style.cssText = position;
$.get("" + id + ".position", position, function(item) {
return el.style.cssText = item["" + 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() {
@@ -6817,11 +6791,24 @@
lastToggledButton = null;
- function Menu() {
+ function Menu(type) {
+ this.type = type;
this.rmEntry = __bind(this.rmEntry, this);
+ this.addEntry = __bind(this.addEntry, this);
this.onFocus = __bind(this.onFocus, this);
this.keybinds = __bind(this.keybinds, this);
this.close = __bind(this.close, 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 = [];
}
@@ -6861,7 +6848,6 @@
menu = this.makeMenu();
currentMenu = menu;
lastToggledButton = button;
- $.addClass(button, 'open');
this.entries.sort(function(first, second) {
return first.order - second.order;
});
@@ -6899,12 +6885,7 @@
Menu.prototype.insertEntry = function(entry, parent, data) {
var subEntry, submenu, _i, _len, _ref;
if (typeof entry.open === 'function') {
- if (!entry.open(data, (function(_this) {
- return function(subEntry) {
- _this.parseEntry(subEntry);
- return entry.subEntries.push(subEntry);
- };
- })(this))) {
+ if (!entry.open(data)) {
return;
}
}
@@ -6928,7 +6909,7 @@
Menu.prototype.close = function() {
$.rm(currentMenu);
- $.rmClass(lastToggledButton, 'open');
+ $.rmClass(lastToggledButton, 'active');
currentMenu = null;
lastToggledButton = null;
return $.off(d, 'click CloseMenu', this.close);
@@ -7043,7 +7024,6 @@
return;
}
$.addClass(el, 'has-submenu');
- $.addClass(el, 'pfa');
for (_i = 0, _len = subEntries.length; _i < _len; _i++) {
subEntry = subEntries[_i];
this.parseEntry(subEntry);
@@ -7054,13 +7034,13 @@
})();
dragstart = function(e) {
- var el, isTouching, o, rect, screenHeight, screenWidth, _ref, _ref1;
+ var el, isTouching, o, rect, screenHeight, screenWidth, _ref;
if (e.type === 'mousedown' && e.button !== 0) {
return;
}
e.preventDefault();
if (isTouching = e.type === 'touchstart') {
- _ref = e.changedTouches, e = _ref[_ref.length - 1];
+ e = e.changedTouches[e.changedTouches.length - 1];
}
el = $.x('ancestor::div[contains(@class,"dialog")][1]', this);
rect = el.getBoundingClientRect();
@@ -7077,7 +7057,7 @@
screenWidth: screenWidth,
isTouching: isTouching
};
- _ref1 = Conf['Header auto-hide'] || !Conf['Fixed Header'] ? [0, 0] : Conf['Bottom Header'] ? [0, Header.bar.getBoundingClientRect().height] : [Header.bar.getBoundingClientRect().height, 0], o.topBorder = _ref1[0], o.bottomBorder = _ref1[1];
+ _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);
@@ -7139,33 +7119,29 @@
return $.set("" + this.id + ".position", this.style.cssText);
};
hoverstart = function(_arg) {
- var asapTest, cb, el, endEvents, latestEvent, noRemove, o, offsetX, offsetY, root;
- root = _arg.root, el = _arg.el, latestEvent = _arg.latestEvent, endEvents = _arg.endEvents, asapTest = _arg.asapTest, cb = _arg.cb, offsetX = _arg.offsetX, offsetY = _arg.offsetY, noRemove = _arg.noRemove;
+ var asapTest, cb, el, endEvents, height, latestEvent, noRemove, o, root, _ref;
+ root = _arg.root, el = _arg.el, latestEvent = _arg.latestEvent, endEvents = _arg.endEvents, asapTest = _arg.asapTest, height = _arg.height, cb = _arg.cb, noRemove = _arg.noRemove;
o = {
root: root,
el: el,
style: el.style,
+ isImage: (_ref = el.nodeName) === 'IMG' || _ref === 'VIDEO',
cb: cb,
- close: close,
endEvents: endEvents,
latestEvent: latestEvent,
clientHeight: doc.clientHeight,
clientWidth: doc.clientWidth,
- offsetX: offsetX || 45,
- offsetY: offsetY || -120,
noRemove: noRemove
};
o.hover = hover.bind(o);
o.hoverend = hoverend.bind(o);
- if (asapTest) {
- $.asap(function() {
- return !el.parentNode || asapTest();
- }, function() {
- if (el.parentNode) {
- return o.hover(o.latestEvent);
- }
- });
- }
+ $.asap(function() {
+ return !el.parentNode || asapTest();
+ }, function() {
+ if (el.parentNode) {
+ return o.hover(o.latestEvent);
+ }
+ });
$.on(root, endEvents, o.hoverend);
if ($.x('ancestor::div[contains(@class,"inline")][1]', root)) {
$.on(d, 'keydown', o.hoverend);
@@ -7173,22 +7149,28 @@
return $.on(root, 'mousemove', o.hover);
};
hover = function(e) {
- var clientX, clientY, height, left, right, top, _ref;
+ var clientX, clientY, height, left, right, style, threshold, top, _ref;
this.latestEvent = e;
- height = this.el.offsetHeight + 25;
+ height = this.height || this.el.offsetHeight;
clientX = e.clientX, clientY = e.clientY;
- top = clientY + this.offsetY;
- top = this.clientHeight <= height || top <= 0 ? 0 : top + height >= this.clientHeight ? this.clientHeight - height : top;
- _ref = clientX <= this.clientWidth / 2 ? [clientX + this.offsetX + 'px', null] : [null, this.clientWidth - clientX + this.offsetX + 'px'], left = _ref[0], right = _ref[1];
- this.style.top = top + 'px';
- this.style.left = left;
- return this.style.right = right;
+ 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);
+ }
+ _ref = clientX <= threshold ? [clientX + 45 + 'px', null] : [null, this.clientWidth - clientX + 45 + 'px'], left = _ref[0], right = _ref[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;
}
- $.rm(this.el);
+ if (!this.noRemove) {
+ $.rm(this.el);
+ }
$.off(this.root, this.endEvents, this.hoverend);
$.off(d, 'keydown', this.hoverend);
$.off(this.root, 'mousemove', this.hover);
@@ -7196,10 +7178,25 @@
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;
+ };
return {
dialog: dialog,
Menu: Menu,
- hover: hoverstart
+ hover: hoverstart,
+ checkbox: checkbox
};
})();
@@ -9784,11 +9781,10 @@
dialog: function() {
var dialog, event, i, items, match_max, match_min, name, node, nodes, rules, save, setNode;
QR.nodes = nodes = {
- el: dialog = UI.dialog('qr', 'top:0;right:0;')
+ el: dialog = UI.dialog('qr', 'top:0;right:0;', {
+ innerHTML: "\r\r\r\r\r"
+ })
};
- $.extend(dialog, {
- innerHTML: "\r\r\r\r\r"
- });
setNode = function(name, query) {
return nodes[name] = $(query, dialog);
};
@@ -14126,6 +14122,7 @@
this.status = $('#watcher-status', this.dialog);
this.list = this.dialog.lastElementChild;
this.refreshButton = $('.refresh', this.dialog);
+ this.unreaddb = Unread.db || new DataBoard('lastReadPosts');
$.on(d, 'QRPostSuccessful', this.cb.post);
if (g.VIEW === 'thread') {
$.on(d, 'ThreadUpdate', this.cb.threadUpdate);
diff --git a/src/General/Build.coffee b/src/General/Build.coffee
index 3175af075..775fd3067 100755
--- a/src/General/Build.coffee
+++ b/src/General/Build.coffee
@@ -181,14 +181,14 @@ Build =
### File Info ###
- if file?.isDeleted
- fileCont = <%= html(
+ fileCont = if file?.isDeleted
+ <%= html(
'' +
'
' +
''
) %>
else if file and boardID is 'f'
- fileCont = <%= html(
+ <%= html(
'' +
'File: ${file.name}' +
'-(${$.bytesToString(file.size)}, ${file.width}x${file.height}, ${file.tag})' +
diff --git a/src/General/Header.coffee b/src/General/Header.coffee
index 56c47d6f3..07a01d55f 100644
--- a/src/General/Header.coffee
+++ b/src/General/Header.coffee
@@ -6,7 +6,7 @@ Header =
className: 'menu-button a-icon'
id: 'main-menu'
- box = UI.checkbox.bind UI
+ box = UI.checkbox
barFixedToggler = box 'Fixed Header', 'Fixed Header'
headerToggler = box 'Header auto-hide', ' Auto-hide header'
diff --git a/src/General/UI.coffee b/src/General/UI.coffee
index 7e7af73cf..877632bcb 100755
--- a/src/General/UI.coffee
+++ b/src/General/UI.coffee
@@ -1,15 +1,19 @@
UI = do ->
- dialog = (id, position, html) ->
+ dialog = (id, position, properties) ->
el = $.el 'div',
className: 'dialog'
- innerHTML: html
id: id
+ $.extend el, properties
el.style.cssText = position
$.get "#{id}.position", position, (item) ->
el.style.cssText = item["#{id}.position"]
move = $ '.move', el
$.on move, 'touchstart mousedown', dragstart
+ for child in move.children
+ continue unless child.tagName
+ $.on child, 'touchstart mousedown', (e) ->
+ e.stopPropagation()
el
@@ -17,7 +21,12 @@ UI = do ->
currentMenu = null
lastToggledButton = null
- constructor: ->
+ constructor: (@type) ->
+ # XXX AddMenuEntry event is deprecated
+ $.on d, 'AddMenuEntry', ({detail}) =>
+ return if detail.type isnt @type
+ delete detail.open
+ @addEntry detail
@entries = []
makeMenu: ->
@@ -47,7 +56,6 @@ UI = do ->
menu = @makeMenu()
currentMenu = menu
lastToggledButton = button
- $.addClass button, 'open'
@entries.sort (first, second) ->
first.order - second.order
@@ -95,9 +103,7 @@ UI = do ->
insertEntry: (entry, parent, data) ->
if typeof entry.open is 'function'
- return unless entry.open data, (subEntry) =>
- @parseEntry subEntry
- entry.subEntries.push subEntry
+ return unless entry.open data
$.add parent, entry.el
return unless entry.subEntries
@@ -113,7 +119,7 @@ UI = do ->
close: =>
$.rm currentMenu
- $.rmClass lastToggledButton, 'open'
+ $.rmClass lastToggledButton, 'active'
currentMenu = null
lastToggledButton = null
$.off d, 'click CloseMenu', @close
@@ -184,7 +190,7 @@ UI = do ->
style.left = left
style.right = right
- addEntry: (entry) ->
+ addEntry: (entry) =>
@parseEntry entry
@entries.push entry
@@ -199,7 +205,6 @@ UI = do ->
el.style.order = entry.order or 100
return unless subEntries
$.addClass el, 'has-submenu'
- $.addClass el, 'pfa'
for subEntry in subEntries
@parseEntry subEntry
return
@@ -209,7 +214,7 @@ UI = do ->
# prevent text selection
e.preventDefault()
if isTouching = e.type is 'touchstart'
- [..., e] = e.changedTouches
+ e = e.changedTouches[e.changedTouches.length - 1]
# distance from pointer to el edge is constant; calculate it here.
el = $.x 'ancestor::div[contains(@class,"dialog")][1]', @
rect = el.getBoundingClientRect()
@@ -302,76 +307,80 @@ UI = do ->
$.off d, 'mouseup', @up
$.set "#{@id}.position", @style.cssText
- hoverstart = ({root, el, latestEvent, endEvents, asapTest, cb, offsetX, offsetY, noRemove}) ->
+ hoverstart = ({root, el, latestEvent, endEvents, asapTest, height, cb, noRemove}) ->
o = {
root
el
style: el.style
+ isImage: el.nodeName in ['IMG', 'VIDEO']
cb
- close: close
endEvents
latestEvent
clientHeight: doc.clientHeight
clientWidth: doc.clientWidth
- offsetX: offsetX or 45
- offsetY: offsetY or -120
noRemove
}
o.hover = hover.bind o
o.hoverend = hoverend.bind o
- if asapTest
- $.asap ->
- !el.parentNode or asapTest()
- , ->
- o.hover o.latestEvent if el.parentNode
+ $.asap ->
+ !el.parentNode or asapTest()
+ , ->
+ o.hover o.latestEvent if el.parentNode
$.on root, endEvents, o.hoverend
if $.x 'ancestor::div[contains(@class,"inline")][1]', root
$.on d, 'keydown', o.hoverend
$.on root, 'mousemove', o.hover
<% if (type === 'userscript') { %>
- # Workaround for https://github.com/MayhemYDG/4chan-x/issues/377
+ # Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=674955
o.workaround = (e) -> o.hoverend(e) unless root.contains e.target
$.on doc, 'mousemove', o.workaround
<% } %>
hover = (e) ->
@latestEvent = e
- height = @el.offsetHeight + 25
+ height = @height or @el.offsetHeight
{clientX, clientY} = e
-
- top = clientY + @offsetY
- top = if @clientHeight <= height or top <= 0
- 0
- else if top + height >= @clientHeight
- @clientHeight - height
+ top = if @isImage
+ Math.max 0, clientY * (@clientHeight - height) / @clientHeight
else
- top
+ Math.max 0, Math.min(@clientHeight - height, clientY - 120)
- [left, right] = if clientX <= @clientWidth / 2
- [clientX + @offsetX + 'px', null]
+ threshold = @clientWidth / 2
+ threshold = Math.max threshold, @clientWidth - 400 unless @isImage
+ [left, right] = if clientX <= threshold
+ [clientX + 45 + 'px', null]
else
- [null, @clientWidth - clientX + @offsetX + 'px']
+ [null, @clientWidth - clientX + 45 + 'px']
- @style.top = top + 'px'
- @style.left = left
- @style.right = right
+ {style} = @
+ style.top = top + 'px'
+ style.left = left
+ style.right = right
hoverend = (e) ->
return if e.type is 'keydown' and e.keyCode isnt 13 or e.target.nodeName is "TEXTAREA"
- $.rm @el
+ $.rm @el unless @noRemove
$.off @root, @endEvents, @hoverend
$.off d, 'keydown', @hoverend
$.off @root, 'mousemove', @hover
<% if (type === 'userscript') { %>
- # Workaround for https://github.com/MayhemYDG/4chan-x/issues/377
+ # Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=674955
$.off doc, 'mousemove', @workaround
<% } %>
@cb.call @ if @cb
+ checkbox = (name, text, checked) ->
+ checked = Conf[name] unless checked?
+ label = $.el 'label'
+ input = $.el 'input', {type: 'checkbox', name, checked}
+ $.add label, [input, $.tn text]
+ label
+
return {
- dialog: dialog
- Menu: Menu
- hover: hoverstart
+ dialog: dialog
+ Menu: Menu
+ hover: hoverstart
+ checkbox: checkbox
}
diff --git a/src/General/eventPage/eventPage.coffee b/src/General/eventPage/eventPage.coffee
new file mode 100644
index 000000000..e5a107dc0
--- /dev/null
+++ b/src/General/eventPage/eventPage.coffee
@@ -0,0 +1,28 @@
+requestID = 0
+
+chrome.runtime.onMessage.addListener (request, sender, sendResponse) ->
+ id = requestID
+ requestID++
+ sendResponse id
+
+ xhr = new XMLHttpRequest()
+ xhr.open 'GET', request.url, true
+ xhr.responseType = request.responseType
+ xhr.addEventListener 'load', ->
+ if @readyState is @DONE && xhr.status is 200
+ contentType = @getResponseHeader 'Content-Type'
+ contentDisposition = @getResponseHeader 'Content-Disposition'
+ {response} = @
+ if request.responseType is 'arraybuffer'
+ response = [new Uint8Array(response)...]
+ chrome.tabs.sendMessage sender.tab.id, {id, response, contentType, contentDisposition}
+ else
+ chrome.tabs.sendMessage sender.tab.id, {id, error: true}
+ , false
+ xhr.addEventListener 'error', ->
+ chrome.tabs.sendMessage sender.tab.id, {id, error: true}
+ , false
+ xhr.addEventListener 'abort', ->
+ chrome.tabs.sendMessage sender.tab.id, {id, error: true}
+ , false
+ xhr.send()
diff --git a/src/General/lib/databoard.class b/src/General/lib/databoard.class
index a93e90e1a..ec0c31099 100755
--- a/src/General/lib/databoard.class
+++ b/src/General/lib/databoard.class
@@ -16,10 +16,13 @@ class DataBoard
save: -> $.set @key, @data
delete: ({boardID, threadID, postID}) ->
+ $.forceSync @key
if postID
+ return unless @data.boards[boardID]?[threadID]
delete @data.boards[boardID][threadID][postID]
@deleteIfEmpty {boardID, threadID}
else if threadID
+ return unless @data.boards[boardID]
delete @data.boards[boardID][threadID]
@deleteIfEmpty {boardID}
else
@@ -27,6 +30,7 @@ class DataBoard
@save()
deleteIfEmpty: ({boardID, threadID}) ->
+ $.forceSync @key
if threadID
unless Object.keys(@data.boards[boardID][threadID]).length
delete @data.boards[boardID][threadID]
@@ -35,6 +39,7 @@ class DataBoard
delete @data.boards[boardID]
set: ({boardID, threadID, postID, val}) ->
+ $.forceSync @key
if postID isnt undefined
((@data.boards[boardID] or= {})[threadID] or= {})[postID] = val
else if threadID isnt undefined
@@ -60,39 +65,28 @@ class DataBoard
thread
val or defaultValue
+ forceSync: ->
+ $.forceSync @key
clean: ->
- now = Date.now()
- return if (@data.lastChecked or 0) > now - 2 * $.HOUR
-
- # Don't even bother writing unless we have data to write
- keys = Object.keys @data.boards
- return unless keys.length
-
- # Since we generated the keys anyways, use them to avoid the slow ``Object::hasOwnProperty`` method
- # Not that ``Object.keys`` is fast
- for boardID in keys
+ $.forceSync @key
+ for boardID, val of @data.boards
@deleteIfEmpty {boardID}
- @ajaxClean boardID if boardID of @data.boards
- @data.lastChecked = now
+ now = Date.now()
+ if (@data.lastChecked or 0) < now - 2 * $.HOUR
+ @data.lastChecked = now
+ for boardID of @data.boards
+ for threadID of @data.boards[boardID]
+ @ajaxClean boardID, threadID
@save()
- ajaxClean: (boardID) ->
- $.cache "//a.4cdn.org/#{boardID}/threads.json", (e) =>
- if e.target.status isnt 200
- @delete {boardID} if e.target.status is 404
- return
- board = @data.boards[boardID]
- threads = {}
- for page in e.target.response
- for thread in page.threads when thread.no of board
- threads[thread.no] = board[thread.no]
- count = Object.keys(threads).length
- return if count is Object.keys(board).length # Nothing changed.
- if count
- @set {boardID, val: threads}
- else
- @delete {boardID}
+ ajaxClean: (boardID, threadID) ->
+ $.ajax "//a.4cdn.org/#{boardID}/thread/#{threadID}.json",
+ onloadend: (e) =>
+ if e.target.status is 404
+ @delete {boardID, threadID}
+ ,
+ type: 'head'
onSync: (data) =>
@data = data or boards: {}
diff --git a/src/General/lib/notice.class b/src/General/lib/notice.class
index c2545ce39..4eae06879 100644
--- a/src/General/lib/notice.class
+++ b/src/General/lib/notice.class
@@ -19,7 +19,7 @@ class Notice
$.on d, 'visibilitychange', @add
return
$.off d, 'visibilitychange', @add
- $.add Header.noticesRoot, @el
+ $.add doc, @el
@el.clientHeight # force reflow
@el.style.opacity = 1
setTimeout @close, @timeout * $.SECOND if @timeout
diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee
index da3e10425..b95a7b5a9 100755
--- a/src/Monitoring/ThreadWatcher.coffee
+++ b/src/Monitoring/ThreadWatcher.coffee
@@ -8,6 +8,7 @@ ThreadWatcher =
@status = $ '#watcher-status', @dialog
@list = @dialog.lastElementChild
@refreshButton = $ '.refresh', @dialog
+ @unreaddb = Unread.db or new DataBoard 'lastReadPosts'
$.on d, 'QRPostSuccessful', @cb.post
$.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread'
diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee
index e4e1783b7..1ae45e1f7 100644
--- a/src/Posting/QR.coffee
+++ b/src/Posting/QR.coffee
@@ -387,7 +387,7 @@ QR =
dialog: ->
QR.nodes = nodes =
el: dialog = UI.dialog 'qr', 'top:0;right:0;',
- $.extend dialog, <%= importHTML('Features/QuickReply') %>
+ <%= importHTML('Features/QuickReply') %>
setNode = (name, query) ->
nodes[name] = $ query, dialog