Merge branch 'master' into webext

This commit is contained in:
ccd0 2017-09-29 02:45:54 -07:00
commit 3e019cc116
35 changed files with 1615 additions and 1053 deletions

View File

@ -2,8 +2,47 @@
-Sometimes the changelog has notes (not comprehensive) acknowledging people's work. This does not mean the changes are their fault, only that their code was used. All changes to the script are chosen by and the fault of the maintainer (ccd0). -Sometimes the changelog has notes (not comprehensive) acknowledging people's work. This does not mean the changes are their fault, only that their code was used. All changes to the script are chosen by and the fault of the maintainer (ccd0).
### v1.13.12
**v1.13.12.1** *(2017-09-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.1/builds/4chan-X-noupdate.crx)]
- Merge v1.13.11.5: Fix lag after settings changes.
**v1.13.12.0** *(2017-09-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.0/builds/4chan-X-noupdate.crx)]
- Based on v1.13.11.4.
- Preliminary support for Greasemonkey 4.
- Minor custom cooldown bugfix.
- (BeltranBot) Fix 'open thread in new tab' keybind for VM/TM
### v1.13.11
**v1.13.11.5** *(2017-09-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.5/builds/4chan-X-noupdate.crx)]
- Fix lag after settings changes.
**v1.13.11.4** *(2017-08-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.4/builds/4chan-X-noupdate.crx)]
- Merge v1.13.10.7: Fix quote preview bug when reply is in index data but no thread object exists. #1478
**v1.13.11.3** *(2017-08-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.3/builds/4chan-X-noupdate.crx)]
- Add language setting for time formatting.
**v1.13.11.2** *(2017-08-12)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.2/builds/4chan-X-noupdate.crx)]
- Last Long Reply order will now ignore hidden and filtered replies.
**v1.13.11.1** *(2017-08-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.1/builds/4chan-X-noupdate.crx)]
- Merge v1.13.10.6: Disable 'Redirect to HTTPS' on platforms where we use localStorage for saving settings.
**v1.13.11.0** *(2017-08-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.0/builds/4chan-X-noupdate.crx)]
- Based on v1.13.10.5.
- Support [spoiler] and [code] tags in 'Copy Text' menu item.
- Trim quoted text to text fully inside post. #1108
### v1.13.10 ### v1.13.10
**v1.13.10.7** *(2017-08-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.7/builds/4chan-X-noupdate.crx)]
- Fix quote preview bug when reply is in index data but no thread object exists. #1478
**v1.13.10.6** *(2017-08-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.6/builds/4chan-X-noupdate.crx)]
- Disable 'Redirect to HTTPS' on platforms where we use localStorage for saving settings.
**v1.13.10.5** *(2017-08-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.5/builds/4chan-X-noupdate.crx)] **v1.13.10.5** *(2017-08-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.5/builds/4chan-X-noupdate.crx)]
- Better parsing of archive links for Quote Inlining / Hover. - Better parsing of archive links for Quote Inlining / Hover.
- Add Board Tips. - Add Board Tips.

View File

