Merge branch 'master' into postjumper
This commit is contained in:
commit
1b9378e809
81
CHANGELOG.md
81
CHANGELOG.md
@ -2,8 +2,89 @@
|
|||||||
|
|
||||||
-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.14.7
|
||||||
|
|
||||||
|
**v1.14.7.2** *(2019-04-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.2/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Fix dragging left to contract WebMs in Firefox. #1547
|
||||||
|
- Remove query string from filename in Post from URL feature.
|
||||||
|
- Speed up Post from URL on some platforms.
|
||||||
|
- Fix issue making WebM title fetching needlessly slow on Chrome extension.
|
||||||
|
|
||||||
|
**v1.14.7.1** *(2019-04-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.1/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Tolerate broken HTML better.
|
||||||
|
- Fix 4chan/4channel not being correct in certain links.
|
||||||
|
- Use boards.json to determine whether to activate [code] and [math] tag related functions. #525
|
||||||
|
|
||||||
|
**v1.14.7.0** *(2019-04-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.0/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Based on v1.14.6.8.
|
||||||
|
- (Teasday) Hotkey to toggle quote threading, `Shift+t` by default.
|
||||||
|
- Show what pages watched threads are on. Can be disabled by unchecking `Show Page` in the thread watcher menu. #1030
|
||||||
|
- Move Thread Watcher settings out of submenu.
|
||||||
|
- Restore filtering on the email field. #2171
|
||||||
|
- Support specifying the sites that filters apply to. #2171
|
||||||
|
- Make per-board filtering work on boards with unusual characters in the name (e.g. certain lainchan boards).
|
||||||
|
- Board names in filters are now case-sensitive.
|
||||||
|
|
||||||
|
### v1.14.6
|
||||||
|
|
||||||
|
**v1.14.6.8** *(2019-04-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.8/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Update list of boards on https://catalog.neet.tv/.
|
||||||
|
|
||||||
|
**v1.14.6.7** *(2019-04-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.7/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Update .crx files to CRX3. This should fix the errors when attempting to install them on newer versions of Chromium.
|
||||||
|
|
||||||
|
**v1.14.6.6** *(2019-04-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.6/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Sauce: Update DeviantArt filename format. #2237
|
||||||
|
- Sauce: Replace unmatched regex groups with empty string, not 'undefined'
|
||||||
|
- Whether to add parameter to avoid cache should be based on site being queried, not site currenly on.
|
||||||
|
|
||||||
|
**v1.14.6.5** *(2019-04-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.5/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Fix Thread Watcher bug that in certain circumstances caused the last check of an archived thread for new replies to be skipped.
|
||||||
|
|
||||||
|
**v1.14.6.4** *(2019-04-02)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.4/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Merge v1.14.5.16: Remove score/perks message. Fix Posting Success Notifications.
|
||||||
|
- Merge v1.14.5.16: Remove like buttons. Continue to show like counts and scores where given in API.
|
||||||
|
- Bugfix: Account for posts added by thread expansion when marking read from index.
|
||||||
|
|
||||||
|
**v1.14.6.3** *(2019-04-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.3/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Merge v1.14.5.15: Show info relating to April 2019 event. #2266
|
||||||
|
|
||||||
|
**v1.14.6.2** *(2019-03-31)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.2/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Support filters that apply to multiple post fields joined by newline characters.
|
||||||
|
|
||||||
|
**v1.14.6.1** *(2019-03-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.1/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Fix errors in certain userscript managers introduced in v1.14.6.0. #2256
|
||||||
|
|
||||||
|
**v1.14.6.0** *(2019-03-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.6.0/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Based on v1.14.5.14.
|
||||||
|
- (ebinBuddha) Added desktop notification for filters (`notify` option).
|
||||||
|
- Make it possible to filter posts without ID (use `//`). #1578
|
||||||
|
- Add `file` option to filter only posts with/without files.
|
||||||
|
- Improvements in Thread Watcher efficiency, particularly when using it with multiple sites.
|
||||||
|
- Allow image hover previews to use full width of screen even in cases where it covers the thumbnail.
|
||||||
|
- Make movement of image hover / quote preview with mouse optional; option is `Follow Cursor`. #471, #2245
|
||||||
|
- Fix image/video hover in case where dimensions are not available. #2197
|
||||||
|
- Implement pruning of data for dead threads on vichan sites with JSON API. #2171
|
||||||
|
- Override 4chan CSS causing sauce links to get cut off. #2193
|
||||||
|
- Change export URL from data: to blob: so larger settings files can be exported. #2255
|
||||||
|
- Unbreak warning in Chrome extension to reload the page after an update.
|
||||||
|
- Various minor bugfixes.
|
||||||
|
|
||||||
### v1.14.5
|
### v1.14.5
|
||||||
|
|
||||||
|
**v1.14.5.16** *(2019-04-02)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.16/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.16/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Remove score/perks message. Fix Posting Success Notifications.
|
||||||
|
- Remove like buttons. Continue to show like counts and scores where given in API.
|
||||||
|
|
||||||
|
**v1.14.5.15** *(2019-04-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.15/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.15/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Show info relating to April 2019 event. #2266
|
||||||
|
- Override 4chan CSS causing sauce links to get cut off. #2193
|
||||||
|
- Unbreak warning in Chrome extension to reload the page after an update.
|
||||||
|
|
||||||
|
**v1.14.5.14** *(2019-03-22)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.14/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.14/builds/4chan-X-noupdate.crx)]
|
||||||
|
- Add message alerting Chrome extension users to disable chrome://flags/#network-service
|
||||||
|
- Minor bugfix in catalog/index loading.
|
||||||
|
|
||||||
**v1.14.5.13** *(2019-03-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.13/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.13/builds/4chan-X-noupdate.crx)]
|
**v1.14.5.13** *(2019-03-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.13/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.13/builds/4chan-X-noupdate.crx)]
|
||||||
- Fix bugs related to additional permissions requests. #2230
|
- Fix bugs related to additional permissions requests. #2230
|
||||||
- Revert changes in thread watcher that caused performance decrease.
|
- Revert changes in thread watcher that caused performance decrease.
|
||||||
|
|||||||
@ -30,7 +30,8 @@ If you're reporting a bug, the more detail you can give, the better. If I can't
|
|||||||
|
|
||||||
- 4chan X is mostly written in [CoffeeScript](http://coffeescript.org/). If you're already familiar with Javascript, it doesn't take long to pick up.
|
- 4chan X is mostly written in [CoffeeScript](http://coffeescript.org/). If you're already familiar with Javascript, it doesn't take long to pick up.
|
||||||
- Edit the sources in the src/ directory (not the compiled scripts in builds/).
|
- Edit the sources in the src/ directory (not the compiled scripts in builds/).
|
||||||
- Compile the script with: `make` (this should fetch needed dependencies automatically; if not, do an `npm install` first)
|
- Fetch needed dependencies with: `npm install`
|
||||||
|
- Compile the script with: `make`
|
||||||
- Install the compiled script (found in the testbuilds/ directory), and test your changes.
|
- Install the compiled script (found in the testbuilds/ directory), and test your changes.
|
||||||
- Make sure you have set your name and email as you want them, as they will be published in your commit message:<br>`git config user.name yourname`<br>`git config user.email youremail`
|
- Make sure you have set your name and email as you want them, as they will be published in your commit message:<br>`git config user.name yourname`<br>`git config user.email youremail`
|
||||||
- Commit your changes: `git commit -a`
|
- Commit your changes: `git commit -a`
|
||||||
|
|||||||
65
Makefile
65
Makefile
@ -15,17 +15,16 @@ else
|
|||||||
endif
|
endif
|
||||||
CP = $(call CAT,$<,$@)
|
CP = $(call CAT,$<,$@)
|
||||||
|
|
||||||
npgoals := clean cleanrel cleanweb cleanfull withtests wrapped archives $(foreach i,1 2 3 4,bump$(i)) tag tagcommit beta stable web update updatehard
|
npgoals := clean cleanrel cleanweb cleanfull withtests archives $(foreach i,1 2 3 4,bump$(i)) tag tagcommit beta stable web update updatehard
|
||||||
ifneq "$(filter $(npgoals),$(MAKECMDGOALS))" ""
|
ifneq "$(filter $(npgoals),$(MAKECMDGOALS))" ""
|
||||||
.NOTPARALLEL :
|
.NOTPARALLEL :
|
||||||
endif
|
endif
|
||||||
|
|
||||||
coffee := $(BIN)coffee -c --no-header
|
coffee := $(BIN)coffee -c --no-header
|
||||||
coffee_deps := node_modules/coffee-script/package.json
|
|
||||||
template := node tools/template.js
|
template := node tools/template.js
|
||||||
template_deps := package.json tools/template.js node_modules/lodash.template/package.json node_modules/esprima/package.json
|
template_deps := package.json tools/template.js
|
||||||
|
|
||||||
# read name meta_name meta_distBranch meta_uploadPath
|
# read name meta_name meta_distBranch
|
||||||
$(eval $(shell node tools/pkgvars.js))
|
$(eval $(shell node tools/pkgvars.js))
|
||||||
|
|
||||||
# must be read in when needed to prevent out-of-date version
|
# must be read in when needed to prevent out-of-date version
|
||||||
@ -55,7 +54,7 @@ uses_tests_enabled := \
|
|||||||
imports_src/globals/globals.js := \
|
imports_src/globals/globals.js := \
|
||||||
version.json
|
version.json
|
||||||
imports_src/css/CSS.js := \
|
imports_src/css/CSS.js := \
|
||||||
node_modules/font-awesome/package.json
|
node_modules/font-awesome/fonts/fontawesome-webfont.woff
|
||||||
imports_src/Monitoring/Favicon.coffee := \
|
imports_src/Monitoring/Favicon.coffee := \
|
||||||
src/meta/icon128.png
|
src/meta/icon128.png
|
||||||
|
|
||||||
@ -104,22 +103,6 @@ all : default release
|
|||||||
.events .events2 tmp testbuilds builds :
|
.events .events2 tmp testbuilds builds :
|
||||||
$(MKDIR)
|
$(MKDIR)
|
||||||
|
|
||||||
ifneq "$(wildcard npm-shrinkwrap.json)" ""
|
|
||||||
|
|
||||||
.events/npm : npm-shrinkwrap.json | .events
|
|
||||||
npm install
|
|
||||||
echo -> $@
|
|
||||||
|
|
||||||
node_modules/%/package.json : .events/npm
|
|
||||||
$(if $(wildcard $@),,npm install && echo -> $<)
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
node_modules/%/package.json : package.json
|
|
||||||
npm install $(call QUOTE,$*@$(version_$*))
|
|
||||||
|
|
||||||
endif
|
|
||||||
|
|
||||||
.tests_enabled :
|
.tests_enabled :
|
||||||
echo false> .tests_enabled
|
echo false> .tests_enabled
|
||||||
|
|
||||||
@ -137,7 +120,7 @@ endef
|
|||||||
|
|
||||||
$(foreach s,$(sources),$(eval $(call check_source,$(subst $$,$$$$,$(s)))))
|
$(foreach s,$(sources),$(eval $(call check_source,$(subst $$,$$$$,$(s)))))
|
||||||
|
|
||||||
.events/compile : $(updates) $(template_deps) $(coffee_deps) tools/chain.js
|
.events/compile : $(updates) $(template_deps) tools/chain.js
|
||||||
node tools/chain.js $(call QUOTE, \
|
node tools/chain.js $(call QUOTE, \
|
||||||
$(subst .events/,tmp/, \
|
$(subst .events/,tmp/, \
|
||||||
$(if $(filter-out $(updates),$?), \
|
$(if $(filter-out $(updates),$?), \
|
||||||
@ -154,7 +137,7 @@ $(dests) : .events/compile
|
|||||||
&& echo -> $< \
|
&& echo -> $< \
|
||||||
)
|
)
|
||||||
|
|
||||||
tmp/eventPage.js : src/meta/eventPage.coffee $(coffee_deps) | tmp
|
tmp/eventPage.js : src/meta/eventPage.coffee | tmp
|
||||||
$(coffee) -o tmp src/meta/eventPage.coffee
|
$(coffee) -o tmp src/meta/eventPage.coffee
|
||||||
|
|
||||||
tmp/LICENSE : LICENSE tools/newlinefix.js | tmp
|
tmp/LICENSE : LICENSE tools/newlinefix.js | tmp
|
||||||
@ -189,11 +172,11 @@ testbuilds/updates$1.json : src/meta/updates.json version.json $(template_deps)
|
|||||||
|
|
||||||
testbuilds/$(name)$1.crx.zip : \
|
testbuilds/$(name)$1.crx.zip : \
|
||||||
$(foreach f,$(crx_contents),testbuilds/crx$1/$(f)) \
|
$(foreach f,$(crx_contents),testbuilds/crx$1/$(f)) \
|
||||||
package.json version.json tools/zip-crx.js node_modules/jszip/package.json
|
package.json version.json tools/zip-crx.js
|
||||||
node tools/zip-crx.js $1
|
node tools/zip-crx.js $1
|
||||||
|
|
||||||
testbuilds/$(name)$1.crx : testbuilds/$(name)$1.crx.zip package.json tools/sign.js node_modules/node-rsa/package.json
|
testbuilds/$(name)$1.crx : $(foreach f,$(crx_contents),testbuilds/crx$1/$(f)) version.json tools/sign.sh | tmp
|
||||||
node tools/sign.js $1
|
tools/sign.sh $1
|
||||||
|
|
||||||
testbuilds/$(name)$1.meta.js : src/meta/metadata.js src/meta/icon48.png version.json src/Archive/archives.json $(template_deps) | testbuilds
|
testbuilds/$(name)$1.meta.js : src/meta/metadata.js src/meta/icon48.png version.json src/Archive/archives.json $(template_deps) | testbuilds
|
||||||
$(template) $$< $$@ type=userscript channel=$1
|
$(template) $$< $$@ type=userscript channel=$1
|
||||||
@ -214,7 +197,7 @@ testbuilds/$(name).zip : testbuilds/$(name)-noupdate.crx.zip
|
|||||||
builds/% : testbuilds/% | builds
|
builds/% : testbuilds/% | builds
|
||||||
$(CP)
|
$(CP)
|
||||||
|
|
||||||
test.html : README.md template.jst tools/markdown.js node_modules/markdown-it/package.json node_modules/markdown-it-anchor/package.json node_modules/lodash.template/package.json
|
test.html : README.md template.jst tools/markdown.js
|
||||||
node tools/markdown.js
|
node tools/markdown.js
|
||||||
|
|
||||||
index.html : test.html
|
index.html : test.html
|
||||||
@ -223,7 +206,7 @@ index.html : test.html
|
|||||||
tmp/.jshintrc : src/meta/jshint.json tmp/declaration.js src/globals/globals.js $(template_deps) | tmp
|
tmp/.jshintrc : src/meta/jshint.json tmp/declaration.js src/globals/globals.js $(template_deps) | tmp
|
||||||
$(template) $< $@
|
$(template) $< $@
|
||||||
|
|
||||||
.events/jshint : $(dests) tmp/.jshintrc node_modules/jshint/package.json
|
.events/jshint : $(dests) tmp/.jshintrc
|
||||||
$(BIN)jshint $(call QUOTE, \
|
$(BIN)jshint $(call QUOTE, \
|
||||||
$(if $(filter-out $(dests),$?), \
|
$(if $(filter-out $(dests),$?), \
|
||||||
$(dests), \
|
$(dests), \
|
||||||
@ -263,13 +246,13 @@ distready : dist $(wildcard dist/* dist/*/*)
|
|||||||
git push web $(meta_distBranch)
|
git push web $(meta_distBranch)
|
||||||
echo -> $@
|
echo -> $@
|
||||||
|
|
||||||
.events2/push-store : .git/refs/tags/stable | .events2 distready node_modules/webstore-upload/package.json node_modules/request/package.json
|
.events2/push-store : .git/refs/tags/stable | .events2 distready
|
||||||
node tools/webstore.js
|
node tools/webstore.js
|
||||||
echo -> $@
|
echo -> $@
|
||||||
|
|
||||||
.SECONDARY :
|
.SECONDARY :
|
||||||
|
|
||||||
.PHONY: default all distready script crx release jshint install push captchas $(npgoals)
|
.PHONY: default all distready script crx release jshint install push $(npgoals)
|
||||||
|
|
||||||
script : $(script)
|
script : $(script)
|
||||||
|
|
||||||
@ -283,23 +266,18 @@ install : .events/install
|
|||||||
|
|
||||||
push : .events2/push-git .events2/push-web .events2/push-store
|
push : .events2/push-git .events2/push-web .events2/push-store
|
||||||
|
|
||||||
captchas : redirect.html $(template_deps)
|
|
||||||
$(template) redirect.html captchas.html url="$(url)"
|
|
||||||
scp captchas.html $(meta_uploadPath)
|
|
||||||
|
|
||||||
clean :
|
clean :
|
||||||
$(RMDIR) tmp testbuilds .events
|
$(RMDIR) tmp tmp-crx testbuilds .events
|
||||||
$(RM) .tests_enabled
|
$(RM) .tests_enabled
|
||||||
|
|
||||||
cleanrel : clean
|
cleanrel : clean
|
||||||
$(RMDIR) builds
|
$(RMDIR) builds
|
||||||
|
|
||||||
cleanweb :
|
cleanweb :
|
||||||
$(RM) test.html captchas.html
|
$(RM) test.html
|
||||||
|
|
||||||
cleanfull : clean cleanweb
|
cleanfull : clean cleanweb
|
||||||
$(RMDIR) .events2 dist node_modules
|
$(RMDIR) .events2 dist node_modules
|
||||||
$(RM) npm-shrinkwrap.json
|
|
||||||
git worktree prune
|
git worktree prune
|
||||||
|
|
||||||
withtests :
|
withtests :
|
||||||
@ -307,10 +285,6 @@ withtests :
|
|||||||
-$(MAKE)
|
-$(MAKE)
|
||||||
echo false> .tests_enabled
|
echo false> .tests_enabled
|
||||||
|
|
||||||
wrapped : src/meta/npm-shrinkwrap.json
|
|
||||||
$(call CAT,$<,npm-shrinkwrap.json)
|
|
||||||
npm install
|
|
||||||
|
|
||||||
archives :
|
archives :
|
||||||
git fetch -n archives
|
git fetch -n archives
|
||||||
git merge --no-commit -s ours archives/gh-pages
|
git merge --no-commit -s ours archives/gh-pages
|
||||||
@ -326,7 +300,6 @@ $(foreach i,1 2 3 4,bump$(i)) :
|
|||||||
tag :
|
tag :
|
||||||
git add builds
|
git add builds
|
||||||
$(MAKE) cleanrel
|
$(MAKE) cleanrel
|
||||||
$(MAKE) wrapped
|
|
||||||
$(MAKE) all
|
$(MAKE) all
|
||||||
git diff --quiet -- builds
|
git diff --quiet -- builds
|
||||||
$(MAKE) tagcommit
|
$(MAKE) tagcommit
|
||||||
@ -355,15 +328,11 @@ web : index.html distready
|
|||||||
cd dist && git commit -am "Update web page."
|
cd dist && git commit -am "Update web page."
|
||||||
|
|
||||||
update :
|
update :
|
||||||
$(RM) npm-shrinkwrap.json
|
$(RM) package-lock.json
|
||||||
npm install --save-dev $(shell node tools/unpinned.js)
|
npm install --save-dev $(shell node tools/unpinned.js)
|
||||||
npm install
|
npm install
|
||||||
npm shrinkwrap --dev
|
|
||||||
$(call CAT,npm-shrinkwrap.json,src/meta/npm-shrinkwrap.json)
|
|
||||||
|
|
||||||
updatehard :
|
updatehard :
|
||||||
$(RM) npm-shrinkwrap.json
|
$(RM) package-lock.json
|
||||||
npm install --save-dev $(shell node tools/unpinned.js latest)
|
npm install --save-dev $(shell node tools/unpinned.js latest)
|
||||||
npm install
|
npm install
|
||||||
npm shrinkwrap --dev
|
|
||||||
$(call CAT,npm-shrinkwrap.json,src/meta/npm-shrinkwrap.json)
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ https://github.com/KevinParnell/OneeChan.
|
|||||||
## Install
|
## Install
|
||||||
|
|
||||||
### Firefox
|
### Firefox
|
||||||
Install [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/), [Violentmonkey](https://addons.mozilla.org/en-US/firefox/addon/violentmonkey/) or [Tampermonkey](https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/), then **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**.
|
Install [Violentmonkey](https://addons.mozilla.org/en-US/firefox/addon/violentmonkey/), [Tampermonkey](https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/), or [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) (issues since v4: [#2526](https://github.com/greasemonkey/greasemonkey/issues/2526), [#2576](https://github.com/greasemonkey/greasemonkey/issues/2574)), 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).
|
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==
|
// ==UserScript==
|
||||||
// @name 4chan X beta
|
// @name 4chan X beta
|
||||||
// @version 1.14.5.13
|
// @version 1.14.7.2
|
||||||
// @minGMVer 1.14
|
// @minGMVer 1.14
|
||||||
// @minFFVer 26
|
// @minFFVer 26
|
||||||
// @namespace 4chan-X
|
// @namespace 4chan-X
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name 4chan X
|
// @name 4chan X
|
||||||
// @version 1.14.5.13
|
// @version 1.14.7.2
|
||||||
// @minGMVer 1.14
|
// @minGMVer 1.14
|
||||||
// @minFFVer 26
|
// @minFFVer 26
|
||||||
// @namespace 4chan-X
|
// @namespace 4chan-X
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -3,7 +3,7 @@
|
|||||||
"4chan-x@4chan-x.net": {
|
"4chan-x@4chan-x.net": {
|
||||||
"updates": [
|
"updates": [
|
||||||
{
|
{
|
||||||
"version": "1.14.5.13",
|
"version": "1.14.7.2",
|
||||||
"update_link": "https://www.4chan-x.net/builds/4chan-X-beta.crx"
|
"update_link": "https://www.4chan-x.net/builds/4chan-X-beta.crx"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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.14.5.13' />
|
<updatecheck codebase='https://www.4chan-x.net/builds/4chan-X-beta.crx' version='1.14.7.2' />
|
||||||
</app>
|
</app>
|
||||||
</gupdate>
|
</gupdate>
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"4chan-x@4chan-x.net": {
|
"4chan-x@4chan-x.net": {
|
||||||
"updates": [
|
"updates": [
|
||||||
{
|
{
|
||||||
"version": "1.14.5.13",
|
"version": "1.14.7.2",
|
||||||
"update_link": "https://www.4chan-x.net/builds/4chan-X.crx"
|
"update_link": "https://www.4chan-x.net/builds/4chan-X.crx"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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.14.5.13' />
|
<updatecheck codebase='https://www.4chan-x.net/builds/4chan-X.crx' version='1.14.7.2' />
|
||||||
</app>
|
</app>
|
||||||
</gupdate>
|
</gupdate>
|
||||||
|
|
||||||
|
|||||||
1
crx-chromium-version.txt
Normal file
1
crx-chromium-version.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Chromium 73.0.3683.75 built on Debian buster/sid, running on Debian buster/sid
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<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>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>
|
<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>Install <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/">Greasemonkey</a>, <a href="https://addons.mozilla.org/en-US/firefox/addon/violentmonkey/">Violentmonkey</a> or <a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/">Tampermonkey</a>, then <strong><a href="https://www.4chan-x.net/builds/4chan-X.user.js">click here to install 4chan X</a></strong>.</p>
|
<p>Install <a href="https://addons.mozilla.org/en-US/firefox/addon/violentmonkey/">Violentmonkey</a>, <a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/">Tampermonkey</a>, or <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/">Greasemonkey</a> (issues since v4: <a href="https://github.com/greasemonkey/greasemonkey/issues/2526">#2526</a>, <a href="https://github.com/greasemonkey/greasemonkey/issues/2574">#2576</a>), then <strong><a href="https://www.4chan-x.net/builds/4chan-X.user.js">click here to install 4chan X</a></strong>.</p>
|
||||||
<p>Ports of Greasemonkey are available for <a href="https://sourceforge.net/projects/gmport/">SeaMonkey</a> and <a href="https://github.com/janekptacijarabaci/greasemonkey/releases/latest">Pale Moon</a>.</p>
|
<p>Ports of Greasemonkey are available for <a href="https://sourceforge.net/projects/gmport/">SeaMonkey</a> and <a href="https://github.com/janekptacijarabaci/greasemonkey/releases/latest">Pale Moon</a>.</p>
|
||||||
</div><input hidden type="checkbox" id="chromium-hide"><div><h3 id="chromium"><label for="chromium-hide">Chromium</label></h3>
|
</div><input hidden type="checkbox" id="chromium-hide"><div><h3 id="chromium"><label for="chromium-hide">Chromium</label></h3>
|
||||||
<p><strong>Userscript</strong>: Install <a href="https://chrome.google.com/webstore/detail/violent-monkey/jinjaccalgkegednnccohejagnlnfdag">Violentmonkey</a>) or <a href="https://tampermonkey.net/">Tampermonkey</a>, then <strong><a href="https://www.4chan-x.net/builds/4chan-X.user.js">click here to install 4chan X</a></strong>.</p>
|
<p><strong>Userscript</strong>: Install <a href="https://chrome.google.com/webstore/detail/violent-monkey/jinjaccalgkegednnccohejagnlnfdag">Violentmonkey</a>) or <a href="https://tampermonkey.net/">Tampermonkey</a>, then <strong><a href="https://www.4chan-x.net/builds/4chan-X.user.js">click here to install 4chan X</a></strong>.</p>
|
||||||
|
|||||||
528
src/meta/npm-shrinkwrap.json → package-lock.json
generated
528
src/meta/npm-shrinkwrap.json → package-lock.json
generated
@ -4,31 +4,34 @@
|
|||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "5.2.3",
|
"version": "6.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
|
||||||
"integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=",
|
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"co": "4.6.0",
|
"fast-deep-equal": "2.0.1",
|
||||||
"fast-deep-equal": "1.0.0",
|
"fast-json-stable-stringify": "2.0.0",
|
||||||
"json-schema-traverse": "0.3.1",
|
"json-schema-traverse": "0.4.1",
|
||||||
"json-stable-stringify": "1.0.1"
|
"uri-js": "4.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"argparse": {
|
"argparse": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||||
"integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
|
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"sprintf-js": "1.0.3"
|
"sprintf-js": "1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"asn1": {
|
"asn1": {
|
||||||
"version": "0.2.3",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||||
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
|
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": "2.1.2"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"assert-plus": {
|
"assert-plus": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -49,9 +52,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"aws4": {
|
"aws4": {
|
||||||
"version": "1.6.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
||||||
"integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
|
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
@ -61,11 +64,10 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"bcrypt-pbkdf": {
|
"bcrypt-pbkdf": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||||
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
|
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"tweetnacl": "0.14.5"
|
"tweetnacl": "0.14.5"
|
||||||
}
|
}
|
||||||
@ -76,19 +78,10 @@
|
|||||||
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=",
|
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"boom": {
|
|
||||||
"version": "4.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
|
|
||||||
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"hoek": "4.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
"integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "1.0.0",
|
"balanced-match": "1.0.0",
|
||||||
@ -108,15 +101,9 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"exit": "0.1.2",
|
"exit": "0.1.2",
|
||||||
"glob": "7.1.2"
|
"glob": "7.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"co": {
|
|
||||||
"version": "4.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
|
||||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"coffee-script": {
|
"coffee-script": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.9.3.tgz",
|
||||||
@ -124,9 +111,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"combined-stream": {
|
"combined-stream": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
||||||
"integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
|
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"delayed-stream": "1.0.0"
|
"delayed-stream": "1.0.0"
|
||||||
@ -147,38 +134,12 @@
|
|||||||
"date-now": "0.1.4"
|
"date-now": "0.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"core-js": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz",
|
|
||||||
"integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"cryptiles": {
|
|
||||||
"version": "3.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
|
|
||||||
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"boom": "5.2.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"boom": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"hoek": "4.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dashdash": {
|
"dashdash": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
@ -201,33 +162,27 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"dom-serializer": {
|
"dom-serializer": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
|
||||||
"integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
|
"integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"domelementtype": "1.1.3",
|
"domelementtype": "1.3.1",
|
||||||
"entities": "1.1.1"
|
"entities": "1.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"domelementtype": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
|
|
||||||
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"entities": {
|
"entities": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||||
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
|
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domelementtype": {
|
"domelementtype": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||||
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
|
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"domhandler": {
|
"domhandler": {
|
||||||
@ -236,7 +191,7 @@
|
|||||||
"integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
|
"integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"domelementtype": "1.3.0"
|
"domelementtype": "1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
@ -245,18 +200,18 @@
|
|||||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"dom-serializer": "0.1.0",
|
"dom-serializer": "0.1.1",
|
||||||
"domelementtype": "1.3.0"
|
"domelementtype": "1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ecc-jsbn": {
|
"ecc-jsbn": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||||
"integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
|
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"jsbn": "0.1.1"
|
"jsbn": "0.1.1",
|
||||||
|
"safer-buffer": "2.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entities": {
|
"entities": {
|
||||||
@ -265,16 +220,10 @@
|
|||||||
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=",
|
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"es6-promise": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
|
|
||||||
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"esprima": {
|
"esprima": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"exit": {
|
"exit": {
|
||||||
@ -284,9 +233,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"extend": {
|
"extend": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=",
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"extsprintf": {
|
"extsprintf": {
|
||||||
@ -296,9 +245,15 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"fast-deep-equal": {
|
"fast-deep-equal": {
|
||||||
"version": "1.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||||
"integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=",
|
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"fast-json-stable-stringify": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"font-awesome": {
|
"font-awesome": {
|
||||||
@ -314,14 +269,14 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"form-data": {
|
"form-data": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||||
"integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
|
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asynckit": "0.4.0",
|
"asynckit": "0.4.0",
|
||||||
"combined-stream": "1.0.5",
|
"combined-stream": "1.0.7",
|
||||||
"mime-types": "2.1.17"
|
"mime-types": "2.1.22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fs.realpath": {
|
"fs.realpath": {
|
||||||
@ -340,9 +295,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fs.realpath": "1.0.0",
|
"fs.realpath": "1.0.0",
|
||||||
@ -360,40 +315,22 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"har-validator": {
|
"har-validator": {
|
||||||
"version": "5.0.3",
|
"version": "5.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||||
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "5.2.3",
|
"ajv": "6.10.0",
|
||||||
"har-schema": "2.0.0"
|
"har-schema": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hawk": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"boom": "4.3.1",
|
|
||||||
"cryptiles": "3.1.2",
|
|
||||||
"hoek": "4.2.0",
|
|
||||||
"sntp": "2.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hoek": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"htmlparser2": {
|
"htmlparser2": {
|
||||||
"version": "3.8.3",
|
"version": "3.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
|
||||||
"integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
|
"integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"domelementtype": "1.3.0",
|
"domelementtype": "1.3.1",
|
||||||
"domhandler": "2.3.0",
|
"domhandler": "2.3.0",
|
||||||
"domutils": "1.5.1",
|
"domutils": "1.5.1",
|
||||||
"entities": "1.0.0",
|
"entities": "1.0.0",
|
||||||
@ -408,7 +345,7 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"assert-plus": "1.0.0",
|
"assert-plus": "1.0.0",
|
||||||
"jsprim": "1.4.1",
|
"jsprim": "1.4.1",
|
||||||
"sshpk": "1.13.1"
|
"sshpk": "1.16.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"immediate": {
|
"immediate": {
|
||||||
@ -455,20 +392,19 @@
|
|||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
|
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"jshint": {
|
"jshint": {
|
||||||
"version": "2.9.5",
|
"version": "2.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz",
|
||||||
"integrity": "sha1-HnJSkVzmgbQIJ+4UJIxG006apiw=",
|
"integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"cli": "1.0.1",
|
"cli": "1.0.1",
|
||||||
"console-browserify": "1.1.0",
|
"console-browserify": "1.1.0",
|
||||||
"exit": "0.1.2",
|
"exit": "0.1.2",
|
||||||
"htmlparser2": "3.8.3",
|
"htmlparser2": "3.8.3",
|
||||||
"lodash": "3.7.0",
|
"lodash": "4.17.11",
|
||||||
"minimatch": "3.0.4",
|
"minimatch": "3.0.4",
|
||||||
"shelljs": "0.3.0",
|
"shelljs": "0.3.0",
|
||||||
"strip-json-comments": "1.0.4"
|
"strip-json-comments": "1.0.4"
|
||||||
@ -481,32 +417,17 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"json-schema-traverse": {
|
"json-schema-traverse": {
|
||||||
"version": "0.3.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"json-stable-stringify": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"jsonify": "0.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json-stringify-safe": {
|
"json-stringify-safe": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||||
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
|
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"jsonify": {
|
|
||||||
"version": "0.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
|
||||||
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"jsprim": {
|
"jsprim": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||||
@ -520,16 +441,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jszip": {
|
"jszip": {
|
||||||
"version": "3.1.4",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz",
|
||||||
"integrity": "sha512-z6w8iYIxZ/fcgul0j/OerkYnkomH8BZigvzbxVmr2h5HkZUrPtk2kjYtLkqR9wwQxEP6ecKNoKLsbhd18jfnGA==",
|
"integrity": "sha512-iCMBbo4eE5rb1VCpm5qXOAaUiRKRUKiItn8ah2YQQx9qymmSAY98eyQfioChEYcVQLh0zxJ3wS4A0mh90AVPvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-js": "2.3.0",
|
"lie": "3.3.0",
|
||||||
"es6-promise": "3.0.2",
|
"pako": "1.0.10",
|
||||||
"lie": "3.1.1",
|
"readable-stream": "2.3.6",
|
||||||
"pako": "1.0.6",
|
"set-immediate-shim": "1.0.1"
|
||||||
"readable-stream": "2.0.6"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isarray": {
|
"isarray": {
|
||||||
@ -539,43 +459,53 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.0.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
|
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "1.0.2",
|
"core-util-is": "1.0.2",
|
||||||
"inherits": "2.0.3",
|
"inherits": "2.0.3",
|
||||||
"isarray": "1.0.0",
|
"isarray": "1.0.0",
|
||||||
"process-nextick-args": "1.0.7",
|
"process-nextick-args": "2.0.0",
|
||||||
"string_decoder": "0.10.31",
|
"safe-buffer": "5.1.2",
|
||||||
|
"string_decoder": "1.1.1",
|
||||||
"util-deprecate": "1.0.2"
|
"util-deprecate": "1.0.2"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "5.1.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lie": {
|
"lie": {
|
||||||
"version": "3.1.1",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||||
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
|
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"immediate": "3.0.6"
|
"immediate": "3.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"linkify-it": {
|
"linkify-it": {
|
||||||
"version": "2.0.3",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.1.0.tgz",
|
||||||
"integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=",
|
"integrity": "sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"uc.micro": "1.0.3"
|
"uc.micro": "1.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "3.7.0",
|
"version": "4.17.11",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||||
"integrity": "sha1-Nni9irmVBXwHreg27S7wh9qBHUU=",
|
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash._reinterpolate": {
|
"lodash._reinterpolate": {
|
||||||
@ -604,22 +534,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markdown-it": {
|
"markdown-it": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz",
|
||||||
"integrity": "sha512-tNuOCCfunY5v5uhcO2AUMArvKAyKMygX8tfup/JrgnsDqcCATQsAExBq7o5Ml9iMmO82bk6jYNLj6khcrl0JGA==",
|
"integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"argparse": "1.0.9",
|
"argparse": "1.0.10",
|
||||||
"entities": "1.1.1",
|
"entities": "1.1.2",
|
||||||
"linkify-it": "2.0.3",
|
"linkify-it": "2.1.0",
|
||||||
"mdurl": "1.0.1",
|
"mdurl": "1.0.1",
|
||||||
"uc.micro": "1.0.3"
|
"uc.micro": "1.0.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"entities": {
|
"entities": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||||
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
|
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -640,18 +570,18 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mime-db": {
|
"mime-db": {
|
||||||
"version": "1.30.0",
|
"version": "1.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
|
||||||
"integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=",
|
"integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mime-types": {
|
"mime-types": {
|
||||||
"version": "2.1.17",
|
"version": "2.1.22",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz",
|
||||||
"integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
|
"integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"mime-db": "1.30.0"
|
"mime-db": "1.38.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
@ -660,22 +590,13 @@
|
|||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "1.1.8"
|
"brace-expansion": "1.1.11"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node-rsa": {
|
|
||||||
"version": "0.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
|
|
||||||
"integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"asn1": "0.2.3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth-sign": {
|
"oauth-sign": {
|
||||||
"version": "0.8.2",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||||
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
|
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"once": {
|
"once": {
|
||||||
@ -694,9 +615,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"pako": {
|
"pako": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
|
||||||
"integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==",
|
"integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"path-is-absolute": {
|
"path-is-absolute": {
|
||||||
@ -712,27 +633,33 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "1.0.7",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
||||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
|
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"psl": {
|
||||||
|
"version": "1.1.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
|
||||||
|
"integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "1.4.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"q": {
|
"q": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||||
"integrity": "sha1-3QG6ydBtMObyGa7LglPunr3DCPE=",
|
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"qs": {
|
"qs": {
|
||||||
"version": "6.5.1",
|
"version": "6.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||||
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
|
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
@ -748,33 +675,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"version": "2.83.0",
|
"version": "2.88.0",
|
||||||
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
|
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||||
"integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
|
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"aws-sign2": "0.7.0",
|
"aws-sign2": "0.7.0",
|
||||||
"aws4": "1.6.0",
|
"aws4": "1.8.0",
|
||||||
"caseless": "0.12.0",
|
"caseless": "0.12.0",
|
||||||
"combined-stream": "1.0.5",
|
"combined-stream": "1.0.7",
|
||||||
"extend": "3.0.1",
|
"extend": "3.0.2",
|
||||||
"forever-agent": "0.6.1",
|
"forever-agent": "0.6.1",
|
||||||
"form-data": "2.3.1",
|
"form-data": "2.3.3",
|
||||||
"har-validator": "5.0.3",
|
"har-validator": "5.1.3",
|
||||||
"hawk": "6.0.2",
|
|
||||||
"http-signature": "1.2.0",
|
"http-signature": "1.2.0",
|
||||||
"is-typedarray": "1.0.0",
|
"is-typedarray": "1.0.0",
|
||||||
"isstream": "0.1.2",
|
"isstream": "0.1.2",
|
||||||
"json-stringify-safe": "5.0.1",
|
"json-stringify-safe": "5.0.1",
|
||||||
"mime-types": "2.1.17",
|
"mime-types": "2.1.22",
|
||||||
"oauth-sign": "0.8.2",
|
"oauth-sign": "0.9.0",
|
||||||
"performance-now": "2.1.0",
|
"performance-now": "2.1.0",
|
||||||
"qs": "6.5.1",
|
"qs": "6.5.2",
|
||||||
"safe-buffer": "5.1.1",
|
"safe-buffer": "5.1.2",
|
||||||
"stringstream": "0.0.5",
|
"tough-cookie": "2.4.3",
|
||||||
"tough-cookie": "2.3.3",
|
|
||||||
"tunnel-agent": "0.6.0",
|
"tunnel-agent": "0.6.0",
|
||||||
"uuid": "3.1.0"
|
"uuid": "3.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"request-promise": {
|
"request-promise": {
|
||||||
@ -784,22 +709,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bluebird": "2.11.0",
|
"bluebird": "2.11.0",
|
||||||
"lodash": "4.17.4",
|
"lodash": "4.17.11",
|
||||||
"request": "2.83.0"
|
"request": "2.88.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
|
|
||||||
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"set-immediate-shim": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"shelljs": {
|
"shelljs": {
|
||||||
@ -808,15 +737,6 @@
|
|||||||
"integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=",
|
"integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"sntp": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz",
|
|
||||||
"integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"hoek": "4.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sprintf-js": {
|
"sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
@ -824,18 +744,19 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"sshpk": {
|
"sshpk": {
|
||||||
"version": "1.13.1",
|
"version": "1.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
|
||||||
"integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
|
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asn1": "0.2.3",
|
"asn1": "0.2.4",
|
||||||
"assert-plus": "1.0.0",
|
"assert-plus": "1.0.0",
|
||||||
"bcrypt-pbkdf": "1.0.1",
|
"bcrypt-pbkdf": "1.0.2",
|
||||||
"dashdash": "1.14.1",
|
"dashdash": "1.14.1",
|
||||||
"ecc-jsbn": "0.1.1",
|
"ecc-jsbn": "0.1.2",
|
||||||
"getpass": "0.1.7",
|
"getpass": "0.1.7",
|
||||||
"jsbn": "0.1.1",
|
"jsbn": "0.1.1",
|
||||||
|
"safer-buffer": "2.1.2",
|
||||||
"tweetnacl": "0.14.5"
|
"tweetnacl": "0.14.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -851,12 +772,6 @@
|
|||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"stringstream": {
|
|
||||||
"version": "0.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
|
||||||
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"strip-json-comments": {
|
"strip-json-comments": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
|
||||||
@ -864,12 +779,21 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"tough-cookie": {
|
"tough-cookie": {
|
||||||
"version": "2.3.3",
|
"version": "2.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||||
"integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
|
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"psl": "1.1.31",
|
||||||
"punycode": "1.4.1"
|
"punycode": "1.4.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tunnel-agent": {
|
"tunnel-agent": {
|
||||||
@ -878,22 +802,30 @@
|
|||||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "5.1.1"
|
"safe-buffer": "5.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tweetnacl": {
|
"tweetnacl": {
|
||||||
"version": "0.14.5",
|
"version": "0.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
|
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"uc.micro": {
|
"uc.micro": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||||
"integrity": "sha1-ftUNXg+an7ClczeSWfKndFjVAZI=",
|
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"uri-js": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"punycode": "2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
@ -901,9 +833,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "3.1.0",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||||
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
|
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"verror": {
|
"verror": {
|
||||||
@ -923,11 +855,11 @@
|
|||||||
"integrity": "sha1-aVfXgSzXlgZDAU0Fea+Y3HakPow=",
|
"integrity": "sha1-aVfXgSzXlgZDAU0Fea+Y3HakPow=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob": "7.1.2",
|
"glob": "7.1.3",
|
||||||
"lodash": "2.4.2",
|
"lodash": "2.4.2",
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"q": "1.5.0",
|
"q": "1.5.1",
|
||||||
"request": "2.83.0",
|
"request": "2.88.0",
|
||||||
"request-promise": "2.0.1"
|
"request-promise": "2.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
12
package.json
12
package.json
@ -22,7 +22,6 @@
|
|||||||
"recaptchaKey": "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc",
|
"recaptchaKey": "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc",
|
||||||
"youtubeAPIKey": "AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE",
|
"youtubeAPIKey": "AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE",
|
||||||
"distBranch": "gh-pages",
|
"distBranch": "gh-pages",
|
||||||
"uploadPath": "www.4chan-x.net:/var/www/html/",
|
|
||||||
"includes_only": [
|
"includes_only": [
|
||||||
"*://boards.4chan.org/*",
|
"*://boards.4chan.org/*",
|
||||||
"*://sys.4chan.org/*",
|
"*://sys.4chan.org/*",
|
||||||
@ -85,15 +84,14 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"coffee-script": "=1.9.3",
|
"coffee-script": "=1.9.3",
|
||||||
"esprima": "^4.0.0",
|
"esprima": "^4.0.1",
|
||||||
"font-awesome": "=4.6.3",
|
"font-awesome": "=4.6.3",
|
||||||
"jshint": "^2.9.5",
|
"jshint": "^2.10.2",
|
||||||
"jszip": "^3.1.4",
|
"jszip": "^3.2.1",
|
||||||
"lodash.template": "^4.4.0",
|
"lodash.template": "^4.4.0",
|
||||||
"markdown-it": "^8.4.0",
|
"markdown-it": "^8.4.2",
|
||||||
"markdown-it-anchor": "^4.0.0",
|
"markdown-it-anchor": "^4.0.0",
|
||||||
"node-rsa": "^0.4.2",
|
"request": "^2.88.0",
|
||||||
"request": "^2.83.0",
|
|
||||||
"webstore-upload": "0.0.7"
|
"webstore-upload": "0.0.7"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
<% url = (url || 'https://www.4chan.org/feedback'); %><!doctype html>
|
|
||||||
<html><head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="refresh" content="0; url=<%= url %>">
|
|
||||||
<title>Redirect</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
Redirecting to <a href="<%= url %>"><%= url %></a>...
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -68,7 +68,8 @@ Redirect =
|
|||||||
continue
|
continue
|
||||||
load(i).call {status: 200, response}
|
load(i).call {status: 200, response}
|
||||||
else
|
else
|
||||||
CrossOrigin.json url, load(i), true
|
CrossOrigin.ajax url,
|
||||||
|
onloadend: load(i)
|
||||||
else
|
else
|
||||||
Redirect.parse [], cb
|
Redirect.parse [], cb
|
||||||
return
|
return
|
||||||
|
|||||||
@ -7,32 +7,23 @@ Filter =
|
|||||||
unless Conf['Filtered Backlinks']
|
unless Conf['Filtered Backlinks']
|
||||||
$.addClass doc, 'hide-backlinks'
|
$.addClass doc, 'hide-backlinks'
|
||||||
|
|
||||||
nsfwBoards = BoardConfig.sfwBoards(false).join(',')
|
|
||||||
sfwBoards = BoardConfig.sfwBoards(true).join(',')
|
|
||||||
|
|
||||||
for key of Config.filter
|
for key of Config.filter
|
||||||
for line in Conf[key].split '\n'
|
for line in Conf[key].split '\n'
|
||||||
continue if line[0] is '#'
|
continue if line[0] is '#'
|
||||||
|
|
||||||
if not (regexp = line.match /\/(.+)\/(\w*)/)
|
if not (regexp = line.match /\/(.*)\/(\w*)/)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Don't mix up filter flags with the regular expression.
|
# Don't mix up filter flags with the regular expression.
|
||||||
filter = line.replace regexp[0], ''
|
filter = line.replace regexp[0], ''
|
||||||
|
|
||||||
# Comma-separated list of the boards this filter applies to.
|
# List of the boards this filter applies to.
|
||||||
# Defaults to global.
|
boards = @parseBoards filter.match(/(?:^|;)\s*boards:([^;]+)/)?[1]
|
||||||
boards = filter.match(/boards:([^;]+)/)?[1].toLowerCase() or 'global'
|
|
||||||
boards = boards.replace('nsfw', nsfwBoards).replace('sfw', sfwBoards)
|
|
||||||
boards = if boards is 'global' then null else boards.split(',')
|
|
||||||
|
|
||||||
# boards to exclude from an otherwise global rule
|
# Boards to exclude from an otherwise global rule.
|
||||||
# due to the sfw and nsfw keywords, also works on all filters
|
excludes = @parseBoards filter.match(/(?:^|;)\s*exclude:([^;]+)/)?[1]
|
||||||
# replaces 'nsfw' and 'sfw' for consistency
|
|
||||||
excludes = filter.match(/exclude:([^;]+)/)?[1].toLowerCase() or null
|
|
||||||
excludes = if excludes is null then null else excludes.replace('nsfw', nsfwBoards).replace('sfw', sfwBoards).split(',')
|
|
||||||
|
|
||||||
if key in ['uniqueID', 'MD5']
|
if (isstring = (key in ['uniqueID', 'MD5']))
|
||||||
# MD5 filter will use strings instead of regular expressions.
|
# MD5 filter will use strings instead of regular expressions.
|
||||||
regexp = regexp[1]
|
regexp = regexp[1]
|
||||||
else
|
else
|
||||||
@ -50,13 +41,17 @@ Filter =
|
|||||||
], 60
|
], 60
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Filter OPs along with their threads, replies only, or both.
|
# Filter OPs along with their threads or replies only.
|
||||||
# Defaults to both.
|
op = filter.match(/(?:^|;)\s*op:(no|only)/)?[1] or ''
|
||||||
op = filter.match(/[^t]op:(yes|no|only)/)?[1] or 'yes'
|
mask = {'no': 1, 'only': 2}[op] or 0
|
||||||
|
|
||||||
|
# Filter only posts with/without files.
|
||||||
|
file = filter.match(/(?:^|;)\s*file:(no|only)/)?[1] or ''
|
||||||
|
mask = mask | ({'no': 4, 'only': 8}[file] or 0)
|
||||||
|
|
||||||
# Overrule the `Show Stubs` setting.
|
# Overrule the `Show Stubs` setting.
|
||||||
# Defaults to stub showing.
|
# Defaults to stub showing.
|
||||||
stub = switch filter.match(/stub:(yes|no)/)?[1]
|
stub = switch filter.match(/(?:^|;)\s*stub:(yes|no)/)?[1]
|
||||||
when 'yes'
|
when 'yes'
|
||||||
true
|
true
|
||||||
when 'no'
|
when 'no'
|
||||||
@ -64,25 +59,29 @@ Filter =
|
|||||||
else
|
else
|
||||||
Conf['Stubs']
|
Conf['Stubs']
|
||||||
|
|
||||||
# Highlight the post, or hide it.
|
# Desktop notification
|
||||||
|
noti = /(?:^|;)\s*notify/.test filter
|
||||||
|
|
||||||
|
# Highlight the post.
|
||||||
# If not specified, the highlight class will be filter-highlight.
|
# If not specified, the highlight class will be filter-highlight.
|
||||||
# Defaults to post hiding.
|
if (hl = /(?:^|;)\s*highlight/.test filter)
|
||||||
if hl = /highlight/.test filter
|
hl = filter.match(/(?:^|;)\s*highlight:([\w-]+)/)?[1] or 'filter-highlight'
|
||||||
hl = filter.match(/highlight:([\w-]+)/)?[1] or 'filter-highlight'
|
|
||||||
# Put highlighted OP's thread on top of the board page or not.
|
# Put highlighted OP's thread on top of the board page or not.
|
||||||
# Defaults to on top.
|
# Defaults to on top.
|
||||||
top = filter.match(/top:(yes|no)/)?[1] or 'yes'
|
top = filter.match(/(?:^|;)\s*top:(yes|no)/)?[1] or 'yes'
|
||||||
top = top is 'yes' # Turn it into a boolean
|
top = top is 'yes' # Turn it into a boolean
|
||||||
|
|
||||||
# Fields that this filter applies to (for 'general' filters)
|
# Fields that this filter applies to (for 'general' filters)
|
||||||
if key is 'general'
|
if key is 'general'
|
||||||
if (types = filter.match /(?:^|;)\s*type:([^;]*)/)
|
if (types = filter.match /(?:^|;)\s*type:([^;]*)/)
|
||||||
types = types[1].split(',').filter (x) ->
|
types = types[1].split(',')
|
||||||
x of Config.filter and x isnt 'general'
|
|
||||||
else
|
else
|
||||||
types = ['subject', 'name', 'filename', 'comment']
|
types = ['subject', 'name', 'filename', 'comment']
|
||||||
|
|
||||||
filter = @createFilter regexp, boards, excludes, op, stub, hl, top
|
# Hide the post (default case).
|
||||||
|
hide = !(hl or noti)
|
||||||
|
|
||||||
|
filter = {isstring, regexp, boards, excludes, mask, hide, stub, hl, top, noti}
|
||||||
if key is 'general'
|
if key is 'general'
|
||||||
for type in types
|
for type in types
|
||||||
(@filters[type] or= []).push filter
|
(@filters[type] or= []).push filter
|
||||||
@ -94,30 +93,27 @@ Filter =
|
|||||||
name: 'Filter'
|
name: 'Filter'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
createFilter: (regexp, boards, excludes, op, stub, hl, top) ->
|
# Parse comma-separated list of boards.
|
||||||
test =
|
# Sites can be specified by a beginning part of the site domain followed by a colon.
|
||||||
if typeof regexp is 'string'
|
parseBoards: (boardsRaw) ->
|
||||||
# MD5 checking
|
return false unless boardsRaw
|
||||||
(value) -> regexp is value
|
return boards if (boards = Filter.parseBoardsMemo[boardsRaw])
|
||||||
else
|
boards = {}
|
||||||
(value) -> regexp.test value
|
siteFilter = ''
|
||||||
|
for boardID in boardsRaw.split(',')
|
||||||
|
if ':' in boardID
|
||||||
|
[siteFilter, boardID] = boardID.split(':')[-2..]
|
||||||
|
for siteID, siteProperties of Conf['siteProperties']
|
||||||
|
continue if siteProperties.canonical or siteID[...siteFilter.length] isnt siteFilter
|
||||||
|
if boardID in ['nsfw', 'sfw']
|
||||||
|
for boardID2 in SW[siteProperties.software]?.sfwBoards?(boardID is 'sfw') or []
|
||||||
|
boards["#{siteID}/#{boardID2}"] = true
|
||||||
|
else
|
||||||
|
boards["#{siteID}/#{encodeURIComponent boardID}"] = true
|
||||||
|
Filter.parseBoardsMemo[boardsRaw] = boards
|
||||||
|
boards
|
||||||
|
|
||||||
settings =
|
parseBoardsMemo: {}
|
||||||
hide: !hl
|
|
||||||
stub: stub
|
|
||||||
class: hl
|
|
||||||
top: top
|
|
||||||
|
|
||||||
(value, boardID, isReply) ->
|
|
||||||
if boards and boardID not in boards
|
|
||||||
return false
|
|
||||||
if excludes and boardID in excludes
|
|
||||||
return false
|
|
||||||
if isReply and op is 'only' or !isReply and op is 'no'
|
|
||||||
return false
|
|
||||||
unless test value
|
|
||||||
return false
|
|
||||||
settings
|
|
||||||
|
|
||||||
test: (post, hideable=true) ->
|
test: (post, hideable=true) ->
|
||||||
return post.filterResults if post.filterResults
|
return post.filterResults if post.filterResults
|
||||||
@ -125,27 +121,40 @@ Filter =
|
|||||||
stub = true
|
stub = true
|
||||||
hl = undefined
|
hl = undefined
|
||||||
top = false
|
top = false
|
||||||
|
noti = false
|
||||||
if QuoteYou.isYou(post)
|
if QuoteYou.isYou(post)
|
||||||
hideable = false
|
hideable = false
|
||||||
for key of Filter.filters when ((value = Filter[key] post)?)
|
mask = (if post.isReply then 2 else 1)
|
||||||
|
mask = (mask | (if post.file then 4 else 8))
|
||||||
|
board = "#{post.siteID}/#{post.boardID}"
|
||||||
|
site = "#{post.siteID}/*"
|
||||||
|
for key of Filter.filters when ((value = Filter.value key, post)?)
|
||||||
# Continue if there's nothing to filter (no tripcode for example).
|
# Continue if there's nothing to filter (no tripcode for example).
|
||||||
for filter in Filter.filters[key] when (result = filter value, post.boardID, post.isReply)
|
for filter in Filter.filters[key]
|
||||||
if result.hide
|
continue if (
|
||||||
|
(filter.boards and !(filter.boards[board] or filter.boards[site] )) or
|
||||||
|
(filter.excludes and (filter.excludes[board] or filter.excludes[site])) or
|
||||||
|
(filter.mask & mask) or
|
||||||
|
(if filter.isstring then (filter.regexp isnt value) else !filter.regexp.test(value))
|
||||||
|
)
|
||||||
|
if filter.hide
|
||||||
if hideable
|
if hideable
|
||||||
hide = true
|
hide = true
|
||||||
stub and= result.stub
|
stub and= filter.stub
|
||||||
else
|
else
|
||||||
unless hl and result.class in hl
|
unless hl and filter.hl in hl
|
||||||
(hl or= []).push result.class
|
(hl or= []).push filter.hl
|
||||||
top or= result.top
|
top or= filter.top
|
||||||
|
if filter.noti
|
||||||
|
noti = true
|
||||||
if hide
|
if hide
|
||||||
{hide, stub}
|
{hide, stub}
|
||||||
else
|
else
|
||||||
{hl, top}
|
{hl, top, noti}
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
return if @isClone
|
return if @isClone
|
||||||
{hide, stub, hl, top} = Filter.test @, (!@isFetchedQuote and (@isReply or g.VIEW is 'index'))
|
{hide, stub, hl, top, noti} = Filter.test @, (!@isFetchedQuote and (@isReply or g.VIEW is 'index'))
|
||||||
if hide
|
if hide
|
||||||
if @isReply
|
if @isReply
|
||||||
PostHiding.hide @, stub
|
PostHiding.hide @, stub
|
||||||
@ -155,24 +164,33 @@ Filter =
|
|||||||
if hl
|
if hl
|
||||||
@highlights = hl
|
@highlights = hl
|
||||||
$.addClass @nodes.root, hl...
|
$.addClass @nodes.root, hl...
|
||||||
return
|
if noti and Unread.posts and (@ID > Unread.lastReadPost) and not QuoteYou.isYou(@)
|
||||||
|
Unread.openNotification @, ' triggered a notification filter'
|
||||||
|
|
||||||
isHidden: (post) ->
|
isHidden: (post) ->
|
||||||
!!Filter.test(post).hide
|
!!Filter.test(post).hide
|
||||||
|
|
||||||
postID: (post) -> "#{post.ID}"
|
valueF:
|
||||||
name: (post) -> post.info.name
|
postID: (post) -> "#{post.ID}"
|
||||||
uniqueID: (post) -> post.info.uniqueID
|
name: (post) -> post.info.name
|
||||||
tripcode: (post) -> post.info.tripcode
|
uniqueID: (post) -> post.info.uniqueID or ''
|
||||||
capcode: (post) -> post.info.capcode
|
tripcode: (post) -> post.info.tripcode
|
||||||
pass: (post) -> post.info.pass
|
capcode: (post) -> post.info.capcode
|
||||||
subject: (post) -> post.info.subject or (if post.isReply then undefined else '')
|
pass: (post) -> post.info.pass
|
||||||
comment: (post) -> (post.info.comment ?= Build.parseComment post.info.commentHTML.innerHTML)
|
email: (post) -> post.info.email
|
||||||
flag: (post) -> post.info.flag
|
subject: (post) -> post.info.subject or (if post.isReply then undefined else '')
|
||||||
filename: (post) -> post.file?.name
|
comment: (post) -> (post.info.comment ?= Build.parseComment post.info.commentHTML.innerHTML)
|
||||||
dimensions: (post) -> post.file?.dimensions
|
flag: (post) -> post.info.flag
|
||||||
filesize: (post) -> post.file?.size
|
filename: (post) -> post.file?.name
|
||||||
MD5: (post) -> post.file?.MD5
|
dimensions: (post) -> post.file?.dimensions
|
||||||
|
filesize: (post) -> post.file?.size
|
||||||
|
MD5: (post) -> post.file?.MD5
|
||||||
|
|
||||||
|
value: (key, post) ->
|
||||||
|
if key of Filter.valueF
|
||||||
|
Filter.valueF[key](post)
|
||||||
|
else
|
||||||
|
key.split('+').map((k) -> Filter.valueF[k]?(post) or '').join('\n')
|
||||||
|
|
||||||
addFilter: (type, re, cb) ->
|
addFilter: (type, re, cb) ->
|
||||||
$.get type, Conf[type], (item) ->
|
$.get type, Conf[type], (item) ->
|
||||||
@ -245,6 +263,7 @@ Filter =
|
|||||||
['Tripcode', 'tripcode']
|
['Tripcode', 'tripcode']
|
||||||
['Capcode', 'capcode']
|
['Capcode', 'capcode']
|
||||||
['Pass Date', 'pass']
|
['Pass Date', 'pass']
|
||||||
|
['Email', 'email']
|
||||||
['Subject', 'subject']
|
['Subject', 'subject']
|
||||||
['Comment', 'comment']
|
['Comment', 'comment']
|
||||||
['Flag', 'flag']
|
['Flag', 'flag']
|
||||||
@ -268,14 +287,14 @@ Filter =
|
|||||||
return {
|
return {
|
||||||
el: el
|
el: el
|
||||||
open: (post) ->
|
open: (post) ->
|
||||||
value = Filter[type] post
|
value = Filter.value type, post
|
||||||
value?
|
value?
|
||||||
}
|
}
|
||||||
|
|
||||||
makeFilter: ->
|
makeFilter: ->
|
||||||
{type} = @dataset
|
{type} = @dataset
|
||||||
# Convert value -> regexp, unless type is MD5
|
# Convert value -> regexp, unless type is MD5
|
||||||
value = Filter[type] Filter.menu.post
|
value = Filter.value type, Filter.menu.post
|
||||||
re = if type in ['uniqueID', 'MD5'] then value else Filter.escape(value)
|
re = if type in ['uniqueID', 'MD5'] then value else Filter.escape(value)
|
||||||
re = if type in ['uniqueID', 'MD5']
|
re = if type in ['uniqueID', 'MD5']
|
||||||
"/#{re}/"
|
"/#{re}/"
|
||||||
|
|||||||
@ -48,6 +48,11 @@ BoardConfig =
|
|||||||
domain: (board) ->
|
domain: (board) ->
|
||||||
"boards.#{if BoardConfig.isSFW(board) then '4channel' else '4chan'}.org"
|
"boards.#{if BoardConfig.isSFW(board) then '4channel' else '4chan'}.org"
|
||||||
|
|
||||||
|
isArchived: (board) ->
|
||||||
|
# assume archive exists if no data available to prevent cleaning of archived threads
|
||||||
|
data = (@boards or Conf['boardConfig'].boards)[board]
|
||||||
|
!data or data.is_archived
|
||||||
|
|
||||||
noAudio: (boardID) ->
|
noAudio: (boardID) ->
|
||||||
return false unless Site.software is 'yotsuba'
|
return false unless Site.software is 'yotsuba'
|
||||||
boards = @boards or Conf['boardConfig'].boards
|
boards = @boards or Conf['boardConfig'].boards
|
||||||
|
|||||||
@ -65,7 +65,8 @@ Build.Test =
|
|||||||
|
|
||||||
testOne: (post) ->
|
testOne: (post) ->
|
||||||
Build.Test.postsRemaining++
|
Build.Test.postsRemaining++
|
||||||
$.cache "#{location.protocol}//a.4cdn.org/#{post.board.ID}/thread/#{post.thread.ID}.json", ->
|
$.cache Site.urls.threadJSON({boardID: post.boardID, threadID: post.threadID}), ->
|
||||||
|
return unless @response
|
||||||
{posts} = @response
|
{posts} = @response
|
||||||
Build.spoilerRange[post.board.ID] = posts[0].custom_spoiler
|
Build.spoilerRange[post.board.ID] = posts[0].custom_spoiler
|
||||||
for postData in posts
|
for postData in posts
|
||||||
@ -90,8 +91,8 @@ Build.Test =
|
|||||||
c.log y.outerHTML
|
c.log y.outerHTML
|
||||||
|
|
||||||
for key of Config.filter when not key is 'General' and not (key is 'MD5' and post.board.ID is 'f')
|
for key of Config.filter when not key is 'General' and not (key is 'MD5' and post.board.ID is 'f')
|
||||||
val1 = Filter[key] obj
|
val1 = Filter.value key, obj
|
||||||
val2 = Filter[key] post2
|
val2 = Filter.value key, post2
|
||||||
if val1 isnt val2
|
if val1 isnt val2
|
||||||
fail = true
|
fail = true
|
||||||
c.log "#{post.fullID} has filter bug in #{key}"
|
c.log "#{post.fullID} has filter bug in #{key}"
|
||||||
|
|||||||
@ -25,18 +25,24 @@ Build =
|
|||||||
sameThread: (boardID, threadID) ->
|
sameThread: (boardID, threadID) ->
|
||||||
g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID
|
g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID
|
||||||
|
|
||||||
postURL: (boardID, threadID, postID) ->
|
threadURL: (boardID, threadID) ->
|
||||||
if Build.sameThread boardID, threadID
|
if boardID isnt g.BOARD.ID
|
||||||
"#p#{postID}"
|
"//#{BoardConfig.domain(boardID)}/#{boardID}/thread/#{threadID}"
|
||||||
|
else if g.VIEW isnt 'thread' or +threadID isnt g.THREADID
|
||||||
|
"/#{boardID}/thread/#{threadID}"
|
||||||
else
|
else
|
||||||
"/#{boardID}/thread/#{threadID}#p#{postID}"
|
''
|
||||||
|
|
||||||
parseJSON: (data, boardID) ->
|
postURL: (boardID, threadID, postID) ->
|
||||||
|
"#{Build.threadURL(boardID, threadID)}#p#{postID}"
|
||||||
|
|
||||||
|
parseJSON: (data, boardID, siteID) ->
|
||||||
o =
|
o =
|
||||||
# id
|
# id
|
||||||
ID: data.no
|
ID: data.no
|
||||||
threadID: data.resto or data.no
|
threadID: data.resto or data.no
|
||||||
boardID: boardID
|
boardID: boardID
|
||||||
|
siteID: siteID or Site.hostname
|
||||||
isReply: !!data.resto
|
isReply: !!data.resto
|
||||||
# thread status
|
# thread status
|
||||||
isSticky: !!data.sticky
|
isSticky: !!data.sticky
|
||||||
@ -44,7 +50,6 @@ Build =
|
|||||||
isArchived: !!data.archived
|
isArchived: !!data.archived
|
||||||
# file status
|
# file status
|
||||||
fileDeleted: !!data.filedeleted
|
fileDeleted: !!data.filedeleted
|
||||||
xa18: data.xa18
|
|
||||||
o.info =
|
o.info =
|
||||||
subject: Build.unescape data.sub
|
subject: Build.unescape data.sub
|
||||||
email: Build.unescape data.email
|
email: Build.unescape data.email
|
||||||
@ -80,6 +85,9 @@ Build =
|
|||||||
tag: data.tag
|
tag: data.tag
|
||||||
hasDownscale: !!data.m_img
|
hasDownscale: !!data.m_img
|
||||||
o.file.dimensions = "#{o.file.width}x#{o.file.height}" unless /\.pdf$/.test o.file.url
|
o.file.dimensions = "#{o.file.width}x#{o.file.height}" unless /\.pdf$/.test o.file.url
|
||||||
|
# Temporary JSON properties for events such as April 1 / Halloween
|
||||||
|
for key of data when key[0] is 'x'
|
||||||
|
o[key] = data[key]
|
||||||
o
|
o
|
||||||
|
|
||||||
parseComment: (html) ->
|
parseComment: (html) ->
|
||||||
@ -124,11 +132,12 @@ Build =
|
|||||||
capcodePlural = "#{capcodeLong}s"
|
capcodePlural = "#{capcodeLong}s"
|
||||||
capcodeDescription = "a 4chan #{capcodeLong}"
|
capcodeDescription = "a 4chan #{capcodeLong}"
|
||||||
|
|
||||||
postLink = Build.postURL boardID, threadID, ID
|
url = Build.threadURL boardID, threadID
|
||||||
|
postLink = "#{url}#p#{ID}"
|
||||||
quoteLink = if Build.sameThread boardID, threadID
|
quoteLink = if Build.sameThread boardID, threadID
|
||||||
"javascript:quote('#{+ID}');"
|
"javascript:quote('#{+ID}');"
|
||||||
else
|
else
|
||||||
"/#{boardID}/thread/#{threadID}#q#{ID}"
|
"#{url}#q#{ID}"
|
||||||
|
|
||||||
postInfo = <%= readHTML('PostInfo.html') %>
|
postInfo = <%= readHTML('PostInfo.html') %>
|
||||||
|
|
||||||
@ -156,12 +165,12 @@ Build =
|
|||||||
# Fix quotelinks
|
# Fix quotelinks
|
||||||
for quote in $$ '.quotelink', container
|
for quote in $$ '.quotelink', container
|
||||||
href = quote.getAttribute 'href'
|
href = quote.getAttribute 'href'
|
||||||
if (href[0] is '#') and !(Build.sameThread boardID, threadID)
|
if (href[0] is '#')
|
||||||
quote.href = ("/#{boardID}/thread/#{threadID}") + href
|
if !Build.sameThread(boardID, threadID)
|
||||||
else if (match = href.match /^\/([^\/]+)\/thread\/(\d+)/) and (Build.sameThread match[1], match[2])
|
quote.href = Build.threadURL(boardID, threadID) + href
|
||||||
quote.href = href.match(/(#[^#]*)?$/)[0] or '#'
|
else
|
||||||
else if /^\d+(#|$)/.test(href) and not (g.VIEW is 'thread' and g.BOARD.ID is boardID) # used on /f/
|
if (match = quote.href.match SW.yotsuba.regexp.quotelink) and (Build.sameThread match[1], match[2])
|
||||||
quote.href = "/#{boardID}/thread/#{href}"
|
quote.href = href.match(/(#[^#]*)?$/)[0] or '#'
|
||||||
|
|
||||||
container
|
container
|
||||||
|
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
?{email}{<a href="mailto:${encodeURIComponent(email).replace(/%40/g, "@")}" class="useremail">}
|
?{email}{<a href="mailto:${encodeURIComponent(email).replace(/%40/g, "@")}" class="useremail">}
|
||||||
<span class="name?{capcode}{ capcode}">${name}</span>
|
<span class="name?{capcode}{ capcode}">${name}</span>
|
||||||
?{tripcode}{ <span class="postertrip">${tripcode}</span>}
|
?{tripcode}{ <span class="postertrip">${tripcode}</span>}
|
||||||
|
?{o.xa19s}{ <span class="like-score">${o.xa19s}</span>}
|
||||||
?{pass}{ <span title="Pass user since ${pass}" class="n-pu"></span>}
|
?{pass}{ <span title="Pass user since ${pass}" class="n-pu"></span>}
|
||||||
?{capcode}{ <strong class="capcode hand id_${capcodeLC}" title="Highlight posts by ${capcodePlural}">## ${capcode}</strong>}
|
?{capcode}{ <strong class="capcode hand id_${capcodeLC}" title="Highlight posts by ${capcodePlural}">## ${capcode}</strong>}
|
||||||
?{!capcode && typeof o.xa18 !== "undefined"}{ <strong class="capcode hand n-atb n-atb-${o.xa18} id_at${o.xa18}"></strong>}
|
|
||||||
?{email}{</a>}
|
?{email}{</a>}
|
||||||
?{boardID === "f" && !o.isReply || capcodeDescription}{}{ }
|
?{boardID === "f" && !o.isReply || capcodeDescription}{}{ }
|
||||||
?{capcodeDescription}{ <img src="${staticPath}${capcodeLC}icon${gifIcon}" alt="${capcode} Icon" title="This user is ${capcodeDescription}." class="identityIcon retina">}
|
?{capcodeDescription}{ <img src="${staticPath}${capcodeLC}icon${gifIcon}" alt="${capcode} Icon" title="This user is ${capcodeDescription}." class="identityIcon retina">}
|
||||||
@ -19,6 +19,7 @@
|
|||||||
<span class="postNum?{!(boardID === "f" && !o.isReply)}{ desktop}">
|
<span class="postNum?{!(boardID === "f" && !o.isReply)}{ desktop}">
|
||||||
<a href="${postLink}" title="Link to this post">No.</a>
|
<a href="${postLink}" title="Link to this post">No.</a>
|
||||||
<a href="${quoteLink}" title="Reply to this post">${ID}</a>
|
<a href="${quoteLink}" title="Reply to this post">${ID}</a>
|
||||||
|
?{o.xa19l && o.isReply}{ <a data-cmd="like-post" href="#" class="like-btn">Like! ×${o.xa19l}</a>}
|
||||||
?{o.isSticky}{ <img src="${staticPath}sticky${gifIcon}" alt="Sticky" title="Sticky"?{boardID === "f"}{ style="height: 18px; width: 18px;"}{ class="stickyIcon retina"}>}
|
?{o.isSticky}{ <img src="${staticPath}sticky${gifIcon}" alt="Sticky" title="Sticky"?{boardID === "f"}{ style="height: 18px; width: 18px;"}{ class="stickyIcon retina"}>}
|
||||||
?{o.isClosed && !o.isArchived}{ <img src="${staticPath}closed${gifIcon}" alt="Closed" title="Closed"?{boardID === "f"}{ style="height: 18px; width: 18px;"}{ class="closedIcon retina"}>}
|
?{o.isClosed && !o.isArchived}{ <img src="${staticPath}closed${gifIcon}" alt="Closed" title="Closed"?{boardID === "f"}{ style="height: 18px; width: 18px;"}{ class="closedIcon retina"}>}
|
||||||
?{o.isArchived}{ <img src="${staticPath}archived${gifIcon}" alt="Archived" title="Archived" class="archivedIcon retina">}
|
?{o.isArchived}{ <img src="${staticPath}archived${gifIcon}" alt="Archived" title="Archived" class="archivedIcon retina">}
|
||||||
|
|||||||
@ -282,7 +282,7 @@ Header =
|
|||||||
return a.firstChild # Its text node.
|
return a.firstChild # Its text node.
|
||||||
|
|
||||||
if /-expired/.test t
|
if /-expired/.test t
|
||||||
if boardID not in ['b', 'f', 'trash', 'bant']
|
if BoardConfig.isArchived(boardID)
|
||||||
a.href = "//#{BoardConfig.domain(boardID)}/#{boardID}/archive"
|
a.href = "//#{BoardConfig.domain(boardID)}/#{boardID}/archive"
|
||||||
else
|
else
|
||||||
return a.firstChild # Its text node.
|
return a.firstChild # Its text node.
|
||||||
|
|||||||
@ -84,7 +84,7 @@ Index =
|
|||||||
@navLinks = $.el 'div', className: 'navLinks json-index'
|
@navLinks = $.el 'div', className: 'navLinks json-index'
|
||||||
$.extend @navLinks, <%= readHTML('NavLinks.html') %>
|
$.extend @navLinks, <%= readHTML('NavLinks.html') %>
|
||||||
$('.cataloglink a', @navLinks).href = CatalogLinks.catalog()
|
$('.cataloglink a', @navLinks).href = CatalogLinks.catalog()
|
||||||
$('.archlistlink', @navLinks).hidden = true if g.BOARD.ID in ['b', 'trash', 'bant']
|
$('.archlistlink', @navLinks).hidden = true unless BoardConfig.isArchived(g.BOARD.ID)
|
||||||
$.on $('#index-last-refresh a', @navLinks), 'click', @cb.refreshFront
|
$.on $('#index-last-refresh a', @navLinks), 'click', @cb.refreshFront
|
||||||
|
|
||||||
# Search field
|
# Search field
|
||||||
@ -573,48 +573,43 @@ Index =
|
|||||||
"#{hiddenCount} hidden threads"
|
"#{hiddenCount} hidden threads"
|
||||||
|
|
||||||
update: (firstTime) ->
|
update: (firstTime) ->
|
||||||
Index.req?.abort()
|
if (oldReq = Index.req)
|
||||||
Index.notice?.close()
|
delete Index.req
|
||||||
|
oldReq.abort()
|
||||||
|
|
||||||
if Conf['Index Refresh Notifications'] and d.readyState isnt 'loading'
|
if Conf['Index Refresh Notifications']
|
||||||
# Optional notification for manual refreshes
|
# Optional notification for manual refreshes
|
||||||
Index.notice = new Notice 'info', 'Refreshing index...'
|
Index.notice or= new Notice 'info', 'Refreshing index...'
|
||||||
else
|
else
|
||||||
# Also display notice if Index Refresh is taking too long
|
# Also display notice if Index Refresh is taking too long
|
||||||
now = Date.now()
|
Index.nTimeout or= setTimeout ->
|
||||||
$.ready ->
|
Index.notice or= new Notice 'info', 'Refreshing index...'
|
||||||
Index.nTimeout = setTimeout (->
|
, 3 * $.SECOND
|
||||||
if Index.req and !Index.notice
|
|
||||||
Index.notice = new Notice 'info', 'Refreshing index...'
|
|
||||||
), 3 * $.SECOND - (Date.now() - now)
|
|
||||||
|
|
||||||
# Hard refresh in case of incomplete page load.
|
# Hard refresh in case of incomplete page load.
|
||||||
if not firstTime and d.readyState isnt 'loading' and not $('.board + *')
|
if not firstTime and d.readyState isnt 'loading' and not $('.board + *')
|
||||||
location.reload()
|
location.reload()
|
||||||
return
|
return
|
||||||
|
|
||||||
Index.req = $.ajax "#{location.protocol}//a.4cdn.org/#{g.BOARD}/catalog.json",
|
Index.req = $.whenModified(
|
||||||
onabort: Index.load
|
Site.urls.catalogJSON({boardID: g.BOARD.ID}),
|
||||||
onloadend: Index.load
|
'Index',
|
||||||
,
|
Index.load
|
||||||
whenModified: 'Index'
|
)
|
||||||
$.addClass Index.button, 'fa-spin'
|
$.addClass Index.button, 'fa-spin'
|
||||||
|
|
||||||
load: (e) ->
|
load: ->
|
||||||
|
return if @ isnt Index.req # aborted
|
||||||
|
|
||||||
$.rmClass Index.button, 'fa-spin'
|
$.rmClass Index.button, 'fa-spin'
|
||||||
{req, notice, nTimeout} = Index
|
{notice, nTimeout} = Index
|
||||||
clearTimeout nTimeout if nTimeout
|
clearTimeout nTimeout if nTimeout
|
||||||
delete Index.nTimeout
|
delete Index.nTimeout
|
||||||
delete Index.req
|
delete Index.req
|
||||||
delete Index.notice
|
delete Index.notice
|
||||||
|
|
||||||
if e.type is 'abort'
|
if @status not in [200, 304]
|
||||||
req.onloadend = null
|
err = "Index refresh failed. #{if @status then "Error #{@statusText} (#{@status})" else 'Connection Error'}"
|
||||||
notice?.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
if req.status not in [200, 304]
|
|
||||||
err = "Index refresh failed. #{if req.status then "Error #{req.statusText} (#{req.status})" else 'Connection Error'}"
|
|
||||||
if notice
|
if notice
|
||||||
notice.setType 'warning'
|
notice.setType 'warning'
|
||||||
notice.el.lastElementChild.textContent = err
|
notice.el.lastElementChild.textContent = err
|
||||||
@ -624,13 +619,12 @@ Index =
|
|||||||
return
|
return
|
||||||
|
|
||||||
try
|
try
|
||||||
if req.status is 200
|
if @status is 200
|
||||||
Index.parse req.response
|
Index.parse @response
|
||||||
else if req.status is 304
|
else if @status is 304
|
||||||
Index.pageLoad()
|
Index.pageLoad()
|
||||||
catch err
|
catch err
|
||||||
c.error "Index failure: #{err.message}", err.stack
|
c.error "Index failure: #{err.message}", err.stack
|
||||||
# network error or non-JSON content for example.
|
|
||||||
if notice
|
if notice
|
||||||
notice.setType 'error'
|
notice.setType 'error'
|
||||||
notice.el.lastElementChild.textContent = 'Index refresh failed.'
|
notice.el.lastElementChild.textContent = 'Index refresh failed.'
|
||||||
@ -648,7 +642,7 @@ Index =
|
|||||||
notice.close()
|
notice.close()
|
||||||
|
|
||||||
timeEl = $ '#index-last-refresh time', Index.navLinks
|
timeEl = $ '#index-last-refresh time', Index.navLinks
|
||||||
timeEl.dataset.utc = Date.parse req.getResponseHeader 'Last-Modified'
|
timeEl.dataset.utc = Date.parse @getResponseHeader 'Last-Modified'
|
||||||
RelativeDates.update timeEl
|
RelativeDates.update timeEl
|
||||||
|
|
||||||
parse: (pages) ->
|
parse: (pages) ->
|
||||||
|
|||||||
@ -206,16 +206,20 @@ Settings =
|
|||||||
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
|
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
|
||||||
|
|
||||||
export: ->
|
export: ->
|
||||||
# Make sure to export the most recent data.
|
# Make sure to export the most recent data, but don't overwrite existing `Conf` object.
|
||||||
$.get Conf, (Conf) ->
|
Conf2 = {}
|
||||||
|
$.extend Conf2, Conf
|
||||||
|
$.get Conf2, (Conf2) ->
|
||||||
# Don't export cached JSON data.
|
# Don't export cached JSON data.
|
||||||
delete Conf['boardConfig']
|
delete Conf2['boardConfig']
|
||||||
(Settings.downloadExport {version: g.VERSION, date: Date.now(), Conf})
|
(Settings.downloadExport {version: g.VERSION, date: Date.now(), Conf: Conf2})
|
||||||
|
|
||||||
downloadExport: (data) ->
|
downloadExport: (data) ->
|
||||||
|
blob = new Blob [JSON.stringify(data, null, 2)], {type: 'application/json'}
|
||||||
|
url = URL.createObjectURL blob
|
||||||
a = $.el 'a',
|
a = $.el 'a',
|
||||||
download: "<%= meta.name %> v#{g.VERSION}-#{data.date}.json"
|
download: "<%= meta.name %> v#{g.VERSION}-#{data.date}.json"
|
||||||
href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}"
|
href: url
|
||||||
p = $ '.imp-exp-result', Settings.dialog
|
p = $ '.imp-exp-result', Settings.dialog
|
||||||
$.rmAll p
|
$.rmAll p
|
||||||
$.add p, a
|
$.add p, a
|
||||||
@ -473,6 +477,12 @@ Settings =
|
|||||||
[hostname, software] = line.split(' ')
|
[hostname, software] = line.split(' ')
|
||||||
siteProperties[hostname] = {software}
|
siteProperties[hostname] = {software}
|
||||||
set 'siteProperties', siteProperties
|
set 'siteProperties', siteProperties
|
||||||
|
if compareString < '00001.00014.00006.00006'
|
||||||
|
if data['sauces']?
|
||||||
|
set 'sauces', data['sauces'].replace(
|
||||||
|
/\/\/%\$1\.deviantart\.com\/gallery\/#\/d%\$2;regexp:\/\^\\w\+_by_\(\\w\+\)-d\(\[\\da-z\]\+\)\//g,
|
||||||
|
'//www.deviantart.com/gallery/#/d%$1%$2;regexp:/^\\w+_by_\\w+[_-]d([\\da-z]{6})\\b|^d([\\da-z]{6})-[\\da-z]{8}-/'
|
||||||
|
)
|
||||||
changes
|
changes
|
||||||
|
|
||||||
loadSettings: (data, cb) ->
|
loadSettings: (data, cb) ->
|
||||||
|
|||||||
@ -3,20 +3,29 @@
|
|||||||
Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions" target="_blank">regular expressions</a>, one per line.<br>
|
Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions" target="_blank">regular expressions</a>, one per line.<br>
|
||||||
Lines starting with a <code>#</code> will be ignored.<br>
|
Lines starting with a <code>#</code> will be ignored.<br>
|
||||||
For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>
|
For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>
|
||||||
MD5 filtering uses exact string matching, not regular expressions.
|
MD5 and Unique ID filtering use exact string matching, not regular expressions.
|
||||||
</p>
|
</p>
|
||||||
<ul>You can use these settings with each regular expression, separate them with semicolons:
|
<ul>You can use these settings with each regular expression, separate them with semicolons:
|
||||||
<li>
|
<li>
|
||||||
Per boards, separate them with commas. It is global if not specified. Use <code>sfw</code> and <code>nsfw</code> to reference all worksafe or not-worksafe boards.<br>
|
Per boards, separate them with commas. It is global if not specified. Use <code>sfw</code> and <code>nsfw</code> to reference all worksafe or not-worksafe boards.<br>
|
||||||
For example: <code>boards:a,jp;</code>.<br>
|
For example: <code>boards:a,jp;</code>.<br>
|
||||||
|
To specify boards on a particular site, put the beginning of the domain and a slash character before the list.<br>
|
||||||
|
Any initial <code>www.</code> should not be included, and all 4chan domains are considered <code>4chan.org</code>.<br>
|
||||||
|
For example: <code>boards:4:a,jp,sama:a,z;</code>.<br>
|
||||||
|
An asterisk can be used to specify all boards on a site.<br>
|
||||||
|
For example: <code>boards:4:*;</code>.<br>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
In case of a global rule or one that uses <code>sfw</code>/<code>nsfw</code>, select boards to be excluded from the filter.<br>
|
Select boards to be excluded from the filter. The syntax is the same as for the <code>boards:</code> option above.<br>
|
||||||
For example: <code>exclude:vg,v;</code>.
|
For example: <code>exclude:vg,v;</code>.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).<br>
|
Filter OPs only along with their threads (`only`) or replies only (`no`).<br>
|
||||||
For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.
|
For example: <code>op:only;</code> or <code>op:no;</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Filter only posts with files (`only`) or only posts without files (`no`).<br>
|
||||||
|
For example: <code>file:only;</code> or <code>file:no;</code>.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>
|
Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>
|
||||||
@ -30,10 +39,16 @@
|
|||||||
Highlighted OPs will have their threads put on top of the board index by default.<br>
|
Highlighted OPs will have their threads put on top of the board index by default.<br>
|
||||||
For example: <code>top:yes;</code> or <code>top:no;</code>.
|
For example: <code>top:yes;</code> or <code>top:no;</code>.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
Show a desktop notification instead of hiding.<br>
|
||||||
|
For example: <code>notify;</code>.
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Filters in the "General" section apply to multiple fields, by default <code>subject,name,filename,comment</code>.<br>
|
Filters in the "General" section apply to multiple fields, by default <code>subject,name,filename,comment</code>.<br>
|
||||||
The fields can be specified with the <code>type</code> option, separated by commas.<br>
|
The fields can be specified with the <code>type</code> option, separated by commas.<br>
|
||||||
For example: <code>type:@{filterTypes};</code>.
|
For example: <code>type:@{filterTypes};</code>.<br>
|
||||||
|
Types can also be combined with a <code>+</code> sign; this indicates the filter applies to the given fields joined by newlines.<br>
|
||||||
|
For example: <code>type:filename+filesize+dimensions;</code>.<br>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
<option value="tripcode">Tripcode</option>
|
<option value="tripcode">Tripcode</option>
|
||||||
<option value="capcode">Capcode</option>
|
<option value="capcode">Capcode</option>
|
||||||
<option value="pass">Pass Date</option>
|
<option value="pass">Pass Date</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
<option value="subject">Subject</option>
|
<option value="subject">Subject</option>
|
||||||
<option value="comment">Comment</option>
|
<option value="comment">Comment</option>
|
||||||
<option value="flag">Flag</option>
|
<option value="flag">Flag</option>
|
||||||
|
|||||||
@ -308,7 +308,8 @@ dragend = ->
|
|||||||
$.off d, 'mouseup', @up
|
$.off d, 'mouseup', @up
|
||||||
$.set "#{@id}.position", @style.cssText
|
$.set "#{@id}.position", @style.cssText
|
||||||
|
|
||||||
hoverstart = ({root, el, latestEvent, endEvents, height, cb, noRemove}) ->
|
hoverstart = ({root, el, latestEvent, endEvents, height, width, cb, noRemove}) ->
|
||||||
|
rect = root.getBoundingClientRect()
|
||||||
o = {
|
o = {
|
||||||
root
|
root
|
||||||
el
|
el
|
||||||
@ -320,7 +321,10 @@ hoverstart = ({root, el, latestEvent, endEvents, height, cb, noRemove}) ->
|
|||||||
clientHeight: doc.clientHeight
|
clientHeight: doc.clientHeight
|
||||||
clientWidth: doc.clientWidth
|
clientWidth: doc.clientWidth
|
||||||
height
|
height
|
||||||
|
width
|
||||||
noRemove
|
noRemove
|
||||||
|
clientX: (rect.left + rect.right) / 2
|
||||||
|
clientY: (rect.top + rect.bottom) / 2
|
||||||
}
|
}
|
||||||
o.hover = hover.bind o
|
o.hover = hover.bind o
|
||||||
o.hoverend = hoverend.bind o
|
o.hoverend = hoverend.bind o
|
||||||
@ -344,7 +348,8 @@ hoverstart.padding = 25
|
|||||||
hover = (e) ->
|
hover = (e) ->
|
||||||
@latestEvent = e
|
@latestEvent = e
|
||||||
height = (@height or @el.offsetHeight) + hoverstart.padding
|
height = (@height or @el.offsetHeight) + hoverstart.padding
|
||||||
{clientX, clientY} = e
|
width = (@width or @el.offsetWidth)
|
||||||
|
{clientX, clientY} = if Conf['Follow Cursor'] then e else @
|
||||||
|
|
||||||
top = if @isImage
|
top = if @isImage
|
||||||
Math.max 0, clientY * (@clientHeight - height) / @clientHeight
|
Math.max 0, clientY * (@clientHeight - height) / @clientHeight
|
||||||
@ -353,10 +358,10 @@ hover = (e) ->
|
|||||||
|
|
||||||
threshold = @clientWidth / 2
|
threshold = @clientWidth / 2
|
||||||
threshold = Math.max threshold, @clientWidth - 400 unless @isImage
|
threshold = Math.max threshold, @clientWidth - 400 unless @isImage
|
||||||
[left, right] = if clientX <= threshold
|
marginX = (if clientX <= threshold then clientX else @clientWidth - clientX) + 45
|
||||||
[clientX + 45 + 'px', '']
|
marginX = Math.min(marginX, @clientWidth - width) if @isImage
|
||||||
else
|
marginX += 'px'
|
||||||
['', @clientWidth - clientX + 45 + 'px']
|
[left, right] = if clientX <= threshold then [marginX, ''] else ['', marginX]
|
||||||
|
|
||||||
{style} = @
|
{style} = @
|
||||||
style.top = top + 'px'
|
style.top = top + 'px'
|
||||||
|
|||||||
@ -54,7 +54,7 @@ ImageCommon =
|
|||||||
clearTimeout timeoutID if delay?
|
clearTimeout timeoutID if delay?
|
||||||
cb URL
|
cb URL
|
||||||
|
|
||||||
$.ajax "#{location.protocol}//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
|
$.ajax Site.urls.threadJSON({boardID: post.boardID, threadID: post.threadID}), onloadend: ->
|
||||||
post.kill !post.isClone if @status is 404
|
post.kill !post.isClone if @status is 404
|
||||||
return redirect() if @status isnt 200
|
return redirect() if @status isnt 200
|
||||||
for postObj in @response.posts
|
for postObj in @response.posts
|
||||||
|
|||||||
@ -250,7 +250,7 @@ ImageExpand =
|
|||||||
mouseover: -> mousedown = false
|
mouseover: -> mousedown = false
|
||||||
mousedown: (e) -> mousedown = true if e.button is 0
|
mousedown: (e) -> mousedown = true if e.button is 0
|
||||||
mouseup: (e) -> mousedown = false if e.button is 0
|
mouseup: (e) -> mousedown = false if e.button is 0
|
||||||
mouseout: (e) -> ImageExpand.toggle(Get.postFromNode @) if mousedown and e.clientX <= @getBoundingClientRect().left
|
mouseout: (e) -> ImageExpand.toggle(Get.postFromNode @) if ((e.buttons & 1) or mousedown) and e.clientX <= @getBoundingClientRect().left
|
||||||
|
|
||||||
setupVideoCB: (post) ->
|
setupVideoCB: (post) ->
|
||||||
for eventName, cb of ImageExpand.videoCB
|
for eventName, cb of ImageExpand.videoCB
|
||||||
|
|||||||
@ -46,19 +46,22 @@ ImageHover =
|
|||||||
if Conf['Autoplay']
|
if Conf['Autoplay']
|
||||||
el.play()
|
el.play()
|
||||||
@currentTime = el.currentTime if @nodeName is 'VIDEO'
|
@currentTime = el.currentTime if @nodeName is 'VIDEO'
|
||||||
[width, height] = (+x for x in file.dimensions.split 'x')
|
if file.dimensions
|
||||||
{left, right} = @getBoundingClientRect()
|
[width, height] = (+x for x in file.dimensions.split 'x')
|
||||||
maxWidth = Math.max left, doc.clientWidth - right
|
maxWidth = doc.clientWidth
|
||||||
maxHeight = doc.clientHeight - UI.hover.padding
|
maxHeight = doc.clientHeight - UI.hover.padding
|
||||||
scale = Math.min 1, maxWidth / width, maxHeight / height
|
scale = Math.min 1, maxWidth / width, maxHeight / height
|
||||||
el.style.maxWidth = "#{scale * width}px"
|
width *= scale
|
||||||
el.style.maxHeight = "#{scale * height}px"
|
height *= scale
|
||||||
|
el.style.maxWidth = "#{width}px"
|
||||||
|
el.style.maxHeight = "#{height}px"
|
||||||
UI.hover
|
UI.hover
|
||||||
root: @
|
root: @
|
||||||
el: el
|
el: el
|
||||||
latestEvent: e
|
latestEvent: e
|
||||||
endEvents: 'mouseout click'
|
endEvents: 'mouseout click'
|
||||||
height: scale * height
|
height: height
|
||||||
|
width: width
|
||||||
noRemove: true
|
noRemove: true
|
||||||
cb: ->
|
cb: ->
|
||||||
$.off el, 'error', error
|
$.off el, 'error', error
|
||||||
|
|||||||
@ -57,7 +57,7 @@ Sauce =
|
|||||||
parts[key] = parts[key].replace /%(T?URL|IMG|[sh]?MD5|board|name|%|semi|\$\d+)/g, (orig, parameter) ->
|
parts[key] = parts[key].replace /%(T?URL|IMG|[sh]?MD5|board|name|%|semi|\$\d+)/g, (orig, parameter) ->
|
||||||
if parameter[0] is '$'
|
if parameter[0] is '$'
|
||||||
return orig unless matches
|
return orig unless matches
|
||||||
type = matches[parameter[1..]]
|
type = matches[parameter[1..]] or ''
|
||||||
else
|
else
|
||||||
type = Sauce.formatters[parameter] post, ext
|
type = Sauce.formatters[parameter] post, ext
|
||||||
if not type?
|
if not type?
|
||||||
|
|||||||
@ -111,7 +111,7 @@ Embedding =
|
|||||||
if service.queue.length >= service.batchSize
|
if service.queue.length >= service.batchSize
|
||||||
Embedding.flushTitles service
|
Embedding.flushTitles service
|
||||||
else
|
else
|
||||||
CrossOrigin.json service.api(uid), (-> Embedding.cb.title @, data)
|
CrossOrigin.cache service.api(uid), (-> Embedding.cb.title @, data)
|
||||||
|
|
||||||
flushTitles: (service) ->
|
flushTitles: (service) ->
|
||||||
{queue} = service
|
{queue} = service
|
||||||
@ -120,7 +120,7 @@ Embedding =
|
|||||||
cb = ->
|
cb = ->
|
||||||
Embedding.cb.title @, data for data in queue
|
Embedding.cb.title @, data for data in queue
|
||||||
return
|
return
|
||||||
CrossOrigin.json service.api(data.uid for data in queue), cb
|
CrossOrigin.cache service.api(data.uid for data in queue), cb
|
||||||
|
|
||||||
preview: (data) ->
|
preview: (data) ->
|
||||||
{key, uid, link} = data
|
{key, uid, link} = data
|
||||||
@ -275,7 +275,7 @@ Embedding =
|
|||||||
el = $.el 'pre',
|
el = $.el 'pre',
|
||||||
hidden: true
|
hidden: true
|
||||||
id: "gist-embed-#{counter++}"
|
id: "gist-embed-#{counter++}"
|
||||||
CrossOrigin.json "https://api.github.com/gists/#{a.dataset.uid}", ->
|
CrossOrigin.cache "https://api.github.com/gists/#{a.dataset.uid}", ->
|
||||||
el.textContent = Object.values(@response.files)[0].content
|
el.textContent = Object.values(@response.files)[0].content
|
||||||
el.className = 'prettyprint'
|
el.className = 'prettyprint'
|
||||||
$.global ->
|
$.global ->
|
||||||
|
|||||||
@ -45,7 +45,7 @@ ArchiveLink =
|
|||||||
value = if type is 'country'
|
value = if type is 'country'
|
||||||
post.info.flagCode or post.info.flagCodeTroll
|
post.info.flagCode or post.info.flagCodeTroll
|
||||||
else
|
else
|
||||||
Filter[type] post
|
Filter.value type, post
|
||||||
# We want to parse the exact same stuff as the filter does already.
|
# We want to parse the exact same stuff as the filter does already.
|
||||||
return false unless value
|
return false unless value
|
||||||
el.href = Redirect.to 'search',
|
el.href = Redirect.to 'search',
|
||||||
|
|||||||
@ -82,12 +82,15 @@ DeleteLink =
|
|||||||
$.ajax $.id('delform').action.replace("/#{g.BOARD}/", "/#{post.board}/"),
|
$.ajax $.id('delform').action.replace("/#{g.BOARD}/", "/#{post.board}/"),
|
||||||
responseType: 'document'
|
responseType: 'document'
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
onload: -> DeleteLink.load link, post, fileOnly, @response
|
onloadend: -> DeleteLink.load link, post, fileOnly, @response
|
||||||
onerror: -> DeleteLink.error link, post
|
|
||||||
,
|
|
||||||
form: $.formData form
|
form: $.formData form
|
||||||
|
|
||||||
load: (link, post, fileOnly, resDoc) ->
|
load: (link, post, fileOnly, resDoc) ->
|
||||||
|
unless resDoc
|
||||||
|
new Notice 'warning', 'Connection error, please retry.', 20
|
||||||
|
$.on link, 'click', DeleteLink.toggle if post.fullID is DeleteLink.post.fullID
|
||||||
|
return
|
||||||
|
|
||||||
link.textContent = DeleteLink.linkText fileOnly
|
link.textContent = DeleteLink.linkText fileOnly
|
||||||
if resDoc.title is '4chan - Banned' # Ban/warn check
|
if resDoc.title is '4chan - Banned' # Ban/warn check
|
||||||
el = $.el 'span', <%= html('You can't delete posts because you are <a href="//www.4chan.org/banned" target="_blank">banned</a>.') %>
|
el = $.el 'span', <%= html('You can't delete posts because you are <a href="//www.4chan.org/banned" target="_blank">banned</a>.') %>
|
||||||
@ -106,10 +109,6 @@ DeleteLink =
|
|||||||
(post.origin or post).kill fileOnly
|
(post.origin or post).kill fileOnly
|
||||||
link.textContent = 'Deleted' if post.fullID is DeleteLink.post.fullID
|
link.textContent = 'Deleted' if post.fullID is DeleteLink.post.fullID
|
||||||
|
|
||||||
error: (link, post) ->
|
|
||||||
new Notice 'warning', 'Connection error, please retry.', 20
|
|
||||||
$.on link, 'click', DeleteLink.toggle if post.fullID is DeleteLink.post.fullID
|
|
||||||
|
|
||||||
cooldown:
|
cooldown:
|
||||||
seconds: {}
|
seconds: {}
|
||||||
|
|
||||||
|
|||||||
@ -75,7 +75,7 @@ CatalogLinks =
|
|||||||
return
|
return
|
||||||
|
|
||||||
catalog: (board=g.BOARD.ID) ->
|
catalog: (board=g.BOARD.ID) ->
|
||||||
if Conf['External Catalog'] and board in ['a', 'c', 'g', 'biz', 'k', 'm', 'o', 'p', 'v', 'vg', 'vr', 'w', 'wg', 'cm', '3', 'adv', 'an', 'asp', 'cgl', 'ck', 'co', 'diy', 'fa', 'fit', 'gd', 'int', 'jp', 'lit', 'mlp', 'mu', 'n', 'out', 'po', 'sci', 'sp', 'tg', 'toy', 'trv', 'tv', 'vp', 'wsg', 'x', 'f', 'pol', 's4s', 'lgbt']
|
if Conf['External Catalog'] and board in ['3', 'a', 'adv', 'an', 'asp', 'biz', 'c', 'cgl', 'ck', 'cm', 'co', 'diy', 'f', 'fa', 'fit', 'g', 'gd', 'his', 'i', 'int', 'jp', 'k', 'lgbt', 'lit', 'm', 'mlp', 'mu', 'n', 'news', 'o', 'out', 'p', 'po', 'pol', 's4s', 'sci', 'sp', 'tg', 'toy', 'trv', 'tv', 'v', 'vg', 'vip', 'vp', 'vr', 'w', 'wg', 'wsg', 'wsr', 'x']
|
||||||
"//catalog.neet.tv/#{board}/"
|
"//catalog.neet.tv/#{board}/"
|
||||||
else if Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog']
|
else if Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog']
|
||||||
if location.hostname in ['boards.4chan.org', 'boards.4channel.org'] and g.BOARD.ID is board and g.VIEW is 'index' then '#catalog' else "//#{BoardConfig.domain(board)}/#{board}/#catalog"
|
if location.hostname in ['boards.4chan.org', 'boards.4channel.org'] and g.BOARD.ID is board and g.VIEW is 'index' then '#catalog' else "//#{BoardConfig.domain(board)}/#{board}/#catalog"
|
||||||
|
|||||||
@ -2,9 +2,6 @@ ExpandComment =
|
|||||||
init: ->
|
init: ->
|
||||||
return if g.VIEW isnt 'index' or !Conf['Comment Expansion'] or Conf['JSON Index']
|
return if g.VIEW isnt 'index' or !Conf['Comment Expansion'] or Conf['JSON Index']
|
||||||
|
|
||||||
@callbacks.push Fourchan.code if g.BOARD.ID is 'g'
|
|
||||||
@callbacks.push Fourchan.math if g.BOARD.ID is 'sci'
|
|
||||||
|
|
||||||
Callbacks.Post.push
|
Callbacks.Post.push
|
||||||
name: 'Comment Expansion'
|
name: 'Comment Expansion'
|
||||||
cb: @node
|
cb: @node
|
||||||
@ -26,7 +23,7 @@ ExpandComment =
|
|||||||
return
|
return
|
||||||
return if not (a = $ '.abbr > a', post.nodes.comment)
|
return if not (a = $ '.abbr > a', post.nodes.comment)
|
||||||
a.textContent = "Post No.#{post} Loading..."
|
a.textContent = "Post No.#{post} Loading..."
|
||||||
$.cache "#{location.protocol}//a.4cdn.org#{a.pathname.split(/\/+/).splice(0,4).join('/')}.json", -> ExpandComment.parse @, a, post
|
$.cache Site.urls.threadJSON({boardID: post.boardID, threadID: post.threadID}), -> ExpandComment.parse @, a, post
|
||||||
|
|
||||||
contract: (post) ->
|
contract: (post) ->
|
||||||
return unless post.nodes.shortComment
|
return unless post.nodes.shortComment
|
||||||
@ -38,7 +35,7 @@ ExpandComment =
|
|||||||
parse: (req, a, post) ->
|
parse: (req, a, post) ->
|
||||||
{status} = req
|
{status} = req
|
||||||
unless status in [200, 304]
|
unless status in [200, 304]
|
||||||
a.textContent = "Error #{req.statusText} (#{status})"
|
a.textContent = if status then "Error #{req.statusText} (#{status})" else 'Connection Error'
|
||||||
return
|
return
|
||||||
|
|
||||||
posts = req.response.posts
|
posts = req.response.posts
|
||||||
|
|||||||
@ -18,7 +18,9 @@ ExpandThread =
|
|||||||
disconnect: (refresh) ->
|
disconnect: (refresh) ->
|
||||||
return if g.VIEW is 'thread' or !Conf['Thread Expansion']
|
return if g.VIEW is 'thread' or !Conf['Thread Expansion']
|
||||||
for threadID, status of ExpandThread.statuses
|
for threadID, status of ExpandThread.statuses
|
||||||
status.req?.abort()
|
if (oldReq = status.req)
|
||||||
|
delete status.req
|
||||||
|
oldReq.abort()
|
||||||
delete ExpandThread.statuses[threadID]
|
delete ExpandThread.statuses[threadID]
|
||||||
|
|
||||||
$.off d, 'IndexRefreshInternal', @onIndexRefresh unless refresh
|
$.off d, 'IndexRefreshInternal', @onIndexRefresh unless refresh
|
||||||
@ -52,15 +54,17 @@ ExpandThread =
|
|||||||
expand: (thread, a) ->
|
expand: (thread, a) ->
|
||||||
ExpandThread.statuses[thread] = status = {}
|
ExpandThread.statuses[thread] = status = {}
|
||||||
a.textContent = Build.summaryText '...', a.textContent.match(/\d+/g)...
|
a.textContent = Build.summaryText '...', a.textContent.match(/\d+/g)...
|
||||||
status.req = $.cache "#{location.protocol}//a.4cdn.org/#{thread.board}/thread/#{thread}.json", ->
|
status.req = $.cache Site.urls.threadJSON({boardID: thread.board.ID, threadID: thread.ID}), ->
|
||||||
|
return if @ isnt status.req # aborted
|
||||||
delete status.req
|
delete status.req
|
||||||
ExpandThread.parse @, thread, a
|
ExpandThread.parse @, thread, a
|
||||||
|
|
||||||
contract: (thread, a, threadRoot) ->
|
contract: (thread, a, threadRoot) ->
|
||||||
status = ExpandThread.statuses[thread]
|
status = ExpandThread.statuses[thread]
|
||||||
delete ExpandThread.statuses[thread]
|
delete ExpandThread.statuses[thread]
|
||||||
if status.req
|
if (oldReq = status.req)
|
||||||
status.req.abort()
|
delete status.req
|
||||||
|
oldReq.abort()
|
||||||
a.textContent = Build.summaryText '+', a.textContent.match(/\d+/g)... if a
|
a.textContent = Build.summaryText '+', a.textContent.match(/\d+/g)... if a
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -89,7 +93,7 @@ ExpandThread =
|
|||||||
|
|
||||||
parse: (req, thread, a) ->
|
parse: (req, thread, a) ->
|
||||||
if req.status not in [200, 304]
|
if req.status not in [200, 304]
|
||||||
a.textContent = "Error #{req.statusText} (#{req.status})"
|
a.textContent = if req.status then "Error #{req.statusText} (#{req.status})" else 'Connection Error'
|
||||||
return
|
return
|
||||||
|
|
||||||
Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler
|
Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
Fourchan =
|
Fourchan =
|
||||||
init: ->
|
init: ->
|
||||||
return unless g.VIEW in ['index', 'thread', 'archive']
|
return unless Site.software is 'yotsuba' and g.VIEW in ['index', 'thread', 'archive']
|
||||||
|
BoardConfig.ready @initBoard
|
||||||
|
Main.ready @initReady
|
||||||
|
|
||||||
if g.BOARD.ID is 'g'
|
initBoard: ->
|
||||||
|
if g.BOARD.config.code_tags
|
||||||
$.on window, 'prettyprint:cb', (e) ->
|
$.on window, 'prettyprint:cb', (e) ->
|
||||||
return if not (post = g.posts[e.detail.ID])
|
return if not (post = g.posts[e.detail.ID])
|
||||||
return if not (pre = $$('.prettyprint', post.nodes.comment)[e.detail.i])
|
return if not (pre = $$('.prettyprint', post.nodes.comment)[e.detail.i])
|
||||||
@ -21,10 +24,12 @@ Fourchan =
|
|||||||
}, false);
|
}, false);
|
||||||
'''
|
'''
|
||||||
Callbacks.Post.push
|
Callbacks.Post.push
|
||||||
name: 'Parse /g/ code'
|
name: 'Parse [code] tags'
|
||||||
cb: @code
|
cb: Fourchan.code
|
||||||
|
g.posts.forEach (post) -> Callbacks.Post.execute post, ['Parse [code] tags'], true
|
||||||
|
ExpandComment.callbacks.push Fourchan.code
|
||||||
|
|
||||||
if g.BOARD.ID is 'sci'
|
if g.BOARD.config.math_tags
|
||||||
$.global ->
|
$.global ->
|
||||||
window.addEventListener 'mathjax', (e) ->
|
window.addEventListener 'mathjax', (e) ->
|
||||||
if window.MathJax
|
if window.MathJax
|
||||||
@ -40,16 +45,18 @@ Fourchan =
|
|||||||
, false
|
, false
|
||||||
, false
|
, false
|
||||||
Callbacks.Post.push
|
Callbacks.Post.push
|
||||||
name: 'Parse /sci/ math'
|
name: 'Parse [math] tags'
|
||||||
cb: @math
|
cb: Fourchan.math
|
||||||
|
g.posts.forEach (post) -> Callbacks.Post.execute post, ['Parse [math] tags'], true
|
||||||
|
ExpandComment.callbacks.push Fourchan.math
|
||||||
|
|
||||||
# Disable 4chan's ID highlighting (replaced by IDHighlight) and reported post hiding.
|
# Disable 4chan's ID highlighting (replaced by IDHighlight) and reported post hiding.
|
||||||
Main.ready ->
|
initReady: ->
|
||||||
$.global ->
|
$.global ->
|
||||||
window.clickable_ids = false
|
window.clickable_ids = false
|
||||||
for node in document.querySelectorAll '.posteruid, .capcode'
|
for node in document.querySelectorAll '.posteruid, .capcode'
|
||||||
node.removeEventListener 'click', window.idClick, false
|
node.removeEventListener 'click', window.idClick, false
|
||||||
return
|
return
|
||||||
|
|
||||||
code: ->
|
code: ->
|
||||||
return if @isClone
|
return if @isClone
|
||||||
|
|||||||
@ -109,6 +109,9 @@ Keybinds =
|
|||||||
when Conf['Toggle thread watcher']
|
when Conf['Toggle thread watcher']
|
||||||
return unless ThreadWatcher.enabled
|
return unless ThreadWatcher.enabled
|
||||||
ThreadWatcher.toggleWatcher()
|
ThreadWatcher.toggleWatcher()
|
||||||
|
when Conf['Toggle threading']
|
||||||
|
return unless QuoteThreading.ready
|
||||||
|
QuoteThreading.toggleThreading()
|
||||||
when Conf['Mark thread read']
|
when Conf['Mark thread read']
|
||||||
return unless g.VIEW is 'index' and thread and UnreadIndex.enabled
|
return unless g.VIEW is 'index' and thread and UnreadIndex.enabled
|
||||||
UnreadIndex.markRead.call threadRoot
|
UnreadIndex.markRead.call threadRoot
|
||||||
|
|||||||
@ -76,14 +76,13 @@ Report =
|
|||||||
results = []
|
results = []
|
||||||
for [name, url] in urls
|
for [name, url] in urls
|
||||||
do (name, url) ->
|
do (name, url) ->
|
||||||
$.ajax url,
|
$.ajax url, {
|
||||||
responseType: 'json'
|
|
||||||
onloadend: ->
|
onloadend: ->
|
||||||
results.push [name, @response or {error: ''}]
|
results.push [name, @response or {error: ''}]
|
||||||
if results.length is urls.length
|
if results.length is urls.length
|
||||||
cb results
|
cb results
|
||||||
,
|
form
|
||||||
{form}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
archiveResults: (results) ->
|
archiveResults: (results) ->
|
||||||
|
|||||||
@ -75,9 +75,11 @@ ThreadStats =
|
|||||||
$.addClass ThreadStats.pageCountEl, 'warning'
|
$.addClass ThreadStats.pageCountEl, 'warning'
|
||||||
return
|
return
|
||||||
ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 2 * $.MINUTE
|
ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 2 * $.MINUTE
|
||||||
$.ajax "#{location.protocol}//a.4cdn.org/#{ThreadStats.thread.board}/threads.json", onload: ThreadStats.onThreadsLoad,
|
$.whenModified(
|
||||||
whenModified: 'ThreadStats'
|
Site.urls.threadsListJSON({boardID: ThreadStats.thread.board}),
|
||||||
bypassCache: true
|
'ThreadStats',
|
||||||
|
ThreadStats.onThreadsLoad
|
||||||
|
)
|
||||||
|
|
||||||
onThreadsLoad: ->
|
onThreadsLoad: ->
|
||||||
if @status is 200
|
if @status is 200
|
||||||
|
|||||||
@ -128,17 +128,17 @@ ThreadUpdater =
|
|||||||
$.cb.value.call @ if e
|
$.cb.value.call @ if e
|
||||||
|
|
||||||
load: ->
|
load: ->
|
||||||
{req} = ThreadUpdater
|
return if @ isnt ThreadUpdater.req # aborted
|
||||||
switch req.status
|
switch @status
|
||||||
when 200
|
when 200
|
||||||
ThreadUpdater.parse req
|
ThreadUpdater.parse @
|
||||||
if ThreadUpdater.thread.isArchived
|
if ThreadUpdater.thread.isArchived
|
||||||
ThreadUpdater.kill()
|
ThreadUpdater.kill()
|
||||||
else
|
else
|
||||||
ThreadUpdater.setInterval()
|
ThreadUpdater.setInterval()
|
||||||
when 404
|
when 404
|
||||||
# XXX workaround for 4chan sending false 404s
|
# XXX workaround for 4chan sending false 404s
|
||||||
$.ajax "#{location.protocol}//a.4cdn.org/#{ThreadUpdater.thread.board}/catalog.json", onloadend: ->
|
$.ajax Site.urls.catalogJSON({boardID: ThreadUpdater.thread.board.ID}), onloadend: ->
|
||||||
if @status is 200
|
if @status is 200
|
||||||
confirmed = true
|
confirmed = true
|
||||||
for page in @response
|
for page in @response
|
||||||
@ -151,9 +151,9 @@ ThreadUpdater =
|
|||||||
if confirmed
|
if confirmed
|
||||||
ThreadUpdater.kill()
|
ThreadUpdater.kill()
|
||||||
else
|
else
|
||||||
ThreadUpdater.error req
|
ThreadUpdater.error @
|
||||||
else
|
else
|
||||||
ThreadUpdater.error req
|
ThreadUpdater.error @
|
||||||
|
|
||||||
kill: ->
|
kill: ->
|
||||||
ThreadUpdater.thread.kill()
|
ThreadUpdater.thread.kill()
|
||||||
@ -230,13 +230,15 @@ ThreadUpdater =
|
|||||||
update: ->
|
update: ->
|
||||||
clearTimeout ThreadUpdater.timeoutID
|
clearTimeout ThreadUpdater.timeoutID
|
||||||
ThreadUpdater.set 'timer', '...', 'loading'
|
ThreadUpdater.set 'timer', '...', 'loading'
|
||||||
ThreadUpdater.req?.abort()
|
if (oldReq = ThreadUpdater.req)
|
||||||
ThreadUpdater.req = $.ajax "#{location.protocol}//a.4cdn.org/#{ThreadUpdater.thread.board}/thread/#{ThreadUpdater.thread}.json",
|
delete ThreadUpdater.req
|
||||||
onloadend: ThreadUpdater.cb.load
|
oldReq.abort()
|
||||||
timeout: $.MINUTE
|
ThreadUpdater.req = $.whenModified(
|
||||||
,
|
Site.urls.threadJSON({boardID: ThreadUpdater.thread.board.ID, threadID: ThreadUpdater.thread.ID}),
|
||||||
whenModified: 'ThreadUpdater'
|
'ThreadUpdater',
|
||||||
bypassCache: true
|
ThreadUpdater.cb.load,
|
||||||
|
{timeout: $.MINUTE}
|
||||||
|
)
|
||||||
|
|
||||||
updateThreadStatus: (type, status) ->
|
updateThreadStatus: (type, status) ->
|
||||||
return if not (hasChanged = ThreadUpdater.thread["is#{type}"] isnt status)
|
return if not (hasChanged = ThreadUpdater.thread["is#{type}"] isnt status)
|
||||||
|
|||||||
@ -10,8 +10,8 @@ ThreadWatcher =
|
|||||||
className: 'fa fa-eye'
|
className: 'fa fa-eye'
|
||||||
|
|
||||||
@db = new DataBoard 'watchedThreads', @refresh, true
|
@db = new DataBoard 'watchedThreads', @refresh, true
|
||||||
|
@dbLM = new DataBoard 'watcherLastModified', null, true
|
||||||
@dialog = UI.dialog 'thread-watcher', <%= readHTML('ThreadWatcher.html') %>
|
@dialog = UI.dialog 'thread-watcher', <%= readHTML('ThreadWatcher.html') %>
|
||||||
|
|
||||||
@status = $ '#watcher-status', @dialog
|
@status = $ '#watcher-status', @dialog
|
||||||
@list = @dialog.lastElementChild
|
@list = @dialog.lastElementChild
|
||||||
@refreshButton = $ '.refresh', @dialog
|
@refreshButton = $ '.refresh', @dialog
|
||||||
@ -41,6 +41,7 @@ ThreadWatcher =
|
|||||||
|
|
||||||
Header.addShortcut 'watcher', sc, 510
|
Header.addShortcut 'watcher', sc, 510
|
||||||
|
|
||||||
|
ThreadWatcher.initLastModified()
|
||||||
ThreadWatcher.fetchAuto()
|
ThreadWatcher.fetchAuto()
|
||||||
$.on window, 'visibilitychange focus', -> $.queueTask ThreadWatcher.fetchAuto
|
$.on window, 'visibilitychange focus', -> $.queueTask ThreadWatcher.fetchAuto
|
||||||
|
|
||||||
@ -92,16 +93,16 @@ ThreadWatcher =
|
|||||||
href: 'javascript:;'
|
href: 'javascript:;'
|
||||||
className: 'watch-thread-link'
|
className: 'watch-thread-link'
|
||||||
$.before $('input', @nodes.info), toggler
|
$.before $('input', @nodes.info), toggler
|
||||||
|
siteID = Site.hostname
|
||||||
boardID = @board.ID
|
boardID = @board.ID
|
||||||
threadID = @thread.ID
|
threadID = @thread.ID
|
||||||
data = ThreadWatcher.db.get {boardID, threadID}
|
data = ThreadWatcher.db.get {siteID, boardID, threadID}
|
||||||
ThreadWatcher.setToggler toggler, !!data
|
ThreadWatcher.setToggler toggler, !!data
|
||||||
$.on toggler, 'click', ThreadWatcher.cb.toggle
|
$.on toggler, 'click', ThreadWatcher.cb.toggle
|
||||||
# Add missing excerpt for threads added by Auto Watch
|
# Add missing excerpt for threads added by Auto Watch
|
||||||
if data and not data.excerpt?
|
if data and not data.excerpt?
|
||||||
$.queueTask =>
|
$.queueTask =>
|
||||||
ThreadWatcher.db.extend {boardID, threadID, val: {excerpt: Get.threadExcerpt @thread}}
|
ThreadWatcher.update siteID, boardID, threadID, val: {excerpt: Get.threadExcerpt @thread}
|
||||||
ThreadWatcher.refresh()
|
|
||||||
|
|
||||||
catalogNode: ->
|
catalogNode: ->
|
||||||
$.addClass @nodes.root, 'watched' if ThreadWatcher.isWatched @thread
|
$.addClass @nodes.root, 'watched' if ThreadWatcher.isWatched @thread
|
||||||
@ -153,13 +154,14 @@ ThreadWatcher =
|
|||||||
for threadID, data of db.data[siteID].boards[boardID] when not data?.isDead and "#{boardID}.#{threadID}" not in e.detail.threads
|
for threadID, data of db.data[siteID].boards[boardID] when not data?.isDead and "#{boardID}.#{threadID}" not in e.detail.threads
|
||||||
# Don't prune threads that have yet to appear in index.
|
# Don't prune threads that have yet to appear in index.
|
||||||
continue unless e.detail.threads.some (fullID) -> +fullID.split('.')[1] > threadID
|
continue unless e.detail.threads.some (fullID) -> +fullID.split('.')[1] > threadID
|
||||||
nKilled++
|
|
||||||
if Conf['Auto Prune'] or not (data and typeof data is 'object') # corrupt data
|
if Conf['Auto Prune'] or not (data and typeof data is 'object') # corrupt data
|
||||||
db.delete {boardID, threadID}
|
db.delete {boardID, threadID}
|
||||||
|
nKilled++
|
||||||
|
else if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
|
||||||
|
ThreadWatcher.fetchStatus {siteID, boardID, threadID, data}
|
||||||
else
|
else
|
||||||
db.extend {boardID, threadID, val: {isDead: true}}
|
db.extend {boardID, threadID, val: {isDead: true, page: undefined, lastPage: undefined, unread: undefined, quotingYou: undefined}}
|
||||||
if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
|
nKilled++
|
||||||
ThreadWatcher.fetchStatus {siteID, boardID, threadID, data}
|
|
||||||
ThreadWatcher.refresh() if nKilled
|
ThreadWatcher.refresh() if nKilled
|
||||||
onThreadRefresh: (e) ->
|
onThreadRefresh: (e) ->
|
||||||
thread = g.threads[e.detail.threadID]
|
thread = g.threads[e.detail.threadID]
|
||||||
@ -170,6 +172,30 @@ ThreadWatcher =
|
|||||||
requests: []
|
requests: []
|
||||||
fetched: 0
|
fetched: 0
|
||||||
|
|
||||||
|
fetch: (url, {siteID, force}, args, cb) ->
|
||||||
|
if ThreadWatcher.requests.length is 0
|
||||||
|
ThreadWatcher.status.textContent = '...'
|
||||||
|
$.addClass ThreadWatcher.refreshButton, 'fa-spin'
|
||||||
|
onloadend = ->
|
||||||
|
return if @finished
|
||||||
|
@finished = true
|
||||||
|
ThreadWatcher.fetched++
|
||||||
|
if ThreadWatcher.fetched is ThreadWatcher.requests.length
|
||||||
|
ThreadWatcher.clearRequests()
|
||||||
|
else
|
||||||
|
ThreadWatcher.status.textContent = "#{Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)}%"
|
||||||
|
cb.apply @, args
|
||||||
|
ajax = if siteID is Site.hostname then $.ajax else CrossOrigin.ajax
|
||||||
|
if force
|
||||||
|
delete $.lastModified.ThreadWatcher?[url]
|
||||||
|
req = $.whenModified(
|
||||||
|
url,
|
||||||
|
'ThreadWatcher',
|
||||||
|
onloadend,
|
||||||
|
{timeout: $.MINUTE, ajax}
|
||||||
|
)
|
||||||
|
ThreadWatcher.requests.push req
|
||||||
|
|
||||||
clearRequests: ->
|
clearRequests: ->
|
||||||
ThreadWatcher.requests = []
|
ThreadWatcher.requests = []
|
||||||
ThreadWatcher.fetched = 0
|
ThreadWatcher.fetched = 0
|
||||||
@ -177,78 +203,137 @@ ThreadWatcher =
|
|||||||
$.rmClass ThreadWatcher.refreshButton, 'fa-spin'
|
$.rmClass ThreadWatcher.refreshButton, 'fa-spin'
|
||||||
|
|
||||||
abort: ->
|
abort: ->
|
||||||
for req in ThreadWatcher.requests when req.readyState isnt 4 # DONE
|
delete ThreadWatcher.syncing
|
||||||
|
for req in ThreadWatcher.requests when !req.finished
|
||||||
|
req.finished = true
|
||||||
req.abort()
|
req.abort()
|
||||||
ThreadWatcher.clearRequests()
|
ThreadWatcher.clearRequests()
|
||||||
|
|
||||||
|
initLastModified: ->
|
||||||
|
lm = ($.lastModified['ThreadWatcher'] or= {})
|
||||||
|
for siteID, boards of ThreadWatcher.dbLM.data
|
||||||
|
for boardID, data of boards.boards
|
||||||
|
if ThreadWatcher.db.get {siteID, boardID}
|
||||||
|
for url, date of data
|
||||||
|
lm[url] = date
|
||||||
|
else
|
||||||
|
ThreadWatcher.dbLM.delete {siteID, boardID}
|
||||||
|
return
|
||||||
|
|
||||||
fetchAuto: ->
|
fetchAuto: ->
|
||||||
clearTimeout ThreadWatcher.timeout
|
clearTimeout ThreadWatcher.timeout
|
||||||
return unless Conf['Auto Update Thread Watcher']
|
return unless Conf['Auto Update Thread Watcher']
|
||||||
{db} = ThreadWatcher
|
{db} = ThreadWatcher
|
||||||
interval = if ThreadWatcher.unreadEnabled and Conf['Show Unread Count'] then 5 * $.MINUTE else 2 * $.HOUR
|
interval = if Conf['Show Page'] or (ThreadWatcher.unreadEnabled and Conf['Show Unread Count']) then 5 * $.MINUTE else 2 * $.HOUR
|
||||||
now = Date.now()
|
now = Date.now()
|
||||||
unless now - interval < (db.data.lastChecked or 0) <= now or d.hidden or not d.hasFocus()
|
unless now - interval < (db.data.lastChecked or 0) <= now or d.hidden or not d.hasFocus()
|
||||||
ThreadWatcher.fetchAllStatus()
|
ThreadWatcher.fetchAllStatus()
|
||||||
db.setLastChecked()
|
|
||||||
ThreadWatcher.timeout = setTimeout ThreadWatcher.fetchAuto, interval
|
ThreadWatcher.timeout = setTimeout ThreadWatcher.fetchAuto, interval
|
||||||
|
|
||||||
buttonFetchAll: ->
|
buttonFetchAll: ->
|
||||||
if ThreadWatcher.requests.length
|
if ThreadWatcher.syncing or ThreadWatcher.requests.length
|
||||||
ThreadWatcher.abort()
|
ThreadWatcher.abort()
|
||||||
else
|
else
|
||||||
ThreadWatcher.fetchAllStatus()
|
ThreadWatcher.fetchAllStatus()
|
||||||
|
|
||||||
fetchAllStatus: ->
|
fetchAllStatus: ->
|
||||||
|
ThreadWatcher.status.textContent = '...'
|
||||||
|
$.addClass ThreadWatcher.refreshButton, 'fa-spin'
|
||||||
|
ThreadWatcher.syncing = true
|
||||||
dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter((x) -> x)
|
dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter((x) -> x)
|
||||||
n = 0
|
n = 0
|
||||||
for db in dbs
|
for dbi in dbs
|
||||||
db.forceSync ->
|
dbi.forceSync ->
|
||||||
if (++n) is dbs.length
|
if (++n) is dbs.length
|
||||||
threads = ThreadWatcher.getAll()
|
return if !ThreadWatcher.syncing # aborted
|
||||||
for thread in threads
|
delete ThreadWatcher.syncing
|
||||||
ThreadWatcher.fetchStatus thread
|
# XXX On vichan boards, last_modified field of threads.json does not account for sage posts.
|
||||||
return
|
# Occasionally check replies field of catalog.json to find these posts.
|
||||||
|
{db} = ThreadWatcher
|
||||||
|
now = Date.now()
|
||||||
|
deep = !(now - 2 * $.HOUR < (db.data.lastChecked2 or 0) <= now)
|
||||||
|
boards = ThreadWatcher.getAll(true)
|
||||||
|
for board in boards
|
||||||
|
ThreadWatcher.fetchBoard board, deep
|
||||||
|
db.setLastChecked()
|
||||||
|
db.setLastChecked('lastChecked2') if deep
|
||||||
|
if ThreadWatcher.fetched is ThreadWatcher.requests.length
|
||||||
|
ThreadWatcher.clearRequests()
|
||||||
|
|
||||||
fetchStatus: (thread, force) ->
|
fetchBoard: (board, deep) ->
|
||||||
{siteID, boardID, threadID, data} = thread
|
return unless board.some (thread) -> !thread.data.isDead
|
||||||
|
force = Conf['Show Page'] and board.some((thread) -> !thread.data.page? and !thread.data.isDead and thread.data.last isnt -1)
|
||||||
|
{siteID, boardID} = board[0]
|
||||||
|
software = Conf['siteProperties'][siteID]?.software
|
||||||
|
urlF = if deep and software is 'tinyboard' then 'catalogJSON' else 'threadsListJSON'
|
||||||
|
url = SW[software]?.urls[urlF]?({siteID, boardID})
|
||||||
|
return unless url
|
||||||
|
ThreadWatcher.fetch url, {siteID, force}, [board, url], ThreadWatcher.parseBoard
|
||||||
|
|
||||||
|
parseBoard: (board, url) ->
|
||||||
|
return unless @status is 200
|
||||||
|
{siteID, boardID} = board[0]
|
||||||
|
software = Conf['siteProperties'][siteID]?.software
|
||||||
|
lmDate = @getResponseHeader('Last-Modified')
|
||||||
|
ThreadWatcher.dbLM.extend {siteID, boardID, val: $.item(url, lmDate)}
|
||||||
|
threads = {}
|
||||||
|
pageLength = 0
|
||||||
|
nThreads = 0
|
||||||
|
oldest = null
|
||||||
|
try
|
||||||
|
pageLength = @response[0]?.threads.length or 0
|
||||||
|
for page, i in @response
|
||||||
|
for item in page.threads
|
||||||
|
threads[item.no] =
|
||||||
|
page: i + 1
|
||||||
|
index: nThreads
|
||||||
|
modified: item.last_modified
|
||||||
|
replies: item.replies
|
||||||
|
nThreads++
|
||||||
|
if !oldest? or item.no < oldest
|
||||||
|
oldest = item.no
|
||||||
|
catch
|
||||||
|
for thread in board
|
||||||
|
ThreadWatcher.fetchStatus thread
|
||||||
|
for thread in board
|
||||||
|
{threadID, data} = thread
|
||||||
|
if threads[threadID]
|
||||||
|
{page, index, modified, replies} = threads[threadID]
|
||||||
|
if Conf['Show Page']
|
||||||
|
lastPage = if SW[software]?.isPrunedByAge?({siteID, boardID})
|
||||||
|
threadID is oldest
|
||||||
|
else
|
||||||
|
index >= nThreads - pageLength
|
||||||
|
ThreadWatcher.update siteID, boardID, threadID, {page, lastPage}
|
||||||
|
if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
|
||||||
|
if modified isnt data.modified or (replies? and replies isnt data.replies)
|
||||||
|
ThreadWatcher.db.extend {siteID, boardID, threadID, val: {modified}}
|
||||||
|
ThreadWatcher.fetchStatus thread
|
||||||
|
else
|
||||||
|
if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
|
||||||
|
ThreadWatcher.fetchStatus thread
|
||||||
|
else
|
||||||
|
ThreadWatcher.update siteID, boardID, threadID, {isDead: true}
|
||||||
|
return
|
||||||
|
|
||||||
|
fetchStatus: (thread) ->
|
||||||
|
{siteID, boardID, threadID, data, force} = thread
|
||||||
software = Conf['siteProperties'][siteID]?.software
|
software = Conf['siteProperties'][siteID]?.software
|
||||||
url = SW[software]?.urls.threadJSON?({siteID, boardID, threadID})
|
url = SW[software]?.urls.threadJSON?({siteID, boardID, threadID})
|
||||||
return unless url
|
return unless url
|
||||||
return if data.isDead and not force
|
return if data.isDead and not force
|
||||||
return if data.last is -1 # 404 or no JSON API
|
return if data.last is -1 # 404 or no JSON API
|
||||||
if ThreadWatcher.requests.length is 0
|
ThreadWatcher.fetch url, {siteID, force}, [thread], ThreadWatcher.parseStatus
|
||||||
ThreadWatcher.status.textContent = '...'
|
|
||||||
$.addClass ThreadWatcher.refreshButton, 'fa-spin'
|
|
||||||
if Site.hasCORS?(url) or url.split('/')[...3].join('/') is location.origin
|
|
||||||
req = $.ajax url,
|
|
||||||
onloadend: ->
|
|
||||||
ThreadWatcher.parseStatus.call @, thread
|
|
||||||
timeout: $.MINUTE
|
|
||||||
,
|
|
||||||
whenModified: if force then false else 'ThreadWatcher'
|
|
||||||
else
|
|
||||||
req = {abort: () -> req.aborted = true}
|
|
||||||
CrossOrigin.json url, ->
|
|
||||||
return if req.aborted
|
|
||||||
ThreadWatcher.parseStatus.call @, thread
|
|
||||||
, true, $.MINUTE
|
|
||||||
ThreadWatcher.requests.push req
|
|
||||||
|
|
||||||
parseStatus: ({siteID, boardID, threadID, data}) ->
|
parseStatus: ({siteID, boardID, threadID, data}) ->
|
||||||
ThreadWatcher.fetched++
|
|
||||||
if ThreadWatcher.fetched is ThreadWatcher.requests.length
|
|
||||||
ThreadWatcher.clearRequests()
|
|
||||||
else
|
|
||||||
ThreadWatcher.status.textContent = "#{Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)}%"
|
|
||||||
|
|
||||||
software = Conf['siteProperties'][siteID]?.software
|
software = Conf['siteProperties'][siteID]?.software
|
||||||
|
|
||||||
if @status is 200 and @response
|
if @status is 200 and @response
|
||||||
last = @response.posts[@response.posts.length-1].no
|
last = @response.posts[@response.posts.length-1].no
|
||||||
|
replies = @response.posts.length-1
|
||||||
isDead = !!@response.posts[0].archived
|
isDead = !!@response.posts[0].archived
|
||||||
if isDead and Conf['Auto Prune']
|
if isDead and Conf['Auto Prune']
|
||||||
ThreadWatcher.db.delete {siteID, boardID, threadID}
|
ThreadWatcher.rm siteID, boardID, threadID
|
||||||
ThreadWatcher.refresh()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
return if last is data.last and isDead is data.isDead
|
return if last is data.last and isDead is data.isDead
|
||||||
@ -264,7 +349,7 @@ ThreadWatcher =
|
|||||||
|
|
||||||
unread++
|
unread++
|
||||||
|
|
||||||
if !quotingYou and !Conf['Require OP Quote Link'] and youOP and not Filter.isHidden(Build.parseJSON postObj, boardID)
|
if !quotingYou and !Conf['Require OP Quote Link'] and youOP and not Filter.isHidden(Build.parseJSON postObj, boardID, siteID)
|
||||||
quotingYou = true
|
quotingYou = true
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -282,31 +367,27 @@ ThreadWatcher =
|
|||||||
}
|
}
|
||||||
quotesYou = true
|
quotesYou = true
|
||||||
break
|
break
|
||||||
if quotesYou and not Filter.isHidden(Build.parseJSON postObj, boardID)
|
if quotesYou and not Filter.isHidden(Build.parseJSON postObj, boardID, siteID)
|
||||||
quotingYou = true
|
quotingYou = true
|
||||||
|
|
||||||
updated = (isDead isnt data.isDead or unread isnt data.unread or quotingYou isnt data.quotingYou)
|
ThreadWatcher.update siteID, boardID, threadID, {last, replies, isDead, unread, quotingYou}
|
||||||
ThreadWatcher.db.extend {siteID, boardID, threadID, val: {last, isDead, unread, quotingYou}}
|
|
||||||
ThreadWatcher.refresh() if updated
|
|
||||||
|
|
||||||
else if @status is 404
|
else if @status is 404
|
||||||
if SW[software].mayLackJSON and !data.last?
|
if SW[software].mayLackJSON and !data.last?
|
||||||
ThreadWatcher.db.extend {siteID, boardID, threadID, val: {last: -1}, rm: ['unread', 'quotingYou']}
|
ThreadWatcher.update siteID, boardID, threadID, {last: -1}
|
||||||
else if Conf['Auto Prune']
|
|
||||||
ThreadWatcher.db.delete {siteID, boardID, threadID}
|
|
||||||
else
|
else
|
||||||
ThreadWatcher.db.extend {siteID, boardID, threadID, val: {isDead: true}, rm: ['unread', 'quotingYou']}
|
ThreadWatcher.update siteID, boardID, threadID, {isDead: true}
|
||||||
|
|
||||||
ThreadWatcher.refresh()
|
getAll: (groupByBoard) ->
|
||||||
|
|
||||||
getAll: ->
|
|
||||||
all = []
|
all = []
|
||||||
for siteID, boards of ThreadWatcher.db.data
|
for siteID, boards of ThreadWatcher.db.data
|
||||||
for boardID, threads of boards.boards
|
for boardID, threads of boards.boards
|
||||||
if Conf['Current Board'] and (siteID isnt Site.hostname or boardID isnt g.BOARD.ID)
|
if Conf['Current Board'] and (siteID isnt Site.hostname or boardID isnt g.BOARD.ID)
|
||||||
continue
|
continue
|
||||||
|
if groupByBoard
|
||||||
|
all.push (cont = [])
|
||||||
for threadID, data of threads when data and typeof data is 'object'
|
for threadID, data of threads when data and typeof data is 'object'
|
||||||
all.push {siteID, boardID, threadID, data}
|
(if groupByBoard then cont else all).push {siteID, boardID, threadID, data}
|
||||||
all
|
all
|
||||||
|
|
||||||
makeLine: (siteID, boardID, threadID, data) ->
|
makeLine: (siteID, boardID, threadID, data) ->
|
||||||
@ -326,6 +407,12 @@ ThreadWatcher =
|
|||||||
title: excerpt
|
title: excerpt
|
||||||
className: 'watcher-link'
|
className: 'watcher-link'
|
||||||
|
|
||||||
|
if Conf['Show Page'] and data.page?
|
||||||
|
page = $.el 'span',
|
||||||
|
textContent: "[#{data.page}]"
|
||||||
|
className: 'watcher-page'
|
||||||
|
$.add link, page
|
||||||
|
|
||||||
if ThreadWatcher.unreadEnabled and Conf['Show Unread Count'] and data.unread?
|
if ThreadWatcher.unreadEnabled and Conf['Show Unread Count'] and data.unread?
|
||||||
count = $.el 'span',
|
count = $.el 'span',
|
||||||
textContent: "(#{data.unread})"
|
textContent: "(#{data.unread})"
|
||||||
@ -343,6 +430,9 @@ ThreadWatcher =
|
|||||||
div.dataset.siteID = siteID
|
div.dataset.siteID = siteID
|
||||||
$.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}"
|
$.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}"
|
||||||
$.addClass div, 'dead-thread' if data.isDead
|
$.addClass div, 'dead-thread' if data.isDead
|
||||||
|
if Conf['Show Page']
|
||||||
|
$.addClass div, 'last-page' if data.lastPage
|
||||||
|
div.dataset.page = data.page if data.page?
|
||||||
if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
|
if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
|
||||||
$.addClass div, 'replies-read' if data.unread is 0
|
$.addClass div, 'replies-read' if data.unread is 0
|
||||||
$.addClass div, 'replies-unread' if data.unread
|
$.addClass div, 'replies-unread' if data.unread
|
||||||
@ -384,9 +474,6 @@ ThreadWatcher =
|
|||||||
$.add list, nodes
|
$.add list, nodes
|
||||||
|
|
||||||
ThreadWatcher.refreshIcon()
|
ThreadWatcher.refreshIcon()
|
||||||
for refresher in ThreadWatcher.menu.refreshers
|
|
||||||
refresher()
|
|
||||||
return
|
|
||||||
|
|
||||||
refresh: ->
|
refresh: ->
|
||||||
ThreadWatcher.build()
|
ThreadWatcher.build()
|
||||||
@ -407,19 +494,19 @@ ThreadWatcher =
|
|||||||
ThreadWatcher.shortcut.classList.toggle className, !!$(".#{className}", ThreadWatcher.dialog)
|
ThreadWatcher.shortcut.classList.toggle className, !!$(".#{className}", ThreadWatcher.dialog)
|
||||||
return
|
return
|
||||||
|
|
||||||
update: (boardID, threadID, newData) ->
|
update: (siteID, boardID, threadID, newData) ->
|
||||||
siteID = Site.hostname
|
return if not (data = ThreadWatcher.db?.get {siteID, boardID, threadID})
|
||||||
return if not (data = ThreadWatcher.db?.get {boardID, threadID})
|
|
||||||
if newData.isDead and Conf['Auto Prune']
|
if newData.isDead and Conf['Auto Prune']
|
||||||
ThreadWatcher.db.delete {boardID, threadID}
|
ThreadWatcher.rm siteID, boardID, threadID
|
||||||
ThreadWatcher.refresh()
|
|
||||||
return
|
return
|
||||||
|
if newData.isDead or newData.last is -1
|
||||||
|
for key in ['page', 'lastPage', 'unread', 'quotingyou'] when key not of newData
|
||||||
|
newData[key] = undefined
|
||||||
n = 0
|
n = 0
|
||||||
n++ for key, val of newData when data[key] isnt val
|
n++ for key, val of newData when data[key] isnt val
|
||||||
return unless n
|
return unless n
|
||||||
return if not (data = ThreadWatcher.db.get {boardID, threadID})
|
ThreadWatcher.db.extend {siteID, boardID, threadID, val: newData}
|
||||||
ThreadWatcher.db.extend {boardID, threadID, val: newData}
|
if (line = $ "#watched-threads > [data-site-i-d='#{siteID}'][data-full-i-d='#{boardID}.#{threadID}']", ThreadWatcher.dialog)
|
||||||
if line = $ "#watched-threads > [data-site-i-d='#{siteID}'][data-full-i-d='#{boardID}.#{threadID}']", ThreadWatcher.dialog
|
|
||||||
newLine = ThreadWatcher.makeLine siteID, boardID, threadID, data
|
newLine = ThreadWatcher.makeLine siteID, boardID, threadID, data
|
||||||
$.replace line, newLine
|
$.replace line, newLine
|
||||||
ThreadWatcher.refreshIcon()
|
ThreadWatcher.refreshIcon()
|
||||||
@ -431,8 +518,8 @@ ThreadWatcher =
|
|||||||
if Conf['Auto Prune']
|
if Conf['Auto Prune']
|
||||||
ThreadWatcher.db.delete {boardID, threadID}
|
ThreadWatcher.db.delete {boardID, threadID}
|
||||||
return cb()
|
return cb()
|
||||||
return cb() if data.isDead and not (data.unread? or data.quotingYou?)
|
return cb() if data.isDead and not (data.page? or data.lastPage? or data.unread? or data.quotingYou?)
|
||||||
ThreadWatcher.db.extend {boardID, threadID, val: {isDead: true}, rm: ['unread', 'quotingYou']}, cb
|
ThreadWatcher.db.extend {boardID, threadID, val: {isDead: true, page: undefined, lastPage: undefined, unread: undefined, quotingYou: undefined}}, cb
|
||||||
|
|
||||||
toggle: (thread) ->
|
toggle: (thread) ->
|
||||||
siteID = Site.hostname
|
siteID = Site.hostname
|
||||||
@ -459,15 +546,17 @@ ThreadWatcher =
|
|||||||
addRaw: (boardID, threadID, data) ->
|
addRaw: (boardID, threadID, data) ->
|
||||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||||
ThreadWatcher.refresh()
|
ThreadWatcher.refresh()
|
||||||
if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
|
thread = {siteID: Site.hostname, boardID, threadID, data, force: true}
|
||||||
ThreadWatcher.fetchStatus {siteID: Site.hostname, boardID, threadID, data}, true
|
if Conf['Show Page'] and !data.isDead
|
||||||
|
ThreadWatcher.fetchBoard [thread]
|
||||||
|
else if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
|
||||||
|
ThreadWatcher.fetchStatus thread
|
||||||
|
|
||||||
rm: (siteID, boardID, threadID) ->
|
rm: (siteID, boardID, threadID) ->
|
||||||
ThreadWatcher.db.delete {siteID, boardID, threadID}
|
ThreadWatcher.db.delete {siteID, boardID, threadID}
|
||||||
ThreadWatcher.refresh()
|
ThreadWatcher.refresh()
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
refreshers: []
|
|
||||||
init: ->
|
init: ->
|
||||||
return if !Conf['Thread Watcher']
|
return if !Conf['Thread Watcher']
|
||||||
menu = @menu = new UI.Menu 'thread watcher'
|
menu = @menu = new UI.Menu 'thread watcher'
|
||||||
@ -482,53 +571,52 @@ ThreadWatcher =
|
|||||||
Header.menu.addEntry
|
Header.menu.addEntry
|
||||||
el: entryEl
|
el: entryEl
|
||||||
order: 60
|
order: 60
|
||||||
|
open: ->
|
||||||
|
[addClass, rmClass, text] = if !!ThreadWatcher.db.get {boardID: g.BOARD.ID, threadID: g.THREADID}
|
||||||
|
['unwatch-thread', 'watch-thread', 'Unwatch thread']
|
||||||
|
else
|
||||||
|
['watch-thread', 'unwatch-thread', 'Watch thread']
|
||||||
|
$.addClass entryEl, addClass
|
||||||
|
$.rmClass entryEl, rmClass
|
||||||
|
entryEl.textContent = text
|
||||||
|
true
|
||||||
$.on entryEl, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"]
|
$.on entryEl, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"]
|
||||||
@refreshers.push ->
|
|
||||||
[addClass, rmClass, text] = if $ '.current', ThreadWatcher.list
|
|
||||||
['unwatch-thread', 'watch-thread', 'Unwatch thread']
|
|
||||||
else
|
|
||||||
['watch-thread', 'unwatch-thread', 'Watch thread']
|
|
||||||
$.addClass entryEl, addClass
|
|
||||||
$.rmClass entryEl, rmClass
|
|
||||||
entryEl.textContent = text
|
|
||||||
|
|
||||||
addMenuEntries: ->
|
addMenuEntries: ->
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
# `Open all` entry
|
# `Open all` entry
|
||||||
entries.push
|
entries.push
|
||||||
|
text: 'Open all threads'
|
||||||
cb: ThreadWatcher.cb.openAll
|
cb: ThreadWatcher.cb.openAll
|
||||||
entry:
|
open: ->
|
||||||
el: $.el 'a',
|
@el.classList.toggle 'disabled', !ThreadWatcher.list.firstElementChild
|
||||||
textContent: 'Open all threads'
|
true
|
||||||
refresh: -> (if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled'
|
|
||||||
|
|
||||||
# `Prune dead threads` entry
|
# `Prune dead threads` entry
|
||||||
entries.push
|
entries.push
|
||||||
|
text: 'Prune dead threads'
|
||||||
cb: ThreadWatcher.cb.pruneDeads
|
cb: ThreadWatcher.cb.pruneDeads
|
||||||
entry:
|
open: ->
|
||||||
|
@el.classList.toggle 'disabled', !$('.dead-thread', ThreadWatcher.list)
|
||||||
|
true
|
||||||
|
|
||||||
|
for {text, cb, open} in entries
|
||||||
|
entry =
|
||||||
el: $.el 'a',
|
el: $.el 'a',
|
||||||
textContent: 'Prune dead threads'
|
textContent: text
|
||||||
refresh: -> (if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled'
|
href: 'javascript:;'
|
||||||
|
$.on entry.el, 'click', cb
|
||||||
# `Settings` entries:
|
entry.open = open.bind(entry)
|
||||||
subEntries = []
|
|
||||||
for name, conf of Config.threadWatcher
|
|
||||||
subEntries.push @createSubEntry name, conf[1]
|
|
||||||
entries.push
|
|
||||||
entry:
|
|
||||||
el: $.el 'span',
|
|
||||||
textContent: 'Settings'
|
|
||||||
subEntries: subEntries
|
|
||||||
|
|
||||||
for {entry, cb, refresh} in entries
|
|
||||||
entry.el.href = 'javascript:;' if entry.el.nodeName is 'A'
|
|
||||||
$.on entry.el, 'click', cb if cb
|
|
||||||
@refreshers.push refresh.bind entry if refresh
|
|
||||||
@menu.addEntry entry
|
@menu.addEntry entry
|
||||||
|
|
||||||
|
# Settings checkbox entries:
|
||||||
|
for name, conf of Config.threadWatcher
|
||||||
|
@addCheckbox name, conf[1]
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
createSubEntry: (name, desc) ->
|
addCheckbox: (name, desc) ->
|
||||||
entry =
|
entry =
|
||||||
type: 'thread watcher'
|
type: 'thread watcher'
|
||||||
el: UI.checkbox name, name.replace(' Thread Watcher', '')
|
el: UI.checkbox name, name.replace(' Thread Watcher', '')
|
||||||
@ -539,6 +627,6 @@ ThreadWatcher =
|
|||||||
$.addClass entry.el, 'disabled'
|
$.addClass entry.el, 'disabled'
|
||||||
entry.el.title += '\n[Remember Last Read Post is disabled.]'
|
entry.el.title += '\n[Remember Last Read Post is disabled.]'
|
||||||
$.on input, 'change', $.cb.checked
|
$.on input, 'change', $.cb.checked
|
||||||
$.on input, 'change', ThreadWatcher.refresh if name in ['Current Board', 'Show Unread Count', 'Show Site Prefix']
|
$.on input, 'change', ThreadWatcher.refresh if name in ['Current Board', 'Show Page', 'Show Unread Count', 'Show Site Prefix']
|
||||||
$.on input, 'change', ThreadWatcher.fetchAuto if name in ['Show Unread Count', 'Auto Update Thread Watcher']
|
$.on input, 'change', ThreadWatcher.fetchAuto if name in ['Show Page', 'Show Unread Count', 'Auto Update Thread Watcher']
|
||||||
entry
|
@menu.addEntry entry
|
||||||
|
|||||||
@ -125,9 +125,9 @@ Unread =
|
|||||||
Unread.openNotification post
|
Unread.openNotification post
|
||||||
return
|
return
|
||||||
|
|
||||||
openNotification: (post) ->
|
openNotification: (post, predicate=' replied to you') ->
|
||||||
return unless Header.areNotificationsEnabled
|
return unless Header.areNotificationsEnabled
|
||||||
notif = new Notification "#{post.info.nameBlock} replied to you",
|
notif = new Notification "#{post.info.nameBlock}#{predicate}",
|
||||||
body: post.commentDisplay()
|
body: post.commentDisplay()
|
||||||
icon: Favicon.logo
|
icon: Favicon.logo
|
||||||
notif.onclick = ->
|
notif.onclick = ->
|
||||||
@ -238,7 +238,7 @@ Unread =
|
|||||||
saveThreadWatcherCount: $.debounce 2 * $.SECOND, ->
|
saveThreadWatcherCount: $.debounce 2 * $.SECOND, ->
|
||||||
$.forceSync 'Remember Last Read Post'
|
$.forceSync 'Remember Last Read Post'
|
||||||
if Conf['Remember Last Read Post'] and (!Unread.thread.isDead or Unread.thread.isArchived)
|
if Conf['Remember Last Read Post'] and (!Unread.thread.isDead or Unread.thread.isArchived)
|
||||||
ThreadWatcher.update Unread.thread.board.ID, Unread.thread.ID,
|
ThreadWatcher.update Site.hostname, Unread.thread.board.ID, Unread.thread.ID,
|
||||||
isDead: Unread.thread.isDead
|
isDead: Unread.thread.isDead
|
||||||
unread: Unread.posts.size
|
unread: Unread.posts.size
|
||||||
quotingYou: !!(if !Conf['Require OP Quote Link'] and QuoteYou.isYou(Unread.thread.OP) then Unread.posts.size else Unread.postsQuotingYou.size)
|
quotingYou: !!(if !Conf['Require OP Quote Link'] and QuoteYou.isYou(Unread.thread.OP) then Unread.posts.size else Unread.postsQuotingYou.size)
|
||||||
|
|||||||
@ -94,13 +94,10 @@ UnreadIndex =
|
|||||||
|
|
||||||
markRead: ->
|
markRead: ->
|
||||||
thread = Get.threadFromNode @
|
thread = Get.threadFromNode @
|
||||||
if Index.enabled
|
lastPost = if Index.enabled then Index.lastPost(thread.ID) else 0
|
||||||
lastPost = Index.lastPost(thread.ID)
|
thread.posts.forEach (post) ->
|
||||||
else
|
if post.ID > lastPost and !post.isFetchedQuote
|
||||||
lastPost = 0
|
lastPost = post.ID
|
||||||
thread.posts.forEach (post) ->
|
|
||||||
if post.ID > lastPost and !post.isFetchedQuote
|
|
||||||
lastPost = post.ID
|
|
||||||
UnreadIndex.lastReadPost[thread.fullID] = lastPost
|
UnreadIndex.lastReadPost[thread.fullID] = lastPost
|
||||||
UnreadIndex.db.set
|
UnreadIndex.db.set
|
||||||
boardID: thread.board.ID
|
boardID: thread.board.ID
|
||||||
@ -108,6 +105,6 @@ UnreadIndex =
|
|||||||
val: lastPost
|
val: lastPost
|
||||||
$.rm UnreadIndex.hr[thread.fullID]
|
$.rm UnreadIndex.hr[thread.fullID]
|
||||||
thread.nodes.root.classList.remove 'unread-thread'
|
thread.nodes.root.classList.remove 'unread-thread'
|
||||||
ThreadWatcher.update thread.board.ID, thread.ID,
|
ThreadWatcher.update Site.hostname, thread.board.ID, thread.ID,
|
||||||
unread: 0
|
unread: 0
|
||||||
quotingYou: false
|
quotingYou: false
|
||||||
|
|||||||
@ -707,41 +707,29 @@ QR =
|
|||||||
options =
|
options =
|
||||||
responseType: 'document'
|
responseType: 'document'
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
onload: QR.response
|
onloadend: QR.response
|
||||||
onerror: ->
|
|
||||||
# On connection error, the post most likely didn't go through.
|
|
||||||
# If the post did go through, it should be stopped by the duplicate reply cooldown.
|
|
||||||
delete QR.req
|
|
||||||
Captcha.cache.save QR.currentCaptcha if QR.currentCaptcha
|
|
||||||
delete QR.currentCaptcha
|
|
||||||
post.unlock()
|
|
||||||
QR.cooldown.auto = true
|
|
||||||
QR.cooldown.addDelay post, 2
|
|
||||||
QR.status()
|
|
||||||
QR.error QR.connectionError()
|
|
||||||
extra =
|
|
||||||
form: $.formData formData
|
form: $.formData formData
|
||||||
if Conf['Show Upload Progress']
|
if Conf['Show Upload Progress']
|
||||||
extra.upCallbacks =
|
options.onprogress = (e) ->
|
||||||
onload: ->
|
return if @ isnt QR.req?.upload # aborted
|
||||||
|
if e.loaded < e.total
|
||||||
|
# Uploading...
|
||||||
|
QR.req.progress = "#{Math.round e.loaded / e.total * 100}%"
|
||||||
|
else
|
||||||
# Upload done, waiting for server response.
|
# Upload done, waiting for server response.
|
||||||
QR.req.isUploadFinished = true
|
QR.req.isUploadFinished = true
|
||||||
QR.req.progress = '...'
|
QR.req.progress = '...'
|
||||||
QR.status()
|
QR.status()
|
||||||
onprogress: (e) ->
|
|
||||||
# Uploading...
|
|
||||||
QR.req.progress = "#{Math.round e.loaded / e.total * 100}%"
|
|
||||||
QR.status()
|
|
||||||
|
|
||||||
cb = (response) ->
|
cb = (response) ->
|
||||||
if response?
|
if response?
|
||||||
QR.currentCaptcha = response
|
QR.currentCaptcha = response
|
||||||
if response.challenge?
|
if response.challenge?
|
||||||
extra.form.append 'recaptcha_challenge_field', response.challenge
|
options.form.append 'recaptcha_challenge_field', response.challenge
|
||||||
extra.form.append 'recaptcha_response_field', response.response
|
options.form.append 'recaptcha_response_field', response.response
|
||||||
else
|
else
|
||||||
extra.form.append 'g-recaptcha-response', response.response
|
options.form.append 'g-recaptcha-response', response.response
|
||||||
QR.req = $.ajax "https://sys.#{location.hostname.split('.')[1]}.org/#{g.BOARD}/post", options, extra
|
QR.req = $.ajax "https://sys.#{location.hostname.split('.')[1]}.org/#{g.BOARD}/post", options
|
||||||
QR.req.progress = '...'
|
QR.req.progress = '...'
|
||||||
|
|
||||||
if typeof captcha is 'function'
|
if typeof captcha is 'function'
|
||||||
@ -765,20 +753,19 @@ QR =
|
|||||||
QR.status()
|
QR.status()
|
||||||
|
|
||||||
response: ->
|
response: ->
|
||||||
{req} = QR
|
return if @ isnt QR.req # aborted
|
||||||
delete QR.req
|
delete QR.req
|
||||||
|
|
||||||
post = QR.posts[0]
|
post = QR.posts[0]
|
||||||
post.unlock()
|
post.unlock()
|
||||||
|
|
||||||
resDoc = req.response
|
if (err = @response?.getElementById 'errmsg') # error!
|
||||||
if (err = resDoc.getElementById 'errmsg') # error!
|
|
||||||
$('a', err)?.target = '_blank' # duplicate image link
|
$('a', err)?.target = '_blank' # duplicate image link
|
||||||
else if (connErr = resDoc.title isnt 'Post successful!')
|
else if (connErr = (!@response or @response.title isnt 'Post successful!'))
|
||||||
err = QR.connectionError()
|
err = QR.connectionError()
|
||||||
Captcha.cache.save QR.currentCaptcha if QR.currentCaptcha
|
Captcha.cache.save QR.currentCaptcha if QR.currentCaptcha
|
||||||
else if req.status isnt 200
|
else if @status isnt 200
|
||||||
err = "Error #{req.statusText} (#{req.status})"
|
err = "Error #{@statusText} (#{@status})"
|
||||||
|
|
||||||
delete QR.currentCaptcha
|
delete QR.currentCaptcha
|
||||||
|
|
||||||
@ -810,7 +797,7 @@ QR =
|
|||||||
QR.error err
|
QR.error err
|
||||||
return
|
return
|
||||||
|
|
||||||
h1 = $ 'h1', resDoc
|
h1 = $ 'h1', @response
|
||||||
|
|
||||||
[_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/
|
[_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/
|
||||||
postID = +postID
|
postID = +postID
|
||||||
@ -880,14 +867,14 @@ QR =
|
|||||||
cb()
|
cb()
|
||||||
else
|
else
|
||||||
setTimeout check, attempts * $.SECOND
|
setTimeout check, attempts * $.SECOND
|
||||||
,
|
responseType: 'text'
|
||||||
type: 'HEAD'
|
type: 'HEAD'
|
||||||
check()
|
check()
|
||||||
|
|
||||||
abort: ->
|
abort: ->
|
||||||
if QR.req and !QR.req.isUploadFinished
|
if (oldReq = QR.req) and !QR.req.isUploadFinished
|
||||||
QR.req.abort()
|
|
||||||
delete QR.req
|
delete QR.req
|
||||||
|
oldReq.abort()
|
||||||
Captcha.cache.save QR.currentCaptcha if QR.currentCaptcha
|
Captcha.cache.save QR.currentCaptcha if QR.currentCaptcha
|
||||||
delete QR.currentCaptcha
|
delete QR.currentCaptcha
|
||||||
QR.posts[0].unlock()
|
QR.posts[0].unlock()
|
||||||
|
|||||||
@ -38,6 +38,14 @@ QuoteThreading =
|
|||||||
children: {}
|
children: {}
|
||||||
inserted: {}
|
inserted: {}
|
||||||
|
|
||||||
|
toggleThreading: ->
|
||||||
|
@setThreadingState !Conf['Thread Quotes']
|
||||||
|
|
||||||
|
setThreadingState: (enabled) ->
|
||||||
|
@input.checked = enabled
|
||||||
|
@setEnabled.call @input
|
||||||
|
@rethread.call @input
|
||||||
|
|
||||||
setEnabled: ->
|
setEnabled: ->
|
||||||
if @checked
|
if @checked
|
||||||
$.set 'Prune All Threads', false
|
$.set 'Prune All Threads', false
|
||||||
|
|||||||
@ -10,8 +10,8 @@ class Callbacks
|
|||||||
@keys.push name unless @[name]
|
@keys.push name unless @[name]
|
||||||
@[name] = cb
|
@[name] = cb
|
||||||
|
|
||||||
execute: (node, keys=@keys) ->
|
execute: (node, keys=@keys, force) ->
|
||||||
return if node.callbacksExecuted
|
return if node.callbacksExecuted and !force
|
||||||
node.callbacksExecuted = true
|
node.callbacksExecuted = true
|
||||||
for name in keys
|
for name in keys
|
||||||
try
|
try
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
class DataBoard
|
class DataBoard
|
||||||
@keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'customTitles']
|
@keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'watcherLastModified', 'customTitles']
|
||||||
|
|
||||||
constructor: (@key, sync, dontClean) ->
|
constructor: (@key, sync, dontClean) ->
|
||||||
@initData Conf[@key]
|
@initData Conf[@key]
|
||||||
@ -85,17 +85,20 @@ class DataBoard
|
|||||||
else
|
else
|
||||||
@data[siteID].boards[boardID] = val
|
@data[siteID].boards[boardID] = val
|
||||||
|
|
||||||
extend: ({siteID, boardID, threadID, postID, val, rm}, cb) ->
|
extend: ({siteID, boardID, threadID, postID, val}, cb) ->
|
||||||
@save =>
|
@save =>
|
||||||
oldVal = @get {siteID, boardID, threadID, postID, val: {}}
|
oldVal = @get {siteID, boardID, threadID, postID, defaultValue: {}}
|
||||||
delete oldVal[key] for key in rm or []
|
for key, subVal of val
|
||||||
$.extend oldVal, val
|
if typeof subVal is 'undefined'
|
||||||
|
delete oldVal[key]
|
||||||
|
else
|
||||||
|
oldVal[key] = subVal
|
||||||
@setUnsafe {siteID, boardID, threadID, postID, val: oldVal}
|
@setUnsafe {siteID, boardID, threadID, postID, val: oldVal}
|
||||||
, cb
|
, cb
|
||||||
|
|
||||||
setLastChecked: ->
|
setLastChecked: (key='lastChecked') ->
|
||||||
@save =>
|
@save =>
|
||||||
@data.lastChecked = Date.now()
|
@data[key] = Date.now()
|
||||||
|
|
||||||
get: ({siteID, boardID, threadID, postID, defaultValue}) ->
|
get: ({siteID, boardID, threadID, postID, defaultValue}) ->
|
||||||
siteID or= Site.hostname
|
siteID or= Site.hostname
|
||||||
@ -116,13 +119,9 @@ class DataBoard
|
|||||||
val or defaultValue
|
val or defaultValue
|
||||||
|
|
||||||
clean: ->
|
clean: ->
|
||||||
# XXX not yet multisite ready
|
|
||||||
return unless Site.software is 'yotsuba'
|
|
||||||
siteID = Site.hostname
|
siteID = Site.hostname
|
||||||
|
|
||||||
for boardID, val of @data[siteID].boards
|
for boardID, val of @data[siteID].boards
|
||||||
@deleteIfEmpty {siteID, boardID}
|
@deleteIfEmpty {siteID, boardID}
|
||||||
|
|
||||||
now = Date.now()
|
now = Date.now()
|
||||||
unless now - 2 * $.HOUR < (@data[siteID].lastChecked or 0) <= now
|
unless now - 2 * $.HOUR < (@data[siteID].lastChecked or 0) <= now
|
||||||
@data[siteID].lastChecked = now
|
@data[siteID].lastChecked = now
|
||||||
@ -131,12 +130,18 @@ class DataBoard
|
|||||||
return
|
return
|
||||||
|
|
||||||
ajaxClean: (boardID) ->
|
ajaxClean: (boardID) ->
|
||||||
$.cache "#{location.protocol}//a.4cdn.org/#{boardID}/threads.json", (e1) =>
|
that = @
|
||||||
return unless e1.target.status is 200
|
siteID = Site.hostname
|
||||||
response1 = e1.target.response
|
threadsList = Site.urls.threadsListJSON?({siteID, boardID})
|
||||||
$.cache "#{location.protocol}//a.4cdn.org/#{boardID}/archive.json", (e2) =>
|
return unless threadsList
|
||||||
return unless e2.target.status is 200 or boardID in ['b', 'f', 'trash', 'bant']
|
$.cache threadsList, ->
|
||||||
@ajaxCleanParse boardID, response1, e2.target.response
|
return unless @status is 200
|
||||||
|
archiveList = Site.urls.archiveListJSON?({siteID, boardID})
|
||||||
|
return that.ajaxCleanParse(boardID, @response) unless archiveList
|
||||||
|
response1 = @response
|
||||||
|
$.cache archiveList, ->
|
||||||
|
return unless @status is 200
|
||||||
|
that.ajaxCleanParse(boardID, response1, @response)
|
||||||
|
|
||||||
ajaxCleanParse: (boardID, response1, response2) ->
|
ajaxCleanParse: (boardID, response1, response2) ->
|
||||||
siteID = Site.hostname
|
siteID = Site.hostname
|
||||||
|
|||||||
@ -15,8 +15,9 @@ class Fetcher
|
|||||||
|
|
||||||
@root.textContent = "Loading post No.#{@postID}..."
|
@root.textContent = "Loading post No.#{@postID}..."
|
||||||
if @threadID
|
if @threadID
|
||||||
$.cache "#{location.protocol}//a.4cdn.org/#{@boardID}/thread/#{@threadID}.json", (e, isCached) =>
|
that = @
|
||||||
@fetchedPost e.target, isCached
|
$.cache Site.urls.threadJSON({boardID: @boardID, threadID: @threadID}), ({isCached}) ->
|
||||||
|
that.fetchedPost @, isCached
|
||||||
else
|
else
|
||||||
@archivedPost()
|
@archivedPost()
|
||||||
|
|
||||||
@ -60,12 +61,14 @@ class Fetcher
|
|||||||
{status} = req
|
{status} = req
|
||||||
unless status is 200
|
unless status is 200
|
||||||
# The thread can die by the time we check a quote.
|
# The thread can die by the time we check a quote.
|
||||||
return if @archivedPost()
|
return if status and @archivedPost()
|
||||||
|
|
||||||
$.addClass @root, 'warning'
|
$.addClass @root, 'warning'
|
||||||
@root.textContent =
|
@root.textContent =
|
||||||
if status is 404
|
if status is 404
|
||||||
"Thread No.#{@threadID} 404'd."
|
"Thread No.#{@threadID} 404'd."
|
||||||
|
else if !status
|
||||||
|
'Connection Error'
|
||||||
else
|
else
|
||||||
"Error #{req.statusText} (#{req.status})."
|
"Error #{req.statusText} (#{req.status})."
|
||||||
return
|
return
|
||||||
@ -78,10 +81,11 @@ class Fetcher
|
|||||||
if post.no isnt @postID
|
if post.no isnt @postID
|
||||||
# Cached requests can be stale and must be rechecked.
|
# Cached requests can be stale and must be rechecked.
|
||||||
if isCached
|
if isCached
|
||||||
api = "#{location.protocol}//a.4cdn.org/#{@boardID}/thread/#{@threadID}.json"
|
api = Site.urls.threadJSON({boardID: @boardID, threadID: @threadID})
|
||||||
$.cleanCache (url) -> url is api
|
$.cleanCache (url) -> url is api
|
||||||
$.cache api, (e) =>
|
that = @
|
||||||
@fetchedPost e.target, false
|
$.cache api, ->
|
||||||
|
that.fetchedPost @, false
|
||||||
return
|
return
|
||||||
|
|
||||||
# The post can be deleted by the time we check a quote.
|
# The post can be deleted by the time we check a quote.
|
||||||
@ -107,7 +111,7 @@ class Fetcher
|
|||||||
encryptionOK = /^https:\/\//.test(url) or location.protocol is 'http:'
|
encryptionOK = /^https:\/\//.test(url) or location.protocol is 'http:'
|
||||||
if encryptionOK or Conf['Exempt Archives from Encryption']
|
if encryptionOK or Conf['Exempt Archives from Encryption']
|
||||||
that = @
|
that = @
|
||||||
CrossOrigin.json url, ->
|
CrossOrigin.cache url, ->
|
||||||
if !encryptionOK and @response?.media
|
if !encryptionOK and @response?.media
|
||||||
{media} = @response
|
{media} = @response
|
||||||
for key of media when /_link$/.test key
|
for key of media when /_link$/.test key
|
||||||
|
|||||||
@ -9,6 +9,7 @@ class Post
|
|||||||
@ID = +root.id.match(/\d*$/)[0]
|
@ID = +root.id.match(/\d*$/)[0]
|
||||||
@threadID = @thread.ID
|
@threadID = @thread.ID
|
||||||
@boardID = @board.ID
|
@boardID = @board.ID
|
||||||
|
@siteID = Site.hostname
|
||||||
@fullID = "#{@board}.#{@ID}"
|
@fullID = "#{@board}.#{@ID}"
|
||||||
@context = @
|
@context = @
|
||||||
@isReply = (@ID isnt @threadID)
|
@isReply = (@ID isnt @threadID)
|
||||||
@ -28,6 +29,7 @@ class Post
|
|||||||
@info =
|
@info =
|
||||||
subject: @nodes.subject?.textContent or undefined
|
subject: @nodes.subject?.textContent or undefined
|
||||||
name: @nodes.name?.textContent
|
name: @nodes.name?.textContent
|
||||||
|
email: if @nodes.email then decodeURIComponent(@nodes.email.href.replace(/^mailto:/, ''))
|
||||||
tripcode: @nodes.tripcode?.textContent
|
tripcode: @nodes.tripcode?.textContent
|
||||||
uniqueID: @nodes.uniqueID?.textContent
|
uniqueID: @nodes.uniqueID?.textContent
|
||||||
capcode: @nodes.capcode?.textContent.replace '## ', ''
|
capcode: @nodes.capcode?.textContent.replace '## ', ''
|
||||||
|
|||||||
@ -19,6 +19,10 @@ Config =
|
|||||||
'Show a notice at the top of the page when the index is refreshed.'
|
'Show a notice at the top of the page when the index is refreshed.'
|
||||||
1
|
1
|
||||||
]
|
]
|
||||||
|
'Follow Cursor': [
|
||||||
|
true
|
||||||
|
'Image Hover and Quote Preview move with the mouse cursor.'
|
||||||
|
]
|
||||||
'Open Threads in New Tab': [
|
'Open Threads in New Tab': [
|
||||||
false
|
false
|
||||||
'Make links to threads in the index / <%= meta.name %> catalog open in a new tab.'
|
'Make links to threads in the index / <%= meta.name %> catalog open in a new tab.'
|
||||||
@ -628,7 +632,7 @@ Config =
|
|||||||
false
|
false
|
||||||
'Advance to next post when contracting an expanded image.'
|
'Advance to next post when contracting an expanded image.'
|
||||||
]
|
]
|
||||||
|
|
||||||
gallery:
|
gallery:
|
||||||
'Hide Thumbnails': [
|
'Hide Thumbnails': [
|
||||||
false
|
false
|
||||||
@ -672,6 +676,10 @@ Config =
|
|||||||
false
|
false
|
||||||
'Automatically remove dead threads.'
|
'Automatically remove dead threads.'
|
||||||
]
|
]
|
||||||
|
'Show Page': [
|
||||||
|
true
|
||||||
|
'Show what page watched threads are on.'
|
||||||
|
]
|
||||||
'Show Unread Count': [
|
'Show Unread Count': [
|
||||||
true
|
true
|
||||||
'Show number of unread posts in watched threads.'
|
'Show number of unread posts in watched threads.'
|
||||||
@ -720,6 +728,8 @@ Config =
|
|||||||
#/./
|
#/./
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
email: ''
|
||||||
|
|
||||||
subject: """
|
subject: """
|
||||||
# Filter Generals on /v/:
|
# Filter Generals on /v/:
|
||||||
#/general/i;boards:v;op:only
|
#/general/i;boards:v;op:only
|
||||||
@ -748,7 +758,7 @@ Config =
|
|||||||
sauces: """
|
sauces: """
|
||||||
# Known filename formats:
|
# Known filename formats:
|
||||||
http://www.pixiv.net/member_illust.php?mode=medium&illust_id=%$1;regexp:/^(\\d+)_p\\d+/
|
http://www.pixiv.net/member_illust.php?mode=medium&illust_id=%$1;regexp:/^(\\d+)_p\\d+/
|
||||||
//%$1.deviantart.com/gallery/#/d%$2;regexp:/^\\w+_by_(\\w+)-d([\\da-z]+)/
|
//www.deviantart.com/gallery/#/d%$1%$2;regexp:/^\\w+_by_\\w+[_-]d([\\da-z]{6})\\b|^d([\\da-z]{6})-[\\da-z]{8}-/
|
||||||
//imgur.com/%$1;regexp:/^(?![a-zA-Z][a-z]{6})(?![A-Z]{7})(?!\\d{7})([\\da-zA-Z]{7})(?: \\(\\d+\\))?\\.\\w+$/
|
//imgur.com/%$1;regexp:/^(?![a-zA-Z][a-z]{6})(?![A-Z]{7})(?!\\d{7})([\\da-zA-Z]{7})(?: \\(\\d+\\))?\\.\\w+$/
|
||||||
http://flickr.com/photo.gne?id=%$1;regexp:/^(\\d+)_[\\da-f]{10}(?:_\\w)*\\b/
|
http://flickr.com/photo.gne?id=%$1;regexp:/^(\\d+)_[\\da-f]{10}(?:_\\w)*\\b/
|
||||||
https://www.facebook.com/photo.php?fbid=%$1;regexp:/^\\d+_(\\d+)_\\d+_[no]\\b/
|
https://www.facebook.com/photo.php?fbid=%$1;regexp:/^\\d+_(\\d+)_\\d+_[no]\\b/
|
||||||
@ -955,6 +965,10 @@ Config =
|
|||||||
't'
|
't'
|
||||||
'Toggle visibility of thread watcher.'
|
'Toggle visibility of thread watcher.'
|
||||||
]
|
]
|
||||||
|
'Toggle threading': [
|
||||||
|
'Shift+t'
|
||||||
|
'Toggle threading.'
|
||||||
|
]
|
||||||
'Mark thread read': [
|
'Mark thread read': [
|
||||||
'Ctrl+0'
|
'Ctrl+0'
|
||||||
'Mark thread read from index (requires "Unread Line in Index").'
|
'Mark thread read from index (requires "Unread Line in Index").'
|
||||||
|
|||||||
@ -93,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Thread Watcher */
|
/* Thread Watcher */
|
||||||
:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.replies-quoting-you {
|
:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.replies-quoting-you, :root.burichan .last-page > a > .watcher-page {
|
||||||
color: #F00;
|
color: #F00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Thread Watcher */
|
/* Thread Watcher */
|
||||||
:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.replies-quoting-you {
|
:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.replies-quoting-you, :root.futaba .last-page > a > .watcher-page {
|
||||||
color: #F00;
|
color: #F00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -91,7 +91,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Thread Watcher */
|
/* Thread Watcher */
|
||||||
:root.photon .replies-quoting-you > a, :root.photon #watcher-link.replies-quoting-you {
|
:root.photon .replies-quoting-you > a, :root.photon #watcher-link.replies-quoting-you, :root.photon .last-page > a > .watcher-page {
|
||||||
color: #00F !important;
|
color: #00F !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -157,7 +157,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Thread Watcher */
|
/* Thread Watcher */
|
||||||
:root.spooky .replies-quoting-you > a, :root.spooky #watcher-link.replies-quoting-you {
|
:root.spooky .replies-quoting-you > a, :root.spooky #watcher-link.replies-quoting-you, :root.spooky .last-page > a > .watcher-page {
|
||||||
color: #F00 !important;
|
color: #F00 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,6 +129,10 @@ body.is_catalog .thread > a > img {
|
|||||||
.nwsb {
|
.nwsb {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
.fileText {
|
||||||
|
max-width: auto;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ads */
|
/* Ads */
|
||||||
.ad-cnt > *, .adg-rects > *, .bsa-cnt {
|
.ad-cnt > *, .adg-rects > *, .bsa-cnt {
|
||||||
@ -1164,12 +1168,11 @@ span.hide-announcement {
|
|||||||
-webkit-flex-direction: row;
|
-webkit-flex-direction: row;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
#watched-threads .watcher-page,
|
||||||
#watched-threads .watcher-unread {
|
#watched-threads .watcher-unread {
|
||||||
-webkit-flex: 0 0 auto;
|
-webkit-flex: 0 0 auto;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
margin-right: 2px;
|
||||||
#watched-threads .watcher-unread::after {
|
|
||||||
content: "\00a0";
|
|
||||||
}
|
}
|
||||||
#watched-threads .watcher-title {
|
#watched-threads .watcher-title {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -1177,7 +1180,10 @@ span.hide-announcement {
|
|||||||
-webkit-flex: 0 1 auto;
|
-webkit-flex: 0 1 auto;
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
}
|
}
|
||||||
.replies-quoting-you > a, #watcher-link.replies-quoting-you {
|
#watched-threads .watcher-title:not(:first-child) {
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
.replies-quoting-you > a, #watcher-link.replies-quoting-you, .last-page > a > .watcher-page {
|
||||||
color: #F00;
|
color: #F00;
|
||||||
}
|
}
|
||||||
#thread-watcher a {
|
#thread-watcher a {
|
||||||
@ -1344,6 +1350,13 @@ span.hide-announcement {
|
|||||||
.fileThumb > .warning {
|
.fileThumb > .warning {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
#ihover {
|
||||||
|
pointer-events: none;
|
||||||
|
/* XXX https://code.google.com/p/chromium/issues/detail?id=168840, https://bugs.webkit.org/show_bug.cgi?id=94158 */
|
||||||
|
max-height: 95vh;
|
||||||
|
max-height: calc(100vh - 25px);
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
/* WEBM Metadata */
|
/* WEBM Metadata */
|
||||||
.webm-title > a::before {
|
.webm-title > a::before {
|
||||||
content: "title";
|
content: "title";
|
||||||
|
|||||||
@ -162,7 +162,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Thread Watcher */
|
/* Thread Watcher */
|
||||||
:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.replies-quoting-you {
|
:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.replies-quoting-you, :root.tomorrow .last-page > a > .watcher-page {
|
||||||
color: #F00 !important;
|
color: #F00 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -88,7 +88,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Thread Watcher */
|
/* Thread Watcher */
|
||||||
:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.replies-quoting-you {
|
:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.replies-quoting-you, :root.yotsuba .last-page > a > .watcher-page {
|
||||||
color: #F00;
|
color: #F00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
Main =
|
Main =
|
||||||
init: ->
|
init: ->
|
||||||
# XXX Work around Pale Moon / old Firefox + GM 1.15 bug where script runs in iframe with wrong window.location.
|
|
||||||
return if d.body and not $ 'title', d.head
|
|
||||||
|
|
||||||
# XXX dwb userscripts extension reloads scripts run at document-start when replaceState/pushState is called.
|
# XXX dwb userscripts extension reloads scripts run at document-start when replaceState/pushState is called.
|
||||||
# XXX Firefox reinjects WebExtension content scripts when extension is updated / reloaded.
|
# XXX Firefox reinjects WebExtension content scripts when extension is updated / reloaded.
|
||||||
try
|
try
|
||||||
|
|||||||
@ -25,16 +25,14 @@ handlers =
|
|||||||
xhr.open 'GET', request.url, true
|
xhr.open 'GET', request.url, true
|
||||||
xhr.responseType = request.responseType
|
xhr.responseType = request.responseType
|
||||||
xhr.timeout = request.timeout
|
xhr.timeout = request.timeout
|
||||||
|
for key, value of (request.headers or {})
|
||||||
|
xhr.setRequestHeader key, value
|
||||||
xhr.addEventListener 'load', ->
|
xhr.addEventListener 'load', ->
|
||||||
{status, statusText, response} = @
|
{status, statusText, response} = @
|
||||||
if @readyState is @DONE && xhr.status is 200
|
responseHeaderString = @getAllResponseHeaders()
|
||||||
if request.responseType is 'arraybuffer'
|
if response and request.responseType is 'arraybuffer'
|
||||||
response = [new Uint8Array(response)...]
|
response = [new Uint8Array(response)...]
|
||||||
contentType = @getResponseHeader 'Content-Type'
|
cb {status, statusText, response, responseHeaderString}
|
||||||
contentDisposition = @getResponseHeader 'Content-Disposition'
|
|
||||||
cb {status, statusText, response, contentType, contentDisposition}
|
|
||||||
else
|
|
||||||
cb {status, statusText, response, error: true}
|
|
||||||
, false
|
, false
|
||||||
xhr.addEventListener 'error', ->
|
xhr.addEventListener 'error', ->
|
||||||
cb {error: true}
|
cb {error: true}
|
||||||
|
|||||||
@ -41,67 +41,83 @@ $.extend = (object, properties) ->
|
|||||||
return
|
return
|
||||||
|
|
||||||
$.ajax = do ->
|
$.ajax = do ->
|
||||||
# Status Code 304: Not modified
|
|
||||||
# With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses.
|
|
||||||
# This saves a lot of bandwidth and CPU time for both the users and the servers.
|
|
||||||
lastModified = {}
|
|
||||||
if window.wrappedJSObject and not XMLHttpRequest.wrappedJSObject
|
if window.wrappedJSObject and not XMLHttpRequest.wrappedJSObject
|
||||||
pageXHR = XPCNativeWrapper window.wrappedJSObject.XMLHttpRequest
|
pageXHR = XPCNativeWrapper window.wrappedJSObject.XMLHttpRequest
|
||||||
else
|
else
|
||||||
pageXHR = XMLHttpRequest
|
pageXHR = XMLHttpRequest
|
||||||
|
|
||||||
(url, options={}, extra={}) ->
|
(url, options={}) ->
|
||||||
{type, whenModified, bypassCache, upCallbacks, form} = extra
|
{onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers} = options
|
||||||
options.responseType ?= 'json' if /\.json$/.test url
|
responseType ?= 'json'
|
||||||
# XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
|
# XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
|
||||||
url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'
|
url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'
|
||||||
if whenModified
|
|
||||||
params = []
|
|
||||||
# XXX https://bugs.chromium.org/p/chromium/issues/detail?id=643659
|
|
||||||
params.push "s=#{whenModified}" if $.engine is 'blink'
|
|
||||||
params.push "t=#{Date.now()}" if Site.software is 'yotsuba' and bypassCache
|
|
||||||
url0 = url
|
|
||||||
url += '?' + params.join('&') if params.length
|
|
||||||
r = new pageXHR()
|
r = new pageXHR()
|
||||||
type or= form and 'post' or 'get'
|
type or= form and 'post' or 'get'
|
||||||
try
|
try
|
||||||
r.open type, url, true
|
r.open type, url, true
|
||||||
if whenModified
|
for key, value of (headers or {})
|
||||||
r.setRequestHeader 'If-Modified-Since', lastModified[whenModified][url0] if lastModified[whenModified]?[url0]?
|
r.setRequestHeader key, value
|
||||||
$.on r, 'load', -> (lastModified[whenModified] or= {})[url0] = r.getResponseHeader 'Last-Modified'
|
$.extend r, {onloadend, timeout, responseType, withCredentials}
|
||||||
$.extend r, options
|
$.extend r.upload, {onprogress}
|
||||||
$.extend r.upload, upCallbacks
|
|
||||||
# connection error or content blocker
|
# connection error or content blocker
|
||||||
$.on r, 'error', -> (c.error "4chan X failed to load: #{url}" unless r.status)
|
$.on r, 'error', -> (c.warn "4chan X failed to load: #{url}" unless r.status)
|
||||||
|
<% if (type === 'crx') { %>
|
||||||
|
# XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638
|
||||||
|
$.on r, 'load', ->
|
||||||
|
return unless r.readyState is 4 and r.status is 200 and r.statusText is '' and r.response is null and !$.ajaxWarningShown
|
||||||
|
new Notice 'warning', "Error loading #{url}; try going to chrome://flags/#network-service and disabling the network service flag."
|
||||||
|
$.ajaxWarningShown = true
|
||||||
|
<% } %>
|
||||||
r.send form
|
r.send form
|
||||||
catch err
|
catch err
|
||||||
# XXX Some content blockers in Firefox (e.g. Adblock Plus and NoScript) throw an exception instead of simulating a connection error.
|
# XXX Some content blockers in Firefox (e.g. Adblock Plus and NoScript) throw an exception instead of simulating a connection error.
|
||||||
throw err unless err.result is 0x805e0006
|
throw err unless err.result is 0x805e0006
|
||||||
for event in ['error', 'loadend']
|
r.onloadend = onloadend
|
||||||
r["on#{event}"] = options["on#{event}"]
|
$.queueTask $.event, 'error', null, r
|
||||||
$.queueTask $.event, event, null, r
|
$.queueTask $.event, 'loadend', null, r
|
||||||
r
|
r
|
||||||
|
|
||||||
|
# Status Code 304: Not modified
|
||||||
|
# With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses.
|
||||||
|
# This saves a lot of bandwidth and CPU time for both the users and the servers.
|
||||||
|
$.lastModified = {}
|
||||||
|
$.whenModified = (url, bucket, cb, options={}) ->
|
||||||
|
{timeout, ajax} = options
|
||||||
|
params = []
|
||||||
|
# XXX https://bugs.chromium.org/p/chromium/issues/detail?id=643659
|
||||||
|
params.push "s=#{bucket}" if $.engine is 'blink'
|
||||||
|
params.push "t=#{Date.now()}" if url.split('/')[2] is 'a.4cdn.org'
|
||||||
|
url0 = url
|
||||||
|
url += '?' + params.join('&') if params.length
|
||||||
|
headers = {}
|
||||||
|
if (t = $.lastModified[bucket]?[url0])?
|
||||||
|
headers['If-Modified-Since'] = t
|
||||||
|
r = (ajax or $.ajax) url, {
|
||||||
|
onloadend: ->
|
||||||
|
($.lastModified[bucket] or= {})[url0] = @getResponseHeader('Last-Modified')
|
||||||
|
cb.call @
|
||||||
|
timeout
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
r
|
||||||
|
|
||||||
do ->
|
do ->
|
||||||
reqs = {}
|
reqs = {}
|
||||||
$.cache = (url, cb, options) ->
|
$.cache = (url, cb, options={}) ->
|
||||||
if req = reqs[url]
|
{ajax} = options
|
||||||
if req.readyState is 4
|
if (req = reqs[url])
|
||||||
$.queueTask -> cb.call req, req.evt, true
|
if req.callbacks
|
||||||
else
|
|
||||||
req.callbacks.push cb
|
req.callbacks.push cb
|
||||||
|
else
|
||||||
|
$.queueTask -> cb.call req, {isCached: true}
|
||||||
return req
|
return req
|
||||||
rm = -> delete reqs[url]
|
onloadend = ->
|
||||||
try
|
unless @status
|
||||||
return if not (req = $.ajax url, options)
|
delete reqs[url]
|
||||||
catch err
|
|
||||||
return
|
|
||||||
$.on req, 'load', (e) ->
|
|
||||||
@evt = e
|
|
||||||
for cb in @callbacks
|
for cb in @callbacks
|
||||||
do (cb) => $.queueTask => cb.call @, e, false
|
do (cb) => $.queueTask => cb.call @, {isCached: false}
|
||||||
delete @callbacks
|
delete @callbacks
|
||||||
$.on req, 'abort error', rm
|
req = (ajax or $.ajax) url, {onloadend}
|
||||||
req.callbacks = [cb]
|
req.callbacks = [cb]
|
||||||
reqs[url] = req
|
reqs[url] = req
|
||||||
$.cleanCache = (testf) ->
|
$.cleanCache = (testf) ->
|
||||||
@ -413,16 +429,16 @@ $.sync = (key, cb) ->
|
|||||||
$.forceSync = -> return
|
$.forceSync = -> return
|
||||||
|
|
||||||
$.crxWorking = ->
|
$.crxWorking = ->
|
||||||
if chrome.runtime.getManifest()
|
try
|
||||||
true
|
if chrome.runtime.getManifest()
|
||||||
else
|
return true
|
||||||
unless $.crxWarningShown
|
unless $.crxWarningShown
|
||||||
msg = $.el 'div',
|
msg = $.el 'div',
|
||||||
<%= html('4chan X seems to have been updated. You will need to <a href="javascript:;">reload</a> the page.') %>
|
<%= html('4chan X seems to have been updated. You will need to <a href="javascript:;">reload</a> the page.') %>
|
||||||
$.on $('a', msg), 'click', -> location.reload()
|
$.on $('a', msg), 'click', -> location.reload()
|
||||||
new Notice 'warning', msg
|
new Notice 'warning', msg
|
||||||
$.crxWarningShown = true
|
$.crxWarningShown = true
|
||||||
false
|
false
|
||||||
|
|
||||||
$.get = $.oneItemSugar (data, cb) ->
|
$.get = $.oneItemSugar (data, cb) ->
|
||||||
return unless $.crxWorking()
|
return unless $.crxWorking()
|
||||||
|
|||||||
@ -14,123 +14,131 @@ CrossOrigin =
|
|||||||
# XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
|
# XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
|
||||||
url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'
|
url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'
|
||||||
<% if (type === 'crx') { %>
|
<% if (type === 'crx') { %>
|
||||||
eventPageRequest {type: 'ajax', url, responseType: 'arraybuffer'}, ({response, contentType, contentDisposition, error}) ->
|
eventPageRequest {type: 'ajax', url, headers, responseType: 'arraybuffer'}, ({response, responseHeaderString}) ->
|
||||||
return cb null if error
|
response = new Uint8Array(response) if response
|
||||||
cb new Uint8Array(response), contentType, contentDisposition
|
cb response, responseHeaderString
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (type === 'userscript') { %>
|
<% if (type === 'userscript') { %>
|
||||||
# Use workaround for binary data in Greasemonkey versions < 3.2, in Pale Moon for all GM versions, and in JS Blocker (Safari).
|
(GM?.xmlHttpRequest or GM_xmlhttpRequest)
|
||||||
workaround = $.engine is 'gecko' and GM_info? and /^[0-2]\.|^3\.[01](?!\d)/.test(GM_info.version)
|
|
||||||
workaround or= /PaleMoon\//.test(navigator.userAgent)
|
|
||||||
workaround or= GM_info?.script?.includeJSB?
|
|
||||||
options =
|
|
||||||
method: "GET"
|
method: "GET"
|
||||||
url: url
|
url: url
|
||||||
headers: headers
|
headers: headers
|
||||||
|
responseType: 'arraybuffer'
|
||||||
|
overrideMimeType: 'text/plain; charset=x-user-defined'
|
||||||
onload: (xhr) ->
|
onload: (xhr) ->
|
||||||
if workaround
|
if xhr.response instanceof ArrayBuffer
|
||||||
|
data = new Uint8Array xhr.response
|
||||||
|
else
|
||||||
r = xhr.responseText
|
r = xhr.responseText
|
||||||
data = new Uint8Array r.length
|
data = new Uint8Array r.length
|
||||||
i = 0
|
i = 0
|
||||||
while i < r.length
|
while i < r.length
|
||||||
data[i] = r.charCodeAt i
|
data[i] = r.charCodeAt i
|
||||||
i++
|
i++
|
||||||
else
|
cb data, xhr.responseHeaders
|
||||||
data = new Uint8Array xhr.response
|
|
||||||
contentType = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)?[1]
|
|
||||||
contentDisposition = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)?[1]
|
|
||||||
cb data, contentType, contentDisposition
|
|
||||||
onerror: ->
|
onerror: ->
|
||||||
cb null
|
cb null
|
||||||
onabort: ->
|
onabort: ->
|
||||||
cb null
|
cb null
|
||||||
if workaround
|
|
||||||
options.overrideMimeType = 'text/plain; charset=x-user-defined'
|
|
||||||
else
|
|
||||||
options.responseType = 'arraybuffer'
|
|
||||||
(GM?.xmlHttpRequest or GM_xmlhttpRequest) options
|
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
file: (url, cb) ->
|
file: (url, cb) ->
|
||||||
CrossOrigin.binary url, (data, contentType, contentDisposition) ->
|
CrossOrigin.binary url, (data, headers) ->
|
||||||
return cb null unless data?
|
return cb null unless data?
|
||||||
name = url.match(/([^\/]+)\/*$/)?[1]
|
name = url.match(/([^\/?#]+)\/*(?:$|[?#])/)?[1]
|
||||||
|
contentType = headers.match(/Content-Type:\s*(.*)/i)?[1]
|
||||||
|
contentDisposition = headers.match(/Content-Disposition:\s*(.*)/i)?[1]
|
||||||
mime = contentType?.match(/[^;]*/)[0] or 'application/octet-stream'
|
mime = contentType?.match(/[^;]*/)[0] or 'application/octet-stream'
|
||||||
match =
|
match =
|
||||||
contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?[1] or
|
contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?[1] or
|
||||||
contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?[1]
|
contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?[1]
|
||||||
if match
|
if match
|
||||||
name = match.replace /\\"/g, '"'
|
name = match.replace /\\"/g, '"'
|
||||||
if GM_info?.script?.includeJSB?
|
if /^text\/plain;\s*charset=x-user-defined$/i.test(mime)
|
||||||
# Content type comes back as 'text/plain; charset=x-user-defined'; guess from filename instead.
|
# In JS Blocker (Safari) content type comes back as 'text/plain; charset=x-user-defined'; guess from filename instead.
|
||||||
mime = QR.typeFromExtension[name.match(/[^.]*$/)[0].toLowerCase()] or 'application/octet-stream'
|
mime = QR.typeFromExtension[name.match(/[^.]*$/)[0].toLowerCase()] or 'application/octet-stream'
|
||||||
blob = new Blob([data], {type: mime})
|
blob = new Blob([data], {type: mime})
|
||||||
blob.name = name
|
blob.name = name
|
||||||
cb blob
|
cb blob
|
||||||
|
|
||||||
|
Request: class Request
|
||||||
|
status: 0
|
||||||
|
statusText: ''
|
||||||
|
response: null
|
||||||
|
responseHeaderString: null
|
||||||
|
getResponseHeader: (headerName) ->
|
||||||
|
if !@responseHeaders? and @responseHeaderString?
|
||||||
|
@responseHeaders = {}
|
||||||
|
for header in @responseHeaderString.split('\r\n')
|
||||||
|
if (i = header.indexOf(':')) >= 0
|
||||||
|
key = header[...i].trim().toLowerCase()
|
||||||
|
val = header[i+1..].trim()
|
||||||
|
@responseHeaders[key] = val
|
||||||
|
(@responseHeaders or {})[headerName.toLowerCase()] ? null
|
||||||
|
abort: ->
|
||||||
|
onloadend: ->
|
||||||
|
|
||||||
# Attempts to fetch `url` in JSON format using cross-origin privileges, if available.
|
# Attempts to fetch `url` in JSON format using cross-origin privileges, if available.
|
||||||
# On success, calls `cb` with a `this` containing properties `status`, `statusText`, `response` and caches result.
|
# Interface is a subset of that of $.ajax.
|
||||||
# On error/abort, calls `cb` with a `this` of `{}`.
|
# Options:
|
||||||
# If `bypassCache` is true, ignores previously cached results.
|
# `onloadend` - called with the returned object as `this` on success or error/abort/timeout.
|
||||||
json: do ->
|
# `timeout` - time limit for request
|
||||||
callbacks = {}
|
# `headers` - request headers
|
||||||
results = {}
|
# Returned object properties:
|
||||||
success = (url, result) ->
|
# `status` - HTTP status (0 if connection not successful)
|
||||||
for cb in callbacks[url]
|
# `statusText` - HTTP status text
|
||||||
$.queueTask -> cb.call result
|
# `response` - decoded response body
|
||||||
delete callbacks[url]
|
# `abort` - function for aborting the request (silently fails on some platforms)
|
||||||
results[url] = result
|
# `getResponseHeader` - function for reading response headers
|
||||||
failure = (url) ->
|
ajax: (url, options={}) ->
|
||||||
for cb in callbacks[url]
|
{onloadend, timeout, headers} = options
|
||||||
$.queueTask -> cb.call {}
|
|
||||||
delete callbacks[url]
|
|
||||||
|
|
||||||
(url, cb, bypassCache, timeout) ->
|
<% if (type === 'userscript') { %>
|
||||||
<% if (type === 'userscript') { %>
|
unless GM?.xmlHttpRequest? or GM_xmlhttpRequest?
|
||||||
unless GM?.xmlHttpRequest? or GM_xmlhttpRequest?
|
return $.ajax url, options
|
||||||
if bypassCache
|
<% } %>
|
||||||
$.cleanCache (url2) -> url2 is url
|
|
||||||
if (req = $.cache url, cb, responseType: 'json')
|
|
||||||
$.on req, 'abort error', -> cb.call({})
|
|
||||||
else
|
|
||||||
cb.call {}
|
|
||||||
return
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
if bypassCache
|
req = new CrossOrigin.Request()
|
||||||
delete results[url]
|
req.onloadend = onloadend
|
||||||
else
|
|
||||||
if results[url]
|
|
||||||
cb.call results[url]
|
|
||||||
return
|
|
||||||
if callbacks[url]
|
|
||||||
callbacks[url].push cb
|
|
||||||
return
|
|
||||||
callbacks[url] = [cb]
|
|
||||||
|
|
||||||
<% if (type === 'userscript') { %>
|
<% if (type === 'userscript') { %>
|
||||||
(GM?.xmlHttpRequest or GM_xmlhttpRequest)
|
gmReq = (GM?.xmlHttpRequest or GM_xmlhttpRequest) {
|
||||||
method: "GET"
|
method: 'GET'
|
||||||
url: url+''
|
url
|
||||||
timeout: timeout
|
headers
|
||||||
onload: (xhr) ->
|
timeout
|
||||||
{status, statusText} = xhr
|
onload: (xhr) ->
|
||||||
try
|
try
|
||||||
response = JSON.parse(xhr.responseText)
|
response = if xhr.responseText then JSON.parse(xhr.responseText) else null
|
||||||
success url, {status, statusText, response}
|
$.extend req, {
|
||||||
catch
|
response
|
||||||
failure url
|
status: xhr.status
|
||||||
onerror: -> failure(url)
|
statusText: xhr.statusText
|
||||||
onabort: -> failure(url)
|
responseHeaderString: xhr.responseHeaders
|
||||||
ontimeout: -> failure(url)
|
}
|
||||||
<% } %>
|
req.onloadend()
|
||||||
<% if (type === 'crx') { %>
|
onerror: -> req.onloadend()
|
||||||
eventPageRequest {type: 'ajax', url, responseType: 'json', timeout}, (result) ->
|
onabort: -> req.onloadend()
|
||||||
if result.status
|
ontimeout: -> req.onloadend()
|
||||||
success url, result
|
}
|
||||||
else
|
if gmReq and typeof gmReq.abort is 'function'
|
||||||
failure url
|
req.abort = ->
|
||||||
<% } %>
|
try
|
||||||
|
gmReq.abort()
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<% if (type === 'crx') { %>
|
||||||
|
eventPageRequest {type: 'ajax', url, responseType: 'json', headers, timeout}, (result) ->
|
||||||
|
if result.status
|
||||||
|
$.extend req, result
|
||||||
|
req.onloadend()
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
req
|
||||||
|
|
||||||
|
cache: (url, cb) ->
|
||||||
|
$.cache url, cb,
|
||||||
|
ajax: CrossOrigin.ajax
|
||||||
|
|
||||||
permission: (cb) ->
|
permission: (cb) ->
|
||||||
<% if (type === 'crx') { %>
|
<% if (type === 'crx') { %>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ SW.tinyboard =
|
|||||||
'Image Host Rewriting'
|
'Image Host Rewriting'
|
||||||
'Index Generator'
|
'Index Generator'
|
||||||
'Announcement Hiding'
|
'Announcement Hiding'
|
||||||
'Fourchan thingies'
|
|
||||||
'Resurrect Quotes'
|
'Resurrect Quotes'
|
||||||
'Quick Reply Personas'
|
'Quick Reply Personas'
|
||||||
'Quick Reply'
|
'Quick Reply'
|
||||||
@ -60,6 +59,12 @@ SW.tinyboard =
|
|||||||
threadJSON: ({siteID, boardID, threadID}) ->
|
threadJSON: ({siteID, boardID, threadID}) ->
|
||||||
root = Conf['siteProperties'][siteID]?.root
|
root = Conf['siteProperties'][siteID]?.root
|
||||||
if root then "#{root}#{boardID}/res/#{threadID}.json" else ''
|
if root then "#{root}#{boardID}/res/#{threadID}.json" else ''
|
||||||
|
threadsListJSON: ({siteID, boardID}) ->
|
||||||
|
root = Conf['siteProperties'][siteID]?.root
|
||||||
|
if root then "#{root}#{boardID}/threads.json" else ''
|
||||||
|
catalogJSON: ({siteID, boardID}) ->
|
||||||
|
root = Conf['siteProperties'][siteID]?.root
|
||||||
|
if root then "#{root}#{boardID}/catalog.json" else ''
|
||||||
|
|
||||||
selectors:
|
selectors:
|
||||||
board: 'form[name="postcontrols"]'
|
board: 'form[name="postcontrols"]'
|
||||||
|
|||||||
@ -4,6 +4,11 @@ SW.yotsuba =
|
|||||||
urls:
|
urls:
|
||||||
thread: ({boardID, threadID}) -> "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/thread/#{threadID}"
|
thread: ({boardID, threadID}) -> "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/thread/#{threadID}"
|
||||||
threadJSON: ({boardID, threadID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/thread/#{threadID}.json"
|
threadJSON: ({boardID, threadID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/thread/#{threadID}.json"
|
||||||
|
threadsListJSON: ({boardID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/threads.json"
|
||||||
|
archiveListJSON: ({boardID}) -> if BoardConfig.isArchived(boardID) then "#{location.protocol}//a.4cdn.org/#{boardID}/archive.json" else ''
|
||||||
|
catalogJSON: ({boardID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/catalog.json"
|
||||||
|
|
||||||
|
isPrunedByAge: ({boardID}) -> boardID is 'f'
|
||||||
|
|
||||||
selectors:
|
selectors:
|
||||||
board: '.board'
|
board: '.board'
|
||||||
@ -101,7 +106,7 @@ SW.yotsuba =
|
|||||||
|
|
||||||
if g.BOARD.ID is 'f' and thread.OP.file
|
if g.BOARD.ID is 'f' and thread.OP.file
|
||||||
{file} = thread.OP
|
{file} = thread.OP
|
||||||
$.ajax "#{location.protocol}//a.4cdn.org/f/thread/#{thread}.json",
|
$.ajax Site.urls.threadJSON({boardID: 'f', threadID: thread.ID}),
|
||||||
timeout: $.MINUTE
|
timeout: $.MINUTE
|
||||||
onloadend: ->
|
onloadend: ->
|
||||||
if @response
|
if @response
|
||||||
@ -152,3 +157,6 @@ SW.yotsuba =
|
|||||||
|
|
||||||
hasCORS: (url) ->
|
hasCORS: (url) ->
|
||||||
url.split('/')[...3].join('/') is location.protocol + '//a.4cdn.org'
|
url.split('/')[...3].join('/') is location.protocol + '//a.4cdn.org'
|
||||||
|
|
||||||
|
sfwBoards: (sfw) ->
|
||||||
|
BoardConfig.sfwBoards(sfw)
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
Site =
|
Site =
|
||||||
defaultProperties:
|
defaultProperties:
|
||||||
'4chan.org': {software: 'yotsuba'}
|
'4chan.org': {software: 'yotsuba'}
|
||||||
'4channel.org': {software: 'yotsuba'}
|
'4channel.org': {canonical: '4chan.org'}
|
||||||
'4cdn.org': {software: 'yotsuba'}
|
'4cdn.org': {canonical: '4chan.org'}
|
||||||
|
|
||||||
init: (cb) ->
|
init: (cb) ->
|
||||||
$.extend Conf['siteProperties'], Site.defaultProperties
|
$.extend Conf['siteProperties'], Site.defaultProperties
|
||||||
{hostname} = location
|
{hostname} = location
|
||||||
while hostname and hostname not of Conf['siteProperties']
|
while hostname and hostname not of Conf['siteProperties']
|
||||||
hostname = hostname.replace(/^[^.]*\.?/, '')
|
hostname = hostname.replace(/^[^.]*\.?/, '')
|
||||||
if hostname and Conf['siteProperties'][hostname].software of SW
|
if hostname
|
||||||
@set hostname
|
hostname = canonical if (canonical = Conf['siteProperties'][hostname].canonical)
|
||||||
cb()
|
if Conf['siteProperties'][hostname].software of SW
|
||||||
|
@set hostname
|
||||||
|
cb()
|
||||||
$.onExists doc, 'body', =>
|
$.onExists doc, 'body', =>
|
||||||
for software of SW when (changes = SW[software].detect?())
|
for software of SW when (changes = SW[software].detect?())
|
||||||
changes.software = software
|
changes.software = software
|
||||||
@ -32,5 +34,4 @@ Site =
|
|||||||
set: (@hostname) ->
|
set: (@hostname) ->
|
||||||
@properties = Conf['siteProperties'][@hostname]
|
@properties = Conf['siteProperties'][@hostname]
|
||||||
@software = @properties.software
|
@software = @properties.software
|
||||||
@hostname = '4chan.org' if @software is 'yotsuba'
|
|
||||||
$.extend @, SW[@software]
|
$.extend @, SW[@software]
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
var fs = require('fs');
|
|
||||||
var crypto = require('crypto');
|
|
||||||
var RSA = require('node-rsa');
|
|
||||||
|
|
||||||
var pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
||||||
var channel = process.argv[2] || '';
|
|
||||||
|
|
||||||
var privateKey = fs.readFileSync(`../${pkg.meta.path}.keys/${pkg.name}.pem`);
|
|
||||||
var archive = fs.readFileSync(`testbuilds/${pkg.name}${channel}.crx.zip`);
|
|
||||||
|
|
||||||
// https://developer.chrome.com/extensions/crx
|
|
||||||
|
|
||||||
var publicKey = new RSA(privateKey).exportKey('pkcs8-public-der');
|
|
||||||
var signature = crypto.createSign('sha1').update(archive).sign(privateKey);
|
|
||||||
|
|
||||||
var header = Buffer.alloc(16);
|
|
||||||
header.write('Cr24');
|
|
||||||
header.writeInt32LE(2, 4);
|
|
||||||
header.writeInt32LE(publicKey.length, 8);
|
|
||||||
header.writeInt32LE(signature.length, 12);
|
|
||||||
|
|
||||||
var crx = Buffer.concat([header, publicKey, signature, archive]);
|
|
||||||
|
|
||||||
fs.writeFileSync(`testbuilds/${pkg.name}${channel}.crx`, crx);
|
|
||||||
8
tools/sign.sh
Executable file
8
tools/sign.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
channel=$1
|
||||||
|
mkdir -p tmp-crx
|
||||||
|
cp -r "testbuilds/crx$channel" "tmp-crx/crx$channel"
|
||||||
|
touch -d "$(jq -r '.date' version.json)" "tmp-crx/crx$channel"/*
|
||||||
|
chromium --pack-extension="tmp-crx/crx$channel" --pack-extension-key="$(dirname "$PWD")/4chan-x.keys/4chan-X.pem"
|
||||||
|
mv "tmp-crx/crx$channel.crx" "testbuilds/4chan-X$channel.crx"
|
||||||
|
rm -r 'tmp-crx/'
|
||||||
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"version": "1.14.5.13",
|
"version": "1.14.7.2",
|
||||||
"date": "2019-03-08T23:32:11.908Z"
|
"date": "2019-04-11T15:38:53.367Z"
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user