diff --git a/CHANGELOG.md b/CHANGELOG.md index 28f8ff4b3..a43249535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ 4chan XT uses a different user script namespace than 4chan X, so to migrate you need to export settings from 4chan X, and import them in XT. +### Not released yet + +- Upstream came back, so I had to port those new features. + - Fix for unwanted sorting of catalog under certain settings. [ccd0#3212](https://github.com/ccd0/4chan-x/issues/3212), + 7dfba22042d01fde1e762af68e92109d80d0164d + - Turn JS Whitelist functionality off by default. 419e90c38eddc65a5a32e4a17a8211b3157ae61e + - Better way of turning off JS Whitelist. 7df2750fadffe0b5cc441b21034563c95c8500bd + - Update documentation. 62e4ccf1e869ab4757fa2b9107d1a52b1890a1fc + - Fallback when XPCNativeWrapper is unavailable [ccd0#3430](https://github.com/ccd0/4chan-x/pull/3430) + - Add ability to clear whole thread watcher [ccd0#2926](https://github.com/ccd0/4chan-x/pull/2926) + ### 2.18.0 (2024-11-20) - The quick reply now supports mp4. [#124](https://github.com/TuxedoTako/4chan-xt/pull/124) diff --git a/src/General/Settings/Advanced.html b/src/General/Settings/Advanced.html index 2fd84649d..f3b1318a6 100644 --- a/src/General/Settings/Advanced.html +++ b/src/General/Settings/Advanced.html @@ -71,7 +71,7 @@ if you are on /g/.
- For custom styling, you can wrap groups or individual links in {{ and }}, to wrap them in + For custom styling, you can wrap groups or individual links in {{
code> and }}, to wrap them in a span. You can also add classes in double quotes right after the {{. For example:
[g-title] {{"favorites"[a-title / jp-title]}}
Results in:
@@ -187,7 +187,7 @@ Javascript Whitelist
Sources from which Javascript is allowed to be loaded by Content Security Policy.
- Lines starting with a # will be ignored. + Lines starting with a # will be ignored. Remove or comment out all lines to allow everything.
diff --git a/src/Monitoring/ThreadWatcher.js b/src/Monitoring/ThreadWatcher.js index aeeea6113..4eb40c90d 100644 --- a/src/Monitoring/ThreadWatcher.js +++ b/src/Monitoring/ThreadWatcher.js @@ -104,7 +104,7 @@ var ThreadWatcher = { if (this.cb) { $.off(this.el, 'click', this.cb); } this.cb = function() { $.event('CloseMenu'); - return ThreadWatcher.toggle(thread); + return ThreadWatcher.toggle(thread, true); }; $.on(this.el, 'click', this.cb); return true; @@ -167,8 +167,8 @@ var ThreadWatcher = { catalogNode() { if (ThreadWatcher.isWatched(this.thread)) { $.addClass(this.nodes.root, 'watched'); } return $.on(this.nodes.root, 'mousedown click', e => { - if ((e.button !== 0) || !e.altKey) { return; } - if (e.type === 'click') { ThreadWatcher.toggle(this.thread); } + if ((e.button !== 0) || !e.altKey) return; + if (e.type === 'click') ThreadWatcher.toggle(this.thread, true); return e.preventDefault(); }); }, // Also on mousedown to prevent highlighting thumbnail in Firefox. @@ -206,6 +206,16 @@ var ThreadWatcher = { } $.event('CloseMenu'); }, + clear() { + if (!confirm("Delete ALL threads from watcher?")) return; + const ref = ThreadWatcher.getAll(); + for (let i = 0, len = ref.length; i < len; i++) { + const { siteID, boardID, threadID } = ref[i]; + ThreadWatcher.db.delete({ siteID, boardID, threadID }); + } + ThreadWatcher.refresh(true); + $.event('CloseMenu'); + }, pruneDeads() { if ($.hasClass(this, 'disabled')) return; for (var {siteID, boardID, threadID, data} of ThreadWatcher.getAll()) { @@ -213,7 +223,7 @@ var ThreadWatcher = { ThreadWatcher.db.delete({siteID, boardID, threadID}); } } - ThreadWatcher.refresh(); + ThreadWatcher.refresh(true); $.event('CloseMenu'); }, pruneReadDeads() { @@ -223,7 +233,7 @@ var ThreadWatcher = { ThreadWatcher.db.delete({ siteID, boardID, threadID }); } } - ThreadWatcher.refresh(); + ThreadWatcher.refresh(true); $.event('CloseMenu'); }, dismiss() { @@ -236,22 +246,24 @@ var ThreadWatcher = { }, toggle() { const {thread} = Get.postFromNode(this); - ThreadWatcher.toggle(thread); + ThreadWatcher.toggle(thread, true); }, rm() { const {siteID} = this.parentNode.dataset; const [boardID, threadID] = this.parentNode.dataset.fullID.split('.'); - ThreadWatcher.rm(siteID, boardID, +threadID); + ThreadWatcher.rm(siteID, boardID, +threadID, undefined, true); }, post(e) { const {boardID, threadID, postID} = e.detail; const cb = PostRedirect.delay(); if (postID === threadID) { if (Conf['Auto Watch']) { - ThreadWatcher.addRaw(boardID, threadID, {}, cb); + ThreadWatcher.addRaw(boardID, threadID, {}, cb, true); } } else if (Conf['Auto Watch Reply']) { - ThreadWatcher.add((g.threads.get(boardID + '.' + threadID) || new Thread(threadID, g.boards[boardID] || new Board(boardID))), cb); + ThreadWatcher.add( + (g.threads.get(boardID + '.' + threadID) || new Thread(threadID, g.boards[boardID] || new Board(boardID))), + cb, true); } }, onIndexUpdate(e) { @@ -684,7 +696,7 @@ var ThreadWatcher = { return ThreadWatcher.refreshIcon(); }, - refresh() { + refresh(manual) { ThreadWatcher.build(); g.threads.forEach(function(thread) { @@ -701,7 +713,7 @@ var ThreadWatcher = { }); if (Conf['Pin Watched Threads']) { - return $.event('SortIndex', {deferred: Conf['Index Mode'] !== 'catalog'}); + return $.event('SortIndex', {deferred: !(manual && Conf['Index Mode'] === 'catalog')}); } }, @@ -752,18 +764,18 @@ var ThreadWatcher = { return ThreadWatcher.db.extend({boardID, threadID, val: {isDead: true, isArchived: undefined, page: undefined, lastPage: undefined, unread: undefined, quotingYou: undefined}}, cb); }, - toggle(thread) { + toggle(thread, manual) { const siteID = g.SITE.ID; const boardID = thread.board.ID; const threadID = thread.ID; if (ThreadWatcher.db.get({boardID, threadID})) { - return ThreadWatcher.rm(siteID, boardID, threadID); + return ThreadWatcher.rm(siteID, boardID, threadID, undefined, manual); } else { - return ThreadWatcher.add(thread); + return ThreadWatcher.add(thread, undefined, manual); } }, - add(thread, cb) { + add(thread, cb, manual) { const data = {}; const siteID = g.SITE.ID; const boardID = thread.board.ID; @@ -776,16 +788,16 @@ var ThreadWatcher = { data.isDead = true; } if (thread.OP) { data.excerpt = Get.threadExcerpt(thread); } - return ThreadWatcher.addRaw(boardID, threadID, data, cb); + return ThreadWatcher.addRaw(boardID, threadID, data, cb, manual); }, - addRaw(boardID, threadID, data, cb) { + addRaw(boardID, threadID, data, cb, manual) { const oldData = ThreadWatcher.db.get({ boardID, threadID, defaultValue: dict() }); delete oldData.last; delete oldData.modified; $.extend(oldData, data); ThreadWatcher.db.set({boardID, threadID, val: oldData}, cb); - ThreadWatcher.refresh(); + ThreadWatcher.refresh(manual); const thread = {siteID: g.SITE.ID, boardID, threadID, data, force: true}; if (Conf['Show Page'] && !data.isDead) { return ThreadWatcher.fetchBoard([thread]); @@ -794,9 +806,9 @@ var ThreadWatcher = { } }, - rm(siteID, boardID, threadID, cb) { + rm(siteID, boardID, threadID, cb, manual) { ThreadWatcher.db.delete({siteID, boardID, threadID}, cb); - return ThreadWatcher.refresh(); + return ThreadWatcher.refresh(manual); }, menu: { @@ -827,68 +839,71 @@ var ThreadWatcher = { return true; } }); - return $.on(entryEl, 'click', () => ThreadWatcher.toggle(g.threads.get(`${g.BOARD}.${g.THREADID}`))); + return $.on(entryEl, 'click', () => ThreadWatcher.toggle(g.threads.get(`${g.BOARD}.${g.THREADID}`), true)); }, addMenuEntries() { - const entries = []; - - // `Open all` entry - entries.push({ - text: 'Open all threads', - cb: ThreadWatcher.cb.openAll, - open() { - this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); - return true; - } - }); - - // `Open Unread` entry - entries.push({ - text: 'Open unread threads', - cb: ThreadWatcher.cb.openUnread, - open() { - this.el.classList.toggle('disabled', !$('.replies-unread', ThreadWatcher.list)); - return true; - } - }); - const toggleDisabledDead = function () { this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list)); return true; }; - // `Open unread dead threads` entry - entries.push({ - text: 'Open unread dead threads', - cb: ThreadWatcher.cb.openDeads, - open: toggleDisabledDead, - }); - - // `Prune all dead threads` entry - entries.push({ - text: 'Prune all dead threads', - cb: ThreadWatcher.cb.pruneDeads, - open: toggleDisabledDead, - }); - - // `Prune read dead threads` entry - entries.push({ - text: 'Prune read dead threads', - cb: ThreadWatcher.cb.pruneReadDeads, - open: toggleDisabledDead, - }); - - // `Dismiss posts quoting you` entry - 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() { - this.el.classList.toggle('disabled', !$.hasClass(ThreadWatcher.shortcut, 'replies-quoting-you')); - return true; - } - }); + const entries = [ + // `Open all` entry + { + text: 'Open all threads', + cb: ThreadWatcher.cb.openAll, + open() { + this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); + return true; + } + }, + { + text: 'Clear all threads', + cb: ThreadWatcher.cb.clear, + open() { + this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); + return true; + } + }, + // `Open Unread` entry + { + text: 'Open unread threads', + cb: ThreadWatcher.cb.openUnread, + open() { + this.el.classList.toggle('disabled', !$('.replies-unread', ThreadWatcher.list)); + return true; + } + }, + // `Open unread dead threads` entry + { + text: 'Open unread dead threads', + cb: ThreadWatcher.cb.openDeads, + open: toggleDisabledDead, + }, + // `Prune all dead threads` entry + { + text: 'Prune all dead threads', + cb: ThreadWatcher.cb.pruneDeads, + open: toggleDisabledDead, + }, + // `Prune read dead threads` entry + { + text: 'Prune read dead threads', + cb: ThreadWatcher.cb.pruneReadDeads, + open: toggleDisabledDead, + }, + // `Dismiss posts quoting you` entry + { + 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() { + this.el.classList.toggle('disabled', !$.hasClass(ThreadWatcher.shortcut, 'replies-quoting-you')); + return true; + } + }, + ]; for (var {text, title, cb, open} of entries) { var entry = { @@ -924,8 +939,10 @@ var ThreadWatcher = { entry.el.title += '\n[Remember Last Read Post is disabled.]'; } $.on(input, 'change', $.cb.checked); - if (['Current Board', 'Show Page', 'Show Unread Count', 'Show Site Prefix'].includes(name)) { $.on(input, 'change', ThreadWatcher.refresh); } - if (['Show Page', 'Show Unread Count', 'Auto Update Thread Watcher'].includes(name)) { $.on(input, 'change', ThreadWatcher.fetchAuto); } + if (['Current Board', 'Show Page', 'Show Unread Count', 'Show Site Prefix'].includes(name)) + $.on(input, 'change', () => ThreadWatcher.refresh()); + if (['Show Page', 'Show Unread Count', 'Auto Update Thread Watcher'].includes(name)) + $.on(input, 'change', ThreadWatcher.fetchAuto); return this.menu.addEntry(entry); } } diff --git a/src/config/Config.js b/src/config/Config.js index 0bbb53b83..4399c8de8 100644 --- a/src/config/Config.js +++ b/src/config/Config.js @@ -892,21 +892,7 @@ current-archive-text:"Archive"] sjisPreview: false }, - jsWhitelist: `\ -http://s.4cdn.org -https://s.4cdn.org -http://www.google.com -https://www.google.com -https://www.gstatic.com -http://cdn.mathjax.org -https://cdn.mathjax.org -https://cdnjs.cloudflare.com -https://hcaptcha.com -https://*.hcaptcha.com -'self' -'unsafe-inline' -'unsafe-eval'\ -`, + jsWhitelist: '', captchaLanguage: '', diff --git a/src/main/Main.js b/src/main/Main.js index 2c6360d12..5830683d1 100644 --- a/src/main/Main.js +++ b/src/main/Main.js @@ -209,7 +209,10 @@ var Main = { !SW.yotsuba.regexp.captcha.test(location.href) && !$$('script:not([src])', d).filter(s => /this\[/.test(s.textContent)).length ) { - ($.getSync || $.get)({'jsWhitelist': Conf['jsWhitelist']}, ({jsWhitelist}) => $.addCSP(`script-src ${jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim()}`)); + ($.getSync || $.get)({'jsWhitelist': Conf['jsWhitelist']}, ({jsWhitelist}) => { + const parsedList = jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim(); + if (/\S/.test(parsedList)) $.addCSP(`script-src ${parsedList}`); + }); } // Get saved values as items diff --git a/src/platform/$.ts b/src/platform/$.ts index 267ff1d58..6dc16c424 100644 --- a/src/platform/$.ts +++ b/src/platform/$.ts @@ -63,11 +63,11 @@ $.getOwn = function(obj, key) { }; $.ajax = (function() { - let pageXHR; + let pageXHR = XMLHttpRequest; if (window.wrappedJSObject && !XMLHttpRequest.wrappedJSObject) { - pageXHR = XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest); - } else { - pageXHR = XMLHttpRequest; + try { + pageXHR = XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest); + } catch (e) {} } return function (url, options={}) {