@ -10,21 +10,12 @@ https://github.com/Nebukazar/OneeChan.
## Please note ## Please note
**Uninstalling**: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the `[Settings]` link in the top right corner, uncheck "`Disable the native extension`" in the panel that appears, and click the "`Save Settings`" button. If you don't see a "`Save Settings`" button, it may be being hidden by your ad blocker. **Uninstalling**: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the `[Settings]` link in the top right corner, uncheck "`Disable the native extension`" in the panel that appears, and click the "`Save Settings`" button. If you don't see a "`Save Settings`" button, it may be being hidden by your ad blocker.
**Private browsing**: 4chan X does not yet support private browsing / incognito mode. Although it may work in this mode, browsing data recorded by 4chan X, such as your last read post in a thread and which posts are yours, will still need to be cleared manually by resetting your settings. To control what browsing data 4chan X records, use the `Remember Last Read Post` and `Remember Your Posts` options in the settings panel. **Private browsing**: By default, 4chan X remembers your last read post in a thread and which posts were made by you, even if you are in private browsing / incognito mode. If you want to turn this off, uncheck the `Remember Last Read Post` and `Remember Your Posts` options in the settings panel. You can clear all 4chan browsing history saved by 4chan X by resetting your settings.
**HTTPS**: 4chan X currently shares your settings and post history between the HTTP and HTTPS versions of 4chan. If you are concerned about protecting your privacy against a man-in-the-middle attack, you should disable 4chan X on the HTTP version of 4chan and/or install [HTTPS Everywhere](https://www.eff.org/https-everywhere).
## Install ## Install
### Firefox ### Firefox
You will first need to install a userscript manager such as [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/). Unfortunately, Firefox's transition to WebExtensions has forced a complete rewrite of Greasemonkey, leaving the current version of Greasemonkey unmaintained and unreliable. Until the WebExtensions version of Greasemonkey is complete, your options are: Install [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/), then **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**.
- Use [Firefox ESR](https://www.mozilla.org/en-US/firefox/organizations/), which should continue to work with the current version of [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) until a new one is ready.
- If you're using a freedom-respecting build/fork of Firefox, you can try [this build of Greasemonkey](https://www.4chan-x.net/greasemonkey-2017.07.27.beta.xpi) based on [this pull request](https://github.com/greasemonkey/greasemonkey/pull/2507) which may fix the issues some people are having.
- Install [Violentmonkey](https://addons.mozilla.org/en-US/firefox/addon/violentmonkey/) or [Tampermonkey](https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/). These are already WebExtensions, but due to the current limitations of WebExtensions, they offer less functionality than Greasemonkey.
- Use [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) with the current version of Firefox, but don't be surprised when things break.
After Greasemonkey or an alternative userscript manager is installed, **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**.
Ports of Greasemonkey are available for [SeaMonkey](https://sourceforge.net/projects/gmport/) and [Pale Moon](https://github.com/janekptacijarabaci/greasemonkey/releases/latest). Ports of Greasemonkey are available for [SeaMonkey](https://sourceforge.net/projects/gmport/) and [Pale Moon](https://github.com/janekptacijarabaci/greasemonkey/releases/latest).

Binary file not shown.

View File

@ -1,6 +1,6 @@
// ==UserScript== // ==UserScript==
// @name 4chan X beta // @name 4chan X beta
// @version 1.13.10.5 // @version 1.13.12.1
// @minGMVer 1.14 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace 4chan-X // @namespace 4chan-X
@ -48,6 +48,11 @@
// @grant GM_addValueChangeListener // @grant GM_addValueChangeListener
// @grant GM_openInTab // @grant GM_openInTab
// @grant GM_xmlhttpRequest // @grant GM_xmlhttpRequest
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @grant GM.listValues
// @grant GM.xmlHttpRequest
// @run-at document-start // @run-at document-start
// @updateURL https://www.4chan-x.net/builds/4chan-X-beta.meta.js // @updateURL https://www.4chan-x.net/builds/4chan-X-beta.meta.js
// @downloadURL https://www.4chan-x.net/builds/4chan-X-beta.user.js // @downloadURL https://www.4chan-x.net/builds/4chan-X-beta.user.js

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,6 +1,6 @@
// ==UserScript== // ==UserScript==
// @name 4chan X // @name 4chan X
// @version 1.13.10.5 // @version 1.13.12.1
// @minGMVer 1.14 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace 4chan-X // @namespace 4chan-X
@ -48,6 +48,11 @@
// @grant GM_addValueChangeListener // @grant GM_addValueChangeListener
// @grant GM_openInTab // @grant GM_openInTab
// @grant GM_xmlhttpRequest // @grant GM_xmlhttpRequest
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @grant GM.listValues
// @grant GM.xmlHttpRequest
// @run-at document-start // @run-at document-start
// @updateURL https://www.4chan-x.net/builds/4chan-X.meta.js // @updateURL https://www.4chan-x.net/builds/4chan-X.meta.js
// @downloadURL https://www.4chan-x.net/builds/4chan-X.user.js // @downloadURL https://www.4chan-x.net/builds/4chan-X.user.js

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='lacclbnghgdicfifcamcmcnilckjamag'> <app appid='lacclbnghgdicfifcamcmcnilckjamag'>
<updatecheck codebase='https://www.4chan-x.net/builds/4chan-X-beta.crx' version='1.13.10.5' /> <updatecheck codebase='https://www.4chan-x.net/builds/4chan-X-beta.crx' version='1.13.12.1' />
</app> </app>
</gupdate> </gupdate>

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='lacclbnghgdicfifcamcmcnilckjamag'> <app appid='lacclbnghgdicfifcamcmcnilckjamag'>
<updatecheck codebase='https://www.4chan-x.net/builds/4chan-X.crx' version='1.13.10.5' /> <updatecheck codebase='https://www.4chan-x.net/builds/4chan-X.crx' version='1.13.12.1' />
</app> </app>
</gupdate> </gupdate>

View File

@ -23,8 +23,7 @@
<a href="https://github.com/Nebukazar/OneeChan">https://github.com/Nebukazar/OneeChan</a>.</p> <a href="https://github.com/Nebukazar/OneeChan">https://github.com/Nebukazar/OneeChan</a>.</p>
<h2 id="please-note">Please note</h2> <h2 id="please-note">Please note</h2>
<p><strong>Uninstalling</strong>: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the <code>[Settings]</code> link in the top right corner, uncheck &quot;<code>Disable the native extension</code>&quot; in the panel that appears, and click the &quot;<code>Save Settings</code>&quot; button. If you don't see a &quot;<code>Save Settings</code>&quot; button, it may be being hidden by your ad blocker.</p> <p><strong>Uninstalling</strong>: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the <code>[Settings]</code> link in the top right corner, uncheck &quot;<code>Disable the native extension</code>&quot; in the panel that appears, and click the &quot;<code>Save Settings</code>&quot; button. If you don't see a &quot;<code>Save Settings</code>&quot; button, it may be being hidden by your ad blocker.</p>
<p><strong>Private browsing</strong>: 4chan X does not yet support private browsing / incognito mode. Although it may work in this mode, browsing data recorded by 4chan X, such as your last read post in a thread and which posts are yours, will still need to be cleared manually by resetting your settings. To control what browsing data 4chan X records, use the <code>Remember Last Read Post</code> and <code>Remember Your Posts</code> options in the settings panel.</p> <p><strong>Private browsing</strong>: By default, 4chan X remembers your last read post in a thread and which posts were made by you, even if you are in private browsing / incognito mode. If you want to turn this off, uncheck the <code>Remember Last Read Post</code> and <code>Remember Your Posts</code> options in the settings panel. You can clear all 4chan browsing history saved by 4chan X by resetting your settings.</p>
<p><strong>HTTPS</strong>: 4chan X currently shares your settings and post history between the HTTP and HTTPS versions of 4chan. If you are concerned about protecting your privacy against a man-in-the-middle attack, you should disable 4chan X on the HTTP version of 4chan and/or install <a href="https://www.eff.org/https-everywhere">HTTPS Everywhere</a>.</p>
<h2 id="install">Install</h2> <h2 id="install">Install</h2>
<input hidden type="checkbox" id="firefox-hide"><div><h3 id="firefox"><label for="firefox-hide">Firefox</label></h3> <input hidden type="checkbox" id="firefox-hide"><div><h3 id="firefox"><label for="firefox-hide">Firefox</label></h3>
<p>You will first need to install a userscript manager such as <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/">Greasemonkey</a>. Unfortunately, Firefox's transition to WebExtensions has forced a complete rewrite of Greasemonkey, leaving the current version of Greasemonkey unmaintained and unreliable. Until the WebExtensions version of Greasemonkey is complete, your options are:</p> <p>You will first need to install a userscript manager such as <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/">Greasemonkey</a>. Unfortunately, Firefox's transition to WebExtensions has forced a complete rewrite of Greasemonkey, leaving the current version of Greasemonkey unmaintained and unreliable. Until the WebExtensions version of Greasemonkey is complete, your options are:</p>

View File

@ -57,7 +57,12 @@
"GM_listValues", "GM_listValues",
"GM_addValueChangeListener", "GM_addValueChangeListener",
"GM_openInTab", "GM_openInTab",
"GM_xmlhttpRequest" "GM_xmlhttpRequest",
"GM.getValue",
"GM.setValue",
"GM.deleteValue",
"GM.listValues",
"GM.xmlHttpRequest"
], ],
"min": { "min": {
"chrome": "33", "chrome": "33",

View File

@ -114,16 +114,6 @@
"boards": ["h", "hc", "hm", "r", "s", "soc"], "boards": ["h", "hc", "hm", "r", "s", "soc"],
"files": ["h", "hc", "hm", "r", "s", "soc"], "files": ["h", "hc", "hm", "r", "s", "soc"],
"reports": true "reports": true
}, {
"uid": 32,
"name": "4tan",
"domain": "boards.4tan.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y"],
"files": ["bant", "can", "cock", "fap", "fitlit", "mlpol", "mo", "mtv", "outsoc", "spa", "vint"],
"reports": true
}, { }, {
"uid": 33, "uid": 33,
"name": "YEET Archive", "name": "YEET Archive",

View File

@ -688,6 +688,9 @@ Index =
else else
Index.parsedThreads[threadID].isHidden Index.parsedThreads[threadID].isHidden
isHiddenReply: (threadID, replyData) ->
PostHiding.isHidden(g.BOARD.ID, threadID, replyData.no) or Filter.isHidden(Build.parseJSON replyData, g.BOARD.ID)
buildThreads: (threadIDs, isCatalog) -> buildThreads: (threadIDs, isCatalog) ->
threads = [] threads = []
newThreads = [] newThreads = []
@ -788,8 +791,7 @@ Index =
replies = [] replies = []
for data in lastReplies for data in lastReplies
continue if PostHiding.isHidden(g.BOARD.ID, thread.ID, data.no) continue if Index.isHiddenReply thread.ID, data
continue if Filter.isHidden(Build.parseJSON data, g.BOARD.ID)
reply = Build.catalogReply thread, data reply = Build.catalogReply thread, data
RelativeDates.update $('time', reply) RelativeDates.update $('time', reply)
$.on $('.catalog-reply-preview', reply), 'mouseover', QuotePreview.mouseover $.on $('.catalog-reply-preview', reply), 'mouseover', QuotePreview.mouseover
@ -813,12 +815,16 @@ Index =
when 'lastlong' when 'lastlong'
lastlong = (thread) -> lastlong = (thread) ->
for r, i in (thread.last_replies or []) by -1 for r, i in (thread.last_replies or []) by -1
continue if Index.isHiddenReply thread.no, r
len = if r.com then Build.parseComment(r.com).replace(/[^a-z]/ig, '').length else 0 len = if r.com then Build.parseComment(r.com).replace(/[^a-z]/ig, '').length else 0
if len >= Index.lastLongThresholds[+!!r.ext] if len >= Index.lastLongThresholds[+!!r.ext]
return r return r
if thread.omitted_posts then thread.last_replies[0] else thread if thread.omitted_posts then thread.last_replies[0] else thread
lastlongD = {}
for thread in liveThreadData
lastlongD[thread.no] = lastlong(thread).no
[liveThreadData...].sort((a, b) -> [liveThreadData...].sort((a, b) ->
lastlong(b).no - lastlong(a).no lastlongD[b.no] - lastlongD[a.no]
).map (post) -> post.no ).map (post) -> post.no
when 'bump' then liveThreadIDs when 'bump' then liveThreadIDs
when 'birth' then [liveThreadIDs... ].sort (a, b) -> b - a when 'birth' then [liveThreadIDs... ].sort (a, b) -> b - a

View File

@ -162,8 +162,12 @@ Settings =
addCheckboxes fs, obj addCheckboxes fs, obj
$.add section, fs $.add section, fs
addCheckboxes $('div[data-name="JSON Index"] > .suboption-list', section), Config.Index addCheckboxes $('div[data-name="JSON Index"] > .suboption-list', section), Config.Index
# Unsupported options
if $.engine isnt 'gecko' if $.engine isnt 'gecko'
$('div[data-name="Remember QR Size"]', section).hidden = true # XXX not supported $('div[data-name="Remember QR Size"]', section).hidden = true
if $.perProtocolSettings
$('div[data-name="Redirect to HTTPS"]', section).hidden = true
$.get items, (items) -> $.get items, (items) ->
for key, val of items for key, val of items
@ -489,7 +493,7 @@ Settings =
$.id('lastarchivecheck').textContent = 'never' $.id('lastarchivecheck').textContent = 'never'
items = {} items = {}
for name in ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist'] for name in ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'timeLocale', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']
items[name] = Conf[name] items[name] = Conf[name]
input = inputs[name] input = inputs[name]
event = if name in ['archiveLists', 'archiveAutoUpdate', 'QR.personas', 'favicon', 'usercss'] then 'change' else 'input' event = if name in ['archiveLists', 'archiveAutoUpdate', 'QR.personas', 'favicon', 'usercss'] then 'change' else 'input'
@ -642,6 +646,9 @@ Settings =
time: -> time: ->
@nextElementSibling.textContent = Time.format @value, new Date() @nextElementSibling.textContent = Time.format @value, new Date()
timeLocale: ->
Settings.time.call $('[name=time]', Settings.dialog)
backlink: -> backlink: ->
@nextElementSibling.textContent = @value.replace /%(?:id|%)/g, (x) -> ({'%id': '123456789', '%%': '%'})[x] @nextElementSibling.textContent = @value.replace /%(?:id|%)/g, (x) -> ({'%id': '123456789', '%%': '%'})[x]

View File

@ -64,6 +64,7 @@
<div>Minute: <code>%M</code></div> <div>Minute: <code>%M</code></div>
<div>Second: <code>%S</code></div> <div>Second: <code>%S</code></div>
<div>Literal <code>%</code>: <code>%%</code></div> <div>Literal <code>%</code>: <code>%%</code></div>
<div><a href="https://www.w3.org/International/articles/language-tags/" target="_blank">Language tag</a>: <input name="timeLocale" class="field" spellcheck="false"></div>
</fieldset> </fieldset>
<fieldset> <fieldset>

View File

@ -12,7 +12,7 @@ CopyTextLink =
el: a el: a
order: 12 order: 12
open: (post) -> open: (post) ->
CopyTextLink.text = post.info.comment CopyTextLink.text = (post.origin or post).commentOrig()
true true
copy: -> copy: ->

View File

@ -5,7 +5,7 @@ BoardTips =
<%= html( <%= html(
'New to /qa/?<br>' + 'New to /qa/?<br>' +
'/qa/ is NOT an effective way to contact the mods.<br>' + '/qa/ is NOT an effective way to contact the mods.<br>' +
'Use <a href="https://www.rizon.net/chat" target="_blank">IRC</a> or <a href="https://www.4chan.org/feedback" target="_blank">feedback</a> instead. ' + 'Message a mod on <a href="https://www.rizon.net/chat" target="_blank">IRC</a> or use <a href="https://www.4chan.org/feedback" target="_blank">feedback</a> instead. ' +
'More details <a href="https://www.4chan-x.net/qa_instructions.png" target="_blank">here</a>.' 'More details <a href="https://www.4chan-x.net/qa_instructions.png" target="_blank">here</a>.'
) %> ) %>
] ]

