Merge branch 'master' into webext
This commit is contained in:
commit
3e019cc116
39
CHANGELOG.md
39
CHANGELOG.md
@ -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).
|
||||
|
||||
### 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.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)]
|
||||
- Better parsing of archive links for Quote Inlining / Hover.
|
||||
- Add Board Tips.
|
||||
|
||||
13
README.md
13
README.md
@ -10,21 +10,12 @@ https://github.com/Nebukazar/OneeChan.
|
||||
## 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.
|
||||
|
||||
**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.
|
||||
|
||||
**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).
|
||||
**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.
|
||||
|
||||
## Install
|
||||
|
||||
### 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:
|
||||
|
||||
- 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)**.
|
||||
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)**.
|
||||
|
||||
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.
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X beta
|
||||
// @version 1.13.10.5
|
||||
// @version 1.13.12.1
|
||||
// @minGMVer 1.14
|
||||
// @minFFVer 26
|
||||
// @namespace 4chan-X
|
||||
@ -48,6 +48,11 @@
|
||||
// @grant GM_addValueChangeListener
|
||||
// @grant GM_openInTab
|
||||
// @grant GM_xmlhttpRequest
|
||||
// @grant GM.getValue
|
||||
// @grant GM.setValue
|
||||
// @grant GM.deleteValue
|
||||
// @grant GM.listValues
|
||||
// @grant GM.xmlHttpRequest
|
||||
// @run-at document-start
|
||||
// @updateURL https://www.4chan-x.net/builds/4chan-X-beta.meta.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.
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X
|
||||
// @version 1.13.10.5
|
||||
// @version 1.13.12.1
|
||||
// @minGMVer 1.14
|
||||
// @minFFVer 26
|
||||
// @namespace 4chan-X
|
||||
@ -48,6 +48,11 @@
|
||||
// @grant GM_addValueChangeListener
|
||||
// @grant GM_openInTab
|
||||
// @grant GM_xmlhttpRequest
|
||||
// @grant GM.getValue
|
||||
// @grant GM.setValue
|
||||
// @grant GM.deleteValue
|
||||
// @grant GM.listValues
|
||||
// @grant GM.xmlHttpRequest
|
||||
// @run-at document-start
|
||||
// @updateURL https://www.4chan-x.net/builds/4chan-X.meta.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.
@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
||||
<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>
|
||||
</gupdate>
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
||||
<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>
|
||||
</gupdate>
|
||||
|
||||
|
||||
@ -23,8 +23,7 @@
|
||||
<a href="https://github.com/Nebukazar/OneeChan">https://github.com/Nebukazar/OneeChan</a>.</p>
|
||||
<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 "<code>Disable the native extension</code>" in the panel that appears, and click the "<code>Save Settings</code>" button. If you don't see a "<code>Save Settings</code>" 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>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>
|
||||
<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>
|
||||
<h2 id="install">Install</h2>
|
||||
<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>
|
||||
|
||||
@ -57,7 +57,12 @@
|
||||
"GM_listValues",
|
||||
"GM_addValueChangeListener",
|
||||
"GM_openInTab",
|
||||
"GM_xmlhttpRequest"
|
||||
"GM_xmlhttpRequest",
|
||||
"GM.getValue",
|
||||
"GM.setValue",
|
||||
"GM.deleteValue",
|
||||
"GM.listValues",
|
||||
"GM.xmlHttpRequest"
|
||||
],
|
||||
"min": {
|
||||
"chrome": "33",
|
||||
|
||||
@ -114,16 +114,6 @@
|
||||
"boards": ["h", "hc", "hm", "r", "s", "soc"],
|
||||
"files": ["h", "hc", "hm", "r", "s", "soc"],
|
||||
"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,
|
||||
"name": "YEET Archive",
|
||||
|
||||
@ -688,6 +688,9 @@ Index =
|
||||
else
|
||||
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) ->
|
||||
threads = []
|
||||
newThreads = []
|
||||
@ -788,8 +791,7 @@ Index =
|
||||
|
||||
replies = []
|
||||
for data in lastReplies
|
||||
continue if PostHiding.isHidden(g.BOARD.ID, thread.ID, data.no)
|
||||
continue if Filter.isHidden(Build.parseJSON data, g.BOARD.ID)
|
||||
continue if Index.isHiddenReply thread.ID, data
|
||||
reply = Build.catalogReply thread, data
|
||||
RelativeDates.update $('time', reply)
|
||||
$.on $('.catalog-reply-preview', reply), 'mouseover', QuotePreview.mouseover
|
||||
@ -813,12 +815,16 @@ Index =
|
||||
when 'lastlong'
|
||||
lastlong = (thread) ->
|
||||
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
|
||||
if len >= Index.lastLongThresholds[+!!r.ext]
|
||||
return r
|
||||
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) ->
|
||||
lastlong(b).no - lastlong(a).no
|
||||
lastlongD[b.no] - lastlongD[a.no]
|
||||
).map (post) -> post.no
|
||||
when 'bump' then liveThreadIDs
|
||||
when 'birth' then [liveThreadIDs... ].sort (a, b) -> b - a
|
||||
|
||||
@ -162,8 +162,12 @@ Settings =
|
||||
addCheckboxes fs, obj
|
||||
$.add section, fs
|
||||
addCheckboxes $('div[data-name="JSON Index"] > .suboption-list', section), Config.Index
|
||||
|
||||
# Unsupported options
|
||||
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) ->
|
||||
for key, val of items
|
||||
@ -489,7 +493,7 @@ Settings =
|
||||
$.id('lastarchivecheck').textContent = 'never'
|
||||
|
||||
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]
|
||||
input = inputs[name]
|
||||
event = if name in ['archiveLists', 'archiveAutoUpdate', 'QR.personas', 'favicon', 'usercss'] then 'change' else 'input'
|
||||
@ -642,6 +646,9 @@ Settings =
|
||||
time: ->
|
||||
@nextElementSibling.textContent = Time.format @value, new Date()
|
||||
|
||||
timeLocale: ->
|
||||
Settings.time.call $('[name=time]', Settings.dialog)
|
||||
|
||||
backlink: ->
|
||||
@nextElementSibling.textContent = @value.replace /%(?:id|%)/g, (x) -> ({'%id': '123456789', '%%': '%'})[x]
|
||||
|
||||
|
||||
@ -64,6 +64,7 @@
|
||||
<div>Minute: <code>%M</code></div>
|
||||
<div>Second: <code>%S</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>
|
||||
|
||||
@ -12,7 +12,7 @@ CopyTextLink =
|
||||
el: a
|
||||
order: 12
|
||||
open: (post) ->
|
||||
CopyTextLink.text = post.info.comment
|
||||
CopyTextLink.text = (post.origin or post).commentOrig()
|
||||
true
|
||||
|
||||
copy: ->
|
||||
|
||||
@ -5,7 +5,7 @@ BoardTips =
|
||||
<%= html(
|
||||
'New to /qa/?<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>.'
|
||||
) %>
|
||||
]
|
||||
|
||||
@ -312,7 +312,7 @@ Keybinds =
|
||||
return if g.VIEW isnt 'index'
|
||||
url = "/#{thread.board}/thread/#{thread}"
|
||||
if tab
|
||||
$.open url
|
||||
$.open location.origin + url
|
||||
else
|
||||
location.href = url
|
||||
|
||||
|
||||
@ -41,13 +41,26 @@ Time =
|
||||
'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
|
||||
|
||||
formatters:
|
||||
a: -> Time.day[@getDay()][...3]
|
||||
A: -> Time.day[@getDay()]
|
||||
b: -> Time.month[@getMonth()][...3]
|
||||
B: -> Time.month[@getMonth()]
|
||||
a: -> Time.localeFormat @, {weekday: 'short'}, Time.day[@getDay()][...3]
|
||||
A: -> Time.localeFormat @, {weekday: 'long'}, Time.day[@getDay()]
|
||||
b: -> Time.localeFormat @, {month: 'short'}, Time.month[@getMonth()][...3]
|
||||
B: -> Time.localeFormat @, {month: 'long'}, Time.month[@getMonth()]
|
||||
d: -> Time.zeroPad @getDate()
|
||||
e: -> @getDate()
|
||||
H: -> Time.zeroPad @getHours()
|
||||
@ -56,8 +69,8 @@ Time =
|
||||
l: -> @getHours() % 12 or 12
|
||||
m: -> Time.zeroPad @getMonth() + 1
|
||||
M: -> Time.zeroPad @getMinutes()
|
||||
p: -> if @getHours() < 12 then 'AM' else 'PM'
|
||||
P: -> if @getHours() < 12 then 'am' else 'pm'
|
||||
p: -> Time.localeFormatPart @, {hour: 'numeric', hour12: true}, 'dayperiod', (if @getHours() < 12 then 'AM' else 'PM')
|
||||
P: -> Time.formatters.p.call(@).toLowerCase()
|
||||
S: -> Time.zeroPad @getSeconds()
|
||||
y: -> @getFullYear().toString()[2..]
|
||||
Y: -> @getFullYear()
|
||||
|
||||
@ -190,7 +190,6 @@ Unread =
|
||||
Unread.lastReadPost = ID
|
||||
Unread.readCount++
|
||||
return if Unread.thread.isDead and !Unread.thread.isArchived
|
||||
Unread.db.forceSync()
|
||||
Unread.db.set
|
||||
boardID: Unread.thread.board.ID
|
||||
threadID: Unread.thread.ID
|
||||
|
||||
@ -297,9 +297,20 @@ QR =
|
||||
return unless QR.postingIsEnabled
|
||||
sel = d.getSelection()
|
||||
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"
|
||||
if sel.toString().trim() and post is Get.postFromNode sel.anchorNode
|
||||
range = sel.getRangeAt 0
|
||||
for i in [0...sel.rangeCount]
|
||||
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()
|
||||
ancestor = range.commonAncestorContainer
|
||||
# Quoting the insides of a spoiler/code tag.
|
||||
@ -313,10 +324,7 @@ QR =
|
||||
$.replace node, $.tn '\n'
|
||||
for node in $$ 'br', frag
|
||||
$.replace node, $.tn '\n>' unless node is frag.lastChild
|
||||
for node in $$ 's, .removed-spoiler', frag
|
||||
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
|
||||
for node in $$ '.prettyprint', frag
|
||||
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
|
||||
Post::insertTags frag
|
||||
for node in $$ '.linkify[data-original]', frag
|
||||
$.replace node, $.tn node.dataset.original
|
||||
for node in $$ '.embedder', frag
|
||||
|
||||
@ -159,9 +159,9 @@ QR.cooldown =
|
||||
''
|
||||
seconds = Math.max seconds, QR.cooldown.delays[type + suffix] - elapsed
|
||||
|
||||
# If additional cooldown is enabled, add the configured seconds to the count.
|
||||
if QR.cooldown.customCooldown
|
||||
seconds = Math.max seconds, parseInt(Conf['customCooldown'], 10) - elapsed
|
||||
# If additional cooldown is enabled, add the configured seconds to the count.
|
||||
if QR.cooldown.customCooldown
|
||||
seconds = Math.max seconds, parseInt(Conf['customCooldown'], 10) - elapsed
|
||||
|
||||
nCooldowns += Object.keys(cooldowns).length
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ class DataBoard
|
||||
@save()
|
||||
|
||||
deleteIfEmpty: ({boardID, threadID}) ->
|
||||
$.forceSync @key
|
||||
if threadID
|
||||
unless Object.keys(@data.boards[boardID][threadID]).length
|
||||
delete @data.boards[boardID][threadID]
|
||||
|
||||
@ -5,9 +5,8 @@ class Fetcher
|
||||
return
|
||||
|
||||
# 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]
|
||||
thread = g.threads["#{@boardID}.#{@threadID}"]
|
||||
post = new Post Build.postFromObject(post, @boardID), thread, board
|
||||
post.isFetchedQuote = true
|
||||
Main.callbackNodes 'Post', [post]
|
||||
|
||||
@ -129,6 +129,12 @@ class Post
|
||||
@cleanCommentDisplay bq
|
||||
@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) ->
|
||||
text = ""
|
||||
nodes = $.X './/br|.//text()', bq
|
||||
@ -155,6 +161,13 @@ class Post
|
||||
$.rm b if (b = $ 'b', bq) and /^Rolled /.test(b.textContent)
|
||||
$.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: ->
|
||||
@quotes = []
|
||||
# XXX https://github.com/4chan/4chan-JS/issues/77
|
||||
|
||||
@ -857,6 +857,7 @@ Config =
|
||||
captchaLanguage: ''
|
||||
|
||||
time: '%m/%d/%y(%a)%H:%M:%S'
|
||||
timeLocale: ''
|
||||
|
||||
backlink: '>>%id'
|
||||
|
||||
|
||||
@ -72,7 +72,7 @@ Main =
|
||||
items[key] = undefined for key of Conf
|
||||
items['previousversion'] = undefined
|
||||
($.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)
|
||||
return
|
||||
$.asap docSet, ->
|
||||
|
||||
@ -16,11 +16,14 @@
|
||||
"globals": {
|
||||
"MediaError": false,
|
||||
"Set": false,
|
||||
"Promise": false,
|
||||
"BroadcastChannel": false,
|
||||
"GM_info": false,
|
||||
"cloneInto": false,
|
||||
"unsafeWindow": false,
|
||||
"chrome": false<%=
|
||||
meta.grants.map(x => `,\n "${x}": false`).join('')
|
||||
"chrome": false,
|
||||
"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('')
|
||||
%><%=
|
||||
|
||||
@ -369,6 +369,13 @@ $.item = (key, val) ->
|
||||
item[key] = val
|
||||
item
|
||||
|
||||
$.oneItemSugar = (fn) ->
|
||||
(key, val, cb) ->
|
||||
if typeof key is 'string'
|
||||
fn $.item(key, val), cb
|
||||
else
|
||||
fn key, val
|
||||
|
||||
$.syncing = {}
|
||||
|
||||
<% if (type === 'crx') { %>
|
||||
@ -402,14 +409,8 @@ $.crxWorking = ->
|
||||
$.crxWarningShown = true
|
||||
false
|
||||
|
||||
$.get = (key, val, cb) ->
|
||||
$.get = $.oneItemSugar (data, cb) ->
|
||||
return unless $.crxWorking()
|
||||
if typeof cb is 'function'
|
||||
data = $.item key, val
|
||||
else
|
||||
data = key
|
||||
cb = val
|
||||
|
||||
results = {}
|
||||
get = (area) ->
|
||||
chrome.storage[area].get Object.keys(data), (result) ->
|
||||
@ -468,13 +469,8 @@ do ->
|
||||
setSync = $.debounce $.SECOND, ->
|
||||
setArea 'sync'
|
||||
|
||||
$.set = (key, val, cb) ->
|
||||
$.set = $.oneItemSugar (data, cb) ->
|
||||
return unless $.crxWorking()
|
||||
if typeof key is 'string'
|
||||
data = $.item key, val
|
||||
else
|
||||
data = key
|
||||
cb = val
|
||||
$.extend items.local, data
|
||||
setArea 'local', cb
|
||||
|
||||
@ -496,131 +492,167 @@ do ->
|
||||
# http://wiki.greasespot.net/Main_Page
|
||||
# https://tampermonkey.net/documentation.php
|
||||
|
||||
# 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
|
||||
if GM?.deleteValue?
|
||||
|
||||
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
|
||||
$.getValue = ->
|
||||
$.listValues = -> []
|
||||
$.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync')
|
||||
|
||||
if GM_addValueChangeListener?
|
||||
$.setValue = $.GM_setValue
|
||||
$.deleteValue = $.GM_deleteValue
|
||||
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
|
||||
$.on $.syncChannel, 'message', (e) ->
|
||||
for key, val of e.data when (cb = $.syncing[key])
|
||||
cb val, key
|
||||
|
||||
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
|
||||
$.delete = (keys, cb) ->
|
||||
unless keys instanceof Array
|
||||
keys = [keys]
|
||||
Promise.all(GM.deleteValue(g.NAMESPACE + key) for key in keys).then ->
|
||||
items = {}
|
||||
items[key] = undefined for key in keys
|
||||
$.syncChannel.postMessage items
|
||||
cb?()
|
||||
|
||||
$.get = (key, val, cb) ->
|
||||
if typeof cb is 'function'
|
||||
items = $.item key, val
|
||||
$.get = $.oneItemSugar (items, cb) ->
|
||||
keys = Object.keys items
|
||||
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
|
||||
items = key
|
||||
cb = val
|
||||
$.queueTask $.getSync, items, cb
|
||||
$.getValue = ->
|
||||
$.listValues = -> []
|
||||
|
||||
$.getSync = (items, cb) ->
|
||||
for key of items when (val2 = $.getValue g.NAMESPACE + key)
|
||||
items[key] = JSON.parse val2
|
||||
cb items
|
||||
|
||||
$.set = (keys, val, cb) ->
|
||||
if typeof keys is 'string'
|
||||
$.setValue(g.NAMESPACE + keys, JSON.stringify val)
|
||||
if GM_addValueChangeListener?
|
||||
$.setValue = $.GM_setValue
|
||||
$.deleteValue = $.GM_deleteValue
|
||||
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
|
||||
for key, value of keys
|
||||
$.setValue(g.NAMESPACE + key, JSON.stringify value)
|
||||
cb = val
|
||||
cb?()
|
||||
$.setValue = ->
|
||||
$.deleteValue = ->
|
||||
$.cantSync = $.cantSet = true
|
||||
|
||||
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?()
|
||||
<% } %>
|
||||
|
||||
@ -63,7 +63,7 @@ CrossOrigin =
|
||||
options.overrideMimeType = 'text/plain; charset=x-user-defined'
|
||||
else
|
||||
options.responseType = 'arraybuffer'
|
||||
GM_xmlhttpRequest options
|
||||
(GM?.xmlHttpRequest or GM_xmlhttpRequest) options
|
||||
<% } %>
|
||||
|
||||
file: (url, cb) ->
|
||||
@ -99,7 +99,7 @@ CrossOrigin =
|
||||
return
|
||||
callbacks[url] = [cb]
|
||||
<% if (type === 'userscript') { %>
|
||||
GM_xmlhttpRequest
|
||||
(GM?.xmlHttpRequest or GM_xmlhttpRequest)
|
||||
method: "GET"
|
||||
url: url+''
|
||||
onload: (xhr) ->
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "1.13.10.5",
|
||||
"date": "2017-08-04T04:26:25.809Z"
|
||||
"version": "1.13.12.1",
|
||||
"date": "2017-09-29T00:42:32.947Z"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user