View File

@ -312,7 +312,7 @@ Keybinds =
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index'
url = "/#{thread.board}/thread/#{thread}" url = "/#{thread.board}/thread/#{thread}"
if tab if tab
$.open url $.open location.origin + url
else else
location.href = url location.href = url

View File

@ -41,13 +41,26 @@ Time =
'December' 'December'
] ]
localeFormat: (date, options, defaultValue) ->
if Conf['timeLocale']
try
return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date)
defaultValue
localeFormatPart: (date, options, part, defaultValue) ->
if Conf['timeLocale']
try
parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date)
return parts.map((x) -> if x.type is part then x.value else '').join('')
defaultValue
zeroPad: (n) -> if n < 10 then "0#{n}" else n zeroPad: (n) -> if n < 10 then "0#{n}" else n
formatters: formatters:
a: -> Time.day[@getDay()][...3] a: -> Time.localeFormat @, {weekday: 'short'}, Time.day[@getDay()][...3]
A: -> Time.day[@getDay()] A: -> Time.localeFormat @, {weekday: 'long'}, Time.day[@getDay()]
b: -> Time.month[@getMonth()][...3] b: -> Time.localeFormat @, {month: 'short'}, Time.month[@getMonth()][...3]
B: -> Time.month[@getMonth()] B: -> Time.localeFormat @, {month: 'long'}, Time.month[@getMonth()]
d: -> Time.zeroPad @getDate() d: -> Time.zeroPad @getDate()
e: -> @getDate() e: -> @getDate()
H: -> Time.zeroPad @getHours() H: -> Time.zeroPad @getHours()
@ -56,8 +69,8 @@ Time =
l: -> @getHours() % 12 or 12 l: -> @getHours() % 12 or 12
m: -> Time.zeroPad @getMonth() + 1 m: -> Time.zeroPad @getMonth() + 1
M: -> Time.zeroPad @getMinutes() M: -> Time.zeroPad @getMinutes()
p: -> if @getHours() < 12 then 'AM' else 'PM' p: -> Time.localeFormatPart @, {hour: 'numeric', hour12: true}, 'dayperiod', (if @getHours() < 12 then 'AM' else 'PM')
P: -> if @getHours() < 12 then 'am' else 'pm' P: -> Time.formatters.p.call(@).toLowerCase()
S: -> Time.zeroPad @getSeconds() S: -> Time.zeroPad @getSeconds()
y: -> @getFullYear().toString()[2..] y: -> @getFullYear().toString()[2..]
Y: -> @getFullYear() Y: -> @getFullYear()

View File

@ -190,7 +190,6 @@ Unread =
Unread.lastReadPost = ID Unread.lastReadPost = ID
Unread.readCount++ Unread.readCount++
return if Unread.thread.isDead and !Unread.thread.isArchived return if Unread.thread.isDead and !Unread.thread.isArchived
Unread.db.forceSync()
Unread.db.set Unread.db.set
boardID: Unread.thread.board.ID boardID: Unread.thread.board.ID
threadID: Unread.thread.ID threadID: Unread.thread.ID

View File

@ -297,9 +297,20 @@ QR =
return unless QR.postingIsEnabled return unless QR.postingIsEnabled
sel = d.getSelection() sel = d.getSelection()
post = Get.postFromNode @ post = Get.postFromNode @
{root} = post.nodes
postRange = new Range()
postRange.selectNode root
text = if post.board.ID is g.BOARD.ID then ">>#{post}\n" else ">>>/#{post.board}/#{post}\n" text = if post.board.ID is g.BOARD.ID then ">>#{post}\n" else ">>>/#{post.board}/#{post}\n"
if sel.toString().trim() and post is Get.postFromNode sel.anchorNode for i in [0...sel.rangeCount]
range = sel.getRangeAt 0 range = sel.getRangeAt i
# Trim range to be fully inside post
if range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0
range.setStartBefore root
if range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0
range.setEndAfter root
continue unless range.toString().trim()
frag = range.cloneContents() frag = range.cloneContents()
ancestor = range.commonAncestorContainer ancestor = range.commonAncestorContainer
# Quoting the insides of a spoiler/code tag. # Quoting the insides of a spoiler/code tag.
@ -313,10 +324,7 @@ QR =
$.replace node, $.tn '\n' $.replace node, $.tn '\n'
for node in $$ 'br', frag for node in $$ 'br', frag
$.replace node, $.tn '\n>' unless node is frag.lastChild $.replace node, $.tn '\n>' unless node is frag.lastChild
for node in $$ 's, .removed-spoiler', frag Post::insertTags frag
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
for node in $$ '.prettyprint', frag
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
for node in $$ '.linkify[data-original]', frag for node in $$ '.linkify[data-original]', frag
$.replace node, $.tn node.dataset.original $.replace node, $.tn node.dataset.original
for node in $$ '.embedder', frag for node in $$ '.embedder', frag

View File

@ -159,9 +159,9 @@ QR.cooldown =
'' ''
seconds = Math.max seconds, QR.cooldown.delays[type + suffix] - elapsed seconds = Math.max seconds, QR.cooldown.delays[type + suffix] - elapsed
# If additional cooldown is enabled, add the configured seconds to the count. # If additional cooldown is enabled, add the configured seconds to the count.
if QR.cooldown.customCooldown if QR.cooldown.customCooldown
seconds = Math.max seconds, parseInt(Conf['customCooldown'], 10) - elapsed seconds = Math.max seconds, parseInt(Conf['customCooldown'], 10) - elapsed
nCooldowns += Object.keys(cooldowns).length nCooldowns += Object.keys(cooldowns).length

View File

@ -30,7 +30,6 @@ class DataBoard
@save() @save()
deleteIfEmpty: ({boardID, threadID}) -> deleteIfEmpty: ({boardID, threadID}) ->
$.forceSync @key
if threadID if threadID
unless Object.keys(@data.boards[boardID][threadID]).length unless Object.keys(@data.boards[boardID][threadID]).length
delete @data.boards[boardID][threadID] delete @data.boards[boardID][threadID]

View File

@ -5,9 +5,8 @@ class Fetcher
return return
# 4chan X catalog data # 4chan X catalog data
if (post = Index.replyData?["#{@boardID}.#{@postID}"]) if (post = Index.replyData?["#{@boardID}.#{@postID}"]) and (thread = g.threads["#{@boardID}.#{@threadID}"])
board = g.boards[@boardID] board = g.boards[@boardID]
thread = g.threads["#{@boardID}.#{@threadID}"]
post = new Post Build.postFromObject(post, @boardID), thread, board post = new Post Build.postFromObject(post, @boardID), thread, board
post.isFetchedQuote = true post.isFetchedQuote = true
Main.callbackNodes 'Post', [post] Main.callbackNodes 'Post', [post]

View File

@ -129,6 +129,12 @@ class Post
@cleanCommentDisplay bq @cleanCommentDisplay bq
@nodesToText(bq).trim().replace(/\s+$/gm, '') @nodesToText(bq).trim().replace(/\s+$/gm, '')
commentOrig: ->
# Get the comment's text for reposting purposes.
bq = @nodes.commentClean.cloneNode true
@insertTags bq
@nodesToText bq
nodesToText: (bq) -> nodesToText: (bq) ->
text = "" text = ""
nodes = $.X './/br|.//text()', bq nodes = $.X './/br|.//text()', bq
@ -155,6 +161,13 @@ class Post
$.rm b if (b = $ 'b', bq) and /^Rolled /.test(b.textContent) $.rm b if (b = $ 'b', bq) and /^Rolled /.test(b.textContent)
$.rm $('.fortune', bq) $.rm $('.fortune', bq)
insertTags: (bq) ->
for node in $$ 's, .removed-spoiler', bq
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
for node in $$ '.prettyprint', bq
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
return
parseQuotes: -> parseQuotes: ->
@quotes = [] @quotes = []
# XXX https://github.com/4chan/4chan-JS/issues/77 # XXX https://github.com/4chan/4chan-JS/issues/77

View File

@ -857,6 +857,7 @@ Config =
captchaLanguage: '' captchaLanguage: ''
time: '%m/%d/%y(%a)%H:%M:%S' time: '%m/%d/%y(%a)%H:%M:%S'
timeLocale: ''
backlink: '>>%id' backlink: '>>%id'

View File

@ -72,7 +72,7 @@ Main =
items[key] = undefined for key of Conf items[key] = undefined for key of Conf
items['previousversion'] = undefined items['previousversion'] = undefined
($.getSync or $.get) items, (items) -> ($.getSync or $.get) items, (items) ->
if (items['Redirect to HTTPS'] ? Conf['Redirect to HTTPS']) and location.protocol isnt 'https:' if !$.perProtocolSettings and (items['Redirect to HTTPS'] ? Conf['Redirect to HTTPS']) and location.protocol isnt 'https:'
location.replace('https:' + location.host + location.pathname + location.search + location.hash) location.replace('https:' + location.host + location.pathname + location.search + location.hash)
return return
$.asap docSet, -> $.asap docSet, ->

View File

@ -16,11 +16,14 @@
"globals": { "globals": {
"MediaError": false, "MediaError": false,
"Set": false, "Set": false,
"Promise": false,
"BroadcastChannel": false,
"GM_info": false, "GM_info": false,
"cloneInto": false, "cloneInto": false,
"unsafeWindow": false, "unsafeWindow": false,
"chrome": false<%= "chrome": false,
meta.grants.map(x => `,\n "${x}": false`).join('') "GM": false<%=
meta.grants.filter(x => !/\./.test(x)).map(x => `,\n "${x}": false`).join('')
%><%= %><%=
read('/tmp/declaration.js').match(/^var (.*);/)[1].split(', ').map(x => `,\n "${x}": true`).join('') read('/tmp/declaration.js').match(/^var (.*);/)[1].split(', ').map(x => `,\n "${x}": true`).join('')
%><%= %><%=

View File

@ -369,6 +369,13 @@ $.item = (key, val) ->
item[key] = val item[key] = val
item item
$.oneItemSugar = (fn) ->
(key, val, cb) ->
if typeof key is 'string'
fn $.item(key, val), cb
else
fn key, val
$.syncing = {} $.syncing = {}
<% if (type === 'crx') { %> <% if (type === 'crx') { %>
@ -402,14 +409,8 @@ $.crxWorking = ->
$.crxWarningShown = true $.crxWarningShown = true
false false
$.get = (key, val, cb) -> $.get = $.oneItemSugar (data, cb) ->
return unless $.crxWorking() return unless $.crxWorking()
if typeof cb is 'function'
data = $.item key, val
else
data = key
cb = val
results = {} results = {}
get = (area) -> get = (area) ->
chrome.storage[area].get Object.keys(data), (result) -> chrome.storage[area].get Object.keys(data), (result) ->
@ -468,13 +469,8 @@ do ->
setSync = $.debounce $.SECOND, -> setSync = $.debounce $.SECOND, ->
setArea 'sync' setArea 'sync'
$.set = (key, val, cb) -> $.set = $.oneItemSugar (data, cb) ->
return unless $.crxWorking() return unless $.crxWorking()
if typeof key is 'string'
data = $.item key, val
else
data = key
cb = val
$.extend items.local, data $.extend items.local, data
setArea 'local', cb setArea 'local', cb
@ -496,131 +492,167 @@ do ->
# http://wiki.greasespot.net/Main_Page # http://wiki.greasespot.net/Main_Page
# https://tampermonkey.net/documentation.php # https://tampermonkey.net/documentation.php
# workaround for Firefox 53 issue if GM?.deleteValue?
$.currentValue = {}
$.GM_getValue = (key) ->
try
$.currentValue[key] = GM_getValue key
catch err
$.currentValue[key]
$.GM_setValue = (key, val) ->
$.currentValue[key] = val
GM_setValue key, val
$.GM_deleteValue = (key) ->
delete $.currentValue[key]
GM_deleteValue key
if GM_deleteValue? $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync')
$.getValue = $.GM_getValue
$.listValues = -> GM_listValues() # error when called if missing
else if $.hasStorage
$.getValue = (key) -> localStorage[key]
$.listValues = ->
key for key of localStorage when key[...g.NAMESPACE.length] is g.NAMESPACE
else
$.getValue = ->
$.listValues = -> []
if GM_addValueChangeListener? $.on $.syncChannel, 'message', (e) ->
$.setValue = $.GM_setValue for key, val of e.data when (cb = $.syncing[key])
$.deleteValue = $.GM_deleteValue cb val, key
else if GM_deleteValue?
$.oldValue = {}
$.setValue = (key, val) ->
$.GM_setValue key, val
if key of $.syncing
$.oldValue[key] = val
localStorage[key] = val if $.hasStorage # for `storage` events
$.deleteValue = (key) ->
$.GM_deleteValue key
if key of $.syncing
delete $.oldValue[key]
localStorage.removeItem key if $.hasStorage # for `storage` events
$.cantSync = true if !$.hasStorage
else if $.hasStorage
$.oldValue = {}
$.setValue = (key, val) ->
$.oldValue[key] = val if key of $.syncing
localStorage[key] = val
$.deleteValue = (key) ->
delete $.oldValue[key] if key of $.syncing
localStorage.removeItem key
else
$.setValue = ->
$.deleteValue = ->
$.cantSync = $.cantSet = true
if GM_addValueChangeListener?
$.sync = (key, cb) -> $.sync = (key, cb) ->
$.syncing[key] = GM_addValueChangeListener g.NAMESPACE + key, (key2, oldValue, newValue, remote) ->
if remote
newValue = JSON.parse newValue unless newValue is undefined
cb newValue, key
$.forceSync = ->
else if GM_deleteValue? or $.hasStorage
$.sync = (key, cb) ->
key = g.NAMESPACE + key
$.syncing[key] = cb $.syncing[key] = cb
$.oldValue[key] = $.getValue key
do ->
onChange = ({key, newValue}) ->
return if not (cb = $.syncing[key])
if newValue?
return if newValue is $.oldValue[key]
$.oldValue[key] = newValue
cb JSON.parse(newValue), key[g.NAMESPACE.length..]
else
return unless $.oldValue[key]?
delete $.oldValue[key]
cb undefined, key[g.NAMESPACE.length..]
$.on window, 'storage', onChange
$.forceSync = (key) ->
# Storage events don't work across origins
# e.g. http://boards.4chan.org and https://boards.4chan.org
# so force a check for changes to avoid lost data.
key = g.NAMESPACE + key
onChange {key, newValue: $.getValue key}
else
$.sync = ->
$.forceSync = -> $.forceSync = ->
$.delete = (keys) -> $.delete = (keys, cb) ->
unless keys instanceof Array unless keys instanceof Array
keys = [keys] keys = [keys]
for key in keys Promise.all(GM.deleteValue(g.NAMESPACE + key) for key in keys).then ->
$.deleteValue g.NAMESPACE + key items = {}
return items[key] = undefined for key in keys
$.syncChannel.postMessage items
cb?()
$.get = (key, val, cb) -> $.get = $.oneItemSugar (items, cb) ->
if typeof cb is 'function' keys = Object.keys items
items = $.item key, val Promise.all(GM.getValue(g.NAMESPACE + key) for key in keys).then (values) ->
for val, i in values when val
items[keys[i]] = JSON.parse val
cb items
$.set = $.oneItemSugar (items, cb) ->
Promise.all(GM.setValue(g.NAMESPACE + key, JSON.stringify(val)) for key, val of items).then ->
$.syncChannel.postMessage items
cb?()
$.clear = (cb) ->
GM.listValues.then (keys) ->
$.delete keys.map((key) -> key.replace g.NAMESPACE, ''), cb
else
# workaround for Firefox 53 issue
$.currentValue = {}
$.GM_getValue = (key) ->
try
$.currentValue[key] = GM_getValue key
catch err
$.currentValue[key]
$.GM_setValue = (key, val) ->
$.currentValue[key] = val
GM_setValue key, val
$.GM_deleteValue = (key) ->
delete $.currentValue[key]
GM_deleteValue key
unless GM_deleteValue?
$.perProtocolSettings = true
if GM_deleteValue?
$.getValue = $.GM_getValue
$.listValues = -> GM_listValues() # error when called if missing
else if $.hasStorage
$.getValue = (key) -> localStorage[key]
$.listValues = ->
key for key of localStorage when key[...g.NAMESPACE.length] is g.NAMESPACE
else else
items = key $.getValue = ->
cb = val $.listValues = -> []
$.queueTask $.getSync, items, cb
$.getSync = (items, cb) -> if GM_addValueChangeListener?
for key of items when (val2 = $.getValue g.NAMESPACE + key) $.setValue = $.GM_setValue
items[key] = JSON.parse val2 $.deleteValue = $.GM_deleteValue
cb items else if GM_deleteValue?
$.oldValue = {}
$.set = (keys, val, cb) -> $.setValue = (key, val) ->
if typeof keys is 'string' $.GM_setValue key, val
$.setValue(g.NAMESPACE + keys, JSON.stringify val) if key of $.syncing
$.oldValue[key] = val
localStorage[key] = val if $.hasStorage # for `storage` events
$.deleteValue = (key) ->
$.GM_deleteValue key
if key of $.syncing
delete $.oldValue[key]
localStorage.removeItem key if $.hasStorage # for `storage` events
$.cantSync = true if !$.hasStorage
else if $.hasStorage
$.oldValue = {}
$.setValue = (key, val) ->
$.oldValue[key] = val if key of $.syncing
localStorage[key] = val
$.deleteValue = (key) ->
delete $.oldValue[key] if key of $.syncing
localStorage.removeItem key
else else
for key, value of keys $.setValue = ->
$.setValue(g.NAMESPACE + key, JSON.stringify value) $.deleteValue = ->
cb = val $.cantSync = $.cantSet = true
cb?()
if GM_addValueChangeListener?
$.sync = (key, cb) ->
$.syncing[key] = GM_addValueChangeListener g.NAMESPACE + key, (key2, oldValue, newValue, remote) ->
if remote
newValue = JSON.parse newValue unless newValue is undefined
cb newValue, key
$.forceSync = ->
else if GM_deleteValue? or $.hasStorage
$.sync = (key, cb) ->
key = g.NAMESPACE + key
$.syncing[key] = cb
$.oldValue[key] = $.getValue key
do ->
onChange = ({key, newValue}) ->
return if not (cb = $.syncing[key])
if newValue?
return if newValue is $.oldValue[key]
$.oldValue[key] = newValue
cb JSON.parse(newValue), key[g.NAMESPACE.length..]
else
return unless $.oldValue[key]?
delete $.oldValue[key]
cb undefined, key[g.NAMESPACE.length..]
$.on window, 'storage', onChange
$.forceSync = (key) ->
# Storage events don't work across origins
# e.g. http://boards.4chan.org and https://boards.4chan.org
# so force a check for changes to avoid lost data.
key = g.NAMESPACE + key
onChange {key, newValue: $.getValue key}
else
$.sync = ->
$.forceSync = ->
$.delete = (keys) ->
unless keys instanceof Array
keys = [keys]
for key in keys
$.deleteValue g.NAMESPACE + key
return
$.get = $.oneItemSugar (items, cb) ->
$.queueTask $.getSync, items, cb
$.getSync = (items, cb) ->
for key of items when (val2 = $.getValue g.NAMESPACE + key)
items[key] = JSON.parse val2
cb items
$.set = $.oneItemSugar (items, cb) ->
$.queueTask ->
for key, value of items
$.setValue(g.NAMESPACE + key, JSON.stringify value)
cb?()
$.clear = (cb) ->
# XXX https://github.com/greasemonkey/greasemonkey/issues/2033
# Also support case where GM_listValues is not defined.
$.delete Object.keys(Conf)
$.delete ['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']
try
$.delete $.listValues().map (key) -> key.replace g.NAMESPACE, ''
cb?()
$.clear = (cb) ->
# XXX https://github.com/greasemonkey/greasemonkey/issues/2033
# Also support case where GM_listValues is not defined.
$.delete Object.keys(Conf)
$.delete ['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']
try
$.delete $.listValues().map (key) -> key.replace g.NAMESPACE, ''
cb?()
<% } %> <% } %>

View File

@ -63,7 +63,7 @@ CrossOrigin =
options.overrideMimeType = 'text/plain; charset=x-user-defined' options.overrideMimeType = 'text/plain; charset=x-user-defined'
else else
options.responseType = 'arraybuffer' options.responseType = 'arraybuffer'
GM_xmlhttpRequest options (GM?.xmlHttpRequest or GM_xmlhttpRequest) options
<% } %> <% } %>
file: (url, cb) -> file: (url, cb) ->
@ -99,7 +99,7 @@ CrossOrigin =
return return
callbacks[url] = [cb] callbacks[url] = [cb]
<% if (type === 'userscript') { %> <% if (type === 'userscript') { %>
GM_xmlhttpRequest (GM?.xmlHttpRequest or GM_xmlhttpRequest)
method: "GET" method: "GET"
url: url+'' url: url+''
onload: (xhr) -> onload: (xhr) ->

View File

@ -1,4 +1,4 @@
{ {
"version": "1.13.10.5", "version": "1.13.12.1",
"date": "2017-08-04T04:26:25.809Z" "date": "2017-09-29T00:42:32.947Z"
} }